aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/sound/soc/sof
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/sof')
-rw-r--r--sound/soc/sof/Kconfig174
-rw-r--r--sound/soc/sof/Makefile48
-rw-r--r--sound/soc/sof/amd/Kconfig90
-rw-r--r--sound/soc/sof/amd/Makefile18
-rw-r--r--sound/soc/sof/amd/acp-common.c265
-rw-r--r--sound/soc/sof/amd/acp-dsp-offset.h109
-rw-r--r--sound/soc/sof/amd/acp-ipc.c301
-rw-r--r--sound/soc/sof/amd/acp-loader.c315
-rw-r--r--sound/soc/sof/amd/acp-pcm.c120
-rw-r--r--sound/soc/sof/amd/acp-probes.c147
-rw-r--r--sound/soc/sof/amd/acp-stream.c187
-rw-r--r--sound/soc/sof/amd/acp-trace.c64
-rw-r--r--sound/soc/sof/amd/acp.c803
-rw-r--r--sound/soc/sof/amd/acp.h347
-rw-r--r--sound/soc/sof/amd/acp63.c146
-rw-r--r--sound/soc/sof/amd/pci-acp63.c113
-rw-r--r--sound/soc/sof/amd/pci-rmb.c103
-rw-r--r--sound/soc/sof/amd/pci-rn.c107
-rw-r--r--sound/soc/sof/amd/pci-vangogh.c105
-rw-r--r--sound/soc/sof/amd/rembrandt.c146
-rw-r--r--sound/soc/sof/amd/renoir.c121
-rw-r--r--sound/soc/sof/amd/vangogh.c162
-rw-r--r--sound/soc/sof/compress.c416
-rw-r--r--sound/soc/sof/compress.h32
-rw-r--r--sound/soc/sof/control.c395
-rw-r--r--sound/soc/sof/core.c593
-rw-r--r--sound/soc/sof/debug.c687
-rw-r--r--sound/soc/sof/fw-file-profile.c334
-rw-r--r--sound/soc/sof/imx/Kconfig52
-rw-r--r--sound/soc/sof/imx/Makefile5
-rw-r--r--sound/soc/sof/imx/imx-common.c101
-rw-r--r--sound/soc/sof/imx/imx-common.h27
-rw-r--r--sound/soc/sof/imx/imx8.c332
-rw-r--r--sound/soc/sof/imx/imx8m.c362
-rw-r--r--sound/soc/sof/imx/imx8ulp.c524
-rw-r--r--sound/soc/sof/intel/Kconfig366
-rw-r--r--sound/soc/sof/intel/Makefile44
-rw-r--r--sound/soc/sof/intel/apl.c166
-rw-r--r--sound/soc/sof/intel/atom.c420
-rw-r--r--sound/soc/sof/intel/atom.h74
-rw-r--r--sound/soc/sof/intel/bdw.c183
-rw-r--r--sound/soc/sof/intel/byt.c958
-rw-r--r--sound/soc/sof/intel/cnl.c425
-rw-r--r--sound/soc/sof/intel/ext_manifest.h35
-rw-r--r--sound/soc/sof/intel/hda-bus.c74
-rw-r--r--sound/soc/sof/intel/hda-codec.c328
-rw-r--r--sound/soc/sof/intel/hda-common-ops.c104
-rw-r--r--sound/soc/sof/intel/hda-compress.c114
-rw-r--r--sound/soc/sof/intel/hda-ctrl.c114
-rw-r--r--sound/soc/sof/intel/hda-dai-ops.c687
-rw-r--r--sound/soc/sof/intel/hda-dai.c868
-rw-r--r--sound/soc/sof/intel/hda-dsp.c559
-rw-r--r--sound/soc/sof/intel/hda-ipc.c249
-rw-r--r--sound/soc/sof/intel/hda-ipc.h5
-rw-r--r--sound/soc/sof/intel/hda-loader-skl.c578
-rw-r--r--sound/soc/sof/intel/hda-loader.c554
-rw-r--r--sound/soc/sof/intel/hda-mlink.c974
-rw-r--r--sound/soc/sof/intel/hda-pcm.c174
-rw-r--r--sound/soc/sof/intel/hda-probes.c148
-rw-r--r--sound/soc/sof/intel/hda-stream.c701
-rw-r--r--sound/soc/sof/intel/hda-trace.c31
-rw-r--r--sound/soc/sof/intel/hda.c1421
-rw-r--r--sound/soc/sof/intel/hda.h497
-rw-r--r--sound/soc/sof/intel/icl.c198
-rw-r--r--sound/soc/sof/intel/intel-ipc.c92
-rw-r--r--sound/soc/sof/intel/lnl.c210
-rw-r--r--sound/soc/sof/intel/mtl.c789
-rw-r--r--sound/soc/sof/intel/mtl.h111
-rw-r--r--sound/soc/sof/intel/pci-apl.c109
-rw-r--r--sound/soc/sof/intel/pci-cnl.c147
-rw-r--r--sound/soc/sof/intel/pci-icl.c112
-rw-r--r--sound/soc/sof/intel/pci-lnl.c71
-rw-r--r--sound/soc/sof/intel/pci-mtl.c137
-rw-r--r--sound/soc/sof/intel/pci-skl.c93
-rw-r--r--sound/soc/sof/intel/pci-tgl.c321
-rw-r--r--sound/soc/sof/intel/pci-tng.c250
-rw-r--r--sound/soc/sof/intel/shim.h47
-rw-r--r--sound/soc/sof/intel/skl.c117
-rw-r--r--sound/soc/sof/intel/telemetry.c95
-rw-r--r--sound/soc/sof/intel/telemetry.h35
-rw-r--r--sound/soc/sof/intel/tgl.c247
-rw-r--r--sound/soc/sof/iomem-utils.c127
-rw-r--r--sound/soc/sof/ipc.c837
-rw-r--r--sound/soc/sof/ipc3-control.c731
-rw-r--r--sound/soc/sof/ipc3-dtrace.c665
-rw-r--r--sound/soc/sof/ipc3-loader.c416
-rw-r--r--sound/soc/sof/ipc3-pcm.c437
-rw-r--r--sound/soc/sof/ipc3-priv.h67
-rw-r--r--sound/soc/sof/ipc3-topology.c2700
-rw-r--r--sound/soc/sof/ipc3.c1161
-rw-r--r--sound/soc/sof/ipc4-control.c858
-rw-r--r--sound/soc/sof/ipc4-fw-reg.h155
-rw-r--r--sound/soc/sof/ipc4-loader.c494
-rw-r--r--sound/soc/sof/ipc4-mtrace.c669
-rw-r--r--sound/soc/sof/ipc4-pcm.c947
-rw-r--r--sound/soc/sof/ipc4-priv.h134
-rw-r--r--sound/soc/sof/ipc4-telemetry.c95
-rw-r--r--sound/soc/sof/ipc4-telemetry.h73
-rw-r--r--sound/soc/sof/ipc4-topology.c3127
-rw-r--r--sound/soc/sof/ipc4-topology.h481
-rw-r--r--sound/soc/sof/ipc4.c847
-rw-r--r--sound/soc/sof/loader.c685
-rw-r--r--sound/soc/sof/mediatek/Kconfig45
-rw-r--r--sound/soc/sof/mediatek/Makefile4
-rw-r--r--sound/soc/sof/mediatek/adsp_helper.h50
-rw-r--r--sound/soc/sof/mediatek/mt8186/Makefile4
-rw-r--r--sound/soc/sof/mediatek/mt8186/mt8186-clk.c100
-rw-r--r--sound/soc/sof/mediatek/mt8186/mt8186-clk.h24
-rw-r--r--sound/soc/sof/mediatek/mt8186/mt8186-loader.c58
-rw-r--r--sound/soc/sof/mediatek/mt8186/mt8186.c671
-rw-r--r--sound/soc/sof/mediatek/mt8186/mt8186.h93
-rw-r--r--sound/soc/sof/mediatek/mt8195/Makefile3
-rw-r--r--sound/soc/sof/mediatek/mt8195/mt8195-clk.c164
-rw-r--r--sound/soc/sof/mediatek/mt8195/mt8195-clk.h28
-rw-r--r--sound/soc/sof/mediatek/mt8195/mt8195-loader.c61
-rw-r--r--sound/soc/sof/mediatek/mt8195/mt8195.c624
-rw-r--r--sound/soc/sof/mediatek/mt8195/mt8195.h161
-rw-r--r--sound/soc/sof/mediatek/mtk-adsp-common.c84
-rw-r--r--sound/soc/sof/mediatek/mtk-adsp-common.h10
-rw-r--r--sound/soc/sof/nocodec.c53
-rw-r--r--sound/soc/sof/ops.c46
-rw-r--r--sound/soc/sof/ops.h365
-rw-r--r--sound/soc/sof/pcm.c473
-rw-r--r--sound/soc/sof/pm.c187
-rw-r--r--sound/soc/sof/probe.c290
-rw-r--r--sound/soc/sof/probe.h85
-rw-r--r--sound/soc/sof/sof-acpi-dev.c166
-rw-r--r--sound/soc/sof/sof-acpi-dev.h16
-rw-r--r--sound/soc/sof/sof-audio.c1069
-rw-r--r--sound/soc/sof/sof-audio.h492
-rw-r--r--sound/soc/sof/sof-client-ipc-flood-test.c394
-rw-r--r--sound/soc/sof/sof-client-ipc-kernel-injector.c162
-rw-r--r--sound/soc/sof/sof-client-ipc-msg-injector.c340
-rw-r--r--sound/soc/sof/sof-client-probes-ipc3.c232
-rw-r--r--sound/soc/sof/sof-client-probes-ipc4.c290
-rw-r--r--sound/soc/sof/sof-client-probes.c545
-rw-r--r--sound/soc/sof/sof-client-probes.h65
-rw-r--r--sound/soc/sof/sof-client.c613
-rw-r--r--sound/soc/sof/sof-client.h80
-rw-r--r--sound/soc/sof/sof-of-dev.c112
-rw-r--r--sound/soc/sof/sof-of-dev.h25
-rw-r--r--sound/soc/sof/sof-pci-dev.c497
-rw-r--r--sound/soc/sof/sof-pci-dev.h17
-rw-r--r--sound/soc/sof/sof-priv.h684
-rw-r--r--sound/soc/sof/sof-utils.c75
-rw-r--r--sound/soc/sof/sof-utils.h19
-rw-r--r--sound/soc/sof/stream-ipc.c129
-rw-r--r--sound/soc/sof/topology.c3641
-rw-r--r--sound/soc/sof/trace.c347
-rw-r--r--sound/soc/sof/utils.c172
-rw-r--r--sound/soc/sof/xtensa/core.c59
151 files changed, 40927 insertions, 10586 deletions
diff --git a/sound/soc/sof/Kconfig b/sound/soc/sof/Kconfig
index 4dda4b62509f..32ffd345e07f 100644
--- a/sound/soc/sof/Kconfig
+++ b/sound/soc/sof/Kconfig
@@ -1,84 +1,114 @@
# SPDX-License-Identifier: GPL-2.0-only
-config SND_SOC_SOF_TOPLEVEL
+menuconfig SND_SOC_SOF_TOPLEVEL
bool "Sound Open Firmware Support"
help
- This adds support for Sound Open Firmware (SOF). SOF is a free and
+ This adds support for Sound Open Firmware (SOF). SOF is free and
generic open source audio DSP firmware for multiple devices.
Say Y if you have such a device that is supported by SOF.
If unsure select "N".
if SND_SOC_SOF_TOPLEVEL
+config SND_SOC_SOF_PCI_DEV
+ tristate
+
config SND_SOC_SOF_PCI
tristate "SOF PCI enumeration support"
depends on PCI
- select SND_SOC_SOF
- select SND_SOC_ACPI if ACPI
help
This adds support for PCI enumeration. This option is
- required to enable Intel Skylake+ devices
- Say Y if you need this option
+ required to enable Intel Skylake+ devices.
+ For backwards-compatibility with previous configurations the selection will
+ be used as default for platform-specific drivers.
+ Say Y if you need this option.
If unsure select "N".
config SND_SOC_SOF_ACPI
tristate "SOF ACPI enumeration support"
depends on ACPI || COMPILE_TEST
- select SND_SOC_SOF
- select SND_SOC_ACPI if ACPI
- select IOSF_MBI if X86 && PCI
help
This adds support for ACPI enumeration. This option is required
- to enable Intel Broadwell/Baytrail/Cherrytrail devices
- Say Y if you need this option
+ to enable Intel Broadwell/Baytrail/Cherrytrail devices.
+ For backwards-compatibility with previous configurations the selection will
+ be used as default for platform-specific drivers.
+ Say Y if you need this option.
If unsure select "N".
+config SND_SOC_SOF_ACPI_DEV
+ tristate
+
config SND_SOC_SOF_OF
tristate "SOF OF enumeration support"
- depends on OF || COMPILE_TEST
- select SND_SOC_SOF
+ depends on OF
help
This adds support for Device Tree enumeration. This option is
- required to enable i.MX8 devices.
+ required to enable i.MX8 or Mediatek devices.
Say Y if you need this option. If unsure select "N".
+config SND_SOC_SOF_OF_DEV
+ tristate
+
+config SND_SOC_SOF_COMPRESS
+ bool
+ select SND_SOC_COMPRESS
+
config SND_SOC_SOF_DEBUG_PROBES
- bool "SOF enable data probing"
+ tristate
+ select SND_SOC_SOF_CLIENT
select SND_SOC_COMPRESS
help
This option enables the data probing feature that can be used to
gather data directly from specific points of the audio pipeline.
- Say Y if you want to enable probes.
- If unsure, select "N".
+ This option is not user-selectable but automagically handled by
+ 'select' statements at a higher level.
+
+config SND_SOC_SOF_CLIENT
+ tristate
+ select AUXILIARY_BUS
+ help
+ This option is not user-selectable but automagically handled by
+ 'select' statements at a higher level.
config SND_SOC_SOF_DEVELOPER_SUPPORT
bool "SOF developer options support"
- depends on EXPERT
+ depends on EXPERT && SND_SOC_SOF
help
- This option unlock SOF developer options for debug/performance/
+ This option unlocks SOF developer options for debug/performance/
code hardening.
Distributions should not select this option, only SOF development
teams should select it.
- Say Y if you are involved in SOF development and need this option
- If not, select N
+ Say Y if you are involved in SOF development and need this option.
+ If not, select N.
if SND_SOC_SOF_DEVELOPER_SUPPORT
+config SND_SOC_SOF_FORCE_PROBE_WORKQUEUE
+ bool "SOF force probe workqueue"
+ select SND_SOC_SOF_PROBE_WORK_QUEUE
+ help
+ This option forces the use of a probe workqueue, which is only used
+ when HDaudio is enabled due to module dependencies. Forcing this
+ option is intended for debug only, but this should not add any
+ functional issues in nominal cases.
+ Say Y if you are involved in SOF development and need this option.
+ If not, select N.
+
config SND_SOC_SOF_NOCODEC
tristate
config SND_SOC_SOF_NOCODEC_SUPPORT
- bool "SOF nocodec mode support"
+ bool "SOF nocodec static mode support"
help
This adds support for a dummy/nocodec machine driver fallback
option if no known codec is detected. This is typically only
enabled for developers or devices where the sound card is
- controlled externally
- This option is mutually exclusive with the Intel HDaudio support,
- selecting it may have negative impacts and prevent e.g. microphone
+ controlled externally.
+ This option is mutually exclusive at build time with the Intel HDAudio support.
+ Selecting it may have negative impacts and prevent e.g. microphone
functionality from being enabled on Intel CoffeeLake and later
platforms.
Distributions should not select this option!
- Say Y if you need this nocodec fallback option
+ Say Y if you need this nocodec fallback option.
If unsure select "N".
config SND_SOC_SOF_STRICT_ABI_CHECKS
@@ -92,10 +122,21 @@ config SND_SOC_SOF_STRICT_ABI_CHECKS
is invoked.
This option will stop topology creation and firmware load upfront.
It is intended for SOF CI/releases and not for users or distros.
- Say Y if you want strict ABI checks for an SOF release
- If you are not involved in SOF releases and CI development
+ Say Y if you want strict ABI checks for an SOF release.
+ If you are not involved in SOF releases and CI development,
select "N".
+config SND_SOC_SOF_ALLOW_FALLBACK_TO_NEWER_IPC_VERSION
+ bool "SOF allow fallback to newer IPC version"
+ help
+ This option will allow the kernel to try to 'fallback' to a newer IPC
+ version if there are missing firmware files to satisfy the default IPC
+ version.
+ IPC version fallback to older versions is not affected by this option,
+ it is always available.
+ Say Y if you are involved in SOF development and need this option.
+ If not, select N.
+
config SND_SOC_SOF_DEBUG
bool "SOF debugging features"
help
@@ -106,6 +147,19 @@ config SND_SOC_SOF_DEBUG
if SND_SOC_SOF_DEBUG
+config SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT
+ bool "SOF nocodec debug mode support"
+ depends on !SND_SOC_SOF_NOCODEC_SUPPORT
+ help
+ This adds support for a dummy/nocodec machine driver fallback
+ option.
+ Unlike the SND_SOC_SOF_NOCODEC_SUPPORT, this option is NOT
+ mutually exclusive at build with the Intel HDAudio support. The
+ selection will be done depending on command line or modprobe.d settings
+ Distributions should not select this option!
+ Say Y if you need this nocodec debug fallback option.
+ If unsure select "N".
+
config SND_SOC_SOF_FORCE_NOCODEC_MODE
bool "SOF force nocodec Mode"
depends on SND_SOC_SOF_NOCODEC_SUPPORT
@@ -114,15 +168,15 @@ config SND_SOC_SOF_FORCE_NOCODEC_MODE
though there is a codec detected on the real platform. This is
typically only enabled for developers for debug purposes, before
codec/machine driver is ready, or to exclude the impact of those
- drivers
- Say Y if you need this force nocodec mode option
+ drivers.
+ Say Y if you need this force nocodec mode option.
If unsure select "N".
config SND_SOC_SOF_DEBUG_XRUN_STOP
bool "SOF stop on XRUN"
help
This option forces PCMs to stop on any XRUN event. This is useful to
- preserve any trace data ond pipeline status prior to the XRUN.
+ preserve any trace data and pipeline status prior to the XRUN.
Say Y if you are debugging SOF FW pipeline XRUNs.
If unsure select "N".
@@ -137,12 +191,12 @@ config SND_SOC_SOF_DEBUG_VERBOSE_IPC
config SND_SOC_SOF_DEBUG_FORCE_IPC_POSITION
bool "SOF force to use IPC for position update on SKL+"
help
- This option force to handle stream position update IPCs and run pcm
+ This option forces to handle stream position update IPCs and run PCM
elapse to inform ALSA about that, on platforms (e.g. Intel SKL+) that
with other approach (e.g. HDAC DPIB/posbuf) to elapse PCM.
On platforms (e.g. Intel SKL-) where position update IPC is the only
one choice, this setting won't impact anything.
- if you are trying to debug pointer update with position IPCs or where
+ If you are trying to debug pointer update with position IPCs or where
DPIB/posbuf is not ready, select "Y".
If unsure select "N".
@@ -161,17 +215,49 @@ config SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE
help
The firmware trace can be enabled either at build-time with
this option, or dynamically by setting flags in the SOF core
- module parameter (similar to dynamic debug)
+ module parameter (similar to dynamic debug).
If unsure, select "N".
config SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST
- bool "SOF enable IPC flood test"
+ tristate "SOF enable IPC flood test"
+ depends on SND_SOC_SOF
+ select SND_SOC_SOF_CLIENT
help
- This option enables the IPC flood test which can be used to flood
- the DSP with test IPCs and gather stats about response times.
+ This option enables a separate client device for IPC flood test
+ which can be used to flood the DSP with test IPCs and gather stats
+ about response times.
Say Y if you want to enable IPC flood test.
If unsure, select "N".
+config SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST_NUM
+ int "Number of IPC flood test clients"
+ range 1 32
+ default 2
+ depends on SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST
+ help
+ Select the number of IPC flood test clients to be created.
+
+config SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR
+ tristate "SOF enable IPC message injector"
+ depends on SND_SOC_SOF
+ select SND_SOC_SOF_CLIENT
+ help
+ This option enables the IPC message injector which can be used to send
+ crafted IPC messages to the DSP to test its robustness.
+ Say Y if you want to enable the IPC message injector.
+ If unsure, select "N".
+
+config SND_SOC_SOF_DEBUG_IPC_KERNEL_INJECTOR
+ tristate "SOF enable IPC kernel injector"
+ depends on SND_SOC_SOF
+ select SND_SOC_SOF_CLIENT
+ help
+ This option enables the IPC kernel injector which can be used to send
+ crafted IPC messages to the kernel to test its robustness against
+ DSP messages.
+ Say Y if you want to enable the IPC kernel injector.
+ If unsure, select "N".
+
config SND_SOC_SOF_DEBUG_RETAIN_DSP_CONTEXT
bool "SOF retain DSP context on any FW exceptions"
help
@@ -188,9 +274,10 @@ config SND_SOC_SOF
tristate
select SND_SOC_TOPOLOGY
select SND_SOC_SOF_NOCODEC if SND_SOC_SOF_NOCODEC_SUPPORT
+ select SND_SOC_SOF_NOCODEC if SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT
help
This option is not user-selectable but automagically handled by
- 'select' statements at a higher level
+ 'select' statements at a higher level.
The selection is made at the top level and does not exactly follow
module dependencies but since the module or built-in type is decided
at the top level it doesn't matter.
@@ -199,12 +286,21 @@ config SND_SOC_SOF_PROBE_WORK_QUEUE
bool
help
This option is not user-selectable but automagically handled by
- 'select' statements at a higher level
+ 'select' statements at a higher level.
When selected, the probe is handled in two steps, for example to
avoid lockdeps if request_module is used in the probe.
+# Supported IPC versions
+config SND_SOC_SOF_IPC3
+ bool
+
+config SND_SOC_SOF_IPC4
+ bool
+
+source "sound/soc/sof/amd/Kconfig"
source "sound/soc/sof/imx/Kconfig"
source "sound/soc/sof/intel/Kconfig"
+source "sound/soc/sof/mediatek/Kconfig"
source "sound/soc/sof/xtensa/Kconfig"
endif
diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile
index 05718dfe6cd2..3624124575af 100644
--- a/sound/soc/sof/Makefile
+++ b/sound/soc/sof/Makefile
@@ -1,23 +1,61 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
snd-sof-objs := core.o ops.o loader.o ipc.o pcm.o pm.o debug.o topology.o\
- control.o trace.o utils.o sof-audio.o
-snd-sof-$(CONFIG_SND_SOC_SOF_DEBUG_PROBES) += probe.o compress.o
+ control.o trace.o iomem-utils.o sof-audio.o stream-ipc.o\
+ fw-file-profile.o
+
+# IPC implementations
+ifneq ($(CONFIG_SND_SOC_SOF_IPC3),)
+snd-sof-objs += ipc3.o ipc3-loader.o ipc3-topology.o ipc3-control.o ipc3-pcm.o\
+ ipc3-dtrace.o
+endif
+ifneq ($(CONFIG_SND_SOC_SOF_IPC4),)
+snd-sof-objs += ipc4.o ipc4-loader.o ipc4-topology.o ipc4-control.o ipc4-pcm.o\
+ ipc4-mtrace.o ipc4-telemetry.o
+endif
+
+# SOF client support
+ifneq ($(CONFIG_SND_SOC_SOF_CLIENT),)
+snd-sof-objs += sof-client.o
+endif
+
+snd-sof-$(CONFIG_SND_SOC_SOF_COMPRESS) += compress.o
snd-sof-pci-objs := sof-pci-dev.o
snd-sof-acpi-objs := sof-acpi-dev.o
snd-sof-of-objs := sof-of-dev.o
+snd-sof-ipc-flood-test-objs := sof-client-ipc-flood-test.o
+snd-sof-ipc-msg-injector-objs := sof-client-ipc-msg-injector.o
+snd-sof-ipc-kernel-injector-objs := sof-client-ipc-kernel-injector.o
+snd-sof-probes-objs := sof-client-probes.o
+ifneq ($(CONFIG_SND_SOC_SOF_IPC3),)
+snd-sof-probes-objs += sof-client-probes-ipc3.o
+endif
+ifneq ($(CONFIG_SND_SOC_SOF_IPC4),)
+snd-sof-probes-objs += sof-client-probes-ipc4.o
+endif
+
snd-sof-nocodec-objs := nocodec.o
+snd-sof-utils-objs := sof-utils.o
+
obj-$(CONFIG_SND_SOC_SOF) += snd-sof.o
obj-$(CONFIG_SND_SOC_SOF_NOCODEC) += snd-sof-nocodec.o
+obj-$(CONFIG_SND_SOC_SOF) += snd-sof-utils.o
+
+obj-$(CONFIG_SND_SOC_SOF_ACPI_DEV) += snd-sof-acpi.o
+obj-$(CONFIG_SND_SOC_SOF_OF_DEV) += snd-sof-of.o
+obj-$(CONFIG_SND_SOC_SOF_PCI_DEV) += snd-sof-pci.o
-obj-$(CONFIG_SND_SOC_SOF_ACPI) += snd-sof-acpi.o
-obj-$(CONFIG_SND_SOC_SOF_OF) += snd-sof-of.o
-obj-$(CONFIG_SND_SOC_SOF_PCI) += snd-sof-pci.o
+obj-$(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) += snd-sof-ipc-flood-test.o
+obj-$(CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR) += snd-sof-ipc-msg-injector.o
+obj-$(CONFIG_SND_SOC_SOF_DEBUG_IPC_KERNEL_INJECTOR) += snd-sof-ipc-kernel-injector.o
+obj-$(CONFIG_SND_SOC_SOF_DEBUG_PROBES) += snd-sof-probes.o
obj-$(CONFIG_SND_SOC_SOF_INTEL_TOPLEVEL) += intel/
obj-$(CONFIG_SND_SOC_SOF_IMX_TOPLEVEL) += imx/
+obj-$(CONFIG_SND_SOC_SOF_AMD_TOPLEVEL) += amd/
obj-$(CONFIG_SND_SOC_SOF_XTENSA) += xtensa/
+obj-$(CONFIG_SND_SOC_SOF_MTK_TOPLEVEL) += mediatek/
diff --git a/sound/soc/sof/amd/Kconfig b/sound/soc/sof/amd/Kconfig
new file mode 100644
index 000000000000..2729c6eb3feb
--- /dev/null
+++ b/sound/soc/sof/amd/Kconfig
@@ -0,0 +1,90 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+# This file is provided under a dual BSD/GPLv2 license. When using or
+# redistributing this file, you may do so under either license.
+#
+# Copyright(c) 2021, 2023 Advanced Micro Devices, Inc. All rights reserved.
+
+config SND_SOC_SOF_AMD_TOPLEVEL
+ tristate "SOF support for AMD audio DSPs"
+ depends on SOUNDWIRE_AMD || !SOUNDWIRE_AMD
+ depends on X86 || COMPILE_TEST
+ help
+ This adds support for Sound Open Firmware for AMD platforms.
+ Say Y if you have such a device.
+ If unsure select "N".
+
+if SND_SOC_SOF_AMD_TOPLEVEL
+
+config SND_SOC_SOF_AMD_COMMON
+ tristate
+ select SND_SOC_SOF
+ select SND_SOC_SOF_IPC3
+ select SND_SOC_SOF_PCI_DEV
+ select SND_AMD_ACP_CONFIG
+ select SND_SOC_SOF_XTENSA
+ select SND_SOC_SOF_ACP_PROBES
+ select SND_SOC_ACPI if ACPI
+ help
+ This option is not user-selectable but automatically handled by
+ 'select' statements at a higher level
+
+config SND_SOC_SOF_AMD_RENOIR
+ tristate "SOF support for RENOIR"
+ depends on SND_SOC_SOF_PCI
+ select SND_SOC_SOF_AMD_COMMON
+ help
+ Select this option for SOF support on AMD Renoir platform
+
+config SND_SOC_SOF_AMD_VANGOGH
+ tristate "SOF support for VANGOGH"
+ depends on SND_SOC_SOF_PCI
+ select SND_SOC_SOF_AMD_COMMON
+ help
+ Select this option for SOF support
+ on AMD Vangogh platform.
+ Say Y if you want to enable SOF on Vangogh.
+ If unsure select "N".
+
+config SND_SOC_SOF_AMD_REMBRANDT
+ tristate "SOF support for REMBRANDT"
+ depends on SND_SOC_SOF_PCI
+ select SND_SOC_SOF_AMD_COMMON
+ help
+ Select this option for SOF support on AMD Rembrandt platform
+ Say Y if you want to enable SOF on Rembrandt.
+ If unsure select "N".
+
+config SND_SOC_SOF_ACP_PROBES
+ tristate
+ select SND_SOC_SOF_DEBUG_PROBES
+ help
+ This option is not user-selectable but automatically handled by
+ 'select' statements at a higher level
+
+config SND_SOC_SOF_AMD_SOUNDWIRE_LINK_BASELINE
+ tristate
+ select SND_AMD_SOUNDWIRE_ACPI if ACPI
+
+config SND_SOC_SOF_AMD_SOUNDWIRE
+ tristate "SOF support for SoundWire based AMD platforms"
+ default SND_SOC_SOF_AMD_SOUNDWIRE_LINK_BASELINE
+ depends on SND_SOC_SOF_AMD_SOUNDWIRE_LINK_BASELINE
+ depends on ACPI
+ depends on SOUNDWIRE_AMD
+ help
+ This adds support for SoundWire with Sound Open Firmware
+ for AMD platforms.
+ Say Y if you want to enable SoundWire links with SOF.
+ If unsure select "N".
+
+config SND_SOC_SOF_AMD_ACP63
+ tristate "SOF support for ACP6.3 platform"
+ depends on SND_SOC_SOF_PCI
+ select SND_SOC_SOF_AMD_COMMON
+ select SND_SOC_SOF_AMD_SOUNDWIRE_LINK_BASELINE
+ help
+ Select this option for SOF support on
+ AMD ACP6.3 version based platforms.
+ Say Y if you want to enable SOF on ACP6.3 based platform.
+ If unsure select "N".
+endif
diff --git a/sound/soc/sof/amd/Makefile b/sound/soc/sof/amd/Makefile
new file mode 100644
index 000000000000..ad25f4206177
--- /dev/null
+++ b/sound/soc/sof/amd/Makefile
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+# This file is provided under a dual BSD/GPLv2 license. When using or
+# redistributing this file, you may do so under either license.
+#
+# Copyright(c) 2021, 2023 Advanced Micro Devices, Inc. All rights reserved.
+
+snd-sof-amd-acp-objs := acp.o acp-loader.o acp-ipc.o acp-pcm.o acp-stream.o acp-trace.o acp-common.o
+snd-sof-amd-acp-$(CONFIG_SND_SOC_SOF_ACP_PROBES) = acp-probes.o
+snd-sof-amd-renoir-objs := pci-rn.o renoir.o
+snd-sof-amd-rembrandt-objs := pci-rmb.o rembrandt.o
+snd-sof-amd-vangogh-objs := pci-vangogh.o vangogh.o
+snd-sof-amd-acp63-objs := pci-acp63.o acp63.o
+
+obj-$(CONFIG_SND_SOC_SOF_AMD_COMMON) += snd-sof-amd-acp.o
+obj-$(CONFIG_SND_SOC_SOF_AMD_RENOIR) +=snd-sof-amd-renoir.o
+obj-$(CONFIG_SND_SOC_SOF_AMD_REMBRANDT) +=snd-sof-amd-rembrandt.o
+obj-$(CONFIG_SND_SOC_SOF_AMD_VANGOGH) +=snd-sof-amd-vangogh.o
+obj-$(CONFIG_SND_SOC_SOF_AMD_ACP63) +=snd-sof-amd-acp63.o
diff --git a/sound/soc/sof/amd/acp-common.c b/sound/soc/sof/amd/acp-common.c
new file mode 100644
index 000000000000..0fc4e20ec673
--- /dev/null
+++ b/sound/soc/sof/amd/acp-common.c
@@ -0,0 +1,265 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2022 Advanced Micro Devices, Inc.
+//
+// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com>
+// V sujith kumar Reddy <Vsujithkumar.Reddy@amd.com>
+
+/* ACP-specific Common code */
+
+#include "../sof-priv.h"
+#include "../sof-audio.h"
+#include "../ops.h"
+#include "acp.h"
+#include "acp-dsp-offset.h"
+#include <sound/sof/xtensa.h>
+
+/**
+ * amd_sof_ipc_dump() - This function is called when IPC tx times out.
+ * @sdev: SOF device.
+ */
+void amd_sof_ipc_dump(struct snd_sof_dev *sdev)
+{
+ const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata);
+ u32 base = desc->dsp_intr_base;
+ u32 dsp_msg_write = sdev->debug_box.offset +
+ offsetof(struct scratch_ipc_conf, sof_dsp_msg_write);
+ u32 dsp_ack_write = sdev->debug_box.offset +
+ offsetof(struct scratch_ipc_conf, sof_dsp_ack_write);
+ u32 host_msg_write = sdev->debug_box.offset +
+ offsetof(struct scratch_ipc_conf, sof_host_msg_write);
+ u32 host_ack_write = sdev->debug_box.offset +
+ offsetof(struct scratch_ipc_conf, sof_host_ack_write);
+ u32 dsp_msg, dsp_ack, host_msg, host_ack, irq_stat;
+
+ dsp_msg = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + dsp_msg_write);
+ dsp_ack = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + dsp_ack_write);
+ host_msg = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + host_msg_write);
+ host_ack = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + host_ack_write);
+ irq_stat = snd_sof_dsp_read(sdev, ACP_DSP_BAR, base + DSP_SW_INTR_STAT_OFFSET);
+
+ dev_err(sdev->dev,
+ "dsp_msg = %#x dsp_ack = %#x host_msg = %#x host_ack = %#x irq_stat = %#x\n",
+ dsp_msg, dsp_ack, host_msg, host_ack, irq_stat);
+}
+
+/**
+ * amd_get_registers() - This function is called in case of DSP oops
+ * in order to gather information about the registers, filename and
+ * linenumber and stack.
+ * @sdev: SOF device.
+ * @xoops: Stores information about registers.
+ * @panic_info: Stores information about filename and line number.
+ * @stack: Stores the stack dump.
+ * @stack_words: Size of the stack dump.
+ */
+static void amd_get_registers(struct snd_sof_dev *sdev,
+ struct sof_ipc_dsp_oops_xtensa *xoops,
+ struct sof_ipc_panic_info *panic_info,
+ u32 *stack, size_t stack_words)
+{
+ u32 offset = sdev->dsp_oops_offset;
+
+ /* first read registers */
+ acp_mailbox_read(sdev, offset, xoops, sizeof(*xoops));
+
+ /* then get panic info */
+ if (xoops->arch_hdr.totalsize > EXCEPT_MAX_HDR_SIZE) {
+ dev_err(sdev->dev, "invalid header size 0x%x. FW oops is bogus\n",
+ xoops->arch_hdr.totalsize);
+ return;
+ }
+
+ offset += xoops->arch_hdr.totalsize;
+ acp_mailbox_read(sdev, offset, panic_info, sizeof(*panic_info));
+
+ /* then get the stack */
+ offset += sizeof(*panic_info);
+ acp_mailbox_read(sdev, offset, stack, stack_words * sizeof(u32));
+}
+
+/**
+ * amd_sof_dump() - This function is called when a panic message is
+ * received from the firmware.
+ * @sdev: SOF device.
+ * @flags: parameter not used but required by ops prototype
+ */
+void amd_sof_dump(struct snd_sof_dev *sdev, u32 flags)
+{
+ struct sof_ipc_dsp_oops_xtensa xoops;
+ struct sof_ipc_panic_info panic_info;
+ u32 stack[AMD_STACK_DUMP_SIZE];
+ u32 status;
+
+ /* Get information about the panic status from the debug box area.
+ * Compute the trace point based on the status.
+ */
+ if (sdev->dsp_oops_offset > sdev->debug_box.offset) {
+ acp_mailbox_read(sdev, sdev->debug_box.offset, &status, sizeof(u32));
+ } else {
+ /* Read DSP Panic status from dsp_box.
+ * As window information for exception box offset and size is not available
+ * before FW_READY
+ */
+ acp_mailbox_read(sdev, sdev->dsp_box.offset, &status, sizeof(u32));
+ sdev->dsp_oops_offset = sdev->dsp_box.offset + sizeof(status);
+ }
+
+ /* Get information about the registers, the filename and line
+ * number and the stack.
+ */
+ amd_get_registers(sdev, &xoops, &panic_info, stack, AMD_STACK_DUMP_SIZE);
+
+ /* Print the information to the console */
+ sof_print_oops_and_stack(sdev, KERN_ERR, status, status, &xoops,
+ &panic_info, stack, AMD_STACK_DUMP_SIZE);
+}
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_AMD_SOUNDWIRE)
+static int amd_sof_sdw_get_slave_info(struct snd_sof_dev *sdev)
+{
+ struct acp_dev_data *acp_data = sdev->pdata->hw_pdata;
+
+ return sdw_amd_get_slave_info(acp_data->sdw);
+}
+
+static struct snd_soc_acpi_mach *amd_sof_sdw_machine_select(struct snd_sof_dev *sdev)
+{
+ struct snd_soc_acpi_mach *mach;
+ const struct snd_soc_acpi_link_adr *link;
+ struct acp_dev_data *acp_data = sdev->pdata->hw_pdata;
+ int ret, i;
+
+ if (acp_data->info.count) {
+ ret = amd_sof_sdw_get_slave_info(sdev);
+ if (ret) {
+ dev_info(sdev->dev, "failed to read slave information\n");
+ return NULL;
+ }
+ for (mach = sdev->pdata->desc->alt_machines; mach; mach++) {
+ if (!mach->links)
+ break;
+ link = mach->links;
+ for (i = 0; i < acp_data->info.count && link->num_adr; link++, i++) {
+ if (!snd_soc_acpi_sdw_link_slaves_found(sdev->dev, link,
+ acp_data->sdw->ids,
+ acp_data->sdw->num_slaves))
+ break;
+ }
+ if (i == acp_data->info.count || !link->num_adr)
+ break;
+ }
+ if (mach && mach->link_mask) {
+ mach->mach_params.links = mach->links;
+ mach->mach_params.link_mask = mach->link_mask;
+ mach->mach_params.platform = dev_name(sdev->dev);
+ return mach;
+ }
+ }
+ dev_info(sdev->dev, "No SoundWire machine driver found\n");
+ return NULL;
+}
+
+#else
+static struct snd_soc_acpi_mach *amd_sof_sdw_machine_select(struct snd_sof_dev *sdev)
+{
+ return NULL;
+}
+#endif
+
+struct snd_soc_acpi_mach *amd_sof_machine_select(struct snd_sof_dev *sdev)
+{
+ struct snd_sof_pdata *sof_pdata = sdev->pdata;
+ const struct sof_dev_desc *desc = sof_pdata->desc;
+ struct snd_soc_acpi_mach *mach = NULL;
+
+ if (desc->machines)
+ mach = snd_soc_acpi_find_machine(desc->machines);
+ if (!mach) {
+ mach = amd_sof_sdw_machine_select(sdev);
+ if (!mach) {
+ dev_warn(sdev->dev, "No matching ASoC machine driver found\n");
+ return NULL;
+ }
+ }
+
+ sof_pdata->tplg_filename = mach->sof_tplg_filename;
+ sof_pdata->fw_filename = mach->fw_filename;
+
+ return mach;
+}
+
+/* AMD Common DSP ops */
+struct snd_sof_dsp_ops sof_acp_common_ops = {
+ /* probe and remove */
+ .probe = amd_sof_acp_probe,
+ .remove = amd_sof_acp_remove,
+
+ /* Register IO */
+ .write = sof_io_write,
+ .read = sof_io_read,
+
+ /* Block IO */
+ .block_read = acp_dsp_block_read,
+ .block_write = acp_dsp_block_write,
+
+ /*Firmware loading */
+ .load_firmware = snd_sof_load_firmware_memcpy,
+ .pre_fw_run = acp_dsp_pre_fw_run,
+ .get_bar_index = acp_get_bar_index,
+
+ /* DSP core boot */
+ .run = acp_sof_dsp_run,
+
+ /*IPC */
+ .send_msg = acp_sof_ipc_send_msg,
+ .ipc_msg_data = acp_sof_ipc_msg_data,
+ .set_stream_data_offset = acp_set_stream_data_offset,
+ .get_mailbox_offset = acp_sof_ipc_get_mailbox_offset,
+ .get_window_offset = acp_sof_ipc_get_window_offset,
+ .irq_thread = acp_sof_ipc_irq_thread,
+
+ /* stream callbacks */
+ .pcm_open = acp_pcm_open,
+ .pcm_close = acp_pcm_close,
+ .pcm_hw_params = acp_pcm_hw_params,
+ .pcm_pointer = acp_pcm_pointer,
+
+ .hw_info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
+
+ /* Machine driver callbacks */
+ .machine_select = amd_sof_machine_select,
+ .machine_register = sof_machine_register,
+ .machine_unregister = sof_machine_unregister,
+
+ /* Trace Logger */
+ .trace_init = acp_sof_trace_init,
+ .trace_release = acp_sof_trace_release,
+
+ /* PM */
+ .suspend = amd_sof_acp_suspend,
+ .resume = amd_sof_acp_resume,
+
+ .ipc_dump = amd_sof_ipc_dump,
+ .dbg_dump = amd_sof_dump,
+ .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem,
+ .dsp_arch_ops = &sof_xtensa_arch_ops,
+
+ /* probe client device registation */
+ .register_ipc_clients = acp_probes_register,
+ .unregister_ipc_clients = acp_probes_unregister,
+};
+EXPORT_SYMBOL_NS(sof_acp_common_ops, SND_SOC_SOF_AMD_COMMON);
+
+MODULE_IMPORT_NS(SND_SOC_SOF_AMD_COMMON);
+MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA);
+MODULE_IMPORT_NS(SOUNDWIRE_AMD_INIT);
+MODULE_DESCRIPTION("ACP SOF COMMON Driver");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/sof/amd/acp-dsp-offset.h b/sound/soc/sof/amd/acp-dsp-offset.h
new file mode 100644
index 000000000000..59afbe2e0f42
--- /dev/null
+++ b/sound/soc/sof/amd/acp-dsp-offset.h
@@ -0,0 +1,109 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+/*
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * Copyright(c) 2021, 2023 Advanced Micro Devices, Inc. All rights reserved.
+ *
+ * Author: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com>
+ */
+
+#ifndef _ACP_DSP_IP_OFFSET_H
+#define _ACP_DSP_IP_OFFSET_H
+
+/* Registers from ACP_DMA_0 block */
+#define ACP_DMA_CNTL_0 0x00
+#define ACP_DMA_DSCR_STRT_IDX_0 0x20
+#define ACP_DMA_DSCR_CNT_0 0x40
+#define ACP_DMA_PRIO_0 0x60
+#define ACP_DMA_CUR_DSCR_0 0x80
+#define ACP_DMA_ERR_STS_0 0xC0
+#define ACP_DMA_DESC_BASE_ADDR 0xE0
+#define ACP_DMA_DESC_MAX_NUM_DSCR 0xE4
+#define ACP_DMA_CH_STS 0xE8
+#define ACP_DMA_CH_GROUP 0xEC
+#define ACP_DMA_CH_RST_STS 0xF0
+
+/* Registers from ACP_DSP_0 block */
+#define ACP_DSP0_RUNSTALL 0x414
+
+/* Registers from ACP_AXI2AXIATU block */
+#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1 0xC00
+#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_1 0xC04
+#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_2 0xC08
+#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_2 0xC0C
+#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_3 0xC10
+#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_3 0xC14
+#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_4 0xC18
+#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_4 0xC1C
+#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_5 0xC20
+#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_5 0xC24
+#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_6 0xC28
+#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_6 0xC2C
+#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_7 0xC30
+#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_7 0xC34
+#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_8 0xC38
+#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_8 0xC3C
+#define ACPAXI2AXI_ATU_CTRL 0xC40
+#define ACP_SOFT_RESET 0x1000
+#define ACP_CONTROL 0x1004
+
+#define ACP3X_I2S_PIN_CONFIG 0x1400
+#define ACP5X_I2S_PIN_CONFIG 0x1400
+#define ACP6X_I2S_PIN_CONFIG 0x1440
+
+/* Registers offsets from ACP_PGFSM block */
+#define ACP3X_PGFSM_BASE 0x141C
+#define ACP5X_PGFSM_BASE 0x1424
+#define ACP6X_PGFSM_BASE 0x1024
+#define PGFSM_CONTROL_OFFSET 0x0
+#define PGFSM_STATUS_OFFSET 0x4
+#define ACP3X_CLKMUX_SEL 0x1424
+#define ACP5X_CLKMUX_SEL 0x142C
+#define ACP6X_CLKMUX_SEL 0x102C
+
+/* Registers from ACP_INTR block */
+#define ACP3X_EXT_INTR_STAT 0x1808
+#define ACP5X_EXT_INTR_STAT 0x1808
+#define ACP6X_EXTERNAL_INTR_ENB 0x1A00
+#define ACP6X_EXTERNAL_INTR_CNTL 0x1A04
+#define ACP6X_EXT_INTR_STAT 0x1A0C
+#define ACP6X_EXT_INTR_STAT1 0x1A10
+
+#define ACP3X_DSP_SW_INTR_BASE 0x1814
+#define ACP5X_DSP_SW_INTR_BASE 0x1814
+#define ACP6X_DSP_SW_INTR_BASE 0x1808
+#define DSP_SW_INTR_CNTL_OFFSET 0x0
+#define DSP_SW_INTR_STAT_OFFSET 0x4
+#define DSP_SW_INTR_TRIG_OFFSET 0x8
+#define ACP_ERROR_STATUS 0x18C4
+#define ACP3X_AXI2DAGB_SEM_0 0x1880
+#define ACP5X_AXI2DAGB_SEM_0 0x1884
+#define ACP6X_AXI2DAGB_SEM_0 0x1874
+
+/* ACP common registers to report errors related to I2S & SoundWire interfaces */
+#define ACP_SW0_I2S_ERROR_REASON 0x18B4
+#define ACP_SW1_I2S_ERROR_REASON 0x1A50
+
+/* Registers from ACP_SHA block */
+#define ACP_SHA_DSP_FW_QUALIFIER 0x1C70
+#define ACP_SHA_DMA_CMD 0x1CB0
+#define ACP_SHA_MSG_LENGTH 0x1CB4
+#define ACP_SHA_DMA_STRT_ADDR 0x1CB8
+#define ACP_SHA_DMA_DESTINATION_ADDR 0x1CBC
+#define ACP_SHA_DMA_CMD_STS 0x1CC0
+#define ACP_SHA_DMA_ERR_STATUS 0x1CC4
+#define ACP_SHA_TRANSFER_BYTE_CNT 0x1CC8
+#define ACP_SHA_DMA_INCLUDE_HDR 0x1CCC
+#define ACP_SHA_PSP_ACK 0x1C74
+
+#define ACP_SCRATCH_REG_0 0x10000
+#define ACP6X_DSP_FUSION_RUNSTALL 0x0644
+
+/* Cache window registers */
+#define ACP_DSP0_CACHE_OFFSET0 0x0420
+#define ACP_DSP0_CACHE_SIZE0 0x0424
+
+#define ACP_SW0_EN 0x3000
+#define ACP_SW1_EN 0x3C00
+#endif
diff --git a/sound/soc/sof/amd/acp-ipc.c b/sound/soc/sof/amd/acp-ipc.c
new file mode 100644
index 000000000000..b44b1b1adb6e
--- /dev/null
+++ b/sound/soc/sof/amd/acp-ipc.c
@@ -0,0 +1,301 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2021, 2023 Advanced Micro Devices, Inc.
+//
+// Authors: Balakishore Pati <Balakishore.pati@amd.com>
+// Ajit Kumar Pandey <AjitKumar.Pandey@amd.com>
+
+/* ACP-specific SOF IPC code */
+
+#include <linux/module.h>
+#include "../ops.h"
+#include "acp.h"
+#include "acp-dsp-offset.h"
+
+void acp_mailbox_write(struct snd_sof_dev *sdev, u32 offset, void *message, size_t bytes)
+{
+ memcpy_to_scratch(sdev, offset, message, bytes);
+}
+EXPORT_SYMBOL_NS(acp_mailbox_write, SND_SOC_SOF_AMD_COMMON);
+
+void acp_mailbox_read(struct snd_sof_dev *sdev, u32 offset, void *message, size_t bytes)
+{
+ memcpy_from_scratch(sdev, offset, message, bytes);
+}
+EXPORT_SYMBOL_NS(acp_mailbox_read, SND_SOC_SOF_AMD_COMMON);
+
+static void acpbus_trigger_host_to_dsp_swintr(struct acp_dev_data *adata)
+{
+ struct snd_sof_dev *sdev = adata->dev;
+ const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata);
+ u32 swintr_trigger;
+
+ swintr_trigger = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->dsp_intr_base +
+ DSP_SW_INTR_TRIG_OFFSET);
+ swintr_trigger |= 0x01;
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->dsp_intr_base + DSP_SW_INTR_TRIG_OFFSET,
+ swintr_trigger);
+}
+
+static void acp_ipc_host_msg_set(struct snd_sof_dev *sdev)
+{
+ unsigned int host_msg = sdev->debug_box.offset +
+ offsetof(struct scratch_ipc_conf, sof_host_msg_write);
+
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + host_msg, 1);
+}
+
+static void acp_dsp_ipc_host_done(struct snd_sof_dev *sdev)
+{
+ unsigned int dsp_msg = sdev->debug_box.offset +
+ offsetof(struct scratch_ipc_conf, sof_dsp_msg_write);
+
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + dsp_msg, 0);
+}
+
+static void acp_dsp_ipc_dsp_done(struct snd_sof_dev *sdev)
+{
+ unsigned int dsp_ack = sdev->debug_box.offset +
+ offsetof(struct scratch_ipc_conf, sof_dsp_ack_write);
+
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + dsp_ack, 0);
+}
+
+int acp_sof_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
+{
+ struct acp_dev_data *adata = sdev->pdata->hw_pdata;
+ const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata);
+ unsigned int offset = sdev->host_box.offset;
+ unsigned int count = ACP_HW_SEM_RETRY_COUNT;
+
+ while (snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->hw_semaphore_offset)) {
+ /* Wait until acquired HW Semaphore Lock or timeout*/
+ count--;
+ if (!count) {
+ dev_err(sdev->dev, "%s: Failed to acquire HW lock\n", __func__);
+ return -EINVAL;
+ }
+ }
+
+ acp_mailbox_write(sdev, offset, msg->msg_data, msg->msg_size);
+ acp_ipc_host_msg_set(sdev);
+
+ /* Trigger host to dsp interrupt for the msg */
+ acpbus_trigger_host_to_dsp_swintr(adata);
+
+ /* Unlock or Release HW Semaphore */
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->hw_semaphore_offset, 0x0);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(acp_sof_ipc_send_msg, SND_SOC_SOF_AMD_COMMON);
+
+static void acp_dsp_ipc_get_reply(struct snd_sof_dev *sdev)
+{
+ struct snd_sof_ipc_msg *msg = sdev->msg;
+ struct sof_ipc_reply reply;
+ struct sof_ipc_cmd_hdr *hdr;
+ unsigned int offset = sdev->host_box.offset;
+ int ret = 0;
+
+ /*
+ * Sometimes, there is unexpected reply ipc arriving. The reply
+ * ipc belongs to none of the ipcs sent from driver.
+ * In this case, the driver must ignore the ipc.
+ */
+ if (!msg) {
+ dev_warn(sdev->dev, "unexpected ipc interrupt raised!\n");
+ return;
+ }
+ hdr = msg->msg_data;
+ if (hdr->cmd == (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CTX_SAVE) ||
+ hdr->cmd == (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_GATE)) {
+ /*
+ * memory windows are powered off before sending IPC reply,
+ * so we can't read the mailbox for CTX_SAVE and PM_GATE
+ * replies.
+ */
+ reply.error = 0;
+ reply.hdr.cmd = SOF_IPC_GLB_REPLY;
+ reply.hdr.size = sizeof(reply);
+ memcpy(msg->reply_data, &reply, sizeof(reply));
+ goto out;
+ }
+ /* get IPC reply from DSP in the mailbox */
+ acp_mailbox_read(sdev, offset, &reply, sizeof(reply));
+ if (reply.error < 0) {
+ memcpy(msg->reply_data, &reply, sizeof(reply));
+ ret = reply.error;
+ } else {
+ /*
+ * To support an IPC tx_message with a
+ * reply_size set to zero.
+ */
+ if (!msg->reply_size)
+ goto out;
+
+ /* reply correct size ? */
+ if (reply.hdr.size != msg->reply_size &&
+ !(reply.hdr.cmd & SOF_IPC_GLB_PROBE)) {
+ dev_err(sdev->dev, "reply expected %zu got %u bytes\n",
+ msg->reply_size, reply.hdr.size);
+ ret = -EINVAL;
+ }
+ /* read the message */
+ if (msg->reply_size > 0)
+ acp_mailbox_read(sdev, offset, msg->reply_data, msg->reply_size);
+ }
+out:
+ msg->reply_error = ret;
+}
+
+irqreturn_t acp_sof_ipc_irq_thread(int irq, void *context)
+{
+ struct snd_sof_dev *sdev = context;
+ const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata);
+ struct acp_dev_data *adata = sdev->pdata->hw_pdata;
+ unsigned int dsp_msg_write = sdev->debug_box.offset +
+ offsetof(struct scratch_ipc_conf, sof_dsp_msg_write);
+ unsigned int dsp_ack_write = sdev->debug_box.offset +
+ offsetof(struct scratch_ipc_conf, sof_dsp_ack_write);
+ bool ipc_irq = false;
+ int dsp_msg, dsp_ack;
+ unsigned int status;
+
+ if (sdev->first_boot && sdev->fw_state != SOF_FW_BOOT_COMPLETE) {
+ acp_mailbox_read(sdev, sdev->dsp_box.offset, &status, sizeof(status));
+ if ((status & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) {
+ snd_sof_dsp_panic(sdev, sdev->dsp_box.offset + sizeof(status),
+ true);
+ status = 0;
+ acp_mailbox_write(sdev, sdev->dsp_box.offset, &status, sizeof(status));
+ return IRQ_HANDLED;
+ }
+ snd_sof_ipc_msgs_rx(sdev);
+ acp_dsp_ipc_host_done(sdev);
+ return IRQ_HANDLED;
+ }
+
+ dsp_msg = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + dsp_msg_write);
+ if (dsp_msg) {
+ snd_sof_ipc_msgs_rx(sdev);
+ acp_dsp_ipc_host_done(sdev);
+ ipc_irq = true;
+ }
+
+ dsp_ack = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + dsp_ack_write);
+ if (dsp_ack) {
+ spin_lock_irq(&sdev->ipc_lock);
+ /* handle immediate reply from DSP core */
+ acp_dsp_ipc_get_reply(sdev);
+ snd_sof_ipc_reply(sdev, 0);
+ /* set the done bit */
+ acp_dsp_ipc_dsp_done(sdev);
+ spin_unlock_irq(&sdev->ipc_lock);
+ ipc_irq = true;
+ }
+
+ acp_mailbox_read(sdev, sdev->debug_box.offset, &status, sizeof(u32));
+ if ((status & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) {
+ snd_sof_dsp_panic(sdev, sdev->dsp_oops_offset, true);
+ status = 0;
+ acp_mailbox_write(sdev, sdev->debug_box.offset, &status, sizeof(status));
+ return IRQ_HANDLED;
+ }
+
+ if (desc->probe_reg_offset) {
+ u32 val;
+ u32 posn;
+
+ /* Probe register consists of two parts
+ * (0-30) bit has cumulative position value
+ * 31 bit is a synchronization flag between DSP and CPU
+ * for the position update
+ */
+ val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->probe_reg_offset);
+ if (val & PROBE_STATUS_BIT) {
+ posn = val & ~PROBE_STATUS_BIT;
+ if (adata->probe_stream) {
+ /* Probe related posn value is of 31 bits limited to 2GB
+ * once wrapped DSP won't send posn interrupt.
+ */
+ adata->probe_stream->cstream_posn = posn;
+ snd_compr_fragment_elapsed(adata->probe_stream->cstream);
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->probe_reg_offset, posn);
+ ipc_irq = true;
+ }
+ }
+ }
+
+ if (!ipc_irq)
+ dev_dbg_ratelimited(sdev->dev, "nothing to do in IPC IRQ thread\n");
+
+ return IRQ_HANDLED;
+}
+EXPORT_SYMBOL_NS(acp_sof_ipc_irq_thread, SND_SOC_SOF_AMD_COMMON);
+
+int acp_sof_ipc_msg_data(struct snd_sof_dev *sdev, struct snd_sof_pcm_stream *sps,
+ void *p, size_t sz)
+{
+ unsigned int offset = sdev->dsp_box.offset;
+
+ if (!sps || !sdev->stream_box.size) {
+ acp_mailbox_read(sdev, offset, p, sz);
+ } else {
+ struct snd_pcm_substream *substream = sps->substream;
+ struct acp_dsp_stream *stream;
+
+ if (!substream || !substream->runtime)
+ return -ESTRPIPE;
+
+ stream = substream->runtime->private_data;
+
+ if (!stream)
+ return -ESTRPIPE;
+
+ acp_mailbox_read(sdev, stream->posn_offset, p, sz);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(acp_sof_ipc_msg_data, SND_SOC_SOF_AMD_COMMON);
+
+int acp_set_stream_data_offset(struct snd_sof_dev *sdev,
+ struct snd_sof_pcm_stream *sps,
+ size_t posn_offset)
+{
+ struct snd_pcm_substream *substream = sps->substream;
+ struct acp_dsp_stream *stream = substream->runtime->private_data;
+
+ /* check for unaligned offset or overflow */
+ if (posn_offset > sdev->stream_box.size ||
+ posn_offset % sizeof(struct sof_ipc_stream_posn) != 0)
+ return -EINVAL;
+
+ stream->posn_offset = sdev->stream_box.offset + posn_offset;
+
+ dev_dbg(sdev->dev, "pcm: stream dir %d, posn mailbox offset is %zu",
+ substream->stream, stream->posn_offset);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(acp_set_stream_data_offset, SND_SOC_SOF_AMD_COMMON);
+
+int acp_sof_ipc_get_mailbox_offset(struct snd_sof_dev *sdev)
+{
+ const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata);
+
+ return desc->sram_pte_offset;
+}
+EXPORT_SYMBOL_NS(acp_sof_ipc_get_mailbox_offset, SND_SOC_SOF_AMD_COMMON);
+
+int acp_sof_ipc_get_window_offset(struct snd_sof_dev *sdev, u32 id)
+{
+ return 0;
+}
+EXPORT_SYMBOL_NS(acp_sof_ipc_get_window_offset, SND_SOC_SOF_AMD_COMMON);
+
+MODULE_DESCRIPTION("AMD ACP sof-ipc driver");
diff --git a/sound/soc/sof/amd/acp-loader.c b/sound/soc/sof/amd/acp-loader.c
new file mode 100644
index 000000000000..d2d21478399e
--- /dev/null
+++ b/sound/soc/sof/amd/acp-loader.c
@@ -0,0 +1,315 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2021, 2023 Advanced Micro Devices, Inc.
+//
+// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com>
+
+/*
+ * Hardware interface for ACP DSP Firmware binaries loader
+ */
+
+#include <linux/firmware.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#include "../ops.h"
+#include "acp-dsp-offset.h"
+#include "acp.h"
+
+#define FW_BIN 0
+#define FW_DATA_BIN 1
+#define FW_SRAM_DATA_BIN 2
+
+#define FW_BIN_PTE_OFFSET 0x00
+#define FW_DATA_BIN_PTE_OFFSET 0x08
+
+#define ACP_DSP_RUN 0x00
+
+int acp_dsp_block_read(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type,
+ u32 offset, void *dest, size_t size)
+{
+ const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata);
+ switch (blk_type) {
+ case SOF_FW_BLK_TYPE_SRAM:
+ offset = offset - desc->sram_pte_offset;
+ memcpy_from_scratch(sdev, offset, dest, size);
+ break;
+ default:
+ dev_err(sdev->dev, "bad blk type 0x%x\n", blk_type);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(acp_dsp_block_read, SND_SOC_SOF_AMD_COMMON);
+
+int acp_dsp_block_write(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type,
+ u32 offset, void *src, size_t size)
+{
+ struct pci_dev *pci = to_pci_dev(sdev->dev);
+ struct acp_dev_data *adata;
+ void *dest;
+ u32 dma_size, page_count;
+ unsigned int size_fw;
+
+ adata = sdev->pdata->hw_pdata;
+
+ switch (blk_type) {
+ case SOF_FW_BLK_TYPE_IRAM:
+ if (!adata->bin_buf) {
+ size_fw = sdev->basefw.fw->size;
+ page_count = PAGE_ALIGN(size_fw) >> PAGE_SHIFT;
+ dma_size = page_count * ACP_PAGE_SIZE;
+ adata->bin_buf = dma_alloc_coherent(&pci->dev, dma_size,
+ &adata->sha_dma_addr,
+ GFP_ATOMIC);
+ if (!adata->bin_buf)
+ return -ENOMEM;
+ }
+ adata->fw_bin_size = size + offset;
+ dest = adata->bin_buf + offset;
+ break;
+ case SOF_FW_BLK_TYPE_DRAM:
+ if (!adata->data_buf) {
+ adata->data_buf = dma_alloc_coherent(&pci->dev,
+ ACP_DEFAULT_DRAM_LENGTH,
+ &adata->dma_addr,
+ GFP_ATOMIC);
+ if (!adata->data_buf)
+ return -ENOMEM;
+ }
+ dest = adata->data_buf + offset;
+ adata->fw_data_bin_size = size + offset;
+ adata->is_dram_in_use = true;
+ break;
+ case SOF_FW_BLK_TYPE_SRAM:
+ if (!adata->sram_data_buf) {
+ adata->sram_data_buf = dma_alloc_coherent(&pci->dev,
+ ACP_DEFAULT_SRAM_LENGTH,
+ &adata->sram_dma_addr,
+ GFP_ATOMIC);
+ if (!adata->sram_data_buf)
+ return -ENOMEM;
+ }
+ adata->fw_sram_data_bin_size = size + offset;
+ dest = adata->sram_data_buf + offset;
+ adata->is_sram_in_use = true;
+ break;
+ default:
+ dev_err(sdev->dev, "bad blk type 0x%x\n", blk_type);
+ return -EINVAL;
+ }
+
+ memcpy(dest, src, size);
+ return 0;
+}
+EXPORT_SYMBOL_NS(acp_dsp_block_write, SND_SOC_SOF_AMD_COMMON);
+
+int acp_get_bar_index(struct snd_sof_dev *sdev, u32 type)
+{
+ return type;
+}
+EXPORT_SYMBOL_NS(acp_get_bar_index, SND_SOC_SOF_AMD_COMMON);
+
+static void configure_pte_for_fw_loading(int type, int num_pages, struct acp_dev_data *adata)
+{
+ struct snd_sof_dev *sdev = adata->dev;
+ const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata);
+ unsigned int low, high;
+ dma_addr_t addr;
+ u16 page_idx;
+ u32 offset;
+
+ switch (type) {
+ case FW_BIN:
+ offset = FW_BIN_PTE_OFFSET;
+ addr = adata->sha_dma_addr;
+ break;
+ case FW_DATA_BIN:
+ offset = adata->fw_bin_page_count * 8;
+ addr = adata->dma_addr;
+ break;
+ case FW_SRAM_DATA_BIN:
+ offset = (adata->fw_bin_page_count + ACP_DRAM_PAGE_COUNT) * 8;
+ addr = adata->sram_dma_addr;
+ break;
+ default:
+ dev_err(sdev->dev, "Invalid data type %x\n", type);
+ return;
+ }
+
+ /* Group Enable */
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACPAXI2AXI_ATU_BASE_ADDR_GRP_1,
+ desc->sram_pte_offset | BIT(31));
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1,
+ PAGE_SIZE_4K_ENABLE);
+
+ for (page_idx = 0; page_idx < num_pages; page_idx++) {
+ low = lower_32_bits(addr);
+ high = upper_32_bits(addr);
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + offset, low);
+ high |= BIT(31);
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + offset + 4, high);
+ offset += 8;
+ addr += PAGE_SIZE;
+ }
+
+ /* Flush ATU Cache after PTE Update */
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACPAXI2AXI_ATU_CTRL, ACP_ATU_CACHE_INVALID);
+}
+
+/* pre fw run operations */
+int acp_dsp_pre_fw_run(struct snd_sof_dev *sdev)
+{
+ struct pci_dev *pci = to_pci_dev(sdev->dev);
+ const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata);
+ struct acp_dev_data *adata;
+ unsigned int src_addr, size_fw, dest_addr;
+ u32 page_count, dma_size;
+ int ret;
+
+ adata = sdev->pdata->hw_pdata;
+
+ if (adata->signed_fw_image)
+ size_fw = adata->fw_bin_size - ACP_FIRMWARE_SIGNATURE;
+ else
+ size_fw = adata->fw_bin_size;
+
+ page_count = PAGE_ALIGN(size_fw) >> PAGE_SHIFT;
+ adata->fw_bin_page_count = page_count;
+
+ configure_pte_for_fw_loading(FW_BIN, page_count, adata);
+ ret = configure_and_run_sha_dma(adata, adata->bin_buf, ACP_SYSTEM_MEMORY_WINDOW,
+ ACP_IRAM_BASE_ADDRESS, size_fw);
+ if (ret < 0) {
+ dev_err(sdev->dev, "SHA DMA transfer failed status: %d\n", ret);
+ return ret;
+ }
+ if (adata->is_dram_in_use) {
+ configure_pte_for_fw_loading(FW_DATA_BIN, ACP_DRAM_PAGE_COUNT, adata);
+ src_addr = ACP_SYSTEM_MEMORY_WINDOW + (page_count * ACP_PAGE_SIZE);
+ dest_addr = ACP_DRAM_BASE_ADDRESS;
+
+ ret = configure_and_run_dma(adata, src_addr, dest_addr, adata->fw_data_bin_size);
+ if (ret < 0) {
+ dev_err(sdev->dev, "acp dma configuration failed: %d\n", ret);
+ return ret;
+ }
+ ret = acp_dma_status(adata, 0);
+ if (ret < 0)
+ dev_err(sdev->dev, "acp dma transfer status: %d\n", ret);
+ }
+ if (adata->is_sram_in_use) {
+ configure_pte_for_fw_loading(FW_SRAM_DATA_BIN, ACP_SRAM_PAGE_COUNT, adata);
+ src_addr = ACP_SYSTEM_MEMORY_WINDOW + ACP_DEFAULT_SRAM_LENGTH +
+ (page_count * ACP_PAGE_SIZE);
+ dest_addr = ACP_SRAM_BASE_ADDRESS;
+
+ ret = configure_and_run_dma(adata, src_addr, dest_addr,
+ adata->fw_sram_data_bin_size);
+ if (ret < 0) {
+ dev_err(sdev->dev, "acp dma configuration failed: %d\n", ret);
+ return ret;
+ }
+ ret = acp_dma_status(adata, 0);
+ if (ret < 0)
+ dev_err(sdev->dev, "acp dma transfer status: %d\n", ret);
+ }
+
+ if (desc->rev > 3) {
+ /* Cache Window enable */
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DSP0_CACHE_OFFSET0, desc->sram_pte_offset);
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DSP0_CACHE_SIZE0, SRAM1_SIZE | BIT(31));
+ }
+
+ /* Free memory once DMA is complete */
+ dma_size = (PAGE_ALIGN(sdev->basefw.fw->size) >> PAGE_SHIFT) * ACP_PAGE_SIZE;
+ dma_free_coherent(&pci->dev, dma_size, adata->bin_buf, adata->sha_dma_addr);
+ adata->bin_buf = NULL;
+ if (adata->is_dram_in_use) {
+ dma_free_coherent(&pci->dev, ACP_DEFAULT_DRAM_LENGTH, adata->data_buf,
+ adata->dma_addr);
+ adata->data_buf = NULL;
+ }
+ if (adata->is_sram_in_use) {
+ dma_free_coherent(&pci->dev, ACP_DEFAULT_SRAM_LENGTH, adata->sram_data_buf,
+ adata->sram_dma_addr);
+ adata->sram_data_buf = NULL;
+ }
+ return ret;
+}
+EXPORT_SYMBOL_NS(acp_dsp_pre_fw_run, SND_SOC_SOF_AMD_COMMON);
+
+int acp_sof_dsp_run(struct snd_sof_dev *sdev)
+{
+ struct acp_dev_data *adata = sdev->pdata->hw_pdata;
+ const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata);
+ int val;
+
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DSP0_RUNSTALL, ACP_DSP_RUN);
+ val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_DSP0_RUNSTALL);
+ dev_dbg(sdev->dev, "ACP_DSP0_RUNSTALL : 0x%0x\n", val);
+
+ /* Some platforms won't support fusion DSP,keep offset zero for no support */
+ if (desc->fusion_dsp_offset && adata->enable_fw_debug) {
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->fusion_dsp_offset, ACP_DSP_RUN);
+ val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->fusion_dsp_offset);
+ dev_dbg(sdev->dev, "ACP_DSP0_FUSION_RUNSTALL : 0x%0x\n", val);
+ }
+ return 0;
+}
+EXPORT_SYMBOL_NS(acp_sof_dsp_run, SND_SOC_SOF_AMD_COMMON);
+
+int acp_sof_load_signed_firmware(struct snd_sof_dev *sdev)
+{
+ struct snd_sof_pdata *plat_data = sdev->pdata;
+ struct acp_dev_data *adata = plat_data->hw_pdata;
+ const char *fw_filename;
+ int ret;
+
+ fw_filename = kasprintf(GFP_KERNEL, "%s/%s",
+ plat_data->fw_filename_prefix,
+ adata->fw_code_bin);
+ if (!fw_filename)
+ return -ENOMEM;
+
+ ret = request_firmware(&sdev->basefw.fw, fw_filename, sdev->dev);
+ if (ret < 0) {
+ kfree(fw_filename);
+ dev_err(sdev->dev, "sof signed firmware code bin is missing\n");
+ return ret;
+ } else {
+ dev_dbg(sdev->dev, "request_firmware %s successful\n", fw_filename);
+ }
+ kfree(fw_filename);
+
+ ret = snd_sof_dsp_block_write(sdev, SOF_FW_BLK_TYPE_IRAM, 0,
+ (void *)sdev->basefw.fw->data,
+ sdev->basefw.fw->size);
+
+ fw_filename = kasprintf(GFP_KERNEL, "%s/%s",
+ plat_data->fw_filename_prefix,
+ adata->fw_data_bin);
+ if (!fw_filename)
+ return -ENOMEM;
+
+ ret = request_firmware(&adata->fw_dbin, fw_filename, sdev->dev);
+ if (ret < 0) {
+ kfree(fw_filename);
+ dev_err(sdev->dev, "sof signed firmware data bin is missing\n");
+ return ret;
+
+ } else {
+ dev_dbg(sdev->dev, "request_firmware %s successful\n", fw_filename);
+ }
+ kfree(fw_filename);
+
+ ret = snd_sof_dsp_block_write(sdev, SOF_FW_BLK_TYPE_DRAM, 0,
+ (void *)adata->fw_dbin->data,
+ adata->fw_dbin->size);
+ return ret;
+}
+EXPORT_SYMBOL_NS(acp_sof_load_signed_firmware, SND_SOC_SOF_AMD_COMMON);
diff --git a/sound/soc/sof/amd/acp-pcm.c b/sound/soc/sof/amd/acp-pcm.c
new file mode 100644
index 000000000000..cee0b1154874
--- /dev/null
+++ b/sound/soc/sof/amd/acp-pcm.c
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2021 Advanced Micro Devices, Inc.
+//
+// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com>
+
+/*
+ * PCM interface for generic AMD audio ACP DSP block
+ */
+#include <sound/pcm_params.h>
+
+#include "../ops.h"
+#include "acp.h"
+#include "acp-dsp-offset.h"
+
+int acp_pcm_hw_params(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_sof_platform_stream_params *platform_params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct acp_dsp_stream *stream = runtime->private_data;
+ unsigned int buf_offset, index;
+ u32 size;
+ int ret;
+
+ size = runtime->dma_bytes;
+ stream->num_pages = PFN_UP(runtime->dma_bytes);
+ stream->dmab = substream->runtime->dma_buffer_p;
+
+ ret = acp_dsp_stream_config(sdev, stream);
+ if (ret < 0) {
+ dev_err(sdev->dev, "stream configuration failed\n");
+ return ret;
+ }
+
+ platform_params->use_phy_address = true;
+ platform_params->phy_addr = stream->reg_offset;
+ platform_params->stream_tag = stream->stream_tag;
+ platform_params->cont_update_posn = 1;
+
+ /* write buffer size of stream in scratch memory */
+
+ buf_offset = sdev->debug_box.offset +
+ offsetof(struct scratch_reg_conf, buf_size);
+ index = stream->stream_tag - 1;
+ buf_offset = buf_offset + index * 4;
+
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + buf_offset, size);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(acp_pcm_hw_params, SND_SOC_SOF_AMD_COMMON);
+
+int acp_pcm_open(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream)
+{
+ struct acp_dsp_stream *stream;
+
+ stream = acp_dsp_stream_get(sdev, 0);
+ if (!stream)
+ return -ENODEV;
+
+ substream->runtime->private_data = stream;
+ stream->substream = substream;
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(acp_pcm_open, SND_SOC_SOF_AMD_COMMON);
+
+int acp_pcm_close(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream)
+{
+ struct acp_dsp_stream *stream;
+
+ stream = substream->runtime->private_data;
+ if (!stream) {
+ dev_err(sdev->dev, "No open stream\n");
+ return -EINVAL;
+ }
+
+ stream->substream = NULL;
+ substream->runtime->private_data = NULL;
+
+ return acp_dsp_stream_put(sdev, stream);
+}
+EXPORT_SYMBOL_NS(acp_pcm_close, SND_SOC_SOF_AMD_COMMON);
+
+snd_pcm_uframes_t acp_pcm_pointer(struct snd_sof_dev *sdev,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_component *scomp = sdev->component;
+ struct snd_sof_pcm_stream *stream;
+ struct sof_ipc_stream_posn posn;
+ struct snd_sof_pcm *spcm;
+ snd_pcm_uframes_t pos;
+ int ret;
+
+ spcm = snd_sof_find_spcm_dai(scomp, rtd);
+ if (!spcm) {
+ dev_warn_ratelimited(sdev->dev, "warn: can't find PCM with DAI ID %d\n",
+ rtd->dai_link->id);
+ return 0;
+ }
+
+ stream = &spcm->stream[substream->stream];
+ ret = snd_sof_ipc_msg_data(sdev, stream, &posn, sizeof(posn));
+ if (ret < 0) {
+ dev_warn(sdev->dev, "failed to read stream position: %d\n", ret);
+ return 0;
+ }
+
+ memcpy(&stream->posn, &posn, sizeof(posn));
+ pos = spcm->stream[substream->stream].posn.host_posn;
+ pos = bytes_to_frames(substream->runtime, pos);
+
+ return pos;
+}
+EXPORT_SYMBOL_NS(acp_pcm_pointer, SND_SOC_SOF_AMD_COMMON);
diff --git a/sound/soc/sof/amd/acp-probes.c b/sound/soc/sof/amd/acp-probes.c
new file mode 100644
index 000000000000..778cf1a8b610
--- /dev/null
+++ b/sound/soc/sof/amd/acp-probes.c
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2023 Advanced Micro Devices, Inc.
+//
+// Authors: V Sujith Kumar Reddy <Vsujithkumar.Reddy@amd.com>
+
+/*
+ * Probe interface for generic AMD audio ACP DSP block
+ */
+
+#include <linux/module.h>
+#include <sound/soc.h>
+#include "../sof-priv.h"
+#include "../sof-client-probes.h"
+#include "../sof-client.h"
+#include "../ops.h"
+#include "acp.h"
+#include "acp-dsp-offset.h"
+
+static int acp_probes_compr_startup(struct sof_client_dev *cdev,
+ struct snd_compr_stream *cstream,
+ struct snd_soc_dai *dai, u32 *stream_id)
+{
+ struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
+ struct acp_dsp_stream *stream;
+ struct acp_dev_data *adata;
+
+ adata = sdev->pdata->hw_pdata;
+ stream = acp_dsp_stream_get(sdev, 0);
+ if (!stream)
+ return -ENODEV;
+
+ stream->cstream = cstream;
+ cstream->runtime->private_data = stream;
+
+ adata->probe_stream = stream;
+ *stream_id = stream->stream_tag;
+
+ return 0;
+}
+
+static int acp_probes_compr_shutdown(struct sof_client_dev *cdev,
+ struct snd_compr_stream *cstream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
+ struct acp_dsp_stream *stream = cstream->runtime->private_data;
+ struct acp_dev_data *adata;
+ int ret;
+
+ ret = acp_dsp_stream_put(sdev, stream);
+ if (ret < 0) {
+ dev_err(sdev->dev, "Failed to release probe compress stream\n");
+ return ret;
+ }
+
+ adata = sdev->pdata->hw_pdata;
+ stream->cstream = NULL;
+ cstream->runtime->private_data = NULL;
+ adata->probe_stream = NULL;
+
+ return 0;
+}
+
+static int acp_probes_compr_set_params(struct sof_client_dev *cdev,
+ struct snd_compr_stream *cstream,
+ struct snd_compr_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
+ struct acp_dsp_stream *stream = cstream->runtime->private_data;
+ unsigned int buf_offset, index;
+ u32 size;
+ int ret;
+
+ stream->dmab = cstream->runtime->dma_buffer_p;
+ stream->num_pages = PFN_UP(cstream->runtime->dma_bytes);
+ size = cstream->runtime->buffer_size;
+
+ ret = acp_dsp_stream_config(sdev, stream);
+ if (ret < 0) {
+ acp_dsp_stream_put(sdev, stream);
+ return ret;
+ }
+
+ /* write buffer size of stream in scratch memory */
+
+ buf_offset = sdev->debug_box.offset +
+ offsetof(struct scratch_reg_conf, buf_size);
+ index = stream->stream_tag - 1;
+ buf_offset = buf_offset + index * 4;
+
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + buf_offset, size);
+
+ return 0;
+}
+
+static int acp_probes_compr_trigger(struct sof_client_dev *cdev,
+ struct snd_compr_stream *cstream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ /* Nothing to do here, as it is a mandatory callback just defined */
+ return 0;
+}
+
+static int acp_probes_compr_pointer(struct sof_client_dev *cdev,
+ struct snd_compr_stream *cstream,
+ struct snd_compr_tstamp *tstamp,
+ struct snd_soc_dai *dai)
+{
+ struct acp_dsp_stream *stream = cstream->runtime->private_data;
+ struct snd_soc_pcm_stream *pstream;
+
+ pstream = &dai->driver->capture;
+ tstamp->copied_total = stream->cstream_posn;
+ tstamp->sampling_rate = snd_pcm_rate_bit_to_rate(pstream->rates);
+
+ return 0;
+}
+
+/* SOF client implementation */
+static const struct sof_probes_host_ops acp_probes_ops = {
+ .startup = acp_probes_compr_startup,
+ .shutdown = acp_probes_compr_shutdown,
+ .set_params = acp_probes_compr_set_params,
+ .trigger = acp_probes_compr_trigger,
+ .pointer = acp_probes_compr_pointer,
+};
+
+int acp_probes_register(struct snd_sof_dev *sdev)
+{
+ return sof_client_dev_register(sdev, "acp-probes", 0, &acp_probes_ops,
+ sizeof(acp_probes_ops));
+}
+EXPORT_SYMBOL_NS(acp_probes_register, SND_SOC_SOF_AMD_COMMON);
+
+void acp_probes_unregister(struct snd_sof_dev *sdev)
+{
+ sof_client_dev_unregister(sdev, "acp-probes", 0);
+}
+EXPORT_SYMBOL_NS(acp_probes_unregister, SND_SOC_SOF_AMD_COMMON);
+
+MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT);
+
diff --git a/sound/soc/sof/amd/acp-stream.c b/sound/soc/sof/amd/acp-stream.c
new file mode 100644
index 000000000000..6f40ef7ba85e
--- /dev/null
+++ b/sound/soc/sof/amd/acp-stream.c
@@ -0,0 +1,187 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2021 Advanced Micro Devices, Inc.
+//
+// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com>
+
+/*
+ * Hardware interface for generic AMD audio DSP ACP IP
+ */
+
+#include "../ops.h"
+#include "acp-dsp-offset.h"
+#include "acp.h"
+
+#define PTE_GRP1_OFFSET 0x00000000
+#define PTE_GRP2_OFFSET 0x00800000
+#define PTE_GRP3_OFFSET 0x01000000
+#define PTE_GRP4_OFFSET 0x01800000
+#define PTE_GRP5_OFFSET 0x02000000
+#define PTE_GRP6_OFFSET 0x02800000
+#define PTE_GRP7_OFFSET 0x03000000
+#define PTE_GRP8_OFFSET 0x03800000
+
+int acp_dsp_stream_config(struct snd_sof_dev *sdev, struct acp_dsp_stream *stream)
+{
+ const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata);
+ unsigned int pte_reg, pte_size, phy_addr_offset, index;
+ int stream_tag = stream->stream_tag;
+ u32 low, high, offset, reg_val;
+ dma_addr_t addr;
+ int page_idx;
+
+ switch (stream_tag) {
+ case 1:
+ pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_1;
+ pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1;
+ offset = offsetof(struct scratch_reg_conf, grp1_pte);
+ stream->reg_offset = PTE_GRP1_OFFSET;
+ break;
+ case 2:
+ pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_2;
+ pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_2;
+ offset = offsetof(struct scratch_reg_conf, grp2_pte);
+ stream->reg_offset = PTE_GRP2_OFFSET;
+ break;
+ case 3:
+ pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_3;
+ pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_3;
+ offset = offsetof(struct scratch_reg_conf, grp3_pte);
+ stream->reg_offset = PTE_GRP3_OFFSET;
+ break;
+ case 4:
+ pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_4;
+ pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_4;
+ offset = offsetof(struct scratch_reg_conf, grp4_pte);
+ stream->reg_offset = PTE_GRP4_OFFSET;
+ break;
+ case 5:
+ pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_5;
+ pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_5;
+ offset = offsetof(struct scratch_reg_conf, grp5_pte);
+ stream->reg_offset = PTE_GRP5_OFFSET;
+ break;
+ case 6:
+ pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_6;
+ pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_6;
+ offset = offsetof(struct scratch_reg_conf, grp6_pte);
+ stream->reg_offset = PTE_GRP6_OFFSET;
+ break;
+ case 7:
+ pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_7;
+ pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_7;
+ offset = offsetof(struct scratch_reg_conf, grp7_pte);
+ stream->reg_offset = PTE_GRP7_OFFSET;
+ break;
+ case 8:
+ pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_8;
+ pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_8;
+ offset = offsetof(struct scratch_reg_conf, grp8_pte);
+ stream->reg_offset = PTE_GRP8_OFFSET;
+ break;
+ default:
+ dev_err(sdev->dev, "Invalid stream tag %d\n", stream_tag);
+ return -EINVAL;
+ }
+
+ /* write phy_addr in scratch memory */
+
+ phy_addr_offset = sdev->debug_box.offset +
+ offsetof(struct scratch_reg_conf, reg_offset);
+ index = stream_tag - 1;
+ phy_addr_offset = phy_addr_offset + index * 4;
+
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 +
+ phy_addr_offset, stream->reg_offset);
+
+ /* Group Enable */
+ offset = offset + sdev->debug_box.offset;
+ reg_val = desc->sram_pte_offset + offset;
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, pte_reg, reg_val | BIT(31));
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, pte_size, PAGE_SIZE_4K_ENABLE);
+
+ for (page_idx = 0; page_idx < stream->num_pages; page_idx++) {
+ addr = snd_sgbuf_get_addr(stream->dmab, page_idx * PAGE_SIZE);
+
+ /* Load the low address of page int ACP SRAM through SRBM */
+ low = lower_32_bits(addr);
+ high = upper_32_bits(addr);
+
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + offset, low);
+
+ high |= BIT(31);
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + offset + 4, high);
+ /* Move to next physically contiguous page */
+ offset += 8;
+ }
+
+ /* Flush ATU Cache after PTE Update */
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACPAXI2AXI_ATU_CTRL, ACP_ATU_CACHE_INVALID);
+
+ return 0;
+}
+
+struct acp_dsp_stream *acp_dsp_stream_get(struct snd_sof_dev *sdev, int tag)
+{
+ struct acp_dev_data *adata = sdev->pdata->hw_pdata;
+ struct acp_dsp_stream *stream = adata->stream_buf;
+ int i;
+
+ for (i = 0; i < ACP_MAX_STREAM; i++, stream++) {
+ if (stream->active)
+ continue;
+
+ /* return stream if tag not specified*/
+ if (!tag) {
+ stream->active = 1;
+ return stream;
+ }
+
+ /* check if this is the requested stream tag */
+ if (stream->stream_tag == tag) {
+ stream->active = 1;
+ return stream;
+ }
+ }
+
+ dev_err(sdev->dev, "stream %d active or no inactive stream\n", tag);
+ return NULL;
+}
+EXPORT_SYMBOL_NS(acp_dsp_stream_get, SND_SOC_SOF_AMD_COMMON);
+
+int acp_dsp_stream_put(struct snd_sof_dev *sdev,
+ struct acp_dsp_stream *acp_stream)
+{
+ struct acp_dev_data *adata = sdev->pdata->hw_pdata;
+ struct acp_dsp_stream *stream = adata->stream_buf;
+ int i;
+
+ /* Free an active stream */
+ for (i = 0; i < ACP_MAX_STREAM; i++, stream++) {
+ if (stream == acp_stream) {
+ stream->active = 0;
+ return 0;
+ }
+ }
+
+ dev_err(sdev->dev, "Cannot find active stream tag %d\n", acp_stream->stream_tag);
+ return -EINVAL;
+}
+EXPORT_SYMBOL_NS(acp_dsp_stream_put, SND_SOC_SOF_AMD_COMMON);
+
+int acp_dsp_stream_init(struct snd_sof_dev *sdev)
+{
+ struct acp_dev_data *adata = sdev->pdata->hw_pdata;
+ int i;
+
+ for (i = 0; i < ACP_MAX_STREAM; i++) {
+ adata->stream_buf[i].sdev = sdev;
+ adata->stream_buf[i].active = 0;
+ adata->stream_buf[i].stream_tag = i + 1;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_NS(acp_dsp_stream_init, SND_SOC_SOF_AMD_COMMON);
diff --git a/sound/soc/sof/amd/acp-trace.c b/sound/soc/sof/amd/acp-trace.c
new file mode 100644
index 000000000000..c9482b27cbe3
--- /dev/null
+++ b/sound/soc/sof/amd/acp-trace.c
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2021 Advanced Micro Devices, Inc. All rights reserved.
+//
+// Authors: Vishnuvardhanrao Ravuapati <vishnuvardhanrao.ravulapati@amd.com>
+// V Sujith Kumar Reddy <Vsujithkumar.Reddy@amd.com>
+
+/*This file support Host TRACE Logger driver callback for SOF FW */
+
+#include "acp.h"
+
+#define ACP_LOGGER_STREAM 8
+#define NUM_PAGES 16
+
+int acp_sof_trace_release(struct snd_sof_dev *sdev)
+{
+ struct acp_dsp_stream *stream;
+ struct acp_dev_data *adata;
+ int ret;
+
+ adata = sdev->pdata->hw_pdata;
+ stream = adata->dtrace_stream;
+ ret = acp_dsp_stream_put(sdev, stream);
+ if (ret < 0) {
+ dev_err(sdev->dev, "Failed to release trace stream\n");
+ return ret;
+ }
+
+ adata->dtrace_stream = NULL;
+ return 0;
+}
+EXPORT_SYMBOL_NS(acp_sof_trace_release, SND_SOC_SOF_AMD_COMMON);
+
+int acp_sof_trace_init(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab,
+ struct sof_ipc_dma_trace_params_ext *dtrace_params)
+{
+ struct acp_dsp_stream *stream;
+ struct acp_dev_data *adata;
+ int ret;
+
+ adata = sdev->pdata->hw_pdata;
+ stream = acp_dsp_stream_get(sdev, ACP_LOGGER_STREAM);
+ if (!stream)
+ return -ENODEV;
+
+ stream->dmab = dmab;
+ stream->num_pages = NUM_PAGES;
+
+ ret = acp_dsp_stream_config(sdev, stream);
+ if (ret < 0) {
+ acp_dsp_stream_put(sdev, stream);
+ return ret;
+ }
+
+ adata->dtrace_stream = stream;
+ dtrace_params->stream_tag = stream->stream_tag;
+ dtrace_params->buffer.phy_addr = stream->reg_offset;
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(acp_sof_trace_init, SND_SOC_SOF_AMD_COMMON);
diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c
new file mode 100644
index 000000000000..9b3c26210db3
--- /dev/null
+++ b/sound/soc/sof/amd/acp.c
@@ -0,0 +1,803 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2021, 2023 Advanced Micro Devices, Inc. All rights reserved.
+//
+// Authors: Vijendar Mukunda <Vijendar.Mukunda@amd.com>
+// Ajit Kumar Pandey <AjitKumar.Pandey@amd.com>
+
+/*
+ * Hardware interface for generic AMD ACP processor
+ */
+
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#include "../ops.h"
+#include "acp.h"
+#include "acp-dsp-offset.h"
+
+#define SECURED_FIRMWARE 1
+
+static bool enable_fw_debug;
+module_param(enable_fw_debug, bool, 0444);
+MODULE_PARM_DESC(enable_fw_debug, "Enable Firmware debug");
+
+const struct dmi_system_id acp_sof_quirk_table[] = {
+ {
+ /* Steam Deck OLED device */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Valve"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Galileo"),
+ },
+ .driver_data = (void *)SECURED_FIRMWARE,
+ },
+ {}
+};
+EXPORT_SYMBOL_GPL(acp_sof_quirk_table);
+
+static int smn_write(struct pci_dev *dev, u32 smn_addr, u32 data)
+{
+ pci_write_config_dword(dev, 0x60, smn_addr);
+ pci_write_config_dword(dev, 0x64, data);
+
+ return 0;
+}
+
+static int smn_read(struct pci_dev *dev, u32 smn_addr)
+{
+ u32 data = 0;
+
+ pci_write_config_dword(dev, 0x60, smn_addr);
+ pci_read_config_dword(dev, 0x64, &data);
+
+ return data;
+}
+
+static void init_dma_descriptor(struct acp_dev_data *adata)
+{
+ struct snd_sof_dev *sdev = adata->dev;
+ const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata);
+ unsigned int addr;
+
+ addr = desc->sram_pte_offset + sdev->debug_box.offset +
+ offsetof(struct scratch_reg_conf, dma_desc);
+
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DMA_DESC_BASE_ADDR, addr);
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DMA_DESC_MAX_NUM_DSCR, ACP_MAX_DESC_CNT);
+}
+
+static void configure_dma_descriptor(struct acp_dev_data *adata, unsigned short idx,
+ struct dma_descriptor *dscr_info)
+{
+ struct snd_sof_dev *sdev = adata->dev;
+ unsigned int offset;
+
+ offset = ACP_SCRATCH_REG_0 + sdev->debug_box.offset +
+ offsetof(struct scratch_reg_conf, dma_desc) +
+ idx * sizeof(struct dma_descriptor);
+
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, offset, dscr_info->src_addr);
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, offset + 0x4, dscr_info->dest_addr);
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, offset + 0x8, dscr_info->tx_cnt.u32_all);
+}
+
+static int config_dma_channel(struct acp_dev_data *adata, unsigned int ch,
+ unsigned int idx, unsigned int dscr_count)
+{
+ struct snd_sof_dev *sdev = adata->dev;
+ unsigned int val, status;
+ int ret;
+
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DMA_CNTL_0 + ch * sizeof(u32),
+ ACP_DMA_CH_RST | ACP_DMA_CH_GRACEFUL_RST_EN);
+
+ ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_DMA_CH_RST_STS, val,
+ val & (1 << ch), ACP_REG_POLL_INTERVAL,
+ ACP_REG_POLL_TIMEOUT_US);
+ if (ret < 0) {
+ status = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_ERROR_STATUS);
+ val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_DMA_ERR_STS_0 + ch * sizeof(u32));
+
+ dev_err(sdev->dev, "ACP_DMA_ERR_STS :0x%x ACP_ERROR_STATUS :0x%x\n", val, status);
+ return ret;
+ }
+
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, (ACP_DMA_CNTL_0 + ch * sizeof(u32)), 0);
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DMA_DSCR_CNT_0 + ch * sizeof(u32), dscr_count);
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DMA_DSCR_STRT_IDX_0 + ch * sizeof(u32), idx);
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DMA_PRIO_0 + ch * sizeof(u32), 0);
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DMA_CNTL_0 + ch * sizeof(u32), ACP_DMA_CH_RUN);
+
+ return ret;
+}
+
+static int acpbus_dma_start(struct acp_dev_data *adata, unsigned int ch,
+ unsigned int dscr_count, struct dma_descriptor *dscr_info)
+{
+ struct snd_sof_dev *sdev = adata->dev;
+ int ret;
+ u16 dscr;
+
+ if (!dscr_info || !dscr_count)
+ return -EINVAL;
+
+ for (dscr = 0; dscr < dscr_count; dscr++)
+ configure_dma_descriptor(adata, dscr, dscr_info++);
+
+ ret = config_dma_channel(adata, ch, 0, dscr_count);
+ if (ret < 0)
+ dev_err(sdev->dev, "config dma ch failed:%d\n", ret);
+
+ return ret;
+}
+
+int configure_and_run_dma(struct acp_dev_data *adata, unsigned int src_addr,
+ unsigned int dest_addr, int dsp_data_size)
+{
+ struct snd_sof_dev *sdev = adata->dev;
+ unsigned int desc_count, index;
+ int ret;
+
+ for (desc_count = 0; desc_count < ACP_MAX_DESC && dsp_data_size >= 0;
+ desc_count++, dsp_data_size -= ACP_PAGE_SIZE) {
+ adata->dscr_info[desc_count].src_addr = src_addr + desc_count * ACP_PAGE_SIZE;
+ adata->dscr_info[desc_count].dest_addr = dest_addr + desc_count * ACP_PAGE_SIZE;
+ adata->dscr_info[desc_count].tx_cnt.bits.count = ACP_PAGE_SIZE;
+ if (dsp_data_size < ACP_PAGE_SIZE)
+ adata->dscr_info[desc_count].tx_cnt.bits.count = dsp_data_size;
+ }
+
+ ret = acpbus_dma_start(adata, 0, desc_count, adata->dscr_info);
+ if (ret)
+ dev_err(sdev->dev, "acpbus_dma_start failed\n");
+
+ /* Clear descriptor array */
+ for (index = 0; index < desc_count; index++)
+ memset(&adata->dscr_info[index], 0x00, sizeof(struct dma_descriptor));
+
+ return ret;
+}
+
+/*
+ * psp_mbox_ready- function to poll ready bit of psp mbox
+ * @adata: acp device data
+ * @ack: bool variable to check ready bit status or psp ack
+ */
+
+static int psp_mbox_ready(struct acp_dev_data *adata, bool ack)
+{
+ struct snd_sof_dev *sdev = adata->dev;
+ int ret;
+ u32 data;
+
+ ret = read_poll_timeout(smn_read, data, data & MBOX_READY_MASK, MBOX_DELAY_US,
+ ACP_PSP_TIMEOUT_US, false, adata->smn_dev, MP0_C2PMSG_114_REG);
+ if (!ret)
+ return 0;
+
+ dev_err(sdev->dev, "PSP error status %x\n", data & MBOX_STATUS_MASK);
+
+ if (ack)
+ return -ETIMEDOUT;
+
+ return -EBUSY;
+}
+
+/*
+ * psp_send_cmd - function to send psp command over mbox
+ * @adata: acp device data
+ * @cmd: non zero integer value for command type
+ */
+
+static int psp_send_cmd(struct acp_dev_data *adata, int cmd)
+{
+ struct snd_sof_dev *sdev = adata->dev;
+ int ret;
+ u32 data;
+
+ if (!cmd)
+ return -EINVAL;
+
+ /* Get a non-zero Doorbell value from PSP */
+ ret = read_poll_timeout(smn_read, data, data, MBOX_DELAY_US, ACP_PSP_TIMEOUT_US, false,
+ adata->smn_dev, MP0_C2PMSG_73_REG);
+
+ if (ret) {
+ dev_err(sdev->dev, "Failed to get Doorbell from MBOX %x\n", MP0_C2PMSG_73_REG);
+ return ret;
+ }
+
+ /* Check if PSP is ready for new command */
+ ret = psp_mbox_ready(adata, 0);
+ if (ret)
+ return ret;
+
+ smn_write(adata->smn_dev, MP0_C2PMSG_114_REG, cmd);
+
+ /* Ring the Doorbell for PSP */
+ smn_write(adata->smn_dev, MP0_C2PMSG_73_REG, data);
+
+ /* Check MBOX ready as PSP ack */
+ ret = psp_mbox_ready(adata, 1);
+
+ return ret;
+}
+
+int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr,
+ unsigned int start_addr, unsigned int dest_addr,
+ unsigned int image_length)
+{
+ struct snd_sof_dev *sdev = adata->dev;
+ const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata);
+ unsigned int tx_count, fw_qualifier, val;
+ int ret;
+
+ if (!image_addr) {
+ dev_err(sdev->dev, "SHA DMA image address is NULL\n");
+ return -EINVAL;
+ }
+
+ val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD);
+ if (val & ACP_SHA_RUN) {
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD, ACP_SHA_RESET);
+ ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD_STS,
+ val, val & ACP_SHA_RESET,
+ ACP_REG_POLL_INTERVAL,
+ ACP_REG_POLL_TIMEOUT_US);
+ if (ret < 0) {
+ dev_err(sdev->dev, "SHA DMA Failed to Reset\n");
+ return ret;
+ }
+ }
+
+ if (adata->signed_fw_image)
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_INCLUDE_HDR, ACP_SHA_HEADER);
+
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_STRT_ADDR, start_addr);
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_DESTINATION_ADDR, dest_addr);
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_MSG_LENGTH, image_length);
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD, ACP_SHA_RUN);
+
+ ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_SHA_TRANSFER_BYTE_CNT,
+ tx_count, tx_count == image_length,
+ ACP_REG_POLL_INTERVAL, ACP_DMA_COMPLETE_TIMEOUT_US);
+ if (ret < 0) {
+ dev_err(sdev->dev, "SHA DMA Failed to Transfer Length %x\n", tx_count);
+ return ret;
+ }
+
+ /* psp_send_cmd only required for renoir platform (rev - 3) */
+ if (desc->rev == 3) {
+ ret = psp_send_cmd(adata, MBOX_ACP_SHA_DMA_COMMAND);
+ if (ret)
+ return ret;
+ }
+
+ /* psp_send_cmd only required for vangogh platform (rev - 5) */
+ if (desc->rev == 5) {
+ /* Modify IRAM and DRAM size */
+ ret = psp_send_cmd(adata, MBOX_ACP_IRAM_DRAM_FENCE_COMMAND | IRAM_DRAM_FENCE_2);
+ if (ret)
+ return ret;
+ ret = psp_send_cmd(adata, MBOX_ACP_IRAM_DRAM_FENCE_COMMAND | MBOX_ISREADY_FLAG);
+ if (ret)
+ return ret;
+ }
+
+ ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_SHA_DSP_FW_QUALIFIER,
+ fw_qualifier, fw_qualifier & DSP_FW_RUN_ENABLE,
+ ACP_REG_POLL_INTERVAL, ACP_DMA_COMPLETE_TIMEOUT_US);
+ if (ret < 0) {
+ dev_err(sdev->dev, "PSP validation failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+int acp_dma_status(struct acp_dev_data *adata, unsigned char ch)
+{
+ struct snd_sof_dev *sdev = adata->dev;
+ unsigned int val;
+ int ret = 0;
+
+ val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_DMA_CNTL_0 + ch * sizeof(u32));
+ if (val & ACP_DMA_CH_RUN) {
+ ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_DMA_CH_STS, val, !val,
+ ACP_REG_POLL_INTERVAL,
+ ACP_DMA_COMPLETE_TIMEOUT_US);
+ if (ret < 0)
+ dev_err(sdev->dev, "DMA_CHANNEL %d status timeout\n", ch);
+ }
+
+ return ret;
+}
+
+void memcpy_from_scratch(struct snd_sof_dev *sdev, u32 offset, unsigned int *dst, size_t bytes)
+{
+ unsigned int reg_offset = offset + ACP_SCRATCH_REG_0;
+ int i, j;
+
+ for (i = 0, j = 0; i < bytes; i = i + 4, j++)
+ dst[j] = snd_sof_dsp_read(sdev, ACP_DSP_BAR, reg_offset + i);
+}
+
+void memcpy_to_scratch(struct snd_sof_dev *sdev, u32 offset, unsigned int *src, size_t bytes)
+{
+ unsigned int reg_offset = offset + ACP_SCRATCH_REG_0;
+ int i, j;
+
+ for (i = 0, j = 0; i < bytes; i = i + 4, j++)
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, reg_offset + i, src[j]);
+}
+
+static int acp_memory_init(struct snd_sof_dev *sdev)
+{
+ struct acp_dev_data *adata = sdev->pdata->hw_pdata;
+ const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata);
+
+ snd_sof_dsp_update_bits(sdev, ACP_DSP_BAR, desc->dsp_intr_base + DSP_SW_INTR_CNTL_OFFSET,
+ ACP_DSP_INTR_EN_MASK, ACP_DSP_INTR_EN_MASK);
+ init_dma_descriptor(adata);
+
+ return 0;
+}
+
+static irqreturn_t acp_irq_thread(int irq, void *context)
+{
+ struct snd_sof_dev *sdev = context;
+ const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata);
+ unsigned int count = ACP_HW_SEM_RETRY_COUNT;
+
+ spin_lock_irq(&sdev->ipc_lock);
+ /* Wait until acquired HW Semaphore lock or timeout */
+ while (snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->hw_semaphore_offset) && --count)
+ ;
+ spin_unlock_irq(&sdev->ipc_lock);
+
+ if (!count) {
+ dev_err(sdev->dev, "%s: Failed to acquire HW lock\n", __func__);
+ return IRQ_NONE;
+ }
+
+ sof_ops(sdev)->irq_thread(irq, sdev);
+ /* Unlock or Release HW Semaphore */
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->hw_semaphore_offset, 0x0);
+
+ return IRQ_HANDLED;
+};
+
+static irqreturn_t acp_irq_handler(int irq, void *dev_id)
+{
+ struct amd_sdw_manager *amd_manager;
+ struct snd_sof_dev *sdev = dev_id;
+ const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata);
+ struct acp_dev_data *adata = sdev->pdata->hw_pdata;
+ unsigned int base = desc->dsp_intr_base;
+ unsigned int val;
+ int irq_flag = 0;
+
+ val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, base + DSP_SW_INTR_STAT_OFFSET);
+ if (val & ACP_DSP_TO_HOST_IRQ) {
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, base + DSP_SW_INTR_STAT_OFFSET,
+ ACP_DSP_TO_HOST_IRQ);
+ return IRQ_WAKE_THREAD;
+ }
+
+ val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->ext_intr_stat);
+ if (val & ACP_SDW0_IRQ_MASK) {
+ amd_manager = dev_get_drvdata(&adata->sdw->pdev[0]->dev);
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ACP_SDW0_IRQ_MASK);
+ if (amd_manager)
+ schedule_work(&amd_manager->amd_sdw_irq_thread);
+ irq_flag = 1;
+ }
+
+ if (val & ACP_ERROR_IRQ_MASK) {
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ACP_ERROR_IRQ_MASK);
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, base + ACP_SW0_I2S_ERROR_REASON, 0);
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, base + ACP_SW1_I2S_ERROR_REASON, 0);
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, base + ACP_ERROR_STATUS, 0);
+ irq_flag = 1;
+ }
+
+ if (desc->ext_intr_stat1) {
+ val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->ext_intr_stat1);
+ if (val & ACP_SDW1_IRQ_MASK) {
+ amd_manager = dev_get_drvdata(&adata->sdw->pdev[1]->dev);
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat1,
+ ACP_SDW1_IRQ_MASK);
+ if (amd_manager)
+ schedule_work(&amd_manager->amd_sdw_irq_thread);
+ irq_flag = 1;
+ }
+ }
+ if (irq_flag)
+ return IRQ_HANDLED;
+ else
+ return IRQ_NONE;
+}
+
+static int acp_power_on(struct snd_sof_dev *sdev)
+{
+ const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata);
+ unsigned int base = desc->pgfsm_base;
+ unsigned int val;
+ int ret;
+
+ val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, base + PGFSM_STATUS_OFFSET);
+
+ if (val == ACP_POWERED_ON)
+ return 0;
+
+ if (val & ACP_PGFSM_STATUS_MASK)
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, base + PGFSM_CONTROL_OFFSET,
+ ACP_PGFSM_CNTL_POWER_ON_MASK);
+
+ ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, base + PGFSM_STATUS_OFFSET, val,
+ !val, ACP_REG_POLL_INTERVAL, ACP_REG_POLL_TIMEOUT_US);
+ if (ret < 0)
+ dev_err(sdev->dev, "timeout in ACP_PGFSM_STATUS read\n");
+
+ return ret;
+}
+
+static int acp_reset(struct snd_sof_dev *sdev)
+{
+ const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata);
+ unsigned int val;
+ int ret;
+
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SOFT_RESET, ACP_ASSERT_RESET);
+
+ ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_SOFT_RESET, val,
+ val & ACP_SOFT_RESET_DONE_MASK,
+ ACP_REG_POLL_INTERVAL, ACP_REG_POLL_TIMEOUT_US);
+ if (ret < 0) {
+ dev_err(sdev->dev, "timeout asserting reset\n");
+ return ret;
+ }
+
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SOFT_RESET, ACP_RELEASE_RESET);
+
+ ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_SOFT_RESET, val, !val,
+ ACP_REG_POLL_INTERVAL, ACP_REG_POLL_TIMEOUT_US);
+ if (ret < 0)
+ dev_err(sdev->dev, "timeout in releasing reset\n");
+
+ if (desc->acp_clkmux_sel)
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->acp_clkmux_sel, ACP_CLOCK_ACLK);
+
+ if (desc->ext_intr_enb)
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_enb, 0x01);
+
+ if (desc->ext_intr_cntl)
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_cntl, ACP_ERROR_IRQ_MASK);
+ return ret;
+}
+
+static int acp_dsp_reset(struct snd_sof_dev *sdev)
+{
+ unsigned int val;
+ int ret;
+
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SOFT_RESET, ACP_DSP_ASSERT_RESET);
+
+ ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_SOFT_RESET, val,
+ val & ACP_DSP_SOFT_RESET_DONE_MASK,
+ ACP_REG_POLL_INTERVAL, ACP_REG_POLL_TIMEOUT_US);
+ if (ret < 0) {
+ dev_err(sdev->dev, "timeout asserting reset\n");
+ return ret;
+ }
+
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SOFT_RESET, ACP_DSP_RELEASE_RESET);
+
+ ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_SOFT_RESET, val, !val,
+ ACP_REG_POLL_INTERVAL, ACP_REG_POLL_TIMEOUT_US);
+ if (ret < 0)
+ dev_err(sdev->dev, "timeout in releasing reset\n");
+
+ return ret;
+}
+
+static int acp_init(struct snd_sof_dev *sdev)
+{
+ int ret;
+
+ /* power on */
+ ret = acp_power_on(sdev);
+ if (ret) {
+ dev_err(sdev->dev, "ACP power on failed\n");
+ return ret;
+ }
+
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_CONTROL, 0x01);
+ /* Reset */
+ return acp_reset(sdev);
+}
+
+static bool check_acp_sdw_enable_status(struct snd_sof_dev *sdev)
+{
+ struct acp_dev_data *acp_data;
+ u32 sdw0_en, sdw1_en;
+
+ acp_data = sdev->pdata->hw_pdata;
+ if (!acp_data->sdw)
+ return false;
+
+ sdw0_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SW0_EN);
+ sdw1_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SW1_EN);
+ acp_data->sdw_en_stat = sdw0_en || sdw1_en;
+ return acp_data->sdw_en_stat;
+}
+
+int amd_sof_acp_suspend(struct snd_sof_dev *sdev, u32 target_state)
+{
+ int ret;
+
+ /* When acp_reset() function is invoked, it will apply ACP SOFT reset and
+ * DSP reset. ACP Soft reset sequence will cause all ACP IP registers will
+ * be reset to default values which will break the ClockStop Mode functionality.
+ * Add a condition check to apply DSP reset when SoundWire ClockStop mode
+ * is selected. For the rest of the scenarios, apply acp reset sequence.
+ */
+ if (check_acp_sdw_enable_status(sdev))
+ return acp_dsp_reset(sdev);
+
+ ret = acp_reset(sdev);
+ if (ret) {
+ dev_err(sdev->dev, "ACP Reset failed\n");
+ return ret;
+ }
+
+ snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_CONTROL, 0x00);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(amd_sof_acp_suspend, SND_SOC_SOF_AMD_COMMON);
+
+int amd_sof_acp_resume(struct snd_sof_dev *sdev)
+{
+ int ret;
+ struct acp_dev_data *acp_data;
+
+ acp_data = sdev->pdata->hw_pdata;
+ if (!acp_data->sdw_en_stat) {
+ ret = acp_init(sdev);
+ if (ret) {
+ dev_err(sdev->dev, "ACP Init failed\n");
+ return ret;
+ }
+ return acp_memory_init(sdev);
+ } else {
+ return acp_dsp_reset(sdev);
+ }
+}
+EXPORT_SYMBOL_NS(amd_sof_acp_resume, SND_SOC_SOF_AMD_COMMON);
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_AMD_SOUNDWIRE)
+static int acp_sof_scan_sdw_devices(struct snd_sof_dev *sdev, u64 addr)
+{
+ struct acpi_device *sdw_dev;
+ struct acp_dev_data *acp_data;
+ const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata);
+
+ if (!addr)
+ return -ENODEV;
+
+ acp_data = sdev->pdata->hw_pdata;
+ sdw_dev = acpi_find_child_device(ACPI_COMPANION(sdev->dev), addr, 0);
+ if (!sdw_dev)
+ return -ENODEV;
+
+ acp_data->info.handle = sdw_dev->handle;
+ acp_data->info.count = desc->sdw_max_link_count;
+
+ return amd_sdw_scan_controller(&acp_data->info);
+}
+
+static int amd_sof_sdw_probe(struct snd_sof_dev *sdev)
+{
+ struct acp_dev_data *acp_data;
+ struct sdw_amd_res sdw_res;
+ int ret;
+
+ acp_data = sdev->pdata->hw_pdata;
+
+ memset(&sdw_res, 0, sizeof(sdw_res));
+ sdw_res.addr = acp_data->addr;
+ sdw_res.reg_range = acp_data->reg_range;
+ sdw_res.handle = acp_data->info.handle;
+ sdw_res.parent = sdev->dev;
+ sdw_res.dev = sdev->dev;
+ sdw_res.acp_lock = &acp_data->acp_lock;
+ sdw_res.count = acp_data->info.count;
+ sdw_res.link_mask = acp_data->info.link_mask;
+ sdw_res.mmio_base = sdev->bar[ACP_DSP_BAR];
+
+ ret = sdw_amd_probe(&sdw_res, &acp_data->sdw);
+ if (ret)
+ dev_err(sdev->dev, "SoundWire probe failed\n");
+ return ret;
+}
+
+static int amd_sof_sdw_exit(struct snd_sof_dev *sdev)
+{
+ struct acp_dev_data *acp_data;
+
+ acp_data = sdev->pdata->hw_pdata;
+ if (acp_data->sdw)
+ sdw_amd_exit(acp_data->sdw);
+ acp_data->sdw = NULL;
+
+ return 0;
+}
+
+#else
+static int acp_sof_scan_sdw_devices(struct snd_sof_dev *sdev, u64 addr)
+{
+ return 0;
+}
+
+static int amd_sof_sdw_probe(struct snd_sof_dev *sdev)
+{
+ return 0;
+}
+
+static int amd_sof_sdw_exit(struct snd_sof_dev *sdev)
+{
+ return 0;
+}
+#endif
+
+int amd_sof_acp_probe(struct snd_sof_dev *sdev)
+{
+ struct pci_dev *pci = to_pci_dev(sdev->dev);
+ struct acp_dev_data *adata;
+ const struct sof_amd_acp_desc *chip;
+ const struct dmi_system_id *dmi_id;
+ unsigned int addr;
+ int ret;
+
+ chip = get_chip_info(sdev->pdata);
+ if (!chip) {
+ dev_err(sdev->dev, "no such device supported, chip id:%x\n", pci->device);
+ return -EIO;
+ }
+ adata = devm_kzalloc(sdev->dev, sizeof(struct acp_dev_data),
+ GFP_KERNEL);
+ if (!adata)
+ return -ENOMEM;
+
+ adata->dev = sdev;
+ adata->dmic_dev = platform_device_register_data(sdev->dev, "dmic-codec",
+ PLATFORM_DEVID_NONE, NULL, 0);
+ if (IS_ERR(adata->dmic_dev)) {
+ dev_err(sdev->dev, "failed to register platform for dmic codec\n");
+ return PTR_ERR(adata->dmic_dev);
+ }
+ addr = pci_resource_start(pci, ACP_DSP_BAR);
+ sdev->bar[ACP_DSP_BAR] = devm_ioremap(sdev->dev, addr, pci_resource_len(pci, ACP_DSP_BAR));
+ if (!sdev->bar[ACP_DSP_BAR]) {
+ dev_err(sdev->dev, "ioremap error\n");
+ ret = -ENXIO;
+ goto unregister_dev;
+ }
+
+ pci_set_master(pci);
+ adata->addr = addr;
+ adata->reg_range = chip->reg_end_addr - chip->reg_start_addr;
+ mutex_init(&adata->acp_lock);
+ sdev->pdata->hw_pdata = adata;
+ adata->smn_dev = pci_get_device(PCI_VENDOR_ID_AMD, chip->host_bridge_id, NULL);
+ if (!adata->smn_dev) {
+ dev_err(sdev->dev, "Failed to get host bridge device\n");
+ ret = -ENODEV;
+ goto unregister_dev;
+ }
+
+ sdev->ipc_irq = pci->irq;
+ ret = request_threaded_irq(sdev->ipc_irq, acp_irq_handler, acp_irq_thread,
+ IRQF_SHARED, "AudioDSP", sdev);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to register IRQ %d\n",
+ sdev->ipc_irq);
+ goto free_smn_dev;
+ }
+
+ ret = acp_init(sdev);
+ if (ret < 0)
+ goto free_ipc_irq;
+
+ /* scan SoundWire capabilities exposed by DSDT */
+ ret = acp_sof_scan_sdw_devices(sdev, chip->sdw_acpi_dev_addr);
+ if (ret < 0) {
+ dev_dbg(sdev->dev, "skipping SoundWire, not detected with ACPI scan\n");
+ goto skip_soundwire;
+ }
+ ret = amd_sof_sdw_probe(sdev);
+ if (ret < 0) {
+ dev_err(sdev->dev, "error: SoundWire probe error\n");
+ free_irq(sdev->ipc_irq, sdev);
+ pci_dev_put(adata->smn_dev);
+ return ret;
+ }
+
+skip_soundwire:
+ sdev->dsp_box.offset = 0;
+ sdev->dsp_box.size = BOX_SIZE_512;
+
+ sdev->host_box.offset = sdev->dsp_box.offset + sdev->dsp_box.size;
+ sdev->host_box.size = BOX_SIZE_512;
+
+ sdev->debug_box.offset = sdev->host_box.offset + sdev->host_box.size;
+ sdev->debug_box.size = BOX_SIZE_1024;
+
+ adata->signed_fw_image = false;
+ dmi_id = dmi_first_match(acp_sof_quirk_table);
+ if (dmi_id && dmi_id->driver_data) {
+ adata->fw_code_bin = devm_kasprintf(sdev->dev, GFP_KERNEL,
+ "sof-%s-code.bin",
+ chip->name);
+ if (!adata->fw_code_bin) {
+ ret = -ENOMEM;
+ goto free_ipc_irq;
+ }
+
+ adata->fw_data_bin = devm_kasprintf(sdev->dev, GFP_KERNEL,
+ "sof-%s-data.bin",
+ chip->name);
+ if (!adata->fw_data_bin) {
+ ret = -ENOMEM;
+ goto free_ipc_irq;
+ }
+
+ adata->signed_fw_image = dmi_id->driver_data;
+ }
+
+ adata->enable_fw_debug = enable_fw_debug;
+ acp_memory_init(sdev);
+
+ acp_dsp_stream_init(sdev);
+
+ return 0;
+
+free_ipc_irq:
+ free_irq(sdev->ipc_irq, sdev);
+free_smn_dev:
+ pci_dev_put(adata->smn_dev);
+unregister_dev:
+ platform_device_unregister(adata->dmic_dev);
+ return ret;
+}
+EXPORT_SYMBOL_NS(amd_sof_acp_probe, SND_SOC_SOF_AMD_COMMON);
+
+void amd_sof_acp_remove(struct snd_sof_dev *sdev)
+{
+ struct acp_dev_data *adata = sdev->pdata->hw_pdata;
+
+ if (adata->smn_dev)
+ pci_dev_put(adata->smn_dev);
+
+ if (adata->sdw)
+ amd_sof_sdw_exit(sdev);
+
+ if (sdev->ipc_irq)
+ free_irq(sdev->ipc_irq, sdev);
+
+ if (adata->dmic_dev)
+ platform_device_unregister(adata->dmic_dev);
+
+ acp_reset(sdev);
+}
+EXPORT_SYMBOL_NS(amd_sof_acp_remove, SND_SOC_SOF_AMD_COMMON);
+
+MODULE_DESCRIPTION("AMD ACP sof driver");
+MODULE_IMPORT_NS(SOUNDWIRE_AMD_INIT);
+MODULE_IMPORT_NS(SND_AMD_SOUNDWIRE_ACPI);
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h
new file mode 100644
index 000000000000..947068da39b5
--- /dev/null
+++ b/sound/soc/sof/amd/acp.h
@@ -0,0 +1,347 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+/*
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * Copyright(c) 2021, 2023 Advanced Micro Devices, Inc. All rights reserved.
+ *
+ * Author: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com>
+ */
+
+#ifndef __SOF_AMD_ACP_H
+#define __SOF_AMD_ACP_H
+
+#include <linux/dmi.h>
+#include <linux/soundwire/sdw_amd.h>
+#include "../sof-priv.h"
+#include "../sof-audio.h"
+
+#define ACP_MAX_STREAM 8
+
+#define ACP_DSP_BAR 0
+
+#define ACP_HW_SEM_RETRY_COUNT 10000
+#define ACP_REG_POLL_INTERVAL 500
+#define ACP_REG_POLL_TIMEOUT_US 2000
+#define ACP_DMA_COMPLETE_TIMEOUT_US 5000
+
+#define ACP_PGFSM_CNTL_POWER_ON_MASK 0x01
+#define ACP_PGFSM_STATUS_MASK 0x03
+#define ACP_POWERED_ON 0x00
+#define ACP_ASSERT_RESET 0x01
+#define ACP_RELEASE_RESET 0x00
+#define ACP_SOFT_RESET_DONE_MASK 0x00010001
+#define ACP_DSP_ASSERT_RESET 0x04
+#define ACP_DSP_RELEASE_RESET 0x00
+#define ACP_DSP_SOFT_RESET_DONE_MASK 0x00050004
+
+#define ACP_DSP_INTR_EN_MASK 0x00000001
+#define ACP3X_SRAM_PTE_OFFSET 0x02050000
+#define ACP5X_SRAM_PTE_OFFSET 0x02050000
+#define ACP6X_SRAM_PTE_OFFSET 0x03800000
+#define PAGE_SIZE_4K_ENABLE 0x2
+#define ACP_PAGE_SIZE 0x1000
+#define ACP_DMA_CH_RUN 0x02
+#define ACP_MAX_DESC_CNT 0x02
+#define DSP_FW_RUN_ENABLE 0x01
+#define ACP_SHA_RUN 0x01
+#define ACP_SHA_RESET 0x02
+#define ACP_SHA_HEADER 0x01
+#define ACP_DMA_CH_RST 0x01
+#define ACP_DMA_CH_GRACEFUL_RST_EN 0x10
+#define ACP_ATU_CACHE_INVALID 0x01
+#define ACP_MAX_DESC 128
+#define ACPBUS_REG_BASE_OFFSET ACP_DMA_CNTL_0
+
+#define ACP_DEFAULT_DRAM_LENGTH 0x00080000
+#define ACP3X_SCRATCH_MEMORY_ADDRESS 0x02050000
+#define ACP_SYSTEM_MEMORY_WINDOW 0x4000000
+#define ACP_IRAM_BASE_ADDRESS 0x000000
+#define ACP_DRAM_BASE_ADDRESS 0x01000000
+#define ACP_DRAM_PAGE_COUNT 128
+#define ACP_SRAM_BASE_ADDRESS 0x3806000
+#define ACP_DSP_TO_HOST_IRQ 0x04
+
+#define ACP_RN_PCI_ID 0x01
+#define ACP_VANGOGH_PCI_ID 0x50
+#define ACP_RMB_PCI_ID 0x6F
+#define ACP63_PCI_ID 0x63
+
+#define HOST_BRIDGE_CZN 0x1630
+#define HOST_BRIDGE_VGH 0x1645
+#define HOST_BRIDGE_RMB 0x14B5
+#define HOST_BRIDGE_ACP63 0x14E8
+#define ACP_SHA_STAT 0x8000
+#define ACP_PSP_TIMEOUT_US 1000000
+#define ACP_EXT_INTR_ERROR_STAT 0x20000000
+#define MP0_C2PMSG_114_REG 0x3810AC8
+#define MP0_C2PMSG_73_REG 0x3810A24
+#define MBOX_ACP_SHA_DMA_COMMAND 0x70000
+#define MBOX_ACP_IRAM_DRAM_FENCE_COMMAND 0x80000
+#define MBOX_DELAY_US 1000
+#define MBOX_READY_MASK 0x80000000
+#define MBOX_STATUS_MASK 0xFFFF
+#define MBOX_ISREADY_FLAG 0x40000000
+#define IRAM_DRAM_FENCE_0 0X0
+#define IRAM_DRAM_FENCE_1 0X01
+#define IRAM_DRAM_FENCE_2 0X02
+
+#define BOX_SIZE_512 0x200
+#define BOX_SIZE_1024 0x400
+
+#define EXCEPT_MAX_HDR_SIZE 0x400
+#define AMD_STACK_DUMP_SIZE 32
+
+#define SRAM1_SIZE 0x280000
+#define PROBE_STATUS_BIT BIT(31)
+
+#define ACP_FIRMWARE_SIGNATURE 0x100
+#define ACP_ERROR_IRQ_MASK BIT(29)
+#define ACP_SDW0_IRQ_MASK BIT(21)
+#define ACP_SDW1_IRQ_MASK BIT(2)
+#define SDW_ACPI_ADDR_ACP63 5
+#define ACP_DEFAULT_SRAM_LENGTH 0x00080000
+#define ACP_SRAM_PAGE_COUNT 128
+#define ACP6X_SDW_MAX_MANAGER_COUNT 2
+
+enum clock_source {
+ ACP_CLOCK_96M = 0,
+ ACP_CLOCK_48M,
+ ACP_CLOCK_24M,
+ ACP_CLOCK_ACLK,
+ ACP_CLOCK_MCLK,
+};
+
+struct acp_atu_grp_pte {
+ u32 low;
+ u32 high;
+};
+
+union dma_tx_cnt {
+ struct {
+ unsigned int count : 19;
+ unsigned int reserved : 12;
+ unsigned ioc : 1;
+ } bitfields, bits;
+ unsigned int u32_all;
+ signed int i32_all;
+};
+
+struct dma_descriptor {
+ unsigned int src_addr;
+ unsigned int dest_addr;
+ union dma_tx_cnt tx_cnt;
+ unsigned int reserved;
+};
+
+/* Scratch memory structure for communication b/w host and dsp */
+struct scratch_ipc_conf {
+ /* Debug memory */
+ u8 sof_debug_box[1024];
+ /* Exception memory*/
+ u8 sof_except_box[1024];
+ /* Stream buffer */
+ u8 sof_stream_box[1024];
+ /* Trace buffer */
+ u8 sof_trace_box[1024];
+ /* Host msg flag */
+ u32 sof_host_msg_write;
+ /* Host ack flag*/
+ u32 sof_host_ack_write;
+ /* DSP msg flag */
+ u32 sof_dsp_msg_write;
+ /* Dsp ack flag */
+ u32 sof_dsp_ack_write;
+};
+
+struct scratch_reg_conf {
+ struct scratch_ipc_conf info;
+ struct acp_atu_grp_pte grp1_pte[16];
+ struct acp_atu_grp_pte grp2_pte[16];
+ struct acp_atu_grp_pte grp3_pte[16];
+ struct acp_atu_grp_pte grp4_pte[16];
+ struct acp_atu_grp_pte grp5_pte[16];
+ struct acp_atu_grp_pte grp6_pte[16];
+ struct acp_atu_grp_pte grp7_pte[16];
+ struct acp_atu_grp_pte grp8_pte[16];
+ struct dma_descriptor dma_desc[64];
+ unsigned int reg_offset[8];
+ unsigned int buf_size[8];
+ u8 acp_tx_fifo_buf[256];
+ u8 acp_rx_fifo_buf[256];
+ unsigned int reserve[];
+};
+
+struct acp_dsp_stream {
+ struct list_head list;
+ struct snd_sof_dev *sdev;
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *dmab;
+ int num_pages;
+ int stream_tag;
+ int active;
+ unsigned int reg_offset;
+ size_t posn_offset;
+ struct snd_compr_stream *cstream;
+ u64 cstream_posn;
+};
+
+struct sof_amd_acp_desc {
+ unsigned int rev;
+ const char *name;
+ unsigned int host_bridge_id;
+ u32 pgfsm_base;
+ u32 ext_intr_enb;
+ u32 ext_intr_cntl;
+ u32 ext_intr_stat;
+ u32 ext_intr_stat1;
+ u32 dsp_intr_base;
+ u32 sram_pte_offset;
+ u32 hw_semaphore_offset;
+ u32 acp_clkmux_sel;
+ u32 fusion_dsp_offset;
+ u32 probe_reg_offset;
+ u32 reg_start_addr;
+ u32 reg_end_addr;
+ u32 sdw_max_link_count;
+ u64 sdw_acpi_dev_addr;
+};
+
+/* Common device data struct for ACP devices */
+struct acp_dev_data {
+ struct snd_sof_dev *dev;
+ const struct firmware *fw_dbin;
+ /* DMIC device */
+ struct platform_device *dmic_dev;
+ /* mutex lock to protect ACP common registers access */
+ struct mutex acp_lock;
+ /* ACPI information stored between scan and probe steps */
+ struct sdw_amd_acpi_info info;
+ /* sdw context allocated by SoundWire driver */
+ struct sdw_amd_ctx *sdw;
+ unsigned int fw_bin_size;
+ unsigned int fw_data_bin_size;
+ unsigned int fw_sram_data_bin_size;
+ const char *fw_code_bin;
+ const char *fw_data_bin;
+ const char *fw_sram_data_bin;
+ u32 fw_bin_page_count;
+ u32 fw_data_bin_page_count;
+ u32 addr;
+ u32 reg_range;
+ u32 blk_type;
+ dma_addr_t sha_dma_addr;
+ u8 *bin_buf;
+ dma_addr_t dma_addr;
+ u8 *data_buf;
+ dma_addr_t sram_dma_addr;
+ u8 *sram_data_buf;
+ bool signed_fw_image;
+ struct dma_descriptor dscr_info[ACP_MAX_DESC];
+ struct acp_dsp_stream stream_buf[ACP_MAX_STREAM];
+ struct acp_dsp_stream *dtrace_stream;
+ struct pci_dev *smn_dev;
+ struct acp_dsp_stream *probe_stream;
+ bool enable_fw_debug;
+ bool is_dram_in_use;
+ bool is_sram_in_use;
+ bool sdw_en_stat;
+};
+
+void memcpy_to_scratch(struct snd_sof_dev *sdev, u32 offset, unsigned int *src, size_t bytes);
+void memcpy_from_scratch(struct snd_sof_dev *sdev, u32 offset, unsigned int *dst, size_t bytes);
+
+int acp_dma_status(struct acp_dev_data *adata, unsigned char ch);
+int configure_and_run_dma(struct acp_dev_data *adata, unsigned int src_addr,
+ unsigned int dest_addr, int dsp_data_size);
+int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr,
+ unsigned int start_addr, unsigned int dest_addr,
+ unsigned int image_length);
+
+/* ACP device probe/remove */
+int amd_sof_acp_probe(struct snd_sof_dev *sdev);
+void amd_sof_acp_remove(struct snd_sof_dev *sdev);
+
+/* DSP Loader callbacks */
+int acp_sof_dsp_run(struct snd_sof_dev *sdev);
+int acp_dsp_pre_fw_run(struct snd_sof_dev *sdev);
+int acp_sof_load_signed_firmware(struct snd_sof_dev *sdev);
+int acp_get_bar_index(struct snd_sof_dev *sdev, u32 type);
+
+/* Block IO callbacks */
+int acp_dsp_block_write(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type,
+ u32 offset, void *src, size_t size);
+int acp_dsp_block_read(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type,
+ u32 offset, void *dest, size_t size);
+
+/* IPC callbacks */
+irqreturn_t acp_sof_ipc_irq_thread(int irq, void *context);
+int acp_sof_ipc_msg_data(struct snd_sof_dev *sdev, struct snd_sof_pcm_stream *sps,
+ void *p, size_t sz);
+int acp_set_stream_data_offset(struct snd_sof_dev *sdev,
+ struct snd_sof_pcm_stream *sps,
+ size_t posn_offset);
+int acp_sof_ipc_send_msg(struct snd_sof_dev *sdev,
+ struct snd_sof_ipc_msg *msg);
+int acp_sof_ipc_get_mailbox_offset(struct snd_sof_dev *sdev);
+int acp_sof_ipc_get_window_offset(struct snd_sof_dev *sdev, u32 id);
+void acp_mailbox_write(struct snd_sof_dev *sdev, u32 offset, void *message, size_t bytes);
+void acp_mailbox_read(struct snd_sof_dev *sdev, u32 offset, void *message, size_t bytes);
+
+/* ACP - DSP stream callbacks */
+int acp_dsp_stream_config(struct snd_sof_dev *sdev, struct acp_dsp_stream *stream);
+int acp_dsp_stream_init(struct snd_sof_dev *sdev);
+struct acp_dsp_stream *acp_dsp_stream_get(struct snd_sof_dev *sdev, int tag);
+int acp_dsp_stream_put(struct snd_sof_dev *sdev, struct acp_dsp_stream *acp_stream);
+
+/*
+ * DSP PCM Operations.
+ */
+int acp_pcm_open(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream);
+int acp_pcm_close(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream);
+int acp_pcm_hw_params(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_sof_platform_stream_params *platform_params);
+snd_pcm_uframes_t acp_pcm_pointer(struct snd_sof_dev *sdev,
+ struct snd_pcm_substream *substream);
+
+extern struct snd_sof_dsp_ops sof_acp_common_ops;
+
+extern struct snd_sof_dsp_ops sof_renoir_ops;
+int sof_renoir_ops_init(struct snd_sof_dev *sdev);
+extern struct snd_sof_dsp_ops sof_vangogh_ops;
+int sof_vangogh_ops_init(struct snd_sof_dev *sdev);
+extern struct snd_sof_dsp_ops sof_rembrandt_ops;
+int sof_rembrandt_ops_init(struct snd_sof_dev *sdev);
+extern struct snd_sof_dsp_ops sof_acp63_ops;
+int sof_acp63_ops_init(struct snd_sof_dev *sdev);
+
+struct snd_soc_acpi_mach *amd_sof_machine_select(struct snd_sof_dev *sdev);
+/* Machine configuration */
+int snd_amd_acp_find_config(struct pci_dev *pci);
+
+/* Trace */
+int acp_sof_trace_init(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab,
+ struct sof_ipc_dma_trace_params_ext *dtrace_params);
+int acp_sof_trace_release(struct snd_sof_dev *sdev);
+
+/* PM Callbacks */
+int amd_sof_acp_suspend(struct snd_sof_dev *sdev, u32 target_state);
+int amd_sof_acp_resume(struct snd_sof_dev *sdev);
+
+void amd_sof_ipc_dump(struct snd_sof_dev *sdev);
+void amd_sof_dump(struct snd_sof_dev *sdev, u32 flags);
+
+static inline const struct sof_amd_acp_desc *get_chip_info(struct snd_sof_pdata *pdata)
+{
+ const struct sof_dev_desc *desc = pdata->desc;
+
+ return desc->chip_info;
+}
+
+int acp_probes_register(struct snd_sof_dev *sdev);
+void acp_probes_unregister(struct snd_sof_dev *sdev);
+
+extern struct snd_soc_acpi_mach snd_soc_acpi_amd_vangogh_sof_machines[];
+extern const struct dmi_system_id acp_sof_quirk_table[];
+#endif
diff --git a/sound/soc/sof/amd/acp63.c b/sound/soc/sof/amd/acp63.c
new file mode 100644
index 000000000000..9fb645079c3a
--- /dev/null
+++ b/sound/soc/sof/amd/acp63.c
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2023 Advanced Micro Devices, Inc.
+//
+// Authors: Vijendar Mukunda <Vijendar.Mukunda@amd.com>
+
+/*
+ * Hardware interface for Audio DSP on ACP6.3 version based platform
+ */
+
+#include <linux/platform_device.h>
+#include <linux/module.h>
+
+#include "../ops.h"
+#include "../sof-audio.h"
+#include "acp.h"
+#include "acp-dsp-offset.h"
+
+#define I2S_HS_INSTANCE 0
+#define I2S_BT_INSTANCE 1
+#define I2S_SP_INSTANCE 2
+#define PDM_DMIC_INSTANCE 3
+#define I2S_HS_VIRTUAL_INSTANCE 4
+
+static struct snd_soc_dai_driver acp63_sof_dai[] = {
+ [I2S_HS_INSTANCE] = {
+ .id = I2S_HS_INSTANCE,
+ .name = "acp-sof-hs",
+ .playback = {
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 2,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ },
+ .capture = {
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
+ /* Supporting only stereo for I2S HS controller capture */
+ .channels_min = 2,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ },
+
+ [I2S_BT_INSTANCE] = {
+ .id = I2S_BT_INSTANCE,
+ .name = "acp-sof-bt",
+ .playback = {
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 2,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ },
+ .capture = {
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
+ /* Supporting only stereo for I2S BT controller capture */
+ .channels_min = 2,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ },
+
+ [I2S_SP_INSTANCE] = {
+ .id = I2S_SP_INSTANCE,
+ .name = "acp-sof-sp",
+ .playback = {
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 2,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ },
+ .capture = {
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
+ /* Supporting only stereo for I2S SP controller capture */
+ .channels_min = 2,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ },
+
+ [PDM_DMIC_INSTANCE] = {
+ .id = PDM_DMIC_INSTANCE,
+ .name = "acp-sof-dmic",
+ .capture = {
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 2,
+ .channels_max = 4,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ },
+
+ [I2S_HS_VIRTUAL_INSTANCE] = {
+ .id = I2S_HS_VIRTUAL_INSTANCE,
+ .name = "acp-sof-hs-virtual",
+ .playback = {
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 2,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ },
+ },
+};
+
+/* Phoenix ops */
+struct snd_sof_dsp_ops sof_acp63_ops;
+EXPORT_SYMBOL_NS(sof_acp63_ops, SND_SOC_SOF_AMD_COMMON);
+
+int sof_acp63_ops_init(struct snd_sof_dev *sdev)
+{
+ /* common defaults */
+ memcpy(&sof_acp63_ops, &sof_acp_common_ops, sizeof(struct snd_sof_dsp_ops));
+
+ sof_acp63_ops.drv = acp63_sof_dai;
+ sof_acp63_ops.num_drv = ARRAY_SIZE(acp63_sof_dai);
+
+ return 0;
+}
+
+MODULE_IMPORT_NS(SND_SOC_SOF_AMD_COMMON);
+MODULE_DESCRIPTION("ACP63 SOF Driver");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/sof/amd/pci-acp63.c b/sound/soc/sof/amd/pci-acp63.c
new file mode 100644
index 000000000000..eeaa12cceb23
--- /dev/null
+++ b/sound/soc/sof/amd/pci-acp63.c
@@ -0,0 +1,113 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2023 Advanced Micro Devices, Inc. All rights reserved.
+//
+// Authors: Vijendar Mukunda <Vijendar.Mukunda@amd.com>
+
+/*.
+ * PCI interface for ACP6.3 device
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <sound/sof.h>
+#include <sound/soc-acpi.h>
+
+#include "../ops.h"
+#include "../sof-pci-dev.h"
+#include "../../amd/mach-config.h"
+#include "acp.h"
+#include "acp-dsp-offset.h"
+
+#define ACP6X_FUTURE_REG_ACLK_0 0x1854
+#define ACP6x_REG_START 0x1240000
+#define ACP6x_REG_END 0x125C000
+
+static const struct sof_amd_acp_desc acp63_chip_info = {
+ .rev = 6,
+ .host_bridge_id = HOST_BRIDGE_ACP63,
+ .pgfsm_base = ACP6X_PGFSM_BASE,
+ .ext_intr_enb = ACP6X_EXTERNAL_INTR_ENB,
+ .ext_intr_cntl = ACP6X_EXTERNAL_INTR_CNTL,
+ .ext_intr_stat = ACP6X_EXT_INTR_STAT,
+ .ext_intr_stat1 = ACP6X_EXT_INTR_STAT1,
+ .dsp_intr_base = ACP6X_DSP_SW_INTR_BASE,
+ .sram_pte_offset = ACP6X_SRAM_PTE_OFFSET,
+ .hw_semaphore_offset = ACP6X_AXI2DAGB_SEM_0,
+ .fusion_dsp_offset = ACP6X_DSP_FUSION_RUNSTALL,
+ .probe_reg_offset = ACP6X_FUTURE_REG_ACLK_0,
+ .sdw_max_link_count = ACP6X_SDW_MAX_MANAGER_COUNT,
+ .sdw_acpi_dev_addr = SDW_ACPI_ADDR_ACP63,
+ .reg_start_addr = ACP6x_REG_START,
+ .reg_end_addr = ACP6x_REG_END,
+};
+
+static const struct sof_dev_desc acp63_desc = {
+ .machines = snd_soc_acpi_amd_acp63_sof_machines,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = -1,
+ .resindex_imr_base = -1,
+ .irqindex_host_ipc = -1,
+ .chip_info = &acp63_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "amd/sof",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "amd/sof-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-acp_6_3.ri",
+ },
+ .nocodec_tplg_filename = "sof-acp.tplg",
+ .ops = &sof_acp63_ops,
+ .ops_init = sof_acp63_ops_init,
+};
+
+static int acp63_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
+{
+ unsigned int flag;
+
+ if (pci->revision != ACP63_PCI_ID)
+ return -ENODEV;
+
+ flag = snd_amd_acp_find_config(pci);
+ if (flag != FLAG_AMD_SOF && flag != FLAG_AMD_SOF_ONLY_DMIC)
+ return -ENODEV;
+
+ return sof_pci_probe(pci, pci_id);
+};
+
+static void acp63_pci_remove(struct pci_dev *pci)
+{
+ sof_pci_remove(pci);
+}
+
+/* PCI IDs */
+static const struct pci_device_id acp63_pci_ids[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID),
+ .driver_data = (unsigned long)&acp63_desc},
+ { 0, }
+};
+MODULE_DEVICE_TABLE(pci, acp63_pci_ids);
+
+/* pci_driver definition */
+static struct pci_driver snd_sof_pci_amd_acp63_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = acp63_pci_ids,
+ .probe = acp63_pci_probe,
+ .remove = acp63_pci_remove,
+ .driver = {
+ .pm = &sof_pci_pm,
+ },
+};
+module_pci_driver(snd_sof_pci_amd_acp63_driver);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_IMPORT_NS(SND_SOC_SOF_AMD_COMMON);
+MODULE_IMPORT_NS(SND_SOC_SOF_PCI_DEV);
diff --git a/sound/soc/sof/amd/pci-rmb.c b/sound/soc/sof/amd/pci-rmb.c
new file mode 100644
index 000000000000..2f288545c426
--- /dev/null
+++ b/sound/soc/sof/amd/pci-rmb.c
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2022 Advanced Micro Devices, Inc. All rights reserved.
+//
+// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com>
+
+/*.
+ * PCI interface for Rembrandt ACP device
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <sound/sof.h>
+#include <sound/soc-acpi.h>
+
+#include "../ops.h"
+#include "../sof-pci-dev.h"
+#include "../../amd/mach-config.h"
+#include "acp.h"
+#include "acp-dsp-offset.h"
+
+#define ACP6x_REG_START 0x1240000
+#define ACP6x_REG_END 0x125C000
+#define ACP6X_FUTURE_REG_ACLK_0 0x1854
+
+static const struct sof_amd_acp_desc rembrandt_chip_info = {
+ .rev = 6,
+ .host_bridge_id = HOST_BRIDGE_RMB,
+ .pgfsm_base = ACP6X_PGFSM_BASE,
+ .ext_intr_stat = ACP6X_EXT_INTR_STAT,
+ .dsp_intr_base = ACP6X_DSP_SW_INTR_BASE,
+ .sram_pte_offset = ACP6X_SRAM_PTE_OFFSET,
+ .hw_semaphore_offset = ACP6X_AXI2DAGB_SEM_0,
+ .fusion_dsp_offset = ACP6X_DSP_FUSION_RUNSTALL,
+ .probe_reg_offset = ACP6X_FUTURE_REG_ACLK_0,
+};
+
+static const struct sof_dev_desc rembrandt_desc = {
+ .machines = snd_soc_acpi_amd_rmb_sof_machines,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = -1,
+ .resindex_imr_base = -1,
+ .irqindex_host_ipc = -1,
+ .chip_info = &rembrandt_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "amd/sof",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "amd/sof-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-rmb.ri",
+ },
+ .nocodec_tplg_filename = "sof-acp.tplg",
+ .ops = &sof_rembrandt_ops,
+ .ops_init = sof_rembrandt_ops_init,
+};
+
+static int acp_pci_rmb_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
+{
+ unsigned int flag;
+
+ if (pci->revision != ACP_RMB_PCI_ID)
+ return -ENODEV;
+
+ flag = snd_amd_acp_find_config(pci);
+ if (flag != FLAG_AMD_SOF && flag != FLAG_AMD_SOF_ONLY_DMIC)
+ return -ENODEV;
+
+ return sof_pci_probe(pci, pci_id);
+};
+
+static void acp_pci_rmb_remove(struct pci_dev *pci)
+{
+ sof_pci_remove(pci);
+}
+
+/* PCI IDs */
+static const struct pci_device_id rmb_pci_ids[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID),
+ .driver_data = (unsigned long)&rembrandt_desc},
+ { 0, }
+};
+MODULE_DEVICE_TABLE(pci, rmb_pci_ids);
+
+/* pci_driver definition */
+static struct pci_driver snd_sof_pci_amd_rmb_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = rmb_pci_ids,
+ .probe = acp_pci_rmb_probe,
+ .remove = acp_pci_rmb_remove,
+};
+module_pci_driver(snd_sof_pci_amd_rmb_driver);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_IMPORT_NS(SND_SOC_SOF_AMD_COMMON);
+MODULE_IMPORT_NS(SND_SOC_SOF_PCI_DEV);
diff --git a/sound/soc/sof/amd/pci-rn.c b/sound/soc/sof/amd/pci-rn.c
new file mode 100644
index 000000000000..a0195e9b400c
--- /dev/null
+++ b/sound/soc/sof/amd/pci-rn.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2021 Advanced Micro Devices, Inc. All rights reserved.
+//
+// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com>
+
+/*
+ * PCI interface for Renoir ACP device
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <sound/sof.h>
+#include <sound/soc-acpi.h>
+
+#include "../ops.h"
+#include "../sof-pci-dev.h"
+#include "../../amd/mach-config.h"
+#include "acp.h"
+#include "acp-dsp-offset.h"
+
+#define ACP3x_REG_START 0x1240000
+#define ACP3x_REG_END 0x125C000
+#define ACP3X_FUTURE_REG_ACLK_0 0x1860
+
+static const struct sof_amd_acp_desc renoir_chip_info = {
+ .rev = 3,
+ .host_bridge_id = HOST_BRIDGE_CZN,
+ .pgfsm_base = ACP3X_PGFSM_BASE,
+ .ext_intr_stat = ACP3X_EXT_INTR_STAT,
+ .dsp_intr_base = ACP3X_DSP_SW_INTR_BASE,
+ .sram_pte_offset = ACP3X_SRAM_PTE_OFFSET,
+ .hw_semaphore_offset = ACP3X_AXI2DAGB_SEM_0,
+ .acp_clkmux_sel = ACP3X_CLKMUX_SEL,
+ .probe_reg_offset = ACP3X_FUTURE_REG_ACLK_0,
+};
+
+static const struct sof_dev_desc renoir_desc = {
+ .machines = snd_soc_acpi_amd_sof_machines,
+ .use_acpi_target_states = true,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = -1,
+ .resindex_imr_base = -1,
+ .irqindex_host_ipc = -1,
+ .chip_info = &renoir_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "amd/sof",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "amd/sof-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-rn.ri",
+ },
+ .nocodec_tplg_filename = "sof-acp.tplg",
+ .ops = &sof_renoir_ops,
+ .ops_init = sof_renoir_ops_init,
+};
+
+static int acp_pci_rn_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
+{
+ unsigned int flag;
+
+ if (pci->revision != ACP_RN_PCI_ID)
+ return -ENODEV;
+
+ flag = snd_amd_acp_find_config(pci);
+ if (flag != FLAG_AMD_SOF && flag != FLAG_AMD_SOF_ONLY_DMIC)
+ return -ENODEV;
+
+ return sof_pci_probe(pci, pci_id);
+};
+
+static void acp_pci_rn_remove(struct pci_dev *pci)
+{
+ return sof_pci_remove(pci);
+}
+
+/* PCI IDs */
+static const struct pci_device_id rn_pci_ids[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID),
+ .driver_data = (unsigned long)&renoir_desc},
+ { 0, }
+};
+MODULE_DEVICE_TABLE(pci, rn_pci_ids);
+
+/* pci_driver definition */
+static struct pci_driver snd_sof_pci_amd_rn_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = rn_pci_ids,
+ .probe = acp_pci_rn_probe,
+ .remove = acp_pci_rn_remove,
+ .driver = {
+ .pm = &sof_pci_pm,
+ },
+};
+module_pci_driver(snd_sof_pci_amd_rn_driver);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_IMPORT_NS(SND_SOC_SOF_AMD_COMMON);
+MODULE_IMPORT_NS(SND_SOC_SOF_PCI_DEV);
diff --git a/sound/soc/sof/amd/pci-vangogh.c b/sound/soc/sof/amd/pci-vangogh.c
new file mode 100644
index 000000000000..5cd3ac84752f
--- /dev/null
+++ b/sound/soc/sof/amd/pci-vangogh.c
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2023 Advanced Micro Devices, Inc. All rights reserved.
+//
+// Authors: Venkata Prasad Potturu <venkataprasad.potturu@amd.com>
+
+/*.
+ * PCI interface for Vangogh ACP device
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <sound/sof.h>
+#include <sound/soc-acpi.h>
+
+#include "../ops.h"
+#include "../sof-pci-dev.h"
+#include "../../amd/mach-config.h"
+#include "acp.h"
+#include "acp-dsp-offset.h"
+
+#define ACP5X_FUTURE_REG_ACLK_0 0x1864
+
+static const struct sof_amd_acp_desc vangogh_chip_info = {
+ .rev = 5,
+ .name = "vangogh",
+ .host_bridge_id = HOST_BRIDGE_VGH,
+ .pgfsm_base = ACP5X_PGFSM_BASE,
+ .ext_intr_stat = ACP5X_EXT_INTR_STAT,
+ .dsp_intr_base = ACP5X_DSP_SW_INTR_BASE,
+ .sram_pte_offset = ACP5X_SRAM_PTE_OFFSET,
+ .hw_semaphore_offset = ACP5X_AXI2DAGB_SEM_0,
+ .acp_clkmux_sel = ACP5X_CLKMUX_SEL,
+ .probe_reg_offset = ACP5X_FUTURE_REG_ACLK_0,
+};
+
+static const struct sof_dev_desc vangogh_desc = {
+ .machines = snd_soc_acpi_amd_vangogh_sof_machines,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = -1,
+ .resindex_imr_base = -1,
+ .irqindex_host_ipc = -1,
+ .chip_info = &vangogh_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "amd/sof",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "amd/sof-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-vangogh.ri",
+ },
+ .nocodec_tplg_filename = "sof-acp.tplg",
+ .ops = &sof_vangogh_ops,
+ .ops_init = sof_vangogh_ops_init,
+};
+
+static int acp_pci_vgh_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
+{
+ unsigned int flag;
+
+ if (pci->revision != ACP_VANGOGH_PCI_ID)
+ return -ENODEV;
+
+ flag = snd_amd_acp_find_config(pci);
+ if (flag != FLAG_AMD_SOF && flag != FLAG_AMD_SOF_ONLY_DMIC)
+ return -ENODEV;
+
+ return sof_pci_probe(pci, pci_id);
+};
+
+static void acp_pci_vgh_remove(struct pci_dev *pci)
+{
+ sof_pci_remove(pci);
+}
+
+/* PCI IDs */
+static const struct pci_device_id vgh_pci_ids[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID),
+ .driver_data = (unsigned long)&vangogh_desc},
+ { 0, }
+};
+MODULE_DEVICE_TABLE(pci, vgh_pci_ids);
+
+/* pci_driver definition */
+static struct pci_driver snd_sof_pci_amd_vgh_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = vgh_pci_ids,
+ .probe = acp_pci_vgh_probe,
+ .remove = acp_pci_vgh_remove,
+ .driver = {
+ .pm = &sof_pci_pm,
+ },
+};
+module_pci_driver(snd_sof_pci_amd_vgh_driver);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_IMPORT_NS(SND_SOC_SOF_AMD_COMMON);
+MODULE_IMPORT_NS(SND_SOC_SOF_PCI_DEV);
diff --git a/sound/soc/sof/amd/rembrandt.c b/sound/soc/sof/amd/rembrandt.c
new file mode 100644
index 000000000000..f1d1ba57ab3a
--- /dev/null
+++ b/sound/soc/sof/amd/rembrandt.c
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2022 Advanced Micro Devices, Inc.
+//
+// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com>
+
+/*
+ * Hardware interface for Audio DSP on Rembrandt platform
+ */
+
+#include <linux/platform_device.h>
+#include <linux/module.h>
+
+#include "../ops.h"
+#include "../sof-audio.h"
+#include "acp.h"
+#include "acp-dsp-offset.h"
+
+#define I2S_HS_INSTANCE 0
+#define I2S_BT_INSTANCE 1
+#define I2S_SP_INSTANCE 2
+#define PDM_DMIC_INSTANCE 3
+#define I2S_HS_VIRTUAL_INSTANCE 4
+
+static struct snd_soc_dai_driver rembrandt_sof_dai[] = {
+ [I2S_HS_INSTANCE] = {
+ .id = I2S_HS_INSTANCE,
+ .name = "acp-sof-hs",
+ .playback = {
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 2,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ },
+ .capture = {
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
+ /* Supporting only stereo for I2S HS controller capture */
+ .channels_min = 2,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ },
+
+ [I2S_BT_INSTANCE] = {
+ .id = I2S_BT_INSTANCE,
+ .name = "acp-sof-bt",
+ .playback = {
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 2,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ },
+ .capture = {
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
+ /* Supporting only stereo for I2S BT controller capture */
+ .channels_min = 2,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ },
+
+ [I2S_SP_INSTANCE] = {
+ .id = I2S_SP_INSTANCE,
+ .name = "acp-sof-sp",
+ .playback = {
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 2,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ },
+ .capture = {
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
+ /* Supporting only stereo for I2S SP controller capture */
+ .channels_min = 2,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ },
+
+ [PDM_DMIC_INSTANCE] = {
+ .id = PDM_DMIC_INSTANCE,
+ .name = "acp-sof-dmic",
+ .capture = {
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 2,
+ .channels_max = 4,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ },
+
+ [I2S_HS_VIRTUAL_INSTANCE] = {
+ .id = I2S_HS_VIRTUAL_INSTANCE,
+ .name = "acp-sof-hs-virtual",
+ .playback = {
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 2,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ },
+ },
+};
+
+/* Rembrandt ops */
+struct snd_sof_dsp_ops sof_rembrandt_ops;
+EXPORT_SYMBOL_NS(sof_rembrandt_ops, SND_SOC_SOF_AMD_COMMON);
+
+int sof_rembrandt_ops_init(struct snd_sof_dev *sdev)
+{
+ /* common defaults */
+ memcpy(&sof_rembrandt_ops, &sof_acp_common_ops, sizeof(struct snd_sof_dsp_ops));
+
+ sof_rembrandt_ops.drv = rembrandt_sof_dai;
+ sof_rembrandt_ops.num_drv = ARRAY_SIZE(rembrandt_sof_dai);
+
+ return 0;
+}
+
+MODULE_IMPORT_NS(SND_SOC_SOF_AMD_COMMON);
+MODULE_DESCRIPTION("REMBRANDT SOF Driver");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/sof/amd/renoir.c b/sound/soc/sof/amd/renoir.c
new file mode 100644
index 000000000000..47b863f6258c
--- /dev/null
+++ b/sound/soc/sof/amd/renoir.c
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2021 Advanced Micro Devices, Inc.
+//
+// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com>
+
+/*
+ * Hardware interface for Audio DSP on Renoir platform
+ */
+
+#include <linux/platform_device.h>
+#include <linux/module.h>
+
+#include "../ops.h"
+#include "../sof-audio.h"
+#include "acp.h"
+#include "acp-dsp-offset.h"
+
+#define I2S_BT_INSTANCE 0
+#define I2S_SP_INSTANCE 1
+#define PDM_DMIC_INSTANCE 2
+#define I2S_SP_VIRTUAL_INSTANCE 3
+
+static struct snd_soc_dai_driver renoir_sof_dai[] = {
+ [I2S_BT_INSTANCE] = {
+ .id = I2S_BT_INSTANCE,
+ .name = "acp-sof-bt",
+ .playback = {
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 2,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ },
+ .capture = {
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
+ /* Supporting only stereo for I2S BT controller capture */
+ .channels_min = 2,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ },
+
+ [I2S_SP_INSTANCE] = {
+ .id = I2S_SP_INSTANCE,
+ .name = "acp-sof-sp",
+ .playback = {
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 2,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ },
+ .capture = {
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
+ /* Supporting only stereo for I2S SP controller capture */
+ .channels_min = 2,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ },
+
+ [PDM_DMIC_INSTANCE] = {
+ .id = PDM_DMIC_INSTANCE,
+ .name = "acp-sof-dmic",
+ .capture = {
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 2,
+ .channels_max = 4,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ },
+
+ [I2S_SP_VIRTUAL_INSTANCE] = {
+ .id = I2S_SP_VIRTUAL_INSTANCE,
+ .name = "acp-sof-sp-virtual",
+ .playback = {
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 2,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ },
+ },
+};
+
+/* Renoir ops */
+struct snd_sof_dsp_ops sof_renoir_ops;
+EXPORT_SYMBOL_NS(sof_renoir_ops, SND_SOC_SOF_AMD_COMMON);
+
+int sof_renoir_ops_init(struct snd_sof_dev *sdev)
+{
+ /* common defaults */
+ memcpy(&sof_renoir_ops, &sof_acp_common_ops, sizeof(struct snd_sof_dsp_ops));
+
+ sof_renoir_ops.drv = renoir_sof_dai;
+ sof_renoir_ops.num_drv = ARRAY_SIZE(renoir_sof_dai);
+
+ return 0;
+}
+
+MODULE_IMPORT_NS(SND_SOC_SOF_AMD_COMMON);
+MODULE_DESCRIPTION("RENOIR SOF Driver");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/sof/amd/vangogh.c b/sound/soc/sof/amd/vangogh.c
new file mode 100644
index 000000000000..de15d21aa6d9
--- /dev/null
+++ b/sound/soc/sof/amd/vangogh.c
@@ -0,0 +1,162 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2023 Advanced Micro Devices, Inc.
+//
+// Authors: Venkata Prasad Potturu <venkataprasad.potturu@amd.com>
+
+/*
+ * Hardware interface for Audio DSP on Vangogh platform
+ */
+
+#include <linux/platform_device.h>
+#include <linux/module.h>
+
+#include "../ops.h"
+#include "../sof-audio.h"
+#include "acp.h"
+#include "acp-dsp-offset.h"
+
+#define I2S_HS_INSTANCE 0
+#define I2S_BT_INSTANCE 1
+#define I2S_SP_INSTANCE 2
+#define PDM_DMIC_INSTANCE 3
+#define I2S_HS_VIRTUAL_INSTANCE 4
+
+static struct snd_soc_dai_driver vangogh_sof_dai[] = {
+ [I2S_HS_INSTANCE] = {
+ .id = I2S_HS_INSTANCE,
+ .name = "acp-sof-hs",
+ .playback = {
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 2,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ },
+ .capture = {
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
+ /* Supporting only stereo for I2S HS controller capture */
+ .channels_min = 2,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ },
+
+ [I2S_BT_INSTANCE] = {
+ .id = I2S_BT_INSTANCE,
+ .name = "acp-sof-bt",
+ .playback = {
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 2,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ },
+ .capture = {
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
+ /* Supporting only stereo for I2S BT controller capture */
+ .channels_min = 2,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ },
+
+ [I2S_SP_INSTANCE] = {
+ .id = I2S_SP_INSTANCE,
+ .name = "acp-sof-sp",
+ .playback = {
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 2,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ },
+ .capture = {
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
+ /* Supporting only stereo for I2S SP controller capture */
+ .channels_min = 2,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ },
+
+ [PDM_DMIC_INSTANCE] = {
+ .id = PDM_DMIC_INSTANCE,
+ .name = "acp-sof-dmic",
+ .capture = {
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 2,
+ .channels_max = 4,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ },
+
+ [I2S_HS_VIRTUAL_INSTANCE] = {
+ .id = I2S_HS_VIRTUAL_INSTANCE,
+ .name = "acp-sof-hs-virtual",
+ .playback = {
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
+ .channels_min = 2,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ },
+ .capture = {
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
+ SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
+ /* Supporting only stereo for I2S HS-Virtual controller capture */
+ .channels_min = 2,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ },
+ },
+};
+
+/* Vangogh ops */
+struct snd_sof_dsp_ops sof_vangogh_ops;
+EXPORT_SYMBOL_NS(sof_vangogh_ops, SND_SOC_SOF_AMD_COMMON);
+
+int sof_vangogh_ops_init(struct snd_sof_dev *sdev)
+{
+ const struct dmi_system_id *dmi_id;
+
+ /* common defaults */
+ memcpy(&sof_vangogh_ops, &sof_acp_common_ops, sizeof(struct snd_sof_dsp_ops));
+
+ sof_vangogh_ops.drv = vangogh_sof_dai;
+ sof_vangogh_ops.num_drv = ARRAY_SIZE(vangogh_sof_dai);
+
+ dmi_id = dmi_first_match(acp_sof_quirk_table);
+ if (dmi_id && dmi_id->driver_data)
+ sof_vangogh_ops.load_firmware = acp_sof_load_signed_firmware;
+
+ return 0;
+}
+
+MODULE_IMPORT_NS(SND_SOC_SOF_AMD_COMMON);
+MODULE_DESCRIPTION("VANGOGH SOF Driver");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/sof/compress.c b/sound/soc/sof/compress.c
index 2d4969c705a4..d7b044f33d79 100644
--- a/sound/soc/sof/compress.c
+++ b/sound/soc/sof/compress.c
@@ -1,134 +1,335 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
-// This file is provided under a dual BSD/GPLv2 license. When using or
-// redistributing this file, you may do so under either license.
-//
-// Copyright(c) 2019-2020 Intel Corporation. All rights reserved.
-//
-// Author: Cezary Rojewski <cezary.rojewski@intel.com>
+// Copyright 2021 NXP
//
+// Author: Daniel Baluta <daniel.baluta@nxp.com>
#include <sound/soc.h>
-#include "compress.h"
+#include <sound/sof.h>
+#include <sound/compress_driver.h>
+#include "sof-audio.h"
+#include "sof-priv.h"
+#include "sof-utils.h"
#include "ops.h"
-#include "probe.h"
-struct snd_compress_ops sof_probe_compressed_ops = {
- .copy = sof_probe_compr_copy,
-};
-EXPORT_SYMBOL(sof_probe_compressed_ops);
+static void sof_set_transferred_bytes(struct sof_compr_stream *sstream,
+ u64 host_pos, u64 buffer_size)
+{
+ u64 prev_pos;
+ unsigned int copied;
-int sof_probe_compr_open(struct snd_compr_stream *cstream,
- struct snd_soc_dai *dai)
+ div64_u64_rem(sstream->copied_total, buffer_size, &prev_pos);
+
+ if (host_pos < prev_pos)
+ copied = (buffer_size - prev_pos) + host_pos;
+ else
+ copied = host_pos - prev_pos;
+
+ sstream->copied_total += copied;
+}
+
+static void snd_sof_compr_fragment_elapsed_work(struct work_struct *work)
{
- struct snd_sof_dev *sdev =
- snd_soc_component_get_drvdata(dai->component);
- int ret;
+ struct snd_sof_pcm_stream *sps =
+ container_of(work, struct snd_sof_pcm_stream,
+ period_elapsed_work);
- ret = snd_sof_probe_compr_assign(sdev, cstream, dai);
- if (ret < 0) {
- dev_err(dai->dev, "Failed to assign probe stream: %d\n", ret);
- return ret;
+ snd_compr_fragment_elapsed(sps->cstream);
+}
+
+void snd_sof_compr_init_elapsed_work(struct work_struct *work)
+{
+ INIT_WORK(work, snd_sof_compr_fragment_elapsed_work);
+}
+
+/*
+ * sof compr fragment elapse, this could be called in irq thread context
+ */
+void snd_sof_compr_fragment_elapsed(struct snd_compr_stream *cstream)
+{
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_compr_runtime *crtd;
+ struct snd_soc_component *component;
+ struct sof_compr_stream *sstream;
+ struct snd_sof_pcm *spcm;
+
+ if (!cstream)
+ return;
+
+ rtd = cstream->private_data;
+ crtd = cstream->runtime;
+ sstream = crtd->private_data;
+ component = snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME);
+
+ spcm = snd_sof_find_spcm_dai(component, rtd);
+ if (!spcm) {
+ dev_err(component->dev,
+ "fragment elapsed called for unknown stream!\n");
+ return;
}
- sdev->extractor_stream_tag = ret;
- return 0;
+ sof_set_transferred_bytes(sstream, spcm->stream[cstream->direction].posn.host_posn,
+ crtd->buffer_size);
+
+ /* use the same workqueue-based solution as for PCM, cf. snd_sof_pcm_elapsed */
+ schedule_work(&spcm->stream[cstream->direction].period_elapsed_work);
}
-EXPORT_SYMBOL(sof_probe_compr_open);
-int sof_probe_compr_free(struct snd_compr_stream *cstream,
- struct snd_soc_dai *dai)
+static int create_page_table(struct snd_soc_component *component,
+ struct snd_compr_stream *cstream,
+ unsigned char *dma_area, size_t size)
{
- struct snd_sof_dev *sdev =
- snd_soc_component_get_drvdata(dai->component);
- struct sof_probe_point_desc *desc;
- size_t num_desc;
- int i, ret;
-
- /* disconnect all probe points */
- ret = sof_ipc_probe_points_info(sdev, &desc, &num_desc);
- if (ret < 0) {
- dev_err(dai->dev, "Failed to get probe points: %d\n", ret);
- goto exit;
+ struct snd_dma_buffer *dmab = cstream->runtime->dma_buffer_p;
+ struct snd_soc_pcm_runtime *rtd = cstream->private_data;
+ int dir = cstream->direction;
+ struct snd_sof_pcm *spcm;
+
+ spcm = snd_sof_find_spcm_dai(component, rtd);
+ if (!spcm)
+ return -EINVAL;
+
+ return snd_sof_create_page_table(component->dev, dmab,
+ spcm->stream[dir].page_table.area, size);
+}
+
+static int sof_compr_open(struct snd_soc_component *component,
+ struct snd_compr_stream *cstream)
+{
+ struct snd_soc_pcm_runtime *rtd = cstream->private_data;
+ struct snd_compr_runtime *crtd = cstream->runtime;
+ struct sof_compr_stream *sstream;
+ struct snd_sof_pcm *spcm;
+ int dir;
+
+ sstream = kzalloc(sizeof(*sstream), GFP_KERNEL);
+ if (!sstream)
+ return -ENOMEM;
+
+ spcm = snd_sof_find_spcm_dai(component, rtd);
+ if (!spcm) {
+ kfree(sstream);
+ return -EINVAL;
}
- for (i = 0; i < num_desc; i++)
- sof_ipc_probe_points_remove(sdev, &desc[i].buffer_id, 1);
- kfree(desc);
+ dir = cstream->direction;
-exit:
- ret = sof_ipc_probe_deinit(sdev);
- if (ret < 0)
- dev_err(dai->dev, "Failed to deinit probe: %d\n", ret);
+ if (spcm->stream[dir].cstream) {
+ kfree(sstream);
+ return -EBUSY;
+ }
+
+ spcm->stream[dir].cstream = cstream;
+ spcm->stream[dir].posn.host_posn = 0;
+ spcm->stream[dir].posn.dai_posn = 0;
+ spcm->prepared[dir] = false;
- sdev->extractor_stream_tag = SOF_PROBE_INVALID_NODE_ID;
- snd_compr_free_pages(cstream);
+ crtd->private_data = sstream;
- return snd_sof_probe_compr_free(sdev, cstream, dai);
+ return 0;
}
-EXPORT_SYMBOL(sof_probe_compr_free);
-int sof_probe_compr_set_params(struct snd_compr_stream *cstream,
- struct snd_compr_params *params, struct snd_soc_dai *dai)
+static int sof_compr_free(struct snd_soc_component *component,
+ struct snd_compr_stream *cstream)
{
- struct snd_compr_runtime *rtd = cstream->runtime;
- struct snd_sof_dev *sdev =
- snd_soc_component_get_drvdata(dai->component);
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+ struct sof_compr_stream *sstream = cstream->runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = cstream->private_data;
+ struct sof_ipc_stream stream;
+ struct snd_sof_pcm *spcm;
+ int ret = 0;
+
+ spcm = snd_sof_find_spcm_dai(component, rtd);
+ if (!spcm)
+ return -EINVAL;
+
+ stream.hdr.size = sizeof(stream);
+ stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_FREE;
+ stream.comp_id = spcm->stream[cstream->direction].comp_id;
+
+ if (spcm->prepared[cstream->direction]) {
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, &stream, sizeof(stream));
+ if (!ret)
+ spcm->prepared[cstream->direction] = false;
+ }
+
+ cancel_work_sync(&spcm->stream[cstream->direction].period_elapsed_work);
+ spcm->stream[cstream->direction].cstream = NULL;
+ kfree(sstream);
+
+ return ret;
+}
+
+static int sof_compr_set_params(struct snd_soc_component *component,
+ struct snd_compr_stream *cstream, struct snd_compr_params *params)
+{
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+ struct snd_soc_pcm_runtime *rtd = cstream->private_data;
+ struct snd_compr_runtime *crtd = cstream->runtime;
+ struct sof_ipc_pcm_params_reply ipc_params_reply;
+ struct sof_ipc_fw_ready *ready = &sdev->fw_ready;
+ struct sof_ipc_fw_version *v = &ready->version;
+ struct sof_compr_stream *sstream;
+ struct sof_ipc_pcm_params *pcm;
+ struct snd_sof_pcm *spcm;
+ size_t ext_data_size;
int ret;
+ if (v->abi_version < SOF_ABI_VER(3, 22, 0)) {
+ dev_err(component->dev,
+ "Compress params not supported with FW ABI version %d:%d:%d\n",
+ SOF_ABI_VERSION_MAJOR(v->abi_version),
+ SOF_ABI_VERSION_MINOR(v->abi_version),
+ SOF_ABI_VERSION_PATCH(v->abi_version));
+ return -EINVAL;
+ }
+
+ sstream = crtd->private_data;
+
+ spcm = snd_sof_find_spcm_dai(component, rtd);
+
+ if (!spcm)
+ return -EINVAL;
+
+ ext_data_size = sizeof(params->codec);
+
+ if (sizeof(*pcm) + ext_data_size > sdev->ipc->max_payload_size)
+ return -EINVAL;
+
+ pcm = kzalloc(sizeof(*pcm) + ext_data_size, GFP_KERNEL);
+ if (!pcm)
+ return -ENOMEM;
+
cstream->dma_buffer.dev.type = SNDRV_DMA_TYPE_DEV_SG;
cstream->dma_buffer.dev.dev = sdev->dev;
- ret = snd_compr_malloc_pages(cstream, rtd->buffer_size);
+ ret = snd_compr_malloc_pages(cstream, crtd->buffer_size);
if (ret < 0)
- return ret;
+ goto out;
- ret = snd_sof_probe_compr_set_params(sdev, cstream, params, dai);
+ ret = create_page_table(component, cstream, crtd->dma_area, crtd->dma_bytes);
if (ret < 0)
- return ret;
+ goto out;
+
+ pcm->params.buffer.pages = PFN_UP(crtd->dma_bytes);
+ pcm->hdr.size = sizeof(*pcm) + ext_data_size;
+ pcm->hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS;
+
+ pcm->comp_id = spcm->stream[cstream->direction].comp_id;
+ pcm->params.hdr.size = sizeof(pcm->params) + ext_data_size;
+ pcm->params.buffer.phy_addr = spcm->stream[cstream->direction].page_table.addr;
+ pcm->params.buffer.size = crtd->dma_bytes;
+ pcm->params.direction = cstream->direction;
+ pcm->params.channels = params->codec.ch_out;
+ pcm->params.rate = params->codec.sample_rate;
+ pcm->params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED;
+ pcm->params.frame_fmt = SOF_IPC_FRAME_S32_LE;
+ pcm->params.sample_container_bytes =
+ snd_pcm_format_physical_width(SNDRV_PCM_FORMAT_S32) >> 3;
+ pcm->params.host_period_bytes = params->buffer.fragment_size;
+ pcm->params.ext_data_length = ext_data_size;
+
+ memcpy((u8 *)pcm->params.ext_data, &params->codec, ext_data_size);
+
+ ret = sof_ipc_tx_message(sdev->ipc, pcm, sizeof(*pcm) + ext_data_size,
+ &ipc_params_reply, sizeof(ipc_params_reply));
+ if (ret < 0) {
+ dev_err(component->dev, "error ipc failed\n");
+ goto out;
+ }
- ret = sof_ipc_probe_init(sdev, sdev->extractor_stream_tag,
- rtd->dma_bytes);
+ ret = snd_sof_set_stream_data_offset(sdev, &spcm->stream[cstream->direction],
+ ipc_params_reply.posn_offset);
if (ret < 0) {
- dev_err(dai->dev, "Failed to init probe: %d\n", ret);
- return ret;
+ dev_err(component->dev, "Invalid stream data offset for Compr %d\n",
+ spcm->pcm.pcm_id);
+ goto out;
}
+ sstream->sampling_rate = params->codec.sample_rate;
+ sstream->channels = params->codec.ch_out;
+ sstream->sample_container_bytes = pcm->params.sample_container_bytes;
+
+ spcm->prepared[cstream->direction] = true;
+
+out:
+ kfree(pcm);
+
+ return ret;
+}
+
+static int sof_compr_get_params(struct snd_soc_component *component,
+ struct snd_compr_stream *cstream, struct snd_codec *params)
+{
+ /* TODO: we don't query the supported codecs for now, if the
+ * application asks for an unsupported codec the set_params() will fail.
+ */
return 0;
}
-EXPORT_SYMBOL(sof_probe_compr_set_params);
-int sof_probe_compr_trigger(struct snd_compr_stream *cstream, int cmd,
- struct snd_soc_dai *dai)
+static int sof_compr_trigger(struct snd_soc_component *component,
+ struct snd_compr_stream *cstream, int cmd)
{
- struct snd_sof_dev *sdev =
- snd_soc_component_get_drvdata(dai->component);
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+ struct snd_soc_pcm_runtime *rtd = cstream->private_data;
+ struct sof_ipc_stream stream;
+ struct snd_sof_pcm *spcm;
- return snd_sof_probe_compr_trigger(sdev, cstream, cmd, dai);
+ spcm = snd_sof_find_spcm_dai(component, rtd);
+ if (!spcm)
+ return -EINVAL;
+
+ stream.hdr.size = sizeof(stream);
+ stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG;
+ stream.comp_id = spcm->stream[cstream->direction].comp_id;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_START;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_STOP;
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_PAUSE;
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_RELEASE;
+ break;
+ default:
+ dev_err(component->dev, "error: unhandled trigger cmd %d\n", cmd);
+ break;
+ }
+
+ return sof_ipc_tx_message_no_reply(sdev->ipc, &stream, sizeof(stream));
}
-EXPORT_SYMBOL(sof_probe_compr_trigger);
-int sof_probe_compr_pointer(struct snd_compr_stream *cstream,
- struct snd_compr_tstamp *tstamp, struct snd_soc_dai *dai)
+static int sof_compr_copy_playback(struct snd_compr_runtime *rtd,
+ char __user *buf, size_t count)
{
- struct snd_sof_dev *sdev =
- snd_soc_component_get_drvdata(dai->component);
+ void *ptr;
+ unsigned int offset, n;
+ int ret;
+
+ div_u64_rem(rtd->total_bytes_available, rtd->buffer_size, &offset);
+ ptr = rtd->dma_area + offset;
+ n = rtd->buffer_size - offset;
+
+ if (count < n) {
+ ret = copy_from_user(ptr, buf, count);
+ } else {
+ ret = copy_from_user(ptr, buf, n);
+ ret += copy_from_user(rtd->dma_area, buf + n, count - n);
+ }
- return snd_sof_probe_compr_pointer(sdev, cstream, tstamp, dai);
+ return count - ret;
}
-EXPORT_SYMBOL(sof_probe_compr_pointer);
-int sof_probe_compr_copy(struct snd_soc_component *component,
- struct snd_compr_stream *cstream,
- char __user *buf, size_t count)
+static int sof_compr_copy_capture(struct snd_compr_runtime *rtd,
+ char __user *buf, size_t count)
{
- struct snd_compr_runtime *rtd = cstream->runtime;
- unsigned int offset, n;
void *ptr;
+ unsigned int offset, n;
int ret;
- if (count > rtd->buffer_size)
- count = rtd->buffer_size;
-
div_u64_rem(rtd->total_bytes_transferred, rtd->buffer_size, &offset);
ptr = rtd->dma_area + offset;
n = rtd->buffer_size - offset;
@@ -140,8 +341,51 @@ int sof_probe_compr_copy(struct snd_soc_component *component,
ret += copy_to_user(buf + n, rtd->dma_area, count - n);
}
- if (ret)
- return count - ret;
- return count;
+ return count - ret;
}
-EXPORT_SYMBOL(sof_probe_compr_copy);
+
+static int sof_compr_copy(struct snd_soc_component *component,
+ struct snd_compr_stream *cstream,
+ char __user *buf, size_t count)
+{
+ struct snd_compr_runtime *rtd = cstream->runtime;
+
+ if (count > rtd->buffer_size)
+ count = rtd->buffer_size;
+
+ if (cstream->direction == SND_COMPRESS_PLAYBACK)
+ return sof_compr_copy_playback(rtd, buf, count);
+ else
+ return sof_compr_copy_capture(rtd, buf, count);
+}
+
+static int sof_compr_pointer(struct snd_soc_component *component,
+ struct snd_compr_stream *cstream,
+ struct snd_compr_tstamp *tstamp)
+{
+ struct snd_sof_pcm *spcm;
+ struct snd_soc_pcm_runtime *rtd = cstream->private_data;
+ struct sof_compr_stream *sstream = cstream->runtime->private_data;
+
+ spcm = snd_sof_find_spcm_dai(component, rtd);
+ if (!spcm)
+ return -EINVAL;
+
+ tstamp->sampling_rate = sstream->sampling_rate;
+ tstamp->copied_total = sstream->copied_total;
+ tstamp->pcm_io_frames = div_u64(spcm->stream[cstream->direction].posn.dai_posn,
+ sstream->channels * sstream->sample_container_bytes);
+
+ return 0;
+}
+
+struct snd_compress_ops sof_compressed_ops = {
+ .open = sof_compr_open,
+ .free = sof_compr_free,
+ .set_params = sof_compr_set_params,
+ .get_params = sof_compr_get_params,
+ .trigger = sof_compr_trigger,
+ .pointer = sof_compr_pointer,
+ .copy = sof_compr_copy,
+};
+EXPORT_SYMBOL(sof_compressed_ops);
diff --git a/sound/soc/sof/compress.h b/sound/soc/sof/compress.h
deleted file mode 100644
index ca8790bd4b13..000000000000
--- a/sound/soc/sof/compress.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
-/*
- * This file is provided under a dual BSD/GPLv2 license. When using or
- * redistributing this file, you may do so under either license.
- *
- * Copyright(c) 2019-2020 Intel Corporation. All rights reserved.
- *
- * Author: Cezary Rojewski <cezary.rojewski@intel.com>
- */
-
-#ifndef __SOF_COMPRESS_H
-#define __SOF_COMPRESS_H
-
-#include <sound/compress_driver.h>
-
-extern struct snd_compress_ops sof_probe_compressed_ops;
-
-int sof_probe_compr_open(struct snd_compr_stream *cstream,
- struct snd_soc_dai *dai);
-int sof_probe_compr_free(struct snd_compr_stream *cstream,
- struct snd_soc_dai *dai);
-int sof_probe_compr_set_params(struct snd_compr_stream *cstream,
- struct snd_compr_params *params, struct snd_soc_dai *dai);
-int sof_probe_compr_trigger(struct snd_compr_stream *cstream, int cmd,
- struct snd_soc_dai *dai);
-int sof_probe_compr_pointer(struct snd_compr_stream *cstream,
- struct snd_compr_tstamp *tstamp, struct snd_soc_dai *dai);
-int sof_probe_compr_copy(struct snd_soc_component *component,
- struct snd_compr_stream *cstream,
- char __user *buf, size_t count);
-
-#endif
diff --git a/sound/soc/sof/control.c b/sound/soc/sof/control.c
index 186eea105bb1..75e13f4fd1eb 100644
--- a/sound/soc/sof/control.c
+++ b/sound/soc/sof/control.c
@@ -15,70 +15,17 @@
#include "sof-priv.h"
#include "sof-audio.h"
-static void update_mute_led(struct snd_sof_control *scontrol,
- struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
-{
- int temp = 0;
- int mask;
- int i;
-
- mask = 1U << snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
-
- for (i = 0; i < scontrol->num_channels; i++) {
- if (ucontrol->value.integer.value[i]) {
- temp |= mask;
- break;
- }
- }
-
- if (temp == scontrol->led_ctl.led_value)
- return;
-
- scontrol->led_ctl.led_value = temp;
-
-#if IS_REACHABLE(CONFIG_LEDS_TRIGGER_AUDIO)
- if (!scontrol->led_ctl.direction)
- ledtrig_audio_set(LED_AUDIO_MUTE, temp ? LED_OFF : LED_ON);
- else
- ledtrig_audio_set(LED_AUDIO_MICMUTE, temp ? LED_OFF : LED_ON);
-#endif
-}
-
-static inline u32 mixer_to_ipc(unsigned int value, u32 *volume_map, int size)
-{
- if (value >= size)
- return volume_map[size - 1];
-
- return volume_map[value];
-}
-
-static inline u32 ipc_to_mixer(u32 value, u32 *volume_map, int size)
-{
- int i;
-
- for (i = 0; i < size; i++) {
- if (volume_map[i] >= value)
- return i;
- }
-
- return i - 1;
-}
-
int snd_sof_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
- struct soc_mixer_control *sm =
- (struct soc_mixer_control *)kcontrol->private_value;
+ struct soc_mixer_control *sm = (struct soc_mixer_control *)kcontrol->private_value;
struct snd_sof_control *scontrol = sm->dobj.private;
- struct sof_ipc_ctrl_data *cdata = scontrol->control_data;
- unsigned int i, channels = scontrol->num_channels;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
- /* read back each channel */
- for (i = 0; i < channels; i++)
- ucontrol->value.integer.value[i] =
- ipc_to_mixer(cdata->chanv[i].value,
- scontrol->volume_table, sm->max + 1);
+ if (tplg_ops && tplg_ops->control && tplg_ops->control->volume_get)
+ return tplg_ops->control->volume_get(scontrol, ucontrol);
return 0;
}
@@ -86,46 +33,51 @@ int snd_sof_volume_get(struct snd_kcontrol *kcontrol,
int snd_sof_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
- struct soc_mixer_control *sm =
- (struct soc_mixer_control *)kcontrol->private_value;
+ struct soc_mixer_control *sm = (struct soc_mixer_control *)kcontrol->private_value;
struct snd_sof_control *scontrol = sm->dobj.private;
struct snd_soc_component *scomp = scontrol->scomp;
- struct sof_ipc_ctrl_data *cdata = scontrol->control_data;
- unsigned int i, channels = scontrol->num_channels;
- bool change = false;
- u32 value;
-
- /* update each channel */
- for (i = 0; i < channels; i++) {
- value = mixer_to_ipc(ucontrol->value.integer.value[i],
- scontrol->volume_table, sm->max + 1);
- change = change || (value != cdata->chanv[i].value);
- cdata->chanv[i].channel = i;
- cdata->chanv[i].value = value;
- }
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
+
+ if (tplg_ops && tplg_ops->control && tplg_ops->control->volume_put)
+ return tplg_ops->control->volume_put(scontrol, ucontrol);
- /* notify DSP of mixer updates */
- if (pm_runtime_active(scomp->dev))
- snd_sof_ipc_set_get_comp_data(scontrol,
- SOF_IPC_COMP_SET_VALUE,
- SOF_CTRL_TYPE_VALUE_CHAN_GET,
- SOF_CTRL_CMD_VOLUME,
- true);
- return change;
+ return false;
+}
+
+int snd_sof_volume_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ struct soc_mixer_control *sm = (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_sof_control *scontrol = sm->dobj.private;
+ unsigned int channels = scontrol->num_channels;
+ int platform_max;
+
+ if (!sm->platform_max)
+ sm->platform_max = sm->max;
+ platform_max = sm->platform_max;
+
+ if (platform_max == 1 && !strstr(kcontrol->id.name, " Volume"))
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ else
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+
+ uinfo->count = channels;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = platform_max - sm->min;
+ return 0;
}
int snd_sof_switch_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
- struct soc_mixer_control *sm =
- (struct soc_mixer_control *)kcontrol->private_value;
+ struct soc_mixer_control *sm = (struct soc_mixer_control *)kcontrol->private_value;
struct snd_sof_control *scontrol = sm->dobj.private;
- struct sof_ipc_ctrl_data *cdata = scontrol->control_data;
- unsigned int i, channels = scontrol->num_channels;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
- /* read back each channel */
- for (i = 0; i < channels; i++)
- ucontrol->value.integer.value[i] = cdata->chanv[i].value;
+ if (tplg_ops && tplg_ops->control && tplg_ops->control->switch_get)
+ return tplg_ops->control->switch_get(scontrol, ucontrol);
return 0;
}
@@ -133,49 +85,29 @@ int snd_sof_switch_get(struct snd_kcontrol *kcontrol,
int snd_sof_switch_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
- struct soc_mixer_control *sm =
- (struct soc_mixer_control *)kcontrol->private_value;
+ struct soc_mixer_control *sm = (struct soc_mixer_control *)kcontrol->private_value;
struct snd_sof_control *scontrol = sm->dobj.private;
struct snd_soc_component *scomp = scontrol->scomp;
- struct sof_ipc_ctrl_data *cdata = scontrol->control_data;
- unsigned int i, channels = scontrol->num_channels;
- bool change = false;
- u32 value;
-
- /* update each channel */
- for (i = 0; i < channels; i++) {
- value = ucontrol->value.integer.value[i];
- change = change || (value != cdata->chanv[i].value);
- cdata->chanv[i].channel = i;
- cdata->chanv[i].value = value;
- }
-
- if (scontrol->led_ctl.use_led)
- update_mute_led(scontrol, kcontrol, ucontrol);
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
- /* notify DSP of mixer updates */
- if (pm_runtime_active(scomp->dev))
- snd_sof_ipc_set_get_comp_data(scontrol,
- SOF_IPC_COMP_SET_VALUE,
- SOF_CTRL_TYPE_VALUE_CHAN_GET,
- SOF_CTRL_CMD_SWITCH,
- true);
+ if (tplg_ops && tplg_ops->control && tplg_ops->control->switch_put)
+ return tplg_ops->control->switch_put(scontrol, ucontrol);
- return change;
+ return false;
}
int snd_sof_enum_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
- struct soc_enum *se =
- (struct soc_enum *)kcontrol->private_value;
+ struct soc_enum *se = (struct soc_enum *)kcontrol->private_value;
struct snd_sof_control *scontrol = se->dobj.private;
- struct sof_ipc_ctrl_data *cdata = scontrol->control_data;
- unsigned int i, channels = scontrol->num_channels;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
- /* read back each channel */
- for (i = 0; i < channels; i++)
- ucontrol->value.enumerated.item[i] = cdata->chanv[i].value;
+ if (tplg_ops && tplg_ops->control && tplg_ops->control->enum_get)
+ return tplg_ops->control->enum_get(scontrol, ucontrol);
return 0;
}
@@ -183,104 +115,44 @@ int snd_sof_enum_get(struct snd_kcontrol *kcontrol,
int snd_sof_enum_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
- struct soc_enum *se =
- (struct soc_enum *)kcontrol->private_value;
+ struct soc_enum *se = (struct soc_enum *)kcontrol->private_value;
struct snd_sof_control *scontrol = se->dobj.private;
struct snd_soc_component *scomp = scontrol->scomp;
- struct sof_ipc_ctrl_data *cdata = scontrol->control_data;
- unsigned int i, channels = scontrol->num_channels;
- bool change = false;
- u32 value;
-
- /* update each channel */
- for (i = 0; i < channels; i++) {
- value = ucontrol->value.enumerated.item[i];
- change = change || (value != cdata->chanv[i].value);
- cdata->chanv[i].channel = i;
- cdata->chanv[i].value = value;
- }
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
- /* notify DSP of enum updates */
- if (pm_runtime_active(scomp->dev))
- snd_sof_ipc_set_get_comp_data(scontrol,
- SOF_IPC_COMP_SET_VALUE,
- SOF_CTRL_TYPE_VALUE_CHAN_GET,
- SOF_CTRL_CMD_ENUM,
- true);
+ if (tplg_ops && tplg_ops->control && tplg_ops->control->enum_put)
+ return tplg_ops->control->enum_put(scontrol, ucontrol);
- return change;
+ return false;
}
int snd_sof_bytes_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
- struct soc_bytes_ext *be =
- (struct soc_bytes_ext *)kcontrol->private_value;
+ struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value;
struct snd_sof_control *scontrol = be->dobj.private;
struct snd_soc_component *scomp = scontrol->scomp;
- struct sof_ipc_ctrl_data *cdata = scontrol->control_data;
- struct sof_abi_hdr *data = cdata->data;
- size_t size;
- int ret = 0;
-
- if (be->max > sizeof(ucontrol->value.bytes.data)) {
- dev_err_ratelimited(scomp->dev,
- "error: data max %d exceeds ucontrol data array size\n",
- be->max);
- return -EINVAL;
- }
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
- size = data->size + sizeof(*data);
- if (size > be->max) {
- dev_err_ratelimited(scomp->dev,
- "error: DSP sent %zu bytes max is %d\n",
- size, be->max);
- ret = -EINVAL;
- goto out;
- }
+ if (tplg_ops && tplg_ops->control && tplg_ops->control->bytes_get)
+ return tplg_ops->control->bytes_get(scontrol, ucontrol);
- /* copy back to kcontrol */
- memcpy(ucontrol->value.bytes.data, data, size);
-
-out:
- return ret;
+ return 0;
}
int snd_sof_bytes_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
- struct soc_bytes_ext *be =
- (struct soc_bytes_ext *)kcontrol->private_value;
+ struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value;
struct snd_sof_control *scontrol = be->dobj.private;
struct snd_soc_component *scomp = scontrol->scomp;
- struct sof_ipc_ctrl_data *cdata = scontrol->control_data;
- struct sof_abi_hdr *data = cdata->data;
- size_t size = data->size + sizeof(*data);
-
- if (be->max > sizeof(ucontrol->value.bytes.data)) {
- dev_err_ratelimited(scomp->dev,
- "error: data max %d exceeds ucontrol data array size\n",
- be->max);
- return -EINVAL;
- }
-
- if (size > be->max) {
- dev_err_ratelimited(scomp->dev,
- "error: size too big %zu bytes max is %d\n",
- size, be->max);
- return -EINVAL;
- }
-
- /* copy from kcontrol */
- memcpy(data, ucontrol->value.bytes.data, size);
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
- /* notify DSP of byte control updates */
- if (pm_runtime_active(scomp->dev))
- snd_sof_ipc_set_get_comp_data(scontrol,
- SOF_IPC_COMP_SET_DATA,
- SOF_CTRL_TYPE_DATA_SET,
- scontrol->cmd,
- true);
+ if (tplg_ops && tplg_ops->control && tplg_ops->control->bytes_put)
+ return tplg_ops->control->bytes_put(scontrol, ucontrol);
return 0;
}
@@ -289,116 +161,61 @@ int snd_sof_bytes_ext_put(struct snd_kcontrol *kcontrol,
const unsigned int __user *binary_data,
unsigned int size)
{
- struct soc_bytes_ext *be =
- (struct soc_bytes_ext *)kcontrol->private_value;
+ struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value;
struct snd_sof_control *scontrol = be->dobj.private;
struct snd_soc_component *scomp = scontrol->scomp;
- struct sof_ipc_ctrl_data *cdata = scontrol->control_data;
- struct snd_ctl_tlv header;
- const struct snd_ctl_tlv __user *tlvd =
- (const struct snd_ctl_tlv __user *)binary_data;
-
- /*
- * The beginning of bytes data contains a header from where
- * the length (as bytes) is needed to know the correct copy
- * length of data from tlvd->tlv.
- */
- if (copy_from_user(&header, tlvd, sizeof(const struct snd_ctl_tlv)))
- return -EFAULT;
-
- /* be->max is coming from topology */
- if (header.length > be->max) {
- dev_err_ratelimited(scomp->dev, "error: Bytes data size %d exceeds max %d.\n",
- header.length, be->max);
- return -EINVAL;
- }
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
- /* Check that header id matches the command */
- if (header.numid != scontrol->cmd) {
- dev_err_ratelimited(scomp->dev,
- "error: incorrect numid %d\n",
- header.numid);
+ /* make sure we have at least a header */
+ if (size < sizeof(struct snd_ctl_tlv))
return -EINVAL;
- }
- if (copy_from_user(cdata->data, tlvd->tlv, header.length))
- return -EFAULT;
+ if (tplg_ops && tplg_ops->control && tplg_ops->control->bytes_ext_put)
+ return tplg_ops->control->bytes_ext_put(scontrol, binary_data, size);
- if (cdata->data->magic != SOF_ABI_MAGIC) {
- dev_err_ratelimited(scomp->dev,
- "error: Wrong ABI magic 0x%08x.\n",
- cdata->data->magic);
- return -EINVAL;
- }
+ return 0;
+}
- if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, cdata->data->abi)) {
- dev_err_ratelimited(scomp->dev, "error: Incompatible ABI version 0x%08x.\n",
- cdata->data->abi);
- return -EINVAL;
- }
+int snd_sof_bytes_ext_volatile_get(struct snd_kcontrol *kcontrol, unsigned int __user *binary_data,
+ unsigned int size)
+{
+ struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value;
+ struct snd_sof_control *scontrol = be->dobj.private;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
+ int ret, err;
- if (cdata->data->size + sizeof(const struct sof_abi_hdr) > be->max) {
- dev_err_ratelimited(scomp->dev, "error: Mismatch in ABI data size (truncated?).\n");
- return -EINVAL;
+ ret = pm_runtime_resume_and_get(scomp->dev);
+ if (ret < 0 && ret != -EACCES) {
+ dev_err_ratelimited(scomp->dev, "%s: failed to resume %d\n", __func__, ret);
+ return ret;
}
- /* notify DSP of byte control updates */
- if (pm_runtime_active(scomp->dev))
- snd_sof_ipc_set_get_comp_data(scontrol,
- SOF_IPC_COMP_SET_DATA,
- SOF_CTRL_TYPE_DATA_SET,
- scontrol->cmd,
- true);
+ if (tplg_ops && tplg_ops->control && tplg_ops->control->bytes_ext_volatile_get)
+ ret = tplg_ops->control->bytes_ext_volatile_get(scontrol, binary_data, size);
- return 0;
+ pm_runtime_mark_last_busy(scomp->dev);
+ err = pm_runtime_put_autosuspend(scomp->dev);
+ if (err < 0)
+ dev_err_ratelimited(scomp->dev, "%s: failed to idle %d\n", __func__, err);
+
+ return ret;
}
int snd_sof_bytes_ext_get(struct snd_kcontrol *kcontrol,
unsigned int __user *binary_data,
unsigned int size)
{
- struct soc_bytes_ext *be =
- (struct soc_bytes_ext *)kcontrol->private_value;
+ struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value;
struct snd_sof_control *scontrol = be->dobj.private;
struct snd_soc_component *scomp = scontrol->scomp;
- struct sof_ipc_ctrl_data *cdata = scontrol->control_data;
- struct snd_ctl_tlv header;
- struct snd_ctl_tlv __user *tlvd =
- (struct snd_ctl_tlv __user *)binary_data;
- int data_size;
- int ret = 0;
-
- /*
- * Decrement the limit by ext bytes header size to
- * ensure the user space buffer is not exceeded.
- */
- size -= sizeof(const struct snd_ctl_tlv);
-
- /* set the ABI header values */
- cdata->data->magic = SOF_ABI_MAGIC;
- cdata->data->abi = SOF_ABI_VERSION;
-
- /* Prevent read of other kernel data or possibly corrupt response */
- data_size = cdata->data->size + sizeof(const struct sof_abi_hdr);
-
- /* check data size doesn't exceed max coming from topology */
- if (data_size > be->max) {
- dev_err_ratelimited(scomp->dev, "error: user data size %d exceeds max size %d.\n",
- data_size, be->max);
- ret = -EINVAL;
- goto out;
- }
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
- header.numid = scontrol->cmd;
- header.length = data_size;
- if (copy_to_user(tlvd, &header, sizeof(const struct snd_ctl_tlv))) {
- ret = -EFAULT;
- goto out;
- }
-
- if (copy_to_user(tlvd->tlv, cdata->data, data_size))
- ret = -EFAULT;
+ if (tplg_ops && tplg_ops->control && tplg_ops->control->bytes_ext_get)
+ return tplg_ops->control->bytes_ext_get(scontrol, binary_data, size);
-out:
- return ret;
+ return 0;
}
diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c
index adc7c37145d6..9b00ede2a486 100644
--- a/sound/soc/sof/core.c
+++ b/sound/soc/sof/core.c
@@ -13,13 +13,14 @@
#include <sound/soc.h>
#include <sound/sof.h>
#include "sof-priv.h"
+#include "sof-of-dev.h"
#include "ops.h"
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES)
-#include "probe.h"
-#endif
+
+#define CREATE_TRACE_POINTS
+#include <trace/events/sof.h>
/* see SOF_DBG_ flags */
-int sof_core_debug;
+static int sof_core_debug = IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE);
module_param_named(sof_debug, sof_core_debug, int, 0444);
MODULE_PARM_DESC(sof_debug, "SOF core debug options (0x0 all off)");
@@ -27,6 +28,22 @@ MODULE_PARM_DESC(sof_debug, "SOF core debug options (0x0 all off)");
#define TIMEOUT_DEFAULT_IPC_MS 500
#define TIMEOUT_DEFAULT_BOOT_MS 2000
+/**
+ * sof_debug_check_flag - check if a given flag(s) is set in sof_core_debug
+ * @mask: Flag or combination of flags to check
+ *
+ * Returns true if all bits set in mask is also set in sof_core_debug, otherwise
+ * false
+ */
+bool sof_debug_check_flag(int mask)
+{
+ if ((sof_core_debug & mask) == mask)
+ return true;
+
+ return false;
+}
+EXPORT_SYMBOL(sof_debug_check_flag);
+
/*
* FW Panic/fault handling.
*/
@@ -52,23 +69,33 @@ static const struct sof_panic_msg panic_msg[] = {
{SOF_IPC_PANIC_ASSERT, "assertion failed"},
};
-/*
+/**
+ * sof_print_oops_and_stack - Handle the printing of DSP oops and stack trace
+ * @sdev: Pointer to the device's sdev
+ * @level: prink log level to use for the printing
+ * @panic_code: the panic code
+ * @tracep_code: tracepoint code
+ * @oops: Pointer to DSP specific oops data
+ * @panic_info: Pointer to the received panic information message
+ * @stack: Pointer to the call stack data
+ * @stack_words: Number of words in the stack data
+ *
* helper to be called from .dbg_dump callbacks. No error code is
* provided, it's left as an exercise for the caller of .dbg_dump
* (typically IPC or loader)
*/
-void snd_sof_get_status(struct snd_sof_dev *sdev, u32 panic_code,
- u32 tracep_code, void *oops,
- struct sof_ipc_panic_info *panic_info,
- void *stack, size_t stack_words)
+void sof_print_oops_and_stack(struct snd_sof_dev *sdev, const char *level,
+ u32 panic_code, u32 tracep_code, void *oops,
+ struct sof_ipc_panic_info *panic_info,
+ void *stack, size_t stack_words)
{
u32 code;
int i;
/* is firmware dead ? */
if ((panic_code & SOF_IPC_PANIC_MAGIC_MASK) != SOF_IPC_PANIC_MAGIC) {
- dev_err(sdev->dev, "error: unexpected fault 0x%8.8x trace 0x%8.8x\n",
- panic_code, tracep_code);
+ dev_printk(level, sdev->dev, "unexpected fault %#010x trace %#010x\n",
+ panic_code, tracep_code);
return; /* no fault ? */
}
@@ -76,54 +103,303 @@ void snd_sof_get_status(struct snd_sof_dev *sdev, u32 panic_code,
for (i = 0; i < ARRAY_SIZE(panic_msg); i++) {
if (panic_msg[i].id == code) {
- dev_err(sdev->dev, "error: %s\n", panic_msg[i].msg);
- dev_err(sdev->dev, "error: trace point %8.8x\n",
- tracep_code);
+ dev_printk(level, sdev->dev, "reason: %s (%#x)\n",
+ panic_msg[i].msg, code & SOF_IPC_PANIC_CODE_MASK);
+ dev_printk(level, sdev->dev, "trace point: %#010x\n", tracep_code);
goto out;
}
}
/* unknown error */
- dev_err(sdev->dev, "error: unknown reason %8.8x\n", panic_code);
- dev_err(sdev->dev, "error: trace point %8.8x\n", tracep_code);
+ dev_printk(level, sdev->dev, "unknown panic code: %#x\n",
+ code & SOF_IPC_PANIC_CODE_MASK);
+ dev_printk(level, sdev->dev, "trace point: %#010x\n", tracep_code);
out:
- dev_err(sdev->dev, "error: panic at %s:%d\n",
- panic_info->filename, panic_info->linenum);
- sof_oops(sdev, oops);
- sof_stack(sdev, oops, stack, stack_words);
+ dev_printk(level, sdev->dev, "panic at %s:%d\n", panic_info->filename,
+ panic_info->linenum);
+ sof_oops(sdev, level, oops);
+ sof_stack(sdev, level, oops, stack, stack_words);
+}
+EXPORT_SYMBOL(sof_print_oops_and_stack);
+
+/* Helper to manage DSP state */
+void sof_set_fw_state(struct snd_sof_dev *sdev, enum sof_fw_state new_state)
+{
+ if (sdev->fw_state == new_state)
+ return;
+
+ dev_dbg(sdev->dev, "fw_state change: %d -> %d\n", sdev->fw_state, new_state);
+ sdev->fw_state = new_state;
+
+ switch (new_state) {
+ case SOF_FW_BOOT_NOT_STARTED:
+ case SOF_FW_BOOT_COMPLETE:
+ case SOF_FW_CRASHED:
+ sof_client_fw_state_dispatcher(sdev);
+ fallthrough;
+ default:
+ break;
+ }
+}
+EXPORT_SYMBOL(sof_set_fw_state);
+
+static struct snd_sof_of_mach *sof_of_machine_select(struct snd_sof_dev *sdev)
+{
+ struct snd_sof_pdata *sof_pdata = sdev->pdata;
+ const struct sof_dev_desc *desc = sof_pdata->desc;
+ struct snd_sof_of_mach *mach = desc->of_machines;
+
+ if (!mach)
+ return NULL;
+
+ for (; mach->compatible; mach++) {
+ if (of_machine_is_compatible(mach->compatible)) {
+ sof_pdata->tplg_filename = mach->sof_tplg_filename;
+ if (mach->fw_filename)
+ sof_pdata->fw_filename = mach->fw_filename;
+
+ return mach;
+ }
+ }
+
+ return NULL;
+}
+
+/* SOF Driver enumeration */
+static int sof_machine_check(struct snd_sof_dev *sdev)
+{
+ struct snd_sof_pdata *sof_pdata = sdev->pdata;
+ const struct sof_dev_desc *desc = sof_pdata->desc;
+ struct snd_soc_acpi_mach *mach;
+
+ if (!IS_ENABLED(CONFIG_SND_SOC_SOF_FORCE_NOCODEC_MODE)) {
+ const struct snd_sof_of_mach *of_mach;
+
+ if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
+ sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
+ goto nocodec;
+
+ /* find machine */
+ mach = snd_sof_machine_select(sdev);
+ if (mach) {
+ sof_pdata->machine = mach;
+
+ if (sof_pdata->subsystem_id_set) {
+ mach->mach_params.subsystem_vendor = sof_pdata->subsystem_vendor;
+ mach->mach_params.subsystem_device = sof_pdata->subsystem_device;
+ mach->mach_params.subsystem_id_set = true;
+ }
+
+ snd_sof_set_mach_params(mach, sdev);
+ return 0;
+ }
+
+ of_mach = sof_of_machine_select(sdev);
+ if (of_mach) {
+ sof_pdata->of_machine = of_mach;
+ return 0;
+ }
+
+ if (!IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC)) {
+ dev_err(sdev->dev, "error: no matching ASoC machine driver found - aborting probe\n");
+ return -ENODEV;
+ }
+ } else {
+ dev_warn(sdev->dev, "Force to use nocodec mode\n");
+ }
+
+nocodec:
+ /* select nocodec mode */
+ dev_warn(sdev->dev, "Using nocodec machine driver\n");
+ mach = devm_kzalloc(sdev->dev, sizeof(*mach), GFP_KERNEL);
+ if (!mach)
+ return -ENOMEM;
+
+ mach->drv_name = "sof-nocodec";
+ if (!sof_pdata->tplg_filename)
+ sof_pdata->tplg_filename = desc->nocodec_tplg_filename;
+
+ sof_pdata->machine = mach;
+ snd_sof_set_mach_params(mach, sdev);
+
+ return 0;
+}
+
+static int sof_select_ipc_and_paths(struct snd_sof_dev *sdev)
+{
+ struct snd_sof_pdata *plat_data = sdev->pdata;
+ struct sof_loadable_file_profile *base_profile = &plat_data->ipc_file_profile_base;
+ struct sof_loadable_file_profile out_profile;
+ struct device *dev = sdev->dev;
+ int ret;
+
+ if (base_profile->ipc_type != plat_data->desc->ipc_default)
+ dev_info(dev,
+ "Module parameter used, overriding default IPC %d to %d\n",
+ plat_data->desc->ipc_default, base_profile->ipc_type);
+
+ if (base_profile->fw_path)
+ dev_dbg(dev, "Module parameter used, changed fw path to %s\n",
+ base_profile->fw_path);
+ else if (base_profile->fw_path_postfix)
+ dev_dbg(dev, "Path postfix appended to default fw path: %s\n",
+ base_profile->fw_path_postfix);
+
+ if (base_profile->fw_lib_path)
+ dev_dbg(dev, "Module parameter used, changed fw_lib path to %s\n",
+ base_profile->fw_lib_path);
+ else if (base_profile->fw_lib_path_postfix)
+ dev_dbg(dev, "Path postfix appended to default fw_lib path: %s\n",
+ base_profile->fw_lib_path_postfix);
+
+ if (base_profile->fw_name)
+ dev_dbg(dev, "Module parameter used, changed fw filename to %s\n",
+ base_profile->fw_name);
+
+ if (base_profile->tplg_path)
+ dev_dbg(dev, "Module parameter used, changed tplg path to %s\n",
+ base_profile->tplg_path);
+
+ if (base_profile->tplg_name)
+ dev_dbg(dev, "Module parameter used, changed tplg name to %s\n",
+ base_profile->tplg_name);
+
+ ret = sof_create_ipc_file_profile(sdev, base_profile, &out_profile);
+ if (ret)
+ return ret;
+
+ plat_data->ipc_type = out_profile.ipc_type;
+ plat_data->fw_filename = out_profile.fw_name;
+ plat_data->fw_filename_prefix = out_profile.fw_path;
+ plat_data->fw_lib_prefix = out_profile.fw_lib_path;
+ plat_data->tplg_filename_prefix = out_profile.tplg_path;
+
+ return 0;
+}
+
+static int validate_sof_ops(struct snd_sof_dev *sdev)
+{
+ int ret;
+
+ /* init ops, if necessary */
+ ret = sof_ops_init(sdev);
+ if (ret < 0)
+ return ret;
+
+ /* check all mandatory ops */
+ if (!sof_ops(sdev) || !sof_ops(sdev)->probe) {
+ dev_err(sdev->dev, "missing mandatory ops\n");
+ sof_ops_free(sdev);
+ return -EINVAL;
+ }
+
+ if (!sdev->dspless_mode_selected &&
+ (!sof_ops(sdev)->run || !sof_ops(sdev)->block_read ||
+ !sof_ops(sdev)->block_write || !sof_ops(sdev)->send_msg ||
+ !sof_ops(sdev)->load_firmware || !sof_ops(sdev)->ipc_msg_data)) {
+ dev_err(sdev->dev, "missing mandatory DSP ops\n");
+ sof_ops_free(sdev);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sof_init_sof_ops(struct snd_sof_dev *sdev)
+{
+ struct snd_sof_pdata *plat_data = sdev->pdata;
+ struct sof_loadable_file_profile *base_profile = &plat_data->ipc_file_profile_base;
+
+ /* check IPC support */
+ if (!(BIT(base_profile->ipc_type) & plat_data->desc->ipc_supported_mask)) {
+ dev_err(sdev->dev,
+ "ipc_type %d is not supported on this platform, mask is %#x\n",
+ base_profile->ipc_type, plat_data->desc->ipc_supported_mask);
+ return -EINVAL;
+ }
+
+ /*
+ * Save the selected IPC type and a topology name override before
+ * selecting ops since platform code might need this information
+ */
+ plat_data->ipc_type = base_profile->ipc_type;
+ plat_data->tplg_filename = base_profile->tplg_name;
+
+ return validate_sof_ops(sdev);
+}
+
+static int sof_init_environment(struct snd_sof_dev *sdev)
+{
+ struct snd_sof_pdata *plat_data = sdev->pdata;
+ struct sof_loadable_file_profile *base_profile = &plat_data->ipc_file_profile_base;
+ int ret;
+
+ /* probe the DSP hardware */
+ ret = snd_sof_probe(sdev);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to probe DSP %d\n", ret);
+ sof_ops_free(sdev);
+ return ret;
+ }
+
+ /* check machine info */
+ ret = sof_machine_check(sdev);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to get machine info %d\n", ret);
+ goto err_machine_check;
+ }
+
+ ret = sof_select_ipc_and_paths(sdev);
+ if (!ret && plat_data->ipc_type != base_profile->ipc_type) {
+ /* IPC type changed, re-initialize the ops */
+ sof_ops_free(sdev);
+
+ ret = validate_sof_ops(sdev);
+ if (ret < 0) {
+ snd_sof_remove(sdev);
+ return ret;
+ }
+ }
+
+err_machine_check:
+ if (ret) {
+ snd_sof_remove(sdev);
+ sof_ops_free(sdev);
+ }
+
+ return ret;
}
-EXPORT_SYMBOL(snd_sof_get_status);
/*
* FW Boot State Transition Diagram
*
- * +-----------------------------------------------------------------------+
- * | |
- * ------------------ ------------------ |
- * | | | | |
- * | BOOT_FAILED | | READY_FAILED |-------------------------+ |
- * | | | | | |
- * ------------------ ------------------ | |
- * ^ ^ | |
- * | | | |
- * (FW Boot Timeout) (FW_READY FAIL) | |
- * | | | |
- * | | | |
- * ------------------ | ------------------ | |
- * | | | | | | |
- * | IN_PROGRESS |---------------+------------->| COMPLETE | | |
- * | | (FW Boot OK) (FW_READY OK) | | | |
- * ------------------ ------------------ | |
- * ^ | | |
- * | | | |
- * (FW Loading OK) (System Suspend/Runtime Suspend)
- * | | | |
- * | | | |
- * ------------------ ------------------ | | |
- * | | | |<-----+ | |
- * | PREPARE | | NOT_STARTED |<---------------------+ |
- * | | | |<---------------------------+
+ * +----------------------------------------------------------------------+
+ * | |
+ * ------------------ ------------------ |
+ * | | | | |
+ * | BOOT_FAILED |<-------| READY_FAILED | |
+ * | |<--+ | | ------------------ |
+ * ------------------ | ------------------ | | |
+ * ^ | ^ | CRASHED |---+ |
+ * | | | | | | |
+ * (FW Boot Timeout) | (FW_READY FAIL) ------------------ | |
+ * | | | ^ | |
+ * | | | |(DSP Panic) | |
+ * ------------------ | | ------------------ | |
+ * | | | | | | | |
+ * | IN_PROGRESS |---------------+------------->| COMPLETE | | |
+ * | | (FW Boot OK) (FW_READY OK) | | | |
+ * ------------------ | ------------------ | |
+ * ^ | | | |
+ * | | | | |
+ * (FW Loading OK) | (System Suspend/Runtime Suspend)
+ * | | | | |
+ * | (FW Loading Fail) | | |
+ * ------------------ | ------------------ | | |
+ * | | | | |<-----+ | |
+ * | PREPARE |---+ | NOT_STARTED |<---------------------+ |
+ * | | | |<--------------------------+
* ------------------ ------------------
* | ^ | ^
* | | | |
@@ -140,26 +416,21 @@ static int sof_probe_continue(struct snd_sof_dev *sdev)
struct snd_sof_pdata *plat_data = sdev->pdata;
int ret;
- /* probe the DSP hardware */
- ret = snd_sof_probe(sdev);
- if (ret < 0) {
- dev_err(sdev->dev, "error: failed to probe DSP %d\n", ret);
+ /* Initialize loadable file paths and check the environment validity */
+ ret = sof_init_environment(sdev);
+ if (ret)
return ret;
- }
- sdev->fw_state = SOF_FW_BOOT_PREPARE;
-
- /* check machine info */
- ret = sof_machine_check(sdev);
- if (ret < 0) {
- dev_err(sdev->dev, "error: failed to get machine info %d\n",
- ret);
- goto dbg_err;
- }
+ sof_set_fw_state(sdev, SOF_FW_BOOT_PREPARE);
/* set up platform component driver */
snd_sof_new_platform_drv(sdev);
+ if (sdev->dspless_mode_selected) {
+ sof_set_fw_state(sdev, SOF_DSPLESS_MODE);
+ goto skip_dsp_init;
+ }
+
/* register any debug/trace capabilities */
ret = snd_sof_dbg_init(sdev);
if (ret < 0) {
@@ -186,10 +457,11 @@ static int sof_probe_continue(struct snd_sof_dev *sdev)
if (ret < 0) {
dev_err(sdev->dev, "error: failed to load DSP firmware %d\n",
ret);
+ sof_set_fw_state(sdev, SOF_FW_BOOT_FAILED);
goto fw_load_err;
}
- sdev->fw_state = SOF_FW_BOOT_IN_PROGRESS;
+ sof_set_fw_state(sdev, SOF_FW_BOOT_IN_PROGRESS);
/*
* Boot the firmware. The FW boot status will be modified
@@ -199,25 +471,25 @@ static int sof_probe_continue(struct snd_sof_dev *sdev)
if (ret < 0) {
dev_err(sdev->dev, "error: failed to boot DSP firmware %d\n",
ret);
+ sof_set_fw_state(sdev, SOF_FW_BOOT_FAILED);
goto fw_run_err;
}
- if (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE) ||
- (sof_core_debug & SOF_DBG_ENABLE_TRACE)) {
- sdev->dtrace_is_supported = true;
+ if (sof_debug_check_flag(SOF_DBG_ENABLE_TRACE)) {
+ sdev->fw_trace_is_supported = true;
- /* init DMA trace */
- ret = snd_sof_init_trace(sdev);
+ /* init firmware tracing */
+ ret = sof_fw_trace_init(sdev);
if (ret < 0) {
/* non fatal */
- dev_warn(sdev->dev,
- "warning: failed to initialize trace %d\n",
+ dev_warn(sdev->dev, "failed to initialize firmware tracing %d\n",
ret);
}
} else {
dev_dbg(sdev->dev, "SOF firmware trace disabled\n");
}
+skip_dsp_init:
/* hereafter all FW boot flows are for PM reasons */
sdev->first_boot = false;
@@ -232,8 +504,17 @@ static int sof_probe_continue(struct snd_sof_dev *sdev)
}
ret = snd_sof_machine_register(sdev, plat_data);
- if (ret < 0)
+ if (ret < 0) {
+ dev_err(sdev->dev,
+ "error: failed to register machine driver %d\n", ret);
goto fw_trace_err;
+ }
+
+ ret = sof_register_clients(sdev);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to register clients %d\n", ret);
+ goto sof_machine_err;
+ }
/*
* Some platforms in SOF, ex: BYT, may not have their platform PM
@@ -246,21 +527,27 @@ static int sof_probe_continue(struct snd_sof_dev *sdev)
if (plat_data->sof_probe_complete)
plat_data->sof_probe_complete(sdev->dev);
+ sdev->probe_completed = true;
+
return 0;
+sof_machine_err:
+ snd_sof_machine_unregister(sdev, plat_data);
fw_trace_err:
- snd_sof_free_trace(sdev);
+ sof_fw_trace_free(sdev);
fw_run_err:
snd_sof_fw_unload(sdev);
fw_load_err:
snd_sof_ipc_free(sdev);
ipc_err:
- snd_sof_free_debug(sdev);
dbg_err:
+ snd_sof_free_debug(sdev);
snd_sof_remove(sdev);
+ snd_sof_remove_late(sdev);
+ sof_ops_free(sdev);
/* all resources freed, update state to match */
- sdev->fw_state = SOF_FW_BOOT_NOT_STARTED;
+ sof_set_fw_state(sdev, SOF_FW_BOOT_NOT_STARTED);
sdev->first_boot = true;
return ret;
@@ -282,6 +569,7 @@ static void sof_probe_work(struct work_struct *work)
int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data)
{
struct snd_sof_dev *sdev;
+ int ret;
sdev = devm_kzalloc(dev, sizeof(*sdev), GFP_KERNEL);
if (!sdev)
@@ -295,30 +583,40 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data)
sdev->pdata = plat_data;
sdev->first_boot = true;
- sdev->fw_state = SOF_FW_BOOT_NOT_STARTED;
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES)
- sdev->extractor_stream_tag = SOF_PROBE_INVALID_NODE_ID;
-#endif
dev_set_drvdata(dev, sdev);
- /* check all mandatory ops */
- if (!sof_ops(sdev) || !sof_ops(sdev)->probe || !sof_ops(sdev)->run ||
- !sof_ops(sdev)->block_read || !sof_ops(sdev)->block_write ||
- !sof_ops(sdev)->send_msg || !sof_ops(sdev)->load_firmware ||
- !sof_ops(sdev)->ipc_msg_data || !sof_ops(sdev)->ipc_pcm_params ||
- !sof_ops(sdev)->fw_ready)
- return -EINVAL;
+ if (sof_core_debug)
+ dev_info(dev, "sof_debug value: %#x\n", sof_core_debug);
+
+ if (sof_debug_check_flag(SOF_DBG_DSPLESS_MODE)) {
+ if (plat_data->desc->dspless_mode_supported) {
+ dev_info(dev, "Switching to DSPless mode\n");
+ sdev->dspless_mode_selected = true;
+ } else {
+ dev_info(dev, "DSPless mode is not supported by the platform\n");
+ }
+ }
+
+ /* Initialize sof_ops based on the initial selected IPC version */
+ ret = sof_init_sof_ops(sdev);
+ if (ret)
+ return ret;
INIT_LIST_HEAD(&sdev->pcm_list);
INIT_LIST_HEAD(&sdev->kcontrol_list);
INIT_LIST_HEAD(&sdev->widget_list);
+ INIT_LIST_HEAD(&sdev->pipeline_list);
INIT_LIST_HEAD(&sdev->dai_list);
+ INIT_LIST_HEAD(&sdev->dai_link_list);
INIT_LIST_HEAD(&sdev->route_list);
+ INIT_LIST_HEAD(&sdev->ipc_client_list);
+ INIT_LIST_HEAD(&sdev->ipc_rx_handler_list);
+ INIT_LIST_HEAD(&sdev->fw_state_handler_list);
spin_lock_init(&sdev->ipc_lock);
spin_lock_init(&sdev->hw_lock);
-
- if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE))
- INIT_WORK(&sdev->probe_work, sof_probe_work);
+ mutex_init(&sdev->power_state_access);
+ mutex_init(&sdev->ipc_client_mutex);
+ mutex_init(&sdev->client_event_handler_mutex);
/* set default timeouts if none provided */
if (plat_data->desc->ipc_timeout == 0)
@@ -330,7 +628,18 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data)
else
sdev->boot_timeout = plat_data->desc->boot_timeout;
+ sof_set_fw_state(sdev, SOF_FW_BOOT_NOT_STARTED);
+
+ /*
+ * first pass of probe which isn't allowed to run in a work-queue,
+ * typically to rely on -EPROBE_DEFER dependencies
+ */
+ ret = snd_sof_probe_early(sdev);
+ if (ret < 0)
+ return ret;
+
if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) {
+ INIT_WORK(&sdev->probe_work, sof_probe_work);
schedule_work(&sdev->probe_work);
return 0;
}
@@ -339,26 +648,29 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data)
}
EXPORT_SYMBOL(snd_sof_device_probe);
+bool snd_sof_device_probe_completed(struct device *dev)
+{
+ struct snd_sof_dev *sdev = dev_get_drvdata(dev);
+
+ return sdev->probe_completed;
+}
+EXPORT_SYMBOL(snd_sof_device_probe_completed);
+
int snd_sof_device_remove(struct device *dev)
{
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
struct snd_sof_pdata *pdata = sdev->pdata;
int ret;
+ bool aborted = false;
if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE))
- cancel_work_sync(&sdev->probe_work);
-
- if (sdev->fw_state > SOF_FW_BOOT_NOT_STARTED) {
- ret = snd_sof_dsp_power_down_notify(sdev);
- if (ret < 0)
- dev_warn(dev, "error: %d failed to prepare DSP for device removal",
- ret);
+ aborted = cancel_work_sync(&sdev->probe_work);
- snd_sof_fw_unload(sdev);
- snd_sof_ipc_free(sdev);
- snd_sof_free_debug(sdev);
- snd_sof_free_trace(sdev);
- }
+ /*
+ * Unregister any registered client device first before IPC and debugfs
+ * to allow client drivers to be removed cleanly
+ */
+ sof_unregister_clients(sdev);
/*
* Unregister machine driver. This will unbind the snd_card which
@@ -368,23 +680,92 @@ int snd_sof_device_remove(struct device *dev)
snd_sof_machine_unregister(sdev, pdata);
/*
- * Unregistering the machine driver results in unloading the topology.
- * Some widgets, ex: scheduler, attempt to power down the core they are
- * scheduled on, when they are unloaded. Therefore, the DSP must be
- * removed only after the topology has been unloaded.
+ * Balance the runtime pm usage count in case we are faced with an
+ * exception and we forcably prevented D3 power state to preserve
+ * context
*/
- if (sdev->fw_state > SOF_FW_BOOT_NOT_STARTED)
+ if (sdev->d3_prevented) {
+ sdev->d3_prevented = false;
+ pm_runtime_put_noidle(sdev->dev);
+ }
+
+ if (sdev->fw_state > SOF_FW_BOOT_NOT_STARTED) {
+ sof_fw_trace_free(sdev);
+ ret = snd_sof_dsp_power_down_notify(sdev);
+ if (ret < 0)
+ dev_warn(dev, "error: %d failed to prepare DSP for device removal",
+ ret);
+
+ snd_sof_ipc_free(sdev);
+ snd_sof_free_debug(sdev);
snd_sof_remove(sdev);
+ snd_sof_remove_late(sdev);
+ sof_ops_free(sdev);
+ } else if (aborted) {
+ /* probe_work never ran */
+ snd_sof_remove_late(sdev);
+ sof_ops_free(sdev);
+ }
/* release firmware */
- release_firmware(pdata->fw);
- pdata->fw = NULL;
+ snd_sof_fw_unload(sdev);
return 0;
}
EXPORT_SYMBOL(snd_sof_device_remove);
+int snd_sof_device_shutdown(struct device *dev)
+{
+ struct snd_sof_dev *sdev = dev_get_drvdata(dev);
+
+ if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE))
+ cancel_work_sync(&sdev->probe_work);
+
+ if (sdev->fw_state == SOF_FW_BOOT_COMPLETE) {
+ sof_fw_trace_free(sdev);
+ return snd_sof_shutdown(sdev);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(snd_sof_device_shutdown);
+
+/* Machine driver registering and unregistering */
+int sof_machine_register(struct snd_sof_dev *sdev, void *pdata)
+{
+ struct snd_sof_pdata *plat_data = pdata;
+ const char *drv_name;
+ const void *mach;
+ int size;
+
+ drv_name = plat_data->machine->drv_name;
+ mach = plat_data->machine;
+ size = sizeof(*plat_data->machine);
+
+ /* register machine driver, pass machine info as pdata */
+ plat_data->pdev_mach =
+ platform_device_register_data(sdev->dev, drv_name,
+ PLATFORM_DEVID_NONE, mach, size);
+ if (IS_ERR(plat_data->pdev_mach))
+ return PTR_ERR(plat_data->pdev_mach);
+
+ dev_dbg(sdev->dev, "created machine %s\n",
+ dev_name(&plat_data->pdev_mach->dev));
+
+ return 0;
+}
+EXPORT_SYMBOL(sof_machine_register);
+
+void sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata)
+{
+ struct snd_sof_pdata *plat_data = pdata;
+
+ platform_device_unregister(plat_data->pdev_mach);
+}
+EXPORT_SYMBOL(sof_machine_unregister);
+
MODULE_AUTHOR("Liam Girdwood");
MODULE_DESCRIPTION("Sound Open Firmware (SOF) Core");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_ALIAS("platform:sof-audio");
+MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT);
diff --git a/sound/soc/sof/debug.c b/sound/soc/sof/debug.c
index 8e15f105d1d5..7c8aafca8fde 100644
--- a/sound/soc/sof/debug.c
+++ b/sound/soc/sof/debug.c
@@ -14,421 +14,25 @@
#include <linux/debugfs.h>
#include <linux/io.h>
#include <linux/pm_runtime.h>
+#include <sound/sof/ext_manifest.h>
+#include <sound/sof/debug.h>
#include "sof-priv.h"
#include "ops.h"
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES)
-#include "probe.h"
-
-/**
- * strsplit_u32 - Split string into sequence of u32 tokens
- * @buf: String to split into tokens.
- * @delim: String containing delimiter characters.
- * @tkns: Returned u32 sequence pointer.
- * @num_tkns: Returned number of tokens obtained.
- */
-static int
-strsplit_u32(char **buf, const char *delim, u32 **tkns, size_t *num_tkns)
-{
- char *s;
- u32 *data, *tmp;
- size_t count = 0;
- size_t cap = 32;
- int ret = 0;
-
- *tkns = NULL;
- *num_tkns = 0;
- data = kcalloc(cap, sizeof(*data), GFP_KERNEL);
- if (!data)
- return -ENOMEM;
-
- while ((s = strsep(buf, delim)) != NULL) {
- ret = kstrtouint(s, 0, data + count);
- if (ret)
- goto exit;
- if (++count >= cap) {
- cap *= 2;
- tmp = krealloc(data, cap * sizeof(*data), GFP_KERNEL);
- if (!tmp) {
- ret = -ENOMEM;
- goto exit;
- }
- data = tmp;
- }
- }
-
- if (!count)
- goto exit;
- *tkns = kmemdup(data, count * sizeof(*data), GFP_KERNEL);
- if (*tkns == NULL) {
- ret = -ENOMEM;
- goto exit;
- }
- *num_tkns = count;
-
-exit:
- kfree(data);
- return ret;
-}
-
-static int tokenize_input(const char __user *from, size_t count,
- loff_t *ppos, u32 **tkns, size_t *num_tkns)
-{
- char *buf;
- int ret;
-
- buf = kmalloc(count + 1, GFP_KERNEL);
- if (!buf)
- return -ENOMEM;
-
- ret = simple_write_to_buffer(buf, count, ppos, from, count);
- if (ret != count) {
- ret = ret >= 0 ? -EIO : ret;
- goto exit;
- }
-
- buf[count] = '\0';
- ret = strsplit_u32((char **)&buf, ",", tkns, num_tkns);
-exit:
- kfree(buf);
- return ret;
-}
-
-static ssize_t probe_points_read(struct file *file,
- char __user *to, size_t count, loff_t *ppos)
-{
- struct snd_sof_dfsentry *dfse = file->private_data;
- struct snd_sof_dev *sdev = dfse->sdev;
- struct sof_probe_point_desc *desc;
- size_t num_desc, len = 0;
- char *buf;
- int i, ret;
-
- if (sdev->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) {
- dev_warn(sdev->dev, "no extractor stream running\n");
- return -ENOENT;
- }
-
- buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
- if (!buf)
- return -ENOMEM;
-
- ret = sof_ipc_probe_points_info(sdev, &desc, &num_desc);
- if (ret < 0)
- goto exit;
-
- for (i = 0; i < num_desc; i++) {
- ret = snprintf(buf + len, PAGE_SIZE - len,
- "Id: %#010x Purpose: %d Node id: %#x\n",
- desc[i].buffer_id, desc[i].purpose, desc[i].stream_tag);
- if (ret < 0)
- goto free_desc;
- len += ret;
- }
-
- ret = simple_read_from_buffer(to, count, ppos, buf, len);
-free_desc:
- kfree(desc);
-exit:
- kfree(buf);
- return ret;
-}
-
-static ssize_t probe_points_write(struct file *file,
- const char __user *from, size_t count, loff_t *ppos)
-{
- struct snd_sof_dfsentry *dfse = file->private_data;
- struct snd_sof_dev *sdev = dfse->sdev;
- struct sof_probe_point_desc *desc;
- size_t num_tkns, bytes;
- u32 *tkns;
- int ret;
-
- if (sdev->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) {
- dev_warn(sdev->dev, "no extractor stream running\n");
- return -ENOENT;
- }
-
- ret = tokenize_input(from, count, ppos, &tkns, &num_tkns);
- if (ret < 0)
- return ret;
- bytes = sizeof(*tkns) * num_tkns;
- if (!num_tkns || (bytes % sizeof(*desc))) {
- ret = -EINVAL;
- goto exit;
- }
-
- desc = (struct sof_probe_point_desc *)tkns;
- ret = sof_ipc_probe_points_add(sdev,
- desc, bytes / sizeof(*desc));
- if (!ret)
- ret = count;
-exit:
- kfree(tkns);
- return ret;
-}
-
-static const struct file_operations probe_points_fops = {
- .open = simple_open,
- .read = probe_points_read,
- .write = probe_points_write,
- .llseek = default_llseek,
-};
-
-static ssize_t probe_points_remove_write(struct file *file,
- const char __user *from, size_t count, loff_t *ppos)
-{
- struct snd_sof_dfsentry *dfse = file->private_data;
- struct snd_sof_dev *sdev = dfse->sdev;
- size_t num_tkns;
- u32 *tkns;
- int ret;
-
- if (sdev->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) {
- dev_warn(sdev->dev, "no extractor stream running\n");
- return -ENOENT;
- }
-
- ret = tokenize_input(from, count, ppos, &tkns, &num_tkns);
- if (ret < 0)
- return ret;
- if (!num_tkns) {
- ret = -EINVAL;
- goto exit;
- }
-
- ret = sof_ipc_probe_points_remove(sdev, tkns, num_tkns);
- if (!ret)
- ret = count;
-exit:
- kfree(tkns);
- return ret;
-}
-
-static const struct file_operations probe_points_remove_fops = {
- .open = simple_open,
- .write = probe_points_remove_write,
- .llseek = default_llseek,
-};
-
-static int snd_sof_debugfs_probe_item(struct snd_sof_dev *sdev,
- const char *name, mode_t mode,
- const struct file_operations *fops)
-{
- struct snd_sof_dfsentry *dfse;
-
- dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL);
- if (!dfse)
- return -ENOMEM;
-
- dfse->type = SOF_DFSENTRY_TYPE_BUF;
- dfse->sdev = sdev;
-
- debugfs_create_file(name, mode, sdev->debugfs_root, dfse, fops);
- /* add to dfsentry list */
- list_add(&dfse->list, &sdev->dfsentry_list);
-
- return 0;
-}
-#endif
-
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)
-#define MAX_IPC_FLOOD_DURATION_MS 1000
-#define MAX_IPC_FLOOD_COUNT 10000
-#define IPC_FLOOD_TEST_RESULT_LEN 512
-
-static int sof_debug_ipc_flood_test(struct snd_sof_dev *sdev,
- struct snd_sof_dfsentry *dfse,
- bool flood_duration_test,
- unsigned long ipc_duration_ms,
- unsigned long ipc_count)
-{
- struct sof_ipc_cmd_hdr hdr;
- struct sof_ipc_reply reply;
- u64 min_response_time = U64_MAX;
- ktime_t start, end, test_end;
- u64 avg_response_time = 0;
- u64 max_response_time = 0;
- u64 ipc_response_time;
- int i = 0;
- int ret;
-
- /* configure test IPC */
- hdr.cmd = SOF_IPC_GLB_TEST_MSG | SOF_IPC_TEST_IPC_FLOOD;
- hdr.size = sizeof(hdr);
-
- /* set test end time for duration flood test */
- if (flood_duration_test)
- test_end = ktime_get_ns() + ipc_duration_ms * NSEC_PER_MSEC;
-
- /* send test IPC's */
- while (1) {
- start = ktime_get();
- ret = sof_ipc_tx_message(sdev->ipc, hdr.cmd, &hdr, hdr.size,
- &reply, sizeof(reply));
- end = ktime_get();
-
- if (ret < 0)
- break;
-
- /* compute min and max response times */
- ipc_response_time = ktime_to_ns(ktime_sub(end, start));
- min_response_time = min(min_response_time, ipc_response_time);
- max_response_time = max(max_response_time, ipc_response_time);
-
- /* sum up response times */
- avg_response_time += ipc_response_time;
- i++;
-
- /* test complete? */
- if (flood_duration_test) {
- if (ktime_to_ns(end) >= test_end)
- break;
- } else {
- if (i == ipc_count)
- break;
- }
- }
-
- if (ret < 0)
- dev_err(sdev->dev,
- "error: ipc flood test failed at %d iterations\n", i);
-
- /* return if the first IPC fails */
- if (!i)
- return ret;
-
- /* compute average response time */
- do_div(avg_response_time, i);
-
- /* clear previous test output */
- memset(dfse->cache_buf, 0, IPC_FLOOD_TEST_RESULT_LEN);
-
- if (flood_duration_test) {
- dev_dbg(sdev->dev, "IPC Flood test duration: %lums\n",
- ipc_duration_ms);
- snprintf(dfse->cache_buf, IPC_FLOOD_TEST_RESULT_LEN,
- "IPC Flood test duration: %lums\n", ipc_duration_ms);
- }
-
- dev_dbg(sdev->dev,
- "IPC Flood count: %d, Avg response time: %lluns\n",
- i, avg_response_time);
- dev_dbg(sdev->dev, "Max response time: %lluns\n",
- max_response_time);
- dev_dbg(sdev->dev, "Min response time: %lluns\n",
- min_response_time);
-
- /* format output string */
- snprintf(dfse->cache_buf + strlen(dfse->cache_buf),
- IPC_FLOOD_TEST_RESULT_LEN - strlen(dfse->cache_buf),
- "IPC Flood count: %d\nAvg response time: %lluns\n",
- i, avg_response_time);
-
- snprintf(dfse->cache_buf + strlen(dfse->cache_buf),
- IPC_FLOOD_TEST_RESULT_LEN - strlen(dfse->cache_buf),
- "Max response time: %lluns\nMin response time: %lluns\n",
- max_response_time, min_response_time);
-
- return ret;
-}
-#endif
-
static ssize_t sof_dfsentry_write(struct file *file, const char __user *buffer,
size_t count, loff_t *ppos)
{
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)
- struct snd_sof_dfsentry *dfse = file->private_data;
- struct snd_sof_dev *sdev = dfse->sdev;
- unsigned long ipc_duration_ms = 0;
- bool flood_duration_test = false;
- unsigned long ipc_count = 0;
- struct dentry *dentry;
- int err;
-#endif
size_t size;
char *string;
int ret;
- string = kzalloc(count, GFP_KERNEL);
+ string = kzalloc(count+1, GFP_KERNEL);
if (!string)
return -ENOMEM;
size = simple_write_to_buffer(string, count, ppos, buffer, count);
ret = size;
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)
- /*
- * write op is only supported for ipc_flood_count or
- * ipc_flood_duration_ms debugfs entries atm.
- * ipc_flood_count floods the DSP with the number of IPC's specified.
- * ipc_duration_ms test floods the DSP for the time specified
- * in the debugfs entry.
- */
- dentry = file->f_path.dentry;
- if (strcmp(dentry->d_name.name, "ipc_flood_count") &&
- strcmp(dentry->d_name.name, "ipc_flood_duration_ms")) {
- ret = -EINVAL;
- goto out;
- }
-
- if (!strcmp(dentry->d_name.name, "ipc_flood_duration_ms"))
- flood_duration_test = true;
-
- /* test completion criterion */
- if (flood_duration_test)
- ret = kstrtoul(string, 0, &ipc_duration_ms);
- else
- ret = kstrtoul(string, 0, &ipc_count);
- if (ret < 0)
- goto out;
-
- /* limit max duration/ipc count for flood test */
- if (flood_duration_test) {
- if (!ipc_duration_ms) {
- ret = size;
- goto out;
- }
-
- /* find the minimum. min() is not used to avoid warnings */
- if (ipc_duration_ms > MAX_IPC_FLOOD_DURATION_MS)
- ipc_duration_ms = MAX_IPC_FLOOD_DURATION_MS;
- } else {
- if (!ipc_count) {
- ret = size;
- goto out;
- }
-
- /* find the minimum. min() is not used to avoid warnings */
- if (ipc_count > MAX_IPC_FLOOD_COUNT)
- ipc_count = MAX_IPC_FLOOD_COUNT;
- }
-
- ret = pm_runtime_get_sync(sdev->dev);
- if (ret < 0) {
- dev_err_ratelimited(sdev->dev,
- "error: debugfs write failed to resume %d\n",
- ret);
- pm_runtime_put_noidle(sdev->dev);
- goto out;
- }
-
- /* flood test */
- ret = sof_debug_ipc_flood_test(sdev, dfse, flood_duration_test,
- ipc_duration_ms, ipc_count);
-
- pm_runtime_mark_last_busy(sdev->dev);
- err = pm_runtime_put_autosuspend(sdev->dev);
- if (err < 0)
- dev_err_ratelimited(sdev->dev,
- "error: debugfs write failed to idle %d\n",
- err);
-
- /* return size if test is successful */
- if (ret >= 0)
- ret = size;
-out:
-#endif
kfree(string);
return ret;
}
@@ -444,25 +48,6 @@ static ssize_t sof_dfsentry_read(struct file *file, char __user *buffer,
int size;
u8 *buf;
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)
- struct dentry *dentry;
-
- dentry = file->f_path.dentry;
- if ((!strcmp(dentry->d_name.name, "ipc_flood_count") ||
- !strcmp(dentry->d_name.name, "ipc_flood_duration_ms")) &&
- dfse->cache_buf) {
- if (*ppos)
- return 0;
-
- count = strlen(dfse->cache_buf);
- size_ret = copy_to_user(buffer, dfse->cache_buf, count);
- if (size_ret)
- return -EFAULT;
-
- *ppos += count;
- return count;
- }
-#endif
size = dfse->size;
/* validate position & count */
@@ -545,10 +130,10 @@ static const struct file_operations sof_dfs_fops = {
};
/* create FS entry for debug files that can expose DSP memories, registers */
-int snd_sof_debugfs_io_item(struct snd_sof_dev *sdev,
- void __iomem *base, size_t size,
- const char *name,
- enum sof_debugfs_access_type access_type)
+static int snd_sof_debugfs_io_item(struct snd_sof_dev *sdev,
+ void __iomem *base, size_t size,
+ const char *name,
+ enum sof_debugfs_access_type access_type)
{
struct snd_sof_dfsentry *dfse;
@@ -585,7 +170,21 @@ int snd_sof_debugfs_io_item(struct snd_sof_dev *sdev,
return 0;
}
-EXPORT_SYMBOL_GPL(snd_sof_debugfs_io_item);
+
+int snd_sof_debugfs_add_region_item_iomem(struct snd_sof_dev *sdev,
+ enum snd_sof_fw_blk_type blk_type, u32 offset,
+ size_t size, const char *name,
+ enum sof_debugfs_access_type access_type)
+{
+ int bar = snd_sof_dsp_get_bar_index(sdev, blk_type);
+
+ if (bar < 0)
+ return bar;
+
+ return snd_sof_debugfs_io_item(sdev, sdev->bar[bar] + offset, size, name,
+ access_type);
+}
+EXPORT_SYMBOL_GPL(snd_sof_debugfs_add_region_item_iomem);
/* create FS entry for debug files to expose kernel memory */
int snd_sof_debugfs_buf_item(struct snd_sof_dev *sdev,
@@ -606,17 +205,6 @@ int snd_sof_debugfs_buf_item(struct snd_sof_dev *sdev,
dfse->size = size;
dfse->sdev = sdev;
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)
- /*
- * cache_buf is unused for SOF_DFSENTRY_TYPE_BUF debugfs entries.
- * So, use it to save the results of the last IPC flood test.
- */
- dfse->cache_buf = devm_kzalloc(sdev->dev, IPC_FLOOD_TEST_RESULT_LEN,
- GFP_KERNEL);
- if (!dfse->cache_buf)
- return -ENOMEM;
-#endif
-
debugfs_create_file(name, mode, sdev->debugfs_root, dfse,
&sof_dfs_fops);
/* add to dfsentry list */
@@ -626,9 +214,123 @@ int snd_sof_debugfs_buf_item(struct snd_sof_dev *sdev,
}
EXPORT_SYMBOL_GPL(snd_sof_debugfs_buf_item);
+static int memory_info_update(struct snd_sof_dev *sdev, char *buf, size_t buff_size)
+{
+ struct sof_ipc_cmd_hdr msg = {
+ .size = sizeof(struct sof_ipc_cmd_hdr),
+ .cmd = SOF_IPC_GLB_DEBUG | SOF_IPC_DEBUG_MEM_USAGE,
+ };
+ struct sof_ipc_dbg_mem_usage *reply;
+ int len;
+ int ret;
+ int i;
+
+ reply = kmalloc(SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL);
+ if (!reply)
+ return -ENOMEM;
+
+ ret = pm_runtime_resume_and_get(sdev->dev);
+ if (ret < 0 && ret != -EACCES) {
+ dev_err(sdev->dev, "error: enabling device failed: %d\n", ret);
+ goto error;
+ }
+
+ ret = sof_ipc_tx_message(sdev->ipc, &msg, msg.size, reply, SOF_IPC_MSG_MAX_SIZE);
+ pm_runtime_mark_last_busy(sdev->dev);
+ pm_runtime_put_autosuspend(sdev->dev);
+ if (ret < 0 || reply->rhdr.error < 0) {
+ ret = min(ret, reply->rhdr.error);
+ dev_err(sdev->dev, "error: reading memory info failed, %d\n", ret);
+ goto error;
+ }
+
+ if (struct_size(reply, elems, reply->num_elems) != reply->rhdr.hdr.size) {
+ dev_err(sdev->dev, "error: invalid memory info ipc struct size, %d\n",
+ reply->rhdr.hdr.size);
+ ret = -EINVAL;
+ goto error;
+ }
+
+ for (i = 0, len = 0; i < reply->num_elems; i++) {
+ ret = scnprintf(buf + len, buff_size - len, "zone %d.%d used %#8x free %#8x\n",
+ reply->elems[i].zone, reply->elems[i].id,
+ reply->elems[i].used, reply->elems[i].free);
+ if (ret < 0)
+ goto error;
+ len += ret;
+ }
+
+ ret = len;
+error:
+ kfree(reply);
+ return ret;
+}
+
+static ssize_t memory_info_read(struct file *file, char __user *to, size_t count, loff_t *ppos)
+{
+ struct snd_sof_dfsentry *dfse = file->private_data;
+ struct snd_sof_dev *sdev = dfse->sdev;
+ int data_length;
+
+ /* read memory info from FW only once for each file read */
+ if (!*ppos) {
+ dfse->buf_data_size = 0;
+ data_length = memory_info_update(sdev, dfse->buf, dfse->size);
+ if (data_length < 0)
+ return data_length;
+ dfse->buf_data_size = data_length;
+ }
+
+ return simple_read_from_buffer(to, count, ppos, dfse->buf, dfse->buf_data_size);
+}
+
+static int memory_info_open(struct inode *inode, struct file *file)
+{
+ struct snd_sof_dfsentry *dfse = inode->i_private;
+ struct snd_sof_dev *sdev = dfse->sdev;
+
+ file->private_data = dfse;
+
+ /* allocate buffer memory only in first open run, to save memory when unused */
+ if (!dfse->buf) {
+ dfse->buf = devm_kmalloc(sdev->dev, PAGE_SIZE, GFP_KERNEL);
+ if (!dfse->buf)
+ return -ENOMEM;
+ dfse->size = PAGE_SIZE;
+ }
+
+ return 0;
+}
+
+static const struct file_operations memory_info_fops = {
+ .open = memory_info_open,
+ .read = memory_info_read,
+ .llseek = default_llseek,
+};
+
+int snd_sof_dbg_memory_info_init(struct snd_sof_dev *sdev)
+{
+ struct snd_sof_dfsentry *dfse;
+
+ dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL);
+ if (!dfse)
+ return -ENOMEM;
+
+ /* don't allocate buffer before first usage, to save memory when unused */
+ dfse->type = SOF_DFSENTRY_TYPE_BUF;
+ dfse->sdev = sdev;
+
+ debugfs_create_file("memory_info", 0444, sdev->debugfs_root, dfse, &memory_info_fops);
+
+ /* add to dfsentry list */
+ list_add(&dfse->list, &sdev->dfsentry_list);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_sof_dbg_memory_info_init);
+
int snd_sof_dbg_init(struct snd_sof_dev *sdev)
{
- const struct snd_sof_dsp_ops *ops = sof_ops(sdev);
+ struct snd_sof_dsp_ops *ops = sof_ops(sdev);
const struct snd_sof_debugfs_map *map;
int i;
int err;
@@ -651,57 +353,100 @@ int snd_sof_dbg_init(struct snd_sof_dev *sdev)
return err;
}
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES)
- err = snd_sof_debugfs_probe_item(sdev, "probe_points",
- 0644, &probe_points_fops);
- if (err < 0)
- return err;
- err = snd_sof_debugfs_probe_item(sdev, "probe_points_remove",
- 0200, &probe_points_remove_fops);
- if (err < 0)
- return err;
-#endif
+ return snd_sof_debugfs_buf_item(sdev, &sdev->fw_state,
+ sizeof(sdev->fw_state),
+ "fw_state", 0444);
+}
+EXPORT_SYMBOL_GPL(snd_sof_dbg_init);
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)
- /* create read-write ipc_flood_count debugfs entry */
- err = snd_sof_debugfs_buf_item(sdev, NULL, 0,
- "ipc_flood_count", 0666);
+void snd_sof_free_debug(struct snd_sof_dev *sdev)
+{
+ debugfs_remove_recursive(sdev->debugfs_root);
+}
+EXPORT_SYMBOL_GPL(snd_sof_free_debug);
- /* errors are only due to memory allocation, not debugfs */
- if (err < 0)
- return err;
+static const struct soc_fw_state_info {
+ enum sof_fw_state state;
+ const char *name;
+} fw_state_dbg[] = {
+ {SOF_FW_BOOT_NOT_STARTED, "SOF_FW_BOOT_NOT_STARTED"},
+ {SOF_DSPLESS_MODE, "SOF_DSPLESS_MODE"},
+ {SOF_FW_BOOT_PREPARE, "SOF_FW_BOOT_PREPARE"},
+ {SOF_FW_BOOT_IN_PROGRESS, "SOF_FW_BOOT_IN_PROGRESS"},
+ {SOF_FW_BOOT_FAILED, "SOF_FW_BOOT_FAILED"},
+ {SOF_FW_BOOT_READY_FAILED, "SOF_FW_BOOT_READY_FAILED"},
+ {SOF_FW_BOOT_READY_OK, "SOF_FW_BOOT_READY_OK"},
+ {SOF_FW_BOOT_COMPLETE, "SOF_FW_BOOT_COMPLETE"},
+ {SOF_FW_CRASHED, "SOF_FW_CRASHED"},
+};
- /* create read-write ipc_flood_duration_ms debugfs entry */
- err = snd_sof_debugfs_buf_item(sdev, NULL, 0,
- "ipc_flood_duration_ms", 0666);
+static void snd_sof_dbg_print_fw_state(struct snd_sof_dev *sdev, const char *level)
+{
+ int i;
- /* errors are only due to memory allocation, not debugfs */
- if (err < 0)
- return err;
-#endif
+ for (i = 0; i < ARRAY_SIZE(fw_state_dbg); i++) {
+ if (sdev->fw_state == fw_state_dbg[i].state) {
+ dev_printk(level, sdev->dev, "fw_state: %s (%d)\n",
+ fw_state_dbg[i].name, i);
+ return;
+ }
+ }
- return 0;
+ dev_printk(level, sdev->dev, "fw_state: UNKNOWN (%d)\n", sdev->fw_state);
}
-EXPORT_SYMBOL_GPL(snd_sof_dbg_init);
-void snd_sof_free_debug(struct snd_sof_dev *sdev)
+void snd_sof_dsp_dbg_dump(struct snd_sof_dev *sdev, const char *msg, u32 flags)
{
- debugfs_remove_recursive(sdev->debugfs_root);
+ char *level = (flags & SOF_DBG_DUMP_OPTIONAL) ? KERN_DEBUG : KERN_ERR;
+ bool print_all = sof_debug_check_flag(SOF_DBG_PRINT_ALL_DUMPS);
+
+ if (flags & SOF_DBG_DUMP_OPTIONAL && !print_all)
+ return;
+
+ if (sof_ops(sdev)->dbg_dump && !sdev->dbg_dump_printed) {
+ dev_printk(level, sdev->dev,
+ "------------[ DSP dump start ]------------\n");
+ if (msg)
+ dev_printk(level, sdev->dev, "%s\n", msg);
+ snd_sof_dbg_print_fw_state(sdev, level);
+ sof_ops(sdev)->dbg_dump(sdev, flags);
+ dev_printk(level, sdev->dev,
+ "------------[ DSP dump end ]------------\n");
+ if (!print_all)
+ sdev->dbg_dump_printed = true;
+ } else if (msg) {
+ dev_printk(level, sdev->dev, "%s\n", msg);
+ }
}
-EXPORT_SYMBOL_GPL(snd_sof_free_debug);
+EXPORT_SYMBOL(snd_sof_dsp_dbg_dump);
-void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev)
+static void snd_sof_ipc_dump(struct snd_sof_dev *sdev)
{
- if (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_RETAIN_DSP_CONTEXT) ||
- (sof_core_debug & SOF_DBG_RETAIN_CTX)) {
+ if (sof_ops(sdev)->ipc_dump && !sdev->ipc_dump_printed) {
+ dev_err(sdev->dev, "------------[ IPC dump start ]------------\n");
+ sof_ops(sdev)->ipc_dump(sdev);
+ dev_err(sdev->dev, "------------[ IPC dump end ]------------\n");
+ if (!sof_debug_check_flag(SOF_DBG_PRINT_ALL_DUMPS))
+ sdev->ipc_dump_printed = true;
+ }
+}
+
+void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev, const char *msg)
+{
+ if ((IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_RETAIN_DSP_CONTEXT) ||
+ sof_debug_check_flag(SOF_DBG_RETAIN_CTX)) && !sdev->d3_prevented) {
/* should we prevent DSP entering D3 ? */
- dev_info(sdev->dev, "info: preventing DSP entering D3 state to preserve context\n");
- pm_runtime_get_noresume(sdev->dev);
+ if (!sdev->ipc_dump_printed)
+ dev_info(sdev->dev,
+ "Attempting to prevent DSP from entering D3 state to preserve context\n");
+
+ if (pm_runtime_get_if_in_use(sdev->dev) == 1)
+ sdev->d3_prevented = true;
}
/* dump vital information to the logs */
- snd_sof_dsp_dbg_dump(sdev, SOF_DBG_REGS | SOF_DBG_MBOX);
snd_sof_ipc_dump(sdev);
- snd_sof_trace_notify_for_error(sdev);
+ snd_sof_dsp_dbg_dump(sdev, msg, SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX);
+ sof_fw_trace_fw_crashed(sdev);
}
EXPORT_SYMBOL(snd_sof_handle_fw_exception);
diff --git a/sound/soc/sof/fw-file-profile.c b/sound/soc/sof/fw-file-profile.c
new file mode 100644
index 000000000000..b56b14232444
--- /dev/null
+++ b/sound/soc/sof/fw-file-profile.c
@@ -0,0 +1,334 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2023 Intel Corporation. All rights reserved.
+//
+
+#include <linux/firmware.h>
+#include <sound/sof.h>
+#include <sound/sof/ext_manifest4.h>
+#include "sof-priv.h"
+
+static int sof_test_firmware_file(struct device *dev,
+ struct sof_loadable_file_profile *profile,
+ enum sof_ipc_type *ipc_type_to_adjust)
+{
+ enum sof_ipc_type fw_ipc_type;
+ const struct firmware *fw;
+ const char *fw_filename;
+ const u32 *magic;
+ int ret;
+
+ fw_filename = kasprintf(GFP_KERNEL, "%s/%s", profile->fw_path,
+ profile->fw_name);
+ if (!fw_filename)
+ return -ENOMEM;
+
+ ret = firmware_request_nowarn(&fw, fw_filename, dev);
+ if (ret < 0) {
+ dev_dbg(dev, "Failed to open firmware file: %s\n", fw_filename);
+ kfree(fw_filename);
+ return ret;
+ }
+
+ /* firmware file exists, check the magic number */
+ magic = (const u32 *)fw->data;
+ switch (*magic) {
+ case SOF_EXT_MAN_MAGIC_NUMBER:
+ fw_ipc_type = SOF_IPC_TYPE_3;
+ break;
+ case SOF_EXT_MAN4_MAGIC_NUMBER:
+ fw_ipc_type = SOF_IPC_TYPE_4;
+ break;
+ default:
+ dev_err(dev, "Invalid firmware magic: %#x\n", *magic);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (ipc_type_to_adjust) {
+ *ipc_type_to_adjust = fw_ipc_type;
+ } else if (fw_ipc_type != profile->ipc_type) {
+ dev_err(dev,
+ "ipc type mismatch between %s and expected: %d vs %d\n",
+ fw_filename, fw_ipc_type, profile->ipc_type);
+ ret = -EINVAL;
+ }
+out:
+ release_firmware(fw);
+ kfree(fw_filename);
+
+ return ret;
+}
+
+static int sof_test_topology_file(struct device *dev,
+ struct sof_loadable_file_profile *profile)
+{
+ const struct firmware *fw;
+ const char *tplg_filename;
+ int ret;
+
+ if (!profile->tplg_path || !profile->tplg_name)
+ return 0;
+
+ tplg_filename = kasprintf(GFP_KERNEL, "%s/%s", profile->tplg_path,
+ profile->tplg_name);
+ if (!tplg_filename)
+ return -ENOMEM;
+
+ ret = firmware_request_nowarn(&fw, tplg_filename, dev);
+ if (!ret)
+ release_firmware(fw);
+ else
+ dev_dbg(dev, "Failed to open topology file: %s\n", tplg_filename);
+
+ kfree(tplg_filename);
+
+ return ret;
+}
+
+static bool sof_platform_uses_generic_loader(struct snd_sof_dev *sdev)
+{
+ return (sdev->pdata->desc->ops->load_firmware == snd_sof_load_firmware_raw ||
+ sdev->pdata->desc->ops->load_firmware == snd_sof_load_firmware_memcpy);
+}
+
+static int
+sof_file_profile_for_ipc_type(struct snd_sof_dev *sdev,
+ enum sof_ipc_type ipc_type,
+ const struct sof_dev_desc *desc,
+ struct sof_loadable_file_profile *base_profile,
+ struct sof_loadable_file_profile *out_profile)
+{
+ struct snd_sof_pdata *plat_data = sdev->pdata;
+ bool fw_lib_path_allocated = false;
+ struct device *dev = sdev->dev;
+ bool fw_path_allocated = false;
+ int ret = 0;
+
+ /* firmware path */
+ if (base_profile->fw_path) {
+ out_profile->fw_path = base_profile->fw_path;
+ } else if (base_profile->fw_path_postfix) {
+ out_profile->fw_path = devm_kasprintf(dev, GFP_KERNEL, "%s/%s",
+ desc->default_fw_path[ipc_type],
+ base_profile->fw_path_postfix);
+ if (!out_profile->fw_path)
+ return -ENOMEM;
+
+ fw_path_allocated = true;
+ } else {
+ out_profile->fw_path = desc->default_fw_path[ipc_type];
+ }
+
+ /* firmware filename */
+ if (base_profile->fw_name)
+ out_profile->fw_name = base_profile->fw_name;
+ else
+ out_profile->fw_name = desc->default_fw_filename[ipc_type];
+
+ /*
+ * Check the custom firmware path/filename and adjust the ipc_type to
+ * match with the existing file for the remaining path configuration.
+ *
+ * For default path and firmware name do a verification before
+ * continuing further.
+ */
+ if ((base_profile->fw_path || base_profile->fw_name) &&
+ sof_platform_uses_generic_loader(sdev)) {
+ ret = sof_test_firmware_file(dev, out_profile, &ipc_type);
+ if (ret)
+ return ret;
+
+ if (!(desc->ipc_supported_mask & BIT(ipc_type))) {
+ dev_err(dev, "Unsupported IPC type %d needed by %s/%s\n",
+ ipc_type, out_profile->fw_path,
+ out_profile->fw_name);
+ return -EINVAL;
+ }
+ }
+
+ /* firmware library path */
+ if (base_profile->fw_lib_path) {
+ out_profile->fw_lib_path = base_profile->fw_lib_path;
+ } else if (desc->default_lib_path[ipc_type]) {
+ if (base_profile->fw_lib_path_postfix) {
+ out_profile->fw_lib_path = devm_kasprintf(dev,
+ GFP_KERNEL, "%s/%s",
+ desc->default_lib_path[ipc_type],
+ base_profile->fw_lib_path_postfix);
+ if (!out_profile->fw_lib_path) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ fw_lib_path_allocated = true;
+ } else {
+ out_profile->fw_lib_path = desc->default_lib_path[ipc_type];
+ }
+ }
+
+ if (base_profile->fw_path_postfix)
+ out_profile->fw_path_postfix = base_profile->fw_path_postfix;
+
+ if (base_profile->fw_lib_path_postfix)
+ out_profile->fw_lib_path_postfix = base_profile->fw_lib_path_postfix;
+
+ /* topology path */
+ if (base_profile->tplg_path)
+ out_profile->tplg_path = base_profile->tplg_path;
+ else
+ out_profile->tplg_path = desc->default_tplg_path[ipc_type];
+
+ /* topology name */
+ out_profile->tplg_name = plat_data->tplg_filename;
+
+ out_profile->ipc_type = ipc_type;
+
+ /* Test only default firmware file */
+ if ((!base_profile->fw_path && !base_profile->fw_name) &&
+ sof_platform_uses_generic_loader(sdev))
+ ret = sof_test_firmware_file(dev, out_profile, NULL);
+
+ if (!ret)
+ ret = sof_test_topology_file(dev, out_profile);
+
+out:
+ if (ret) {
+ /* Free up path strings created with devm_kasprintf */
+ if (fw_path_allocated)
+ devm_kfree(dev, out_profile->fw_path);
+ if (fw_lib_path_allocated)
+ devm_kfree(dev, out_profile->fw_lib_path);
+
+ memset(out_profile, 0, sizeof(*out_profile));
+ }
+
+ return ret;
+}
+
+static void
+sof_print_missing_firmware_info(struct snd_sof_dev *sdev,
+ enum sof_ipc_type ipc_type,
+ struct sof_loadable_file_profile *base_profile)
+{
+ struct snd_sof_pdata *plat_data = sdev->pdata;
+ const struct sof_dev_desc *desc = plat_data->desc;
+ struct device *dev = sdev->dev;
+ int ipc_type_count, i;
+ char *marker;
+
+ dev_err(dev, "SOF firmware and/or topology file not found.\n");
+ dev_info(dev, "Supported default profiles\n");
+
+ if (IS_ENABLED(CONFIG_SND_SOC_SOF_ALLOW_FALLBACK_TO_NEWER_IPC_VERSION))
+ ipc_type_count = SOF_IPC_TYPE_COUNT - 1;
+ else
+ ipc_type_count = base_profile->ipc_type;
+
+ for (i = 0; i <= ipc_type_count; i++) {
+ if (!(desc->ipc_supported_mask & BIT(i)))
+ continue;
+
+ if (i == ipc_type)
+ marker = "Requested";
+ else
+ marker = "Fallback";
+
+ dev_info(dev, "- ipc type %d (%s):\n", i, marker);
+ if (base_profile->fw_path_postfix)
+ dev_info(dev, " Firmware file: %s/%s/%s\n",
+ desc->default_fw_path[i],
+ base_profile->fw_path_postfix,
+ desc->default_fw_filename[i]);
+ else
+ dev_info(dev, " Firmware file: %s/%s\n",
+ desc->default_fw_path[i],
+ desc->default_fw_filename[i]);
+
+ dev_info(dev, " Topology file: %s/%s\n",
+ desc->default_tplg_path[i],
+ plat_data->tplg_filename);
+ }
+
+ if (base_profile->fw_path || base_profile->fw_name ||
+ base_profile->tplg_path || base_profile->tplg_name)
+ dev_info(dev, "Verify the path/name override module parameters.\n");
+
+ dev_info(dev, "Check if you have 'sof-firmware' package installed.\n");
+ dev_info(dev, "Optionally it can be manually downloaded from:\n");
+ dev_info(dev, " https://github.com/thesofproject/sof-bin/\n");
+}
+
+static void sof_print_profile_info(struct snd_sof_dev *sdev,
+ enum sof_ipc_type ipc_type,
+ struct sof_loadable_file_profile *profile)
+{
+ struct device *dev = sdev->dev;
+
+ if (ipc_type != profile->ipc_type)
+ dev_info(dev,
+ "Using fallback IPC type %d (requested type was %d)\n",
+ profile->ipc_type, ipc_type);
+
+ dev_info(dev, "Firmware paths/files for ipc type %d:\n", profile->ipc_type);
+
+ /* The firmware path is only valid when generic loader is used */
+ if (sof_platform_uses_generic_loader(sdev))
+ dev_info(dev, " Firmware file: %s/%s\n",
+ profile->fw_path, profile->fw_name);
+
+ if (profile->fw_lib_path)
+ dev_info(dev, " Firmware lib path: %s\n", profile->fw_lib_path);
+ dev_info(dev, " Topology file: %s/%s\n", profile->tplg_path, profile->tplg_name);
+}
+
+int sof_create_ipc_file_profile(struct snd_sof_dev *sdev,
+ struct sof_loadable_file_profile *base_profile,
+ struct sof_loadable_file_profile *out_profile)
+{
+ const struct sof_dev_desc *desc = sdev->pdata->desc;
+ int ipc_fallback_start, ret, i;
+
+ memset(out_profile, 0, sizeof(*out_profile));
+
+ ret = sof_file_profile_for_ipc_type(sdev, base_profile->ipc_type, desc,
+ base_profile, out_profile);
+ if (!ret)
+ goto out;
+
+ /*
+ * No firmware file was found for the requested IPC type, as fallback
+ * if SND_SOC_SOF_ALLOW_FALLBACK_TO_NEWER_IPC_VERSION is selected, check
+ * all IPC versions in a backwards direction (from newer to older)
+ * if SND_SOC_SOF_ALLOW_FALLBACK_TO_NEWER_IPC_VERSION is not selected,
+ * check only older IPC versions than the selected/default version
+ */
+ if (IS_ENABLED(CONFIG_SND_SOC_SOF_ALLOW_FALLBACK_TO_NEWER_IPC_VERSION))
+ ipc_fallback_start = SOF_IPC_TYPE_COUNT - 1;
+ else
+ ipc_fallback_start = (int)base_profile->ipc_type - 1;
+
+ for (i = ipc_fallback_start; i >= 0 ; i--) {
+ if (i == base_profile->ipc_type ||
+ !(desc->ipc_supported_mask & BIT(i)))
+ continue;
+
+ ret = sof_file_profile_for_ipc_type(sdev, i, desc, base_profile,
+ out_profile);
+ if (!ret)
+ break;
+ }
+
+out:
+ if (ret)
+ sof_print_missing_firmware_info(sdev, base_profile->ipc_type,
+ base_profile);
+ else
+ sof_print_profile_info(sdev, base_profile->ipc_type, out_profile);
+
+ return ret;
+}
+EXPORT_SYMBOL(sof_create_ipc_file_profile);
diff --git a/sound/soc/sof/imx/Kconfig b/sound/soc/sof/imx/Kconfig
index 8230285baa43..4751b04d5e6f 100644
--- a/sound/soc/sof/imx/Kconfig
+++ b/sound/soc/sof/imx/Kconfig
@@ -11,41 +11,43 @@ config SND_SOC_SOF_IMX_TOPLEVEL
if SND_SOC_SOF_IMX_TOPLEVEL
-config SND_SOC_SOF_IMX_OF
- def_tristate SND_SOC_SOF_OF
- select SND_SOC_SOF_IMX8 if SND_SOC_SOF_IMX8_SUPPORT
- select SND_SOC_SOF_IMX8M if SND_SOC_SOF_IMX8M_SUPPORT
+config SND_SOC_SOF_IMX_COMMON
+ tristate
+ select SND_SOC_SOF_OF_DEV
+ select SND_SOC_SOF
+ select SND_SOC_SOF_IPC3
+ select SND_SOC_SOF_XTENSA
+ select SND_SOC_SOF_COMPRESS
help
This option is not user-selectable but automagically handled by
- 'select' statements at a higher level
+ 'select' statements at a higher level.
-config SND_SOC_SOF_IMX8_SUPPORT
- bool "SOF support for i.MX8"
- depends on IMX_SCU=y || IMX_SCU=SND_SOC_SOF_IMX_OF
- depends on IMX_DSP=y || IMX_DSP=SND_SOC_SOF_IMX_OF
+config SND_SOC_SOF_IMX8
+ tristate "SOF support for i.MX8"
+ depends on IMX_SCU
+ depends on IMX_DSP
+ select SND_SOC_SOF_IMX_COMMON
help
- This adds support for Sound Open Firmware for NXP i.MX8 platforms
+ This adds support for Sound Open Firmware for NXP i.MX8 platforms.
Say Y if you have such a device.
If unsure select "N".
-config SND_SOC_SOF_IMX8
- tristate
- help
- This option is not user-selectable but automagically handled by
- 'select' statements at a higher level
-
-config SND_SOC_SOF_IMX8M_SUPPORT
- bool "SOF support for i.MX8M"
- depends on IMX_DSP=y || IMX_DSP=SND_SOC_SOF_OF
+config SND_SOC_SOF_IMX8M
+ tristate "SOF support for i.MX8M"
+ depends on IMX_DSP
+ select SND_SOC_SOF_IMX_COMMON
help
- This adds support for Sound Open Firmware for NXP i.MX8M platforms
+ This adds support for Sound Open Firmware for NXP i.MX8M platforms.
Say Y if you have such a device.
If unsure select "N".
-config SND_SOC_SOF_IMX8M
- tristate
+config SND_SOC_SOF_IMX8ULP
+ tristate "SOF support for i.MX8ULP"
+ depends on IMX_DSP
+ select SND_SOC_SOF_IMX_COMMON
help
- This option is not user-selectable but automagically handled by
- 'select' statements at a higher level
+ This adds support for Sound Open Firmware for NXP i.MX8ULP platforms.
+ Say Y if you have such a device.
+ If unsure select "N".
-endif ## SND_SOC_SOF_IMX_IMX_TOPLEVEL
+endif ## SND_SOC_SOF_IMX_TOPLEVEL
diff --git a/sound/soc/sof/imx/Makefile b/sound/soc/sof/imx/Makefile
index 2b933b02bbac..798b43a415bf 100644
--- a/sound/soc/sof/imx/Makefile
+++ b/sound/soc/sof/imx/Makefile
@@ -1,6 +1,11 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
snd-sof-imx8-objs := imx8.o
snd-sof-imx8m-objs := imx8m.o
+snd-sof-imx8ulp-objs := imx8ulp.o
+
+snd-sof-imx-common-objs := imx-common.o
obj-$(CONFIG_SND_SOC_SOF_IMX8) += snd-sof-imx8.o
obj-$(CONFIG_SND_SOC_SOF_IMX8M) += snd-sof-imx8m.o
+obj-$(CONFIG_SND_SOC_SOF_IMX8ULP) += snd-sof-imx8ulp.o
+obj-$(CONFIG_SND_SOC_SOF_IMX_COMMON) += imx-common.o
diff --git a/sound/soc/sof/imx/imx-common.c b/sound/soc/sof/imx/imx-common.c
new file mode 100644
index 000000000000..36e3d414a18f
--- /dev/null
+++ b/sound/soc/sof/imx/imx-common.c
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// Copyright 2020 NXP
+//
+// Common helpers for the audio DSP on i.MX8
+
+#include <linux/module.h>
+#include <sound/sof/xtensa.h>
+#include "../ops.h"
+
+#include "imx-common.h"
+
+/**
+ * imx8_get_registers() - This function is called in case of DSP oops
+ * in order to gather information about the registers, filename and
+ * linenumber and stack.
+ * @sdev: SOF device
+ * @xoops: Stores information about registers.
+ * @panic_info: Stores information about filename and line number.
+ * @stack: Stores the stack dump.
+ * @stack_words: Size of the stack dump.
+ */
+void imx8_get_registers(struct snd_sof_dev *sdev,
+ struct sof_ipc_dsp_oops_xtensa *xoops,
+ struct sof_ipc_panic_info *panic_info,
+ u32 *stack, size_t stack_words)
+{
+ u32 offset = sdev->dsp_oops_offset;
+
+ /* first read registers */
+ sof_mailbox_read(sdev, offset, xoops, sizeof(*xoops));
+
+ /* then get panic info */
+ if (xoops->arch_hdr.totalsize > EXCEPT_MAX_HDR_SIZE) {
+ dev_err(sdev->dev, "invalid header size 0x%x. FW oops is bogus\n",
+ xoops->arch_hdr.totalsize);
+ return;
+ }
+ offset += xoops->arch_hdr.totalsize;
+ sof_mailbox_read(sdev, offset, panic_info, sizeof(*panic_info));
+
+ /* then get the stack */
+ offset += sizeof(*panic_info);
+ sof_mailbox_read(sdev, offset, stack, stack_words * sizeof(u32));
+}
+
+/**
+ * imx8_dump() - This function is called when a panic message is
+ * received from the firmware.
+ * @sdev: SOF device
+ * @flags: parameter not used but required by ops prototype
+ */
+void imx8_dump(struct snd_sof_dev *sdev, u32 flags)
+{
+ struct sof_ipc_dsp_oops_xtensa xoops;
+ struct sof_ipc_panic_info panic_info;
+ u32 stack[IMX8_STACK_DUMP_SIZE];
+ u32 status;
+
+ /* Get information about the panic status from the debug box area.
+ * Compute the trace point based on the status.
+ */
+ sof_mailbox_read(sdev, sdev->debug_box.offset + 0x4, &status, 4);
+
+ /* Get information about the registers, the filename and line
+ * number and the stack.
+ */
+ imx8_get_registers(sdev, &xoops, &panic_info, stack,
+ IMX8_STACK_DUMP_SIZE);
+
+ /* Print the information to the console */
+ sof_print_oops_and_stack(sdev, KERN_ERR, status, status, &xoops,
+ &panic_info, stack, IMX8_STACK_DUMP_SIZE);
+}
+EXPORT_SYMBOL(imx8_dump);
+
+int imx8_parse_clocks(struct snd_sof_dev *sdev, struct imx_clocks *clks)
+{
+ int ret;
+
+ ret = devm_clk_bulk_get(sdev->dev, clks->num_dsp_clks, clks->dsp_clks);
+ if (ret)
+ dev_err(sdev->dev, "Failed to request DSP clocks\n");
+
+ return ret;
+}
+EXPORT_SYMBOL(imx8_parse_clocks);
+
+int imx8_enable_clocks(struct snd_sof_dev *sdev, struct imx_clocks *clks)
+{
+ return clk_bulk_prepare_enable(clks->num_dsp_clks, clks->dsp_clks);
+}
+EXPORT_SYMBOL(imx8_enable_clocks);
+
+void imx8_disable_clocks(struct snd_sof_dev *sdev, struct imx_clocks *clks)
+{
+ clk_bulk_disable_unprepare(clks->num_dsp_clks, clks->dsp_clks);
+}
+EXPORT_SYMBOL(imx8_disable_clocks);
+
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/sof/imx/imx-common.h b/sound/soc/sof/imx/imx-common.h
new file mode 100644
index 000000000000..ec4b3a5c7496
--- /dev/null
+++ b/sound/soc/sof/imx/imx-common.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+
+#ifndef __IMX_COMMON_H__
+#define __IMX_COMMON_H__
+
+#include <linux/clk.h>
+
+#define EXCEPT_MAX_HDR_SIZE 0x400
+#define IMX8_STACK_DUMP_SIZE 32
+
+void imx8_get_registers(struct snd_sof_dev *sdev,
+ struct sof_ipc_dsp_oops_xtensa *xoops,
+ struct sof_ipc_panic_info *panic_info,
+ u32 *stack, size_t stack_words);
+
+void imx8_dump(struct snd_sof_dev *sdev, u32 flags);
+
+struct imx_clocks {
+ struct clk_bulk_data *dsp_clks;
+ int num_dsp_clks;
+};
+
+int imx8_parse_clocks(struct snd_sof_dev *sdev, struct imx_clocks *clks);
+int imx8_enable_clocks(struct snd_sof_dev *sdev, struct imx_clocks *clks);
+void imx8_disable_clocks(struct snd_sof_dev *sdev, struct imx_clocks *clks);
+
+#endif
diff --git a/sound/soc/sof/imx/imx8.c b/sound/soc/sof/imx/imx8.c
index bc0628c7b88c..07f51489d6c9 100644
--- a/sound/soc/sof/imx/imx8.c
+++ b/sound/soc/sof/imx/imx8.c
@@ -21,6 +21,8 @@
#include <linux/firmware/imx/svc/misc.h>
#include <dt-bindings/firmware/imx/rsrc.h>
#include "../ops.h"
+#include "../sof-of-dev.h"
+#include "imx-common.h"
/* DSP memories */
#define IRAM_OFFSET 0x10000
@@ -39,6 +41,13 @@
#define MBOX_OFFSET 0x800000
#define MBOX_SIZE 0x1000
+/* DSP clocks */
+static struct clk_bulk_data imx8_dsp_clks[] = {
+ { .id = "ipg" },
+ { .id = "ocram" },
+ { .id = "core" },
+};
+
struct imx8_priv {
struct device *dev;
struct snd_sof_dev *sdev;
@@ -55,42 +64,9 @@ struct imx8_priv {
struct device **pd_dev;
struct device_link **link;
+ struct imx_clocks *clks;
};
-static void imx8_get_reply(struct snd_sof_dev *sdev)
-{
- struct snd_sof_ipc_msg *msg = sdev->msg;
- struct sof_ipc_reply reply;
- int ret = 0;
-
- if (!msg) {
- dev_warn(sdev->dev, "unexpected ipc interrupt\n");
- return;
- }
-
- /* get reply */
- sof_mailbox_read(sdev, sdev->host_box.offset, &reply, sizeof(reply));
-
- if (reply.error < 0) {
- memcpy(msg->reply_data, &reply, sizeof(reply));
- ret = reply.error;
- } else {
- /* reply has correct size? */
- if (reply.hdr.size != msg->reply_size) {
- dev_err(sdev->dev, "error: reply expected %zu got %u bytes\n",
- msg->reply_size, reply.hdr.size);
- ret = -EINVAL;
- }
-
- /* read the message */
- if (msg->reply_size > 0)
- sof_mailbox_read(sdev, sdev->host_box.offset,
- msg->reply_data, msg->reply_size);
- }
-
- msg->reply_error = ret;
-}
-
static int imx8_get_mailbox_offset(struct snd_sof_dev *sdev)
{
return MBOX_OFFSET;
@@ -107,16 +83,23 @@ static void imx8_dsp_handle_reply(struct imx_dsp_ipc *ipc)
unsigned long flags;
spin_lock_irqsave(&priv->sdev->ipc_lock, flags);
- imx8_get_reply(priv->sdev);
- snd_sof_ipc_reply(priv->sdev, 0);
+ snd_sof_ipc_process_reply(priv->sdev, 0);
spin_unlock_irqrestore(&priv->sdev->ipc_lock, flags);
}
static void imx8_dsp_handle_request(struct imx_dsp_ipc *ipc)
{
struct imx8_priv *priv = imx_dsp_get_data(ipc);
+ u32 p; /* panic code */
+
+ /* Read the message from the debug box. */
+ sof_mailbox_read(priv->sdev, priv->sdev->debug_box.offset + 4, &p, sizeof(p));
- snd_sof_ipc_msgs_rx(priv->sdev);
+ /* Check to see if the message is a panic code (0x0dead***) */
+ if ((p & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC)
+ snd_sof_dsp_panic(priv->sdev, p, true);
+ else
+ snd_sof_ipc_msgs_rx(priv->sdev);
}
static struct imx_dsp_ops dsp_ops = {
@@ -126,7 +109,7 @@ static struct imx_dsp_ops dsp_ops = {
static int imx8_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
{
- struct imx8_priv *priv = (struct imx8_priv *)sdev->private;
+ struct imx8_priv *priv = sdev->pdata->hw_pdata;
sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data,
msg->msg_size);
@@ -140,7 +123,7 @@ static int imx8_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
*/
static int imx8x_run(struct snd_sof_dev *sdev)
{
- struct imx8_priv *dsp_priv = (struct imx8_priv *)sdev->private;
+ struct imx8_priv *dsp_priv = sdev->pdata->hw_pdata;
int ret;
ret = imx_sc_misc_set_control(dsp_priv->sc_ipc, IMX_SC_R_DSP,
@@ -180,7 +163,7 @@ static int imx8x_run(struct snd_sof_dev *sdev)
static int imx8_run(struct snd_sof_dev *sdev)
{
- struct imx8_priv *dsp_priv = (struct imx8_priv *)sdev->private;
+ struct imx8_priv *dsp_priv = sdev->pdata->hw_pdata;
int ret;
ret = imx_sc_misc_set_control(dsp_priv->sc_ipc, IMX_SC_R_DSP,
@@ -213,7 +196,12 @@ static int imx8_probe(struct snd_sof_dev *sdev)
if (!priv)
return -ENOMEM;
- sdev->private = priv;
+ priv->clks = devm_kzalloc(&pdev->dev, sizeof(*priv->clks), GFP_KERNEL);
+ if (!priv->clks)
+ return -ENOMEM;
+
+ sdev->num_cores = 1;
+ sdev->pdata->hw_pdata = priv;
priv->dev = sdev->dev;
priv->sdev = sdev;
@@ -306,6 +294,7 @@ static int imx8_probe(struct snd_sof_dev *sdev)
}
ret = of_address_to_resource(res_node, 0, &res);
+ of_node_put(res_node);
if (ret) {
dev_err(&pdev->dev, "failed to get reserved region address\n");
goto exit_pdev_unregister;
@@ -324,6 +313,18 @@ static int imx8_probe(struct snd_sof_dev *sdev)
/* set default mailbox offset for FW ready message */
sdev->dsp_box.offset = MBOX_OFFSET;
+ /* init clocks info */
+ priv->clks->dsp_clks = imx8_dsp_clks;
+ priv->clks->num_dsp_clks = ARRAY_SIZE(imx8_dsp_clks);
+
+ ret = imx8_parse_clocks(sdev, priv->clks);
+ if (ret < 0)
+ goto exit_pdev_unregister;
+
+ ret = imx8_enable_clocks(sdev, priv->clks);
+ if (ret < 0)
+ goto exit_pdev_unregister;
+
return 0;
exit_pdev_unregister:
@@ -337,41 +338,119 @@ exit_unroll_pm:
return ret;
}
-static int imx8_remove(struct snd_sof_dev *sdev)
+static void imx8_remove(struct snd_sof_dev *sdev)
{
- struct imx8_priv *priv = (struct imx8_priv *)sdev->private;
+ struct imx8_priv *priv = sdev->pdata->hw_pdata;
int i;
+ imx8_disable_clocks(sdev, priv->clks);
platform_device_unregister(priv->ipc_dev);
for (i = 0; i < priv->num_domains; i++) {
device_link_del(priv->link[i]);
dev_pm_domain_detach(priv->pd_dev[i], false);
}
-
- return 0;
}
/* on i.MX8 there is 1 to 1 match between type and BAR idx */
static int imx8_get_bar_index(struct snd_sof_dev *sdev, u32 type)
{
- return type;
+ /* Only IRAM and SRAM bars are valid */
+ switch (type) {
+ case SOF_FW_BLK_TYPE_IRAM:
+ case SOF_FW_BLK_TYPE_SRAM:
+ return type;
+ default:
+ return -EINVAL;
+ }
}
-static void imx8_ipc_msg_data(struct snd_sof_dev *sdev,
- struct snd_pcm_substream *substream,
- void *p, size_t sz)
+static void imx8_suspend(struct snd_sof_dev *sdev)
{
- sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz);
+ int i;
+ struct imx8_priv *priv = (struct imx8_priv *)sdev->pdata->hw_pdata;
+
+ for (i = 0; i < DSP_MU_CHAN_NUM; i++)
+ imx_dsp_free_channel(priv->dsp_ipc, i);
+
+ imx8_disable_clocks(sdev, priv->clks);
}
-static int imx8_ipc_pcm_params(struct snd_sof_dev *sdev,
- struct snd_pcm_substream *substream,
- const struct sof_ipc_pcm_params_reply *reply)
+static int imx8_resume(struct snd_sof_dev *sdev)
{
+ struct imx8_priv *priv = (struct imx8_priv *)sdev->pdata->hw_pdata;
+ int ret;
+ int i;
+
+ ret = imx8_enable_clocks(sdev, priv->clks);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < DSP_MU_CHAN_NUM; i++)
+ imx_dsp_request_channel(priv->dsp_ipc, i);
+
return 0;
}
+static int imx8_dsp_runtime_resume(struct snd_sof_dev *sdev)
+{
+ int ret;
+ const struct sof_dsp_power_state target_dsp_state = {
+ .state = SOF_DSP_PM_D0,
+ };
+
+ ret = imx8_resume(sdev);
+ if (ret < 0)
+ return ret;
+
+ return snd_sof_dsp_set_power_state(sdev, &target_dsp_state);
+}
+
+static int imx8_dsp_runtime_suspend(struct snd_sof_dev *sdev)
+{
+ const struct sof_dsp_power_state target_dsp_state = {
+ .state = SOF_DSP_PM_D3,
+ };
+
+ imx8_suspend(sdev);
+
+ return snd_sof_dsp_set_power_state(sdev, &target_dsp_state);
+}
+
+static int imx8_dsp_suspend(struct snd_sof_dev *sdev, unsigned int target_state)
+{
+ const struct sof_dsp_power_state target_dsp_state = {
+ .state = target_state,
+ };
+
+ if (!pm_runtime_suspended(sdev->dev))
+ imx8_suspend(sdev);
+
+ return snd_sof_dsp_set_power_state(sdev, &target_dsp_state);
+}
+
+static int imx8_dsp_resume(struct snd_sof_dev *sdev)
+{
+ int ret;
+ const struct sof_dsp_power_state target_dsp_state = {
+ .state = SOF_DSP_PM_D0,
+ };
+
+ ret = imx8_resume(sdev);
+ if (ret < 0)
+ return ret;
+
+ if (pm_runtime_suspended(sdev->dev)) {
+ pm_runtime_disable(sdev->dev);
+ pm_runtime_set_active(sdev->dev);
+ pm_runtime_mark_last_busy(sdev->dev);
+ pm_runtime_enable(sdev->dev);
+ pm_runtime_idle(sdev->dev);
+ }
+
+ return snd_sof_dsp_set_power_state(sdev, &target_dsp_state);
+}
+
static struct snd_soc_dai_driver imx8_dai[] = {
{
.name = "esai0",
@@ -397,8 +476,16 @@ static struct snd_soc_dai_driver imx8_dai[] = {
},
};
+static int imx8_dsp_set_power_state(struct snd_sof_dev *sdev,
+ const struct sof_dsp_power_state *target_state)
+{
+ sdev->dsp_power_state = *target_state;
+
+ return 0;
+}
+
/* i.MX8 ops */
-struct snd_sof_dsp_ops sof_imx8_ops = {
+static struct snd_sof_dsp_ops sof_imx8_ops = {
/* probe and remove */
.probe = imx8_probe,
.remove = imx8_remove,
@@ -409,21 +496,34 @@ struct snd_sof_dsp_ops sof_imx8_ops = {
.block_read = sof_block_read,
.block_write = sof_block_write,
+ /* Mailbox IO */
+ .mailbox_read = sof_mailbox_read,
+ .mailbox_write = sof_mailbox_write,
+
/* ipc */
.send_msg = imx8_send_msg,
- .fw_ready = sof_fw_ready,
.get_mailbox_offset = imx8_get_mailbox_offset,
.get_window_offset = imx8_get_window_offset,
- .ipc_msg_data = imx8_ipc_msg_data,
- .ipc_pcm_params = imx8_ipc_pcm_params,
+ .ipc_msg_data = sof_ipc_msg_data,
+ .set_stream_data_offset = sof_set_stream_data_offset,
- /* module loading */
- .load_module = snd_sof_parse_module_memcpy,
.get_bar_index = imx8_get_bar_index,
+
/* firmware loading */
.load_firmware = snd_sof_load_firmware_memcpy,
+ /* Debug information */
+ .dbg_dump = imx8_dump,
+ .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem,
+
+ /* stream callbacks */
+ .pcm_open = sof_stream_pcm_open,
+ .pcm_close = sof_stream_pcm_close,
+
+ /* Firmware ops */
+ .dsp_arch_ops = &sof_xtensa_arch_ops,
+
/* DAI drivers */
.drv = imx8_dai,
.num_drv = ARRAY_SIZE(imx8_dai),
@@ -434,11 +534,19 @@ struct snd_sof_dsp_ops sof_imx8_ops = {
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
+
+ /* PM */
+ .runtime_suspend = imx8_dsp_runtime_suspend,
+ .runtime_resume = imx8_dsp_runtime_resume,
+
+ .suspend = imx8_dsp_suspend,
+ .resume = imx8_dsp_resume,
+
+ .set_power_state = imx8_dsp_set_power_state,
};
-EXPORT_SYMBOL(sof_imx8_ops);
/* i.MX8X ops */
-struct snd_sof_dsp_ops sof_imx8x_ops = {
+static struct snd_sof_dsp_ops sof_imx8x_ops = {
/* probe and remove */
.probe = imx8_probe,
.remove = imx8_remove,
@@ -449,32 +557,122 @@ struct snd_sof_dsp_ops sof_imx8x_ops = {
.block_read = sof_block_read,
.block_write = sof_block_write,
+ /* Mailbox IO */
+ .mailbox_read = sof_mailbox_read,
+ .mailbox_write = sof_mailbox_write,
+
/* ipc */
.send_msg = imx8_send_msg,
- .fw_ready = sof_fw_ready,
.get_mailbox_offset = imx8_get_mailbox_offset,
.get_window_offset = imx8_get_window_offset,
- .ipc_msg_data = imx8_ipc_msg_data,
- .ipc_pcm_params = imx8_ipc_pcm_params,
+ .ipc_msg_data = sof_ipc_msg_data,
+ .set_stream_data_offset = sof_set_stream_data_offset,
- /* module loading */
- .load_module = snd_sof_parse_module_memcpy,
.get_bar_index = imx8_get_bar_index,
+
/* firmware loading */
.load_firmware = snd_sof_load_firmware_memcpy,
+ /* Debug information */
+ .dbg_dump = imx8_dump,
+ .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem,
+
+ /* stream callbacks */
+ .pcm_open = sof_stream_pcm_open,
+ .pcm_close = sof_stream_pcm_close,
+
+ /* Firmware ops */
+ .dsp_arch_ops = &sof_xtensa_arch_ops,
+
/* DAI drivers */
.drv = imx8_dai,
.num_drv = ARRAY_SIZE(imx8_dai),
+ /* PM */
+ .runtime_suspend = imx8_dsp_runtime_suspend,
+ .runtime_resume = imx8_dsp_runtime_resume,
+
+ .suspend = imx8_dsp_suspend,
+ .resume = imx8_dsp_resume,
+
+ .set_power_state = imx8_dsp_set_power_state,
+
/* ALSA HW info flags */
.hw_info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_BATCH |
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP
};
-EXPORT_SYMBOL(sof_imx8x_ops);
+static struct snd_sof_of_mach sof_imx8_machs[] = {
+ {
+ .compatible = "fsl,imx8qxp-mek",
+ .sof_tplg_filename = "sof-imx8-wm8960.tplg",
+ .drv_name = "asoc-audio-graph-card2",
+ },
+ {
+ .compatible = "fsl,imx8qm-mek",
+ .sof_tplg_filename = "sof-imx8-wm8960.tplg",
+ .drv_name = "asoc-audio-graph-card2",
+ },
+ {}
+};
+
+static struct sof_dev_desc sof_of_imx8qxp_desc = {
+ .of_machines = sof_imx8_machs,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "imx/sof",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "imx/sof-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-imx8x.ri",
+ },
+ .nocodec_tplg_filename = "sof-imx8-nocodec.tplg",
+ .ops = &sof_imx8x_ops,
+};
+
+static struct sof_dev_desc sof_of_imx8qm_desc = {
+ .of_machines = sof_imx8_machs,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "imx/sof",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "imx/sof-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-imx8.ri",
+ },
+ .nocodec_tplg_filename = "sof-imx8-nocodec.tplg",
+ .ops = &sof_imx8_ops,
+};
+
+static const struct of_device_id sof_of_imx8_ids[] = {
+ { .compatible = "fsl,imx8qxp-dsp", .data = &sof_of_imx8qxp_desc},
+ { .compatible = "fsl,imx8qm-dsp", .data = &sof_of_imx8qm_desc},
+ { }
+};
+MODULE_DEVICE_TABLE(of, sof_of_imx8_ids);
+
+/* DT driver definition */
+static struct platform_driver snd_sof_of_imx8_driver = {
+ .probe = sof_of_probe,
+ .remove_new = sof_of_remove,
+ .driver = {
+ .name = "sof-audio-of-imx8",
+ .pm = &sof_of_pm,
+ .of_match_table = sof_of_imx8_ids,
+ },
+};
+module_platform_driver(snd_sof_of_imx8_driver);
+
+MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA);
MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/sof/imx/imx8m.c b/sound/soc/sof/imx/imx8m.c
index 86320941fcee..222cd1467da6 100644
--- a/sound/soc/sof/imx/imx8m.c
+++ b/sound/soc/sof/imx/imx8m.c
@@ -6,10 +6,13 @@
//
// Hardware interface for audio DSP on i.MX8M
+#include <linux/bits.h>
#include <linux/firmware.h>
+#include <linux/mfd/syscon.h>
#include <linux/of_platform.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
+#include <linux/regmap.h>
#include <linux/module.h>
#include <sound/sof.h>
@@ -17,10 +20,32 @@
#include <linux/firmware/imx/dsp.h>
#include "../ops.h"
+#include "../sof-of-dev.h"
+#include "imx-common.h"
#define MBOX_OFFSET 0x800000
#define MBOX_SIZE 0x1000
+static struct clk_bulk_data imx8m_dsp_clks[] = {
+ { .id = "ipg" },
+ { .id = "ocram" },
+ { .id = "core" },
+};
+
+/* DAP registers */
+#define IMX8M_DAP_DEBUG 0x28800000
+#define IMX8M_DAP_DEBUG_SIZE (64 * 1024)
+#define IMX8M_DAP_PWRCTL (0x4000 + 0x3020)
+#define IMX8M_PWRCTL_CORERESET BIT(16)
+
+/* DSP audio mix registers */
+#define AudioDSP_REG0 0x100
+#define AudioDSP_REG1 0x104
+#define AudioDSP_REG2 0x108
+#define AudioDSP_REG3 0x10c
+
+#define AudioDSP_REG2_RUNSTALL BIT(5)
+
struct imx8m_priv {
struct device *dev;
struct snd_sof_dev *sdev;
@@ -28,41 +53,12 @@ struct imx8m_priv {
/* DSP IPC handler */
struct imx_dsp_ipc *dsp_ipc;
struct platform_device *ipc_dev;
-};
-
-static void imx8m_get_reply(struct snd_sof_dev *sdev)
-{
- struct snd_sof_ipc_msg *msg = sdev->msg;
- struct sof_ipc_reply reply;
- int ret = 0;
-
- if (!msg) {
- dev_warn(sdev->dev, "unexpected ipc interrupt\n");
- return;
- }
-
- /* get reply */
- sof_mailbox_read(sdev, sdev->host_box.offset, &reply, sizeof(reply));
- if (reply.error < 0) {
- memcpy(msg->reply_data, &reply, sizeof(reply));
- ret = reply.error;
- } else {
- /* reply has correct size? */
- if (reply.hdr.size != msg->reply_size) {
- dev_err(sdev->dev, "error: reply expected %zu got %u bytes\n",
- msg->reply_size, reply.hdr.size);
- ret = -EINVAL;
- }
-
- /* read the message */
- if (msg->reply_size > 0)
- sof_mailbox_read(sdev, sdev->host_box.offset,
- msg->reply_data, msg->reply_size);
- }
+ struct imx_clocks *clks;
- msg->reply_error = ret;
-}
+ void __iomem *dap;
+ struct regmap *regmap;
+};
static int imx8m_get_mailbox_offset(struct snd_sof_dev *sdev)
{
@@ -80,16 +76,23 @@ static void imx8m_dsp_handle_reply(struct imx_dsp_ipc *ipc)
unsigned long flags;
spin_lock_irqsave(&priv->sdev->ipc_lock, flags);
- imx8m_get_reply(priv->sdev);
- snd_sof_ipc_reply(priv->sdev, 0);
+ snd_sof_ipc_process_reply(priv->sdev, 0);
spin_unlock_irqrestore(&priv->sdev->ipc_lock, flags);
}
static void imx8m_dsp_handle_request(struct imx_dsp_ipc *ipc)
{
struct imx8m_priv *priv = imx_dsp_get_data(ipc);
+ u32 p; /* Panic code */
- snd_sof_ipc_msgs_rx(priv->sdev);
+ /* Read the message from the debug box. */
+ sof_mailbox_read(priv->sdev, priv->sdev->debug_box.offset + 4, &p, sizeof(p));
+
+ /* Check to see if the message is a panic code (0x0dead***) */
+ if ((p & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC)
+ snd_sof_dsp_panic(priv->sdev, p, true);
+ else
+ snd_sof_ipc_msgs_rx(priv->sdev);
}
static struct imx_dsp_ops imx8m_dsp_ops = {
@@ -99,7 +102,7 @@ static struct imx_dsp_ops imx8m_dsp_ops = {
static int imx8m_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
{
- struct imx8m_priv *priv = (struct imx8m_priv *)sdev->private;
+ struct imx8m_priv *priv = sdev->pdata->hw_pdata;
sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data,
msg->msg_size);
@@ -113,7 +116,34 @@ static int imx8m_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
*/
static int imx8m_run(struct snd_sof_dev *sdev)
{
- /* TODO: start DSP using Audio MIX bits */
+ struct imx8m_priv *priv = (struct imx8m_priv *)sdev->pdata->hw_pdata;
+
+ regmap_update_bits(priv->regmap, AudioDSP_REG2, AudioDSP_REG2_RUNSTALL, 0);
+
+ return 0;
+}
+
+static int imx8m_reset(struct snd_sof_dev *sdev)
+{
+ struct imx8m_priv *priv = (struct imx8m_priv *)sdev->pdata->hw_pdata;
+ u32 pwrctl;
+
+ /* put DSP into reset and stall */
+ pwrctl = readl(priv->dap + IMX8M_DAP_PWRCTL);
+ pwrctl |= IMX8M_PWRCTL_CORERESET;
+ writel(pwrctl, priv->dap + IMX8M_DAP_PWRCTL);
+
+ /* keep reset asserted for 10 cycles */
+ usleep_range(1, 2);
+
+ regmap_update_bits(priv->regmap, AudioDSP_REG2,
+ AudioDSP_REG2_RUNSTALL, AudioDSP_REG2_RUNSTALL);
+
+ /* take the DSP out of reset and keep stalled for FW loading */
+ pwrctl = readl(priv->dap + IMX8M_DAP_PWRCTL);
+ pwrctl &= ~IMX8M_PWRCTL_CORERESET;
+ writel(pwrctl, priv->dap + IMX8M_DAP_PWRCTL);
+
return 0;
}
@@ -133,7 +163,12 @@ static int imx8m_probe(struct snd_sof_dev *sdev)
if (!priv)
return -ENOMEM;
- sdev->private = priv;
+ priv->clks = devm_kzalloc(&pdev->dev, sizeof(*priv->clks), GFP_KERNEL);
+ if (!priv->clks)
+ return -ENOMEM;
+
+ sdev->num_cores = 1;
+ sdev->pdata->hw_pdata = priv;
priv->dev = sdev->dev;
priv->sdev = sdev;
@@ -165,6 +200,13 @@ static int imx8m_probe(struct snd_sof_dev *sdev)
goto exit_pdev_unregister;
}
+ priv->dap = devm_ioremap(sdev->dev, IMX8M_DAP_DEBUG, IMX8M_DAP_DEBUG_SIZE);
+ if (!priv->dap) {
+ dev_err(sdev->dev, "error: failed to map DAP debug memory area");
+ ret = -ENODEV;
+ goto exit_pdev_unregister;
+ }
+
sdev->bar[SOF_FW_BLK_TYPE_IRAM] = devm_ioremap(sdev->dev, base, size);
if (!sdev->bar[SOF_FW_BLK_TYPE_IRAM]) {
dev_err(sdev->dev, "failed to ioremap base 0x%x size 0x%x\n",
@@ -182,6 +224,7 @@ static int imx8m_probe(struct snd_sof_dev *sdev)
}
ret = of_address_to_resource(res_node, 0, &res);
+ of_node_put(res_node);
if (ret) {
dev_err(&pdev->dev, "failed to get reserved region address\n");
goto exit_pdev_unregister;
@@ -200,6 +243,25 @@ static int imx8m_probe(struct snd_sof_dev *sdev)
/* set default mailbox offset for FW ready message */
sdev->dsp_box.offset = MBOX_OFFSET;
+ priv->regmap = syscon_regmap_lookup_by_compatible("fsl,dsp-ctrl");
+ if (IS_ERR(priv->regmap)) {
+ dev_err(sdev->dev, "cannot find dsp-ctrl registers");
+ ret = PTR_ERR(priv->regmap);
+ goto exit_pdev_unregister;
+ }
+
+ /* init clocks info */
+ priv->clks->dsp_clks = imx8m_dsp_clks;
+ priv->clks->num_dsp_clks = ARRAY_SIZE(imx8m_dsp_clks);
+
+ ret = imx8_parse_clocks(sdev, priv->clks);
+ if (ret < 0)
+ goto exit_pdev_unregister;
+
+ ret = imx8_enable_clocks(sdev, priv->clks);
+ if (ret < 0)
+ goto exit_pdev_unregister;
+
return 0;
exit_pdev_unregister:
@@ -207,37 +269,40 @@ exit_pdev_unregister:
return ret;
}
-static int imx8m_remove(struct snd_sof_dev *sdev)
+static void imx8m_remove(struct snd_sof_dev *sdev)
{
- struct imx8m_priv *priv = (struct imx8m_priv *)sdev->private;
+ struct imx8m_priv *priv = sdev->pdata->hw_pdata;
+ imx8_disable_clocks(sdev, priv->clks);
platform_device_unregister(priv->ipc_dev);
-
- return 0;
}
/* on i.MX8 there is 1 to 1 match between type and BAR idx */
static int imx8m_get_bar_index(struct snd_sof_dev *sdev, u32 type)
{
- return type;
-}
-
-static void imx8m_ipc_msg_data(struct snd_sof_dev *sdev,
- struct snd_pcm_substream *substream,
- void *p, size_t sz)
-{
- sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz);
-}
-
-static int imx8m_ipc_pcm_params(struct snd_sof_dev *sdev,
- struct snd_pcm_substream *substream,
- const struct sof_ipc_pcm_params_reply *reply)
-{
- return 0;
+ /* Only IRAM and SRAM bars are valid */
+ switch (type) {
+ case SOF_FW_BLK_TYPE_IRAM:
+ case SOF_FW_BLK_TYPE_SRAM:
+ return type;
+ default:
+ return -EINVAL;
+ }
}
static struct snd_soc_dai_driver imx8m_dai[] = {
{
+ .name = "sai1",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 32,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 32,
+ },
+},
+{
.name = "sai3",
.playback = {
.channels_min = 1,
@@ -248,45 +313,212 @@ static struct snd_soc_dai_driver imx8m_dai[] = {
.channels_max = 32,
},
},
+{
+ .name = "micfil",
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+},
};
+static int imx8m_dsp_set_power_state(struct snd_sof_dev *sdev,
+ const struct sof_dsp_power_state *target_state)
+{
+ sdev->dsp_power_state = *target_state;
+
+ return 0;
+}
+
+static int imx8m_resume(struct snd_sof_dev *sdev)
+{
+ struct imx8m_priv *priv = (struct imx8m_priv *)sdev->pdata->hw_pdata;
+ int ret;
+ int i;
+
+ ret = imx8_enable_clocks(sdev, priv->clks);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < DSP_MU_CHAN_NUM; i++)
+ imx_dsp_request_channel(priv->dsp_ipc, i);
+
+ return 0;
+}
+
+static void imx8m_suspend(struct snd_sof_dev *sdev)
+{
+ struct imx8m_priv *priv = (struct imx8m_priv *)sdev->pdata->hw_pdata;
+ int i;
+
+ for (i = 0; i < DSP_MU_CHAN_NUM; i++)
+ imx_dsp_free_channel(priv->dsp_ipc, i);
+
+ imx8_disable_clocks(sdev, priv->clks);
+}
+
+static int imx8m_dsp_runtime_resume(struct snd_sof_dev *sdev)
+{
+ int ret;
+ const struct sof_dsp_power_state target_dsp_state = {
+ .state = SOF_DSP_PM_D0,
+ };
+
+ ret = imx8m_resume(sdev);
+ if (ret < 0)
+ return ret;
+
+ return snd_sof_dsp_set_power_state(sdev, &target_dsp_state);
+}
+
+static int imx8m_dsp_runtime_suspend(struct snd_sof_dev *sdev)
+{
+ const struct sof_dsp_power_state target_dsp_state = {
+ .state = SOF_DSP_PM_D3,
+ };
+
+ imx8m_suspend(sdev);
+
+ return snd_sof_dsp_set_power_state(sdev, &target_dsp_state);
+}
+
+static int imx8m_dsp_resume(struct snd_sof_dev *sdev)
+{
+ int ret;
+ const struct sof_dsp_power_state target_dsp_state = {
+ .state = SOF_DSP_PM_D0,
+ };
+
+ ret = imx8m_resume(sdev);
+ if (ret < 0)
+ return ret;
+
+ if (pm_runtime_suspended(sdev->dev)) {
+ pm_runtime_disable(sdev->dev);
+ pm_runtime_set_active(sdev->dev);
+ pm_runtime_mark_last_busy(sdev->dev);
+ pm_runtime_enable(sdev->dev);
+ pm_runtime_idle(sdev->dev);
+ }
+
+ return snd_sof_dsp_set_power_state(sdev, &target_dsp_state);
+}
+
+static int imx8m_dsp_suspend(struct snd_sof_dev *sdev, unsigned int target_state)
+{
+ const struct sof_dsp_power_state target_dsp_state = {
+ .state = target_state,
+ };
+
+ if (!pm_runtime_suspended(sdev->dev))
+ imx8m_suspend(sdev);
+
+ return snd_sof_dsp_set_power_state(sdev, &target_dsp_state);
+}
+
/* i.MX8 ops */
-struct snd_sof_dsp_ops sof_imx8m_ops = {
+static struct snd_sof_dsp_ops sof_imx8m_ops = {
/* probe and remove */
.probe = imx8m_probe,
.remove = imx8m_remove,
/* DSP core boot */
.run = imx8m_run,
+ .reset = imx8m_reset,
/* Block IO */
.block_read = sof_block_read,
.block_write = sof_block_write,
+ /* Mailbox IO */
+ .mailbox_read = sof_mailbox_read,
+ .mailbox_write = sof_mailbox_write,
+
/* ipc */
.send_msg = imx8m_send_msg,
- .fw_ready = sof_fw_ready,
.get_mailbox_offset = imx8m_get_mailbox_offset,
.get_window_offset = imx8m_get_window_offset,
- .ipc_msg_data = imx8m_ipc_msg_data,
- .ipc_pcm_params = imx8m_ipc_pcm_params,
+ .ipc_msg_data = sof_ipc_msg_data,
+ .set_stream_data_offset = sof_set_stream_data_offset,
- /* module loading */
- .load_module = snd_sof_parse_module_memcpy,
.get_bar_index = imx8m_get_bar_index,
+
/* firmware loading */
.load_firmware = snd_sof_load_firmware_memcpy,
+ /* Debug information */
+ .dbg_dump = imx8_dump,
+ .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem,
+
+ /* stream callbacks */
+ .pcm_open = sof_stream_pcm_open,
+ .pcm_close = sof_stream_pcm_close,
+ /* Firmware ops */
+ .dsp_arch_ops = &sof_xtensa_arch_ops,
+
/* DAI drivers */
.drv = imx8m_dai,
.num_drv = ARRAY_SIZE(imx8m_dai),
+ .suspend = imx8m_dsp_suspend,
+ .resume = imx8m_dsp_resume,
+
+ .runtime_suspend = imx8m_dsp_runtime_suspend,
+ .runtime_resume = imx8m_dsp_runtime_resume,
+
+ .set_power_state = imx8m_dsp_set_power_state,
+
.hw_info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_BATCH |
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
};
-EXPORT_SYMBOL(sof_imx8m_ops);
+static struct snd_sof_of_mach sof_imx8mp_machs[] = {
+ {
+ .compatible = "fsl,imx8mp-evk",
+ .sof_tplg_filename = "sof-imx8mp-wm8960.tplg",
+ .drv_name = "asoc-audio-graph-card2",
+ },
+ {}
+};
+
+static struct sof_dev_desc sof_of_imx8mp_desc = {
+ .of_machines = sof_imx8mp_machs,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "imx/sof",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "imx/sof-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-imx8m.ri",
+ },
+ .nocodec_tplg_filename = "sof-imx8-nocodec.tplg",
+ .ops = &sof_imx8m_ops,
+};
+
+static const struct of_device_id sof_of_imx8m_ids[] = {
+ { .compatible = "fsl,imx8mp-dsp", .data = &sof_of_imx8mp_desc},
+ { }
+};
+MODULE_DEVICE_TABLE(of, sof_of_imx8m_ids);
+
+/* DT driver definition */
+static struct platform_driver snd_sof_of_imx8m_driver = {
+ .probe = sof_of_probe,
+ .remove_new = sof_of_remove,
+ .driver = {
+ .name = "sof-audio-of-imx8m",
+ .pm = &sof_of_pm,
+ .of_match_table = sof_of_imx8m_ids,
+ },
+};
+module_platform_driver(snd_sof_of_imx8m_driver);
+
+MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA);
MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/sof/imx/imx8ulp.c b/sound/soc/sof/imx/imx8ulp.c
new file mode 100644
index 000000000000..7b527ffde488
--- /dev/null
+++ b/sound/soc/sof/imx/imx8ulp.c
@@ -0,0 +1,524 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// Copyright 2021-2022 NXP
+//
+// Author: Peng Zhang <peng.zhang_8@nxp.com>
+//
+// Hardware interface for audio DSP on i.MX8ULP
+
+#include <linux/arm-smccc.h>
+#include <linux/clk.h>
+#include <linux/firmware.h>
+#include <linux/firmware/imx/dsp.h>
+#include <linux/firmware/imx/ipc.h>
+#include <linux/firmware/imx/svc/misc.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/of_reserved_mem.h>
+
+#include <sound/sof.h>
+#include <sound/sof/xtensa.h>
+
+#include "../ops.h"
+#include "../sof-of-dev.h"
+#include "imx-common.h"
+
+#define FSL_SIP_HIFI_XRDC 0xc200000e
+
+/* SIM Domain register */
+#define SYSCTRL0 0x8
+#define EXECUTE_BIT BIT(13)
+#define RESET_BIT BIT(16)
+#define HIFI4_CLK_BIT BIT(17)
+#define PB_CLK_BIT BIT(18)
+#define PLAT_CLK_BIT BIT(19)
+#define DEBUG_LOGIC_BIT BIT(25)
+
+#define MBOX_OFFSET 0x800000
+#define MBOX_SIZE 0x1000
+
+static struct clk_bulk_data imx8ulp_dsp_clks[] = {
+ { .id = "core" },
+ { .id = "ipg" },
+ { .id = "ocram" },
+ { .id = "mu" },
+};
+
+struct imx8ulp_priv {
+ struct device *dev;
+ struct snd_sof_dev *sdev;
+
+ /* DSP IPC handler */
+ struct imx_dsp_ipc *dsp_ipc;
+ struct platform_device *ipc_dev;
+
+ struct regmap *regmap;
+ struct imx_clocks *clks;
+};
+
+static void imx8ulp_sim_lpav_start(struct imx8ulp_priv *priv)
+{
+ /* Controls the HiFi4 DSP Reset: 1 in reset, 0 out of reset */
+ regmap_update_bits(priv->regmap, SYSCTRL0, RESET_BIT, 0);
+
+ /* Reset HiFi4 DSP Debug logic: 1 debug reset, 0 out of reset*/
+ regmap_update_bits(priv->regmap, SYSCTRL0, DEBUG_LOGIC_BIT, 0);
+
+ /* Stall HIFI4 DSP Execution: 1 stall, 0 run */
+ regmap_update_bits(priv->regmap, SYSCTRL0, EXECUTE_BIT, 0);
+}
+
+static int imx8ulp_get_mailbox_offset(struct snd_sof_dev *sdev)
+{
+ return MBOX_OFFSET;
+}
+
+static int imx8ulp_get_window_offset(struct snd_sof_dev *sdev, u32 id)
+{
+ return MBOX_OFFSET;
+}
+
+static void imx8ulp_dsp_handle_reply(struct imx_dsp_ipc *ipc)
+{
+ struct imx8ulp_priv *priv = imx_dsp_get_data(ipc);
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->sdev->ipc_lock, flags);
+
+ snd_sof_ipc_process_reply(priv->sdev, 0);
+
+ spin_unlock_irqrestore(&priv->sdev->ipc_lock, flags);
+}
+
+static void imx8ulp_dsp_handle_request(struct imx_dsp_ipc *ipc)
+{
+ struct imx8ulp_priv *priv = imx_dsp_get_data(ipc);
+ u32 p; /* panic code */
+
+ /* Read the message from the debug box. */
+ sof_mailbox_read(priv->sdev, priv->sdev->debug_box.offset + 4, &p, sizeof(p));
+
+ /* Check to see if the message is a panic code (0x0dead***) */
+ if ((p & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC)
+ snd_sof_dsp_panic(priv->sdev, p, true);
+ else
+ snd_sof_ipc_msgs_rx(priv->sdev);
+}
+
+static struct imx_dsp_ops dsp_ops = {
+ .handle_reply = imx8ulp_dsp_handle_reply,
+ .handle_request = imx8ulp_dsp_handle_request,
+};
+
+static int imx8ulp_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
+{
+ struct imx8ulp_priv *priv = sdev->pdata->hw_pdata;
+
+ sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data,
+ msg->msg_size);
+ imx_dsp_ring_doorbell(priv->dsp_ipc, 0);
+
+ return 0;
+}
+
+static int imx8ulp_run(struct snd_sof_dev *sdev)
+{
+ struct imx8ulp_priv *priv = sdev->pdata->hw_pdata;
+
+ imx8ulp_sim_lpav_start(priv);
+
+ return 0;
+}
+
+static int imx8ulp_reset(struct snd_sof_dev *sdev)
+{
+ struct imx8ulp_priv *priv = sdev->pdata->hw_pdata;
+ struct arm_smccc_res smc_resource;
+
+ /* HiFi4 Platform Clock Enable: 1 enabled, 0 disabled */
+ regmap_update_bits(priv->regmap, SYSCTRL0, PLAT_CLK_BIT, PLAT_CLK_BIT);
+
+ /* HiFi4 PBCLK clock enable: 1 enabled, 0 disabled */
+ regmap_update_bits(priv->regmap, SYSCTRL0, PB_CLK_BIT, PB_CLK_BIT);
+
+ /* HiFi4 Clock Enable: 1 enabled, 0 disabled */
+ regmap_update_bits(priv->regmap, SYSCTRL0, HIFI4_CLK_BIT, HIFI4_CLK_BIT);
+
+ regmap_update_bits(priv->regmap, SYSCTRL0, RESET_BIT, RESET_BIT);
+ usleep_range(1, 2);
+
+ /* Stall HIFI4 DSP Execution: 1 stall, 0 not stall */
+ regmap_update_bits(priv->regmap, SYSCTRL0, EXECUTE_BIT, EXECUTE_BIT);
+ usleep_range(1, 2);
+
+ arm_smccc_smc(FSL_SIP_HIFI_XRDC, 0, 0, 0, 0, 0, 0, 0, &smc_resource);
+
+ return 0;
+}
+
+static int imx8ulp_probe(struct snd_sof_dev *sdev)
+{
+ struct platform_device *pdev =
+ container_of(sdev->dev, struct platform_device, dev);
+ struct device_node *np = pdev->dev.of_node;
+ struct device_node *res_node;
+ struct resource *mmio;
+ struct imx8ulp_priv *priv;
+ struct resource res;
+ u32 base, size;
+ int ret = 0;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->clks = devm_kzalloc(&pdev->dev, sizeof(*priv->clks), GFP_KERNEL);
+ if (!priv->clks)
+ return -ENOMEM;
+
+ sdev->num_cores = 1;
+ sdev->pdata->hw_pdata = priv;
+ priv->dev = sdev->dev;
+ priv->sdev = sdev;
+
+ /* System integration module(SIM) control dsp configuration */
+ priv->regmap = syscon_regmap_lookup_by_phandle(np, "fsl,dsp-ctrl");
+ if (IS_ERR(priv->regmap))
+ return PTR_ERR(priv->regmap);
+
+ priv->ipc_dev = platform_device_register_data(sdev->dev, "imx-dsp",
+ PLATFORM_DEVID_NONE,
+ pdev, sizeof(*pdev));
+ if (IS_ERR(priv->ipc_dev))
+ return PTR_ERR(priv->ipc_dev);
+
+ priv->dsp_ipc = dev_get_drvdata(&priv->ipc_dev->dev);
+ if (!priv->dsp_ipc) {
+ /* DSP IPC driver not probed yet, try later */
+ ret = -EPROBE_DEFER;
+ dev_err(sdev->dev, "Failed to get drvdata\n");
+ goto exit_pdev_unregister;
+ }
+
+ imx_dsp_set_data(priv->dsp_ipc, priv);
+ priv->dsp_ipc->ops = &dsp_ops;
+
+ /* DSP base */
+ mmio = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (mmio) {
+ base = mmio->start;
+ size = resource_size(mmio);
+ } else {
+ dev_err(sdev->dev, "error: failed to get DSP base at idx 0\n");
+ ret = -EINVAL;
+ goto exit_pdev_unregister;
+ }
+
+ sdev->bar[SOF_FW_BLK_TYPE_IRAM] = devm_ioremap(sdev->dev, base, size);
+ if (!sdev->bar[SOF_FW_BLK_TYPE_IRAM]) {
+ dev_err(sdev->dev, "failed to ioremap base 0x%x size 0x%x\n",
+ base, size);
+ ret = -ENODEV;
+ goto exit_pdev_unregister;
+ }
+ sdev->mmio_bar = SOF_FW_BLK_TYPE_IRAM;
+
+ res_node = of_parse_phandle(np, "memory-reserved", 0);
+ if (!res_node) {
+ dev_err(&pdev->dev, "failed to get memory region node\n");
+ ret = -ENODEV;
+ goto exit_pdev_unregister;
+ }
+
+ ret = of_address_to_resource(res_node, 0, &res);
+ of_node_put(res_node);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get reserved region address\n");
+ goto exit_pdev_unregister;
+ }
+
+ sdev->bar[SOF_FW_BLK_TYPE_SRAM] = devm_ioremap_wc(sdev->dev, res.start,
+ resource_size(&res));
+ if (!sdev->bar[SOF_FW_BLK_TYPE_SRAM]) {
+ dev_err(sdev->dev, "failed to ioremap mem 0x%x size 0x%x\n",
+ base, size);
+ ret = -ENOMEM;
+ goto exit_pdev_unregister;
+ }
+ sdev->mailbox_bar = SOF_FW_BLK_TYPE_SRAM;
+
+ /* set default mailbox offset for FW ready message */
+ sdev->dsp_box.offset = MBOX_OFFSET;
+
+ ret = of_reserved_mem_device_init(sdev->dev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to init reserved memory region %d\n", ret);
+ goto exit_pdev_unregister;
+ }
+
+ priv->clks->dsp_clks = imx8ulp_dsp_clks;
+ priv->clks->num_dsp_clks = ARRAY_SIZE(imx8ulp_dsp_clks);
+
+ ret = imx8_parse_clocks(sdev, priv->clks);
+ if (ret < 0)
+ goto exit_pdev_unregister;
+
+ ret = imx8_enable_clocks(sdev, priv->clks);
+ if (ret < 0)
+ goto exit_pdev_unregister;
+
+ return 0;
+
+exit_pdev_unregister:
+ platform_device_unregister(priv->ipc_dev);
+
+ return ret;
+}
+
+static void imx8ulp_remove(struct snd_sof_dev *sdev)
+{
+ struct imx8ulp_priv *priv = sdev->pdata->hw_pdata;
+
+ imx8_disable_clocks(sdev, priv->clks);
+ platform_device_unregister(priv->ipc_dev);
+}
+
+/* on i.MX8 there is 1 to 1 match between type and BAR idx */
+static int imx8ulp_get_bar_index(struct snd_sof_dev *sdev, u32 type)
+{
+ return type;
+}
+
+static int imx8ulp_suspend(struct snd_sof_dev *sdev)
+{
+ int i;
+ struct imx8ulp_priv *priv = (struct imx8ulp_priv *)sdev->pdata->hw_pdata;
+
+ /*Stall DSP, release in .run() */
+ regmap_update_bits(priv->regmap, SYSCTRL0, EXECUTE_BIT, EXECUTE_BIT);
+
+ for (i = 0; i < DSP_MU_CHAN_NUM; i++)
+ imx_dsp_free_channel(priv->dsp_ipc, i);
+
+ imx8_disable_clocks(sdev, priv->clks);
+
+ return 0;
+}
+
+static int imx8ulp_resume(struct snd_sof_dev *sdev)
+{
+ struct imx8ulp_priv *priv = (struct imx8ulp_priv *)sdev->pdata->hw_pdata;
+ int i;
+
+ imx8_enable_clocks(sdev, priv->clks);
+
+ for (i = 0; i < DSP_MU_CHAN_NUM; i++)
+ imx_dsp_request_channel(priv->dsp_ipc, i);
+
+ return 0;
+}
+
+static int imx8ulp_dsp_runtime_resume(struct snd_sof_dev *sdev)
+{
+ const struct sof_dsp_power_state target_dsp_state = {
+ .state = SOF_DSP_PM_D0,
+ .substate = 0,
+ };
+
+ imx8ulp_resume(sdev);
+
+ return snd_sof_dsp_set_power_state(sdev, &target_dsp_state);
+}
+
+static int imx8ulp_dsp_runtime_suspend(struct snd_sof_dev *sdev)
+{
+ const struct sof_dsp_power_state target_dsp_state = {
+ .state = SOF_DSP_PM_D3,
+ .substate = 0,
+ };
+
+ imx8ulp_suspend(sdev);
+
+ return snd_sof_dsp_set_power_state(sdev, &target_dsp_state);
+}
+
+static int imx8ulp_dsp_suspend(struct snd_sof_dev *sdev, unsigned int target_state)
+{
+ const struct sof_dsp_power_state target_dsp_state = {
+ .state = target_state,
+ .substate = 0,
+ };
+
+ if (!pm_runtime_suspended(sdev->dev))
+ imx8ulp_suspend(sdev);
+
+ return snd_sof_dsp_set_power_state(sdev, &target_dsp_state);
+}
+
+static int imx8ulp_dsp_resume(struct snd_sof_dev *sdev)
+{
+ const struct sof_dsp_power_state target_dsp_state = {
+ .state = SOF_DSP_PM_D0,
+ .substate = 0,
+ };
+
+ imx8ulp_resume(sdev);
+
+ if (pm_runtime_suspended(sdev->dev)) {
+ pm_runtime_disable(sdev->dev);
+ pm_runtime_set_active(sdev->dev);
+ pm_runtime_mark_last_busy(sdev->dev);
+ pm_runtime_enable(sdev->dev);
+ pm_runtime_idle(sdev->dev);
+ }
+
+ return snd_sof_dsp_set_power_state(sdev, &target_dsp_state);
+}
+
+static struct snd_soc_dai_driver imx8ulp_dai[] = {
+ {
+ .name = "sai5",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 32,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 32,
+ },
+ },
+ {
+ .name = "sai6",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 32,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 32,
+ },
+ },
+};
+
+static int imx8ulp_dsp_set_power_state(struct snd_sof_dev *sdev,
+ const struct sof_dsp_power_state *target_state)
+{
+ sdev->dsp_power_state = *target_state;
+
+ return 0;
+}
+
+/* i.MX8 ops */
+static struct snd_sof_dsp_ops sof_imx8ulp_ops = {
+ /* probe and remove */
+ .probe = imx8ulp_probe,
+ .remove = imx8ulp_remove,
+ /* DSP core boot */
+ .run = imx8ulp_run,
+ .reset = imx8ulp_reset,
+
+ /* Block IO */
+ .block_read = sof_block_read,
+ .block_write = sof_block_write,
+
+ /* Module IO */
+ .read64 = sof_io_read64,
+
+ /* Mailbox IO */
+ .mailbox_read = sof_mailbox_read,
+ .mailbox_write = sof_mailbox_write,
+
+ /* ipc */
+ .send_msg = imx8ulp_send_msg,
+ .get_mailbox_offset = imx8ulp_get_mailbox_offset,
+ .get_window_offset = imx8ulp_get_window_offset,
+
+ .ipc_msg_data = sof_ipc_msg_data,
+ .set_stream_data_offset = sof_set_stream_data_offset,
+
+ /* stream callbacks */
+ .pcm_open = sof_stream_pcm_open,
+ .pcm_close = sof_stream_pcm_close,
+
+ /* module loading */
+ .get_bar_index = imx8ulp_get_bar_index,
+ /* firmware loading */
+ .load_firmware = snd_sof_load_firmware_memcpy,
+
+ /* Debug information */
+ .dbg_dump = imx8_dump,
+
+ /* Firmware ops */
+ .dsp_arch_ops = &sof_xtensa_arch_ops,
+
+ /* DAI drivers */
+ .drv = imx8ulp_dai,
+ .num_drv = ARRAY_SIZE(imx8ulp_dai),
+
+ /* ALSA HW info flags */
+ .hw_info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_BATCH |
+ SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
+
+ /* PM */
+ .runtime_suspend = imx8ulp_dsp_runtime_suspend,
+ .runtime_resume = imx8ulp_dsp_runtime_resume,
+
+ .suspend = imx8ulp_dsp_suspend,
+ .resume = imx8ulp_dsp_resume,
+
+ .set_power_state = imx8ulp_dsp_set_power_state,
+};
+
+static struct snd_sof_of_mach sof_imx8ulp_machs[] = {
+ {
+ .compatible = "fsl,imx8ulp-evk",
+ .sof_tplg_filename = "sof-imx8ulp-btsco.tplg",
+ .drv_name = "asoc-audio-graph-card2",
+ },
+ {}
+};
+
+static struct sof_dev_desc sof_of_imx8ulp_desc = {
+ .of_machines = sof_imx8ulp_machs,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "imx/sof",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "imx/sof-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-imx8ulp.ri",
+ },
+ .nocodec_tplg_filename = "sof-imx8ulp-nocodec.tplg",
+ .ops = &sof_imx8ulp_ops,
+};
+
+static const struct of_device_id sof_of_imx8ulp_ids[] = {
+ { .compatible = "fsl,imx8ulp-dsp", .data = &sof_of_imx8ulp_desc},
+ { }
+};
+MODULE_DEVICE_TABLE(of, sof_of_imx8ulp_ids);
+
+/* DT driver definition */
+static struct platform_driver snd_sof_of_imx8ulp_driver = {
+ .probe = sof_of_probe,
+ .remove_new = sof_of_remove,
+ .driver = {
+ .name = "sof-audio-of-imx8ulp",
+ .pm = &sof_of_pm,
+ .of_match_table = sof_of_imx8ulp_ids,
+ },
+};
+module_platform_driver(snd_sof_of_imx8ulp_driver);
+
+MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA);
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/sof/intel/Kconfig b/sound/soc/sof/intel/Kconfig
index 3aaf25e4f766..9de86aaa8d07 100644
--- a/sound/soc/sof/intel/Kconfig
+++ b/sound/soc/sof/intel/Kconfig
@@ -9,35 +9,11 @@ config SND_SOC_SOF_INTEL_TOPLEVEL
if SND_SOC_SOF_INTEL_TOPLEVEL
-config SND_SOC_SOF_INTEL_ACPI
- def_tristate SND_SOC_SOF_ACPI
- select SND_SOC_SOF_BAYTRAIL if SND_SOC_SOF_BAYTRAIL_SUPPORT
- select SND_SOC_SOF_BROADWELL if SND_SOC_SOF_BROADWELL_SUPPORT
- help
- This option is not user-selectable but automagically handled by
- 'select' statements at a higher level
-
-config SND_SOC_SOF_INTEL_PCI
- def_tristate SND_SOC_SOF_PCI
- select SND_SOC_SOF_MERRIFIELD if SND_SOC_SOF_MERRIFIELD_SUPPORT
- select SND_SOC_SOF_APOLLOLAKE if SND_SOC_SOF_APOLLOLAKE_SUPPORT
- select SND_SOC_SOF_GEMINILAKE if SND_SOC_SOF_GEMINILAKE_SUPPORT
- select SND_SOC_SOF_CANNONLAKE if SND_SOC_SOF_CANNONLAKE_SUPPORT
- select SND_SOC_SOF_COFFEELAKE if SND_SOC_SOF_COFFEELAKE_SUPPORT
- select SND_SOC_SOF_ICELAKE if SND_SOC_SOF_ICELAKE_SUPPORT
- select SND_SOC_SOF_COMETLAKE if SND_SOC_SOF_COMETLAKE_SUPPORT
- select SND_SOC_SOF_TIGERLAKE if SND_SOC_SOF_TIGERLAKE_SUPPORT
- select SND_SOC_SOF_ELKHARTLAKE if SND_SOC_SOF_ELKHARTLAKE_SUPPORT
- select SND_SOC_SOF_JASPERLAKE if SND_SOC_SOF_JASPERLAKE_SUPPORT
- help
- This option is not user-selectable but automagically handled by
- 'select' statements at a higher level
-
config SND_SOC_SOF_INTEL_HIFI_EP_IPC
tristate
help
This option is not user-selectable but automagically handled by
- 'select' statements at a higher level
+ 'select' statements at a higher level.
config SND_SOC_SOF_INTEL_ATOM_HIFI_EP
tristate
@@ -45,272 +21,301 @@ config SND_SOC_SOF_INTEL_ATOM_HIFI_EP
select SND_SOC_SOF_INTEL_HIFI_EP_IPC
help
This option is not user-selectable but automagically handled by
- 'select' statements at a higher level
+ 'select' statements at a higher level.
config SND_SOC_SOF_INTEL_COMMON
tristate
+ select SND_SOC_SOF
select SND_SOC_ACPI_INTEL_MATCH
select SND_SOC_SOF_XTENSA
select SND_SOC_INTEL_MACH
select SND_SOC_ACPI if ACPI
+ select SND_INTEL_DSP_CONFIG
help
This option is not user-selectable but automagically handled by
- 'select' statements at a higher level
+ 'select' statements at a higher level.
-if SND_SOC_SOF_INTEL_ACPI
-
-config SND_SOC_SOF_BAYTRAIL_SUPPORT
- bool "SOF support for Baytrail, Braswell and Cherrytrail"
- depends on SND_SST_ATOM_HIFI2_PLATFORM_ACPI=n
- help
- This adds support for Sound Open Firmware for Intel(R) platforms
- using the Baytrail, Braswell or Cherrytrail processors.
- This option is mutually exclusive with the Atom/SST and Baytrail
- legacy drivers. If you want to enable SOF on Baytrail/Cherrytrail,
- you need to deselect those options first.
- SOF does not support Baytrail-CR for now, so this option is not
- recommended for distros. At some point all legacy drivers will be
- deprecated but not before all userspace firmware/topology/UCM files
- are made available to downstream distros.
- Say Y if you want to enable SOF on Baytrail/Cherrytrail
- If unsure select "N".
+if SND_SOC_SOF_ACPI
config SND_SOC_SOF_BAYTRAIL
- tristate
+ tristate "SOF support for Baytrail, Braswell and Cherrytrail"
+ default SND_SOC_SOF_ACPI
+ select SND_SOC_SOF_IPC3
+ select SND_SOC_SOF_INTEL_COMMON
select SND_SOC_SOF_INTEL_ATOM_HIFI_EP
- help
- This option is not user-selectable but automagically handled by
- 'select' statements at a higher level
-
-config SND_SOC_SOF_BROADWELL_SUPPORT
- bool "SOF support for Broadwell"
- depends on SND_SOC_INTEL_HASWELL=n
+ select SND_SOC_SOF_ACPI_DEV
+ select IOSF_MBI if X86 && PCI
help
This adds support for Sound Open Firmware for Intel(R) platforms
- using the Broadwell processors.
- This option is mutually exclusive with the Haswell/Broadwell legacy
- driver. If you want to enable SOF on Broadwell you need to deselect
- the legacy driver first.
- SOF does fully support Broadwell yet, so this option is not
- recommended for distros. At some point all legacy drivers will be
- deprecated but not before all userspace firmware/topology/UCM files
- are made available to downstream distros.
- Say Y if you want to enable SOF on Broadwell
+ using the Baytrail, Braswell or Cherrytrail processors.
+ This option can coexist in the same build with the Atom legacy
+ drivers, currently the default but which will be deprecated
+ at some point.
+ Existing firmware/topology binaries and UCM configurations
+ typically located in the root file system are already
+ compatible with both SOF or Atom/SST legacy drivers.
+ This is a recommended option for distributions.
+ Say Y if you want to enable SOF on Baytrail/Cherrytrail.
If unsure select "N".
config SND_SOC_SOF_BROADWELL
- tristate
+ tristate "SOF support for Broadwell"
+ default SND_SOC_SOF_ACPI
+ select SND_SOC_SOF_IPC3
select SND_SOC_SOF_INTEL_COMMON
select SND_SOC_SOF_INTEL_HIFI_EP_IPC
+ select SND_SOC_SOF_ACPI_DEV
help
- This option is not user-selectable but automagically handled by
- 'select' statements at a higher level
+ This adds support for Sound Open Firmware for Intel(R) platforms
+ using the Broadwell processors.
+ This option can coexist in the same build with the default 'catpt'
+ driver.
+ Existing firmware/topology binaries and UCM configurations typically
+ located in the root file system are already compatible with both SOF
+ or catpt drivers.
+ SOF does not fully support Broadwell and has limitations related to
+ DMA and suspend-resume, this is not a recommended option for
+ distributions.
+ Say Y if you want to enable SOF on Broadwell.
+ If unsure select "N".
-endif ## SND_SOC_SOF_INTEL_ACPI
+endif ## SND_SOC_SOF_ACPI
-if SND_SOC_SOF_INTEL_PCI
+if SND_SOC_SOF_PCI
-config SND_SOC_SOF_MERRIFIELD_SUPPORT
- bool "SOF support for Tangier/Merrifield"
+config SND_SOC_SOF_MERRIFIELD
+ tristate "SOF support for Tangier/Merrifield"
+ default SND_SOC_SOF_PCI
+ select SND_SOC_SOF_PCI_DEV
+ select SND_SOC_SOF_IPC3
+ select SND_SOC_SOF_INTEL_ATOM_HIFI_EP
help
This adds support for Sound Open Firmware for Intel(R) platforms
using the Tangier/Merrifield processors.
Say Y if you have such a device.
If unsure select "N".
-config SND_SOC_SOF_MERRIFIELD
+config SND_SOC_SOF_INTEL_SKL
tristate
- select SND_SOC_SOF_INTEL_ATOM_HIFI_EP
+ select SND_SOC_SOF_HDA_COMMON
+ select SND_SOC_SOF_IPC4
+
+config SND_SOC_SOF_SKYLAKE
+ tristate "SOF support for SkyLake"
+ default SND_SOC_SOF_PCI
+ select SND_SOC_SOF_INTEL_SKL
help
- This option is not user-selectable but automagically handled by
- 'select' statements at a higher level
+ This adds support for the Intel(R) platforms using the SkyLake processors.
+ Say Y if you have such a device.
+ If unsure select "N".
+ This is intended only for developers and not a recommend option for distros.
-config SND_SOC_SOF_APOLLOLAKE_SUPPORT
- bool "SOF support for Apollolake"
+config SND_SOC_SOF_KABYLAKE
+ tristate "SOF support for KabyLake"
+ default SND_SOC_SOF_PCI
+ select SND_SOC_SOF_INTEL_SKL
help
- This adds support for Sound Open Firmware for Intel(R) platforms
- using the Apollolake processors.
+ This adds support for the Intel(R) platforms using the KabyLake processors.
Say Y if you have such a device.
If unsure select "N".
+ This is intended only for developers and not a recommend option for distros.
-config SND_SOC_SOF_APOLLOLAKE
+config SND_SOC_SOF_INTEL_APL
tristate
select SND_SOC_SOF_HDA_COMMON
+ select SND_SOC_SOF_IPC3
+ select SND_SOC_SOF_IPC4
+
+config SND_SOC_SOF_APOLLOLAKE
+ tristate "SOF support for Apollolake"
+ default SND_SOC_SOF_PCI
+ select SND_SOC_SOF_INTEL_APL
help
- This option is not user-selectable but automagically handled by
- 'select' statements at a higher level
+ This adds support for Sound Open Firmware for Intel(R) platforms
+ using the Apollolake processors.
+ Say Y if you have such a device.
+ If unsure select "N".
-config SND_SOC_SOF_GEMINILAKE_SUPPORT
- bool "SOF support for GeminiLake"
+config SND_SOC_SOF_GEMINILAKE
+ tristate "SOF support for GeminiLake"
+ default SND_SOC_SOF_PCI
+ select SND_SOC_SOF_INTEL_APL
help
This adds support for Sound Open Firmware for Intel(R) platforms
using the Geminilake processors.
Say Y if you have such a device.
If unsure select "N".
-config SND_SOC_SOF_GEMINILAKE
+config SND_SOC_SOF_INTEL_CNL
tristate
select SND_SOC_SOF_HDA_COMMON
- help
- This option is not user-selectable but automagically handled by
- 'select' statements at a higher level
+ select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
+ select SND_SOC_SOF_IPC3
+ select SND_SOC_SOF_IPC4
-config SND_SOC_SOF_CANNONLAKE_SUPPORT
- bool "SOF support for Cannonlake"
+config SND_SOC_SOF_CANNONLAKE
+ tristate "SOF support for Cannonlake"
+ default SND_SOC_SOF_PCI
+ select SND_SOC_SOF_INTEL_CNL
help
This adds support for Sound Open Firmware for Intel(R) platforms
using the Cannonlake processors.
Say Y if you have such a device.
If unsure select "N".
-config SND_SOC_SOF_CANNONLAKE
- tristate
- select SND_SOC_SOF_HDA_COMMON
- help
- This option is not user-selectable but automagically handled by
- 'select' statements at a higher level
-
-config SND_SOC_SOF_COFFEELAKE_SUPPORT
- bool "SOF support for CoffeeLake"
+config SND_SOC_SOF_COFFEELAKE
+ tristate "SOF support for CoffeeLake"
+ default SND_SOC_SOF_PCI
+ select SND_SOC_SOF_INTEL_CNL
help
This adds support for Sound Open Firmware for Intel(R) platforms
using the Coffeelake processors.
Say Y if you have such a device.
If unsure select "N".
-config SND_SOC_SOF_COFFEELAKE
+config SND_SOC_SOF_COMETLAKE
+ tristate "SOF support for CometLake"
+ default SND_SOC_SOF_PCI
+ select SND_SOC_SOF_INTEL_CNL
+ help
+ This adds support for Sound Open Firmware for Intel(R) platforms
+ using the Cometlake processors.
+ If unsure select "N".
+
+config SND_SOC_SOF_INTEL_ICL
tristate
select SND_SOC_SOF_HDA_COMMON
- help
- This option is not user-selectable but automagically handled by
- 'select' statements at a higher level
+ select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
+ select SND_SOC_SOF_IPC3
+ select SND_SOC_SOF_IPC4
-config SND_SOC_SOF_ICELAKE_SUPPORT
- bool "SOF support for Icelake"
+config SND_SOC_SOF_ICELAKE
+ tristate "SOF support for Icelake"
+ default SND_SOC_SOF_PCI
+ select SND_SOC_SOF_INTEL_ICL
help
This adds support for Sound Open Firmware for Intel(R) platforms
using the Icelake processors.
Say Y if you have such a device.
If unsure select "N".
-config SND_SOC_SOF_ICELAKE
- tristate
- select SND_SOC_SOF_HDA_COMMON
+config SND_SOC_SOF_JASPERLAKE
+ tristate "SOF support for JasperLake"
+ default SND_SOC_SOF_PCI
+ select SND_SOC_SOF_INTEL_ICL
help
- This option is not user-selectable but automagically handled by
- 'select' statements at a higher level
+ This adds support for Sound Open Firmware for Intel(R) platforms
+ using the JasperLake processors.
+ Say Y if you have such a device.
+ If unsure select "N".
-config SND_SOC_SOF_COMETLAKE
+config SND_SOC_SOF_INTEL_TGL
tristate
select SND_SOC_SOF_HDA_COMMON
- help
- This option is not user-selectable but automagically handled by
- 'select' statements at a higher level
+ select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
+ select SND_SOC_SOF_IPC3
+ select SND_SOC_SOF_IPC4
-config SND_SOC_SOF_COMETLAKE_SUPPORT
- bool
+config SND_SOC_SOF_TIGERLAKE
+ tristate "SOF support for Tigerlake"
+ default SND_SOC_SOF_PCI
+ select SND_SOC_SOF_INTEL_TGL
+ help
+ This adds support for Sound Open Firmware for Intel(R) platforms
+ using the Tigerlake processors.
+ Say Y if you have such a device.
+ If unsure select "N".
-config SND_SOC_SOF_COMETLAKE_LP_SUPPORT
- bool "SOF support for CometLake"
- select SND_SOC_SOF_COMETLAKE_SUPPORT
+config SND_SOC_SOF_ELKHARTLAKE
+ tristate "SOF support for ElkhartLake"
+ default SND_SOC_SOF_PCI
+ select SND_SOC_SOF_INTEL_TGL
help
This adds support for Sound Open Firmware for Intel(R) platforms
- using the Cometlake processors.
+ using the ElkhartLake processors.
+ Say Y if you have such a device.
If unsure select "N".
-config SND_SOC_SOF_TIGERLAKE_SUPPORT
- bool "SOF support for Tigerlake"
+config SND_SOC_SOF_ALDERLAKE
+ tristate "SOF support for Alderlake"
+ default SND_SOC_SOF_PCI
+ select SND_SOC_SOF_INTEL_TGL
help
This adds support for Sound Open Firmware for Intel(R) platforms
- using the Tigerlake processors.
+ using the Alderlake processors.
Say Y if you have such a device.
If unsure select "N".
-config SND_SOC_SOF_TIGERLAKE
+config SND_SOC_SOF_INTEL_MTL
tristate
select SND_SOC_SOF_HDA_COMMON
- help
- This option is not user-selectable but automagically handled by
- 'select' statements at a higher level
+ select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
+ select SND_SOC_SOF_IPC4
-config SND_SOC_SOF_ELKHARTLAKE_SUPPORT
- bool "SOF support for ElkhartLake"
+config SND_SOC_SOF_METEORLAKE
+ tristate "SOF support for Meteorlake"
+ default SND_SOC_SOF_PCI
+ select SND_SOC_SOF_INTEL_MTL
help
This adds support for Sound Open Firmware for Intel(R) platforms
- using the ElkhartLake processors.
+ using the Meteorlake processors.
Say Y if you have such a device.
If unsure select "N".
-config SND_SOC_SOF_ELKHARTLAKE
+config SND_SOC_SOF_INTEL_LNL
tristate
select SND_SOC_SOF_HDA_COMMON
- help
- This option is not user-selectable but automagically handled by
- 'select' statements at a higher level
+ select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
+ select SND_SOC_SOF_IPC4
-config SND_SOC_SOF_JASPERLAKE_SUPPORT
- bool "SOF support for JasperLake"
+config SND_SOC_SOF_LUNARLAKE
+ tristate "SOF support for Lunarlake"
+ default SND_SOC_SOF_PCI
+ select SND_SOC_SOF_INTEL_LNL
help
This adds support for Sound Open Firmware for Intel(R) platforms
- using the JasperLake processors.
+ using the Lunarlake processors.
Say Y if you have such a device.
If unsure select "N".
-config SND_SOC_SOF_JASPERLAKE
+config SND_SOC_SOF_HDA_COMMON
tristate
- select SND_SOC_SOF_HDA_COMMON
+ select SND_SOC_SOF_INTEL_COMMON
+ select SND_SOC_SOF_PCI_DEV
+ select SND_INTEL_DSP_CONFIG
+ select SND_SOC_SOF_HDA_LINK_BASELINE
+ select SND_SOC_SOF_HDA_PROBES
+ select SND_SOC_SOF_HDA_MLINK if SND_SOC_SOF_HDA_LINK
help
This option is not user-selectable but automagically handled by
- 'select' statements at a higher level
+ 'select' statements at a higher level.
-config SND_SOC_SOF_HDA_COMMON
+config SND_SOC_SOF_HDA_MLINK
tristate
- select SND_SOC_SOF_INTEL_COMMON
- select SND_SOC_SOF_HDA_LINK_BASELINE
help
This option is not user-selectable but automagically handled by
- 'select' statements at a higher level
+ 'select' statements at a higher level.
if SND_SOC_SOF_HDA_COMMON
config SND_SOC_SOF_HDA_LINK
bool "SOF support for HDA Links(HDA/HDMI)"
- depends on SND_SOC_SOF_NOCODEC=n
- select SND_SOC_SOF_PROBE_WORK_QUEUE
help
This adds support for HDA links(HDA/HDMI) with Sound Open Firmware
- for Intel(R) platforms.
+ for Intel(R) platforms.
Say Y if you want to enable HDA links with SOF.
If unsure select "N".
config SND_SOC_SOF_HDA_AUDIO_CODEC
bool "SOF support for HDAudio codecs"
depends on SND_SOC_SOF_HDA_LINK
+ select SND_SOC_SOF_PROBE_WORK_QUEUE
help
This adds support for HDAudio codecs with Sound Open Firmware
- for Intel(R) platforms.
+ for Intel(R) platforms.
Say Y if you want to enable HDAudio codecs with SOF.
If unsure select "N".
-config SND_SOC_SOF_HDA_PROBES
- bool "SOF enable probes over HDA"
- depends on SND_SOC_SOF_DEBUG_PROBES
- help
- This option enables the data probing for Intel(R).
- Intel(R) Skylake and newer platforms.
- Say Y if you want to enable probes.
- If unsure, select "N".
-
-config SND_SOC_SOF_HDA_ALWAYS_ENABLE_DMI_L1
- bool "SOF enable DMI Link L1"
- help
- This option enables DMI L1 for both playback and capture
- and disables known workarounds for specific HDaudio platforms.
- Only use to look into power optimizations on platforms not
- affected by DMI L1 issues. This option is not recommended.
- Say Y if you want to enable DMI Link L1
- If unsure, select "N".
-
endif ## SND_SOC_SOF_HDA_COMMON
config SND_SOC_SOF_HDA_LINK_BASELINE
@@ -318,17 +323,42 @@ config SND_SOC_SOF_HDA_LINK_BASELINE
select SND_SOC_SOF_HDA if SND_SOC_SOF_HDA_LINK
help
This option is not user-selectable but automagically handled by
- 'select' statements at a higher level
+ 'select' statements at a higher level.
config SND_SOC_SOF_HDA
tristate
select SND_HDA_EXT_CORE if SND_SOC_SOF_HDA_LINK
select SND_SOC_HDAC_HDA if SND_SOC_SOF_HDA_AUDIO_CODEC
- select SND_INTEL_DSP_CONFIG
help
This option is not user-selectable but automagically handled by
- 'select' statements at a higher level
+ 'select' statements at a higher level.
+
+config SND_SOC_SOF_HDA_PROBES
+ tristate
+ select SND_SOC_SOF_DEBUG_PROBES
+ help
+ The option enables the data probing for Intel(R) Skylake and newer
+ (HDA) platforms.
+ This option is not user-selectable but automagically handled by
+ 'select' statements at a higher level.
+
+config SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
+ tristate
+ select SOUNDWIRE_INTEL if SND_SOC_SOF_INTEL_SOUNDWIRE != n
+ select SND_INTEL_SOUNDWIRE_ACPI if SND_SOC_SOF_INTEL_SOUNDWIRE != n
+
+config SND_SOC_SOF_INTEL_SOUNDWIRE
+ tristate "SOF support for SoundWire"
+ default SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
+ depends on SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
+ depends on ACPI && SOUNDWIRE
+ depends on !(SOUNDWIRE=m && SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE=y)
+ help
+ This adds support for SoundWire with Sound Open Firmware
+ for Intel(R) platforms.
+ Say Y if you want to enable SoundWire links with SOF.
+ If unsure select "N".
-endif ## SND_SOC_SOF_INTEL_PCI
+endif ## SND_SOC_SOF_PCI
endif ## SND_SOC_SOF_INTEL_TOPLEVEL
diff --git a/sound/soc/sof/intel/Makefile b/sound/soc/sof/intel/Makefile
index f7e9358f1f06..6489d0660d58 100644
--- a/sound/soc/sof/intel/Makefile
+++ b/sound/soc/sof/intel/Makefile
@@ -1,20 +1,44 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
-snd-sof-intel-byt-objs := byt.o
-snd-sof-intel-bdw-objs := bdw.o
-
-snd-sof-intel-ipc-objs := intel-ipc.o
+snd-sof-acpi-intel-byt-objs := byt.o
+snd-sof-acpi-intel-bdw-objs := bdw.o
snd-sof-intel-hda-common-objs := hda.o hda-loader.o hda-stream.o hda-trace.o \
hda-dsp.o hda-ipc.o hda-ctrl.o hda-pcm.o \
- hda-dai.o hda-bus.o \
- apl.o cnl.o
-snd-sof-intel-hda-common-$(CONFIG_SND_SOC_SOF_HDA_PROBES) += hda-compress.o
+ hda-dai.o hda-dai-ops.o hda-bus.o \
+ skl.o hda-loader-skl.o \
+ apl.o cnl.o tgl.o icl.o mtl.o lnl.o hda-common-ops.o \
+ telemetry.o
+
+snd-sof-intel-hda-mlink-objs := hda-mlink.o
+
+snd-sof-intel-hda-common-$(CONFIG_SND_SOC_SOF_HDA_PROBES) += hda-probes.o
snd-sof-intel-hda-objs := hda-codec.o
-obj-$(CONFIG_SND_SOC_SOF_INTEL_ATOM_HIFI_EP) += snd-sof-intel-byt.o
-obj-$(CONFIG_SND_SOC_SOF_BROADWELL) += snd-sof-intel-bdw.o
-obj-$(CONFIG_SND_SOC_SOF_INTEL_HIFI_EP_IPC) += snd-sof-intel-ipc.o
+snd-sof-intel-atom-objs := atom.o
+
+obj-$(CONFIG_SND_SOC_SOF_INTEL_ATOM_HIFI_EP) += snd-sof-intel-atom.o
+obj-$(CONFIG_SND_SOC_SOF_BAYTRAIL) += snd-sof-acpi-intel-byt.o
+obj-$(CONFIG_SND_SOC_SOF_BROADWELL) += snd-sof-acpi-intel-bdw.o
obj-$(CONFIG_SND_SOC_SOF_HDA_COMMON) += snd-sof-intel-hda-common.o
+obj-$(CONFIG_SND_SOC_SOF_HDA_MLINK) += snd-sof-intel-hda-mlink.o
obj-$(CONFIG_SND_SOC_SOF_HDA) += snd-sof-intel-hda.o
+
+snd-sof-pci-intel-tng-objs := pci-tng.o
+snd-sof-pci-intel-skl-objs := pci-skl.o
+snd-sof-pci-intel-apl-objs := pci-apl.o
+snd-sof-pci-intel-cnl-objs := pci-cnl.o
+snd-sof-pci-intel-icl-objs := pci-icl.o
+snd-sof-pci-intel-tgl-objs := pci-tgl.o
+snd-sof-pci-intel-mtl-objs := pci-mtl.o
+snd-sof-pci-intel-lnl-objs := pci-lnl.o
+
+obj-$(CONFIG_SND_SOC_SOF_MERRIFIELD) += snd-sof-pci-intel-tng.o
+obj-$(CONFIG_SND_SOC_SOF_INTEL_SKL) += snd-sof-pci-intel-skl.o
+obj-$(CONFIG_SND_SOC_SOF_INTEL_APL) += snd-sof-pci-intel-apl.o
+obj-$(CONFIG_SND_SOC_SOF_INTEL_CNL) += snd-sof-pci-intel-cnl.o
+obj-$(CONFIG_SND_SOC_SOF_INTEL_ICL) += snd-sof-pci-intel-icl.o
+obj-$(CONFIG_SND_SOC_SOF_INTEL_TGL) += snd-sof-pci-intel-tgl.o
+obj-$(CONFIG_SND_SOC_SOF_INTEL_MTL) += snd-sof-pci-intel-mtl.o
+obj-$(CONFIG_SND_SOC_SOF_INTEL_LNL) += snd-sof-pci-intel-lnl.o
diff --git a/sound/soc/sof/intel/apl.c b/sound/soc/sof/intel/apl.c
index 9e29d4fd393a..dee6c7f73e80 100644
--- a/sound/soc/sof/intel/apl.c
+++ b/sound/soc/sof/intel/apl.c
@@ -15,6 +15,8 @@
* Hardware interface for audio DSP on Apollolake and GeminiLake
*/
+#include <sound/sof/ext_manifest4.h>
+#include "../ipc4-priv.h"
#include "../sof-priv.h"
#include "hda.h"
#include "../sof-audio.h"
@@ -26,117 +28,97 @@ static const struct snd_sof_debugfs_map apl_dsp_debugfs[] = {
};
/* apollolake ops */
-const struct snd_sof_dsp_ops sof_apl_ops = {
- /* probe and remove */
- .probe = hda_dsp_probe,
- .remove = hda_dsp_remove,
-
- /* Register IO */
- .write = sof_io_write,
- .read = sof_io_read,
- .write64 = sof_io_write64,
- .read64 = sof_io_read64,
-
- /* Block IO */
- .block_read = sof_block_read,
- .block_write = sof_block_write,
-
- /* doorbell */
- .irq_thread = hda_dsp_ipc_irq_thread,
-
- /* ipc */
- .send_msg = hda_dsp_ipc_send_msg,
- .fw_ready = sof_fw_ready,
- .get_mailbox_offset = hda_dsp_ipc_get_mailbox_offset,
- .get_window_offset = hda_dsp_ipc_get_window_offset,
-
- .ipc_msg_data = hda_ipc_msg_data,
- .ipc_pcm_params = hda_ipc_pcm_params,
-
- /* machine driver */
- .machine_select = hda_machine_select,
- .machine_register = sof_machine_register,
- .machine_unregister = sof_machine_unregister,
- .set_mach_params = hda_set_mach_params,
+struct snd_sof_dsp_ops sof_apl_ops;
+EXPORT_SYMBOL_NS(sof_apl_ops, SND_SOC_SOF_INTEL_HDA_COMMON);
+
+int sof_apl_ops_init(struct snd_sof_dev *sdev)
+{
+ /* common defaults */
+ memcpy(&sof_apl_ops, &sof_hda_common_ops, sizeof(struct snd_sof_dsp_ops));
+
+ /* probe/remove/shutdown */
+ sof_apl_ops.shutdown = hda_dsp_shutdown;
+
+ if (sdev->pdata->ipc_type == SOF_IPC_TYPE_3) {
+ /* doorbell */
+ sof_apl_ops.irq_thread = hda_dsp_ipc_irq_thread;
+
+ /* ipc */
+ sof_apl_ops.send_msg = hda_dsp_ipc_send_msg;
+
+ /* debug */
+ sof_apl_ops.ipc_dump = hda_ipc_dump;
+
+ sof_apl_ops.set_power_state = hda_dsp_set_power_state_ipc3;
+ }
+
+ if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4) {
+ struct sof_ipc4_fw_data *ipc4_data;
+
+ sdev->private = kzalloc(sizeof(*ipc4_data), GFP_KERNEL);
+ if (!sdev->private)
+ return -ENOMEM;
+
+ ipc4_data = sdev->private;
+ ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET;
+
+ ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_1_5;
+
+ /* External library loading support */
+ ipc4_data->load_library = hda_dsp_ipc4_load_library;
+
+ /* doorbell */
+ sof_apl_ops.irq_thread = hda_dsp_ipc4_irq_thread;
+
+ /* ipc */
+ sof_apl_ops.send_msg = hda_dsp_ipc4_send_msg;
+
+ /* debug */
+ sof_apl_ops.ipc_dump = hda_ipc4_dump;
+
+ sof_apl_ops.set_power_state = hda_dsp_set_power_state_ipc4;
+ }
+
+ /* set DAI driver ops */
+ hda_set_dai_drv_ops(sdev, &sof_apl_ops);
/* debug */
- .debug_map = apl_dsp_debugfs,
- .debug_map_count = ARRAY_SIZE(apl_dsp_debugfs),
- .dbg_dump = hda_dsp_dump,
- .ipc_dump = hda_ipc_dump,
-
- /* stream callbacks */
- .pcm_open = hda_dsp_pcm_open,
- .pcm_close = hda_dsp_pcm_close,
- .pcm_hw_params = hda_dsp_pcm_hw_params,
- .pcm_hw_free = hda_dsp_stream_hw_free,
- .pcm_trigger = hda_dsp_pcm_trigger,
- .pcm_pointer = hda_dsp_pcm_pointer,
-
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES)
- /* probe callbacks */
- .probe_assign = hda_probe_compr_assign,
- .probe_free = hda_probe_compr_free,
- .probe_set_params = hda_probe_compr_set_params,
- .probe_trigger = hda_probe_compr_trigger,
- .probe_pointer = hda_probe_compr_pointer,
-#endif
-
- /* firmware loading */
- .load_firmware = snd_sof_load_firmware_raw,
+ sof_apl_ops.debug_map = apl_dsp_debugfs;
+ sof_apl_ops.debug_map_count = ARRAY_SIZE(apl_dsp_debugfs);
/* firmware run */
- .run = hda_dsp_cl_boot_firmware,
+ sof_apl_ops.run = hda_dsp_cl_boot_firmware;
/* pre/post fw run */
- .pre_fw_run = hda_dsp_pre_fw_run,
- .post_fw_run = hda_dsp_post_fw_run,
-
- /* dsp core power up/down */
- .core_power_up = hda_dsp_enable_core,
- .core_power_down = hda_dsp_core_reset_power_down,
-
- /* trace callback */
- .trace_init = hda_dsp_trace_init,
- .trace_release = hda_dsp_trace_release,
- .trace_trigger = hda_dsp_trace_trigger,
-
- /* DAI drivers */
- .drv = skl_dai,
- .num_drv = SOF_SKL_NUM_DAIS,
-
- /* PM */
- .suspend = hda_dsp_suspend,
- .resume = hda_dsp_resume,
- .runtime_suspend = hda_dsp_runtime_suspend,
- .runtime_resume = hda_dsp_runtime_resume,
- .runtime_idle = hda_dsp_runtime_idle,
- .set_hw_params_upon_resume = hda_dsp_set_hw_params_upon_resume,
- .set_power_state = hda_dsp_set_power_state,
-
- /* ALSA HW info flags */
- .hw_info = SNDRV_PCM_INFO_MMAP |
- SNDRV_PCM_INFO_MMAP_VALID |
- SNDRV_PCM_INFO_INTERLEAVED |
- SNDRV_PCM_INFO_PAUSE |
- SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
-
- .arch_ops = &sof_xtensa_arch_ops,
+ sof_apl_ops.post_fw_run = hda_dsp_post_fw_run;
+
+ /* dsp core get/put */
+ sof_apl_ops.core_get = hda_dsp_core_get;
+
+ return 0;
};
-EXPORT_SYMBOL_NS(sof_apl_ops, SND_SOC_SOF_INTEL_HDA_COMMON);
+EXPORT_SYMBOL_NS(sof_apl_ops_init, SND_SOC_SOF_INTEL_HDA_COMMON);
const struct sof_intel_dsp_desc apl_chip_info = {
/* Apollolake */
.cores_num = 2,
.init_core_mask = 1,
- .cores_mask = HDA_DSP_CORE_MASK(0) | HDA_DSP_CORE_MASK(1),
+ .host_managed_cores_mask = GENMASK(1, 0),
.ipc_req = HDA_DSP_REG_HIPCI,
.ipc_req_mask = HDA_DSP_REG_HIPCI_BUSY,
.ipc_ack = HDA_DSP_REG_HIPCIE,
.ipc_ack_mask = HDA_DSP_REG_HIPCIE_DONE,
.ipc_ctl = HDA_DSP_REG_HIPCCTL,
+ .rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS,
.rom_init_timeout = 150,
.ssp_count = APL_SSP_COUNT,
.ssp_base_offset = APL_SSP_BASE_OFFSET,
+ .d0i3_offset = SOF_HDA_VS_D0I3C,
+ .quirks = SOF_INTEL_PROCEN_FMT_QUIRK,
+ .check_ipc_irq = hda_dsp_check_ipc_irq,
+ .cl_init = cl_dsp_init,
+ .power_down_dsp = hda_power_down_dsp,
+ .disable_interrupts = hda_dsp_disable_interrupts,
+ .hw_ip_version = SOF_INTEL_CAVS_1_5_PLUS,
};
EXPORT_SYMBOL_NS(apl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
diff --git a/sound/soc/sof/intel/atom.c b/sound/soc/sof/intel/atom.c
new file mode 100644
index 000000000000..bd9789b483b1
--- /dev/null
+++ b/sound/soc/sof/intel/atom.c
@@ -0,0 +1,420 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2018-2021 Intel Corporation. All rights reserved.
+//
+// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
+//
+
+/*
+ * Hardware interface for audio DSP on Atom devices
+ */
+
+#include <linux/module.h>
+#include <sound/sof.h>
+#include <sound/sof/xtensa.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+#include <sound/intel-dsp-config.h>
+#include "../ops.h"
+#include "shim.h"
+#include "atom.h"
+#include "../sof-acpi-dev.h"
+#include "../sof-audio.h"
+#include "../../intel/common/soc-intel-quirks.h"
+
+static void atom_host_done(struct snd_sof_dev *sdev);
+static void atom_dsp_done(struct snd_sof_dev *sdev);
+
+/*
+ * Debug
+ */
+
+static void atom_get_registers(struct snd_sof_dev *sdev,
+ struct sof_ipc_dsp_oops_xtensa *xoops,
+ struct sof_ipc_panic_info *panic_info,
+ u32 *stack, size_t stack_words)
+{
+ u32 offset = sdev->dsp_oops_offset;
+
+ /* first read regsisters */
+ sof_mailbox_read(sdev, offset, xoops, sizeof(*xoops));
+
+ /* note: variable AR register array is not read */
+
+ /* then get panic info */
+ if (xoops->arch_hdr.totalsize > EXCEPT_MAX_HDR_SIZE) {
+ dev_err(sdev->dev, "invalid header size 0x%x. FW oops is bogus\n",
+ xoops->arch_hdr.totalsize);
+ return;
+ }
+ offset += xoops->arch_hdr.totalsize;
+ sof_mailbox_read(sdev, offset, panic_info, sizeof(*panic_info));
+
+ /* then get the stack */
+ offset += sizeof(*panic_info);
+ sof_mailbox_read(sdev, offset, stack, stack_words * sizeof(u32));
+}
+
+void atom_dump(struct snd_sof_dev *sdev, u32 flags)
+{
+ struct sof_ipc_dsp_oops_xtensa xoops;
+ struct sof_ipc_panic_info panic_info;
+ u32 stack[STACK_DUMP_SIZE];
+ u64 status, panic, imrd, imrx;
+
+ /* now try generic SOF status messages */
+ status = snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_IPCD);
+ panic = snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_IPCX);
+ atom_get_registers(sdev, &xoops, &panic_info, stack,
+ STACK_DUMP_SIZE);
+ sof_print_oops_and_stack(sdev, KERN_ERR, status, panic, &xoops,
+ &panic_info, stack, STACK_DUMP_SIZE);
+
+ /* provide some context for firmware debug */
+ imrx = snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_IMRX);
+ imrd = snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_IMRD);
+ dev_err(sdev->dev,
+ "error: ipc host -> DSP: pending %s complete %s raw 0x%llx\n",
+ (panic & SHIM_IPCX_BUSY) ? "yes" : "no",
+ (panic & SHIM_IPCX_DONE) ? "yes" : "no", panic);
+ dev_err(sdev->dev,
+ "error: mask host: pending %s complete %s raw 0x%llx\n",
+ (imrx & SHIM_IMRX_BUSY) ? "yes" : "no",
+ (imrx & SHIM_IMRX_DONE) ? "yes" : "no", imrx);
+ dev_err(sdev->dev,
+ "error: ipc DSP -> host: pending %s complete %s raw 0x%llx\n",
+ (status & SHIM_IPCD_BUSY) ? "yes" : "no",
+ (status & SHIM_IPCD_DONE) ? "yes" : "no", status);
+ dev_err(sdev->dev,
+ "error: mask DSP: pending %s complete %s raw 0x%llx\n",
+ (imrd & SHIM_IMRD_BUSY) ? "yes" : "no",
+ (imrd & SHIM_IMRD_DONE) ? "yes" : "no", imrd);
+
+}
+EXPORT_SYMBOL_NS(atom_dump, SND_SOC_SOF_INTEL_ATOM_HIFI_EP);
+
+/*
+ * IPC Doorbell IRQ handler and thread.
+ */
+
+irqreturn_t atom_irq_handler(int irq, void *context)
+{
+ struct snd_sof_dev *sdev = context;
+ u64 ipcx, ipcd;
+ int ret = IRQ_NONE;
+
+ ipcx = snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_IPCX);
+ ipcd = snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_IPCD);
+
+ if (ipcx & SHIM_BYT_IPCX_DONE) {
+
+ /* reply message from DSP, Mask Done interrupt first */
+ snd_sof_dsp_update_bits64_unlocked(sdev, DSP_BAR,
+ SHIM_IMRX,
+ SHIM_IMRX_DONE,
+ SHIM_IMRX_DONE);
+ ret = IRQ_WAKE_THREAD;
+ }
+
+ if (ipcd & SHIM_BYT_IPCD_BUSY) {
+
+ /* new message from DSP, Mask Busy interrupt first */
+ snd_sof_dsp_update_bits64_unlocked(sdev, DSP_BAR,
+ SHIM_IMRX,
+ SHIM_IMRX_BUSY,
+ SHIM_IMRX_BUSY);
+ ret = IRQ_WAKE_THREAD;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_NS(atom_irq_handler, SND_SOC_SOF_INTEL_ATOM_HIFI_EP);
+
+irqreturn_t atom_irq_thread(int irq, void *context)
+{
+ struct snd_sof_dev *sdev = context;
+ u64 ipcx, ipcd;
+
+ ipcx = snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_IPCX);
+ ipcd = snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_IPCD);
+
+ /* reply message from DSP */
+ if (ipcx & SHIM_BYT_IPCX_DONE) {
+
+ spin_lock_irq(&sdev->ipc_lock);
+
+ /*
+ * handle immediate reply from DSP core. If the msg is
+ * found, set done bit in cmd_done which is called at the
+ * end of message processing function, else set it here
+ * because the done bit can't be set in cmd_done function
+ * which is triggered by msg
+ */
+ snd_sof_ipc_process_reply(sdev, ipcx);
+
+ atom_dsp_done(sdev);
+
+ spin_unlock_irq(&sdev->ipc_lock);
+ }
+
+ /* new message from DSP */
+ if (ipcd & SHIM_BYT_IPCD_BUSY) {
+
+ /* Handle messages from DSP Core */
+ if ((ipcd & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) {
+ snd_sof_dsp_panic(sdev, PANIC_OFFSET(ipcd) + MBOX_OFFSET,
+ true);
+ } else {
+ snd_sof_ipc_msgs_rx(sdev);
+ }
+
+ atom_host_done(sdev);
+ }
+
+ return IRQ_HANDLED;
+}
+EXPORT_SYMBOL_NS(atom_irq_thread, SND_SOC_SOF_INTEL_ATOM_HIFI_EP);
+
+int atom_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
+{
+ /* unmask and prepare to receive Done interrupt */
+ snd_sof_dsp_update_bits64_unlocked(sdev, DSP_BAR, SHIM_IMRX,
+ SHIM_IMRX_DONE, 0);
+
+ /* send the message */
+ sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data,
+ msg->msg_size);
+ snd_sof_dsp_write64(sdev, DSP_BAR, SHIM_IPCX, SHIM_BYT_IPCX_BUSY);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(atom_send_msg, SND_SOC_SOF_INTEL_ATOM_HIFI_EP);
+
+int atom_get_mailbox_offset(struct snd_sof_dev *sdev)
+{
+ return MBOX_OFFSET;
+}
+EXPORT_SYMBOL_NS(atom_get_mailbox_offset, SND_SOC_SOF_INTEL_ATOM_HIFI_EP);
+
+int atom_get_window_offset(struct snd_sof_dev *sdev, u32 id)
+{
+ return MBOX_OFFSET;
+}
+EXPORT_SYMBOL_NS(atom_get_window_offset, SND_SOC_SOF_INTEL_ATOM_HIFI_EP);
+
+static void atom_host_done(struct snd_sof_dev *sdev)
+{
+ /* clear BUSY bit and set DONE bit - accept new messages */
+ snd_sof_dsp_update_bits64_unlocked(sdev, DSP_BAR, SHIM_IPCD,
+ SHIM_BYT_IPCD_BUSY |
+ SHIM_BYT_IPCD_DONE,
+ SHIM_BYT_IPCD_DONE);
+
+ /* unmask and prepare to receive next new message */
+ snd_sof_dsp_update_bits64_unlocked(sdev, DSP_BAR, SHIM_IMRX,
+ SHIM_IMRX_BUSY, 0);
+}
+
+static void atom_dsp_done(struct snd_sof_dev *sdev)
+{
+ /* clear DONE bit - tell DSP we have completed */
+ snd_sof_dsp_update_bits64_unlocked(sdev, DSP_BAR, SHIM_IPCX,
+ SHIM_BYT_IPCX_DONE, 0);
+}
+
+/*
+ * DSP control.
+ */
+
+int atom_run(struct snd_sof_dev *sdev)
+{
+ int tries = 10;
+
+ /* release stall and wait to unstall */
+ snd_sof_dsp_update_bits64(sdev, DSP_BAR, SHIM_CSR,
+ SHIM_BYT_CSR_STALL, 0x0);
+ while (tries--) {
+ if (!(snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_CSR) &
+ SHIM_BYT_CSR_PWAITMODE))
+ break;
+ msleep(100);
+ }
+ if (tries < 0)
+ return -ENODEV;
+
+ /* return init core mask */
+ return 1;
+}
+EXPORT_SYMBOL_NS(atom_run, SND_SOC_SOF_INTEL_ATOM_HIFI_EP);
+
+int atom_reset(struct snd_sof_dev *sdev)
+{
+ /* put DSP into reset, set reset vector and stall */
+ snd_sof_dsp_update_bits64(sdev, DSP_BAR, SHIM_CSR,
+ SHIM_BYT_CSR_RST | SHIM_BYT_CSR_VECTOR_SEL |
+ SHIM_BYT_CSR_STALL,
+ SHIM_BYT_CSR_RST | SHIM_BYT_CSR_VECTOR_SEL |
+ SHIM_BYT_CSR_STALL);
+
+ usleep_range(10, 15);
+
+ /* take DSP out of reset and keep stalled for FW loading */
+ snd_sof_dsp_update_bits64(sdev, DSP_BAR, SHIM_CSR,
+ SHIM_BYT_CSR_RST, 0);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(atom_reset, SND_SOC_SOF_INTEL_ATOM_HIFI_EP);
+
+static const char *fixup_tplg_name(struct snd_sof_dev *sdev,
+ const char *sof_tplg_filename,
+ const char *ssp_str)
+{
+ const char *tplg_filename = NULL;
+ const char *split_ext;
+ char *filename, *tmp;
+
+ filename = kstrdup(sof_tplg_filename, GFP_KERNEL);
+ if (!filename)
+ return NULL;
+
+ /* this assumes a .tplg extension */
+ tmp = filename;
+ split_ext = strsep(&tmp, ".");
+ if (split_ext)
+ tplg_filename = devm_kasprintf(sdev->dev, GFP_KERNEL,
+ "%s-%s.tplg",
+ split_ext, ssp_str);
+ kfree(filename);
+
+ return tplg_filename;
+}
+
+struct snd_soc_acpi_mach *atom_machine_select(struct snd_sof_dev *sdev)
+{
+ struct snd_sof_pdata *sof_pdata = sdev->pdata;
+ const struct sof_dev_desc *desc = sof_pdata->desc;
+ struct snd_soc_acpi_mach *mach;
+ struct platform_device *pdev;
+ const char *tplg_filename;
+
+ mach = snd_soc_acpi_find_machine(desc->machines);
+ if (!mach) {
+ dev_warn(sdev->dev, "warning: No matching ASoC machine driver found\n");
+ return NULL;
+ }
+
+ pdev = to_platform_device(sdev->dev);
+ if (soc_intel_is_byt_cr(pdev)) {
+ dev_dbg(sdev->dev,
+ "BYT-CR detected, SSP0 used instead of SSP2\n");
+
+ tplg_filename = fixup_tplg_name(sdev,
+ mach->sof_tplg_filename,
+ "ssp0");
+ } else {
+ tplg_filename = mach->sof_tplg_filename;
+ }
+
+ if (!tplg_filename) {
+ dev_dbg(sdev->dev,
+ "error: no topology filename\n");
+ return NULL;
+ }
+
+ sof_pdata->tplg_filename = tplg_filename;
+ mach->mach_params.acpi_ipc_irq_index = desc->irqindex_host_ipc;
+
+ return mach;
+}
+EXPORT_SYMBOL_NS(atom_machine_select, SND_SOC_SOF_INTEL_ATOM_HIFI_EP);
+
+/* Atom DAIs */
+struct snd_soc_dai_driver atom_dai[] = {
+{
+ .name = "ssp0-port",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+},
+{
+ .name = "ssp1-port",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+},
+{
+ .name = "ssp2-port",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 8,
+ }
+},
+{
+ .name = "ssp3-port",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+},
+{
+ .name = "ssp4-port",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+},
+{
+ .name = "ssp5-port",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+},
+};
+EXPORT_SYMBOL_NS(atom_dai, SND_SOC_SOF_INTEL_ATOM_HIFI_EP);
+
+void atom_set_mach_params(struct snd_soc_acpi_mach *mach,
+ struct snd_sof_dev *sdev)
+{
+ struct snd_sof_pdata *pdata = sdev->pdata;
+ const struct sof_dev_desc *desc = pdata->desc;
+ struct snd_soc_acpi_mach_params *mach_params;
+
+ mach_params = &mach->mach_params;
+ mach_params->platform = dev_name(sdev->dev);
+ mach_params->num_dai_drivers = desc->ops->num_drv;
+ mach_params->dai_drivers = desc->ops->drv;
+}
+EXPORT_SYMBOL_NS(atom_set_mach_params, SND_SOC_SOF_INTEL_ATOM_HIFI_EP);
+
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/sof/intel/atom.h b/sound/soc/sof/intel/atom.h
new file mode 100644
index 000000000000..b965e5e080a6
--- /dev/null
+++ b/sound/soc/sof/intel/atom.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+/*
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * Copyright(c) 2017-2021 Intel Corporation. All rights reserved.
+ *
+ * Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
+ */
+
+#ifndef __SOF_INTEL_ATOM_H
+#define __SOF_INTEL_ATOM_H
+
+/* DSP memories */
+#define IRAM_OFFSET 0x0C0000
+#define IRAM_SIZE (80 * 1024)
+#define DRAM_OFFSET 0x100000
+#define DRAM_SIZE (160 * 1024)
+#define SHIM_OFFSET 0x140000
+#define SHIM_SIZE_BYT 0x100
+#define SHIM_SIZE_CHT 0x118
+#define MBOX_OFFSET 0x144000
+#define MBOX_SIZE 0x1000
+#define EXCEPT_OFFSET 0x800
+#define EXCEPT_MAX_HDR_SIZE 0x400
+
+/* DSP peripherals */
+#define DMAC0_OFFSET 0x098000
+#define DMAC1_OFFSET 0x09c000
+#define DMAC2_OFFSET 0x094000
+#define DMAC_SIZE 0x420
+#define SSP0_OFFSET 0x0a0000
+#define SSP1_OFFSET 0x0a1000
+#define SSP2_OFFSET 0x0a2000
+#define SSP3_OFFSET 0x0a4000
+#define SSP4_OFFSET 0x0a5000
+#define SSP5_OFFSET 0x0a6000
+#define SSP_SIZE 0x100
+
+#define STACK_DUMP_SIZE 32
+
+#define PCI_BAR_SIZE 0x200000
+
+#define PANIC_OFFSET(x) (((x) & GENMASK_ULL(47, 32)) >> 32)
+
+/*
+ * Debug
+ */
+
+#define MBOX_DUMP_SIZE 0x30
+
+/* BARs */
+#define DSP_BAR 0
+#define PCI_BAR 1
+#define IMR_BAR 2
+
+irqreturn_t atom_irq_handler(int irq, void *context);
+irqreturn_t atom_irq_thread(int irq, void *context);
+
+int atom_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg);
+int atom_get_mailbox_offset(struct snd_sof_dev *sdev);
+int atom_get_window_offset(struct snd_sof_dev *sdev, u32 id);
+
+int atom_run(struct snd_sof_dev *sdev);
+int atom_reset(struct snd_sof_dev *sdev);
+void atom_dump(struct snd_sof_dev *sdev, u32 flags);
+
+struct snd_soc_acpi_mach *atom_machine_select(struct snd_sof_dev *sdev);
+void atom_set_mach_params(struct snd_soc_acpi_mach *mach,
+ struct snd_sof_dev *sdev);
+
+extern struct snd_soc_dai_driver atom_dai[];
+
+#endif
diff --git a/sound/soc/sof/intel/bdw.c b/sound/soc/sof/intel/bdw.c
index 99fd0bd7276e..e30ca086f3f8 100644
--- a/sound/soc/sof/intel/bdw.c
+++ b/sound/soc/sof/intel/bdw.c
@@ -15,8 +15,12 @@
#include <linux/module.h>
#include <sound/sof.h>
#include <sound/sof/xtensa.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+#include <sound/intel-dsp-config.h>
#include "../ops.h"
#include "shim.h"
+#include "../sof-acpi-dev.h"
#include "../sof-audio.h"
/* BARs */
@@ -71,7 +75,6 @@ static const struct snd_sof_debugfs_map bdw_debugfs[] = {
static void bdw_host_done(struct snd_sof_dev *sdev);
static void bdw_dsp_done(struct snd_sof_dev *sdev);
-static void bdw_get_reply(struct snd_sof_dev *sdev);
/*
* DSP Control.
@@ -255,8 +258,8 @@ static void bdw_dump(struct snd_sof_dev *sdev, u32 flags)
panic = snd_sof_dsp_read(sdev, BDW_DSP_BAR, SHIM_IPCX);
bdw_get_registers(sdev, &xoops, &panic_info, stack,
BDW_STACK_DUMP_SIZE);
- snd_sof_get_status(sdev, status, panic, &xoops, &panic_info, stack,
- BDW_STACK_DUMP_SIZE);
+ sof_print_oops_and_stack(sdev, KERN_ERR, status, panic, &xoops,
+ &panic_info, stack, BDW_STACK_DUMP_SIZE);
/* provide some context for firmware debug */
imrx = snd_sof_dsp_read(sdev, BDW_DSP_BAR, SHIM_IMRX);
@@ -322,8 +325,7 @@ static irqreturn_t bdw_irq_thread(int irq, void *context)
* because the done bit can't be set in cmd_done function
* which is triggered by msg
*/
- bdw_get_reply(sdev);
- snd_sof_ipc_reply(sdev, ipcx);
+ snd_sof_ipc_process_reply(sdev, ipcx);
bdw_dsp_done(sdev);
@@ -342,8 +344,8 @@ static irqreturn_t bdw_irq_thread(int irq, void *context)
/* Handle messages from DSP Core */
if ((ipcd & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) {
- snd_sof_dsp_panic(sdev, BDW_PANIC_OFFSET(ipcx) +
- MBOX_OFFSET);
+ snd_sof_dsp_panic(sdev, BDW_PANIC_OFFSET(ipcx) + MBOX_OFFSET,
+ true);
} else {
snd_sof_ipc_msgs_rx(sdev);
}
@@ -368,45 +370,6 @@ static int bdw_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
return 0;
}
-static void bdw_get_reply(struct snd_sof_dev *sdev)
-{
- struct snd_sof_ipc_msg *msg = sdev->msg;
- struct sof_ipc_reply reply;
- int ret = 0;
-
- /*
- * Sometimes, there is unexpected reply ipc arriving. The reply
- * ipc belongs to none of the ipcs sent from driver.
- * In this case, the driver must ignore the ipc.
- */
- if (!msg) {
- dev_warn(sdev->dev, "unexpected ipc interrupt raised!\n");
- return;
- }
-
- /* get reply */
- sof_mailbox_read(sdev, sdev->host_box.offset, &reply, sizeof(reply));
-
- if (reply.error < 0) {
- memcpy(msg->reply_data, &reply, sizeof(reply));
- ret = reply.error;
- } else {
- /* reply correct size ? */
- if (reply.hdr.size != msg->reply_size) {
- dev_err(sdev->dev, "error: reply expected %zu got %u bytes\n",
- msg->reply_size, reply.hdr.size);
- ret = -EINVAL;
- }
-
- /* read the message */
- if (msg->reply_size > 0)
- sof_mailbox_read(sdev, sdev->host_box.offset,
- msg->reply_data, msg->reply_size);
- }
-
- msg->reply_error = ret;
-}
-
static int bdw_get_mailbox_offset(struct snd_sof_dev *sdev)
{
return MBOX_OFFSET;
@@ -449,10 +412,19 @@ static int bdw_probe(struct snd_sof_dev *sdev)
const struct sof_dev_desc *desc = pdata->desc;
struct platform_device *pdev =
container_of(sdev->dev, struct platform_device, dev);
+ const struct sof_intel_dsp_desc *chip;
struct resource *mmio;
u32 base, size;
int ret;
+ chip = get_chip_info(sdev->pdata);
+ if (!chip) {
+ dev_err(sdev->dev, "error: no such device supported\n");
+ return -EIO;
+ }
+
+ sdev->num_cores = chip->cores_num;
+
/* LPE base */
mmio = platform_get_resource(pdev, IORESOURCE_MEM,
desc->resindex_lpe_base);
@@ -531,13 +503,13 @@ static int bdw_probe(struct snd_sof_dev *sdev)
return ret;
}
- /* set default mailbox */
- snd_sof_dsp_mailbox_init(sdev, MBOX_OFFSET, MBOX_SIZE, 0, 0);
+ /* set default mailbox offset for FW ready message */
+ sdev->dsp_box.offset = MBOX_OFFSET;
return ret;
}
-static void bdw_machine_select(struct snd_sof_dev *sdev)
+static struct snd_soc_acpi_mach *bdw_machine_select(struct snd_sof_dev *sdev)
{
struct snd_sof_pdata *sof_pdata = sdev->pdata;
const struct sof_dev_desc *desc = sof_pdata->desc;
@@ -546,21 +518,26 @@ static void bdw_machine_select(struct snd_sof_dev *sdev)
mach = snd_soc_acpi_find_machine(desc->machines);
if (!mach) {
dev_warn(sdev->dev, "warning: No matching ASoC machine driver found\n");
- return;
+ return NULL;
}
sof_pdata->tplg_filename = mach->sof_tplg_filename;
mach->mach_params.acpi_ipc_irq_index = desc->irqindex_host_ipc;
- sof_pdata->machine = mach;
+
+ return mach;
}
-static void bdw_set_mach_params(const struct snd_soc_acpi_mach *mach,
- struct device *dev)
+static void bdw_set_mach_params(struct snd_soc_acpi_mach *mach,
+ struct snd_sof_dev *sdev)
{
+ struct snd_sof_pdata *pdata = sdev->pdata;
+ const struct sof_dev_desc *desc = pdata->desc;
struct snd_soc_acpi_mach_params *mach_params;
- mach_params = (struct snd_soc_acpi_mach_params *)&mach->mach_params;
- mach_params->platform = dev_name(dev);
+ mach_params = &mach->mach_params;
+ mach_params->platform = dev_name(sdev->dev);
+ mach_params->num_dai_drivers = desc->ops->num_drv;
+ mach_params->dai_drivers = desc->ops->drv;
}
/* Broadwell DAIs */
@@ -590,7 +567,7 @@ static struct snd_soc_dai_driver bdw_dai[] = {
};
/* broadwell ops */
-const struct snd_sof_dsp_ops sof_bdw_ops = {
+static struct snd_sof_dsp_ops sof_bdw_ops = {
/*Device init */
.probe = bdw_probe,
@@ -598,24 +575,23 @@ const struct snd_sof_dsp_ops sof_bdw_ops = {
.run = bdw_run,
.reset = bdw_reset,
- /* Register IO */
- .write = sof_io_write,
- .read = sof_io_read,
- .write64 = sof_io_write64,
- .read64 = sof_io_read64,
+ /* Register IO uses direct mmio */
/* Block IO */
.block_read = sof_block_read,
.block_write = sof_block_write,
+ /* Mailbox IO */
+ .mailbox_read = sof_mailbox_read,
+ .mailbox_write = sof_mailbox_write,
+
/* ipc */
.send_msg = bdw_send_msg,
- .fw_ready = sof_fw_ready,
.get_mailbox_offset = bdw_get_mailbox_offset,
.get_window_offset = bdw_get_window_offset,
- .ipc_msg_data = intel_ipc_msg_data,
- .ipc_pcm_params = intel_ipc_pcm_params,
+ .ipc_msg_data = sof_ipc_msg_data,
+ .set_stream_data_offset = sof_set_stream_data_offset,
/* machine driver */
.machine_select = bdw_machine_select,
@@ -627,13 +603,11 @@ const struct snd_sof_dsp_ops sof_bdw_ops = {
.debug_map = bdw_debugfs,
.debug_map_count = ARRAY_SIZE(bdw_debugfs),
.dbg_dump = bdw_dump,
+ .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem,
/* stream callbacks */
- .pcm_open = intel_pcm_open,
- .pcm_close = intel_pcm_close,
-
- /* Module loading */
- .load_module = snd_sof_parse_module_memcpy,
+ .pcm_open = sof_stream_pcm_open,
+ .pcm_close = sof_stream_pcm_close,
/*Firmware loading */
.load_firmware = snd_sof_load_firmware_memcpy,
@@ -649,16 +623,77 @@ const struct snd_sof_dsp_ops sof_bdw_ops = {
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_BATCH,
- .arch_ops = &sof_xtensa_arch_ops,
+ .dsp_arch_ops = &sof_xtensa_arch_ops,
};
-EXPORT_SYMBOL_NS(sof_bdw_ops, SND_SOC_SOF_BROADWELL);
-const struct sof_intel_dsp_desc bdw_chip_info = {
+static const struct sof_intel_dsp_desc bdw_chip_info = {
.cores_num = 1,
- .cores_mask = 1,
+ .host_managed_cores_mask = 1,
+ .hw_ip_version = SOF_INTEL_BROADWELL,
+};
+
+static const struct sof_dev_desc sof_acpi_broadwell_desc = {
+ .machines = snd_soc_acpi_intel_broadwell_machines,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = 1,
+ .resindex_imr_base = -1,
+ .irqindex_host_ipc = 0,
+ .chip_info = &bdw_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-bdw.ri",
+ },
+ .nocodec_tplg_filename = "sof-bdw-nocodec.tplg",
+ .ops = &sof_bdw_ops,
+};
+
+static const struct acpi_device_id sof_broadwell_match[] = {
+ { "INT3438", (unsigned long)&sof_acpi_broadwell_desc },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, sof_broadwell_match);
+
+static int sof_broadwell_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct acpi_device_id *id;
+ const struct sof_dev_desc *desc;
+ int ret;
+
+ id = acpi_match_device(dev->driver->acpi_match_table, dev);
+ if (!id)
+ return -ENODEV;
+
+ ret = snd_intel_acpi_dsp_driver_probe(dev, id->id);
+ if (ret != SND_INTEL_DSP_DRIVER_ANY && ret != SND_INTEL_DSP_DRIVER_SOF) {
+ dev_dbg(dev, "SOF ACPI driver not selected, aborting probe\n");
+ return -ENODEV;
+ }
+
+ desc = (const struct sof_dev_desc *)id->driver_data;
+ return sof_acpi_probe(pdev, desc);
+}
+
+/* acpi_driver definition */
+static struct platform_driver snd_sof_acpi_intel_bdw_driver = {
+ .probe = sof_broadwell_probe,
+ .remove_new = sof_acpi_remove,
+ .driver = {
+ .name = "sof-audio-acpi-intel-bdw",
+ .pm = &sof_acpi_pm,
+ .acpi_match_table = sof_broadwell_match,
+ },
};
-EXPORT_SYMBOL_NS(bdw_chip_info, SND_SOC_SOF_BROADWELL);
+module_platform_driver(snd_sof_acpi_intel_bdw_driver);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_HIFI_EP_IPC);
MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA);
+MODULE_IMPORT_NS(SND_SOC_SOF_ACPI_DEV);
diff --git a/sound/soc/sof/intel/byt.c b/sound/soc/sof/intel/byt.c
index 49f67f1b94e0..373527b206d7 100644
--- a/sound/soc/sof/intel/byt.c
+++ b/sound/soc/sof/intel/byt.c
@@ -15,658 +15,70 @@
#include <linux/module.h>
#include <sound/sof.h>
#include <sound/sof/xtensa.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+#include <sound/intel-dsp-config.h>
#include "../ops.h"
+#include "atom.h"
#include "shim.h"
+#include "../sof-acpi-dev.h"
#include "../sof-audio.h"
#include "../../intel/common/soc-intel-quirks.h"
-/* DSP memories */
-#define IRAM_OFFSET 0x0C0000
-#define IRAM_SIZE (80 * 1024)
-#define DRAM_OFFSET 0x100000
-#define DRAM_SIZE (160 * 1024)
-#define SHIM_OFFSET 0x140000
-#define SHIM_SIZE_BYT 0x100
-#define SHIM_SIZE_CHT 0x118
-#define MBOX_OFFSET 0x144000
-#define MBOX_SIZE 0x1000
-#define EXCEPT_OFFSET 0x800
-#define EXCEPT_MAX_HDR_SIZE 0x400
-
-/* DSP peripherals */
-#define DMAC0_OFFSET 0x098000
-#define DMAC1_OFFSET 0x09c000
-#define DMAC2_OFFSET 0x094000
-#define DMAC_SIZE 0x420
-#define SSP0_OFFSET 0x0a0000
-#define SSP1_OFFSET 0x0a1000
-#define SSP2_OFFSET 0x0a2000
-#define SSP3_OFFSET 0x0a4000
-#define SSP4_OFFSET 0x0a5000
-#define SSP5_OFFSET 0x0a6000
-#define SSP_SIZE 0x100
-
-#define BYT_STACK_DUMP_SIZE 32
-
-#define BYT_PCI_BAR_SIZE 0x200000
-
-#define BYT_PANIC_OFFSET(x) (((x) & GENMASK_ULL(47, 32)) >> 32)
-
-/*
- * Debug
- */
-
-#define MBOX_DUMP_SIZE 0x30
-
-/* BARs */
-#define BYT_DSP_BAR 0
-#define BYT_PCI_BAR 1
-#define BYT_IMR_BAR 2
-
static const struct snd_sof_debugfs_map byt_debugfs[] = {
- {"dmac0", BYT_DSP_BAR, DMAC0_OFFSET, DMAC_SIZE,
+ {"dmac0", DSP_BAR, DMAC0_OFFSET, DMAC_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
- {"dmac1", BYT_DSP_BAR, DMAC1_OFFSET, DMAC_SIZE,
+ {"dmac1", DSP_BAR, DMAC1_OFFSET, DMAC_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
- {"ssp0", BYT_DSP_BAR, SSP0_OFFSET, SSP_SIZE,
+ {"ssp0", DSP_BAR, SSP0_OFFSET, SSP_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
- {"ssp1", BYT_DSP_BAR, SSP1_OFFSET, SSP_SIZE,
+ {"ssp1", DSP_BAR, SSP1_OFFSET, SSP_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
- {"ssp2", BYT_DSP_BAR, SSP2_OFFSET, SSP_SIZE,
+ {"ssp2", DSP_BAR, SSP2_OFFSET, SSP_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
- {"iram", BYT_DSP_BAR, IRAM_OFFSET, IRAM_SIZE,
+ {"iram", DSP_BAR, IRAM_OFFSET, IRAM_SIZE,
SOF_DEBUGFS_ACCESS_D0_ONLY},
- {"dram", BYT_DSP_BAR, DRAM_OFFSET, DRAM_SIZE,
+ {"dram", DSP_BAR, DRAM_OFFSET, DRAM_SIZE,
SOF_DEBUGFS_ACCESS_D0_ONLY},
- {"shim", BYT_DSP_BAR, SHIM_OFFSET, SHIM_SIZE_BYT,
+ {"shim", DSP_BAR, SHIM_OFFSET, SHIM_SIZE_BYT,
SOF_DEBUGFS_ACCESS_ALWAYS},
};
-static void byt_host_done(struct snd_sof_dev *sdev);
-static void byt_dsp_done(struct snd_sof_dev *sdev);
-static void byt_get_reply(struct snd_sof_dev *sdev);
-
-/*
- * Debug
- */
-
-static void byt_get_registers(struct snd_sof_dev *sdev,
- struct sof_ipc_dsp_oops_xtensa *xoops,
- struct sof_ipc_panic_info *panic_info,
- u32 *stack, size_t stack_words)
-{
- u32 offset = sdev->dsp_oops_offset;
-
- /* first read regsisters */
- sof_mailbox_read(sdev, offset, xoops, sizeof(*xoops));
-
- /* note: variable AR register array is not read */
-
- /* then get panic info */
- if (xoops->arch_hdr.totalsize > EXCEPT_MAX_HDR_SIZE) {
- dev_err(sdev->dev, "invalid header size 0x%x. FW oops is bogus\n",
- xoops->arch_hdr.totalsize);
- return;
- }
- offset += xoops->arch_hdr.totalsize;
- sof_mailbox_read(sdev, offset, panic_info, sizeof(*panic_info));
-
- /* then get the stack */
- offset += sizeof(*panic_info);
- sof_mailbox_read(sdev, offset, stack, stack_words * sizeof(u32));
-}
-
-static void byt_dump(struct snd_sof_dev *sdev, u32 flags)
-{
- struct sof_ipc_dsp_oops_xtensa xoops;
- struct sof_ipc_panic_info panic_info;
- u32 stack[BYT_STACK_DUMP_SIZE];
- u64 status, panic, imrd, imrx;
-
- /* now try generic SOF status messages */
- status = snd_sof_dsp_read64(sdev, BYT_DSP_BAR, SHIM_IPCD);
- panic = snd_sof_dsp_read64(sdev, BYT_DSP_BAR, SHIM_IPCX);
- byt_get_registers(sdev, &xoops, &panic_info, stack,
- BYT_STACK_DUMP_SIZE);
- snd_sof_get_status(sdev, status, panic, &xoops, &panic_info, stack,
- BYT_STACK_DUMP_SIZE);
-
- /* provide some context for firmware debug */
- imrx = snd_sof_dsp_read64(sdev, BYT_DSP_BAR, SHIM_IMRX);
- imrd = snd_sof_dsp_read64(sdev, BYT_DSP_BAR, SHIM_IMRD);
- dev_err(sdev->dev,
- "error: ipc host -> DSP: pending %s complete %s raw 0x%llx\n",
- (panic & SHIM_IPCX_BUSY) ? "yes" : "no",
- (panic & SHIM_IPCX_DONE) ? "yes" : "no", panic);
- dev_err(sdev->dev,
- "error: mask host: pending %s complete %s raw 0x%llx\n",
- (imrx & SHIM_IMRX_BUSY) ? "yes" : "no",
- (imrx & SHIM_IMRX_DONE) ? "yes" : "no", imrx);
- dev_err(sdev->dev,
- "error: ipc DSP -> host: pending %s complete %s raw 0x%llx\n",
- (status & SHIM_IPCD_BUSY) ? "yes" : "no",
- (status & SHIM_IPCD_DONE) ? "yes" : "no", status);
- dev_err(sdev->dev,
- "error: mask DSP: pending %s complete %s raw 0x%llx\n",
- (imrd & SHIM_IMRD_BUSY) ? "yes" : "no",
- (imrd & SHIM_IMRD_DONE) ? "yes" : "no", imrd);
-
-}
-
-/*
- * IPC Doorbell IRQ handler and thread.
- */
-
-static irqreturn_t byt_irq_handler(int irq, void *context)
-{
- struct snd_sof_dev *sdev = context;
- u64 ipcx, ipcd;
- int ret = IRQ_NONE;
-
- ipcx = snd_sof_dsp_read64(sdev, BYT_DSP_BAR, SHIM_IPCX);
- ipcd = snd_sof_dsp_read64(sdev, BYT_DSP_BAR, SHIM_IPCD);
-
- if (ipcx & SHIM_BYT_IPCX_DONE) {
-
- /* reply message from DSP, Mask Done interrupt first */
- snd_sof_dsp_update_bits64_unlocked(sdev, BYT_DSP_BAR,
- SHIM_IMRX,
- SHIM_IMRX_DONE,
- SHIM_IMRX_DONE);
- ret = IRQ_WAKE_THREAD;
- }
-
- if (ipcd & SHIM_BYT_IPCD_BUSY) {
-
- /* new message from DSP, Mask Busy interrupt first */
- snd_sof_dsp_update_bits64_unlocked(sdev, BYT_DSP_BAR,
- SHIM_IMRX,
- SHIM_IMRX_BUSY,
- SHIM_IMRX_BUSY);
- ret = IRQ_WAKE_THREAD;
- }
-
- return ret;
-}
-
-static irqreturn_t byt_irq_thread(int irq, void *context)
-{
- struct snd_sof_dev *sdev = context;
- u64 ipcx, ipcd;
-
- ipcx = snd_sof_dsp_read64(sdev, BYT_DSP_BAR, SHIM_IPCX);
- ipcd = snd_sof_dsp_read64(sdev, BYT_DSP_BAR, SHIM_IPCD);
-
- /* reply message from DSP */
- if (ipcx & SHIM_BYT_IPCX_DONE) {
-
- spin_lock_irq(&sdev->ipc_lock);
-
- /*
- * handle immediate reply from DSP core. If the msg is
- * found, set done bit in cmd_done which is called at the
- * end of message processing function, else set it here
- * because the done bit can't be set in cmd_done function
- * which is triggered by msg
- */
- byt_get_reply(sdev);
- snd_sof_ipc_reply(sdev, ipcx);
-
- byt_dsp_done(sdev);
-
- spin_unlock_irq(&sdev->ipc_lock);
- }
-
- /* new message from DSP */
- if (ipcd & SHIM_BYT_IPCD_BUSY) {
-
- /* Handle messages from DSP Core */
- if ((ipcd & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) {
- snd_sof_dsp_panic(sdev, BYT_PANIC_OFFSET(ipcd) +
- MBOX_OFFSET);
- } else {
- snd_sof_ipc_msgs_rx(sdev);
- }
-
- byt_host_done(sdev);
- }
-
- return IRQ_HANDLED;
-}
-
-static int byt_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
-{
- /* unmask and prepare to receive Done interrupt */
- snd_sof_dsp_update_bits64_unlocked(sdev, BYT_DSP_BAR, SHIM_IMRX,
- SHIM_IMRX_DONE, 0);
-
- /* send the message */
- sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data,
- msg->msg_size);
- snd_sof_dsp_write64(sdev, BYT_DSP_BAR, SHIM_IPCX, SHIM_BYT_IPCX_BUSY);
-
- return 0;
-}
-
-static void byt_get_reply(struct snd_sof_dev *sdev)
-{
- struct snd_sof_ipc_msg *msg = sdev->msg;
- struct sof_ipc_reply reply;
- int ret = 0;
-
- /*
- * Sometimes, there is unexpected reply ipc arriving. The reply
- * ipc belongs to none of the ipcs sent from driver.
- * In this case, the driver must ignore the ipc.
- */
- if (!msg) {
- dev_warn(sdev->dev, "unexpected ipc interrupt raised!\n");
- return;
- }
-
- /* get reply */
- sof_mailbox_read(sdev, sdev->host_box.offset, &reply, sizeof(reply));
-
- if (reply.error < 0) {
- memcpy(msg->reply_data, &reply, sizeof(reply));
- ret = reply.error;
- } else {
- /* reply correct size ? */
- if (reply.hdr.size != msg->reply_size) {
- dev_err(sdev->dev, "error: reply expected %zu got %u bytes\n",
- msg->reply_size, reply.hdr.size);
- ret = -EINVAL;
- }
-
- /* read the message */
- if (msg->reply_size > 0)
- sof_mailbox_read(sdev, sdev->host_box.offset,
- msg->reply_data, msg->reply_size);
- }
-
- msg->reply_error = ret;
-}
-
-static int byt_get_mailbox_offset(struct snd_sof_dev *sdev)
-{
- return MBOX_OFFSET;
-}
-
-static int byt_get_window_offset(struct snd_sof_dev *sdev, u32 id)
-{
- return MBOX_OFFSET;
-}
-
-static void byt_host_done(struct snd_sof_dev *sdev)
-{
- /* clear BUSY bit and set DONE bit - accept new messages */
- snd_sof_dsp_update_bits64_unlocked(sdev, BYT_DSP_BAR, SHIM_IPCD,
- SHIM_BYT_IPCD_BUSY |
- SHIM_BYT_IPCD_DONE,
- SHIM_BYT_IPCD_DONE);
-
- /* unmask and prepare to receive next new message */
- snd_sof_dsp_update_bits64_unlocked(sdev, BYT_DSP_BAR, SHIM_IMRX,
- SHIM_IMRX_BUSY, 0);
-}
-
-static void byt_dsp_done(struct snd_sof_dev *sdev)
-{
- /* clear DONE bit - tell DSP we have completed */
- snd_sof_dsp_update_bits64_unlocked(sdev, BYT_DSP_BAR, SHIM_IPCX,
- SHIM_BYT_IPCX_DONE, 0);
-}
-
-/*
- * DSP control.
- */
-
-static int byt_run(struct snd_sof_dev *sdev)
-{
- int tries = 10;
-
- /* release stall and wait to unstall */
- snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_CSR,
- SHIM_BYT_CSR_STALL, 0x0);
- while (tries--) {
- if (!(snd_sof_dsp_read64(sdev, BYT_DSP_BAR, SHIM_CSR) &
- SHIM_BYT_CSR_PWAITMODE))
- break;
- msleep(100);
- }
- if (tries < 0) {
- dev_err(sdev->dev, "error: unable to run DSP firmware\n");
- byt_dump(sdev, SOF_DBG_REGS | SOF_DBG_MBOX);
- return -ENODEV;
- }
-
- /* return init core mask */
- return 1;
-}
-
-static int byt_reset(struct snd_sof_dev *sdev)
-{
- /* put DSP into reset, set reset vector and stall */
- snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_CSR,
- SHIM_BYT_CSR_RST | SHIM_BYT_CSR_VECTOR_SEL |
- SHIM_BYT_CSR_STALL,
- SHIM_BYT_CSR_RST | SHIM_BYT_CSR_VECTOR_SEL |
- SHIM_BYT_CSR_STALL);
-
- usleep_range(10, 15);
-
- /* take DSP out of reset and keep stalled for FW loading */
- snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_CSR,
- SHIM_BYT_CSR_RST, 0);
-
- return 0;
-}
-
-static const char *fixup_tplg_name(struct snd_sof_dev *sdev,
- const char *sof_tplg_filename,
- const char *ssp_str)
-{
- const char *tplg_filename = NULL;
- char *filename;
- char *split_ext;
-
- filename = devm_kstrdup(sdev->dev, sof_tplg_filename, GFP_KERNEL);
- if (!filename)
- return NULL;
-
- /* this assumes a .tplg extension */
- split_ext = strsep(&filename, ".");
- if (split_ext) {
- tplg_filename = devm_kasprintf(sdev->dev, GFP_KERNEL,
- "%s-%s.tplg",
- split_ext, ssp_str);
- if (!tplg_filename)
- return NULL;
- }
- return tplg_filename;
-}
-
-static void byt_machine_select(struct snd_sof_dev *sdev)
-{
- struct snd_sof_pdata *sof_pdata = sdev->pdata;
- const struct sof_dev_desc *desc = sof_pdata->desc;
- struct snd_soc_acpi_mach *mach;
- struct platform_device *pdev;
- const char *tplg_filename;
-
- mach = snd_soc_acpi_find_machine(desc->machines);
- if (!mach) {
- dev_warn(sdev->dev, "warning: No matching ASoC machine driver found\n");
- return;
- }
-
- pdev = to_platform_device(sdev->dev);
- if (soc_intel_is_byt_cr(pdev)) {
- dev_dbg(sdev->dev,
- "BYT-CR detected, SSP0 used instead of SSP2\n");
-
- tplg_filename = fixup_tplg_name(sdev,
- mach->sof_tplg_filename,
- "ssp0");
- } else {
- tplg_filename = mach->sof_tplg_filename;
- }
-
- if (!tplg_filename) {
- dev_dbg(sdev->dev,
- "error: no topology filename\n");
- return;
- }
-
- sof_pdata->tplg_filename = tplg_filename;
- mach->mach_params.acpi_ipc_irq_index = desc->irqindex_host_ipc;
- sof_pdata->machine = mach;
-}
-
-static void byt_set_mach_params(const struct snd_soc_acpi_mach *mach,
- struct device *dev)
-{
- struct snd_soc_acpi_mach_params *mach_params;
-
- mach_params = (struct snd_soc_acpi_mach_params *)&mach->mach_params;
- mach_params->platform = dev_name(dev);
-}
-
-/* Baytrail DAIs */
-static struct snd_soc_dai_driver byt_dai[] = {
-{
- .name = "ssp0-port",
- .playback = {
- .channels_min = 1,
- .channels_max = 8,
- },
- .capture = {
- .channels_min = 1,
- .channels_max = 8,
- },
-},
-{
- .name = "ssp1-port",
- .playback = {
- .channels_min = 1,
- .channels_max = 8,
- },
- .capture = {
- .channels_min = 1,
- .channels_max = 8,
- },
-},
-{
- .name = "ssp2-port",
- .playback = {
- .channels_min = 1,
- .channels_max = 8,
- },
- .capture = {
- .channels_min = 1,
- .channels_max = 8,
- }
-},
-{
- .name = "ssp3-port",
- .playback = {
- .channels_min = 1,
- .channels_max = 8,
- },
- .capture = {
- .channels_min = 1,
- .channels_max = 8,
- },
-},
-{
- .name = "ssp4-port",
- .playback = {
- .channels_min = 1,
- .channels_max = 8,
- },
- .capture = {
- .channels_min = 1,
- .channels_max = 8,
- },
-},
-{
- .name = "ssp5-port",
- .playback = {
- .channels_min = 1,
- .channels_max = 8,
- },
- .capture = {
- .channels_min = 1,
- .channels_max = 8,
- },
-},
-};
-
-/*
- * Probe and remove.
- */
-
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_MERRIFIELD)
-
-static int tangier_pci_probe(struct snd_sof_dev *sdev)
-{
- struct snd_sof_pdata *pdata = sdev->pdata;
- const struct sof_dev_desc *desc = pdata->desc;
- struct pci_dev *pci = to_pci_dev(sdev->dev);
- u32 base, size;
- int ret;
-
- /* DSP DMA can only access low 31 bits of host memory */
- ret = dma_coerce_mask_and_coherent(&pci->dev, DMA_BIT_MASK(31));
- if (ret < 0) {
- dev_err(sdev->dev, "error: failed to set DMA mask %d\n", ret);
- return ret;
- }
-
- /* LPE base */
- base = pci_resource_start(pci, desc->resindex_lpe_base) - IRAM_OFFSET;
- size = BYT_PCI_BAR_SIZE;
-
- dev_dbg(sdev->dev, "LPE PHY base at 0x%x size 0x%x", base, size);
- sdev->bar[BYT_DSP_BAR] = devm_ioremap(sdev->dev, base, size);
- if (!sdev->bar[BYT_DSP_BAR]) {
- dev_err(sdev->dev, "error: failed to ioremap LPE base 0x%x size 0x%x\n",
- base, size);
- return -ENODEV;
- }
- dev_dbg(sdev->dev, "LPE VADDR %p\n", sdev->bar[BYT_DSP_BAR]);
-
- /* IMR base - optional */
- if (desc->resindex_imr_base == -1)
- goto irq;
-
- base = pci_resource_start(pci, desc->resindex_imr_base);
- size = pci_resource_len(pci, desc->resindex_imr_base);
-
- /* some BIOSes don't map IMR */
- if (base == 0x55aa55aa || base == 0x0) {
- dev_info(sdev->dev, "IMR not set by BIOS. Ignoring\n");
- goto irq;
- }
-
- dev_dbg(sdev->dev, "IMR base at 0x%x size 0x%x", base, size);
- sdev->bar[BYT_IMR_BAR] = devm_ioremap(sdev->dev, base, size);
- if (!sdev->bar[BYT_IMR_BAR]) {
- dev_err(sdev->dev, "error: failed to ioremap IMR base 0x%x size 0x%x\n",
- base, size);
- return -ENODEV;
- }
- dev_dbg(sdev->dev, "IMR VADDR %p\n", sdev->bar[BYT_IMR_BAR]);
-
-irq:
- /* register our IRQ */
- sdev->ipc_irq = pci->irq;
- dev_dbg(sdev->dev, "using IRQ %d\n", sdev->ipc_irq);
- ret = devm_request_threaded_irq(sdev->dev, sdev->ipc_irq,
- byt_irq_handler, byt_irq_thread,
- 0, "AudioDSP", sdev);
- if (ret < 0) {
- dev_err(sdev->dev, "error: failed to register IRQ %d\n",
- sdev->ipc_irq);
- return ret;
- }
-
- /* enable BUSY and disable DONE Interrupt by default */
- snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_IMRX,
- SHIM_IMRX_BUSY | SHIM_IMRX_DONE,
- SHIM_IMRX_DONE);
-
- /* set default mailbox offset for FW ready message */
- sdev->dsp_box.offset = MBOX_OFFSET;
-
- return ret;
-}
-
-const struct snd_sof_dsp_ops sof_tng_ops = {
- /* device init */
- .probe = tangier_pci_probe,
-
- /* DSP core boot / reset */
- .run = byt_run,
- .reset = byt_reset,
-
- /* Register IO */
- .write = sof_io_write,
- .read = sof_io_read,
- .write64 = sof_io_write64,
- .read64 = sof_io_read64,
-
- /* Block IO */
- .block_read = sof_block_read,
- .block_write = sof_block_write,
-
- /* doorbell */
- .irq_handler = byt_irq_handler,
- .irq_thread = byt_irq_thread,
-
- /* ipc */
- .send_msg = byt_send_msg,
- .fw_ready = sof_fw_ready,
- .get_mailbox_offset = byt_get_mailbox_offset,
- .get_window_offset = byt_get_window_offset,
-
- .ipc_msg_data = intel_ipc_msg_data,
- .ipc_pcm_params = intel_ipc_pcm_params,
-
- /* machine driver */
- .machine_select = byt_machine_select,
- .machine_register = sof_machine_register,
- .machine_unregister = sof_machine_unregister,
- .set_mach_params = byt_set_mach_params,
-
- /* debug */
- .debug_map = byt_debugfs,
- .debug_map_count = ARRAY_SIZE(byt_debugfs),
- .dbg_dump = byt_dump,
-
- /* stream callbacks */
- .pcm_open = intel_pcm_open,
- .pcm_close = intel_pcm_close,
-
- /* module loading */
- .load_module = snd_sof_parse_module_memcpy,
-
- /*Firmware loading */
- .load_firmware = snd_sof_load_firmware_memcpy,
-
- /* DAI drivers */
- .drv = byt_dai,
- .num_drv = 3, /* we have only 3 SSPs on byt*/
-
- /* ALSA HW info flags */
- .hw_info = SNDRV_PCM_INFO_MMAP |
- SNDRV_PCM_INFO_MMAP_VALID |
- SNDRV_PCM_INFO_INTERLEAVED |
- SNDRV_PCM_INFO_PAUSE |
- SNDRV_PCM_INFO_BATCH,
-
- .arch_ops = &sof_xtensa_arch_ops,
-};
-EXPORT_SYMBOL_NS(sof_tng_ops, SND_SOC_SOF_MERRIFIELD);
-
-const struct sof_intel_dsp_desc tng_chip_info = {
- .cores_num = 1,
- .cores_mask = 1,
+static const struct snd_sof_debugfs_map cht_debugfs[] = {
+ {"dmac0", DSP_BAR, DMAC0_OFFSET, DMAC_SIZE,
+ SOF_DEBUGFS_ACCESS_ALWAYS},
+ {"dmac1", DSP_BAR, DMAC1_OFFSET, DMAC_SIZE,
+ SOF_DEBUGFS_ACCESS_ALWAYS},
+ {"dmac2", DSP_BAR, DMAC2_OFFSET, DMAC_SIZE,
+ SOF_DEBUGFS_ACCESS_ALWAYS},
+ {"ssp0", DSP_BAR, SSP0_OFFSET, SSP_SIZE,
+ SOF_DEBUGFS_ACCESS_ALWAYS},
+ {"ssp1", DSP_BAR, SSP1_OFFSET, SSP_SIZE,
+ SOF_DEBUGFS_ACCESS_ALWAYS},
+ {"ssp2", DSP_BAR, SSP2_OFFSET, SSP_SIZE,
+ SOF_DEBUGFS_ACCESS_ALWAYS},
+ {"ssp3", DSP_BAR, SSP3_OFFSET, SSP_SIZE,
+ SOF_DEBUGFS_ACCESS_ALWAYS},
+ {"ssp4", DSP_BAR, SSP4_OFFSET, SSP_SIZE,
+ SOF_DEBUGFS_ACCESS_ALWAYS},
+ {"ssp5", DSP_BAR, SSP5_OFFSET, SSP_SIZE,
+ SOF_DEBUGFS_ACCESS_ALWAYS},
+ {"iram", DSP_BAR, IRAM_OFFSET, IRAM_SIZE,
+ SOF_DEBUGFS_ACCESS_D0_ONLY},
+ {"dram", DSP_BAR, DRAM_OFFSET, DRAM_SIZE,
+ SOF_DEBUGFS_ACCESS_D0_ONLY},
+ {"shim", DSP_BAR, SHIM_OFFSET, SHIM_SIZE_CHT,
+ SOF_DEBUGFS_ACCESS_ALWAYS},
};
-EXPORT_SYMBOL_NS(tng_chip_info, SND_SOC_SOF_MERRIFIELD);
-
-#endif /* CONFIG_SND_SOC_SOF_MERRIFIELD */
-
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
static void byt_reset_dsp_disable_int(struct snd_sof_dev *sdev)
{
/* Disable Interrupt from both sides */
- snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_IMRX, 0x3, 0x3);
- snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_IMRD, 0x3, 0x3);
+ snd_sof_dsp_update_bits64(sdev, DSP_BAR, SHIM_IMRX, 0x3, 0x3);
+ snd_sof_dsp_update_bits64(sdev, DSP_BAR, SHIM_IMRD, 0x3, 0x3);
/* Put DSP into reset, set reset vector */
- snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_CSR,
+ snd_sof_dsp_update_bits64(sdev, DSP_BAR, SHIM_CSR,
SHIM_BYT_CSR_RST | SHIM_BYT_CSR_VECTOR_SEL,
SHIM_BYT_CSR_RST | SHIM_BYT_CSR_VECTOR_SEL);
}
@@ -681,57 +93,37 @@ static int byt_suspend(struct snd_sof_dev *sdev, u32 target_state)
static int byt_resume(struct snd_sof_dev *sdev)
{
/* enable BUSY and disable DONE Interrupt by default */
- snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_IMRX,
+ snd_sof_dsp_update_bits64(sdev, DSP_BAR, SHIM_IMRX,
SHIM_IMRX_BUSY | SHIM_IMRX_DONE,
SHIM_IMRX_DONE);
return 0;
}
-static int byt_remove(struct snd_sof_dev *sdev)
+static void byt_remove(struct snd_sof_dev *sdev)
{
byt_reset_dsp_disable_int(sdev);
-
- return 0;
}
-static const struct snd_sof_debugfs_map cht_debugfs[] = {
- {"dmac0", BYT_DSP_BAR, DMAC0_OFFSET, DMAC_SIZE,
- SOF_DEBUGFS_ACCESS_ALWAYS},
- {"dmac1", BYT_DSP_BAR, DMAC1_OFFSET, DMAC_SIZE,
- SOF_DEBUGFS_ACCESS_ALWAYS},
- {"dmac2", BYT_DSP_BAR, DMAC2_OFFSET, DMAC_SIZE,
- SOF_DEBUGFS_ACCESS_ALWAYS},
- {"ssp0", BYT_DSP_BAR, SSP0_OFFSET, SSP_SIZE,
- SOF_DEBUGFS_ACCESS_ALWAYS},
- {"ssp1", BYT_DSP_BAR, SSP1_OFFSET, SSP_SIZE,
- SOF_DEBUGFS_ACCESS_ALWAYS},
- {"ssp2", BYT_DSP_BAR, SSP2_OFFSET, SSP_SIZE,
- SOF_DEBUGFS_ACCESS_ALWAYS},
- {"ssp3", BYT_DSP_BAR, SSP3_OFFSET, SSP_SIZE,
- SOF_DEBUGFS_ACCESS_ALWAYS},
- {"ssp4", BYT_DSP_BAR, SSP4_OFFSET, SSP_SIZE,
- SOF_DEBUGFS_ACCESS_ALWAYS},
- {"ssp5", BYT_DSP_BAR, SSP5_OFFSET, SSP_SIZE,
- SOF_DEBUGFS_ACCESS_ALWAYS},
- {"iram", BYT_DSP_BAR, IRAM_OFFSET, IRAM_SIZE,
- SOF_DEBUGFS_ACCESS_D0_ONLY},
- {"dram", BYT_DSP_BAR, DRAM_OFFSET, DRAM_SIZE,
- SOF_DEBUGFS_ACCESS_D0_ONLY},
- {"shim", BYT_DSP_BAR, SHIM_OFFSET, SHIM_SIZE_CHT,
- SOF_DEBUGFS_ACCESS_ALWAYS},
-};
-
static int byt_acpi_probe(struct snd_sof_dev *sdev)
{
struct snd_sof_pdata *pdata = sdev->pdata;
const struct sof_dev_desc *desc = pdata->desc;
struct platform_device *pdev =
container_of(sdev->dev, struct platform_device, dev);
+ const struct sof_intel_dsp_desc *chip;
struct resource *mmio;
u32 base, size;
int ret;
+ chip = get_chip_info(sdev->pdata);
+ if (!chip) {
+ dev_err(sdev->dev, "error: no such device supported\n");
+ return -EIO;
+ }
+
+ sdev->num_cores = chip->cores_num;
+
/* DSP DMA can only access low 31 bits of host memory */
ret = dma_coerce_mask_and_coherent(sdev->dev, DMA_BIT_MASK(31));
if (ret < 0) {
@@ -752,17 +144,17 @@ static int byt_acpi_probe(struct snd_sof_dev *sdev)
}
dev_dbg(sdev->dev, "LPE PHY base at 0x%x size 0x%x", base, size);
- sdev->bar[BYT_DSP_BAR] = devm_ioremap(sdev->dev, base, size);
- if (!sdev->bar[BYT_DSP_BAR]) {
+ sdev->bar[DSP_BAR] = devm_ioremap(sdev->dev, base, size);
+ if (!sdev->bar[DSP_BAR]) {
dev_err(sdev->dev, "error: failed to ioremap LPE base 0x%x size 0x%x\n",
base, size);
return -ENODEV;
}
- dev_dbg(sdev->dev, "LPE VADDR %p\n", sdev->bar[BYT_DSP_BAR]);
+ dev_dbg(sdev->dev, "LPE VADDR %p\n", sdev->bar[DSP_BAR]);
/* TODO: add offsets */
- sdev->mmio_bar = BYT_DSP_BAR;
- sdev->mailbox_bar = BYT_DSP_BAR;
+ sdev->mmio_bar = DSP_BAR;
+ sdev->mailbox_bar = DSP_BAR;
/* IMR base - optional */
if (desc->resindex_imr_base == -1)
@@ -786,13 +178,13 @@ static int byt_acpi_probe(struct snd_sof_dev *sdev)
}
dev_dbg(sdev->dev, "IMR base at 0x%x size 0x%x", base, size);
- sdev->bar[BYT_IMR_BAR] = devm_ioremap(sdev->dev, base, size);
- if (!sdev->bar[BYT_IMR_BAR]) {
+ sdev->bar[IMR_BAR] = devm_ioremap(sdev->dev, base, size);
+ if (!sdev->bar[IMR_BAR]) {
dev_err(sdev->dev, "error: failed to ioremap IMR base 0x%x size 0x%x\n",
base, size);
return -ENODEV;
}
- dev_dbg(sdev->dev, "IMR VADDR %p\n", sdev->bar[BYT_IMR_BAR]);
+ dev_dbg(sdev->dev, "IMR VADDR %p\n", sdev->bar[IMR_BAR]);
irq:
/* register our IRQ */
@@ -802,7 +194,7 @@ irq:
dev_dbg(sdev->dev, "using IRQ %d\n", sdev->ipc_irq);
ret = devm_request_threaded_irq(sdev->dev, sdev->ipc_irq,
- byt_irq_handler, byt_irq_thread,
+ atom_irq_handler, atom_irq_thread,
IRQF_SHARED, "AudioDSP", sdev);
if (ret < 0) {
dev_err(sdev->dev, "error: failed to register IRQ %d\n",
@@ -811,7 +203,7 @@ irq:
}
/* enable BUSY and disable DONE Interrupt by default */
- snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_IMRX,
+ snd_sof_dsp_update_bits64(sdev, DSP_BAR, SHIM_IMRX,
SHIM_IMRX_BUSY | SHIM_IMRX_DONE,
SHIM_IMRX_DONE);
@@ -822,55 +214,52 @@ irq:
}
/* baytrail ops */
-const struct snd_sof_dsp_ops sof_byt_ops = {
+static struct snd_sof_dsp_ops sof_byt_ops = {
/* device init */
.probe = byt_acpi_probe,
.remove = byt_remove,
/* DSP core boot / reset */
- .run = byt_run,
- .reset = byt_reset,
+ .run = atom_run,
+ .reset = atom_reset,
- /* Register IO */
- .write = sof_io_write,
- .read = sof_io_read,
- .write64 = sof_io_write64,
- .read64 = sof_io_read64,
+ /* Register IO uses direct mmio */
/* Block IO */
.block_read = sof_block_read,
.block_write = sof_block_write,
+ /* Mailbox IO */
+ .mailbox_read = sof_mailbox_read,
+ .mailbox_write = sof_mailbox_write,
+
/* doorbell */
- .irq_handler = byt_irq_handler,
- .irq_thread = byt_irq_thread,
+ .irq_handler = atom_irq_handler,
+ .irq_thread = atom_irq_thread,
/* ipc */
- .send_msg = byt_send_msg,
- .fw_ready = sof_fw_ready,
- .get_mailbox_offset = byt_get_mailbox_offset,
- .get_window_offset = byt_get_window_offset,
+ .send_msg = atom_send_msg,
+ .get_mailbox_offset = atom_get_mailbox_offset,
+ .get_window_offset = atom_get_window_offset,
- .ipc_msg_data = intel_ipc_msg_data,
- .ipc_pcm_params = intel_ipc_pcm_params,
+ .ipc_msg_data = sof_ipc_msg_data,
+ .set_stream_data_offset = sof_set_stream_data_offset,
/* machine driver */
- .machine_select = byt_machine_select,
+ .machine_select = atom_machine_select,
.machine_register = sof_machine_register,
.machine_unregister = sof_machine_unregister,
- .set_mach_params = byt_set_mach_params,
+ .set_mach_params = atom_set_mach_params,
/* debug */
.debug_map = byt_debugfs,
.debug_map_count = ARRAY_SIZE(byt_debugfs),
- .dbg_dump = byt_dump,
+ .dbg_dump = atom_dump,
+ .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem,
/* stream callbacks */
- .pcm_open = intel_pcm_open,
- .pcm_close = intel_pcm_close,
-
- /* module loading */
- .load_module = snd_sof_parse_module_memcpy,
+ .pcm_open = sof_stream_pcm_open,
+ .pcm_close = sof_stream_pcm_close,
/*Firmware loading */
.load_firmware = snd_sof_load_firmware_memcpy,
@@ -880,7 +269,7 @@ const struct snd_sof_dsp_ops sof_byt_ops = {
.resume = byt_resume,
/* DAI drivers */
- .drv = byt_dai,
+ .drv = atom_dai,
.num_drv = 3, /* we have only 3 SSPs on byt*/
/* ALSA HW info flags */
@@ -890,66 +279,62 @@ const struct snd_sof_dsp_ops sof_byt_ops = {
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_BATCH,
- .arch_ops = &sof_xtensa_arch_ops,
+ .dsp_arch_ops = &sof_xtensa_arch_ops,
};
-EXPORT_SYMBOL_NS(sof_byt_ops, SND_SOC_SOF_BAYTRAIL);
-const struct sof_intel_dsp_desc byt_chip_info = {
+static const struct sof_intel_dsp_desc byt_chip_info = {
.cores_num = 1,
- .cores_mask = 1,
+ .host_managed_cores_mask = 1,
+ .hw_ip_version = SOF_INTEL_BAYTRAIL,
};
-EXPORT_SYMBOL_NS(byt_chip_info, SND_SOC_SOF_BAYTRAIL);
/* cherrytrail and braswell ops */
-const struct snd_sof_dsp_ops sof_cht_ops = {
+static struct snd_sof_dsp_ops sof_cht_ops = {
/* device init */
.probe = byt_acpi_probe,
.remove = byt_remove,
/* DSP core boot / reset */
- .run = byt_run,
- .reset = byt_reset,
+ .run = atom_run,
+ .reset = atom_reset,
- /* Register IO */
- .write = sof_io_write,
- .read = sof_io_read,
- .write64 = sof_io_write64,
- .read64 = sof_io_read64,
+ /* Register IO uses direct mmio */
/* Block IO */
.block_read = sof_block_read,
.block_write = sof_block_write,
+ /* Mailbox IO */
+ .mailbox_read = sof_mailbox_read,
+ .mailbox_write = sof_mailbox_write,
+
/* doorbell */
- .irq_handler = byt_irq_handler,
- .irq_thread = byt_irq_thread,
+ .irq_handler = atom_irq_handler,
+ .irq_thread = atom_irq_thread,
/* ipc */
- .send_msg = byt_send_msg,
- .fw_ready = sof_fw_ready,
- .get_mailbox_offset = byt_get_mailbox_offset,
- .get_window_offset = byt_get_window_offset,
+ .send_msg = atom_send_msg,
+ .get_mailbox_offset = atom_get_mailbox_offset,
+ .get_window_offset = atom_get_window_offset,
- .ipc_msg_data = intel_ipc_msg_data,
- .ipc_pcm_params = intel_ipc_pcm_params,
+ .ipc_msg_data = sof_ipc_msg_data,
+ .set_stream_data_offset = sof_set_stream_data_offset,
/* machine driver */
- .machine_select = byt_machine_select,
+ .machine_select = atom_machine_select,
.machine_register = sof_machine_register,
.machine_unregister = sof_machine_unregister,
- .set_mach_params = byt_set_mach_params,
+ .set_mach_params = atom_set_mach_params,
/* debug */
.debug_map = cht_debugfs,
.debug_map_count = ARRAY_SIZE(cht_debugfs),
- .dbg_dump = byt_dump,
+ .dbg_dump = atom_dump,
+ .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem,
/* stream callbacks */
- .pcm_open = intel_pcm_open,
- .pcm_close = intel_pcm_close,
-
- /* module loading */
- .load_module = snd_sof_parse_module_memcpy,
+ .pcm_open = sof_stream_pcm_open,
+ .pcm_close = sof_stream_pcm_close,
/*Firmware loading */
.load_firmware = snd_sof_load_firmware_memcpy,
@@ -959,9 +344,9 @@ const struct snd_sof_dsp_ops sof_cht_ops = {
.resume = byt_resume,
/* DAI drivers */
- .drv = byt_dai,
+ .drv = atom_dai,
/* all 6 SSPs may be available for cherrytrail */
- .num_drv = ARRAY_SIZE(byt_dai),
+ .num_drv = 6,
/* ALSA HW info flags */
.hw_info = SNDRV_PCM_INFO_MMAP |
@@ -970,18 +355,127 @@ const struct snd_sof_dsp_ops sof_cht_ops = {
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_BATCH,
- .arch_ops = &sof_xtensa_arch_ops,
+ .dsp_arch_ops = &sof_xtensa_arch_ops,
};
-EXPORT_SYMBOL_NS(sof_cht_ops, SND_SOC_SOF_BAYTRAIL);
-const struct sof_intel_dsp_desc cht_chip_info = {
+static const struct sof_intel_dsp_desc cht_chip_info = {
.cores_num = 1,
- .cores_mask = 1,
+ .host_managed_cores_mask = 1,
+ .hw_ip_version = SOF_INTEL_BAYTRAIL,
+};
+
+/* BYTCR uses different IRQ index */
+static const struct sof_dev_desc sof_acpi_baytrailcr_desc = {
+ .machines = snd_soc_acpi_intel_baytrail_machines,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = 1,
+ .resindex_imr_base = 2,
+ .irqindex_host_ipc = 0,
+ .chip_info = &byt_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-byt.ri",
+ },
+ .nocodec_tplg_filename = "sof-byt-nocodec.tplg",
+ .ops = &sof_byt_ops,
+};
+
+static const struct sof_dev_desc sof_acpi_baytrail_desc = {
+ .machines = snd_soc_acpi_intel_baytrail_machines,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = 1,
+ .resindex_imr_base = 2,
+ .irqindex_host_ipc = 5,
+ .chip_info = &byt_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-byt.ri",
+ },
+ .nocodec_tplg_filename = "sof-byt-nocodec.tplg",
+ .ops = &sof_byt_ops,
+};
+
+static const struct sof_dev_desc sof_acpi_cherrytrail_desc = {
+ .machines = snd_soc_acpi_intel_cherrytrail_machines,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = 1,
+ .resindex_imr_base = 2,
+ .irqindex_host_ipc = 5,
+ .chip_info = &cht_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-cht.ri",
+ },
+ .nocodec_tplg_filename = "sof-cht-nocodec.tplg",
+ .ops = &sof_cht_ops,
+};
+
+static const struct acpi_device_id sof_baytrail_match[] = {
+ { "80860F28", (unsigned long)&sof_acpi_baytrail_desc },
+ { "808622A8", (unsigned long)&sof_acpi_cherrytrail_desc },
+ { }
};
-EXPORT_SYMBOL_NS(cht_chip_info, SND_SOC_SOF_BAYTRAIL);
+MODULE_DEVICE_TABLE(acpi, sof_baytrail_match);
-#endif /* CONFIG_SND_SOC_SOF_BAYTRAIL */
+static int sof_baytrail_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct sof_dev_desc *desc;
+ const struct acpi_device_id *id;
+ int ret;
+
+ id = acpi_match_device(dev->driver->acpi_match_table, dev);
+ if (!id)
+ return -ENODEV;
+
+ ret = snd_intel_acpi_dsp_driver_probe(dev, id->id);
+ if (ret != SND_INTEL_DSP_DRIVER_ANY && ret != SND_INTEL_DSP_DRIVER_SOF) {
+ dev_dbg(dev, "SOF ACPI driver not selected, aborting probe\n");
+ return -ENODEV;
+ }
+
+ desc = (const struct sof_dev_desc *)id->driver_data;
+ if (desc == &sof_acpi_baytrail_desc && soc_intel_is_byt_cr(pdev))
+ desc = &sof_acpi_baytrailcr_desc;
+
+ return sof_acpi_probe(pdev, desc);
+}
+
+/* acpi_driver definition */
+static struct platform_driver snd_sof_acpi_intel_byt_driver = {
+ .probe = sof_baytrail_probe,
+ .remove_new = sof_acpi_remove,
+ .driver = {
+ .name = "sof-audio-acpi-intel-byt",
+ .pm = &sof_acpi_pm,
+ .acpi_match_table = sof_baytrail_match,
+ },
+};
+module_platform_driver(snd_sof_acpi_intel_byt_driver);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_HIFI_EP_IPC);
MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA);
+MODULE_IMPORT_NS(SND_SOC_SOF_ACPI_DEV);
+MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_ATOM_HIFI_EP);
diff --git a/sound/soc/sof/intel/cnl.c b/sound/soc/sof/intel/cnl.c
index 16db0f50d139..85e1e4760d0e 100644
--- a/sound/soc/sof/intel/cnl.c
+++ b/sound/soc/sof/intel/cnl.c
@@ -15,6 +15,10 @@
* Hardware interface for audio DSP on Cannonlake.
*/
+#include <sound/sof/ext_manifest4.h>
+#include <sound/sof/ipc4/header.h>
+#include <trace/events/sof_intel.h>
+#include "../ipc4-priv.h"
#include "../ops.h"
#include "hda.h"
#include "hda-ipc.h"
@@ -29,7 +33,85 @@ static const struct snd_sof_debugfs_map cnl_dsp_debugfs[] = {
static void cnl_ipc_host_done(struct snd_sof_dev *sdev);
static void cnl_ipc_dsp_done(struct snd_sof_dev *sdev);
-static irqreturn_t cnl_ipc_irq_thread(int irq, void *context)
+irqreturn_t cnl_ipc4_irq_thread(int irq, void *context)
+{
+ struct sof_ipc4_msg notification_data = {{ 0 }};
+ struct snd_sof_dev *sdev = context;
+ bool ack_received = false;
+ bool ipc_irq = false;
+ u32 hipcida, hipctdr;
+
+ hipcida = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDA);
+ hipctdr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCTDR);
+ if (hipcida & CNL_DSP_REG_HIPCIDA_DONE) {
+ /* DSP received the message */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
+ CNL_DSP_REG_HIPCCTL,
+ CNL_DSP_REG_HIPCCTL_DONE, 0);
+ cnl_ipc_dsp_done(sdev);
+
+ ipc_irq = true;
+ ack_received = true;
+ }
+
+ if (hipctdr & CNL_DSP_REG_HIPCTDR_BUSY) {
+ /* Message from DSP (reply or notification) */
+ u32 hipctdd = snd_sof_dsp_read(sdev, HDA_DSP_BAR,
+ CNL_DSP_REG_HIPCTDD);
+ u32 primary = hipctdr & CNL_DSP_REG_HIPCTDR_MSG_MASK;
+ u32 extension = hipctdd & CNL_DSP_REG_HIPCTDD_MSG_MASK;
+
+ if (primary & SOF_IPC4_MSG_DIR_MASK) {
+ /* Reply received */
+ if (likely(sdev->fw_state == SOF_FW_BOOT_COMPLETE)) {
+ struct sof_ipc4_msg *data = sdev->ipc->msg.reply_data;
+
+ data->primary = primary;
+ data->extension = extension;
+
+ spin_lock_irq(&sdev->ipc_lock);
+
+ snd_sof_ipc_get_reply(sdev);
+ cnl_ipc_host_done(sdev);
+ snd_sof_ipc_reply(sdev, data->primary);
+
+ spin_unlock_irq(&sdev->ipc_lock);
+ } else {
+ dev_dbg_ratelimited(sdev->dev,
+ "IPC reply before FW_READY: %#x|%#x\n",
+ primary, extension);
+ }
+ } else {
+ /* Notification received */
+ notification_data.primary = primary;
+ notification_data.extension = extension;
+
+ sdev->ipc->msg.rx_data = &notification_data;
+ snd_sof_ipc_msgs_rx(sdev);
+ sdev->ipc->msg.rx_data = NULL;
+
+ /* Let DSP know that we have finished processing the message */
+ cnl_ipc_host_done(sdev);
+ }
+
+ ipc_irq = true;
+ }
+
+ if (!ipc_irq)
+ /* This interrupt is not shared so no need to return IRQ_NONE. */
+ dev_dbg_ratelimited(sdev->dev, "nothing to do in IPC IRQ thread\n");
+
+ if (ack_received) {
+ struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
+
+ if (hdev->delayed_ipc_tx_msg)
+ cnl_ipc4_send_msg(sdev, hdev->delayed_ipc_tx_msg);
+ }
+
+ return IRQ_HANDLED;
+}
+
+irqreturn_t cnl_ipc_irq_thread(int irq, void *context)
{
struct snd_sof_dev *sdev = context;
u32 hipci;
@@ -50,24 +132,27 @@ static irqreturn_t cnl_ipc_irq_thread(int irq, void *context)
msg_ext = hipci & CNL_DSP_REG_HIPCIDR_MSG_MASK;
msg = hipcida & CNL_DSP_REG_HIPCIDA_MSG_MASK;
- dev_vdbg(sdev->dev,
- "ipc: firmware response, msg:0x%x, msg_ext:0x%x\n",
- msg, msg_ext);
+ trace_sof_intel_ipc_firmware_response(sdev, msg, msg_ext);
/* mask Done interrupt */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
CNL_DSP_REG_HIPCCTL,
CNL_DSP_REG_HIPCCTL_DONE, 0);
- spin_lock_irq(&sdev->ipc_lock);
+ if (likely(sdev->fw_state == SOF_FW_BOOT_COMPLETE)) {
+ spin_lock_irq(&sdev->ipc_lock);
- /* handle immediate reply from DSP core */
- hda_dsp_ipc_get_reply(sdev);
- snd_sof_ipc_reply(sdev, msg);
+ /* handle immediate reply from DSP core */
+ hda_dsp_ipc_get_reply(sdev);
+ snd_sof_ipc_reply(sdev, msg);
- cnl_ipc_dsp_done(sdev);
+ cnl_ipc_dsp_done(sdev);
- spin_unlock_irq(&sdev->ipc_lock);
+ spin_unlock_irq(&sdev->ipc_lock);
+ } else {
+ dev_dbg_ratelimited(sdev->dev, "IPC reply before FW_READY: %#x\n",
+ msg);
+ }
ipc_irq = true;
}
@@ -77,14 +162,27 @@ static irqreturn_t cnl_ipc_irq_thread(int irq, void *context)
msg = hipctdr & CNL_DSP_REG_HIPCTDR_MSG_MASK;
msg_ext = hipctdd & CNL_DSP_REG_HIPCTDD_MSG_MASK;
- dev_vdbg(sdev->dev,
- "ipc: firmware initiated, msg:0x%x, msg_ext:0x%x\n",
- msg, msg_ext);
+ trace_sof_intel_ipc_firmware_initiated(sdev, msg, msg_ext);
/* handle messages from DSP */
- if ((hipctdr & SOF_IPC_PANIC_MAGIC_MASK) ==
- SOF_IPC_PANIC_MAGIC) {
- snd_sof_dsp_panic(sdev, HDA_DSP_PANIC_OFFSET(msg_ext));
+ if ((hipctdr & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) {
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+ bool non_recoverable = true;
+
+ /*
+ * This is a PANIC message!
+ *
+ * If it is arriving during firmware boot and it is not
+ * the last boot attempt then change the non_recoverable
+ * to false as the DSP might be able to boot in the next
+ * iteration(s)
+ */
+ if (sdev->fw_state == SOF_FW_BOOT_IN_PROGRESS &&
+ hda->boot_iteration < HDA_FW_BOOT_ATTEMPTS)
+ non_recoverable = false;
+
+ snd_sof_dsp_panic(sdev, HDA_DSP_PANIC_OFFSET(msg_ext),
+ non_recoverable);
} else {
snd_sof_ipc_msgs_rx(sdev);
}
@@ -146,11 +244,9 @@ static void cnl_ipc_dsp_done(struct snd_sof_dev *sdev)
static bool cnl_compact_ipc_compress(struct snd_sof_ipc_msg *msg,
u32 *dr, u32 *dd)
{
- struct sof_ipc_pm_gate *pm_gate;
-
- if (msg->header == (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_GATE)) {
- pm_gate = msg->msg_data;
+ struct sof_ipc_pm_gate *pm_gate = msg->msg_data;
+ if (pm_gate->hdr.cmd == (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_GATE)) {
/* send the compact message via the primary register */
*dr = HDA_IPC_MSG_COMPACT | HDA_IPC_PM_GATE;
@@ -163,8 +259,33 @@ static bool cnl_compact_ipc_compress(struct snd_sof_ipc_msg *msg,
return false;
}
-static int cnl_ipc_send_msg(struct snd_sof_dev *sdev,
- struct snd_sof_ipc_msg *msg)
+int cnl_ipc4_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
+{
+ struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
+ struct sof_ipc4_msg *msg_data = msg->msg_data;
+
+ if (hda_ipc4_tx_is_busy(sdev)) {
+ hdev->delayed_ipc_tx_msg = msg;
+ return 0;
+ }
+
+ hdev->delayed_ipc_tx_msg = NULL;
+
+ /* send the message via mailbox */
+ if (msg_data->data_size)
+ sof_mailbox_write(sdev, sdev->host_box.offset, msg_data->data_ptr,
+ msg_data->data_size);
+
+ snd_sof_dsp_write(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDD, msg_data->extension);
+ snd_sof_dsp_write(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDR,
+ msg_data->primary | CNL_DSP_REG_HIPCIDR_BUSY);
+
+ hda_dsp_ipc4_schedule_d0i3_work(hdev, msg);
+
+ return 0;
+}
+
+int cnl_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
{
struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
struct sof_ipc_cmd_hdr *hdr;
@@ -202,7 +323,7 @@ static int cnl_ipc_send_msg(struct snd_sof_dev *sdev,
* IPCs are sent at a high-rate. mod_delayed_work()
* modifies the timer if the work is pending.
* Also, a new delayed work should not be queued after the
- * the CTX_SAVE IPC, which is sent before the DSP enters D3.
+ * CTX_SAVE IPC, which is sent before the DSP enters D3.
*/
if (hdr->cmd != (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CTX_SAVE))
mod_delayed_work(system_wq, &hdev->d0i3_work,
@@ -211,7 +332,7 @@ static int cnl_ipc_send_msg(struct snd_sof_dev *sdev,
return 0;
}
-static void cnl_ipc_dump(struct snd_sof_dev *sdev)
+void cnl_ipc_dump(struct snd_sof_dev *sdev)
{
u32 hipcctl;
u32 hipcida;
@@ -231,189 +352,161 @@ static void cnl_ipc_dump(struct snd_sof_dev *sdev)
hipcida, hipctdr, hipcctl);
}
-/* cannonlake ops */
-const struct snd_sof_dsp_ops sof_cnl_ops = {
- /* probe and remove */
- .probe = hda_dsp_probe,
- .remove = hda_dsp_remove,
+void cnl_ipc4_dump(struct snd_sof_dev *sdev)
+{
+ u32 hipcidr, hipcidd, hipcida, hipctdr, hipctdd, hipctda, hipcctl;
+
+ hda_ipc_irq_dump(sdev);
+
+ hipcidr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDR);
+ hipcidd = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDD);
+ hipcida = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDA);
+ hipctdr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCTDR);
+ hipctdd = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCTDD);
+ hipctda = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCTDA);
+ hipcctl = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCCTL);
+
+ /* dump the IPC regs */
+ /* TODO: parse the raw msg */
+ dev_err(sdev->dev,
+ "Host IPC initiator: %#x|%#x|%#x, target: %#x|%#x|%#x, ctl: %#x\n",
+ hipcidr, hipcidd, hipcida, hipctdr, hipctdd, hipctda, hipcctl);
+}
- /* Register IO */
- .write = sof_io_write,
- .read = sof_io_read,
- .write64 = sof_io_write64,
- .read64 = sof_io_read64,
+/* cannonlake ops */
+struct snd_sof_dsp_ops sof_cnl_ops;
+EXPORT_SYMBOL_NS(sof_cnl_ops, SND_SOC_SOF_INTEL_HDA_COMMON);
- /* Block IO */
- .block_read = sof_block_read,
- .block_write = sof_block_write,
+int sof_cnl_ops_init(struct snd_sof_dev *sdev)
+{
+ /* common defaults */
+ memcpy(&sof_cnl_ops, &sof_hda_common_ops, sizeof(struct snd_sof_dsp_ops));
- /* doorbell */
- .irq_thread = cnl_ipc_irq_thread,
+ /* probe/remove/shutdown */
+ sof_cnl_ops.shutdown = hda_dsp_shutdown;
/* ipc */
- .send_msg = cnl_ipc_send_msg,
- .fw_ready = sof_fw_ready,
- .get_mailbox_offset = hda_dsp_ipc_get_mailbox_offset,
- .get_window_offset = hda_dsp_ipc_get_window_offset,
+ if (sdev->pdata->ipc_type == SOF_IPC_TYPE_3) {
+ /* doorbell */
+ sof_cnl_ops.irq_thread = cnl_ipc_irq_thread;
+
+ /* ipc */
+ sof_cnl_ops.send_msg = cnl_ipc_send_msg;
+
+ /* debug */
+ sof_cnl_ops.ipc_dump = cnl_ipc_dump;
+
+ sof_cnl_ops.set_power_state = hda_dsp_set_power_state_ipc3;
+ }
+
+ if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4) {
+ struct sof_ipc4_fw_data *ipc4_data;
+
+ sdev->private = kzalloc(sizeof(*ipc4_data), GFP_KERNEL);
+ if (!sdev->private)
+ return -ENOMEM;
+
+ ipc4_data = sdev->private;
+ ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET;
+
+ ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_1_8;
+
+ /* External library loading support */
+ ipc4_data->load_library = hda_dsp_ipc4_load_library;
+
+ /* doorbell */
+ sof_cnl_ops.irq_thread = cnl_ipc4_irq_thread;
- .ipc_msg_data = hda_ipc_msg_data,
- .ipc_pcm_params = hda_ipc_pcm_params,
+ /* ipc */
+ sof_cnl_ops.send_msg = cnl_ipc4_send_msg;
- /* machine driver */
- .machine_select = hda_machine_select,
- .machine_register = sof_machine_register,
- .machine_unregister = sof_machine_unregister,
- .set_mach_params = hda_set_mach_params,
+ /* debug */
+ sof_cnl_ops.ipc_dump = cnl_ipc4_dump;
+
+ sof_cnl_ops.set_power_state = hda_dsp_set_power_state_ipc4;
+ }
+
+ /* set DAI driver ops */
+ hda_set_dai_drv_ops(sdev, &sof_cnl_ops);
/* debug */
- .debug_map = cnl_dsp_debugfs,
- .debug_map_count = ARRAY_SIZE(cnl_dsp_debugfs),
- .dbg_dump = hda_dsp_dump,
- .ipc_dump = cnl_ipc_dump,
-
- /* stream callbacks */
- .pcm_open = hda_dsp_pcm_open,
- .pcm_close = hda_dsp_pcm_close,
- .pcm_hw_params = hda_dsp_pcm_hw_params,
- .pcm_hw_free = hda_dsp_stream_hw_free,
- .pcm_trigger = hda_dsp_pcm_trigger,
- .pcm_pointer = hda_dsp_pcm_pointer,
-
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES)
- /* probe callbacks */
- .probe_assign = hda_probe_compr_assign,
- .probe_free = hda_probe_compr_free,
- .probe_set_params = hda_probe_compr_set_params,
- .probe_trigger = hda_probe_compr_trigger,
- .probe_pointer = hda_probe_compr_pointer,
-#endif
-
- /* firmware loading */
- .load_firmware = snd_sof_load_firmware_raw,
+ sof_cnl_ops.debug_map = cnl_dsp_debugfs;
+ sof_cnl_ops.debug_map_count = ARRAY_SIZE(cnl_dsp_debugfs);
/* pre/post fw run */
- .pre_fw_run = hda_dsp_pre_fw_run,
- .post_fw_run = hda_dsp_post_fw_run,
-
- /* dsp core power up/down */
- .core_power_up = hda_dsp_enable_core,
- .core_power_down = hda_dsp_core_reset_power_down,
+ sof_cnl_ops.post_fw_run = hda_dsp_post_fw_run;
/* firmware run */
- .run = hda_dsp_cl_boot_firmware,
-
- /* trace callback */
- .trace_init = hda_dsp_trace_init,
- .trace_release = hda_dsp_trace_release,
- .trace_trigger = hda_dsp_trace_trigger,
-
- /* DAI drivers */
- .drv = skl_dai,
- .num_drv = SOF_SKL_NUM_DAIS,
-
- /* PM */
- .suspend = hda_dsp_suspend,
- .resume = hda_dsp_resume,
- .runtime_suspend = hda_dsp_runtime_suspend,
- .runtime_resume = hda_dsp_runtime_resume,
- .runtime_idle = hda_dsp_runtime_idle,
- .set_hw_params_upon_resume = hda_dsp_set_hw_params_upon_resume,
- .set_power_state = hda_dsp_set_power_state,
-
- /* ALSA HW info flags */
- .hw_info = SNDRV_PCM_INFO_MMAP |
- SNDRV_PCM_INFO_MMAP_VALID |
- SNDRV_PCM_INFO_INTERLEAVED |
- SNDRV_PCM_INFO_PAUSE |
- SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
-
- .arch_ops = &sof_xtensa_arch_ops,
+ sof_cnl_ops.run = hda_dsp_cl_boot_firmware;
+
+ /* dsp core get/put */
+ sof_cnl_ops.core_get = hda_dsp_core_get;
+
+ return 0;
};
-EXPORT_SYMBOL_NS(sof_cnl_ops, SND_SOC_SOF_INTEL_HDA_COMMON);
+EXPORT_SYMBOL_NS(sof_cnl_ops_init, SND_SOC_SOF_INTEL_HDA_COMMON);
const struct sof_intel_dsp_desc cnl_chip_info = {
/* Cannonlake */
.cores_num = 4,
.init_core_mask = 1,
- .cores_mask = HDA_DSP_CORE_MASK(0) |
- HDA_DSP_CORE_MASK(1) |
- HDA_DSP_CORE_MASK(2) |
- HDA_DSP_CORE_MASK(3),
+ .host_managed_cores_mask = GENMASK(3, 0),
.ipc_req = CNL_DSP_REG_HIPCIDR,
.ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY,
.ipc_ack = CNL_DSP_REG_HIPCIDA,
.ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE,
.ipc_ctl = CNL_DSP_REG_HIPCCTL,
+ .rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS,
.rom_init_timeout = 300,
.ssp_count = CNL_SSP_COUNT,
.ssp_base_offset = CNL_SSP_BASE_OFFSET,
+ .sdw_shim_base = SDW_SHIM_BASE,
+ .sdw_alh_base = SDW_ALH_BASE,
+ .d0i3_offset = SOF_HDA_VS_D0I3C,
+ .read_sdw_lcount = hda_sdw_check_lcount_common,
+ .enable_sdw_irq = hda_common_enable_sdw_irq,
+ .check_sdw_irq = hda_common_check_sdw_irq,
+ .check_sdw_wakeen_irq = hda_sdw_check_wakeen_irq_common,
+ .check_ipc_irq = hda_dsp_check_ipc_irq,
+ .cl_init = cl_dsp_init,
+ .power_down_dsp = hda_power_down_dsp,
+ .disable_interrupts = hda_dsp_disable_interrupts,
+ .hw_ip_version = SOF_INTEL_CAVS_1_8,
};
EXPORT_SYMBOL_NS(cnl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
-const struct sof_intel_dsp_desc icl_chip_info = {
- /* Icelake */
- .cores_num = 4,
- .init_core_mask = 1,
- .cores_mask = HDA_DSP_CORE_MASK(0) |
- HDA_DSP_CORE_MASK(1) |
- HDA_DSP_CORE_MASK(2) |
- HDA_DSP_CORE_MASK(3),
- .ipc_req = CNL_DSP_REG_HIPCIDR,
- .ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY,
- .ipc_ack = CNL_DSP_REG_HIPCIDA,
- .ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE,
- .ipc_ctl = CNL_DSP_REG_HIPCCTL,
- .rom_init_timeout = 300,
- .ssp_count = ICL_SSP_COUNT,
- .ssp_base_offset = CNL_SSP_BASE_OFFSET,
-};
-EXPORT_SYMBOL_NS(icl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
-
-const struct sof_intel_dsp_desc tgl_chip_info = {
- /* Tigerlake */
- .cores_num = 4,
- .init_core_mask = 1,
- .cores_mask = HDA_DSP_CORE_MASK(0),
- .ipc_req = CNL_DSP_REG_HIPCIDR,
- .ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY,
- .ipc_ack = CNL_DSP_REG_HIPCIDA,
- .ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE,
- .ipc_ctl = CNL_DSP_REG_HIPCCTL,
- .rom_init_timeout = 300,
- .ssp_count = ICL_SSP_COUNT,
- .ssp_base_offset = CNL_SSP_BASE_OFFSET,
-};
-EXPORT_SYMBOL_NS(tgl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
-
-const struct sof_intel_dsp_desc ehl_chip_info = {
- /* Elkhartlake */
- .cores_num = 4,
- .init_core_mask = 1,
- .cores_mask = HDA_DSP_CORE_MASK(0),
- .ipc_req = CNL_DSP_REG_HIPCIDR,
- .ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY,
- .ipc_ack = CNL_DSP_REG_HIPCIDA,
- .ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE,
- .ipc_ctl = CNL_DSP_REG_HIPCCTL,
- .rom_init_timeout = 300,
- .ssp_count = ICL_SSP_COUNT,
- .ssp_base_offset = CNL_SSP_BASE_OFFSET,
-};
-EXPORT_SYMBOL_NS(ehl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
-
+/*
+ * JasperLake is technically derived from IceLake, and should be in
+ * described in icl.c. However since JasperLake was designed with
+ * two cores, it cannot support the IceLake-specific power-up sequences
+ * which rely on core3. To simplify, JasperLake uses the CannonLake ops and
+ * is described in cnl.c
+ */
const struct sof_intel_dsp_desc jsl_chip_info = {
/* Jasperlake */
.cores_num = 2,
.init_core_mask = 1,
- .cores_mask = HDA_DSP_CORE_MASK(0) |
- HDA_DSP_CORE_MASK(1),
+ .host_managed_cores_mask = GENMASK(1, 0),
.ipc_req = CNL_DSP_REG_HIPCIDR,
.ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY,
.ipc_ack = CNL_DSP_REG_HIPCIDA,
.ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE,
.ipc_ctl = CNL_DSP_REG_HIPCCTL,
+ .rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS,
.rom_init_timeout = 300,
.ssp_count = ICL_SSP_COUNT,
.ssp_base_offset = CNL_SSP_BASE_OFFSET,
+ .sdw_shim_base = SDW_SHIM_BASE,
+ .sdw_alh_base = SDW_ALH_BASE,
+ .d0i3_offset = SOF_HDA_VS_D0I3C,
+ .read_sdw_lcount = hda_sdw_check_lcount_common,
+ .enable_sdw_irq = hda_common_enable_sdw_irq,
+ .check_sdw_irq = hda_common_check_sdw_irq,
+ .check_sdw_wakeen_irq = hda_sdw_check_wakeen_irq_common,
+ .check_ipc_irq = hda_dsp_check_ipc_irq,
+ .cl_init = cl_dsp_init,
+ .power_down_dsp = hda_power_down_dsp,
+ .disable_interrupts = hda_dsp_disable_interrupts,
+ .hw_ip_version = SOF_INTEL_CAVS_2_0,
};
EXPORT_SYMBOL_NS(jsl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
diff --git a/sound/soc/sof/intel/ext_manifest.h b/sound/soc/sof/intel/ext_manifest.h
new file mode 100644
index 000000000000..2dfae9285d3c
--- /dev/null
+++ b/sound/soc/sof/intel/ext_manifest.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/*
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * Copyright(c) 2020 Intel Corporation. All rights reserved.
+ */
+
+/*
+ * Intel extended manifest is a extra place to store Intel cavs specific
+ * metadata about firmware, for example LPRO/HPRO configuration is
+ * Intel cavs specific. This part of output binary is not signed.
+ */
+
+#ifndef __INTEL_CAVS_EXT_MANIFEST_H__
+#define __INTEL_CAVS_EXT_MANIFEST_H__
+
+#include <sound/sof/ext_manifest.h>
+
+/* EXT_MAN_ELEM_PLATFORM_CONFIG_DATA elements identificators */
+enum sof_cavs_config_elem_type {
+ SOF_EXT_MAN_CAVS_CONFIG_EMPTY = 0,
+ SOF_EXT_MAN_CAVS_CONFIG_CAVS_LPRO = 1,
+ SOF_EXT_MAN_CAVS_CONFIG_OUTBOX_SIZE = 2,
+ SOF_EXT_MAN_CAVS_CONFIG_INBOX_SIZE = 3,
+};
+
+/* EXT_MAN_ELEM_PLATFORM_CONFIG_DATA elements */
+struct sof_ext_man_cavs_config_data {
+ struct sof_ext_man_elem_header hdr;
+
+ struct sof_config_elem elems[];
+} __packed;
+
+#endif /* __INTEL_CAVS_EXT_MANIFEST_H__ */
diff --git a/sound/soc/sof/intel/hda-bus.c b/sound/soc/sof/intel/hda-bus.c
index 789148e5584b..fc63085d2d74 100644
--- a/sound/soc/sof/intel/hda-bus.c
+++ b/sound/soc/sof/intel/hda-bus.c
@@ -9,24 +9,75 @@
#include <linux/io.h>
#include <sound/hdaudio.h>
+#include <sound/hda_i915.h>
+#include <sound/hda_codec.h>
+#include <sound/hda_register.h>
#include "../sof-priv.h"
#include "hda.h"
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
#include "../../codecs/hdac_hda.h"
#define sof_hda_ext_ops snd_soc_hdac_hda_get_ops()
-#else
-#define sof_hda_ext_ops NULL
+
+static void update_codec_wake_enable(struct hdac_bus *bus, unsigned int addr, bool link_power)
+{
+ unsigned int mask = snd_hdac_chip_readw(bus, WAKEEN);
+
+ if (link_power)
+ mask &= ~BIT(addr);
+ else
+ mask |= BIT(addr);
+
+ snd_hdac_chip_updatew(bus, WAKEEN, STATESTS_INT_MASK, mask);
+}
+
+static void sof_hda_bus_link_power(struct hdac_device *codec, bool enable)
+{
+ struct hdac_bus *bus = codec->bus;
+ bool oldstate = test_bit(codec->addr, &bus->codec_powered);
+
+ snd_hdac_ext_bus_link_power(codec, enable);
+
+ if (enable == oldstate)
+ return;
+
+ /*
+ * Both codec driver and controller can hold references to
+ * display power. To avoid unnecessary power-up/down cycles,
+ * controller doesn't immediately release its reference.
+ *
+ * If the codec driver powers down the link, release
+ * the controller reference as well.
+ */
+ if (codec->addr == HDA_IDISP_ADDR && !enable)
+ snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, false);
+
+ /* WAKEEN needs to be set for disabled links */
+ update_codec_wake_enable(bus, codec->addr, enable);
+}
+
+static const struct hdac_bus_ops bus_core_ops = {
+ .command = snd_hdac_bus_send_cmd,
+ .get_response = snd_hdac_bus_get_response,
+ .link_power = sof_hda_bus_link_power,
+};
#endif
/*
* This can be used for both with/without hda link support.
*/
-void sof_hda_bus_init(struct hdac_bus *bus, struct device *dev)
+void sof_hda_bus_init(struct snd_sof_dev *sdev, struct device *dev)
{
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
- snd_hdac_ext_bus_init(bus, dev, NULL, sof_hda_ext_ops);
-#else /* CONFIG_SND_SOC_SOF_HDA */
+ struct hdac_bus *bus = sof_to_bus(sdev);
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_LINK)
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
+ snd_hdac_ext_bus_init(bus, dev, &bus_core_ops, sof_hda_ext_ops);
+#else
+ snd_hdac_ext_bus_init(bus, dev, NULL, NULL);
+#endif
+#else
+
memset(bus, 0, sizeof(*bus));
bus->dev = dev;
@@ -41,5 +92,14 @@ void sof_hda_bus_init(struct hdac_bus *bus, struct device *dev)
bus->idx = 0;
spin_lock_init(&bus->reg_lock);
-#endif /* CONFIG_SND_SOC_SOF_HDA */
+#endif /* CONFIG_SND_SOC_SOF_HDA_LINK */
+}
+
+void sof_hda_bus_exit(struct snd_sof_dev *sdev)
+{
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_LINK)
+ struct hdac_bus *bus = sof_to_bus(sdev);
+
+ snd_hdac_ext_bus_exit(bus);
+#endif
}
diff --git a/sound/soc/sof/intel/hda-codec.c b/sound/soc/sof/intel/hda-codec.c
index 2c5c451fa19d..9f84b0d287a5 100644
--- a/sound/soc/sof/intel/hda-codec.c
+++ b/sound/soc/sof/intel/hda-codec.c
@@ -1,7 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
-//
-// This file is provided under a dual BSD/GPLv2 license. When using or
-// redistributing this file, you may do so under either license.
+// SPDX-License-Identifier: GPL-2.0-only
//
// Copyright(c) 2018 Intel Corporation. All rights reserved.
//
@@ -16,13 +13,18 @@
#include <sound/sof.h>
#include "../ops.h"
#include "hda.h"
+
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
#include "../../codecs/hdac_hda.h"
-#endif /* CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC */
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
+#define CODEC_PROBE_RETRIES 3
+
#define IDISP_VID_INTEL 0x80860000
+static int hda_codec_mask = -1;
+module_param_named(codec_mask, hda_codec_mask, int, 0444);
+MODULE_PARM_DESC(codec_mask, "SOF HDA codec mask for probing");
+
/* load the legacy HDA codec driver */
static int request_codec_module(struct hda_codec *codec)
{
@@ -52,8 +54,16 @@ static int request_codec_module(struct hda_codec *codec)
static int hda_codec_load_module(struct hda_codec *codec)
{
- int ret = request_codec_module(codec);
+ int ret;
+ ret = snd_hdac_device_register(&codec->core);
+ if (ret) {
+ dev_err(&codec->core.dev, "failed to register hdac device\n");
+ put_device(&codec->core.dev);
+ return ret;
+ }
+
+ ret = request_codec_module(codec);
if (ret <= 0) {
codec->probe_id = HDA_CODEC_ID_GENERIC;
ret = request_codec_module(codec);
@@ -63,29 +73,36 @@ static int hda_codec_load_module(struct hda_codec *codec)
}
/* enable controller wake up event for all codecs with jack connectors */
-void hda_codec_jack_wake_enable(struct snd_sof_dev *sdev)
+void hda_codec_jack_wake_enable(struct snd_sof_dev *sdev, bool enable)
{
struct hda_bus *hbus = sof_to_hbus(sdev);
struct hdac_bus *bus = sof_to_bus(sdev);
struct hda_codec *codec;
unsigned int mask = 0;
- list_for_each_codec(codec, hbus)
- if (codec->jacktbl.used)
- mask |= BIT(codec->core.addr);
+ if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
+ sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
+ return;
+
+ if (enable) {
+ list_for_each_codec(codec, hbus)
+ if (codec->jacktbl.used)
+ mask |= BIT(codec->core.addr);
+ }
snd_hdac_chip_updatew(bus, WAKEEN, STATESTS_INT_MASK, mask);
}
+EXPORT_SYMBOL_NS_GPL(hda_codec_jack_wake_enable, SND_SOC_SOF_HDA_AUDIO_CODEC);
/* check jack status after resuming from suspend mode */
void hda_codec_jack_check(struct snd_sof_dev *sdev)
{
struct hda_bus *hbus = sof_to_hbus(sdev);
- struct hdac_bus *bus = sof_to_bus(sdev);
struct hda_codec *codec;
- /* disable controller Wake Up event*/
- snd_hdac_chip_updatew(bus, WAKEEN, STATESTS_INT_MASK, 0);
+ if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
+ sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
+ return;
list_for_each_codec(codec, hbus)
/*
@@ -93,15 +110,9 @@ void hda_codec_jack_check(struct snd_sof_dev *sdev)
* has been recorded in STATESTS
*/
if (codec->jacktbl.used)
- schedule_delayed_work(&codec->jackpoll_work,
- codec->jackpoll_interval);
+ pm_request_resume(&codec->core.dev);
}
-#else
-void hda_codec_jack_wake_enable(struct snd_sof_dev *sdev) {}
-void hda_codec_jack_check(struct snd_sof_dev *sdev) {}
-#endif /* CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC */
-EXPORT_SYMBOL_NS(hda_codec_jack_wake_enable, SND_SOC_SOF_HDA_AUDIO_CODEC);
-EXPORT_SYMBOL_NS(hda_codec_jack_check, SND_SOC_SOF_HDA_AUDIO_CODEC);
+EXPORT_SYMBOL_NS_GPL(hda_codec_jack_check, SND_SOC_SOF_HDA_AUDIO_CODEC);
#if IS_ENABLED(CONFIG_SND_HDA_GENERIC)
#define is_generic_config(bus) \
@@ -110,48 +121,63 @@ EXPORT_SYMBOL_NS(hda_codec_jack_check, SND_SOC_SOF_HDA_AUDIO_CODEC);
#define is_generic_config(x) 0
#endif
+static struct hda_codec *hda_codec_device_init(struct hdac_bus *bus, int addr, int type)
+{
+ struct hda_codec *codec;
+
+ codec = snd_hda_codec_device_init(to_hda_bus(bus), addr, "ehdaudio%dD%d", bus->idx, addr);
+ if (IS_ERR(codec)) {
+ dev_err(bus->dev, "device init failed for hdac device\n");
+ return codec;
+ }
+
+ codec->core.type = type;
+
+ return codec;
+}
+
/* probe individual codec */
-static int hda_codec_probe(struct snd_sof_dev *sdev, int address,
- bool hda_codec_use_common_hdmi)
+static int hda_codec_probe(struct snd_sof_dev *sdev, int address)
{
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
struct hdac_hda_priv *hda_priv;
-#endif
struct hda_bus *hbus = sof_to_hbus(sdev);
- struct hdac_device *hdev;
struct hda_codec *codec;
u32 hda_cmd = (address << 28) | (AC_NODE_ROOT << 20) |
(AC_VERB_PARAMETERS << 8) | AC_PAR_VENDOR_ID;
u32 resp = -1;
- int ret;
+ int ret, retry = 0;
+
+ do {
+ mutex_lock(&hbus->core.cmd_mutex);
+ snd_hdac_bus_send_cmd(&hbus->core, hda_cmd);
+ snd_hdac_bus_get_response(&hbus->core, address, &resp);
+ mutex_unlock(&hbus->core.cmd_mutex);
+ } while (resp == -1 && retry++ < CODEC_PROBE_RETRIES);
- mutex_lock(&hbus->core.cmd_mutex);
- snd_hdac_bus_send_cmd(&hbus->core, hda_cmd);
- snd_hdac_bus_get_response(&hbus->core, address, &resp);
- mutex_unlock(&hbus->core.cmd_mutex);
if (resp == -1)
return -EIO;
dev_dbg(sdev->dev, "HDA codec #%d probed OK: response: %x\n",
address, resp);
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
hda_priv = devm_kzalloc(sdev->dev, sizeof(*hda_priv), GFP_KERNEL);
if (!hda_priv)
return -ENOMEM;
- hda_priv->codec.bus = hbus;
- hdev = &hda_priv->codec.core;
- codec = &hda_priv->codec;
-
- ret = snd_hdac_ext_bus_device_init(&hbus->core, address, hdev);
+ codec = hda_codec_device_init(&hbus->core, address, HDA_DEV_LEGACY);
+ ret = PTR_ERR_OR_ZERO(codec);
if (ret < 0)
return ret;
+ hda_priv->codec = codec;
+ hda_priv->dev_index = address;
+ dev_set_drvdata(&codec->core.dev, hda_priv);
+
if ((resp & 0xFFFF0000) == IDISP_VID_INTEL) {
- if (!hdev->bus->audio_component) {
+ if (!hbus->core.audio_component) {
dev_dbg(sdev->dev,
"iDisp hw present but no driver\n");
- return -ENOENT;
+ ret = -ENOENT;
+ goto out;
}
hda_priv->need_display_power = true;
}
@@ -161,48 +187,40 @@ static int hda_codec_probe(struct snd_sof_dev *sdev, int address,
else
codec->probe_id = 0;
+ ret = hda_codec_load_module(codec);
/*
- * if common HDMI codec driver is not used, codec load
- * is skipped here and hdac_hdmi is used instead
+ * handle ret==0 (no driver bound) as an error, but pass
+ * other return codes without modification
*/
- if (hda_codec_use_common_hdmi ||
- (resp & 0xFFFF0000) != IDISP_VID_INTEL) {
- hdev->type = HDA_DEV_LEGACY;
- ret = hda_codec_load_module(codec);
- /*
- * handle ret==0 (no driver bound) as an error, but pass
- * other return codes without modification
- */
- if (ret == 0)
- ret = -ENOENT;
- }
+ if (ret == 0)
+ ret = -ENOENT;
- return ret;
-#else
- hdev = devm_kzalloc(sdev->dev, sizeof(*hdev), GFP_KERNEL);
- if (!hdev)
- return -ENOMEM;
-
- ret = snd_hdac_ext_bus_device_init(&hbus->core, address, hdev);
+out:
+ if (ret < 0) {
+ snd_hdac_device_unregister(&codec->core);
+ put_device(&codec->core.dev);
+ }
return ret;
-#endif
}
/* Codec initialization */
-void hda_codec_probe_bus(struct snd_sof_dev *sdev,
- bool hda_codec_use_common_hdmi)
+void hda_codec_probe_bus(struct snd_sof_dev *sdev)
{
struct hdac_bus *bus = sof_to_bus(sdev);
int i, ret;
+ if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
+ sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
+ return;
+
/* probe codecs in avail slots */
for (i = 0; i < HDA_MAX_CODECS; i++) {
if (!(bus->codec_mask & (1 << i)))
continue;
- ret = hda_codec_probe(sdev, i, hda_codec_use_common_hdmi);
+ ret = hda_codec_probe(sdev, i);
if (ret < 0) {
dev_warn(bus->dev, "codec #%d probe error, ret: %d\n",
i, ret);
@@ -210,27 +228,193 @@ void hda_codec_probe_bus(struct snd_sof_dev *sdev,
}
}
}
-EXPORT_SYMBOL_NS(hda_codec_probe_bus, SND_SOC_SOF_HDA_AUDIO_CODEC);
+EXPORT_SYMBOL_NS_GPL(hda_codec_probe_bus, SND_SOC_SOF_HDA_AUDIO_CODEC);
+
+void hda_codec_check_for_state_change(struct snd_sof_dev *sdev)
+{
+ struct hdac_bus *bus = sof_to_bus(sdev);
+ unsigned int codec_mask;
+
+ codec_mask = snd_hdac_chip_readw(bus, STATESTS);
+ if (codec_mask) {
+ hda_codec_jack_check(sdev);
+ snd_hdac_chip_writew(bus, STATESTS, codec_mask);
+ }
+}
+EXPORT_SYMBOL_NS_GPL(hda_codec_check_for_state_change, SND_SOC_SOF_HDA_AUDIO_CODEC);
+
+void hda_codec_detect_mask(struct snd_sof_dev *sdev)
+{
+ struct hdac_bus *bus = sof_to_bus(sdev);
-#if IS_ENABLED(CONFIG_SND_HDA_CODEC_HDMI) || \
- IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI)
+ if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
+ sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
+ return;
+
+ /* Accept unsolicited responses */
+ snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_UNSOL, AZX_GCTL_UNSOL);
+
+ /* detect codecs */
+ if (!bus->codec_mask) {
+ bus->codec_mask = snd_hdac_chip_readw(bus, STATESTS);
+ dev_dbg(bus->dev, "codec_mask = 0x%lx\n", bus->codec_mask);
+ }
+
+ if (hda_codec_mask != -1) {
+ bus->codec_mask &= hda_codec_mask;
+ dev_dbg(bus->dev, "filtered codec_mask = 0x%lx\n",
+ bus->codec_mask);
+ }
+}
+EXPORT_SYMBOL_NS_GPL(hda_codec_detect_mask, SND_SOC_SOF_HDA_AUDIO_CODEC);
+
+void hda_codec_init_cmd_io(struct snd_sof_dev *sdev)
+{
+ struct hdac_bus *bus = sof_to_bus(sdev);
+
+ if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
+ sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
+ return;
+
+ /* initialize the codec command I/O */
+ snd_hdac_bus_init_cmd_io(bus);
+}
+EXPORT_SYMBOL_NS_GPL(hda_codec_init_cmd_io, SND_SOC_SOF_HDA_AUDIO_CODEC);
+
+void hda_codec_resume_cmd_io(struct snd_sof_dev *sdev)
+{
+ struct hdac_bus *bus = sof_to_bus(sdev);
+
+ if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
+ sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
+ return;
+
+ /* set up CORB/RIRB buffers if was on before suspend */
+ if (bus->cmd_dma_state)
+ snd_hdac_bus_init_cmd_io(bus);
+}
+EXPORT_SYMBOL_NS_GPL(hda_codec_resume_cmd_io, SND_SOC_SOF_HDA_AUDIO_CODEC);
+
+void hda_codec_stop_cmd_io(struct snd_sof_dev *sdev)
+{
+ struct hdac_bus *bus = sof_to_bus(sdev);
+
+ if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
+ sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
+ return;
+
+ /* initialize the codec command I/O */
+ snd_hdac_bus_stop_cmd_io(bus);
+}
+EXPORT_SYMBOL_NS_GPL(hda_codec_stop_cmd_io, SND_SOC_SOF_HDA_AUDIO_CODEC);
+
+void hda_codec_suspend_cmd_io(struct snd_sof_dev *sdev)
+{
+ struct hdac_bus *bus = sof_to_bus(sdev);
+
+ if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
+ sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
+ return;
+
+ /* stop the CORB/RIRB DMA if it is On */
+ if (bus->cmd_dma_state)
+ snd_hdac_bus_stop_cmd_io(bus);
+
+}
+EXPORT_SYMBOL_NS_GPL(hda_codec_suspend_cmd_io, SND_SOC_SOF_HDA_AUDIO_CODEC);
+
+void hda_codec_rirb_status_clear(struct snd_sof_dev *sdev)
+{
+ struct hdac_bus *bus = sof_to_bus(sdev);
+
+ if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
+ sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
+ return;
+
+ /* clear rirb status */
+ snd_hdac_chip_writeb(bus, RIRBSTS, RIRB_INT_MASK);
+}
+EXPORT_SYMBOL_NS_GPL(hda_codec_rirb_status_clear, SND_SOC_SOF_HDA_AUDIO_CODEC);
+
+void hda_codec_set_codec_wakeup(struct snd_sof_dev *sdev, bool status)
+{
+ struct hdac_bus *bus = sof_to_bus(sdev);
+
+ if (sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
+ return;
+
+ snd_hdac_set_codec_wakeup(bus, status);
+}
+EXPORT_SYMBOL_NS_GPL(hda_codec_set_codec_wakeup, SND_SOC_SOF_HDA_AUDIO_CODEC);
+
+bool hda_codec_check_rirb_status(struct snd_sof_dev *sdev)
+{
+ struct hdac_bus *bus = sof_to_bus(sdev);
+ bool active = false;
+ u32 rirb_status;
+
+ if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
+ sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
+ return false;
+
+ rirb_status = snd_hdac_chip_readb(bus, RIRBSTS);
+ if (rirb_status & RIRB_INT_MASK) {
+ /*
+ * Clearing the interrupt status here ensures
+ * that no interrupt gets masked after the RIRB
+ * wp is read in snd_hdac_bus_update_rirb.
+ */
+ snd_hdac_chip_writeb(bus, RIRBSTS,
+ RIRB_INT_MASK);
+ active = true;
+ if (rirb_status & RIRB_INT_RESPONSE)
+ snd_hdac_bus_update_rirb(bus);
+ }
+ return active;
+}
+EXPORT_SYMBOL_NS_GPL(hda_codec_check_rirb_status, SND_SOC_SOF_HDA_AUDIO_CODEC);
+
+void hda_codec_device_remove(struct snd_sof_dev *sdev)
+{
+ struct hdac_bus *bus = sof_to_bus(sdev);
+
+ if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
+ sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
+ return;
+
+ /* codec removal, invoke bus_device_remove */
+ snd_hdac_ext_bus_device_remove(bus);
+}
+EXPORT_SYMBOL_NS_GPL(hda_codec_device_remove, SND_SOC_SOF_HDA_AUDIO_CODEC);
+
+#endif /* CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC */
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) && IS_ENABLED(CONFIG_SND_HDA_CODEC_HDMI)
void hda_codec_i915_display_power(struct snd_sof_dev *sdev, bool enable)
{
struct hdac_bus *bus = sof_to_bus(sdev);
+ if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
+ sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
+ return;
+
if (HDA_IDISP_CODEC(bus->codec_mask)) {
dev_dbg(bus->dev, "Turning i915 HDAC power %d\n", enable);
snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, enable);
}
}
-EXPORT_SYMBOL_NS(hda_codec_i915_display_power, SND_SOC_SOF_HDA_AUDIO_CODEC_I915);
+EXPORT_SYMBOL_NS_GPL(hda_codec_i915_display_power, SND_SOC_SOF_HDA_AUDIO_CODEC_I915);
int hda_codec_i915_init(struct snd_sof_dev *sdev)
{
struct hdac_bus *bus = sof_to_bus(sdev);
int ret;
+ if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
+ sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
+ return 0;
+
/* i915 exposes a HDA codec for HDMI audio */
ret = snd_hdac_i915_init(bus);
if (ret < 0)
@@ -241,12 +425,16 @@ int hda_codec_i915_init(struct snd_sof_dev *sdev)
return 0;
}
-EXPORT_SYMBOL_NS(hda_codec_i915_init, SND_SOC_SOF_HDA_AUDIO_CODEC_I915);
+EXPORT_SYMBOL_NS_GPL(hda_codec_i915_init, SND_SOC_SOF_HDA_AUDIO_CODEC_I915);
int hda_codec_i915_exit(struct snd_sof_dev *sdev)
{
struct hdac_bus *bus = sof_to_bus(sdev);
+ if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
+ sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
+ return 0;
+
if (!bus->audio_component)
return 0;
@@ -255,7 +443,7 @@ int hda_codec_i915_exit(struct snd_sof_dev *sdev)
return snd_hdac_i915_exit(bus);
}
-EXPORT_SYMBOL_NS(hda_codec_i915_exit, SND_SOC_SOF_HDA_AUDIO_CODEC_I915);
+EXPORT_SYMBOL_NS_GPL(hda_codec_i915_exit, SND_SOC_SOF_HDA_AUDIO_CODEC_I915);
#endif
diff --git a/sound/soc/sof/intel/hda-common-ops.c b/sound/soc/sof/intel/hda-common-ops.c
new file mode 100644
index 000000000000..2b385cddc385
--- /dev/null
+++ b/sound/soc/sof/intel/hda-common-ops.c
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2022 Intel Corporation. All rights reserved.
+//
+
+/*
+ * common ops for SKL+ HDAudio platforms
+ */
+
+#include "../sof-priv.h"
+#include "hda.h"
+#include "../sof-audio.h"
+
+struct snd_sof_dsp_ops sof_hda_common_ops = {
+ /* probe/remove/shutdown */
+ .probe_early = hda_dsp_probe_early,
+ .probe = hda_dsp_probe,
+ .remove = hda_dsp_remove,
+ .remove_late = hda_dsp_remove_late,
+
+ /* Register IO uses direct mmio */
+
+ /* Block IO */
+ .block_read = sof_block_read,
+ .block_write = sof_block_write,
+
+ /* Mailbox IO */
+ .mailbox_read = sof_mailbox_read,
+ .mailbox_write = sof_mailbox_write,
+
+ /* ipc */
+ .get_mailbox_offset = hda_dsp_ipc_get_mailbox_offset,
+ .get_window_offset = hda_dsp_ipc_get_window_offset,
+
+ .ipc_msg_data = hda_ipc_msg_data,
+ .set_stream_data_offset = hda_set_stream_data_offset,
+
+ /* machine driver */
+ .machine_select = hda_machine_select,
+ .machine_register = sof_machine_register,
+ .machine_unregister = sof_machine_unregister,
+ .set_mach_params = hda_set_mach_params,
+
+ /* debug */
+ .dbg_dump = hda_dsp_dump,
+ .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem,
+
+ /* stream callbacks */
+ .pcm_open = hda_dsp_pcm_open,
+ .pcm_close = hda_dsp_pcm_close,
+ .pcm_hw_params = hda_dsp_pcm_hw_params,
+ .pcm_hw_free = hda_dsp_stream_hw_free,
+ .pcm_trigger = hda_dsp_pcm_trigger,
+ .pcm_pointer = hda_dsp_pcm_pointer,
+ .pcm_ack = hda_dsp_pcm_ack,
+
+ /* firmware loading */
+ .load_firmware = snd_sof_load_firmware_raw,
+
+ /* pre/post fw run */
+ .pre_fw_run = hda_dsp_pre_fw_run,
+
+ /* firmware run */
+ .run = hda_dsp_cl_boot_firmware,
+
+ /* parse platform specific extended manifest */
+ .parse_platform_ext_manifest = hda_dsp_ext_man_get_cavs_config_data,
+
+ /* dsp core get/put */
+
+ /* trace callback */
+ .trace_init = hda_dsp_trace_init,
+ .trace_release = hda_dsp_trace_release,
+ .trace_trigger = hda_dsp_trace_trigger,
+
+ /* client ops */
+ .register_ipc_clients = hda_register_clients,
+ .unregister_ipc_clients = hda_unregister_clients,
+
+ /* DAI drivers */
+ .drv = skl_dai,
+ .num_drv = SOF_SKL_NUM_DAIS,
+ .is_chain_dma_supported = hda_is_chain_dma_supported,
+
+ /* PM */
+ .suspend = hda_dsp_suspend,
+ .resume = hda_dsp_resume,
+ .runtime_suspend = hda_dsp_runtime_suspend,
+ .runtime_resume = hda_dsp_runtime_resume,
+ .runtime_idle = hda_dsp_runtime_idle,
+ .set_hw_params_upon_resume = hda_dsp_set_hw_params_upon_resume,
+
+ /* ALSA HW info flags */
+ .hw_info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
+
+ .dsp_arch_ops = &sof_xtensa_arch_ops,
+};
diff --git a/sound/soc/sof/intel/hda-compress.c b/sound/soc/sof/intel/hda-compress.c
deleted file mode 100644
index 53c08034fa22..000000000000
--- a/sound/soc/sof/intel/hda-compress.c
+++ /dev/null
@@ -1,114 +0,0 @@
-// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
-//
-// This file is provided under a dual BSD/GPLv2 license. When using or
-// redistributing this file, you may do so under either license.
-//
-// Copyright(c) 2019-2020 Intel Corporation. All rights reserved.
-//
-// Author: Cezary Rojewski <cezary.rojewski@intel.com>
-//
-
-#include <sound/hdaudio_ext.h>
-#include <sound/soc.h>
-#include "../sof-priv.h"
-#include "hda.h"
-
-static inline struct hdac_ext_stream *
-hda_compr_get_stream(struct snd_compr_stream *cstream)
-{
- return cstream->runtime->private_data;
-}
-
-int hda_probe_compr_assign(struct snd_sof_dev *sdev,
- struct snd_compr_stream *cstream,
- struct snd_soc_dai *dai)
-{
- struct hdac_ext_stream *stream;
-
- stream = hda_dsp_stream_get(sdev, cstream->direction);
- if (!stream)
- return -EBUSY;
-
- hdac_stream(stream)->curr_pos = 0;
- hdac_stream(stream)->cstream = cstream;
- cstream->runtime->private_data = stream;
-
- return hdac_stream(stream)->stream_tag;
-}
-
-int hda_probe_compr_free(struct snd_sof_dev *sdev,
- struct snd_compr_stream *cstream,
- struct snd_soc_dai *dai)
-{
- struct hdac_ext_stream *stream = hda_compr_get_stream(cstream);
- int ret;
-
- ret = hda_dsp_stream_put(sdev, cstream->direction,
- hdac_stream(stream)->stream_tag);
- if (ret < 0) {
- dev_dbg(sdev->dev, "stream put failed: %d\n", ret);
- return ret;
- }
-
- hdac_stream(stream)->cstream = NULL;
- cstream->runtime->private_data = NULL;
-
- return 0;
-}
-
-int hda_probe_compr_set_params(struct snd_sof_dev *sdev,
- struct snd_compr_stream *cstream,
- struct snd_compr_params *params,
- struct snd_soc_dai *dai)
-{
- struct hdac_ext_stream *stream = hda_compr_get_stream(cstream);
- struct hdac_stream *hstream = hdac_stream(stream);
- struct snd_dma_buffer *dmab;
- u32 bits, rate;
- int bps, ret;
-
- dmab = cstream->runtime->dma_buffer_p;
- /* compr params do not store bit depth, default to S32_LE */
- bps = snd_pcm_format_physical_width(SNDRV_PCM_FORMAT_S32_LE);
- if (bps < 0)
- return bps;
- bits = hda_dsp_get_bits(sdev, bps);
- rate = hda_dsp_get_mult_div(sdev, params->codec.sample_rate);
-
- hstream->format_val = rate | bits | (params->codec.ch_out - 1);
- hstream->bufsize = cstream->runtime->buffer_size;
- hstream->period_bytes = cstream->runtime->fragment_size;
- hstream->no_period_wakeup = 0;
-
- ret = hda_dsp_stream_hw_params(sdev, stream, dmab, NULL);
- if (ret < 0) {
- dev_err(sdev->dev, "error: hdac prepare failed: %x\n", ret);
- return ret;
- }
-
- return 0;
-}
-
-int hda_probe_compr_trigger(struct snd_sof_dev *sdev,
- struct snd_compr_stream *cstream, int cmd,
- struct snd_soc_dai *dai)
-{
- struct hdac_ext_stream *stream = hda_compr_get_stream(cstream);
-
- return hda_dsp_stream_trigger(sdev, stream, cmd);
-}
-
-int hda_probe_compr_pointer(struct snd_sof_dev *sdev,
- struct snd_compr_stream *cstream,
- struct snd_compr_tstamp *tstamp,
- struct snd_soc_dai *dai)
-{
- struct hdac_ext_stream *stream = hda_compr_get_stream(cstream);
- struct snd_soc_pcm_stream *pstream;
-
- pstream = &dai->driver->capture;
- tstamp->copied_total = hdac_stream(stream)->curr_pos;
- tstamp->sampling_rate = snd_pcm_rate_bit_to_rate(pstream->rates);
-
- return 0;
-}
diff --git a/sound/soc/sof/intel/hda-ctrl.c b/sound/soc/sof/intel/hda-ctrl.c
index fa5f0a718901..84bf01bd360a 100644
--- a/sound/soc/sof/intel/hda-ctrl.c
+++ b/sound/soc/sof/intel/hda-ctrl.c
@@ -19,15 +19,10 @@
#include <sound/hdaudio_ext.h>
#include <sound/hda_register.h>
#include <sound/hda_component.h>
+#include <sound/hda-mlink.h>
#include "../ops.h"
#include "hda.h"
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
-static int hda_codec_mask = -1;
-module_param_named(codec_mask, hda_codec_mask, int, 0444);
-MODULE_PARM_DESC(codec_mask, "SOF HDA codec mask for probing");
-#endif
-
/*
* HDA Operations.
*/
@@ -164,16 +159,18 @@ void hda_dsp_ctrl_misc_clock_gating(struct snd_sof_dev *sdev, bool enable)
*/
int hda_dsp_ctrl_clock_power_gating(struct snd_sof_dev *sdev, bool enable)
{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
u32 val;
/* enable/disable audio dsp clock gating */
val = enable ? PCI_CGCTL_ADSPDCGE : 0;
snd_sof_pci_update_bits(sdev, PCI_CGCTL, PCI_CGCTL_ADSPDCGE, val);
- /* enable/disable DMI Link L1 support */
+ /* disable the DMI link when requested. But enable only if it wasn't disabled previously */
val = enable ? HDA_VS_INTEL_EM2_L1SEN : 0;
- snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_EM2,
- HDA_VS_INTEL_EM2_L1SEN, val);
+ if (!enable || !hda->l1_disabled)
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_EM2,
+ HDA_VS_INTEL_EM2_L1SEN, val);
/* enable/disable audio dsp power gating */
val = enable ? 0 : PCI_PGCTL_ADSPPGD;
@@ -182,72 +179,43 @@ int hda_dsp_ctrl_clock_power_gating(struct snd_sof_dev *sdev, bool enable)
return 0;
}
-int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool full_reset)
+int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev)
{
struct hdac_bus *bus = sof_to_bus(sdev);
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
- struct hdac_ext_link *hlink;
-#endif
struct hdac_stream *stream;
int sd_offset, ret = 0;
if (bus->chip_init)
return 0;
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
- snd_hdac_set_codec_wakeup(bus, true);
-#endif
- hda_dsp_ctrl_misc_clock_gating(sdev, false);
+ hda_codec_set_codec_wakeup(sdev, true);
- if (full_reset) {
- /* reset HDA controller */
- ret = hda_dsp_ctrl_link_reset(sdev, true);
- if (ret < 0) {
- dev_err(sdev->dev, "error: failed to reset HDA controller\n");
- goto err;
- }
-
- usleep_range(500, 1000);
-
- /* exit HDA controller reset */
- ret = hda_dsp_ctrl_link_reset(sdev, false);
- if (ret < 0) {
- dev_err(sdev->dev, "error: failed to exit HDA controller reset\n");
- goto err;
- }
-
- usleep_range(1000, 1200);
- }
+ hda_dsp_ctrl_misc_clock_gating(sdev, false);
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
- /* check to see if controller is ready */
- if (!snd_hdac_chip_readb(bus, GCTL)) {
- dev_dbg(bus->dev, "controller not ready!\n");
- ret = -EBUSY;
+ /* reset HDA controller */
+ ret = hda_dsp_ctrl_link_reset(sdev, true);
+ if (ret < 0) {
+ dev_err(sdev->dev, "error: failed to reset HDA controller\n");
goto err;
}
- /* Accept unsolicited responses */
- snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_UNSOL, AZX_GCTL_UNSOL);
+ usleep_range(500, 1000);
- /* detect codecs */
- if (!bus->codec_mask) {
- bus->codec_mask = snd_hdac_chip_readw(bus, STATESTS);
- dev_dbg(bus->dev, "codec_mask = 0x%lx\n", bus->codec_mask);
+ /* exit HDA controller reset */
+ ret = hda_dsp_ctrl_link_reset(sdev, false);
+ if (ret < 0) {
+ dev_err(sdev->dev, "error: failed to exit HDA controller reset\n");
+ goto err;
}
+ usleep_range(1000, 1200);
- if (hda_codec_mask != -1) {
- bus->codec_mask &= hda_codec_mask;
- dev_dbg(bus->dev, "filtered codec_mask = 0x%lx\n",
- bus->codec_mask);
- }
-#endif
+ hda_codec_detect_mask(sdev);
/* clear stream status */
list_for_each_entry(stream, &bus->stream_list, list) {
sd_offset = SOF_STREAM_SD_OFFSET(stream);
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
- sd_offset + SOF_HDA_ADSP_REG_CL_SD_STS,
+ sd_offset + SOF_HDA_ADSP_REG_SD_STS,
SOF_HDA_CL_DMA_SD_INT_MASK);
}
@@ -255,19 +223,13 @@ int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool full_reset)
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_WAKESTS,
SOF_HDA_WAKESTS_INT_MASK);
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
- /* clear rirb status */
- snd_hdac_chip_writeb(bus, RIRBSTS, RIRB_INT_MASK);
-#endif
+ hda_codec_rirb_status_clear(sdev);
/* clear interrupt status register */
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTSTS,
SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_ALL_STREAM);
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
- /* initialize the codec command I/O */
- snd_hdac_bus_init_cmd_io(bus);
-#endif
+ hda_codec_init_cmd_io(sdev);
/* enable CIE and GIE interrupts */
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL,
@@ -282,19 +244,14 @@ int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool full_reset)
upper_32_bits(bus->posbuf.addr));
}
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
- /* Reset stream-to-link mapping */
- list_for_each_entry(hlink, &bus->hlink_list, list)
- writel(0, hlink->ml_addr + AZX_REG_ML_LOSIDV);
-#endif
+ hda_bus_ml_reset_losidv(bus);
bus->chip_init = true;
err:
hda_dsp_ctrl_misc_clock_gating(sdev, true);
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
- snd_hdac_set_codec_wakeup(bus, false);
-#endif
+
+ hda_codec_set_codec_wakeup(sdev, false);
return ret;
}
@@ -313,7 +270,7 @@ void hda_dsp_ctrl_stop_chip(struct snd_sof_dev *sdev)
sd_offset = SOF_STREAM_SD_OFFSET(stream);
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
sd_offset +
- SOF_HDA_ADSP_REG_CL_SD_CTL,
+ SOF_HDA_ADSP_REG_SD_CTL,
SOF_HDA_CL_DMA_SD_INT_MASK,
0);
}
@@ -331,7 +288,7 @@ void hda_dsp_ctrl_stop_chip(struct snd_sof_dev *sdev)
list_for_each_entry(stream, &bus->stream_list, list) {
sd_offset = SOF_STREAM_SD_OFFSET(stream);
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
- sd_offset + SOF_HDA_ADSP_REG_CL_SD_STS,
+ sd_offset + SOF_HDA_ADSP_REG_SD_STS,
SOF_HDA_CL_DMA_SD_INT_MASK);
}
@@ -339,21 +296,16 @@ void hda_dsp_ctrl_stop_chip(struct snd_sof_dev *sdev)
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_WAKESTS,
SOF_HDA_WAKESTS_INT_MASK);
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
- /* clear rirb status */
- snd_hdac_chip_writeb(bus, RIRBSTS, RIRB_INT_MASK);
-#endif
+ hda_codec_rirb_status_clear(sdev);
/* clear interrupt status register */
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTSTS,
SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_ALL_STREAM);
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
- /* disable CORB/RIRB */
- snd_hdac_bus_stop_cmd_io(bus);
-#endif
+ hda_codec_stop_cmd_io(sdev);
+
/* disable position buffer */
- if (bus->posbuf.addr) {
+ if (bus->use_posbuf && bus->posbuf.addr) {
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
SOF_HDA_ADSP_DPLBASE, 0);
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
diff --git a/sound/soc/sof/intel/hda-dai-ops.c b/sound/soc/sof/intel/hda-dai-ops.c
new file mode 100644
index 000000000000..c50ca9e72d37
--- /dev/null
+++ b/sound/soc/sof/intel/hda-dai-ops.c
@@ -0,0 +1,687 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2022 Intel Corporation. All rights reserved.
+
+#include <sound/pcm_params.h>
+#include <sound/hdaudio_ext.h>
+#include <sound/hda-mlink.h>
+#include <sound/sof/ipc4/header.h>
+#include <uapi/sound/sof/header.h>
+#include "../ipc4-priv.h"
+#include "../ipc4-topology.h"
+#include "../sof-priv.h"
+#include "../sof-audio.h"
+#include "hda.h"
+
+/* These ops are only applicable for the HDA DAI's in their current form */
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_LINK)
+/*
+ * This function checks if the host dma channel corresponding
+ * to the link DMA stream_tag argument is assigned to one
+ * of the FEs connected to the BE DAI.
+ */
+static bool hda_check_fes(struct snd_soc_pcm_runtime *rtd,
+ int dir, int stream_tag)
+{
+ struct snd_pcm_substream *fe_substream;
+ struct hdac_stream *fe_hstream;
+ struct snd_soc_dpcm *dpcm;
+
+ for_each_dpcm_fe(rtd, dir, dpcm) {
+ fe_substream = snd_soc_dpcm_get_substream(dpcm->fe, dir);
+ fe_hstream = fe_substream->runtime->private_data;
+ if (fe_hstream->stream_tag == stream_tag)
+ return true;
+ }
+
+ return false;
+}
+
+static struct hdac_ext_stream *
+hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct sof_intel_hda_stream *hda_stream;
+ const struct sof_intel_dsp_desc *chip;
+ struct snd_sof_dev *sdev;
+ struct hdac_ext_stream *res = NULL;
+ struct hdac_stream *hstream = NULL;
+
+ int stream_dir = substream->stream;
+
+ if (!bus->ppcap) {
+ dev_err(bus->dev, "stream type not supported\n");
+ return NULL;
+ }
+
+ spin_lock_irq(&bus->reg_lock);
+ list_for_each_entry(hstream, &bus->stream_list, list) {
+ struct hdac_ext_stream *hext_stream =
+ stream_to_hdac_ext_stream(hstream);
+ if (hstream->direction != substream->stream)
+ continue;
+
+ hda_stream = hstream_to_sof_hda_stream(hext_stream);
+ sdev = hda_stream->sdev;
+ chip = get_chip_info(sdev->pdata);
+
+ /* check if link is available */
+ if (!hext_stream->link_locked) {
+ /*
+ * choose the first available link for platforms that do not have the
+ * PROCEN_FMT_QUIRK set.
+ */
+ if (!(chip->quirks & SOF_INTEL_PROCEN_FMT_QUIRK)) {
+ res = hext_stream;
+ break;
+ }
+
+ if (hstream->opened) {
+ /*
+ * check if the stream tag matches the stream
+ * tag of one of the connected FEs
+ */
+ if (hda_check_fes(rtd, stream_dir,
+ hstream->stream_tag)) {
+ res = hext_stream;
+ break;
+ }
+ } else {
+ res = hext_stream;
+
+ /*
+ * This must be a hostless stream.
+ * So reserve the host DMA channel.
+ */
+ hda_stream->host_reserved = 1;
+ break;
+ }
+ }
+ }
+
+ if (res) {
+ /* Make sure that host and link DMA is decoupled. */
+ snd_hdac_ext_stream_decouple_locked(bus, res, true);
+
+ res->link_locked = 1;
+ res->link_substream = substream;
+ }
+ spin_unlock_irq(&bus->reg_lock);
+
+ return res;
+}
+
+static struct hdac_ext_stream *hda_get_hext_stream(struct snd_sof_dev *sdev,
+ struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream)
+{
+ return snd_soc_dai_get_dma_data(cpu_dai, substream);
+}
+
+static struct hdac_ext_stream *hda_ipc4_get_hext_stream(struct snd_sof_dev *sdev,
+ struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_sof_widget *pipe_widget;
+ struct sof_ipc4_pipeline *pipeline;
+ struct snd_sof_widget *swidget;
+ struct snd_soc_dapm_widget *w;
+
+ w = snd_soc_dai_get_widget(cpu_dai, substream->stream);
+ swidget = w->dobj.private;
+ pipe_widget = swidget->spipe->pipe_widget;
+ pipeline = pipe_widget->private;
+
+ /* mark pipeline so that it can be skipped during FE trigger */
+ pipeline->skip_during_fe_trigger = true;
+
+ return snd_soc_dai_get_dma_data(cpu_dai, substream);
+}
+
+static struct hdac_ext_stream *hda_assign_hext_stream(struct snd_sof_dev *sdev,
+ struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *dai;
+ struct hdac_ext_stream *hext_stream;
+
+ /* only allocate a stream_tag for the first DAI in the dailink */
+ dai = snd_soc_rtd_to_cpu(rtd, 0);
+ if (dai == cpu_dai)
+ hext_stream = hda_link_stream_assign(sof_to_bus(sdev), substream);
+ else
+ hext_stream = snd_soc_dai_get_dma_data(dai, substream);
+
+ if (!hext_stream)
+ return NULL;
+
+ snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)hext_stream);
+
+ return hext_stream;
+}
+
+static void hda_release_hext_stream(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream)
+{
+ struct hdac_ext_stream *hext_stream = hda_get_hext_stream(sdev, cpu_dai, substream);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *dai;
+
+ /* only release a stream_tag for the first DAI in the dailink */
+ dai = snd_soc_rtd_to_cpu(rtd, 0);
+ if (dai == cpu_dai)
+ snd_hdac_ext_stream_release(hext_stream, HDAC_EXT_STREAM_TYPE_LINK);
+ snd_soc_dai_set_dma_data(cpu_dai, substream, NULL);
+}
+
+static void hda_setup_hext_stream(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream,
+ unsigned int format_val)
+{
+ snd_hdac_ext_stream_setup(hext_stream, format_val);
+}
+
+static void hda_reset_hext_stream(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream)
+{
+ snd_hdac_ext_stream_reset(hext_stream);
+}
+
+static void hda_codec_dai_set_stream(struct snd_sof_dev *sdev,
+ struct snd_pcm_substream *substream,
+ struct hdac_stream *hstream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+
+ /* set the hdac_stream in the codec dai */
+ snd_soc_dai_set_stream(codec_dai, hstream, substream->stream);
+}
+
+static unsigned int hda_calc_stream_format(struct snd_sof_dev *sdev,
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+ unsigned int link_bps;
+ unsigned int format_val;
+ unsigned int bits;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ link_bps = codec_dai->driver->playback.sig_bits;
+ else
+ link_bps = codec_dai->driver->capture.sig_bits;
+
+ bits = snd_hdac_stream_format_bits(params_format(params), SNDRV_PCM_SUBFORMAT_STD,
+ link_bps);
+ format_val = snd_hdac_stream_format(params_channels(params), bits, params_rate(params));
+
+ dev_dbg(sdev->dev, "format_val=%#x, rate=%d, ch=%d, format=%d\n", format_val,
+ params_rate(params), params_channels(params), params_format(params));
+
+ return format_val;
+}
+
+static struct hdac_ext_link *hda_get_hlink(struct snd_sof_dev *sdev,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+ struct hdac_bus *bus = sof_to_bus(sdev);
+
+ return snd_hdac_ext_bus_get_hlink_by_name(bus, codec_dai->component->name);
+}
+
+static unsigned int generic_calc_stream_format(struct snd_sof_dev *sdev,
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ unsigned int format_val;
+ unsigned int bits;
+
+ bits = snd_hdac_stream_format_bits(params_format(params), SNDRV_PCM_SUBFORMAT_STD,
+ params_physical_width(params));
+ format_val = snd_hdac_stream_format(params_channels(params), bits, params_rate(params));
+
+ dev_dbg(sdev->dev, "format_val=%#x, rate=%d, ch=%d, format=%d\n", format_val,
+ params_rate(params), params_channels(params), params_format(params));
+
+ return format_val;
+}
+
+static unsigned int dmic_calc_stream_format(struct snd_sof_dev *sdev,
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ unsigned int format_val;
+ snd_pcm_format_t format;
+ unsigned int channels;
+ unsigned int width;
+ unsigned int bits;
+
+ channels = params_channels(params);
+ format = params_format(params);
+ width = params_physical_width(params);
+
+ if (format == SNDRV_PCM_FORMAT_S16_LE) {
+ format = SNDRV_PCM_FORMAT_S32_LE;
+ channels /= 2;
+ width = 32;
+ }
+
+ bits = snd_hdac_stream_format_bits(format, SNDRV_PCM_SUBFORMAT_STD, width);
+ format_val = snd_hdac_stream_format(channels, bits, params_rate(params));
+
+ dev_dbg(sdev->dev, "format_val=%#x, rate=%d, ch=%d, format=%d\n", format_val,
+ params_rate(params), channels, format);
+
+ return format_val;
+}
+
+static struct hdac_ext_link *ssp_get_hlink(struct snd_sof_dev *sdev,
+ struct snd_pcm_substream *substream)
+{
+ struct hdac_bus *bus = sof_to_bus(sdev);
+
+ return hdac_bus_eml_ssp_get_hlink(bus);
+}
+
+static struct hdac_ext_link *dmic_get_hlink(struct snd_sof_dev *sdev,
+ struct snd_pcm_substream *substream)
+{
+ struct hdac_bus *bus = sof_to_bus(sdev);
+
+ return hdac_bus_eml_dmic_get_hlink(bus);
+}
+
+static struct hdac_ext_link *sdw_get_hlink(struct snd_sof_dev *sdev,
+ struct snd_pcm_substream *substream)
+{
+ struct hdac_bus *bus = sof_to_bus(sdev);
+
+ return hdac_bus_eml_sdw_get_hlink(bus);
+}
+
+static int hda_ipc4_pre_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream, int cmd)
+{
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+ struct snd_sof_widget *pipe_widget;
+ struct sof_ipc4_pipeline *pipeline;
+ struct snd_sof_widget *swidget;
+ struct snd_soc_dapm_widget *w;
+ int ret = 0;
+
+ w = snd_soc_dai_get_widget(cpu_dai, substream->stream);
+ swidget = w->dobj.private;
+ pipe_widget = swidget->spipe->pipe_widget;
+ pipeline = pipe_widget->private;
+
+ if (pipe_widget->instance_id < 0)
+ return 0;
+
+ mutex_lock(&ipc4_data->pipeline_state_mutex);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id,
+ SOF_IPC4_PIPE_PAUSED);
+ if (ret < 0)
+ goto out;
+
+ pipeline->state = SOF_IPC4_PIPE_PAUSED;
+ break;
+ default:
+ dev_err(sdev->dev, "unknown trigger command %d\n", cmd);
+ ret = -EINVAL;
+ }
+out:
+ mutex_unlock(&ipc4_data->pipeline_state_mutex);
+ return ret;
+}
+
+static int hda_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream, int cmd)
+{
+ struct hdac_ext_stream *hext_stream = snd_soc_dai_get_dma_data(cpu_dai, substream);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ snd_hdac_ext_stream_start(hext_stream);
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ snd_hdac_ext_stream_clear(hext_stream);
+ break;
+ default:
+ dev_err(sdev->dev, "unknown trigger command %d\n", cmd);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int hda_ipc4_post_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream, int cmd)
+{
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+ struct snd_sof_widget *pipe_widget;
+ struct sof_ipc4_pipeline *pipeline;
+ struct snd_sof_widget *swidget;
+ struct snd_soc_dapm_widget *w;
+ int ret = 0;
+
+ w = snd_soc_dai_get_widget(cpu_dai, substream->stream);
+ swidget = w->dobj.private;
+ pipe_widget = swidget->spipe->pipe_widget;
+ pipeline = pipe_widget->private;
+
+ if (pipe_widget->instance_id < 0)
+ return 0;
+
+ mutex_lock(&ipc4_data->pipeline_state_mutex);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ if (pipeline->state != SOF_IPC4_PIPE_PAUSED) {
+ ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id,
+ SOF_IPC4_PIPE_PAUSED);
+ if (ret < 0)
+ goto out;
+ pipeline->state = SOF_IPC4_PIPE_PAUSED;
+ }
+
+ ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id,
+ SOF_IPC4_PIPE_RUNNING);
+ if (ret < 0)
+ goto out;
+ pipeline->state = SOF_IPC4_PIPE_RUNNING;
+ swidget->spipe->started_count++;
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id,
+ SOF_IPC4_PIPE_RUNNING);
+ if (ret < 0)
+ goto out;
+ pipeline->state = SOF_IPC4_PIPE_RUNNING;
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ /*
+ * STOP/SUSPEND trigger is invoked only once when all users of this pipeline have
+ * been stopped. So, clear the started_count so that the pipeline can be reset
+ */
+ swidget->spipe->started_count = 0;
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ break;
+ default:
+ dev_err(sdev->dev, "unknown trigger command %d\n", cmd);
+ ret = -EINVAL;
+ break;
+ }
+out:
+ mutex_unlock(&ipc4_data->pipeline_state_mutex);
+ return ret;
+}
+
+static struct hdac_ext_stream *sdw_hda_ipc4_get_hext_stream(struct snd_sof_dev *sdev,
+ struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(cpu_dai, substream->stream);
+ struct snd_sof_widget *swidget = w->dobj.private;
+ struct snd_sof_dai *dai = swidget->private;
+ struct sof_ipc4_copier *ipc4_copier = dai->private;
+ struct sof_ipc4_alh_configuration_blob *blob;
+
+ blob = (struct sof_ipc4_alh_configuration_blob *)ipc4_copier->copier_config;
+
+ /*
+ * Starting with ACE_2_0, re-setting the device_count is mandatory to avoid using
+ * the multi-gateway firmware configuration. The DMA hardware can take care of
+ * multiple links without needing any firmware assistance
+ */
+ blob->alh_cfg.device_count = 1;
+
+ return hda_ipc4_get_hext_stream(sdev, cpu_dai, substream);
+}
+
+static const struct hda_dai_widget_dma_ops hda_ipc4_dma_ops = {
+ .get_hext_stream = hda_ipc4_get_hext_stream,
+ .assign_hext_stream = hda_assign_hext_stream,
+ .release_hext_stream = hda_release_hext_stream,
+ .setup_hext_stream = hda_setup_hext_stream,
+ .reset_hext_stream = hda_reset_hext_stream,
+ .pre_trigger = hda_ipc4_pre_trigger,
+ .trigger = hda_trigger,
+ .post_trigger = hda_ipc4_post_trigger,
+ .codec_dai_set_stream = hda_codec_dai_set_stream,
+ .calc_stream_format = hda_calc_stream_format,
+ .get_hlink = hda_get_hlink,
+};
+
+static const struct hda_dai_widget_dma_ops ssp_ipc4_dma_ops = {
+ .get_hext_stream = hda_ipc4_get_hext_stream,
+ .assign_hext_stream = hda_assign_hext_stream,
+ .release_hext_stream = hda_release_hext_stream,
+ .setup_hext_stream = hda_setup_hext_stream,
+ .reset_hext_stream = hda_reset_hext_stream,
+ .pre_trigger = hda_ipc4_pre_trigger,
+ .trigger = hda_trigger,
+ .post_trigger = hda_ipc4_post_trigger,
+ .calc_stream_format = generic_calc_stream_format,
+ .get_hlink = ssp_get_hlink,
+};
+
+static const struct hda_dai_widget_dma_ops dmic_ipc4_dma_ops = {
+ .get_hext_stream = hda_ipc4_get_hext_stream,
+ .assign_hext_stream = hda_assign_hext_stream,
+ .release_hext_stream = hda_release_hext_stream,
+ .setup_hext_stream = hda_setup_hext_stream,
+ .reset_hext_stream = hda_reset_hext_stream,
+ .pre_trigger = hda_ipc4_pre_trigger,
+ .trigger = hda_trigger,
+ .post_trigger = hda_ipc4_post_trigger,
+ .calc_stream_format = dmic_calc_stream_format,
+ .get_hlink = dmic_get_hlink,
+};
+
+static const struct hda_dai_widget_dma_ops sdw_ipc4_dma_ops = {
+ .get_hext_stream = sdw_hda_ipc4_get_hext_stream,
+ .assign_hext_stream = hda_assign_hext_stream,
+ .release_hext_stream = hda_release_hext_stream,
+ .setup_hext_stream = hda_setup_hext_stream,
+ .reset_hext_stream = hda_reset_hext_stream,
+ .pre_trigger = hda_ipc4_pre_trigger,
+ .trigger = hda_trigger,
+ .post_trigger = hda_ipc4_post_trigger,
+ .calc_stream_format = generic_calc_stream_format,
+ .get_hlink = sdw_get_hlink,
+};
+
+static const struct hda_dai_widget_dma_ops hda_ipc4_chain_dma_ops = {
+ .get_hext_stream = hda_get_hext_stream,
+ .assign_hext_stream = hda_assign_hext_stream,
+ .release_hext_stream = hda_release_hext_stream,
+ .setup_hext_stream = hda_setup_hext_stream,
+ .reset_hext_stream = hda_reset_hext_stream,
+ .trigger = hda_trigger,
+ .codec_dai_set_stream = hda_codec_dai_set_stream,
+ .calc_stream_format = hda_calc_stream_format,
+ .get_hlink = hda_get_hlink,
+};
+
+static const struct hda_dai_widget_dma_ops sdw_ipc4_chain_dma_ops = {
+ .get_hext_stream = hda_get_hext_stream,
+ .assign_hext_stream = hda_assign_hext_stream,
+ .release_hext_stream = hda_release_hext_stream,
+ .setup_hext_stream = hda_setup_hext_stream,
+ .reset_hext_stream = hda_reset_hext_stream,
+ .trigger = hda_trigger,
+ .calc_stream_format = generic_calc_stream_format,
+ .get_hlink = sdw_get_hlink,
+};
+
+static int hda_ipc3_post_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream, int cmd)
+{
+ struct hdac_ext_stream *hext_stream = hda_get_hext_stream(sdev, cpu_dai, substream);
+ struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(cpu_dai, substream->stream);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ {
+ struct snd_sof_dai_config_data data = { 0 };
+ int ret;
+
+ data.dai_data = DMA_CHAN_INVALID;
+ ret = hda_dai_config(w, SOF_DAI_CONFIG_FLAGS_HW_FREE, &data);
+ if (ret < 0)
+ return ret;
+
+ if (cmd == SNDRV_PCM_TRIGGER_STOP)
+ return hda_link_dma_cleanup(substream, hext_stream, cpu_dai);
+
+ break;
+ }
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ return hda_dai_config(w, SOF_DAI_CONFIG_FLAGS_PAUSE, NULL);
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const struct hda_dai_widget_dma_ops hda_ipc3_dma_ops = {
+ .get_hext_stream = hda_get_hext_stream,
+ .assign_hext_stream = hda_assign_hext_stream,
+ .release_hext_stream = hda_release_hext_stream,
+ .setup_hext_stream = hda_setup_hext_stream,
+ .reset_hext_stream = hda_reset_hext_stream,
+ .trigger = hda_trigger,
+ .post_trigger = hda_ipc3_post_trigger,
+ .codec_dai_set_stream = hda_codec_dai_set_stream,
+ .calc_stream_format = hda_calc_stream_format,
+ .get_hlink = hda_get_hlink,
+};
+
+static struct hdac_ext_stream *
+hda_dspless_get_hext_stream(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream)
+{
+ struct hdac_stream *hstream = substream->runtime->private_data;
+
+ return stream_to_hdac_ext_stream(hstream);
+}
+
+static void hda_dspless_setup_hext_stream(struct snd_sof_dev *sdev,
+ struct hdac_ext_stream *hext_stream,
+ unsigned int format_val)
+{
+ /*
+ * Save the format_val which was adjusted by the maxbps of the codec.
+ * This information is not available on the FE side since there we are
+ * using dummy_codec.
+ */
+ hext_stream->hstream.format_val = format_val;
+}
+
+static const struct hda_dai_widget_dma_ops hda_dspless_dma_ops = {
+ .get_hext_stream = hda_dspless_get_hext_stream,
+ .setup_hext_stream = hda_dspless_setup_hext_stream,
+ .codec_dai_set_stream = hda_codec_dai_set_stream,
+ .calc_stream_format = hda_calc_stream_format,
+ .get_hlink = hda_get_hlink,
+};
+
+static const struct hda_dai_widget_dma_ops sdw_dspless_dma_ops = {
+ .get_hext_stream = hda_dspless_get_hext_stream,
+ .setup_hext_stream = hda_dspless_setup_hext_stream,
+ .calc_stream_format = generic_calc_stream_format,
+ .get_hlink = sdw_get_hlink,
+};
+
+#endif
+
+const struct hda_dai_widget_dma_ops *
+hda_select_dai_widget_ops(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
+{
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_LINK)
+ struct snd_sof_dai *sdai;
+ const struct sof_intel_dsp_desc *chip;
+
+ chip = get_chip_info(sdev->pdata);
+ sdai = swidget->private;
+
+ if (sdev->dspless_mode_selected) {
+ switch (sdai->type) {
+ case SOF_DAI_INTEL_HDA:
+ return &hda_dspless_dma_ops;
+ case SOF_DAI_INTEL_ALH:
+ if (chip->hw_ip_version < SOF_INTEL_ACE_2_0)
+ return NULL;
+ return &sdw_dspless_dma_ops;
+ default:
+ return NULL;
+ }
+ }
+
+ switch (sdev->pdata->ipc_type) {
+ case SOF_IPC_TYPE_3:
+ {
+ struct sof_dai_private_data *private = sdai->private;
+
+ if (private->dai_config->type == SOF_DAI_INTEL_HDA)
+ return &hda_ipc3_dma_ops;
+ break;
+ }
+ case SOF_IPC_TYPE_4:
+ {
+ struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget;
+ struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
+
+ switch (sdai->type) {
+ case SOF_DAI_INTEL_HDA:
+ if (pipeline->use_chain_dma)
+ return &hda_ipc4_chain_dma_ops;
+
+ return &hda_ipc4_dma_ops;
+ case SOF_DAI_INTEL_SSP:
+ if (chip->hw_ip_version < SOF_INTEL_ACE_2_0)
+ return NULL;
+ return &ssp_ipc4_dma_ops;
+ case SOF_DAI_INTEL_DMIC:
+ if (chip->hw_ip_version < SOF_INTEL_ACE_2_0)
+ return NULL;
+ return &dmic_ipc4_dma_ops;
+ case SOF_DAI_INTEL_ALH:
+ if (chip->hw_ip_version < SOF_INTEL_ACE_2_0)
+ return NULL;
+ if (pipeline->use_chain_dma)
+ return &sdw_ipc4_chain_dma_ops;
+ return &sdw_ipc4_dma_ops;
+
+ default:
+ break;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+#endif
+ return NULL;
+}
diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c
index c6cb8c212eca..c1682bcdb5a6 100644
--- a/sound/soc/sof/intel/hda-dai.c
+++ b/sound/soc/sof/intel/hda-dai.c
@@ -10,409 +10,628 @@
#include <sound/pcm_params.h>
#include <sound/hdaudio_ext.h>
+#include <sound/hda-mlink.h>
+#include <sound/hda_register.h>
+#include <sound/intel-nhlt.h>
+#include <sound/sof/ipc4/header.h>
+#include <uapi/sound/sof/header.h>
+#include "../ipc4-priv.h"
+#include "../ipc4-topology.h"
#include "../sof-priv.h"
#include "../sof-audio.h"
#include "hda.h"
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
-
-struct hda_pipe_params {
- u8 host_dma_id;
- u8 link_dma_id;
- u32 ch;
- u32 s_freq;
- u32 s_fmt;
- u8 linktype;
- snd_pcm_format_t format;
- int link_index;
- int stream;
- unsigned int host_bps;
- unsigned int link_bps;
-};
-
/*
- * This function checks if the host dma channel corresponding
- * to the link DMA stream_tag argument is assigned to one
- * of the FEs connected to the BE DAI.
+ * The default method is to fetch NHLT from BIOS. With this parameter set
+ * it is possible to override that with NHLT in the SOF topology manifest.
*/
-static bool hda_check_fes(struct snd_soc_pcm_runtime *rtd,
- int dir, int stream_tag)
-{
- struct snd_pcm_substream *fe_substream;
- struct hdac_stream *fe_hstream;
- struct snd_soc_dpcm *dpcm;
-
- for_each_dpcm_fe(rtd, dir, dpcm) {
- fe_substream = snd_soc_dpcm_get_substream(dpcm->fe, dir);
- fe_hstream = fe_substream->runtime->private_data;
- if (fe_hstream->stream_tag == stream_tag)
- return true;
- }
+static bool hda_use_tplg_nhlt;
+module_param_named(sof_use_tplg_nhlt, hda_use_tplg_nhlt, bool, 0444);
+MODULE_PARM_DESC(sof_use_tplg_nhlt, "SOF topology nhlt override");
+
+static struct snd_sof_dev *widget_to_sdev(struct snd_soc_dapm_widget *w)
+{
+ struct snd_sof_widget *swidget = w->dobj.private;
+ struct snd_soc_component *component = swidget->scomp;
- return false;
+ return snd_soc_component_get_drvdata(component);
}
-static struct hdac_ext_stream *
- hda_link_stream_assign(struct hdac_bus *bus,
- struct snd_pcm_substream *substream)
+int hda_dai_config(struct snd_soc_dapm_widget *w, unsigned int flags,
+ struct snd_sof_dai_config_data *data)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
- struct sof_intel_hda_stream *hda_stream;
- struct hdac_ext_stream *res = NULL;
- struct hdac_stream *stream = NULL;
+ struct snd_sof_widget *swidget = w->dobj.private;
+ const struct sof_ipc_tplg_ops *tplg_ops;
+ struct snd_sof_dev *sdev;
+ int ret;
- int stream_dir = substream->stream;
+ if (!swidget)
+ return 0;
- if (!bus->ppcap) {
- dev_err(bus->dev, "stream type not supported\n");
- return NULL;
- }
+ sdev = widget_to_sdev(w);
+ tplg_ops = sof_ipc_get_ops(sdev, tplg);
- list_for_each_entry(stream, &bus->stream_list, list) {
- struct hdac_ext_stream *hstream =
- stream_to_hdac_ext_stream(stream);
- if (stream->direction != substream->stream)
- continue;
-
- hda_stream = hstream_to_sof_hda_stream(hstream);
-
- /* check if link is available */
- if (!hstream->link_locked) {
- if (stream->opened) {
- /*
- * check if the stream tag matches the stream
- * tag of one of the connected FEs
- */
- if (hda_check_fes(rtd, stream_dir,
- stream->stream_tag)) {
- res = hstream;
- break;
- }
- } else {
- res = hstream;
-
- /*
- * This must be a hostless stream.
- * So reserve the host DMA channel.
- */
- hda_stream->host_reserved = 1;
- break;
- }
+ if (tplg_ops && tplg_ops->dai_config) {
+ ret = tplg_ops->dai_config(sdev, swidget, flags, data);
+ if (ret < 0) {
+ dev_err(sdev->dev, "DAI config with flags %x failed for widget %s\n",
+ flags, w->name);
+ return ret;
}
}
- if (res) {
- /*
- * Decouple host and link DMA. The decoupled flag
- * is updated in snd_hdac_ext_stream_decouple().
- */
- if (!res->decoupled)
- snd_hdac_ext_stream_decouple(bus, res, true);
- spin_lock_irq(&bus->reg_lock);
- res->link_locked = 1;
- res->link_substream = substream;
- spin_unlock_irq(&bus->reg_lock);
- }
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_LINK)
- return res;
+static struct snd_sof_dev *dai_to_sdev(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(cpu_dai, substream->stream);
+
+ return widget_to_sdev(w);
}
-static int hda_link_dma_params(struct hdac_ext_stream *stream,
- struct hda_pipe_params *params)
+static const struct hda_dai_widget_dma_ops *
+hda_dai_get_ops(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai)
{
- struct hdac_stream *hstream = &stream->hstream;
- unsigned char stream_tag = hstream->stream_tag;
- struct hdac_bus *bus = hstream->bus;
- struct hdac_ext_link *link;
- unsigned int format_val;
+ struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(cpu_dai, substream->stream);
+ struct snd_sof_widget *swidget = w->dobj.private;
+ struct snd_sof_dev *sdev;
+ struct snd_sof_dai *sdai;
- snd_hdac_ext_stream_decouple(bus, stream, true);
- snd_hdac_ext_link_stream_reset(stream);
+ sdev = widget_to_sdev(w);
- format_val = snd_hdac_calc_stream_format(params->s_freq, params->ch,
- params->format,
- params->link_bps, 0);
+ if (!swidget) {
+ dev_err(sdev->dev, "%s: swidget is NULL\n", __func__);
+ return NULL;
+ }
- dev_dbg(bus->dev, "format_val=%d, rate=%d, ch=%d, format=%d\n",
- format_val, params->s_freq, params->ch, params->format);
+ if (sdev->dspless_mode_selected)
+ return hda_select_dai_widget_ops(sdev, swidget);
- snd_hdac_ext_link_stream_setup(stream, format_val);
+ sdai = swidget->private;
- if (stream->hstream.direction == SNDRV_PCM_STREAM_PLAYBACK) {
- list_for_each_entry(link, &bus->hlink_list, list) {
- if (link->index == params->link_index)
- snd_hdac_ext_link_set_stream_id(link,
- stream_tag);
- }
- }
+ /* select and set the DAI widget ops if not set already */
+ if (!sdai->platform_private) {
+ const struct hda_dai_widget_dma_ops *ops =
+ hda_select_dai_widget_ops(sdev, swidget);
+ if (!ops)
+ return NULL;
- stream->link_prepared = 1;
+ /* check if mandatory ops are set */
+ if (!ops || !ops->get_hext_stream)
+ return NULL;
- return 0;
+ sdai->platform_private = ops;
+ }
+
+ return sdai->platform_private;
}
-/* Send DAI_CONFIG IPC to the DAI that matches the dai_name and direction */
-static int hda_link_config_ipc(struct sof_intel_hda_stream *hda_stream,
- const char *dai_name, int channel, int dir)
+int hda_link_dma_cleanup(struct snd_pcm_substream *substream, struct hdac_ext_stream *hext_stream,
+ struct snd_soc_dai *cpu_dai)
{
- struct sof_ipc_dai_config *config;
- struct snd_sof_dai *sof_dai;
- struct sof_ipc_reply reply;
- int ret = 0;
+ const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, cpu_dai);
+ struct sof_intel_hda_stream *hda_stream;
+ struct hdac_ext_link *hlink;
+ struct snd_sof_dev *sdev;
+ int stream_tag;
- list_for_each_entry(sof_dai, &hda_stream->sdev->dai_list, list) {
- if (!sof_dai->cpu_dai_name)
- continue;
+ if (!ops) {
+ dev_err(cpu_dai->dev, "DAI widget ops not set\n");
+ return -EINVAL;
+ }
- if (!strcmp(dai_name, sof_dai->cpu_dai_name) &&
- dir == sof_dai->comp_dai.direction) {
- config = sof_dai->dai_config;
+ sdev = dai_to_sdev(substream, cpu_dai);
- if (!config) {
- dev_err(hda_stream->sdev->dev,
- "error: no config for DAI %s\n",
- sof_dai->name);
- return -EINVAL;
- }
+ hlink = ops->get_hlink(sdev, substream);
+ if (!hlink)
+ return -EINVAL;
- /* update config with stream tag */
- config->hda.link_dma_ch = channel;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ stream_tag = hdac_stream(hext_stream)->stream_tag;
+ snd_hdac_ext_bus_link_clear_stream_id(hlink, stream_tag);
+ }
- /* send IPC */
- ret = sof_ipc_tx_message(hda_stream->sdev->ipc,
- config->hdr.cmd,
- config,
- config->hdr.size,
- &reply, sizeof(reply));
+ if (ops->release_hext_stream)
+ ops->release_hext_stream(sdev, cpu_dai, substream);
- if (ret < 0)
- dev_err(hda_stream->sdev->dev,
- "error: failed to set dai config for %s\n",
- sof_dai->name);
- return ret;
- }
- }
+ hext_stream->link_prepared = 0;
- return -EINVAL;
+ /* free the host DMA channel reserved by hostless streams */
+ hda_stream = hstream_to_sof_hda_stream(hext_stream);
+ hda_stream->host_reserved = 0;
+
+ return 0;
}
-static int hda_link_hw_params(struct snd_pcm_substream *substream,
- struct snd_pcm_hw_params *params,
- struct snd_soc_dai *dai)
+static int hda_link_dma_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *cpu_dai)
{
- struct hdac_stream *hstream = substream->runtime->private_data;
- struct hdac_bus *bus = hstream->bus;
- struct hdac_ext_stream *link_dev;
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
- struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
- struct sof_intel_hda_stream *hda_stream;
- struct hda_pipe_params p_params = {0};
- struct hdac_ext_link *link;
+ const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, cpu_dai);
+ struct hdac_ext_stream *hext_stream;
+ struct hdac_stream *hstream;
+ struct hdac_ext_link *hlink;
+ struct snd_sof_dev *sdev;
int stream_tag;
- int ret;
- /* get stored dma data if resuming from system suspend */
- link_dev = snd_soc_dai_get_dma_data(dai, substream);
- if (!link_dev) {
- link_dev = hda_link_stream_assign(bus, substream);
- if (!link_dev)
- return -EBUSY;
+ if (!ops) {
+ dev_err(cpu_dai->dev, "DAI widget ops not set\n");
+ return -EINVAL;
+ }
+
+ sdev = dai_to_sdev(substream, cpu_dai);
+
+ hlink = ops->get_hlink(sdev, substream);
+ if (!hlink)
+ return -EINVAL;
+
+ hext_stream = ops->get_hext_stream(sdev, cpu_dai, substream);
- snd_soc_dai_set_dma_data(dai, substream, (void *)link_dev);
+ if (!hext_stream) {
+ if (ops->assign_hext_stream)
+ hext_stream = ops->assign_hext_stream(sdev, cpu_dai, substream);
}
- stream_tag = hdac_stream(link_dev)->stream_tag;
+ if (!hext_stream)
+ return -EBUSY;
- hda_stream = hstream_to_sof_hda_stream(link_dev);
+ hstream = &hext_stream->hstream;
+ stream_tag = hstream->stream_tag;
- /* update the DSP with the new tag */
- ret = hda_link_config_ipc(hda_stream, dai->name, stream_tag - 1,
- substream->stream);
- if (ret < 0)
- return ret;
+ if (hext_stream->hstream.direction == SNDRV_PCM_STREAM_PLAYBACK)
+ snd_hdac_ext_bus_link_set_stream_id(hlink, stream_tag);
+
+ /* set the hdac_stream in the codec dai */
+ if (ops->codec_dai_set_stream)
+ ops->codec_dai_set_stream(sdev, substream, hstream);
+
+ if (ops->reset_hext_stream)
+ ops->reset_hext_stream(sdev, hext_stream);
+
+ if (ops->calc_stream_format && ops->setup_hext_stream) {
+ unsigned int format_val = ops->calc_stream_format(sdev, substream, params);
+
+ ops->setup_hext_stream(sdev, hext_stream, format_val);
+ }
- link = snd_hdac_ext_bus_get_link(bus, codec_dai->component->name);
- if (!link)
+ hext_stream->link_prepared = 1;
+
+ return 0;
+}
+
+static int __maybe_unused hda_dai_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, cpu_dai);
+ struct hdac_ext_stream *hext_stream;
+ struct snd_sof_dev *sdev = dai_to_sdev(substream, cpu_dai);
+
+ if (!ops) {
+ dev_err(cpu_dai->dev, "DAI widget ops not set\n");
return -EINVAL;
+ }
- /* set the stream tag in the codec dai dma params */
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
- snd_soc_dai_set_tdm_slot(codec_dai, stream_tag, 0, 0, 0);
- else
- snd_soc_dai_set_tdm_slot(codec_dai, 0, stream_tag, 0, 0);
-
- p_params.s_fmt = snd_pcm_format_width(params_format(params));
- p_params.ch = params_channels(params);
- p_params.s_freq = params_rate(params);
- p_params.stream = substream->stream;
- p_params.link_dma_id = stream_tag - 1;
- p_params.link_index = link->index;
- p_params.format = params_format(params);
-
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
- p_params.link_bps = codec_dai->driver->playback.sig_bits;
- else
- p_params.link_bps = codec_dai->driver->capture.sig_bits;
-
- return hda_link_dma_params(link_dev, &p_params);
+ hext_stream = ops->get_hext_stream(sdev, cpu_dai, substream);
+ if (!hext_stream)
+ return 0;
+
+ return hda_link_dma_cleanup(substream, hext_stream, cpu_dai);
}
-static int hda_link_pcm_prepare(struct snd_pcm_substream *substream,
- struct snd_soc_dai *dai)
+static int __maybe_unused hda_dai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
{
- struct hdac_ext_stream *link_dev =
- snd_soc_dai_get_dma_data(dai, substream);
- struct snd_sof_dev *sdev =
- snd_soc_component_get_drvdata(dai->component);
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
- int stream = substream->stream;
+ struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(dai, substream->stream);
+ const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, dai);
+ struct hdac_ext_stream *hext_stream;
+ struct snd_sof_dai_config_data data = { 0 };
+ unsigned int flags = SOF_DAI_CONFIG_FLAGS_HW_PARAMS;
+ struct snd_sof_dev *sdev = widget_to_sdev(w);
+ int ret;
- if (link_dev->link_prepared)
+ if (!ops) {
+ dev_err(sdev->dev, "DAI widget ops not set\n");
+ return -EINVAL;
+ }
+
+ hext_stream = ops->get_hext_stream(sdev, dai, substream);
+ if (hext_stream && hext_stream->link_prepared)
return 0;
- dev_dbg(sdev->dev, "hda: prepare stream dir %d\n", substream->stream);
+ ret = hda_link_dma_hw_params(substream, params, dai);
+ if (ret < 0)
+ return ret;
+
+ hext_stream = ops->get_hext_stream(sdev, dai, substream);
- return hda_link_hw_params(substream, &rtd->dpcm[stream].hw_params,
- dai);
+ flags |= SOF_DAI_CONFIG_FLAGS_2_STEP_STOP << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT;
+ data.dai_data = hdac_stream(hext_stream)->stream_tag - 1;
+
+ return hda_dai_config(w, flags, &data);
}
-static int hda_link_pcm_trigger(struct snd_pcm_substream *substream,
- int cmd, struct snd_soc_dai *dai)
+/*
+ * In contrast to IPC3, the dai trigger in IPC4 mixes pipeline state changes
+ * (over IPC channel) and DMA state change (direct host register changes).
+ */
+static int __maybe_unused hda_dai_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
{
- struct hdac_ext_stream *link_dev =
- snd_soc_dai_get_dma_data(dai, substream);
- struct sof_intel_hda_stream *hda_stream;
- struct snd_soc_pcm_runtime *rtd;
- struct hdac_ext_link *link;
- struct hdac_stream *hstream;
- struct hdac_bus *bus;
- int stream_tag;
+ const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, dai);
+ struct hdac_ext_stream *hext_stream;
+ struct snd_sof_dev *sdev;
int ret;
- hstream = substream->runtime->private_data;
- bus = hstream->bus;
- rtd = asoc_substream_to_rtd(substream);
+ if (!ops) {
+ dev_err(dai->dev, "DAI widget ops not set\n");
+ return -EINVAL;
+ }
+
+ dev_dbg(dai->dev, "cmd=%d dai %s direction %d\n", cmd,
+ dai->name, substream->stream);
- link = snd_hdac_ext_bus_get_link(bus, asoc_rtd_to_codec(rtd, 0)->component->name);
- if (!link)
+ sdev = dai_to_sdev(substream, dai);
+
+ hext_stream = ops->get_hext_stream(sdev, dai, substream);
+ if (!hext_stream)
return -EINVAL;
- hda_stream = hstream_to_sof_hda_stream(link_dev);
+ if (ops->pre_trigger) {
+ ret = ops->pre_trigger(sdev, dai, substream, cmd);
+ if (ret < 0)
+ return ret;
+ }
- dev_dbg(dai->dev, "In %s cmd=%d\n", __func__, cmd);
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_RESUME:
- /* set up hw_params */
- ret = hda_link_pcm_prepare(substream, dai);
- if (ret < 0) {
- dev_err(dai->dev,
- "error: setting up hw_params during resume\n");
+ if (ops->trigger) {
+ ret = ops->trigger(sdev, dai, substream, cmd);
+ if (ret < 0)
return ret;
- }
+ }
- fallthrough;
- case SNDRV_PCM_TRIGGER_START:
- case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- snd_hdac_ext_link_stream_start(link_dev);
- break;
- case SNDRV_PCM_TRIGGER_SUSPEND:
- case SNDRV_PCM_TRIGGER_STOP:
- /*
- * clear link DMA channel. It will be assigned when
- * hw_params is set up again after resume.
- */
- ret = hda_link_config_ipc(hda_stream, dai->name,
- DMA_CHAN_INVALID, substream->stream);
+ if (ops->post_trigger) {
+ ret = ops->post_trigger(sdev, dai, substream, cmd);
if (ret < 0)
return ret;
+ }
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
- stream_tag = hdac_stream(link_dev)->stream_tag;
- snd_hdac_ext_link_clear_stream_id(link, stream_tag);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ ret = hda_link_dma_cleanup(substream, hext_stream, dai);
+ if (ret < 0) {
+ dev_err(sdev->dev, "%s: failed to clean up link DMA\n", __func__);
+ return ret;
}
-
- link_dev->link_prepared = 0;
-
- fallthrough;
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
- snd_hdac_ext_link_stream_clear(link_dev);
break;
default:
- return -EINVAL;
+ break;
}
+
return 0;
}
-static int hda_link_hw_free(struct snd_pcm_substream *substream,
- struct snd_soc_dai *dai)
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
+
+static int hda_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
- unsigned int stream_tag;
- struct sof_intel_hda_stream *hda_stream;
- struct hdac_bus *bus;
- struct hdac_ext_link *link;
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ int stream = substream->stream;
+
+ return hda_dai_hw_params(substream, &rtd->dpcm[stream].hw_params, dai);
+}
+
+static const struct snd_soc_dai_ops hda_dai_ops = {
+ .hw_params = hda_dai_hw_params,
+ .hw_free = hda_dai_hw_free,
+ .trigger = hda_dai_trigger,
+ .prepare = hda_dai_prepare,
+};
+
+#endif
+
+static struct sof_ipc4_copier *widget_to_copier(struct snd_soc_dapm_widget *w)
+{
+ struct snd_sof_widget *swidget = w->dobj.private;
+ struct snd_sof_dai *sdai = swidget->private;
+ struct sof_ipc4_copier *ipc4_copier = (struct sof_ipc4_copier *)sdai->private;
+
+ return ipc4_copier;
+}
+
+static int non_hda_dai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(cpu_dai, substream->stream);
+ struct sof_ipc4_dma_config_tlv *dma_config_tlv;
+ const struct hda_dai_widget_dma_ops *ops;
+ struct sof_ipc4_dma_config *dma_config;
+ struct sof_ipc4_copier *ipc4_copier;
+ struct hdac_ext_stream *hext_stream;
struct hdac_stream *hstream;
- struct snd_soc_pcm_runtime *rtd;
- struct hdac_ext_stream *link_dev;
+ struct snd_sof_dev *sdev;
+ int stream_id;
int ret;
- hstream = substream->runtime->private_data;
- bus = hstream->bus;
- rtd = asoc_substream_to_rtd(substream);
- link_dev = snd_soc_dai_get_dma_data(dai, substream);
-
- if (!link_dev) {
- dev_dbg(dai->dev,
- "%s: link_dev is not assigned\n", __func__);
+ ops = hda_dai_get_ops(substream, cpu_dai);
+ if (!ops) {
+ dev_err(cpu_dai->dev, "DAI widget ops not set\n");
return -EINVAL;
}
- hda_stream = hstream_to_sof_hda_stream(link_dev);
-
- /* free the link DMA channel in the FW */
- ret = hda_link_config_ipc(hda_stream, dai->name, DMA_CHAN_INVALID,
- substream->stream);
- if (ret < 0)
+ /* use HDaudio stream handling */
+ ret = hda_dai_hw_params(substream, params, cpu_dai);
+ if (ret < 0) {
+ dev_err(cpu_dai->dev, "%s: hda_dai_hw_params failed: %d\n", __func__, ret);
return ret;
+ }
- link = snd_hdac_ext_bus_get_link(bus, asoc_rtd_to_codec(rtd, 0)->component->name);
- if (!link)
- return -EINVAL;
+ sdev = widget_to_sdev(w);
+ if (sdev->dspless_mode_selected)
+ goto skip_tlv;
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
- stream_tag = hdac_stream(link_dev)->stream_tag;
- snd_hdac_ext_link_clear_stream_id(link, stream_tag);
+ /* get stream_id */
+ hext_stream = ops->get_hext_stream(sdev, cpu_dai, substream);
+
+ if (!hext_stream) {
+ dev_err(cpu_dai->dev, "%s: no hext_stream found\n", __func__);
+ return -ENODEV;
}
- snd_soc_dai_set_dma_data(dai, substream, NULL);
- snd_hdac_ext_stream_release(link_dev, HDAC_EXT_STREAM_TYPE_LINK);
- link_dev->link_prepared = 0;
+ hstream = &hext_stream->hstream;
+ stream_id = hstream->stream_tag;
- /* free the host DMA channel reserved by hostless streams */
- hda_stream->host_reserved = 0;
+ if (!stream_id) {
+ dev_err(cpu_dai->dev, "%s: no stream_id allocated\n", __func__);
+ return -ENODEV;
+ }
+
+ /* configure TLV */
+ ipc4_copier = widget_to_copier(w);
+
+ dma_config_tlv = &ipc4_copier->dma_config_tlv;
+ dma_config_tlv->type = SOF_IPC4_GTW_DMA_CONFIG_ID;
+ /* dma_config_priv_size is zero */
+ dma_config_tlv->length = sizeof(dma_config_tlv->dma_config);
+ dma_config = &dma_config_tlv->dma_config;
+
+ dma_config->dma_method = SOF_IPC4_DMA_METHOD_HDA;
+ dma_config->pre_allocated_by_host = 1;
+ dma_config->dma_channel_id = stream_id - 1;
+ dma_config->stream_id = stream_id;
+ dma_config->dma_stream_channel_map.device_count = 0; /* mapping not used */
+ dma_config->dma_priv_config_size = 0;
+
+skip_tlv:
return 0;
}
-static const struct snd_soc_dai_ops hda_link_dai_ops = {
- .hw_params = hda_link_hw_params,
- .hw_free = hda_link_hw_free,
- .trigger = hda_link_pcm_trigger,
- .prepare = hda_link_pcm_prepare,
-};
+static int non_hda_dai_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ int stream = substream->stream;
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES)
-#include "../compress.h"
+ return non_hda_dai_hw_params(substream, &rtd->dpcm[stream].hw_params, cpu_dai);
+}
-static struct snd_soc_cdai_ops sof_probe_compr_ops = {
- .startup = sof_probe_compr_open,
- .shutdown = sof_probe_compr_free,
- .set_params = sof_probe_compr_set_params,
- .trigger = sof_probe_compr_trigger,
- .pointer = sof_probe_compr_pointer,
+static const struct snd_soc_dai_ops ssp_dai_ops = {
+ .hw_params = non_hda_dai_hw_params,
+ .hw_free = hda_dai_hw_free,
+ .trigger = hda_dai_trigger,
+ .prepare = non_hda_dai_prepare,
};
+static const struct snd_soc_dai_ops dmic_dai_ops = {
+ .hw_params = non_hda_dai_hw_params,
+ .hw_free = hda_dai_hw_free,
+ .trigger = hda_dai_trigger,
+ .prepare = non_hda_dai_prepare,
+};
+
+int sdw_hda_dai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai,
+ int link_id)
+{
+ struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(cpu_dai, substream->stream);
+ const struct hda_dai_widget_dma_ops *ops;
+ struct hdac_ext_stream *hext_stream;
+ struct snd_sof_dev *sdev;
+ int ret;
+
+ ret = non_hda_dai_hw_params(substream, params, cpu_dai);
+ if (ret < 0) {
+ dev_err(cpu_dai->dev, "%s: non_hda_dai_hw_params failed %d\n", __func__, ret);
+ return ret;
+ }
+
+ ops = hda_dai_get_ops(substream, cpu_dai);
+ sdev = widget_to_sdev(w);
+ hext_stream = ops->get_hext_stream(sdev, cpu_dai, substream);
+
+ if (!hext_stream)
+ return -ENODEV;
+
+ /* in the case of SoundWire we need to program the PCMSyCM registers */
+ ret = hdac_bus_eml_sdw_map_stream_ch(sof_to_bus(sdev), link_id, cpu_dai->id,
+ GENMASK(params_channels(params) - 1, 0),
+ hdac_stream(hext_stream)->stream_tag,
+ substream->stream);
+ if (ret < 0) {
+ dev_err(cpu_dai->dev, "%s: hdac_bus_eml_sdw_map_stream_ch failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+int sdw_hda_dai_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai,
+ int link_id)
+{
+ struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(cpu_dai, substream->stream);
+ struct snd_sof_dev *sdev;
+ int ret;
+
+ ret = hda_dai_hw_free(substream, cpu_dai);
+ if (ret < 0) {
+ dev_err(cpu_dai->dev, "%s: non_hda_dai_hw_free failed %d\n", __func__, ret);
+ return ret;
+ }
+
+ sdev = widget_to_sdev(w);
+
+ /* in the case of SoundWire we need to reset the PCMSyCM registers */
+ ret = hdac_bus_eml_sdw_map_stream_ch(sof_to_bus(sdev), link_id, cpu_dai->id,
+ 0, 0, substream->stream);
+ if (ret < 0) {
+ dev_err(cpu_dai->dev, "%s: hdac_bus_eml_sdw_map_stream_ch failed %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+int sdw_hda_dai_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *cpu_dai)
+{
+ return hda_dai_trigger(substream, cmd, cpu_dai);
+}
+
+static int hda_dai_suspend(struct hdac_bus *bus)
+{
+ struct snd_soc_pcm_runtime *rtd;
+ struct hdac_ext_stream *hext_stream;
+ struct hdac_stream *s;
+ int ret;
+
+ /* set internal flag for BE */
+ list_for_each_entry(s, &bus->stream_list, list) {
+
+ hext_stream = stream_to_hdac_ext_stream(s);
+
+ /*
+ * clear stream. This should already be taken care for running
+ * streams when the SUSPEND trigger is called. But paused
+ * streams do not get suspended, so this needs to be done
+ * explicitly during suspend.
+ */
+ if (hext_stream->link_substream) {
+ const struct hda_dai_widget_dma_ops *ops;
+ struct snd_sof_widget *swidget;
+ struct snd_soc_dapm_widget *w;
+ struct snd_soc_dai *cpu_dai;
+ struct snd_sof_dev *sdev;
+ struct snd_sof_dai *sdai;
+
+ rtd = snd_soc_substream_to_rtd(hext_stream->link_substream);
+ cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ w = snd_soc_dai_get_widget(cpu_dai, hdac_stream(hext_stream)->direction);
+ swidget = w->dobj.private;
+ sdev = widget_to_sdev(w);
+ sdai = swidget->private;
+ ops = sdai->platform_private;
+
+ ret = hda_link_dma_cleanup(hext_stream->link_substream,
+ hext_stream,
+ cpu_dai);
+ if (ret < 0)
+ return ret;
+
+ /* for consistency with TRIGGER_SUSPEND */
+ if (ops->post_trigger) {
+ ret = ops->post_trigger(sdev, cpu_dai,
+ hext_stream->link_substream,
+ SNDRV_PCM_TRIGGER_SUSPEND);
+ if (ret < 0)
+ return ret;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static void ssp_set_dai_drv_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *ops)
+{
+ const struct sof_intel_dsp_desc *chip;
+ int i;
+
+ chip = get_chip_info(sdev->pdata);
+
+ if (chip->hw_ip_version >= SOF_INTEL_ACE_2_0) {
+ for (i = 0; i < ops->num_drv; i++) {
+ if (strstr(ops->drv[i].name, "SSP"))
+ ops->drv[i].ops = &ssp_dai_ops;
+ }
+ }
+}
+
+static void dmic_set_dai_drv_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *ops)
+{
+ const struct sof_intel_dsp_desc *chip;
+ int i;
+
+ chip = get_chip_info(sdev->pdata);
+
+ if (chip->hw_ip_version >= SOF_INTEL_ACE_2_0) {
+ for (i = 0; i < ops->num_drv; i++) {
+ if (strstr(ops->drv[i].name, "DMIC"))
+ ops->drv[i].ops = &dmic_dai_ops;
+ }
+ }
+}
+
+#else
+
+static inline void ssp_set_dai_drv_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *ops) {}
+static inline void dmic_set_dai_drv_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *ops) {}
+
+#endif /* CONFIG_SND_SOC_SOF_HDA_LINK */
+
+void hda_set_dai_drv_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *ops)
+{
+ int i;
+
+ for (i = 0; i < ops->num_drv; i++) {
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
+ if (strstr(ops->drv[i].name, "iDisp") ||
+ strstr(ops->drv[i].name, "Analog") ||
+ strstr(ops->drv[i].name, "Digital"))
+ ops->drv[i].ops = &hda_dai_ops;
#endif
-#endif
+ }
+
+ ssp_set_dai_drv_ops(sdev, ops);
+ dmic_set_dai_drv_ops(sdev, ops);
+
+ if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4 && !hda_use_tplg_nhlt) {
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+
+ ipc4_data->nhlt = intel_nhlt_init(sdev->dev);
+ }
+}
+
+void hda_ops_free(struct snd_sof_dev *sdev)
+{
+ if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4) {
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+
+ if (!hda_use_tplg_nhlt)
+ intel_nhlt_free(ipc4_data->nhlt);
+
+ kfree(sdev->private);
+ sdev->private = NULL;
+ }
+}
+EXPORT_SYMBOL_NS(hda_ops_free, SND_SOC_SOF_INTEL_HDA_COMMON);
/*
* common dai driver for skl+ platforms.
@@ -500,10 +719,9 @@ struct snd_soc_dai_driver skl_dai[] = {
.channels_max = 4,
},
},
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
{
.name = "iDisp1 Pin",
- .ops = &hda_link_dai_ops,
.playback = {
.channels_min = 1,
.channels_max = 8,
@@ -511,7 +729,6 @@ struct snd_soc_dai_driver skl_dai[] = {
},
{
.name = "iDisp2 Pin",
- .ops = &hda_link_dai_ops,
.playback = {
.channels_min = 1,
.channels_max = 8,
@@ -519,7 +736,6 @@ struct snd_soc_dai_driver skl_dai[] = {
},
{
.name = "iDisp3 Pin",
- .ops = &hda_link_dai_ops,
.playback = {
.channels_min = 1,
.channels_max = 8,
@@ -527,7 +743,6 @@ struct snd_soc_dai_driver skl_dai[] = {
},
{
.name = "iDisp4 Pin",
- .ops = &hda_link_dai_ops,
.playback = {
.channels_min = 1,
.channels_max = 8,
@@ -535,7 +750,6 @@ struct snd_soc_dai_driver skl_dai[] = {
},
{
.name = "Analog CPU DAI",
- .ops = &hda_link_dai_ops,
.playback = {
.channels_min = 1,
.channels_max = 16,
@@ -547,7 +761,6 @@ struct snd_soc_dai_driver skl_dai[] = {
},
{
.name = "Digital CPU DAI",
- .ops = &hda_link_dai_ops,
.playback = {
.channels_min = 1,
.channels_max = 16,
@@ -559,7 +772,6 @@ struct snd_soc_dai_driver skl_dai[] = {
},
{
.name = "Alt Analog CPU DAI",
- .ops = &hda_link_dai_ops,
.playback = {
.channels_min = 1,
.channels_max = 16,
@@ -569,20 +781,24 @@ struct snd_soc_dai_driver skl_dai[] = {
.channels_max = 16,
},
},
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES)
-{
- .name = "Probe Extraction CPU DAI",
- .compress_new = snd_soc_new_compress,
- .cops = &sof_probe_compr_ops,
- .capture = {
- .stream_name = "Probe Extraction",
- .channels_min = 1,
- .channels_max = 8,
- .rates = SNDRV_PCM_RATE_48000,
- .rate_min = 48000,
- .rate_max = 48000,
- },
-},
-#endif
#endif
};
+
+int hda_dsp_dais_suspend(struct snd_sof_dev *sdev)
+{
+ /*
+ * In the corner case where a SUSPEND happens during a PAUSE, the ALSA core
+ * does not throw the TRIGGER_SUSPEND. This leaves the DAIs in an unbalanced state.
+ * Since the component suspend is called last, we can trap this corner case
+ * and force the DAIs to release their resources.
+ */
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_LINK)
+ int ret;
+
+ ret = hda_dai_suspend(sof_to_bus(sdev));
+ if (ret < 0)
+ return ret;
+#endif
+
+ return 0;
+}
diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c
index ed4d65a29d3a..31ffa1a8f2ac 100644
--- a/sound/soc/sof/intel/hda-dsp.c
+++ b/sound/soc/sof/intel/hda-dsp.c
@@ -18,6 +18,8 @@
#include <linux/module.h>
#include <sound/hdaudio_ext.h>
#include <sound/hda_register.h>
+#include <sound/hda-mlink.h>
+#include <trace/events/sof_intel.h>
#include "../sof-audio.h"
#include "../ops.h"
#include "hda.h"
@@ -34,7 +36,7 @@ MODULE_PARM_DESC(enable_trace_D0I3_S0,
* DSP Core control.
*/
-int hda_dsp_core_reset_enter(struct snd_sof_dev *sdev, unsigned int core_mask)
+static int hda_dsp_core_reset_enter(struct snd_sof_dev *sdev, unsigned int core_mask)
{
u32 adspcs;
u32 reset;
@@ -44,7 +46,7 @@ int hda_dsp_core_reset_enter(struct snd_sof_dev *sdev, unsigned int core_mask)
reset = HDA_DSP_ADSPCS_CRST_MASK(core_mask);
snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR,
HDA_DSP_REG_ADSPCS,
- reset, reset),
+ reset, reset);
/* poll with timeout to check if operation successful */
ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR,
@@ -73,7 +75,7 @@ int hda_dsp_core_reset_enter(struct snd_sof_dev *sdev, unsigned int core_mask)
return ret;
}
-int hda_dsp_core_reset_leave(struct snd_sof_dev *sdev, unsigned int core_mask)
+static int hda_dsp_core_reset_leave(struct snd_sof_dev *sdev, unsigned int core_mask)
{
unsigned int crst;
u32 adspcs;
@@ -125,6 +127,31 @@ int hda_dsp_core_stall_reset(struct snd_sof_dev *sdev, unsigned int core_mask)
return hda_dsp_core_reset_enter(sdev, core_mask);
}
+bool hda_dsp_core_is_enabled(struct snd_sof_dev *sdev, unsigned int core_mask)
+{
+ int val;
+ bool is_enable;
+
+ val = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPCS);
+
+#define MASK_IS_EQUAL(v, m, field) ({ \
+ u32 _m = field(m); \
+ ((v) & _m) == _m; \
+})
+
+ is_enable = MASK_IS_EQUAL(val, core_mask, HDA_DSP_ADSPCS_CPA_MASK) &&
+ MASK_IS_EQUAL(val, core_mask, HDA_DSP_ADSPCS_SPA_MASK) &&
+ !(val & HDA_DSP_ADSPCS_CRST_MASK(core_mask)) &&
+ !(val & HDA_DSP_ADSPCS_CSTALL_MASK(core_mask));
+
+#undef MASK_IS_EQUAL
+
+ dev_dbg(sdev->dev, "DSP core(s) enabled? %d : core_mask %x\n",
+ is_enable, core_mask);
+
+ return is_enable;
+}
+
int hda_dsp_core_run(struct snd_sof_dev *sdev, unsigned int core_mask)
{
int ret;
@@ -158,10 +185,18 @@ int hda_dsp_core_run(struct snd_sof_dev *sdev, unsigned int core_mask)
int hda_dsp_core_power_up(struct snd_sof_dev *sdev, unsigned int core_mask)
{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+ const struct sof_intel_dsp_desc *chip = hda->desc;
unsigned int cpa;
u32 adspcs;
int ret;
+ /* restrict core_mask to host managed cores mask */
+ core_mask &= chip->host_managed_cores_mask;
+ /* return if core_mask is not valid */
+ if (!core_mask)
+ return 0;
+
/* update bits */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPCS,
HDA_DSP_ADSPCS_SPA_MASK(core_mask),
@@ -195,7 +230,7 @@ int hda_dsp_core_power_up(struct snd_sof_dev *sdev, unsigned int core_mask)
return ret;
}
-int hda_dsp_core_power_down(struct snd_sof_dev *sdev, unsigned int core_mask)
+static int hda_dsp_core_power_down(struct snd_sof_dev *sdev, unsigned int core_mask)
{
u32 adspcs;
int ret;
@@ -207,7 +242,7 @@ int hda_dsp_core_power_down(struct snd_sof_dev *sdev, unsigned int core_mask)
ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR,
HDA_DSP_REG_ADSPCS, adspcs,
- !(adspcs & HDA_DSP_ADSPCS_SPA_MASK(core_mask)),
+ !(adspcs & HDA_DSP_ADSPCS_CPA_MASK(core_mask)),
HDA_DSP_REG_POLL_INTERVAL_US,
HDA_DSP_PD_TIMEOUT * USEC_PER_MSEC);
if (ret < 0)
@@ -218,31 +253,17 @@ int hda_dsp_core_power_down(struct snd_sof_dev *sdev, unsigned int core_mask)
return ret;
}
-bool hda_dsp_core_is_enabled(struct snd_sof_dev *sdev,
- unsigned int core_mask)
-{
- int val;
- bool is_enable;
-
- val = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPCS);
-
- is_enable = (val & HDA_DSP_ADSPCS_CPA_MASK(core_mask)) &&
- (val & HDA_DSP_ADSPCS_SPA_MASK(core_mask)) &&
- !(val & HDA_DSP_ADSPCS_CRST_MASK(core_mask)) &&
- !(val & HDA_DSP_ADSPCS_CSTALL_MASK(core_mask));
-
- dev_dbg(sdev->dev, "DSP core(s) enabled? %d : core_mask %x\n",
- is_enable, core_mask);
-
- return is_enable;
-}
-
int hda_dsp_enable_core(struct snd_sof_dev *sdev, unsigned int core_mask)
{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+ const struct sof_intel_dsp_desc *chip = hda->desc;
int ret;
- /* return if core is already enabled */
- if (hda_dsp_core_is_enabled(sdev, core_mask))
+ /* restrict core_mask to host managed cores mask */
+ core_mask &= chip->host_managed_cores_mask;
+
+ /* return if core_mask is not valid or cores are already enabled */
+ if (!core_mask || hda_dsp_core_is_enabled(sdev, core_mask))
return 0;
/* power up */
@@ -259,8 +280,17 @@ int hda_dsp_enable_core(struct snd_sof_dev *sdev, unsigned int core_mask)
int hda_dsp_core_reset_power_down(struct snd_sof_dev *sdev,
unsigned int core_mask)
{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+ const struct sof_intel_dsp_desc *chip = hda->desc;
int ret;
+ /* restrict core_mask to host managed cores mask */
+ core_mask &= chip->host_managed_cores_mask;
+
+ /* return if core_mask is not valid */
+ if (!core_mask)
+ return 0;
+
/* place core in reset prior to power down */
ret = hda_dsp_core_stall_reset(sdev, core_mask);
if (ret < 0) {
@@ -292,6 +322,9 @@ void hda_dsp_ipc_int_enable(struct snd_sof_dev *sdev)
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
const struct sof_intel_dsp_desc *chip = hda->desc;
+ if (sdev->dspless_mode_selected)
+ return;
+
/* enable IPC DONE and BUSY interrupts */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, chip->ipc_ctl,
HDA_DSP_REG_HIPCCTL_DONE | HDA_DSP_REG_HIPCCTL_BUSY,
@@ -307,6 +340,9 @@ void hda_dsp_ipc_int_disable(struct snd_sof_dev *sdev)
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
const struct sof_intel_dsp_desc *chip = hda->desc;
+ if (sdev->dspless_mode_selected)
+ return;
+
/* disable IPC interrupt */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIC,
HDA_DSP_ADSPIC_IPC, 0);
@@ -318,10 +354,13 @@ void hda_dsp_ipc_int_disable(struct snd_sof_dev *sdev)
static int hda_dsp_wait_d0i3c_done(struct snd_sof_dev *sdev)
{
- struct hdac_bus *bus = sof_to_bus(sdev);
int retry = HDA_DSP_REG_POLL_RETRY_COUNT;
+ struct snd_sof_pdata *pdata = sdev->pdata;
+ const struct sof_intel_dsp_desc *chip;
- while (snd_hdac_chip_readb(bus, VS_D0I3C) & SOF_HDA_VS_D0I3C_CIP) {
+ chip = get_chip_info(pdata);
+ while (snd_sof_dsp_read8(sdev, HDA_DSP_HDA_BAR, chip->d0i3_offset) &
+ SOF_HDA_VS_D0I3C_CIP) {
if (!retry--)
return -ETIMEDOUT;
usleep_range(10, 15);
@@ -332,50 +371,87 @@ static int hda_dsp_wait_d0i3c_done(struct snd_sof_dev *sdev)
static int hda_dsp_send_pm_gate_ipc(struct snd_sof_dev *sdev, u32 flags)
{
- struct sof_ipc_pm_gate pm_gate;
- struct sof_ipc_reply reply;
-
- memset(&pm_gate, 0, sizeof(pm_gate));
+ const struct sof_ipc_pm_ops *pm_ops = sof_ipc_get_ops(sdev, pm);
- /* configure pm_gate ipc message */
- pm_gate.hdr.size = sizeof(pm_gate);
- pm_gate.hdr.cmd = SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_GATE;
- pm_gate.flags = flags;
+ if (pm_ops && pm_ops->set_pm_gate)
+ return pm_ops->set_pm_gate(sdev, flags);
- /* send pm_gate ipc to dsp */
- return sof_ipc_tx_message_no_pm(sdev->ipc, pm_gate.hdr.cmd,
- &pm_gate, sizeof(pm_gate), &reply,
- sizeof(reply));
+ return 0;
}
static int hda_dsp_update_d0i3c_register(struct snd_sof_dev *sdev, u8 value)
{
- struct hdac_bus *bus = sof_to_bus(sdev);
+ struct snd_sof_pdata *pdata = sdev->pdata;
+ const struct sof_intel_dsp_desc *chip;
int ret;
+ u8 reg;
+
+ chip = get_chip_info(pdata);
/* Write to D0I3C after Command-In-Progress bit is cleared */
ret = hda_dsp_wait_d0i3c_done(sdev);
if (ret < 0) {
- dev_err(bus->dev, "CIP timeout before D0I3C update!\n");
+ dev_err(sdev->dev, "CIP timeout before D0I3C update!\n");
return ret;
}
/* Update D0I3C register */
- snd_hdac_chip_updateb(bus, VS_D0I3C, SOF_HDA_VS_D0I3C_I3, value);
+ snd_sof_dsp_update8(sdev, HDA_DSP_HDA_BAR, chip->d0i3_offset,
+ SOF_HDA_VS_D0I3C_I3, value);
+
+ /*
+ * The value written to the D0I3C::I3 bit may not be taken into account immediately.
+ * A delay is recommended before checking if D0I3C::CIP is cleared
+ */
+ usleep_range(30, 40);
/* Wait for cmd in progress to be cleared before exiting the function */
ret = hda_dsp_wait_d0i3c_done(sdev);
if (ret < 0) {
- dev_err(bus->dev, "CIP timeout after D0I3C update!\n");
+ dev_err(sdev->dev, "CIP timeout after D0I3C update!\n");
return ret;
}
- dev_vdbg(bus->dev, "D0I3C updated, register = 0x%x\n",
- snd_hdac_chip_readb(bus, VS_D0I3C));
+ reg = snd_sof_dsp_read8(sdev, HDA_DSP_HDA_BAR, chip->d0i3_offset);
+ /* Confirm d0i3 state changed with paranoia check */
+ if ((reg ^ value) & SOF_HDA_VS_D0I3C_I3) {
+ dev_err(sdev->dev, "failed to update D0I3C!\n");
+ return -EIO;
+ }
+
+ trace_sof_intel_D0I3C_updated(sdev, reg);
return 0;
}
+/*
+ * d0i3 streaming is enabled if all the active streams can
+ * work in d0i3 state and playback is enabled
+ */
+static bool hda_dsp_d0i3_streaming_applicable(struct snd_sof_dev *sdev)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_sof_pcm *spcm;
+ bool playback_active = false;
+ int dir;
+
+ list_for_each_entry(spcm, &sdev->pcm_list, list) {
+ for_each_pcm_streams(dir) {
+ substream = spcm->stream[dir].substream;
+ if (!substream || !substream->runtime)
+ continue;
+
+ if (!spcm->stream[dir].d0i3_compatible)
+ return false;
+
+ if (dir == SNDRV_PCM_STREAM_PLAYBACK)
+ playback_active = true;
+ }
+ }
+
+ return playback_active;
+}
+
static int hda_dsp_set_D0_state(struct snd_sof_dev *sdev,
const struct sof_dsp_power_state *target_state)
{
@@ -413,10 +489,13 @@ static int hda_dsp_set_D0_state(struct snd_sof_dev *sdev,
* when the DSP enters D0I3 while the system is in S0
* for debug purpose.
*/
- if (!sdev->dtrace_is_supported ||
+ if (!sdev->fw_trace_is_supported ||
!hda_enable_trace_D0I3_S0 ||
sdev->system_suspend_target != SOF_SUSPEND_NONE)
flags = HDA_PM_NO_DMA_TRACE;
+
+ if (hda_dsp_d0i3_streaming_applicable(sdev))
+ flags |= HDA_PM_PG_STREAMING;
} else {
/* prevent power gating in D0I0 */
flags = HDA_PM_PPG;
@@ -478,15 +557,9 @@ static void hda_dsp_state_log(struct snd_sof_dev *sdev)
case SOF_DSP_PM_D2:
dev_dbg(sdev->dev, "Current DSP power state: D2\n");
break;
- case SOF_DSP_PM_D3_HOT:
- dev_dbg(sdev->dev, "Current DSP power state: D3_HOT\n");
- break;
case SOF_DSP_PM_D3:
dev_dbg(sdev->dev, "Current DSP power state: D3\n");
break;
- case SOF_DSP_PM_D3_COLD:
- dev_dbg(sdev->dev, "Current DSP power state: D3_COLD\n");
- break;
default:
dev_dbg(sdev->dev, "Unknown DSP power state: %d\n",
sdev->dsp_power_state.state);
@@ -501,31 +574,11 @@ static void hda_dsp_state_log(struct snd_sof_dev *sdev)
* is called again either because of a new IPC sent to the DSP or
* during system suspend/resume.
*/
-int hda_dsp_set_power_state(struct snd_sof_dev *sdev,
- const struct sof_dsp_power_state *target_state)
+static int hda_dsp_set_power_state(struct snd_sof_dev *sdev,
+ const struct sof_dsp_power_state *target_state)
{
int ret = 0;
- /*
- * When the DSP is already in D0I3 and the target state is D0I3,
- * it could be the case that the DSP is in D0I3 during S0
- * and the system is suspending to S0Ix. Therefore,
- * hda_dsp_set_D0_state() must be called to disable trace DMA
- * by sending the PM_GATE IPC to the FW.
- */
- if (target_state->substate == SOF_HDA_DSP_PM_D0I3 &&
- sdev->system_suspend_target == SOF_SUSPEND_S0IX)
- goto set_state;
-
- /*
- * For all other cases, return without doing anything if
- * the DSP is already in the target state.
- */
- if (target_state->state == sdev->dsp_power_state.state &&
- target_state->substate == sdev->dsp_power_state.substate)
- return 0;
-
-set_state:
switch (target_state->state) {
case SOF_DSP_PM_D0:
ret = hda_dsp_set_D0_state(sdev, target_state);
@@ -557,6 +610,42 @@ set_state:
return ret;
}
+int hda_dsp_set_power_state_ipc3(struct snd_sof_dev *sdev,
+ const struct sof_dsp_power_state *target_state)
+{
+ /*
+ * When the DSP is already in D0I3 and the target state is D0I3,
+ * it could be the case that the DSP is in D0I3 during S0
+ * and the system is suspending to S0Ix. Therefore,
+ * hda_dsp_set_D0_state() must be called to disable trace DMA
+ * by sending the PM_GATE IPC to the FW.
+ */
+ if (target_state->substate == SOF_HDA_DSP_PM_D0I3 &&
+ sdev->system_suspend_target == SOF_SUSPEND_S0IX)
+ return hda_dsp_set_power_state(sdev, target_state);
+
+ /*
+ * For all other cases, return without doing anything if
+ * the DSP is already in the target state.
+ */
+ if (target_state->state == sdev->dsp_power_state.state &&
+ target_state->substate == sdev->dsp_power_state.substate)
+ return 0;
+
+ return hda_dsp_set_power_state(sdev, target_state);
+}
+
+int hda_dsp_set_power_state_ipc4(struct snd_sof_dev *sdev,
+ const struct sof_dsp_power_state *target_state)
+{
+ /* Return without doing anything if the DSP is already in the target state */
+ if (target_state->state == sdev->dsp_power_state.state &&
+ target_state->substate == sdev->dsp_power_state.substate)
+ return 0;
+
+ return hda_dsp_set_power_state(sdev, target_state);
+}
+
/*
* Audio DSP states may transform as below:-
*
@@ -591,35 +680,50 @@ static int hda_suspend(struct snd_sof_dev *sdev, bool runtime_suspend)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
const struct sof_intel_dsp_desc *chip = hda->desc;
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
struct hdac_bus *bus = sof_to_bus(sdev);
-#endif
- int ret;
+ int ret, j;
- hda_sdw_int_enable(sdev, false);
+ /*
+ * The memory used for IMR boot loses its content in deeper than S3 state
+ * We must not try IMR boot on next power up (as it will fail).
+ *
+ * In case of firmware crash or boot failure set the skip_imr_boot to true
+ * as well in order to try to re-load the firmware to do a 'cold' boot.
+ */
+ if (sdev->system_suspend_target > SOF_SUSPEND_S3 ||
+ sdev->fw_state == SOF_FW_CRASHED ||
+ sdev->fw_state == SOF_FW_BOOT_FAILED)
+ hda->skip_imr_boot = true;
- /* disable IPC interrupts */
- hda_dsp_ipc_int_disable(sdev);
+ ret = chip->disable_interrupts(sdev);
+ if (ret < 0)
+ return ret;
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
- if (runtime_suspend)
- hda_codec_jack_wake_enable(sdev);
+ /* make sure that no irq handler is pending before shutdown */
+ synchronize_irq(sdev->ipc_irq);
- /* power down all hda link */
- snd_hdac_ext_bus_link_power_down_all(bus);
-#endif
+ hda_codec_jack_wake_enable(sdev, runtime_suspend);
+
+ /* power down all hda links */
+ hda_bus_ml_suspend(bus);
- /* power down DSP */
- ret = hda_dsp_core_reset_power_down(sdev, chip->cores_mask);
+ if (sdev->dspless_mode_selected)
+ goto skip_dsp;
+
+ ret = chip->power_down_dsp(sdev);
if (ret < 0) {
- dev_err(sdev->dev,
- "error: failed to power down core during suspend\n");
+ dev_err(sdev->dev, "failed to power down DSP during suspend\n");
return ret;
}
+ /* reset ref counts for all cores */
+ for (j = 0; j < chip->cores_num; j++)
+ sdev->dsp_core_ref_count[j] = 0;
+
/* disable ppcap interrupt */
hda_dsp_ctrl_ppcap_enable(sdev, false);
hda_dsp_ctrl_ppcap_int_enable(sdev, false);
+skip_dsp:
/* disable hda bus irq and streams */
hda_dsp_ctrl_stop_chip(sdev);
@@ -644,10 +748,7 @@ static int hda_suspend(struct snd_sof_dev *sdev, bool runtime_suspend)
static int hda_resume(struct snd_sof_dev *sdev, bool runtime_resume)
{
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
- struct hdac_bus *bus = sof_to_bus(sdev);
- struct hdac_ext_link *hlink = NULL;
-#endif
+ const struct sof_intel_dsp_desc *chip;
int ret;
/* display codec must be powered before link reset */
@@ -660,32 +761,33 @@ static int hda_resume(struct snd_sof_dev *sdev, bool runtime_resume)
snd_sof_pci_update_bits(sdev, PCI_TCSEL, 0x07, 0);
/* reset and start hda controller */
- ret = hda_dsp_ctrl_init_chip(sdev, true);
+ ret = hda_dsp_ctrl_init_chip(sdev);
if (ret < 0) {
dev_err(sdev->dev,
"error: failed to start controller after resume\n");
- return ret;
+ goto cleanup;
}
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
/* check jack status */
- if (runtime_resume)
- hda_codec_jack_check(sdev);
+ if (runtime_resume) {
+ hda_codec_jack_wake_enable(sdev, false);
+ if (sdev->system_suspend_target == SOF_SUSPEND_NONE)
+ hda_codec_jack_check(sdev);
+ }
- /* turn off the links that were off before suspend */
- list_for_each_entry(hlink, &bus->hlink_list, list) {
- if (!hlink->ref_count)
- snd_hdac_ext_bus_link_power_down(hlink);
+ if (!sdev->dspless_mode_selected) {
+ /* enable ppcap interrupt */
+ hda_dsp_ctrl_ppcap_enable(sdev, true);
+ hda_dsp_ctrl_ppcap_int_enable(sdev, true);
}
- /* check dma status and clean up CORB/RIRB buffers */
- if (!bus->cmd_dma_state)
- snd_hdac_bus_stop_cmd_io(bus);
-#endif
+ chip = get_chip_info(sdev->pdata);
+ if (chip && chip->hw_ip_version >= SOF_INTEL_ACE_2_0)
+ hda_sdw_int_enable(sdev, true);
- /* enable ppcap interrupt */
- hda_dsp_ctrl_ppcap_enable(sdev, true);
- hda_dsp_ctrl_ppcap_int_enable(sdev, true);
+cleanup:
+ /* display codec can powered off after controller init */
+ hda_codec_i915_display_power(sdev, false);
return 0;
}
@@ -693,39 +795,26 @@ static int hda_resume(struct snd_sof_dev *sdev, bool runtime_resume)
int hda_dsp_resume(struct snd_sof_dev *sdev)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+ struct hdac_bus *bus = sof_to_bus(sdev);
struct pci_dev *pci = to_pci_dev(sdev->dev);
const struct sof_dsp_power_state target_state = {
.state = SOF_DSP_PM_D0,
.substate = SOF_HDA_DSP_PM_D0I0,
};
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
- struct hdac_bus *bus = sof_to_bus(sdev);
- struct hdac_ext_link *hlink = NULL;
-#endif
int ret;
/* resume from D0I3 */
if (sdev->dsp_power_state.state == SOF_DSP_PM_D0) {
- hda_codec_i915_display_power(sdev, true);
-
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
- /* power up links that were active before suspend */
- list_for_each_entry(hlink, &bus->hlink_list, list) {
- if (hlink->ref_count) {
- ret = snd_hdac_ext_bus_link_power_up(hlink);
- if (ret < 0) {
- dev_dbg(sdev->dev,
- "error %x in %s: failed to power up links",
- ret, __func__);
- return ret;
- }
- }
+ ret = hda_bus_ml_resume(bus);
+ if (ret < 0) {
+ dev_err(sdev->dev,
+ "error %d in %s: failed to power up links",
+ ret, __func__);
+ return ret;
}
/* set up CORB/RIRB buffers if was on before suspend */
- if (bus->cmd_dma_state)
- snd_hdac_bus_init_cmd_io(bus);
-#endif
+ hda_codec_resume_cmd_io(sdev);
/* Set DSP power state */
ret = snd_sof_dsp_set_power_state(sdev, &target_state);
@@ -736,7 +825,7 @@ int hda_dsp_resume(struct snd_sof_dev *sdev)
}
/* restore L1SEN bit */
- if (hda->l1_support_changed)
+ if (hda->l1_disabled)
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
HDA_VS_INTEL_EM2,
HDA_VS_INTEL_EM2_L1SEN, 0);
@@ -785,11 +874,17 @@ int hda_dsp_runtime_idle(struct snd_sof_dev *sdev)
int hda_dsp_runtime_suspend(struct snd_sof_dev *sdev)
{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
const struct sof_dsp_power_state target_state = {
.state = SOF_DSP_PM_D3,
};
int ret;
+ if (!sdev->dspless_mode_selected) {
+ /* cancel any attempt for DSP D0I3 */
+ cancel_delayed_work_sync(&hda->d0i3_work);
+ }
+
/* stop hda controller and power dsp off */
ret = hda_suspend(sdev, true);
if (ret < 0)
@@ -810,13 +905,12 @@ int hda_dsp_suspend(struct snd_sof_dev *sdev, u32 target_state)
};
int ret;
- /* cancel any attempt for DSP D0I3 */
- cancel_delayed_work_sync(&hda->d0i3_work);
+ if (!sdev->dspless_mode_selected) {
+ /* cancel any attempt for DSP D0I3 */
+ cancel_delayed_work_sync(&hda->d0i3_work);
+ }
if (target_state == SOF_DSP_PM_D0) {
- /* we can't keep a wakeref to display driver at suspend */
- hda_codec_i915_display_power(sdev, false);
-
/* Set DSP power state */
ret = snd_sof_dsp_set_power_state(sdev, &target_dsp_state);
if (ret < 0) {
@@ -827,26 +921,21 @@ int hda_dsp_suspend(struct snd_sof_dev *sdev, u32 target_state)
}
/* enable L1SEN to make sure the system can enter S0Ix */
- hda->l1_support_changed =
- snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
- HDA_VS_INTEL_EM2,
- HDA_VS_INTEL_EM2_L1SEN,
- HDA_VS_INTEL_EM2_L1SEN);
+ if (hda->l1_disabled)
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_EM2,
+ HDA_VS_INTEL_EM2_L1SEN, HDA_VS_INTEL_EM2_L1SEN);
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
/* stop the CORB/RIRB DMA if it is On */
- if (bus->cmd_dma_state)
- snd_hdac_bus_stop_cmd_io(bus);
+ hda_codec_suspend_cmd_io(sdev);
/* no link can be powered in s0ix state */
- ret = snd_hdac_ext_bus_link_power_down_all(bus);
+ ret = hda_bus_ml_suspend(bus);
if (ret < 0) {
- dev_dbg(sdev->dev,
+ dev_err(sdev->dev,
"error %d in %s: failed to power down links",
ret, __func__);
return ret;
}
-#endif
/* enable the system waking up via IPC IRQ */
enable_irq_wake(pci->irq);
@@ -864,46 +953,94 @@ int hda_dsp_suspend(struct snd_sof_dev *sdev, u32 target_state)
return snd_sof_dsp_set_power_state(sdev, &target_dsp_state);
}
-int hda_dsp_set_hw_params_upon_resume(struct snd_sof_dev *sdev)
+static unsigned int hda_dsp_check_for_dma_streams(struct snd_sof_dev *sdev)
{
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
struct hdac_bus *bus = sof_to_bus(sdev);
- struct snd_soc_pcm_runtime *rtd;
- struct hdac_ext_stream *stream;
- struct hdac_ext_link *link;
struct hdac_stream *s;
- const char *name;
- int stream_tag;
+ unsigned int active_streams = 0;
+ int sd_offset;
+ u32 val;
- /* set internal flag for BE */
list_for_each_entry(s, &bus->stream_list, list) {
- stream = stream_to_hdac_ext_stream(s);
+ sd_offset = SOF_STREAM_SD_OFFSET(s);
+ val = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR,
+ sd_offset);
+ if (val & SOF_HDA_SD_CTL_DMA_START)
+ active_streams |= BIT(s->index);
+ }
- /*
- * clear stream. This should already be taken care for running
- * streams when the SUSPEND trigger is called. But paused
- * streams do not get suspended, so this needs to be done
- * explicitly during suspend.
- */
- if (stream->link_substream) {
- rtd = asoc_substream_to_rtd(stream->link_substream);
- name = asoc_rtd_to_codec(rtd, 0)->component->name;
- link = snd_hdac_ext_bus_get_link(bus, name);
- if (!link)
- return -EINVAL;
+ return active_streams;
+}
- stream->link_prepared = 0;
+static int hda_dsp_s5_quirk(struct snd_sof_dev *sdev)
+{
+ int ret;
- if (hdac_stream(stream)->direction ==
- SNDRV_PCM_STREAM_CAPTURE)
- continue;
+ /*
+ * Do not assume a certain timing between the prior
+ * suspend flow, and running of this quirk function.
+ * This is needed if the controller was just put
+ * to reset before calling this function.
+ */
+ usleep_range(500, 1000);
- stream_tag = hdac_stream(stream)->stream_tag;
- snd_hdac_ext_link_clear_stream_id(link, stream_tag);
- }
+ /*
+ * Take controller out of reset to flush DMA
+ * transactions.
+ */
+ ret = hda_dsp_ctrl_link_reset(sdev, false);
+ if (ret < 0)
+ return ret;
+
+ usleep_range(500, 1000);
+
+ /* Restore state for shutdown, back to reset */
+ ret = hda_dsp_ctrl_link_reset(sdev, true);
+ if (ret < 0)
+ return ret;
+
+ return ret;
+}
+
+int hda_dsp_shutdown_dma_flush(struct snd_sof_dev *sdev)
+{
+ unsigned int active_streams;
+ int ret, ret2;
+
+ /* check if DMA cleanup has been successful */
+ active_streams = hda_dsp_check_for_dma_streams(sdev);
+
+ sdev->system_suspend_target = SOF_SUSPEND_S3;
+ ret = snd_sof_suspend(sdev->dev);
+
+ if (active_streams) {
+ dev_warn(sdev->dev,
+ "There were active DSP streams (%#x) at shutdown, trying to recover\n",
+ active_streams);
+ ret2 = hda_dsp_s5_quirk(sdev);
+ if (ret2 < 0)
+ dev_err(sdev->dev, "shutdown recovery failed (%d)\n", ret2);
}
-#endif
- return 0;
+
+ return ret;
+}
+
+int hda_dsp_shutdown(struct snd_sof_dev *sdev)
+{
+ sdev->system_suspend_target = SOF_SUSPEND_S3;
+ return snd_sof_suspend(sdev->dev);
+}
+
+int hda_dsp_set_hw_params_upon_resume(struct snd_sof_dev *sdev)
+{
+ int ret;
+
+ /* make sure all DAI resources are freed */
+ ret = hda_dsp_dais_suspend(sdev);
+ if (ret < 0)
+ dev_warn(sdev->dev, "%s: failure in hda_dsp_dais_suspend\n", __func__);
+
+ return ret;
}
void hda_dsp_d0i3_work(struct work_struct *work)
@@ -913,19 +1050,15 @@ void hda_dsp_d0i3_work(struct work_struct *work)
d0i3_work.work);
struct hdac_bus *bus = &hdev->hbus.core;
struct snd_sof_dev *sdev = dev_get_drvdata(bus->dev);
- struct sof_dsp_power_state target_state;
+ struct sof_dsp_power_state target_state = {
+ .state = SOF_DSP_PM_D0,
+ .substate = SOF_HDA_DSP_PM_D0I3,
+ };
int ret;
- target_state.state = SOF_DSP_PM_D0;
-
/* DSP can enter D0I3 iff only D0I3-compatible streams are active */
- if (snd_sof_dsp_only_d0i3_compatible_stream_active(sdev))
- target_state.substate = SOF_HDA_DSP_PM_D0I3;
- else
- target_state.substate = SOF_HDA_DSP_PM_D0I0;
-
- /* remain in D0I0 */
- if (target_state.substate == SOF_HDA_DSP_PM_D0I0)
+ if (!snd_sof_dsp_only_d0i3_compatible_stream_active(sdev))
+ /* remain in D0I0 */
return;
/* This can fail but error cannot be propagated */
@@ -935,3 +1068,51 @@ void hda_dsp_d0i3_work(struct work_struct *work)
"error: failed to set DSP state %d substate %d\n",
target_state.state, target_state.substate);
}
+
+int hda_dsp_core_get(struct snd_sof_dev *sdev, int core)
+{
+ const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm;
+ int ret, ret1;
+
+ /* power up core */
+ ret = hda_dsp_enable_core(sdev, BIT(core));
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to power up core %d with err: %d\n",
+ core, ret);
+ return ret;
+ }
+
+ /* No need to send IPC for primary core or if FW boot is not complete */
+ if (sdev->fw_state != SOF_FW_BOOT_COMPLETE || core == SOF_DSP_PRIMARY_CORE)
+ return 0;
+
+ /* No need to continue the set_core_state ops is not available */
+ if (!pm_ops->set_core_state)
+ return 0;
+
+ /* Now notify DSP for secondary cores */
+ ret = pm_ops->set_core_state(sdev, core, true);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to enable secondary core '%d' failed with %d\n",
+ core, ret);
+ goto power_down;
+ }
+
+ return ret;
+
+power_down:
+ /* power down core if it is host managed and return the original error if this fails too */
+ ret1 = hda_dsp_core_reset_power_down(sdev, BIT(core));
+ if (ret1 < 0)
+ dev_err(sdev->dev, "failed to power down core: %d with err: %d\n", core, ret1);
+
+ return ret;
+}
+
+int hda_dsp_disable_interrupts(struct snd_sof_dev *sdev)
+{
+ hda_sdw_int_enable(sdev, false);
+ hda_dsp_ipc_int_disable(sdev);
+
+ return 0;
+}
diff --git a/sound/soc/sof/intel/hda-ipc.c b/sound/soc/sof/intel/hda-ipc.c
index c91aa951df22..a838dddb1d32 100644
--- a/sound/soc/sof/intel/hda-ipc.c
+++ b/sound/soc/sof/intel/hda-ipc.c
@@ -15,6 +15,8 @@
* Hardware interface for generic Intel audio DSP HDA IP
*/
+#include <sound/sof/ipc4/header.h>
+#include <trace/events/sof_intel.h>
#include "../ops.h"
#include "hda.h"
@@ -65,12 +67,63 @@ int hda_dsp_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
return 0;
}
+static inline bool hda_dsp_ipc4_pm_msg(u32 primary)
+{
+ /* pm setting is only supported by module msg */
+ if (SOF_IPC4_MSG_IS_MODULE_MSG(primary) != SOF_IPC4_MODULE_MSG)
+ return false;
+
+ if (SOF_IPC4_MSG_TYPE_GET(primary) == SOF_IPC4_MOD_SET_DX ||
+ SOF_IPC4_MSG_TYPE_GET(primary) == SOF_IPC4_MOD_SET_D0IX)
+ return true;
+
+ return false;
+}
+
+void hda_dsp_ipc4_schedule_d0i3_work(struct sof_intel_hda_dev *hdev,
+ struct snd_sof_ipc_msg *msg)
+{
+ struct sof_ipc4_msg *msg_data = msg->msg_data;
+
+ /* Schedule a delayed work for d0i3 entry after sending non-pm ipc msg */
+ if (hda_dsp_ipc4_pm_msg(msg_data->primary))
+ return;
+
+ mod_delayed_work(system_wq, &hdev->d0i3_work,
+ msecs_to_jiffies(SOF_HDA_D0I3_WORK_DELAY_MS));
+}
+
+int hda_dsp_ipc4_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
+{
+ struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
+ struct sof_ipc4_msg *msg_data = msg->msg_data;
+
+ if (hda_ipc4_tx_is_busy(sdev)) {
+ hdev->delayed_ipc_tx_msg = msg;
+ return 0;
+ }
+
+ hdev->delayed_ipc_tx_msg = NULL;
+
+ /* send the message via mailbox */
+ if (msg_data->data_size)
+ sof_mailbox_write(sdev, sdev->host_box.offset, msg_data->data_ptr,
+ msg_data->data_size);
+
+ snd_sof_dsp_write(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCIE, msg_data->extension);
+ snd_sof_dsp_write(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCI,
+ msg_data->primary | HDA_DSP_REG_HIPCI_BUSY);
+
+ hda_dsp_ipc4_schedule_d0i3_work(hdev, msg);
+
+ return 0;
+}
+
void hda_dsp_ipc_get_reply(struct snd_sof_dev *sdev)
{
struct snd_sof_ipc_msg *msg = sdev->msg;
struct sof_ipc_reply reply;
struct sof_ipc_cmd_hdr *hdr;
- int ret = 0;
/*
* Sometimes, there is unexpected reply ipc arriving. The reply
@@ -94,35 +147,93 @@ void hda_dsp_ipc_get_reply(struct snd_sof_dev *sdev)
reply.hdr.cmd = SOF_IPC_GLB_REPLY;
reply.hdr.size = sizeof(reply);
memcpy(msg->reply_data, &reply, sizeof(reply));
- goto out;
+
+ msg->reply_error = 0;
+ } else {
+ snd_sof_ipc_get_reply(sdev);
}
+}
- /* get IPC reply from DSP in the mailbox */
- sof_mailbox_read(sdev, sdev->host_box.offset, &reply,
- sizeof(reply));
+irqreturn_t hda_dsp_ipc4_irq_thread(int irq, void *context)
+{
+ struct sof_ipc4_msg notification_data = {{ 0 }};
+ struct snd_sof_dev *sdev = context;
+ bool ack_received = false;
+ bool ipc_irq = false;
+ u32 hipcie, hipct;
- if (reply.error < 0) {
- memcpy(msg->reply_data, &reply, sizeof(reply));
- ret = reply.error;
- } else {
- /* reply correct size ? */
- if (reply.hdr.size != msg->reply_size &&
- /* getter payload is never known upfront */
- !(reply.hdr.cmd & SOF_IPC_GLB_PROBE)) {
- dev_err(sdev->dev, "error: reply expected %zu got %u bytes\n",
- msg->reply_size, reply.hdr.size);
- ret = -EINVAL;
+ hipcie = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCIE);
+ hipct = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCT);
+
+ if (hipcie & HDA_DSP_REG_HIPCIE_DONE) {
+ /* DSP received the message */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCCTL,
+ HDA_DSP_REG_HIPCCTL_DONE, 0);
+ hda_dsp_ipc_dsp_done(sdev);
+
+ ipc_irq = true;
+ ack_received = true;
+ }
+
+ if (hipct & HDA_DSP_REG_HIPCT_BUSY) {
+ /* Message from DSP (reply or notification) */
+ u32 hipcte = snd_sof_dsp_read(sdev, HDA_DSP_BAR,
+ HDA_DSP_REG_HIPCTE);
+ u32 primary = hipct & HDA_DSP_REG_HIPCT_MSG_MASK;
+ u32 extension = hipcte & HDA_DSP_REG_HIPCTE_MSG_MASK;
+
+ /* mask BUSY interrupt */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCCTL,
+ HDA_DSP_REG_HIPCCTL_BUSY, 0);
+
+ if (primary & SOF_IPC4_MSG_DIR_MASK) {
+ /* Reply received */
+ if (likely(sdev->fw_state == SOF_FW_BOOT_COMPLETE)) {
+ struct sof_ipc4_msg *data = sdev->ipc->msg.reply_data;
+
+ data->primary = primary;
+ data->extension = extension;
+
+ spin_lock_irq(&sdev->ipc_lock);
+
+ snd_sof_ipc_get_reply(sdev);
+ hda_dsp_ipc_host_done(sdev);
+ snd_sof_ipc_reply(sdev, data->primary);
+
+ spin_unlock_irq(&sdev->ipc_lock);
+ } else {
+ dev_dbg_ratelimited(sdev->dev,
+ "IPC reply before FW_READY: %#x|%#x\n",
+ primary, extension);
+ }
+ } else {
+ /* Notification received */
+
+ notification_data.primary = primary;
+ notification_data.extension = extension;
+ sdev->ipc->msg.rx_data = &notification_data;
+ snd_sof_ipc_msgs_rx(sdev);
+ sdev->ipc->msg.rx_data = NULL;
+
+ /* Let DSP know that we have finished processing the message */
+ hda_dsp_ipc_host_done(sdev);
}
- /* read the message */
- if (msg->reply_size > 0)
- sof_mailbox_read(sdev, sdev->host_box.offset,
- msg->reply_data, msg->reply_size);
+ ipc_irq = true;
}
-out:
- msg->reply_error = ret;
+ if (!ipc_irq)
+ /* This interrupt is not shared so no need to return IRQ_NONE. */
+ dev_dbg_ratelimited(sdev->dev, "nothing to do in IPC IRQ thread\n");
+
+ if (ack_received) {
+ struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
+ if (hdev->delayed_ipc_tx_msg)
+ hda_dsp_ipc4_send_msg(sdev, hdev->delayed_ipc_tx_msg);
+ }
+
+ return IRQ_HANDLED;
}
/* IPC handler thread */
@@ -149,9 +260,7 @@ irqreturn_t hda_dsp_ipc_irq_thread(int irq, void *context)
msg = hipci & HDA_DSP_REG_HIPCI_MSG_MASK;
msg_ext = hipcie & HDA_DSP_REG_HIPCIE_MSG_MASK;
- dev_vdbg(sdev->dev,
- "ipc: firmware response, msg:0x%x, msg_ext:0x%x\n",
- msg, msg_ext);
+ trace_sof_intel_ipc_firmware_response(sdev, msg, msg_ext);
/* mask Done interrupt */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
@@ -168,16 +277,21 @@ irqreturn_t hda_dsp_ipc_irq_thread(int irq, void *context)
* place, the message might not yet be marked as expecting a
* reply.
*/
- spin_lock_irq(&sdev->ipc_lock);
+ if (likely(sdev->fw_state == SOF_FW_BOOT_COMPLETE)) {
+ spin_lock_irq(&sdev->ipc_lock);
- /* handle immediate reply from DSP core */
- hda_dsp_ipc_get_reply(sdev);
- snd_sof_ipc_reply(sdev, msg);
+ /* handle immediate reply from DSP core */
+ hda_dsp_ipc_get_reply(sdev);
+ snd_sof_ipc_reply(sdev, msg);
- /* set the done bit */
- hda_dsp_ipc_dsp_done(sdev);
+ /* set the done bit */
+ hda_dsp_ipc_dsp_done(sdev);
- spin_unlock_irq(&sdev->ipc_lock);
+ spin_unlock_irq(&sdev->ipc_lock);
+ } else {
+ dev_dbg_ratelimited(sdev->dev, "IPC reply before FW_READY: %#x\n",
+ msg);
+ }
ipc_irq = true;
}
@@ -187,9 +301,7 @@ irqreturn_t hda_dsp_ipc_irq_thread(int irq, void *context)
msg = hipct & HDA_DSP_REG_HIPCT_MSG_MASK;
msg_ext = hipcte & HDA_DSP_REG_HIPCTE_MSG_MASK;
- dev_vdbg(sdev->dev,
- "ipc: firmware initiated, msg:0x%x, msg_ext:0x%x\n",
- msg, msg_ext);
+ trace_sof_intel_ipc_firmware_initiated(sdev, msg, msg_ext);
/* mask BUSY interrupt */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
@@ -198,8 +310,23 @@ irqreturn_t hda_dsp_ipc_irq_thread(int irq, void *context)
/* handle messages from DSP */
if ((hipct & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) {
- /* this is a PANIC message !! */
- snd_sof_dsp_panic(sdev, HDA_DSP_PANIC_OFFSET(msg_ext));
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+ bool non_recoverable = true;
+
+ /*
+ * This is a PANIC message!
+ *
+ * If it is arriving during firmware boot and it is not
+ * the last boot attempt then change the non_recoverable
+ * to false as the DSP might be able to boot in the next
+ * iteration(s)
+ */
+ if (sdev->fw_state == SOF_FW_BOOT_IN_PROGRESS &&
+ hda->boot_iteration < HDA_FW_BOOT_ATTEMPTS)
+ non_recoverable = false;
+
+ snd_sof_dsp_panic(sdev, HDA_DSP_PANIC_OFFSET(msg_ext),
+ non_recoverable);
} else {
/* normal message - process normally */
snd_sof_ipc_msgs_rx(sdev);
@@ -224,12 +351,16 @@ irqreturn_t hda_dsp_ipc_irq_thread(int irq, void *context)
/* Check if an IPC IRQ occurred */
bool hda_dsp_check_ipc_irq(struct snd_sof_dev *sdev)
{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
bool ret = false;
u32 irq_status;
+ if (sdev->dspless_mode_selected)
+ return false;
+
/* store status */
irq_status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIS);
- dev_vdbg(sdev->dev, "irq handler: irq_status:0x%x\n", irq_status);
+ trace_sof_intel_hda_irq_ipc_check(sdev, irq_status);
/* invalid message ? */
if (irq_status == 0xffffffff)
@@ -239,6 +370,13 @@ bool hda_dsp_check_ipc_irq(struct snd_sof_dev *sdev)
if (irq_status & HDA_DSP_ADSPIS_IPC)
ret = true;
+ /* CLDMA message ? */
+ if (irq_status & HDA_DSP_ADSPIS_CL_DMA) {
+ hda->code_loading = 0;
+ wake_up(&hda->waitq);
+ ret = false;
+ }
+
out:
return ret;
}
@@ -253,48 +391,51 @@ int hda_dsp_ipc_get_window_offset(struct snd_sof_dev *sdev, u32 id)
return SRAM_WINDOW_OFFSET(id);
}
-void hda_ipc_msg_data(struct snd_sof_dev *sdev,
- struct snd_pcm_substream *substream,
- void *p, size_t sz)
+int hda_ipc_msg_data(struct snd_sof_dev *sdev,
+ struct snd_sof_pcm_stream *sps,
+ void *p, size_t sz)
{
- if (!substream || !sdev->stream_box.size) {
+ if (!sps || !sdev->stream_box.size) {
sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz);
} else {
+ struct snd_pcm_substream *substream = sps->substream;
struct hdac_stream *hstream = substream->runtime->private_data;
struct sof_intel_hda_stream *hda_stream;
hda_stream = container_of(hstream,
struct sof_intel_hda_stream,
- hda_stream.hstream);
+ hext_stream.hstream);
/* The stream might already be closed */
- if (hstream)
- sof_mailbox_read(sdev, hda_stream->stream.posn_offset,
- p, sz);
+ if (!hstream)
+ return -ESTRPIPE;
+
+ sof_mailbox_read(sdev, hda_stream->sof_intel_stream.posn_offset, p, sz);
}
+
+ return 0;
}
-int hda_ipc_pcm_params(struct snd_sof_dev *sdev,
- struct snd_pcm_substream *substream,
- const struct sof_ipc_pcm_params_reply *reply)
+int hda_set_stream_data_offset(struct snd_sof_dev *sdev,
+ struct snd_sof_pcm_stream *sps,
+ size_t posn_offset)
{
+ struct snd_pcm_substream *substream = sps->substream;
struct hdac_stream *hstream = substream->runtime->private_data;
struct sof_intel_hda_stream *hda_stream;
- /* validate offset */
- size_t posn_offset = reply->posn_offset;
hda_stream = container_of(hstream, struct sof_intel_hda_stream,
- hda_stream.hstream);
+ hext_stream.hstream);
/* check for unaligned offset or overflow */
if (posn_offset > sdev->stream_box.size ||
posn_offset % sizeof(struct sof_ipc_stream_posn) != 0)
return -EINVAL;
- hda_stream->stream.posn_offset = sdev->stream_box.offset + posn_offset;
+ hda_stream->sof_intel_stream.posn_offset = sdev->stream_box.offset + posn_offset;
dev_dbg(sdev->dev, "pcm: stream dir %d, posn mailbox offset is %zu",
- substream->stream, hda_stream->stream.posn_offset);
+ substream->stream, hda_stream->sof_intel_stream.posn_offset);
return 0;
}
diff --git a/sound/soc/sof/intel/hda-ipc.h b/sound/soc/sof/intel/hda-ipc.h
index ade4c3191a39..8ec5e9f6f8d7 100644
--- a/sound/soc/sof/intel/hda-ipc.h
+++ b/sound/soc/sof/intel/hda-ipc.h
@@ -48,4 +48,9 @@
#define HDA_PM_PG_STREAMING BIT(1)
#define HDA_PM_PG_RSVD BIT(0)
+irqreturn_t cnl_ipc_irq_thread(int irq, void *context);
+int cnl_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg);
+void cnl_ipc_dump(struct snd_sof_dev *sdev);
+void cnl_ipc4_dump(struct snd_sof_dev *sdev);
+
#endif
diff --git a/sound/soc/sof/intel/hda-loader-skl.c b/sound/soc/sof/intel/hda-loader-skl.c
new file mode 100644
index 000000000000..1e77ca936f80
--- /dev/null
+++ b/sound/soc/sof/intel/hda-loader-skl.c
@@ -0,0 +1,578 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2018-2022 Intel Corporation. All rights reserved.
+//
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/firmware.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <sound/hdaudio_ext.h>
+#include <sound/sof.h>
+#include <sound/pcm_params.h>
+
+#include "../sof-priv.h"
+#include "../ops.h"
+#include "hda.h"
+
+#define HDA_SKL_WAIT_TIMEOUT 500 /* 500 msec */
+#define HDA_SKL_CLDMA_MAX_BUFFER_SIZE (32 * PAGE_SIZE)
+
+/* Stream Reset */
+#define HDA_CL_SD_CTL_SRST_SHIFT 0
+#define HDA_CL_SD_CTL_SRST(x) (((x) & 0x1) << \
+ HDA_CL_SD_CTL_SRST_SHIFT)
+
+/* Stream Run */
+#define HDA_CL_SD_CTL_RUN_SHIFT 1
+#define HDA_CL_SD_CTL_RUN(x) (((x) & 0x1) << \
+ HDA_CL_SD_CTL_RUN_SHIFT)
+
+/* Interrupt On Completion Enable */
+#define HDA_CL_SD_CTL_IOCE_SHIFT 2
+#define HDA_CL_SD_CTL_IOCE(x) (((x) & 0x1) << \
+ HDA_CL_SD_CTL_IOCE_SHIFT)
+
+/* FIFO Error Interrupt Enable */
+#define HDA_CL_SD_CTL_FEIE_SHIFT 3
+#define HDA_CL_SD_CTL_FEIE(x) (((x) & 0x1) << \
+ HDA_CL_SD_CTL_FEIE_SHIFT)
+
+/* Descriptor Error Interrupt Enable */
+#define HDA_CL_SD_CTL_DEIE_SHIFT 4
+#define HDA_CL_SD_CTL_DEIE(x) (((x) & 0x1) << \
+ HDA_CL_SD_CTL_DEIE_SHIFT)
+
+/* FIFO Limit Change */
+#define HDA_CL_SD_CTL_FIFOLC_SHIFT 5
+#define HDA_CL_SD_CTL_FIFOLC(x) (((x) & 0x1) << \
+ HDA_CL_SD_CTL_FIFOLC_SHIFT)
+
+/* Stripe Control */
+#define HDA_CL_SD_CTL_STRIPE_SHIFT 16
+#define HDA_CL_SD_CTL_STRIPE(x) (((x) & 0x3) << \
+ HDA_CL_SD_CTL_STRIPE_SHIFT)
+
+/* Traffic Priority */
+#define HDA_CL_SD_CTL_TP_SHIFT 18
+#define HDA_CL_SD_CTL_TP(x) (((x) & 0x1) << \
+ HDA_CL_SD_CTL_TP_SHIFT)
+
+/* Bidirectional Direction Control */
+#define HDA_CL_SD_CTL_DIR_SHIFT 19
+#define HDA_CL_SD_CTL_DIR(x) (((x) & 0x1) << \
+ HDA_CL_SD_CTL_DIR_SHIFT)
+
+/* Stream Number */
+#define HDA_CL_SD_CTL_STRM_SHIFT 20
+#define HDA_CL_SD_CTL_STRM(x) (((x) & 0xf) << \
+ HDA_CL_SD_CTL_STRM_SHIFT)
+
+#define HDA_CL_SD_CTL_INT(x) \
+ (HDA_CL_SD_CTL_IOCE(x) | \
+ HDA_CL_SD_CTL_FEIE(x) | \
+ HDA_CL_SD_CTL_DEIE(x))
+
+#define HDA_CL_SD_CTL_INT_MASK \
+ (HDA_CL_SD_CTL_IOCE(1) | \
+ HDA_CL_SD_CTL_FEIE(1) | \
+ HDA_CL_SD_CTL_DEIE(1))
+
+#define DMA_ADDRESS_128_BITS_ALIGNMENT 7
+#define BDL_ALIGN(x) ((x) >> DMA_ADDRESS_128_BITS_ALIGNMENT)
+
+/* Buffer Descriptor List Lower Base Address */
+#define HDA_CL_SD_BDLPLBA_SHIFT 7
+#define HDA_CL_SD_BDLPLBA_MASK GENMASK(31, 7)
+#define HDA_CL_SD_BDLPLBA(x) \
+ ((BDL_ALIGN(lower_32_bits(x)) << HDA_CL_SD_BDLPLBA_SHIFT) & \
+ HDA_CL_SD_BDLPLBA_MASK)
+
+/* Buffer Descriptor List Upper Base Address */
+#define HDA_CL_SD_BDLPUBA(x) \
+ (upper_32_bits(x))
+
+/* Software Position in Buffer Enable */
+#define HDA_CL_SPBFIFO_SPBFCCTL_SPIBE_SHIFT 0
+#define HDA_CL_SPBFIFO_SPBFCCTL_SPIBE_MASK \
+ (1 << HDA_CL_SPBFIFO_SPBFCCTL_SPIBE_SHIFT)
+
+#define HDA_CL_SPBFIFO_SPBFCCTL_SPIBE(x) \
+ (((x) << HDA_CL_SPBFIFO_SPBFCCTL_SPIBE_SHIFT) & \
+ HDA_CL_SPBFIFO_SPBFCCTL_SPIBE_MASK)
+
+#define HDA_CL_DMA_SD_INT_COMPLETE 0x4
+
+static int cl_skl_cldma_setup_bdle(struct snd_sof_dev *sdev,
+ struct snd_dma_buffer *dmab_data,
+ __le32 **bdlp, int size, int with_ioc)
+{
+ phys_addr_t addr = virt_to_phys(dmab_data->area);
+ __le32 *bdl = *bdlp;
+
+ /*
+ * This code is simplified by using one fragment of physical memory and assuming
+ * all the code fits. This could be improved with scatter-gather but the firmware
+ * size is limited by DSP memory anyways
+ */
+ bdl[0] = cpu_to_le32(lower_32_bits(addr));
+ bdl[1] = cpu_to_le32(upper_32_bits(addr));
+ bdl[2] = cpu_to_le32(size);
+ bdl[3] = (!with_ioc) ? 0 : cpu_to_le32(0x01);
+
+ return 1; /* one fragment */
+}
+
+static void cl_skl_cldma_stream_run(struct snd_sof_dev *sdev, bool enable)
+{
+ int sd_offset = SOF_HDA_ADSP_LOADER_BASE;
+ unsigned char val;
+ int retries;
+ u32 run = enable ? 0x1 : 0;
+
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_SD_CTL,
+ HDA_CL_SD_CTL_RUN(1), HDA_CL_SD_CTL_RUN(run));
+
+ retries = 300;
+ do {
+ udelay(3);
+
+ /* waiting for hardware to report the stream Run bit set */
+ val = snd_sof_dsp_read(sdev, HDA_DSP_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_SD_CTL);
+ val &= HDA_CL_SD_CTL_RUN(1);
+ if (enable && val)
+ break;
+ else if (!enable && !val)
+ break;
+ } while (--retries);
+
+ if (retries == 0)
+ dev_err(sdev->dev, "%s: failed to set Run bit=%d enable=%d\n",
+ __func__, val, enable);
+}
+
+static void cl_skl_cldma_stream_clear(struct snd_sof_dev *sdev)
+{
+ int sd_offset = SOF_HDA_ADSP_LOADER_BASE;
+
+ /* make sure Run bit is cleared before setting stream register */
+ cl_skl_cldma_stream_run(sdev, 0);
+
+ /* Disable the Interrupt On Completion, FIFO Error Interrupt,
+ * Descriptor Error Interrupt and set the cldma stream number to 0.
+ */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_SD_CTL,
+ HDA_CL_SD_CTL_INT_MASK, HDA_CL_SD_CTL_INT(0));
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_SD_CTL,
+ HDA_CL_SD_CTL_STRM(0xf), HDA_CL_SD_CTL_STRM(0));
+
+ snd_sof_dsp_write(sdev, HDA_DSP_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_SD_BDLPL, HDA_CL_SD_BDLPLBA(0));
+ snd_sof_dsp_write(sdev, HDA_DSP_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_SD_BDLPU, 0);
+
+ /* Set the Cyclic Buffer Length to 0. */
+ snd_sof_dsp_write(sdev, HDA_DSP_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_SD_CBL, 0);
+ /* Set the Last Valid Index. */
+ snd_sof_dsp_write(sdev, HDA_DSP_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_SD_LVI, 0);
+}
+
+static void cl_skl_cldma_setup_spb(struct snd_sof_dev *sdev,
+ unsigned int size, bool enable)
+{
+ int sd_offset = SOF_DSP_REG_CL_SPBFIFO;
+
+ if (enable)
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_CL_SPBFIFO_SPBFCCTL,
+ HDA_CL_SPBFIFO_SPBFCCTL_SPIBE_MASK,
+ HDA_CL_SPBFIFO_SPBFCCTL_SPIBE(1));
+
+ snd_sof_dsp_write(sdev, HDA_DSP_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_CL_SPBFIFO_SPIB, size);
+}
+
+static void cl_skl_cldma_set_intr(struct snd_sof_dev *sdev, bool enable)
+{
+ u32 val = enable ? HDA_DSP_ADSPIC_CL_DMA : 0;
+
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIC,
+ HDA_DSP_ADSPIC_CL_DMA, val);
+}
+
+static void cl_skl_cldma_cleanup_spb(struct snd_sof_dev *sdev)
+{
+ int sd_offset = SOF_DSP_REG_CL_SPBFIFO;
+
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_CL_SPBFIFO_SPBFCCTL,
+ HDA_CL_SPBFIFO_SPBFCCTL_SPIBE_MASK,
+ HDA_CL_SPBFIFO_SPBFCCTL_SPIBE(0));
+
+ snd_sof_dsp_write(sdev, HDA_DSP_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_CL_SPBFIFO_SPIB, 0);
+}
+
+static void cl_skl_cldma_setup_controller(struct snd_sof_dev *sdev,
+ struct snd_dma_buffer *dmab_bdl,
+ unsigned int max_size, u32 count)
+{
+ int sd_offset = SOF_HDA_ADSP_LOADER_BASE;
+
+ /* Clear the stream first and then set it. */
+ cl_skl_cldma_stream_clear(sdev);
+
+ /* setting the stream register */
+ snd_sof_dsp_write(sdev, HDA_DSP_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_SD_BDLPL,
+ HDA_CL_SD_BDLPLBA(dmab_bdl->addr));
+ snd_sof_dsp_write(sdev, HDA_DSP_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_SD_BDLPU,
+ HDA_CL_SD_BDLPUBA(dmab_bdl->addr));
+
+ /* Set the Cyclic Buffer Length. */
+ snd_sof_dsp_write(sdev, HDA_DSP_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_SD_CBL, max_size);
+ /* Set the Last Valid Index. */
+ snd_sof_dsp_write(sdev, HDA_DSP_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_SD_LVI, count - 1);
+
+ /* Set the Interrupt On Completion, FIFO Error Interrupt,
+ * Descriptor Error Interrupt and the cldma stream number.
+ */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_SD_CTL,
+ HDA_CL_SD_CTL_INT_MASK, HDA_CL_SD_CTL_INT(1));
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_SD_CTL,
+ HDA_CL_SD_CTL_STRM(0xf),
+ HDA_CL_SD_CTL_STRM(1));
+}
+
+static int cl_stream_prepare_skl(struct snd_sof_dev *sdev,
+ struct snd_dma_buffer *dmab,
+ struct snd_dma_buffer *dmab_bdl)
+
+{
+ unsigned int bufsize = HDA_SKL_CLDMA_MAX_BUFFER_SIZE;
+ __le32 *bdl;
+ int frags;
+ int ret;
+
+ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->dev, bufsize, dmab);
+ if (ret < 0) {
+ dev_err(sdev->dev, "%s: failed to alloc fw buffer: %x\n", __func__, ret);
+ return ret;
+ }
+
+ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->dev, bufsize, dmab_bdl);
+ if (ret < 0) {
+ dev_err(sdev->dev, "%s: failed to alloc blde: %x\n", __func__, ret);
+ snd_dma_free_pages(dmab);
+ return ret;
+ }
+
+ bdl = (__le32 *)dmab_bdl->area;
+ frags = cl_skl_cldma_setup_bdle(sdev, dmab, &bdl, bufsize, 1);
+ cl_skl_cldma_setup_controller(sdev, dmab_bdl, bufsize, frags);
+
+ return ret;
+}
+
+static void cl_cleanup_skl(struct snd_sof_dev *sdev,
+ struct snd_dma_buffer *dmab,
+ struct snd_dma_buffer *dmab_bdl)
+{
+ cl_skl_cldma_cleanup_spb(sdev);
+ cl_skl_cldma_stream_clear(sdev);
+ snd_dma_free_pages(dmab);
+ snd_dma_free_pages(dmab_bdl);
+}
+
+static int cl_dsp_init_skl(struct snd_sof_dev *sdev,
+ struct snd_dma_buffer *dmab,
+ struct snd_dma_buffer *dmab_bdl)
+{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+ const struct sof_intel_dsp_desc *chip = hda->desc;
+ unsigned int status;
+ u32 flags;
+ int ret;
+
+ /* check if the init_core is already enabled, if yes, reset and make it run,
+ * if not, powerdown and enable it again.
+ */
+ if (hda_dsp_core_is_enabled(sdev, chip->init_core_mask)) {
+ /* if enabled, reset it, and run the init_core. */
+ ret = hda_dsp_core_stall_reset(sdev, chip->init_core_mask);
+ if (ret < 0)
+ goto err;
+
+ ret = hda_dsp_core_run(sdev, chip->init_core_mask);
+ if (ret < 0) {
+ dev_err(sdev->dev, "%s: dsp core start failed %d\n", __func__, ret);
+ goto err;
+ }
+ } else {
+ /* if not enabled, power down it first and then powerup and run
+ * the init_core.
+ */
+ ret = hda_dsp_core_reset_power_down(sdev, chip->init_core_mask);
+ if (ret < 0) {
+ dev_err(sdev->dev, "%s: dsp core0 disable fail: %d\n", __func__, ret);
+ goto err;
+ }
+ ret = hda_dsp_enable_core(sdev, chip->init_core_mask);
+ if (ret < 0) {
+ dev_err(sdev->dev, "%s: dsp core0 enable fail: %d\n", __func__, ret);
+ goto err;
+ }
+ }
+
+ /* prepare DMA for code loader stream */
+ ret = cl_stream_prepare_skl(sdev, dmab, dmab_bdl);
+ if (ret < 0) {
+ dev_err(sdev->dev, "%s: dma prepare fw loading err: %x\n", __func__, ret);
+ return ret;
+ }
+
+ /* enable the interrupt */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIC,
+ HDA_DSP_ADSPIC_IPC, HDA_DSP_ADSPIC_IPC);
+
+ /* enable IPC DONE interrupt */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, chip->ipc_ctl,
+ HDA_DSP_REG_HIPCCTL_DONE,
+ HDA_DSP_REG_HIPCCTL_DONE);
+
+ /* enable IPC BUSY interrupt */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, chip->ipc_ctl,
+ HDA_DSP_REG_HIPCCTL_BUSY,
+ HDA_DSP_REG_HIPCCTL_BUSY);
+
+ /* polling the ROM init status information. */
+ ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR,
+ chip->rom_status_reg, status,
+ (FSR_TO_STATE_CODE(status)
+ == FSR_STATE_INIT_DONE),
+ HDA_DSP_REG_POLL_INTERVAL_US,
+ chip->rom_init_timeout *
+ USEC_PER_MSEC);
+ if (ret < 0)
+ goto err;
+
+ return ret;
+
+err:
+ flags = SOF_DBG_DUMP_PCI | SOF_DBG_DUMP_MBOX;
+
+ snd_sof_dsp_dbg_dump(sdev, "Boot failed\n", flags);
+ cl_cleanup_skl(sdev, dmab, dmab_bdl);
+ hda_dsp_core_reset_power_down(sdev, chip->init_core_mask);
+ return ret;
+}
+
+static void cl_skl_cldma_fill_buffer(struct snd_sof_dev *sdev,
+ struct snd_dma_buffer *dmab,
+ unsigned int bufsize,
+ unsigned int copysize,
+ const void *curr_pos,
+ bool intr_enable)
+{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+
+ /* copy the image into the buffer with the maximum buffer size. */
+ unsigned int size = (bufsize == copysize) ? bufsize : copysize;
+
+ memcpy(dmab->area, curr_pos, size);
+
+ /* Set the wait condition for every load. */
+ hda->code_loading = 1;
+
+ /* Set the interrupt. */
+ if (intr_enable)
+ cl_skl_cldma_set_intr(sdev, true);
+
+ /* Set the SPB. */
+ cl_skl_cldma_setup_spb(sdev, size, true);
+
+ /* Trigger the code loading stream. */
+ cl_skl_cldma_stream_run(sdev, true);
+}
+
+static int cl_skl_cldma_wait_interruptible(struct snd_sof_dev *sdev,
+ bool intr_wait)
+{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+ const struct sof_intel_dsp_desc *chip = hda->desc;
+ int sd_offset = SOF_HDA_ADSP_LOADER_BASE;
+ u8 cl_dma_intr_status;
+
+ /*
+ * Wait for CLDMA interrupt to inform the binary segment transfer is
+ * complete.
+ */
+ if (!wait_event_timeout(hda->waitq, !hda->code_loading,
+ msecs_to_jiffies(HDA_SKL_WAIT_TIMEOUT))) {
+ dev_err(sdev->dev, "cldma copy timeout\n");
+ dev_err(sdev->dev, "ROM code=%#x: FW status=%#x\n",
+ snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_SRAM_REG_ROM_ERROR),
+ snd_sof_dsp_read(sdev, HDA_DSP_BAR, chip->rom_status_reg));
+ return -EIO;
+ }
+
+ /* now check DMA interrupt status */
+ cl_dma_intr_status = snd_sof_dsp_read(sdev, HDA_DSP_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_SD_STS);
+
+ if (!(cl_dma_intr_status & HDA_CL_DMA_SD_INT_COMPLETE)) {
+ dev_err(sdev->dev, "cldma copy failed\n");
+ return -EIO;
+ }
+
+ dev_dbg(sdev->dev, "cldma buffer copy complete\n");
+ return 0;
+}
+
+static int
+cl_skl_cldma_copy_to_buf(struct snd_sof_dev *sdev,
+ struct snd_dma_buffer *dmab,
+ const void *bin,
+ u32 total_size, u32 bufsize)
+{
+ unsigned int bytes_left = total_size;
+ const void *curr_pos = bin;
+ int ret;
+
+ if (total_size <= 0)
+ return -EINVAL;
+
+ while (bytes_left > 0) {
+ if (bytes_left > bufsize) {
+ dev_dbg(sdev->dev, "cldma copy %#x bytes\n", bufsize);
+
+ cl_skl_cldma_fill_buffer(sdev, dmab, bufsize, bufsize, curr_pos, true);
+
+ ret = cl_skl_cldma_wait_interruptible(sdev, false);
+ if (ret < 0) {
+ dev_err(sdev->dev, "%s: fw failed to load. %#x bytes remaining\n",
+ __func__, bytes_left);
+ return ret;
+ }
+
+ bytes_left -= bufsize;
+ curr_pos += bufsize;
+ } else {
+ dev_dbg(sdev->dev, "cldma copy %#x bytes\n", bytes_left);
+
+ cl_skl_cldma_set_intr(sdev, false);
+ cl_skl_cldma_fill_buffer(sdev, dmab, bufsize, bytes_left, curr_pos, false);
+ return 0;
+ }
+ }
+
+ return bytes_left;
+}
+
+static int cl_copy_fw_skl(struct snd_sof_dev *sdev,
+ struct snd_dma_buffer *dmab)
+
+{
+ const struct firmware *fw = sdev->basefw.fw;
+ struct firmware stripped_firmware;
+ unsigned int bufsize = HDA_SKL_CLDMA_MAX_BUFFER_SIZE;
+ int ret;
+
+ stripped_firmware.data = fw->data + sdev->basefw.payload_offset;
+ stripped_firmware.size = fw->size - sdev->basefw.payload_offset;
+
+ dev_dbg(sdev->dev, "firmware size: %#zx buffer size %#x\n", fw->size, bufsize);
+
+ ret = cl_skl_cldma_copy_to_buf(sdev, dmab, stripped_firmware.data,
+ stripped_firmware.size, bufsize);
+ if (ret < 0)
+ dev_err(sdev->dev, "%s: fw copy failed %d\n", __func__, ret);
+
+ return ret;
+}
+
+int hda_dsp_cl_boot_firmware_skl(struct snd_sof_dev *sdev)
+{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+ const struct sof_intel_dsp_desc *chip = hda->desc;
+ struct snd_dma_buffer dmab_bdl;
+ struct snd_dma_buffer dmab;
+ unsigned int reg;
+ u32 flags;
+ int ret;
+
+ ret = cl_dsp_init_skl(sdev, &dmab, &dmab_bdl);
+
+ /* retry enabling core and ROM load. seemed to help */
+ if (ret < 0) {
+ ret = cl_dsp_init_skl(sdev, &dmab, &dmab_bdl);
+ if (ret < 0) {
+ dev_err(sdev->dev, "Error code=%#x: FW status=%#x\n",
+ snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_SRAM_REG_ROM_ERROR),
+ snd_sof_dsp_read(sdev, HDA_DSP_BAR, chip->rom_status_reg));
+ dev_err(sdev->dev, "Core En/ROM load fail:%d\n", ret);
+ return ret;
+ }
+ }
+
+ dev_dbg(sdev->dev, "ROM init successful\n");
+
+ /* at this point DSP ROM has been initialized and should be ready for
+ * code loading and firmware boot
+ */
+ ret = cl_copy_fw_skl(sdev, &dmab);
+ if (ret < 0) {
+ dev_err(sdev->dev, "%s: load firmware failed : %d\n", __func__, ret);
+ goto err;
+ }
+
+ ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR,
+ chip->rom_status_reg, reg,
+ (FSR_TO_STATE_CODE(reg)
+ == FSR_STATE_ROM_BASEFW_ENTERED),
+ HDA_DSP_REG_POLL_INTERVAL_US,
+ HDA_DSP_BASEFW_TIMEOUT_US);
+
+ dev_dbg(sdev->dev, "Firmware download successful, booting...\n");
+
+ cl_skl_cldma_stream_run(sdev, false);
+ cl_cleanup_skl(sdev, &dmab, &dmab_bdl);
+
+ if (!ret)
+ return chip->init_core_mask;
+
+ return ret;
+
+err:
+ flags = SOF_DBG_DUMP_PCI | SOF_DBG_DUMP_MBOX;
+
+ snd_sof_dsp_dbg_dump(sdev, "Boot failed\n", flags);
+
+ /* power down DSP */
+ hda_dsp_core_reset_power_down(sdev, chip->init_core_mask);
+ cl_skl_cldma_stream_run(sdev, false);
+ cl_cleanup_skl(sdev, &dmab, &dmab_bdl);
+
+ dev_err(sdev->dev, "%s: load fw failed err: %d\n", __func__, ret);
+ return ret;
+}
diff --git a/sound/soc/sof/intel/hda-loader.c b/sound/soc/sof/intel/hda-loader.c
index 441d05cda604..b81f231abee3 100644
--- a/sound/soc/sof/intel/hda-loader.c
+++ b/sound/soc/sof/intel/hda-loader.c
@@ -17,102 +17,123 @@
#include <linux/firmware.h>
#include <sound/hdaudio_ext.h>
+#include <sound/hda_register.h>
#include <sound/sof.h>
+#include <sound/sof/ipc4/header.h>
+#include "ext_manifest.h"
+#include "../ipc4-priv.h"
#include "../ops.h"
+#include "../sof-priv.h"
#include "hda.h"
-#define HDA_FW_BOOT_ATTEMPTS 3
+static void hda_ssp_set_cbp_cfp(struct snd_sof_dev *sdev)
+{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+ const struct sof_intel_dsp_desc *chip = hda->desc;
+ int i;
-static int cl_stream_prepare(struct snd_sof_dev *sdev, unsigned int format,
- unsigned int size, struct snd_dma_buffer *dmab,
- int direction)
+ /* DSP is powered up, set all SSPs to clock consumer/codec provider mode */
+ for (i = 0; i < chip->ssp_count; i++) {
+ snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR,
+ chip->ssp_base_offset
+ + i * SSP_DEV_MEM_SIZE
+ + SSP_SSC1_OFFSET,
+ SSP_SET_CBP_CFP,
+ SSP_SET_CBP_CFP);
+ }
+}
+
+struct hdac_ext_stream *hda_cl_stream_prepare(struct snd_sof_dev *sdev, unsigned int format,
+ unsigned int size, struct snd_dma_buffer *dmab,
+ int direction)
{
- struct hdac_ext_stream *dsp_stream;
+ struct hdac_ext_stream *hext_stream;
struct hdac_stream *hstream;
struct pci_dev *pci = to_pci_dev(sdev->dev);
int ret;
- if (direction != SNDRV_PCM_STREAM_PLAYBACK) {
- dev_err(sdev->dev, "error: code loading DMA is playback only\n");
- return -EINVAL;
- }
+ hext_stream = hda_dsp_stream_get(sdev, direction, 0);
- dsp_stream = hda_dsp_stream_get(sdev, direction);
-
- if (!dsp_stream) {
+ if (!hext_stream) {
dev_err(sdev->dev, "error: no stream available\n");
- return -ENODEV;
+ return ERR_PTR(-ENODEV);
}
- hstream = &dsp_stream->hstream;
+ hstream = &hext_stream->hstream;
hstream->substream = NULL;
/* allocate DMA buffer */
ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV_SG, &pci->dev, size, dmab);
if (ret < 0) {
- dev_err(sdev->dev, "error: memory alloc failed: %x\n", ret);
- goto error;
+ dev_err(sdev->dev, "error: memory alloc failed: %d\n", ret);
+ goto out_put;
}
hstream->period_bytes = 0;/* initialize period_bytes */
hstream->format_val = format;
hstream->bufsize = size;
- ret = hda_dsp_stream_hw_params(sdev, dsp_stream, dmab, NULL);
- if (ret < 0) {
- dev_err(sdev->dev, "error: hdac prepare failed: %x\n", ret);
- goto error;
+ if (direction == SNDRV_PCM_STREAM_CAPTURE) {
+ ret = hda_dsp_iccmax_stream_hw_params(sdev, hext_stream, dmab, NULL);
+ if (ret < 0) {
+ dev_err(sdev->dev, "error: iccmax stream prepare failed: %d\n", ret);
+ goto out_free;
+ }
+ } else {
+ ret = hda_dsp_stream_hw_params(sdev, hext_stream, dmab, NULL);
+ if (ret < 0) {
+ dev_err(sdev->dev, "error: hdac prepare failed: %d\n", ret);
+ goto out_free;
+ }
+ hda_dsp_stream_spib_config(sdev, hext_stream, HDA_DSP_SPIB_ENABLE, size);
}
- hda_dsp_stream_spib_config(sdev, dsp_stream, HDA_DSP_SPIB_ENABLE, size);
-
- return hstream->stream_tag;
+ return hext_stream;
-error:
- hda_dsp_stream_put(sdev, direction, hstream->stream_tag);
+out_free:
snd_dma_free_pages(dmab);
- return ret;
+out_put:
+ hda_dsp_stream_put(sdev, direction, hstream->stream_tag);
+ return ERR_PTR(ret);
}
/*
- * first boot sequence has some extra steps. core 0 waits for power
- * status on core 1, so power up core 1 also momentarily, keep it in
- * reset/stall and then turn it off
+ * first boot sequence has some extra steps.
+ * power on all host managed cores and only unstall/run the boot core to boot the
+ * DSP then turn off all non boot cores (if any) is powered on.
*/
-static int cl_dsp_init(struct snd_sof_dev *sdev, const void *fwdata,
- u32 fwsize, int stream_tag)
+int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag, bool imr_boot)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
const struct sof_intel_dsp_desc *chip = hda->desc;
- unsigned int status;
+ unsigned int status, target_status;
+ u32 flags, ipc_hdr, j;
+ unsigned long mask;
+ char *dump_msg;
int ret;
- int i;
/* step 1: power up corex */
- ret = hda_dsp_core_power_up(sdev, chip->cores_mask);
+ ret = hda_dsp_core_power_up(sdev, chip->host_managed_cores_mask);
if (ret < 0) {
- dev_err(sdev->dev, "error: dsp core 0/1 power up failed\n");
+ if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS)
+ dev_err(sdev->dev, "error: dsp core 0/1 power up failed\n");
goto err;
}
- /* DSP is powered up, set all SSPs to slave mode */
- for (i = 0; i < chip->ssp_count; i++) {
- snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR,
- chip->ssp_base_offset
- + i * SSP_DEV_MEM_SIZE
- + SSP_SSC1_OFFSET,
- SSP_SET_SLAVE,
- SSP_SET_SLAVE);
- }
+ hda_ssp_set_cbp_cfp(sdev);
+
+ /* step 2: Send ROM_CONTROL command (stream_tag is ignored for IMR boot) */
+ ipc_hdr = chip->ipc_req_mask | HDA_DSP_ROM_IPC_CONTROL;
+ if (!imr_boot)
+ ipc_hdr |= HDA_DSP_ROM_IPC_PURGE_FW | ((stream_tag - 1) << 9);
- /* step 2: purge FW request */
- snd_sof_dsp_write(sdev, HDA_DSP_BAR, chip->ipc_req,
- chip->ipc_req_mask | (HDA_DSP_IPC_PURGE_FW |
- ((stream_tag - 1) << 9)));
+ snd_sof_dsp_write(sdev, HDA_DSP_BAR, chip->ipc_req, ipc_hdr);
/* step 3: unset core 0 reset state & unstall/run core 0 */
- ret = hda_dsp_core_run(sdev, HDA_DSP_CORE_MASK(0));
+ ret = hda_dsp_core_run(sdev, chip->init_core_mask);
if (ret < 0) {
- dev_err(sdev->dev, "error: dsp core start failed %d\n", ret);
+ if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS)
+ dev_err(sdev->dev,
+ "error: dsp core start failed %d\n", ret);
ret = -EIO;
goto err;
}
@@ -126,8 +147,10 @@ static int cl_dsp_init(struct snd_sof_dev *sdev, const void *fwdata,
HDA_DSP_INIT_TIMEOUT_US);
if (ret < 0) {
- dev_err(sdev->dev, "error: %s: timeout for HIPCIE done\n",
- __func__);
+ if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS)
+ dev_err(sdev->dev,
+ "error: %s: timeout for HIPCIE done\n",
+ __func__);
goto err;
}
@@ -137,43 +160,69 @@ static int cl_dsp_init(struct snd_sof_dev *sdev, const void *fwdata,
chip->ipc_ack_mask,
chip->ipc_ack_mask);
- /* step 5: power down corex */
- ret = hda_dsp_core_power_down(sdev,
- chip->cores_mask & ~(HDA_DSP_CORE_MASK(0)));
+ /* step 5: power down cores that are no longer needed */
+ ret = hda_dsp_core_reset_power_down(sdev, chip->host_managed_cores_mask &
+ ~(chip->init_core_mask));
if (ret < 0) {
- dev_err(sdev->dev, "error: dsp core x power down failed\n");
+ if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS)
+ dev_err(sdev->dev,
+ "error: dsp core x power down failed\n");
goto err;
}
/* step 6: enable IPC interrupts */
hda_dsp_ipc_int_enable(sdev);
- /* step 7: wait for ROM init */
+ /*
+ * step 7:
+ * - Cold/Full boot: wait for ROM init to proceed to download the firmware
+ * - IMR boot: wait for ROM firmware entered (firmware booted up from IMR)
+ */
+ if (imr_boot)
+ target_status = FSR_STATE_FW_ENTERED;
+ else
+ target_status = FSR_STATE_INIT_DONE;
+
ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR,
- HDA_DSP_SRAM_REG_ROM_STATUS, status,
- ((status & HDA_DSP_ROM_STS_MASK)
- == HDA_DSP_ROM_INIT),
+ chip->rom_status_reg, status,
+ (FSR_TO_STATE_CODE(status) == target_status),
HDA_DSP_REG_POLL_INTERVAL_US,
chip->rom_init_timeout *
USEC_PER_MSEC);
- if (!ret)
+ if (!ret) {
+ /* set enabled cores mask and increment ref count for cores in init_core_mask */
+ sdev->enabled_cores_mask |= chip->init_core_mask;
+ mask = sdev->enabled_cores_mask;
+ for_each_set_bit(j, &mask, SOF_MAX_DSP_NUM_CORES)
+ sdev->dsp_core_ref_count[j]++;
return 0;
+ }
- dev_err(sdev->dev,
- "error: %s: timeout HDA_DSP_SRAM_REG_ROM_STATUS read\n",
- __func__);
+ if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS)
+ dev_err(sdev->dev,
+ "%s: timeout with rom_status_reg (%#x) read\n",
+ __func__, chip->rom_status_reg);
err:
- hda_dsp_dump(sdev, SOF_DBG_REGS | SOF_DBG_PCI | SOF_DBG_MBOX);
- hda_dsp_core_reset_power_down(sdev, chip->cores_mask);
+ flags = SOF_DBG_DUMP_PCI | SOF_DBG_DUMP_MBOX | SOF_DBG_DUMP_OPTIONAL;
+ /* after max boot attempts make sure that the dump is printed */
+ if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS)
+ flags &= ~SOF_DBG_DUMP_OPTIONAL;
+
+ dump_msg = kasprintf(GFP_KERNEL, "Boot iteration failed: %d/%d",
+ hda->boot_iteration, HDA_FW_BOOT_ATTEMPTS);
+ snd_sof_dsp_dbg_dump(sdev, dump_msg, flags);
+ hda_dsp_core_reset_power_down(sdev, chip->host_managed_cores_mask);
+
+ kfree(dump_msg);
return ret;
}
static int cl_trigger(struct snd_sof_dev *sdev,
- struct hdac_ext_stream *stream, int cmd)
+ struct hdac_ext_stream *hext_stream, int cmd)
{
- struct hdac_stream *hstream = &stream->hstream;
+ struct hdac_stream *hstream = &hext_stream->hstream;
int sd_offset = SOF_STREAM_SD_OFFSET(hstream);
/* code loader is special case that reuses stream ops */
@@ -193,46 +242,32 @@ static int cl_trigger(struct snd_sof_dev *sdev,
hstream->running = true;
return 0;
default:
- return hda_dsp_stream_trigger(sdev, stream, cmd);
+ return hda_dsp_stream_trigger(sdev, hext_stream, cmd);
}
}
-static struct hdac_ext_stream *get_stream_with_tag(struct snd_sof_dev *sdev,
- int tag)
+int hda_cl_cleanup(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab,
+ struct hdac_ext_stream *hext_stream)
{
- struct hdac_bus *bus = sof_to_bus(sdev);
- struct hdac_stream *s;
-
- /* get stream with tag */
- list_for_each_entry(s, &bus->stream_list, list) {
- if (s->direction == SNDRV_PCM_STREAM_PLAYBACK &&
- s->stream_tag == tag) {
- return stream_to_hdac_ext_stream(s);
- }
- }
-
- return NULL;
-}
-
-static int cl_cleanup(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab,
- struct hdac_ext_stream *stream)
-{
- struct hdac_stream *hstream = &stream->hstream;
+ struct hdac_stream *hstream = &hext_stream->hstream;
int sd_offset = SOF_STREAM_SD_OFFSET(hstream);
- int ret;
+ int ret = 0;
- ret = hda_dsp_stream_spib_config(sdev, stream, HDA_DSP_SPIB_DISABLE, 0);
+ if (hstream->direction == SNDRV_PCM_STREAM_PLAYBACK)
+ ret = hda_dsp_stream_spib_config(sdev, hext_stream, HDA_DSP_SPIB_DISABLE, 0);
+ else
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset,
+ SOF_HDA_SD_CTL_DMA_START, 0);
- hda_dsp_stream_put(sdev, SNDRV_PCM_STREAM_PLAYBACK,
- hstream->stream_tag);
+ hda_dsp_stream_put(sdev, hstream->direction, hstream->stream_tag);
hstream->running = 0;
hstream->substream = NULL;
/* reset BDL address */
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
- sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPL, 0);
+ sd_offset + SOF_HDA_ADSP_REG_SD_BDLPL, 0);
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
- sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPU, 0);
+ sd_offset + SOF_HDA_ADSP_REG_SD_BDLPU, 0);
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, sd_offset, 0);
snd_dma_free_pages(dmab);
@@ -243,21 +278,22 @@ static int cl_cleanup(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab,
return ret;
}
-static int cl_copy_fw(struct snd_sof_dev *sdev, struct hdac_ext_stream *stream)
+int hda_cl_copy_fw(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream)
{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+ const struct sof_intel_dsp_desc *chip = hda->desc;
unsigned int reg;
int ret, status;
- ret = cl_trigger(sdev, stream, SNDRV_PCM_TRIGGER_START);
+ ret = cl_trigger(sdev, hext_stream, SNDRV_PCM_TRIGGER_START);
if (ret < 0) {
dev_err(sdev->dev, "error: DMA trigger start failed\n");
return ret;
}
status = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR,
- HDA_DSP_SRAM_REG_ROM_STATUS, reg,
- ((reg & HDA_DSP_ROM_STS_MASK)
- == HDA_DSP_ROM_FW_ENTERED),
+ chip->rom_status_reg, reg,
+ (FSR_TO_STATE_CODE(reg) == FSR_STATE_FW_ENTERED),
HDA_DSP_REG_POLL_INTERVAL_US,
HDA_DSP_BASEFW_TIMEOUT_US);
@@ -268,11 +304,11 @@ static int cl_copy_fw(struct snd_sof_dev *sdev, struct hdac_ext_stream *stream)
if (status < 0) {
dev_err(sdev->dev,
- "error: %s: timeout HDA_DSP_SRAM_REG_ROM_STATUS read\n",
- __func__);
+ "%s: timeout with rom_status_reg (%#x) read\n",
+ __func__, chip->rom_status_reg);
}
- ret = cl_trigger(sdev, stream, SNDRV_PCM_TRIGGER_STOP);
+ ret = cl_trigger(sdev, hext_stream, SNDRV_PCM_TRIGGER_STOP);
if (ret < 0) {
dev_err(sdev->dev, "error: DMA trigger stop failed\n");
if (!status)
@@ -282,67 +318,131 @@ static int cl_copy_fw(struct snd_sof_dev *sdev, struct hdac_ext_stream *stream)
return status;
}
+int hda_dsp_cl_boot_firmware_iccmax(struct snd_sof_dev *sdev)
+{
+ struct hdac_ext_stream *iccmax_stream;
+ struct snd_dma_buffer dmab_bdl;
+ int ret, ret1;
+ u8 original_gb;
+
+ /* save the original LTRP guardband value */
+ original_gb = snd_sof_dsp_read8(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_LTRP) &
+ HDA_VS_INTEL_LTRP_GB_MASK;
+
+ /*
+ * Prepare capture stream for ICCMAX. We do not need to store
+ * the data, so use a buffer of PAGE_SIZE for receiving.
+ */
+ iccmax_stream = hda_cl_stream_prepare(sdev, HDA_CL_STREAM_FORMAT, PAGE_SIZE,
+ &dmab_bdl, SNDRV_PCM_STREAM_CAPTURE);
+ if (IS_ERR(iccmax_stream)) {
+ dev_err(sdev->dev, "error: dma prepare for ICCMAX stream failed\n");
+ return PTR_ERR(iccmax_stream);
+ }
+
+ ret = hda_dsp_cl_boot_firmware(sdev);
+
+ /*
+ * Perform iccmax stream cleanup. This should be done even if firmware loading fails.
+ * If the cleanup also fails, we return the initial error
+ */
+ ret1 = hda_cl_cleanup(sdev, &dmab_bdl, iccmax_stream);
+ if (ret1 < 0) {
+ dev_err(sdev->dev, "error: ICCMAX stream cleanup failed\n");
+
+ /* set return value to indicate cleanup failure */
+ if (!ret)
+ ret = ret1;
+ }
+
+ /* restore the original guardband value after FW boot */
+ snd_sof_dsp_update8(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_LTRP,
+ HDA_VS_INTEL_LTRP_GB_MASK, original_gb);
+
+ return ret;
+}
+
+static int hda_dsp_boot_imr(struct snd_sof_dev *sdev)
+{
+ const struct sof_intel_dsp_desc *chip_info;
+ int ret;
+
+ chip_info = get_chip_info(sdev->pdata);
+ if (chip_info->cl_init)
+ ret = chip_info->cl_init(sdev, 0, true);
+ else
+ ret = -EINVAL;
+
+ if (!ret)
+ hda_sdw_process_wakeen(sdev);
+
+ return ret;
+}
+
int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev)
{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
struct snd_sof_pdata *plat_data = sdev->pdata;
const struct sof_dev_desc *desc = plat_data->desc;
const struct sof_intel_dsp_desc *chip_info;
- struct hdac_ext_stream *stream;
+ struct hdac_ext_stream *hext_stream;
struct firmware stripped_firmware;
- int ret, ret1, tag, i;
+ struct snd_dma_buffer dmab;
+ int ret, ret1, i;
+
+ if (hda->imrboot_supported && !sdev->first_boot && !hda->skip_imr_boot) {
+ dev_dbg(sdev->dev, "IMR restore supported, booting from IMR directly\n");
+ hda->boot_iteration = 0;
+ ret = hda_dsp_boot_imr(sdev);
+ if (!ret) {
+ hda->booted_from_imr = true;
+ return 0;
+ }
+
+ dev_warn(sdev->dev, "IMR restore failed, trying to cold boot\n");
+ }
+
+ hda->booted_from_imr = false;
chip_info = desc->chip_info;
- if (plat_data->fw->size <= plat_data->fw_offset) {
+ if (sdev->basefw.fw->size <= sdev->basefw.payload_offset) {
dev_err(sdev->dev, "error: firmware size must be greater than firmware offset\n");
return -EINVAL;
}
- stripped_firmware.data = plat_data->fw->data + plat_data->fw_offset;
- stripped_firmware.size = plat_data->fw->size - plat_data->fw_offset;
+ stripped_firmware.data = sdev->basefw.fw->data + sdev->basefw.payload_offset;
+ stripped_firmware.size = sdev->basefw.fw->size - sdev->basefw.payload_offset;
/* init for booting wait */
init_waitqueue_head(&sdev->boot_wait);
/* prepare DMA for code loader stream */
- tag = cl_stream_prepare(sdev, 0x40, stripped_firmware.size,
- &sdev->dmab, SNDRV_PCM_STREAM_PLAYBACK);
-
- if (tag < 0) {
- dev_err(sdev->dev, "error: dma prepare for fw loading err: %x\n",
- tag);
- return tag;
+ hext_stream = hda_cl_stream_prepare(sdev, HDA_CL_STREAM_FORMAT,
+ stripped_firmware.size,
+ &dmab, SNDRV_PCM_STREAM_PLAYBACK);
+ if (IS_ERR(hext_stream)) {
+ dev_err(sdev->dev, "error: dma prepare for fw loading failed\n");
+ return PTR_ERR(hext_stream);
}
- /* get stream with tag */
- stream = get_stream_with_tag(sdev, tag);
- if (!stream) {
- dev_err(sdev->dev,
- "error: could not get stream with stream tag %d\n",
- tag);
- ret = -ENODEV;
- goto err;
- }
-
- memcpy(sdev->dmab.area, stripped_firmware.data,
+ memcpy(dmab.area, stripped_firmware.data,
stripped_firmware.size);
/* try ROM init a few times before giving up */
for (i = 0; i < HDA_FW_BOOT_ATTEMPTS; i++) {
- ret = cl_dsp_init(sdev, stripped_firmware.data,
- stripped_firmware.size, tag);
+ dev_dbg(sdev->dev,
+ "Attempting iteration %d of Core En/ROM load...\n", i);
+
+ hda->boot_iteration = i + 1;
+ if (chip_info->cl_init)
+ ret = chip_info->cl_init(sdev, hext_stream->hstream.stream_tag, false);
+ else
+ ret = -EINVAL;
/* don't retry anymore if successful */
if (!ret)
break;
-
- dev_dbg(sdev->dev, "iteration %d of Core En/ROM load failed: %d\n",
- i, ret);
- dev_dbg(sdev->dev, "Error code=0x%x: FW status=0x%x\n",
- snd_sof_dsp_read(sdev, HDA_DSP_BAR,
- HDA_DSP_SRAM_REG_ROM_ERROR),
- snd_sof_dsp_read(sdev, HDA_DSP_BAR,
- HDA_DSP_SRAM_REG_ROM_STATUS));
}
if (i == HDA_FW_BOOT_ATTEMPTS) {
@@ -370,14 +470,22 @@ int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev)
hda_sdw_process_wakeen(sdev);
/*
- * at this point DSP ROM has been initialized and
- * should be ready for code loading and firmware boot
+ * Set the boot_iteration to the last attempt, indicating that the
+ * DSP ROM has been initialized and from this point there will be no
+ * retry done to boot.
+ *
+ * Continue with code loading and firmware boot
*/
- ret = cl_copy_fw(sdev, stream);
- if (!ret)
+ hda->boot_iteration = HDA_FW_BOOT_ATTEMPTS;
+ ret = hda_cl_copy_fw(sdev, hext_stream);
+ if (!ret) {
dev_dbg(sdev->dev, "Firmware download successful, booting...\n");
- else
- dev_err(sdev->dev, "error: load fw failed ret: %d\n", ret);
+ hda->skip_imr_boot = false;
+ } else {
+ snd_sof_dsp_dbg_dump(sdev, "Firmware download failed",
+ SOF_DBG_DUMP_PCI | SOF_DBG_DUMP_MBOX);
+ hda->skip_imr_boot = true;
+ }
cleanup:
/*
@@ -385,7 +493,7 @@ cleanup:
* This should be done even if firmware loading fails.
* If the cleanup also fails, we return the initial error
*/
- ret1 = cl_cleanup(sdev, &sdev->dmab, stream);
+ ret1 = hda_cl_cleanup(sdev, &dmab, hext_stream);
if (ret1 < 0) {
dev_err(sdev->dev, "error: Code loader DSP cleanup failed\n");
@@ -395,20 +503,118 @@ cleanup:
}
/*
- * return master core id if both fw copy
+ * return primary core id if both fw copy
* and stream clean up are successful
*/
if (!ret)
return chip_info->init_core_mask;
- /* dump dsp registers and disable DSP upon error */
-err:
- hda_dsp_dump(sdev, SOF_DBG_REGS | SOF_DBG_PCI | SOF_DBG_MBOX);
-
/* disable DSP */
- snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR,
- SOF_HDA_REG_PP_PPCTL,
- SOF_HDA_PPCTL_GPROCEN, 0);
+ hda_dsp_ctrl_ppcap_enable(sdev, false);
+
+ return ret;
+}
+
+int hda_dsp_ipc4_load_library(struct snd_sof_dev *sdev,
+ struct sof_ipc4_fw_library *fw_lib, bool reload)
+{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+ struct hdac_ext_stream *hext_stream;
+ struct firmware stripped_firmware;
+ struct sof_ipc4_msg msg = {};
+ struct snd_dma_buffer dmab;
+ int ret, ret1;
+
+ /* if IMR booting is enabled and fw context is saved for D3 state, skip the loading */
+ if (reload && hda->booted_from_imr && ipc4_data->fw_context_save)
+ return 0;
+
+ /* the fw_lib has been verified during loading, we can trust the validity here */
+ stripped_firmware.data = fw_lib->sof_fw.fw->data + fw_lib->sof_fw.payload_offset;
+ stripped_firmware.size = fw_lib->sof_fw.fw->size - fw_lib->sof_fw.payload_offset;
+
+ /* prepare DMA for code loader stream */
+ hext_stream = hda_cl_stream_prepare(sdev, HDA_CL_STREAM_FORMAT,
+ stripped_firmware.size,
+ &dmab, SNDRV_PCM_STREAM_PLAYBACK);
+ if (IS_ERR(hext_stream)) {
+ dev_err(sdev->dev, "%s: DMA prepare failed\n", __func__);
+ return PTR_ERR(hext_stream);
+ }
+
+ memcpy(dmab.area, stripped_firmware.data, stripped_firmware.size);
+
+ /*
+ * 1st stage: SOF_IPC4_GLB_LOAD_LIBRARY_PREPARE
+ * Message includes the dma_id to be prepared for the library loading.
+ * If the firmware does not have support for the message, we will
+ * receive -EOPNOTSUPP. In this case we will use single step library
+ * loading and proceed to send the LOAD_LIBRARY message.
+ */
+ msg.primary = hext_stream->hstream.stream_tag - 1;
+ msg.primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_LOAD_LIBRARY_PREPARE);
+ msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ msg.primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG);
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0);
+ if (!ret) {
+ int sd_offset = SOF_STREAM_SD_OFFSET(&hext_stream->hstream);
+ unsigned int status;
+
+ /*
+ * Make sure that the FIFOS value is not 0 in SDxFIFOS register
+ * which indicates that the firmware set the GEN bit and we can
+ * continue to start the DMA
+ */
+ ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_HDA_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_SD_FIFOSIZE,
+ status,
+ status & SOF_HDA_SD_FIFOSIZE_FIFOS_MASK,
+ HDA_DSP_REG_POLL_INTERVAL_US,
+ HDA_DSP_BASEFW_TIMEOUT_US);
+
+ if (ret < 0)
+ dev_warn(sdev->dev,
+ "%s: timeout waiting for FIFOS\n", __func__);
+ } else if (ret != -EOPNOTSUPP) {
+ goto cleanup;
+ }
+
+ ret = cl_trigger(sdev, hext_stream, SNDRV_PCM_TRIGGER_START);
+ if (ret < 0) {
+ dev_err(sdev->dev, "%s: DMA trigger start failed\n", __func__);
+ goto cleanup;
+ }
+
+ /*
+ * 2nd stage: LOAD_LIBRARY
+ * Message includes the dma_id and the lib_id, the dma_id must be
+ * identical to the one sent via LOAD_LIBRARY_PREPARE
+ */
+ msg.primary &= ~SOF_IPC4_MSG_TYPE_MASK;
+ msg.primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_LOAD_LIBRARY);
+ msg.primary |= SOF_IPC4_GLB_LOAD_LIBRARY_LIB_ID(fw_lib->id);
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0);
+
+ /* Stop the DMA channel */
+ ret1 = cl_trigger(sdev, hext_stream, SNDRV_PCM_TRIGGER_STOP);
+ if (ret1 < 0) {
+ dev_err(sdev->dev, "%s: DMA trigger stop failed\n", __func__);
+ if (!ret)
+ ret = ret1;
+ }
+
+cleanup:
+ /* clean up even in case of error and return the first error */
+ ret1 = hda_cl_cleanup(sdev, &dmab, hext_stream);
+ if (ret1 < 0) {
+ dev_err(sdev->dev, "%s: Code loader DSP cleanup failed\n", __func__);
+
+ /* set return value to indicate cleanup failure */
+ if (!ret)
+ ret = ret1;
+ }
+
return ret;
}
@@ -425,12 +631,20 @@ int hda_dsp_post_fw_run(struct snd_sof_dev *sdev)
int ret;
if (sdev->first_boot) {
+ struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
+
ret = hda_sdw_startup(sdev);
if (ret < 0) {
dev_err(sdev->dev,
"error: could not startup SoundWire links\n");
return ret;
}
+
+ /* Check if IMR boot is usable */
+ if (!sof_debug_check_flag(SOF_DBG_IGNORE_D3_PERSISTENT) &&
+ (sdev->fw_ready.flags & SOF_IPC_INFO_D3_PERSISTENT ||
+ sdev->pdata->ipc_type == SOF_IPC_TYPE_4))
+ hdev->imrboot_supported = true;
}
hda_sdw_int_enable(sdev, true);
@@ -438,3 +652,41 @@ int hda_dsp_post_fw_run(struct snd_sof_dev *sdev)
/* re-enable clock gating and power gating */
return hda_dsp_ctrl_clock_power_gating(sdev, true);
}
+
+int hda_dsp_ext_man_get_cavs_config_data(struct snd_sof_dev *sdev,
+ const struct sof_ext_man_elem_header *hdr)
+{
+ const struct sof_ext_man_cavs_config_data *config_data =
+ container_of(hdr, struct sof_ext_man_cavs_config_data, hdr);
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+ int i, elem_num;
+
+ /* calculate total number of config data elements */
+ elem_num = (hdr->size - sizeof(struct sof_ext_man_elem_header))
+ / sizeof(struct sof_config_elem);
+ if (elem_num <= 0) {
+ dev_err(sdev->dev, "cavs config data is inconsistent: %d\n", elem_num);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < elem_num; i++)
+ switch (config_data->elems[i].token) {
+ case SOF_EXT_MAN_CAVS_CONFIG_EMPTY:
+ /* skip empty token */
+ break;
+ case SOF_EXT_MAN_CAVS_CONFIG_CAVS_LPRO:
+ hda->clk_config_lpro = config_data->elems[i].value;
+ dev_dbg(sdev->dev, "FW clock config: %s\n",
+ hda->clk_config_lpro ? "LPRO" : "HPRO");
+ break;
+ case SOF_EXT_MAN_CAVS_CONFIG_OUTBOX_SIZE:
+ case SOF_EXT_MAN_CAVS_CONFIG_INBOX_SIZE:
+ /* These elements are defined but not being used yet. No warn is required */
+ break;
+ default:
+ dev_info(sdev->dev, "unsupported token type: %d\n",
+ config_data->elems[i].token);
+ }
+
+ return 0;
+}
diff --git a/sound/soc/sof/intel/hda-mlink.c b/sound/soc/sof/intel/hda-mlink.c
new file mode 100644
index 000000000000..b592e687a87a
--- /dev/null
+++ b/sound/soc/sof/intel/hda-mlink.c
@@ -0,0 +1,974 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2022 Intel Corporation. All rights reserved.
+//
+
+/*
+ * Management of HDaudio multi-link (capabilities, power, coupling)
+ */
+
+#include <sound/hdaudio_ext.h>
+#include <sound/hda_register.h>
+#include <sound/hda-mlink.h>
+
+#include <linux/bitfield.h>
+#include <linux/module.h>
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_MLINK)
+
+/* worst-case number of sublinks is used for sublink refcount array allocation only */
+#define HDAML_MAX_SUBLINKS (AZX_ML_LCTL_CPA_SHIFT - AZX_ML_LCTL_SPA_SHIFT)
+
+/**
+ * struct hdac_ext2_link - HDAudio extended+alternate link
+ *
+ * @hext_link: hdac_ext_link
+ * @alt: flag set for alternate extended links
+ * @intc: boolean for interrupt capable
+ * @ofls: boolean for offload support
+ * @lss: boolean for link synchronization capabilities
+ * @slcount: sublink count
+ * @elid: extended link ID (AZX_REG_ML_LEPTR_ID_ defines)
+ * @elver: extended link version
+ * @leptr: extended link pointer
+ * @eml_lock: mutual exclusion to access shared registers e.g. CPA/SPA bits
+ * in LCTL register
+ * @sublink_ref_count: array of refcounts, required to power-manage sublinks independently
+ * @base_ptr: pointer to shim/ip/shim_vs space
+ * @instance_offset: offset between each of @slcount instances managed by link
+ * @shim_offset: offset to SHIM register base
+ * @ip_offset: offset to IP register base
+ * @shim_vs_offset: offset to vendor-specific (VS) SHIM base
+ */
+struct hdac_ext2_link {
+ struct hdac_ext_link hext_link;
+
+ /* read directly from LCAP register */
+ bool alt;
+ bool intc;
+ bool ofls;
+ bool lss;
+ int slcount;
+ int elid;
+ int elver;
+ u32 leptr;
+
+ struct mutex eml_lock; /* prevent concurrent access to e.g. CPA/SPA */
+ int sublink_ref_count[HDAML_MAX_SUBLINKS];
+
+ /* internal values computed from LCAP contents */
+ void __iomem *base_ptr;
+ u32 instance_offset;
+ u32 shim_offset;
+ u32 ip_offset;
+ u32 shim_vs_offset;
+};
+
+#define hdac_ext_link_to_ext2(h) container_of(h, struct hdac_ext2_link, hext_link)
+
+#define AZX_REG_SDW_INSTANCE_OFFSET 0x8000
+#define AZX_REG_SDW_SHIM_OFFSET 0x0
+#define AZX_REG_SDW_IP_OFFSET 0x100
+#define AZX_REG_SDW_VS_SHIM_OFFSET 0x6000
+#define AZX_REG_SDW_SHIM_PCMSyCM(y) (0x16 + 0x4 * (y))
+
+/* only one instance supported */
+#define AZX_REG_INTEL_DMIC_SHIM_OFFSET 0x0
+#define AZX_REG_INTEL_DMIC_IP_OFFSET 0x100
+#define AZX_REG_INTEL_DMIC_VS_SHIM_OFFSET 0x6000
+
+#define AZX_REG_INTEL_SSP_INSTANCE_OFFSET 0x1000
+#define AZX_REG_INTEL_SSP_SHIM_OFFSET 0x0
+#define AZX_REG_INTEL_SSP_IP_OFFSET 0x100
+#define AZX_REG_INTEL_SSP_VS_SHIM_OFFSET 0xC00
+
+/* only one instance supported */
+#define AZX_REG_INTEL_UAOL_SHIM_OFFSET 0x0
+#define AZX_REG_INTEL_UAOL_IP_OFFSET 0x100
+#define AZX_REG_INTEL_UAOL_VS_SHIM_OFFSET 0xC00
+
+/* HDAML section - this part follows sequences in the hardware specification,
+ * including naming conventions and the use of the hdaml_ prefix.
+ * The code is intentionally minimal with limited dependencies on frameworks or
+ * helpers. Locking and scanning lists is handled at a higher level
+ */
+
+static int hdaml_lnk_enum(struct device *dev, struct hdac_ext2_link *h2link,
+ void __iomem *remap_addr, void __iomem *ml_addr, int link_idx)
+{
+ struct hdac_ext_link *hlink = &h2link->hext_link;
+ u32 base_offset;
+
+ hlink->lcaps = readl(ml_addr + AZX_REG_ML_LCAP);
+
+ h2link->alt = FIELD_GET(AZX_ML_HDA_LCAP_ALT, hlink->lcaps);
+
+ /* handle alternate extensions */
+ if (!h2link->alt) {
+ h2link->slcount = 1;
+
+ /*
+ * LSDIID is initialized by hardware for HDaudio link,
+ * it needs to be setup by software for alternate links
+ */
+ hlink->lsdiid = readw(ml_addr + AZX_REG_ML_LSDIID);
+
+ dev_dbg(dev, "Link %d: HDAudio - lsdiid=%d\n",
+ link_idx, hlink->lsdiid);
+
+ return 0;
+ }
+
+ h2link->intc = FIELD_GET(AZX_ML_HDA_LCAP_INTC, hlink->lcaps);
+ h2link->ofls = FIELD_GET(AZX_ML_HDA_LCAP_OFLS, hlink->lcaps);
+ h2link->lss = FIELD_GET(AZX_ML_HDA_LCAP_LSS, hlink->lcaps);
+
+ /* read slcount (increment due to zero-based hardware representation */
+ h2link->slcount = FIELD_GET(AZX_ML_HDA_LCAP_SLCOUNT, hlink->lcaps) + 1;
+ dev_dbg(dev, "Link %d: HDAudio extended - sublink count %d\n",
+ link_idx, h2link->slcount);
+
+ /* find IP ID and offsets */
+ h2link->leptr = readl(ml_addr + AZX_REG_ML_LEPTR);
+
+ h2link->elid = FIELD_GET(AZX_REG_ML_LEPTR_ID, h2link->leptr);
+
+ base_offset = FIELD_GET(AZX_REG_ML_LEPTR_PTR, h2link->leptr);
+ h2link->base_ptr = remap_addr + base_offset;
+
+ switch (h2link->elid) {
+ case AZX_REG_ML_LEPTR_ID_SDW:
+ h2link->instance_offset = AZX_REG_SDW_INSTANCE_OFFSET;
+ h2link->shim_offset = AZX_REG_SDW_SHIM_OFFSET;
+ h2link->ip_offset = AZX_REG_SDW_IP_OFFSET;
+ h2link->shim_vs_offset = AZX_REG_SDW_VS_SHIM_OFFSET;
+ dev_dbg(dev, "Link %d: HDAudio extended - SoundWire alternate link, leptr.ptr %#x\n",
+ link_idx, base_offset);
+ break;
+ case AZX_REG_ML_LEPTR_ID_INTEL_DMIC:
+ h2link->shim_offset = AZX_REG_INTEL_DMIC_SHIM_OFFSET;
+ h2link->ip_offset = AZX_REG_INTEL_DMIC_IP_OFFSET;
+ h2link->shim_vs_offset = AZX_REG_INTEL_DMIC_VS_SHIM_OFFSET;
+ dev_dbg(dev, "Link %d: HDAudio extended - INTEL DMIC alternate link, leptr.ptr %#x\n",
+ link_idx, base_offset);
+ break;
+ case AZX_REG_ML_LEPTR_ID_INTEL_SSP:
+ h2link->instance_offset = AZX_REG_INTEL_SSP_INSTANCE_OFFSET;
+ h2link->shim_offset = AZX_REG_INTEL_SSP_SHIM_OFFSET;
+ h2link->ip_offset = AZX_REG_INTEL_SSP_IP_OFFSET;
+ h2link->shim_vs_offset = AZX_REG_INTEL_SSP_VS_SHIM_OFFSET;
+ dev_dbg(dev, "Link %d: HDAudio extended - INTEL SSP alternate link, leptr.ptr %#x\n",
+ link_idx, base_offset);
+ break;
+ case AZX_REG_ML_LEPTR_ID_INTEL_UAOL:
+ h2link->shim_offset = AZX_REG_INTEL_UAOL_SHIM_OFFSET;
+ h2link->ip_offset = AZX_REG_INTEL_UAOL_IP_OFFSET;
+ h2link->shim_vs_offset = AZX_REG_INTEL_UAOL_VS_SHIM_OFFSET;
+ dev_dbg(dev, "Link %d: HDAudio extended - INTEL UAOL alternate link, leptr.ptr %#x\n",
+ link_idx, base_offset);
+ break;
+ default:
+ dev_err(dev, "Link %d: HDAudio extended - Unsupported alternate link, leptr.id=%#02x value\n",
+ link_idx, h2link->elid);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*
+ * Hardware recommendations are to wait ~10us before checking any hardware transition
+ * reported by bits changing status.
+ * This value does not need to be super-precise, a slack of 5us is perfectly acceptable.
+ * The worst-case is about 1ms before reporting an issue
+ */
+#define HDAML_POLL_DELAY_MIN_US 10
+#define HDAML_POLL_DELAY_SLACK_US 5
+#define HDAML_POLL_DELAY_RETRY 100
+
+static int check_sublink_power(u32 __iomem *lctl, int sublink, bool enabled)
+{
+ int mask = BIT(sublink) << AZX_ML_LCTL_CPA_SHIFT;
+ int retry = HDAML_POLL_DELAY_RETRY;
+ u32 val;
+
+ usleep_range(HDAML_POLL_DELAY_MIN_US,
+ HDAML_POLL_DELAY_MIN_US + HDAML_POLL_DELAY_SLACK_US);
+ do {
+ val = readl(lctl);
+ if (enabled) {
+ if (val & mask)
+ return 0;
+ } else {
+ if (!(val & mask))
+ return 0;
+ }
+ usleep_range(HDAML_POLL_DELAY_MIN_US,
+ HDAML_POLL_DELAY_MIN_US + HDAML_POLL_DELAY_SLACK_US);
+
+ } while (--retry);
+
+ return -EIO;
+}
+
+static int hdaml_link_init(u32 __iomem *lctl, int sublink)
+{
+ u32 val;
+ u32 mask = BIT(sublink) << AZX_ML_LCTL_SPA_SHIFT;
+
+ val = readl(lctl);
+ val |= mask;
+
+ writel(val, lctl);
+
+ return check_sublink_power(lctl, sublink, true);
+}
+
+static int hdaml_link_shutdown(u32 __iomem *lctl, int sublink)
+{
+ u32 val;
+ u32 mask;
+
+ val = readl(lctl);
+ mask = BIT(sublink) << AZX_ML_LCTL_SPA_SHIFT;
+ val &= ~mask;
+
+ writel(val, lctl);
+
+ return check_sublink_power(lctl, sublink, false);
+}
+
+static void hdaml_link_enable_interrupt(u32 __iomem *lctl, bool enable)
+{
+ u32 val;
+
+ val = readl(lctl);
+ if (enable)
+ val |= AZX_ML_LCTL_INTEN;
+ else
+ val &= ~AZX_ML_LCTL_INTEN;
+
+ writel(val, lctl);
+}
+
+static bool hdaml_link_check_interrupt(u32 __iomem *lctl)
+{
+ u32 val;
+
+ val = readl(lctl);
+
+ return val & AZX_ML_LCTL_INTSTS;
+}
+
+static int hdaml_wait_bit(void __iomem *base, int offset, u32 mask, u32 target)
+{
+ int timeout = HDAML_POLL_DELAY_RETRY;
+ u32 reg_read;
+
+ do {
+ reg_read = readl(base + offset);
+ if ((reg_read & mask) == target)
+ return 0;
+
+ timeout--;
+ usleep_range(HDAML_POLL_DELAY_MIN_US,
+ HDAML_POLL_DELAY_MIN_US + HDAML_POLL_DELAY_SLACK_US);
+ } while (timeout != 0);
+
+ return -EAGAIN;
+}
+
+static void hdaml_link_set_syncprd(u32 __iomem *lsync, u32 syncprd)
+{
+ u32 val;
+
+ val = readl(lsync);
+ val &= ~AZX_REG_ML_LSYNC_SYNCPRD;
+ val |= (syncprd & AZX_REG_ML_LSYNC_SYNCPRD);
+
+ /*
+ * set SYNCPU but do not wait. The bit is cleared by hardware when
+ * the link becomes active.
+ */
+ val |= AZX_REG_ML_LSYNC_SYNCPU;
+
+ writel(val, lsync);
+}
+
+static int hdaml_link_wait_syncpu(u32 __iomem *lsync)
+{
+ return hdaml_wait_bit(lsync, 0, AZX_REG_ML_LSYNC_SYNCPU, 0);
+}
+
+static void hdaml_link_sync_arm(u32 __iomem *lsync, int sublink)
+{
+ u32 val;
+
+ val = readl(lsync);
+ val |= (AZX_REG_ML_LSYNC_CMDSYNC << sublink);
+
+ writel(val, lsync);
+}
+
+static void hdaml_link_sync_go(u32 __iomem *lsync)
+{
+ u32 val;
+
+ val = readl(lsync);
+ val |= AZX_REG_ML_LSYNC_SYNCGO;
+
+ writel(val, lsync);
+}
+
+static bool hdaml_link_check_cmdsync(u32 __iomem *lsync, u32 cmdsync_mask)
+{
+ u32 val;
+
+ val = readl(lsync);
+
+ return !!(val & cmdsync_mask);
+}
+
+static u16 hdaml_link_get_lsdiid(u16 __iomem *lsdiid)
+{
+ return readw(lsdiid);
+}
+
+static void hdaml_link_set_lsdiid(u16 __iomem *lsdiid, int dev_num)
+{
+ u16 val;
+
+ val = readw(lsdiid);
+ val |= BIT(dev_num);
+
+ writew(val, lsdiid);
+}
+
+static void hdaml_shim_map_stream_ch(u16 __iomem *pcmsycm, int lchan, int hchan,
+ int stream_id, int dir)
+{
+ u16 val;
+
+ val = readw(pcmsycm);
+
+ u16p_replace_bits(&val, lchan, GENMASK(3, 0));
+ u16p_replace_bits(&val, hchan, GENMASK(7, 4));
+ u16p_replace_bits(&val, stream_id, GENMASK(13, 8));
+ u16p_replace_bits(&val, dir, BIT(15));
+
+ writew(val, pcmsycm);
+}
+
+static void hdaml_lctl_offload_enable(u32 __iomem *lctl, bool enable)
+{
+ u32 val = readl(lctl);
+
+ if (enable)
+ val |= AZX_ML_LCTL_OFLEN;
+ else
+ val &= ~AZX_ML_LCTL_OFLEN;
+
+ writel(val, lctl);
+}
+
+/* END HDAML section */
+
+static int hda_ml_alloc_h2link(struct hdac_bus *bus, int index)
+{
+ struct hdac_ext2_link *h2link;
+ struct hdac_ext_link *hlink;
+ int ret;
+
+ h2link = kzalloc(sizeof(*h2link), GFP_KERNEL);
+ if (!h2link)
+ return -ENOMEM;
+
+ /* basic initialization */
+ hlink = &h2link->hext_link;
+
+ hlink->index = index;
+ hlink->bus = bus;
+ hlink->ml_addr = bus->mlcap + AZX_ML_BASE + (AZX_ML_INTERVAL * index);
+
+ ret = hdaml_lnk_enum(bus->dev, h2link, bus->remap_addr, hlink->ml_addr, index);
+ if (ret < 0) {
+ kfree(h2link);
+ return ret;
+ }
+
+ mutex_init(&h2link->eml_lock);
+
+ list_add_tail(&hlink->list, &bus->hlink_list);
+
+ /*
+ * HDaudio regular links are powered-on by default, the
+ * refcount needs to be initialized.
+ */
+ if (!h2link->alt)
+ hlink->ref_count = 1;
+
+ return 0;
+}
+
+int hda_bus_ml_init(struct hdac_bus *bus)
+{
+ u32 link_count;
+ int ret;
+ int i;
+
+ if (!bus->mlcap)
+ return 0;
+
+ link_count = readl(bus->mlcap + AZX_REG_ML_MLCD) + 1;
+
+ dev_dbg(bus->dev, "HDAudio Multi-Link count: %d\n", link_count);
+
+ for (i = 0; i < link_count; i++) {
+ ret = hda_ml_alloc_h2link(bus, i);
+ if (ret < 0) {
+ hda_bus_ml_free(bus);
+ return ret;
+ }
+ }
+ return 0;
+}
+EXPORT_SYMBOL_NS(hda_bus_ml_init, SND_SOC_SOF_HDA_MLINK);
+
+void hda_bus_ml_free(struct hdac_bus *bus)
+{
+ struct hdac_ext_link *hlink, *_h;
+ struct hdac_ext2_link *h2link;
+
+ if (!bus->mlcap)
+ return;
+
+ list_for_each_entry_safe(hlink, _h, &bus->hlink_list, list) {
+ list_del(&hlink->list);
+ h2link = hdac_ext_link_to_ext2(hlink);
+
+ mutex_destroy(&h2link->eml_lock);
+ kfree(h2link);
+ }
+}
+EXPORT_SYMBOL_NS(hda_bus_ml_free, SND_SOC_SOF_HDA_MLINK);
+
+static struct hdac_ext2_link *
+find_ext2_link(struct hdac_bus *bus, bool alt, int elid)
+{
+ struct hdac_ext_link *hlink;
+
+ list_for_each_entry(hlink, &bus->hlink_list, list) {
+ struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
+
+ if (h2link->alt == alt && h2link->elid == elid)
+ return h2link;
+ }
+
+ return NULL;
+}
+
+int hdac_bus_eml_get_count(struct hdac_bus *bus, bool alt, int elid)
+{
+ struct hdac_ext2_link *h2link;
+
+ h2link = find_ext2_link(bus, alt, elid);
+ if (!h2link)
+ return 0;
+
+ return h2link->slcount;
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_get_count, SND_SOC_SOF_HDA_MLINK);
+
+void hdac_bus_eml_enable_interrupt(struct hdac_bus *bus, bool alt, int elid, bool enable)
+{
+ struct hdac_ext2_link *h2link;
+ struct hdac_ext_link *hlink;
+
+ h2link = find_ext2_link(bus, alt, elid);
+ if (!h2link)
+ return;
+
+ if (!h2link->intc)
+ return;
+
+ hlink = &h2link->hext_link;
+
+ mutex_lock(&h2link->eml_lock);
+
+ hdaml_link_enable_interrupt(hlink->ml_addr + AZX_REG_ML_LCTL, enable);
+
+ mutex_unlock(&h2link->eml_lock);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_enable_interrupt, SND_SOC_SOF_HDA_MLINK);
+
+bool hdac_bus_eml_check_interrupt(struct hdac_bus *bus, bool alt, int elid)
+{
+ struct hdac_ext2_link *h2link;
+ struct hdac_ext_link *hlink;
+
+ h2link = find_ext2_link(bus, alt, elid);
+ if (!h2link)
+ return false;
+
+ if (!h2link->intc)
+ return false;
+
+ hlink = &h2link->hext_link;
+
+ return hdaml_link_check_interrupt(hlink->ml_addr + AZX_REG_ML_LCTL);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_check_interrupt, SND_SOC_SOF_HDA_MLINK);
+
+int hdac_bus_eml_set_syncprd_unlocked(struct hdac_bus *bus, bool alt, int elid, u32 syncprd)
+{
+ struct hdac_ext2_link *h2link;
+ struct hdac_ext_link *hlink;
+
+ h2link = find_ext2_link(bus, alt, elid);
+ if (!h2link)
+ return 0;
+
+ if (!h2link->lss)
+ return 0;
+
+ hlink = &h2link->hext_link;
+
+ hdaml_link_set_syncprd(hlink->ml_addr + AZX_REG_ML_LSYNC, syncprd);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_set_syncprd_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+int hdac_bus_eml_sdw_set_syncprd_unlocked(struct hdac_bus *bus, u32 syncprd)
+{
+ return hdac_bus_eml_set_syncprd_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, syncprd);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_set_syncprd_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+int hdac_bus_eml_wait_syncpu_unlocked(struct hdac_bus *bus, bool alt, int elid)
+{
+ struct hdac_ext2_link *h2link;
+ struct hdac_ext_link *hlink;
+
+ h2link = find_ext2_link(bus, alt, elid);
+ if (!h2link)
+ return 0;
+
+ if (!h2link->lss)
+ return 0;
+
+ hlink = &h2link->hext_link;
+
+ return hdaml_link_wait_syncpu(hlink->ml_addr + AZX_REG_ML_LSYNC);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_wait_syncpu_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+int hdac_bus_eml_sdw_wait_syncpu_unlocked(struct hdac_bus *bus)
+{
+ return hdac_bus_eml_wait_syncpu_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_wait_syncpu_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+void hdac_bus_eml_sync_arm_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink)
+{
+ struct hdac_ext2_link *h2link;
+ struct hdac_ext_link *hlink;
+
+ h2link = find_ext2_link(bus, alt, elid);
+ if (!h2link)
+ return;
+
+ if (!h2link->lss)
+ return;
+
+ hlink = &h2link->hext_link;
+
+ hdaml_link_sync_arm(hlink->ml_addr + AZX_REG_ML_LSYNC, sublink);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_sync_arm_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+void hdac_bus_eml_sdw_sync_arm_unlocked(struct hdac_bus *bus, int sublink)
+{
+ hdac_bus_eml_sync_arm_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, sublink);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_sync_arm_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+int hdac_bus_eml_sync_go_unlocked(struct hdac_bus *bus, bool alt, int elid)
+{
+ struct hdac_ext2_link *h2link;
+ struct hdac_ext_link *hlink;
+
+ h2link = find_ext2_link(bus, alt, elid);
+ if (!h2link)
+ return 0;
+
+ if (!h2link->lss)
+ return 0;
+
+ hlink = &h2link->hext_link;
+
+ hdaml_link_sync_go(hlink->ml_addr + AZX_REG_ML_LSYNC);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_sync_go_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+int hdac_bus_eml_sdw_sync_go_unlocked(struct hdac_bus *bus)
+{
+ return hdac_bus_eml_sync_go_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_sync_go_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+bool hdac_bus_eml_check_cmdsync_unlocked(struct hdac_bus *bus, bool alt, int elid)
+{
+ struct hdac_ext2_link *h2link;
+ struct hdac_ext_link *hlink;
+ u32 cmdsync_mask;
+
+ h2link = find_ext2_link(bus, alt, elid);
+ if (!h2link)
+ return 0;
+
+ if (!h2link->lss)
+ return 0;
+
+ hlink = &h2link->hext_link;
+
+ cmdsync_mask = GENMASK(AZX_REG_ML_LSYNC_CMDSYNC_SHIFT + h2link->slcount - 1,
+ AZX_REG_ML_LSYNC_CMDSYNC_SHIFT);
+
+ return hdaml_link_check_cmdsync(hlink->ml_addr + AZX_REG_ML_LSYNC,
+ cmdsync_mask);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_check_cmdsync_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+bool hdac_bus_eml_sdw_check_cmdsync_unlocked(struct hdac_bus *bus)
+{
+ return hdac_bus_eml_check_cmdsync_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_check_cmdsync_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+static int hdac_bus_eml_power_up_base(struct hdac_bus *bus, bool alt, int elid, int sublink,
+ bool eml_lock)
+{
+ struct hdac_ext2_link *h2link;
+ struct hdac_ext_link *hlink;
+ int ret = 0;
+
+ h2link = find_ext2_link(bus, alt, elid);
+ if (!h2link)
+ return -ENODEV;
+
+ if (sublink >= h2link->slcount)
+ return -EINVAL;
+
+ hlink = &h2link->hext_link;
+
+ if (eml_lock)
+ mutex_lock(&h2link->eml_lock);
+
+ if (!alt) {
+ if (++hlink->ref_count > 1)
+ goto skip_init;
+ } else {
+ if (++h2link->sublink_ref_count[sublink] > 1)
+ goto skip_init;
+ }
+
+ ret = hdaml_link_init(hlink->ml_addr + AZX_REG_ML_LCTL, sublink);
+
+skip_init:
+ if (eml_lock)
+ mutex_unlock(&h2link->eml_lock);
+
+ return ret;
+}
+
+int hdac_bus_eml_power_up(struct hdac_bus *bus, bool alt, int elid, int sublink)
+{
+ return hdac_bus_eml_power_up_base(bus, alt, elid, sublink, true);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_power_up, SND_SOC_SOF_HDA_MLINK);
+
+int hdac_bus_eml_power_up_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink)
+{
+ return hdac_bus_eml_power_up_base(bus, alt, elid, sublink, false);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_power_up_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+static int hdac_bus_eml_power_down_base(struct hdac_bus *bus, bool alt, int elid, int sublink,
+ bool eml_lock)
+{
+ struct hdac_ext2_link *h2link;
+ struct hdac_ext_link *hlink;
+ int ret = 0;
+
+ h2link = find_ext2_link(bus, alt, elid);
+ if (!h2link)
+ return -ENODEV;
+
+ if (sublink >= h2link->slcount)
+ return -EINVAL;
+
+ hlink = &h2link->hext_link;
+
+ if (eml_lock)
+ mutex_lock(&h2link->eml_lock);
+
+ if (!alt) {
+ if (--hlink->ref_count > 0)
+ goto skip_shutdown;
+ } else {
+ if (--h2link->sublink_ref_count[sublink] > 0)
+ goto skip_shutdown;
+ }
+ ret = hdaml_link_shutdown(hlink->ml_addr + AZX_REG_ML_LCTL, sublink);
+
+skip_shutdown:
+ if (eml_lock)
+ mutex_unlock(&h2link->eml_lock);
+
+ return ret;
+}
+
+int hdac_bus_eml_power_down(struct hdac_bus *bus, bool alt, int elid, int sublink)
+{
+ return hdac_bus_eml_power_down_base(bus, alt, elid, sublink, true);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_power_down, SND_SOC_SOF_HDA_MLINK);
+
+int hdac_bus_eml_power_down_unlocked(struct hdac_bus *bus, bool alt, int elid, int sublink)
+{
+ return hdac_bus_eml_power_down_base(bus, alt, elid, sublink, false);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_power_down_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+int hdac_bus_eml_sdw_power_up_unlocked(struct hdac_bus *bus, int sublink)
+{
+ return hdac_bus_eml_power_up_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, sublink);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_power_up_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+int hdac_bus_eml_sdw_power_down_unlocked(struct hdac_bus *bus, int sublink)
+{
+ return hdac_bus_eml_power_down_unlocked(bus, true, AZX_REG_ML_LEPTR_ID_SDW, sublink);
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_power_down_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+int hdac_bus_eml_sdw_get_lsdiid_unlocked(struct hdac_bus *bus, int sublink, u16 *lsdiid)
+{
+ struct hdac_ext2_link *h2link;
+ struct hdac_ext_link *hlink;
+
+ h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
+ if (!h2link)
+ return -ENODEV;
+
+ hlink = &h2link->hext_link;
+
+ *lsdiid = hdaml_link_get_lsdiid(hlink->ml_addr + AZX_REG_ML_LSDIID_OFFSET(sublink));
+
+ return 0;
+} EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_get_lsdiid_unlocked, SND_SOC_SOF_HDA_MLINK);
+
+int hdac_bus_eml_sdw_set_lsdiid(struct hdac_bus *bus, int sublink, int dev_num)
+{
+ struct hdac_ext2_link *h2link;
+ struct hdac_ext_link *hlink;
+
+ h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
+ if (!h2link)
+ return -ENODEV;
+
+ hlink = &h2link->hext_link;
+
+ mutex_lock(&h2link->eml_lock);
+
+ hdaml_link_set_lsdiid(hlink->ml_addr + AZX_REG_ML_LSDIID_OFFSET(sublink), dev_num);
+
+ mutex_unlock(&h2link->eml_lock);
+
+ return 0;
+} EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_set_lsdiid, SND_SOC_SOF_HDA_MLINK);
+
+/*
+ * the 'y' parameter comes from the PCMSyCM hardware register naming. 'y' refers to the
+ * PDI index, i.e. the FIFO used for RX or TX
+ */
+int hdac_bus_eml_sdw_map_stream_ch(struct hdac_bus *bus, int sublink, int y,
+ int channel_mask, int stream_id, int dir)
+{
+ struct hdac_ext2_link *h2link;
+ u16 __iomem *pcmsycm;
+ int hchan;
+ int lchan;
+ u16 val;
+
+ h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
+ if (!h2link)
+ return -ENODEV;
+
+ pcmsycm = h2link->base_ptr + h2link->shim_offset +
+ h2link->instance_offset * sublink +
+ AZX_REG_SDW_SHIM_PCMSyCM(y);
+
+ if (channel_mask) {
+ hchan = __fls(channel_mask);
+ lchan = __ffs(channel_mask);
+ } else {
+ hchan = 0;
+ lchan = 0;
+ }
+
+ mutex_lock(&h2link->eml_lock);
+
+ hdaml_shim_map_stream_ch(pcmsycm, lchan, hchan,
+ stream_id, dir);
+
+ mutex_unlock(&h2link->eml_lock);
+
+ val = readw(pcmsycm);
+
+ dev_dbg(bus->dev, "sublink %d channel_mask %#x stream_id %d dir %d pcmscm %#x\n",
+ sublink, channel_mask, stream_id, dir, val);
+
+ return 0;
+} EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_map_stream_ch, SND_SOC_SOF_HDA_MLINK);
+
+void hda_bus_ml_put_all(struct hdac_bus *bus)
+{
+ struct hdac_ext_link *hlink;
+
+ list_for_each_entry(hlink, &bus->hlink_list, list) {
+ struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
+
+ if (!h2link->alt)
+ snd_hdac_ext_bus_link_put(bus, hlink);
+ }
+}
+EXPORT_SYMBOL_NS(hda_bus_ml_put_all, SND_SOC_SOF_HDA_MLINK);
+
+void hda_bus_ml_reset_losidv(struct hdac_bus *bus)
+{
+ struct hdac_ext_link *hlink;
+
+ /* Reset stream-to-link mapping */
+ list_for_each_entry(hlink, &bus->hlink_list, list)
+ writel(0, hlink->ml_addr + AZX_REG_ML_LOSIDV);
+}
+EXPORT_SYMBOL_NS(hda_bus_ml_reset_losidv, SND_SOC_SOF_HDA_MLINK);
+
+int hda_bus_ml_resume(struct hdac_bus *bus)
+{
+ struct hdac_ext_link *hlink;
+ int ret;
+
+ /* power up links that were active before suspend */
+ list_for_each_entry(hlink, &bus->hlink_list, list) {
+ struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
+
+ if (!h2link->alt && hlink->ref_count) {
+ ret = snd_hdac_ext_bus_link_power_up(hlink);
+ if (ret < 0)
+ return ret;
+ }
+ }
+ return 0;
+}
+EXPORT_SYMBOL_NS(hda_bus_ml_resume, SND_SOC_SOF_HDA_MLINK);
+
+int hda_bus_ml_suspend(struct hdac_bus *bus)
+{
+ struct hdac_ext_link *hlink;
+ int ret;
+
+ list_for_each_entry(hlink, &bus->hlink_list, list) {
+ struct hdac_ext2_link *h2link = hdac_ext_link_to_ext2(hlink);
+
+ if (!h2link->alt) {
+ ret = snd_hdac_ext_bus_link_power_down(hlink);
+ if (ret < 0)
+ return ret;
+ }
+ }
+ return 0;
+}
+EXPORT_SYMBOL_NS(hda_bus_ml_suspend, SND_SOC_SOF_HDA_MLINK);
+
+struct mutex *hdac_bus_eml_get_mutex(struct hdac_bus *bus, bool alt, int elid)
+{
+ struct hdac_ext2_link *h2link;
+
+ h2link = find_ext2_link(bus, alt, elid);
+ if (!h2link)
+ return NULL;
+
+ return &h2link->eml_lock;
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_get_mutex, SND_SOC_SOF_HDA_MLINK);
+
+struct hdac_ext_link *hdac_bus_eml_ssp_get_hlink(struct hdac_bus *bus)
+{
+ struct hdac_ext2_link *h2link;
+
+ h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_INTEL_SSP);
+ if (!h2link)
+ return NULL;
+
+ return &h2link->hext_link;
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_ssp_get_hlink, SND_SOC_SOF_HDA_MLINK);
+
+struct hdac_ext_link *hdac_bus_eml_dmic_get_hlink(struct hdac_bus *bus)
+{
+ struct hdac_ext2_link *h2link;
+
+ h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_INTEL_DMIC);
+ if (!h2link)
+ return NULL;
+
+ return &h2link->hext_link;
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_dmic_get_hlink, SND_SOC_SOF_HDA_MLINK);
+
+struct hdac_ext_link *hdac_bus_eml_sdw_get_hlink(struct hdac_bus *bus)
+{
+ struct hdac_ext2_link *h2link;
+
+ h2link = find_ext2_link(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
+ if (!h2link)
+ return NULL;
+
+ return &h2link->hext_link;
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_sdw_get_hlink, SND_SOC_SOF_HDA_MLINK);
+
+int hdac_bus_eml_enable_offload(struct hdac_bus *bus, bool alt, int elid, bool enable)
+{
+ struct hdac_ext2_link *h2link;
+ struct hdac_ext_link *hlink;
+
+ h2link = find_ext2_link(bus, alt, elid);
+ if (!h2link)
+ return -ENODEV;
+
+ if (!h2link->ofls)
+ return 0;
+
+ hlink = &h2link->hext_link;
+
+ mutex_lock(&h2link->eml_lock);
+
+ hdaml_lctl_offload_enable(hlink->ml_addr + AZX_REG_ML_LCTL, enable);
+
+ mutex_unlock(&h2link->eml_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(hdac_bus_eml_enable_offload, SND_SOC_SOF_HDA_MLINK);
+
+#endif
+
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/sof/intel/hda-pcm.c b/sound/soc/sof/intel/hda-pcm.c
index b527d5958ae5..18f07364d219 100644
--- a/sound/soc/sof/intel/hda-pcm.c
+++ b/sound/soc/sof/intel/hda-pcm.c
@@ -15,8 +15,10 @@
* Hardware interface for generic Intel audio DSP HDA IP
*/
+#include <linux/moduleparam.h>
#include <sound/hda_register.h>
#include <sound/pcm_params.h>
+#include <trace/events/sof_intel.h>
#include "../sof-audio.h"
#include "../ops.h"
#include "hda.h"
@@ -27,6 +29,14 @@
#define SDnFMT_BITS(x) ((x) << 4)
#define SDnFMT_CHAN(x) ((x) << 0)
+static bool hda_always_enable_dmi_l1;
+module_param_named(always_enable_dmi_l1, hda_always_enable_dmi_l1, bool, 0444);
+MODULE_PARM_DESC(always_enable_dmi_l1, "SOF HDA always enable DMI l1");
+
+static bool hda_disable_rewinds;
+module_param_named(disable_rewinds, hda_disable_rewinds, bool, 0444);
+MODULE_PARM_DESC(disable_rewinds, "SOF HDA disable rewinds");
+
u32 hda_dsp_get_mult_div(struct snd_sof_dev *sdev, int rate)
{
switch (rate) {
@@ -84,53 +94,73 @@ u32 hda_dsp_get_bits(struct snd_sof_dev *sdev, int sample_bits)
int hda_dsp_pcm_hw_params(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
- struct sof_ipc_stream_params *ipc_params)
+ struct snd_sof_platform_stream_params *platform_params)
{
struct hdac_stream *hstream = substream->runtime->private_data;
- struct hdac_ext_stream *stream = stream_to_hdac_ext_stream(hstream);
+ struct hdac_ext_stream *hext_stream = stream_to_hdac_ext_stream(hstream);
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
struct snd_dma_buffer *dmab;
- struct sof_ipc_fw_version *v = &sdev->fw_ready.version;
int ret;
- u32 size, rate, bits;
-
- size = params_buffer_bytes(params);
- rate = hda_dsp_get_mult_div(sdev, params_rate(params));
- bits = hda_dsp_get_bits(sdev, params_width(params));
hstream->substream = substream;
dmab = substream->runtime->dma_buffer_p;
- hstream->format_val = rate | bits | (params_channels(params) - 1);
- hstream->bufsize = size;
+ /*
+ * Use the codec required format val (which is link_bps adjusted) when
+ * the DSP is not in use
+ */
+ if (!sdev->dspless_mode_selected) {
+ u32 rate = hda_dsp_get_mult_div(sdev, params_rate(params));
+ u32 bits = hda_dsp_get_bits(sdev, params_width(params));
+
+ hstream->format_val = rate | bits | (params_channels(params) - 1);
+ }
+
+ hstream->bufsize = params_buffer_bytes(params);
hstream->period_bytes = params_period_bytes(params);
hstream->no_period_wakeup =
(params->info & SNDRV_PCM_INFO_NO_PERIOD_WAKEUP) &&
(params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP);
- ret = hda_dsp_stream_hw_params(sdev, stream, dmab, params);
+ ret = hda_dsp_stream_hw_params(sdev, hext_stream, dmab, params);
if (ret < 0) {
- dev_err(sdev->dev, "error: hdac prepare failed: %x\n", ret);
+ dev_err(sdev->dev, "error: hdac prepare failed: %d\n", ret);
return ret;
}
- /* disable SPIB, to enable buffer wrap for stream */
- hda_dsp_stream_spib_config(sdev, stream, HDA_DSP_SPIB_DISABLE, 0);
-
- /* update no_stream_position flag for ipc params */
- if (hda && hda->no_ipc_position) {
- /* For older ABIs set host_period_bytes to zero to inform
- * FW we don't want position updates. Newer versions use
- * no_stream_position for this purpose.
- */
- if (v->abi_version < SOF_ABI_VER(3, 10, 0))
- ipc_params->host_period_bytes = 0;
- else
- ipc_params->no_stream_position = 1;
- }
+ /* enable SPIB when rewinds are disabled */
+ if (hda_disable_rewinds)
+ hda_dsp_stream_spib_config(sdev, hext_stream, HDA_DSP_SPIB_ENABLE, 0);
+ else
+ hda_dsp_stream_spib_config(sdev, hext_stream, HDA_DSP_SPIB_DISABLE, 0);
+
+ if (hda)
+ platform_params->no_ipc_position = hda->no_ipc_position;
+
+ platform_params->stream_tag = hstream->stream_tag;
+
+ return 0;
+}
+
+/* update SPIB register with appl position */
+int hda_dsp_pcm_ack(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream)
+{
+ struct hdac_stream *hstream = substream->runtime->private_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ ssize_t appl_pos, buf_size;
+ u32 spib;
+
+ appl_pos = frames_to_bytes(runtime, runtime->control->appl_ptr);
+ buf_size = frames_to_bytes(runtime, runtime->buffer_size);
+
+ spib = appl_pos % buf_size;
+
+ /* Allowable value for SPIB is 1 byte to max buffer size */
+ if (!spib)
+ spib = buf_size;
- ipc_params->stream_tag = hstream->stream_tag;
+ sof_io_write(sdev, hstream->spib_addr, spib);
return 0;
}
@@ -139,15 +169,15 @@ int hda_dsp_pcm_trigger(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream, int cmd)
{
struct hdac_stream *hstream = substream->runtime->private_data;
- struct hdac_ext_stream *stream = stream_to_hdac_ext_stream(hstream);
+ struct hdac_ext_stream *hext_stream = stream_to_hdac_ext_stream(hstream);
- return hda_dsp_stream_trigger(sdev, stream, cmd);
+ return hda_dsp_stream_trigger(sdev, hext_stream, cmd);
}
snd_pcm_uframes_t hda_dsp_pcm_pointer(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_soc_component *scomp = sdev->component;
struct hdac_stream *hstream = substream->runtime->private_data;
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
@@ -167,64 +197,68 @@ snd_pcm_uframes_t hda_dsp_pcm_pointer(struct snd_sof_dev *sdev,
goto found;
}
- /*
- * DPIB/posbuf position mode:
- * For Playback, Use DPIB register from HDA space which
- * reflects the actual data transferred.
- * For Capture, Use the position buffer for pointer, as DPIB
- * is not accurate enough, its update may be completed
- * earlier than the data written to DDR.
- */
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
- pos = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR,
- AZX_REG_VS_SDXDPIB_XBASE +
- (AZX_REG_VS_SDXDPIB_XINTERVAL *
- hstream->index));
- } else {
- /*
- * For capture stream, we need more workaround to fix the
- * position incorrect issue:
- *
- * 1. Wait at least 20us before reading position buffer after
- * the interrupt generated(IOC), to make sure position update
- * happens on frame boundary i.e. 20.833uSec for 48KHz.
- * 2. Perform a dummy Read to DPIB register to flush DMA
- * position value.
- * 3. Read the DMA Position from posbuf. Now the readback
- * value should be >= period boundary.
- */
- usleep_range(20, 21);
- snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR,
- AZX_REG_VS_SDXDPIB_XBASE +
- (AZX_REG_VS_SDXDPIB_XINTERVAL *
- hstream->index));
- pos = snd_hdac_stream_get_pos_posbuf(hstream);
- }
-
- if (pos >= hstream->bufsize)
- pos = 0;
-
+ pos = hda_dsp_stream_get_position(hstream, substream->stream, true);
found:
pos = bytes_to_frames(substream->runtime, pos);
- dev_vdbg(sdev->dev, "PCM: stream %d dir %d position %lu\n",
- hstream->index, substream->stream, pos);
+ trace_sof_intel_hda_dsp_pcm(sdev, hstream, substream, pos);
return pos;
}
int hda_dsp_pcm_open(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream)
{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_component *scomp = sdev->component;
struct hdac_ext_stream *dsp_stream;
+ struct snd_sof_pcm *spcm;
int direction = substream->stream;
+ u32 flags = 0;
+
+ spcm = snd_sof_find_spcm_dai(scomp, rtd);
+ if (!spcm) {
+ dev_err(sdev->dev, "error: can't find PCM with DAI ID %d\n", rtd->dai_link->id);
+ return -EINVAL;
+ }
+
+ /*
+ * if we want the .ack to work, we need to prevent the control from being mapped.
+ * The status can still be mapped.
+ */
+ if (hda_disable_rewinds)
+ runtime->hw.info |= SNDRV_PCM_INFO_NO_REWINDS | SNDRV_PCM_INFO_SYNC_APPLPTR;
+
+ /*
+ * All playback streams are DMI L1 capable, capture streams need
+ * pause push/release to be disabled
+ */
+ if (hda_always_enable_dmi_l1 && direction == SNDRV_PCM_STREAM_CAPTURE)
+ runtime->hw.info &= ~SNDRV_PCM_INFO_PAUSE;
- dsp_stream = hda_dsp_stream_get(sdev, direction);
+ if (hda_always_enable_dmi_l1 ||
+ direction == SNDRV_PCM_STREAM_PLAYBACK ||
+ spcm->stream[substream->stream].d0i3_compatible)
+ flags |= SOF_HDA_STREAM_DMI_L1_COMPATIBLE;
+ dsp_stream = hda_dsp_stream_get(sdev, direction, flags);
if (!dsp_stream) {
dev_err(sdev->dev, "error: no stream available\n");
return -ENODEV;
}
+ /* minimum as per HDA spec */
+ snd_pcm_hw_constraint_step(substream->runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 4);
+
+ /* avoid circular buffer wrap in middle of period */
+ snd_pcm_hw_constraint_integer(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+
+ /* Only S16 and S32 supported by HDA hardware when used without DSP */
+ if (sdev->dspless_mode_selected)
+ snd_pcm_hw_constraint_mask64(substream->runtime, SNDRV_PCM_HW_PARAM_FORMAT,
+ SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_S32);
+
/* binding pcm substream to hda stream */
substream->runtime->private_data = &dsp_stream->hstream;
return 0;
diff --git a/sound/soc/sof/intel/hda-probes.c b/sound/soc/sof/intel/hda-probes.c
new file mode 100644
index 000000000000..56a533c63cb0
--- /dev/null
+++ b/sound/soc/sof/intel/hda-probes.c
@@ -0,0 +1,148 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2019-2021 Intel Corporation. All rights reserved.
+//
+// Author: Cezary Rojewski <cezary.rojewski@intel.com>
+// Converted to SOF client:
+// Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
+// Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
+//
+
+#include <linux/module.h>
+#include <sound/hdaudio_ext.h>
+#include <sound/soc.h>
+#include "../sof-priv.h"
+#include "../sof-client-probes.h"
+#include "../sof-client.h"
+#include "hda.h"
+
+static inline struct hdac_ext_stream *
+hda_compr_get_stream(struct snd_compr_stream *cstream)
+{
+ return cstream->runtime->private_data;
+}
+
+static int hda_probes_compr_startup(struct sof_client_dev *cdev,
+ struct snd_compr_stream *cstream,
+ struct snd_soc_dai *dai, u32 *stream_id)
+{
+ struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
+ struct hdac_ext_stream *hext_stream;
+
+ hext_stream = hda_dsp_stream_get(sdev, cstream->direction, 0);
+ if (!hext_stream)
+ return -EBUSY;
+
+ hdac_stream(hext_stream)->curr_pos = 0;
+ hdac_stream(hext_stream)->cstream = cstream;
+ cstream->runtime->private_data = hext_stream;
+
+ *stream_id = hdac_stream(hext_stream)->stream_tag;
+
+ return 0;
+}
+
+static int hda_probes_compr_shutdown(struct sof_client_dev *cdev,
+ struct snd_compr_stream *cstream,
+ struct snd_soc_dai *dai)
+{
+ struct hdac_ext_stream *hext_stream = hda_compr_get_stream(cstream);
+ struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
+ int ret;
+
+ ret = hda_dsp_stream_put(sdev, cstream->direction,
+ hdac_stream(hext_stream)->stream_tag);
+ if (ret < 0) {
+ dev_dbg(sdev->dev, "stream put failed: %d\n", ret);
+ return ret;
+ }
+
+ hdac_stream(hext_stream)->cstream = NULL;
+ cstream->runtime->private_data = NULL;
+
+ return 0;
+}
+
+static int hda_probes_compr_set_params(struct sof_client_dev *cdev,
+ struct snd_compr_stream *cstream,
+ struct snd_compr_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct hdac_ext_stream *hext_stream = hda_compr_get_stream(cstream);
+ struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
+ struct hdac_stream *hstream = hdac_stream(hext_stream);
+ struct snd_dma_buffer *dmab;
+ u32 bits, rate;
+ int bps, ret;
+
+ dmab = cstream->runtime->dma_buffer_p;
+ /* compr params do not store bit depth, default to S32_LE */
+ bps = snd_pcm_format_physical_width(SNDRV_PCM_FORMAT_S32_LE);
+ if (bps < 0)
+ return bps;
+ bits = hda_dsp_get_bits(sdev, bps);
+ rate = hda_dsp_get_mult_div(sdev, params->codec.sample_rate);
+
+ hstream->format_val = rate | bits | (params->codec.ch_out - 1);
+ hstream->bufsize = cstream->runtime->buffer_size;
+ hstream->period_bytes = cstream->runtime->fragment_size;
+ hstream->no_period_wakeup = 0;
+
+ ret = hda_dsp_stream_hw_params(sdev, hext_stream, dmab, NULL);
+ if (ret < 0) {
+ dev_err(sdev->dev, "error: hdac prepare failed: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int hda_probes_compr_trigger(struct sof_client_dev *cdev,
+ struct snd_compr_stream *cstream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ struct hdac_ext_stream *hext_stream = hda_compr_get_stream(cstream);
+ struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
+
+ return hda_dsp_stream_trigger(sdev, hext_stream, cmd);
+}
+
+static int hda_probes_compr_pointer(struct sof_client_dev *cdev,
+ struct snd_compr_stream *cstream,
+ struct snd_compr_tstamp *tstamp,
+ struct snd_soc_dai *dai)
+{
+ struct hdac_ext_stream *hext_stream = hda_compr_get_stream(cstream);
+ struct snd_soc_pcm_stream *pstream;
+
+ pstream = &dai->driver->capture;
+ tstamp->copied_total = hdac_stream(hext_stream)->curr_pos;
+ tstamp->sampling_rate = snd_pcm_rate_bit_to_rate(pstream->rates);
+
+ return 0;
+}
+
+/* SOF client implementation */
+static const struct sof_probes_host_ops hda_probes_ops = {
+ .startup = hda_probes_compr_startup,
+ .shutdown = hda_probes_compr_shutdown,
+ .set_params = hda_probes_compr_set_params,
+ .trigger = hda_probes_compr_trigger,
+ .pointer = hda_probes_compr_pointer,
+};
+
+int hda_probes_register(struct snd_sof_dev *sdev)
+{
+ return sof_client_dev_register(sdev, "hda-probes", 0, &hda_probes_ops,
+ sizeof(hda_probes_ops));
+}
+
+void hda_probes_unregister(struct snd_sof_dev *sdev)
+{
+ sof_client_dev_unregister(sdev, "hda-probes", 0);
+}
+
+MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT);
diff --git a/sound/soc/sof/intel/hda-stream.c b/sound/soc/sof/intel/hda-stream.c
index 1bda14c3590c..b387b1a69d7e 100644
--- a/sound/soc/sof/intel/hda-stream.c
+++ b/sound/soc/sof/intel/hda-stream.c
@@ -15,20 +15,50 @@
* Hardware interface for generic Intel audio DSP HDA IP
*/
-#include <linux/pm_runtime.h>
#include <sound/hdaudio_ext.h>
#include <sound/hda_register.h>
#include <sound/sof.h>
+#include <trace/events/sof_intel.h>
#include "../ops.h"
#include "../sof-audio.h"
+#include "../ipc4-priv.h"
#include "hda.h"
+#define HDA_LTRP_GB_VALUE_US 95
+
+static inline const char *hda_hstream_direction_str(struct hdac_stream *hstream)
+{
+ if (hstream->direction == SNDRV_PCM_STREAM_PLAYBACK)
+ return "Playback";
+ else
+ return "Capture";
+}
+
+static char *hda_hstream_dbg_get_stream_info_str(struct hdac_stream *hstream)
+{
+ struct snd_soc_pcm_runtime *rtd;
+
+ if (hstream->substream)
+ rtd = snd_soc_substream_to_rtd(hstream->substream);
+ else if (hstream->cstream)
+ rtd = hstream->cstream->private_data;
+ else
+ /* Non audio DMA user, like dma-trace */
+ return kasprintf(GFP_KERNEL, "-- (%s, stream_tag: %u)",
+ hda_hstream_direction_str(hstream),
+ hstream->stream_tag);
+
+ return kasprintf(GFP_KERNEL, "dai_link \"%s\" (%s, stream_tag: %u)",
+ rtd->dai_link->name, hda_hstream_direction_str(hstream),
+ hstream->stream_tag);
+}
+
/*
* set up one of BDL entries for a stream
*/
static int hda_setup_bdle(struct snd_sof_dev *sdev,
struct snd_dma_buffer *dmab,
- struct hdac_stream *stream,
+ struct hdac_stream *hstream,
struct sof_intel_dsp_bdl **bdlp,
int offset, int size, int ioc)
{
@@ -39,7 +69,7 @@ static int hda_setup_bdle(struct snd_sof_dev *sdev,
dma_addr_t addr;
int chunk;
- if (stream->frags >= HDA_DSP_MAX_BDL_ENTRIES) {
+ if (hstream->frags >= HDA_DSP_MAX_BDL_ENTRIES) {
dev_err(sdev->dev, "error: stream frags exceeded\n");
return -EINVAL;
}
@@ -62,11 +92,8 @@ static int hda_setup_bdle(struct snd_sof_dev *sdev,
size -= chunk;
bdl->ioc = (size || !ioc) ? 0 : cpu_to_le32(0x01);
bdl++;
- stream->frags++;
+ hstream->frags++;
offset += chunk;
-
- dev_vdbg(sdev->dev, "bdl, frags:%d, chunk size:0x%x;\n",
- stream->frags, chunk);
}
*bdlp = bdl;
@@ -79,47 +106,47 @@ static int hda_setup_bdle(struct snd_sof_dev *sdev,
*/
int hda_dsp_stream_setup_bdl(struct snd_sof_dev *sdev,
struct snd_dma_buffer *dmab,
- struct hdac_stream *stream)
+ struct hdac_stream *hstream)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
struct sof_intel_dsp_bdl *bdl;
int i, offset, period_bytes, periods;
int remain, ioc;
- period_bytes = stream->period_bytes;
+ period_bytes = hstream->period_bytes;
dev_dbg(sdev->dev, "period_bytes:0x%x\n", period_bytes);
if (!period_bytes)
- period_bytes = stream->bufsize;
+ period_bytes = hstream->bufsize;
- periods = stream->bufsize / period_bytes;
+ periods = hstream->bufsize / period_bytes;
dev_dbg(sdev->dev, "periods:%d\n", periods);
- remain = stream->bufsize % period_bytes;
+ remain = hstream->bufsize % period_bytes;
if (remain)
periods++;
/* program the initial BDL entries */
- bdl = (struct sof_intel_dsp_bdl *)stream->bdl.area;
+ bdl = (struct sof_intel_dsp_bdl *)hstream->bdl.area;
offset = 0;
- stream->frags = 0;
+ hstream->frags = 0;
/*
* set IOC if don't use position IPC
* and period_wakeup needed.
*/
ioc = hda->no_ipc_position ?
- !stream->no_period_wakeup : 0;
+ !hstream->no_period_wakeup : 0;
for (i = 0; i < periods; i++) {
if (i == (periods - 1) && remain)
/* set the last small entry */
offset = hda_setup_bdle(sdev, dmab,
- stream, &bdl, offset,
+ hstream, &bdl, offset,
remain, 0);
else
offset = hda_setup_bdle(sdev, dmab,
- stream, &bdl, offset,
+ hstream, &bdl, offset,
period_bytes, ioc);
}
@@ -127,10 +154,10 @@ int hda_dsp_stream_setup_bdl(struct snd_sof_dev *sdev,
}
int hda_dsp_stream_spib_config(struct snd_sof_dev *sdev,
- struct hdac_ext_stream *stream,
+ struct hdac_ext_stream *hext_stream,
int enable, u32 size)
{
- struct hdac_stream *hstream = &stream->hstream;
+ struct hdac_stream *hstream = &hext_stream->hstream;
u32 mask;
if (!sdev->bar[HDA_DSP_SPIB_BAR]) {
@@ -146,18 +173,20 @@ int hda_dsp_stream_spib_config(struct snd_sof_dev *sdev,
enable << hstream->index);
/* set the SPIB value */
- sof_io_write(sdev, stream->spib_addr, size);
+ sof_io_write(sdev, hstream->spib_addr, size);
return 0;
}
/* get next unused stream */
struct hdac_ext_stream *
-hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction)
+hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction, u32 flags)
{
+ const struct sof_intel_dsp_desc *chip_info = get_chip_info(sdev->pdata);
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
struct hdac_bus *bus = sof_to_bus(sdev);
struct sof_intel_hda_stream *hda_stream;
- struct hdac_ext_stream *stream = NULL;
+ struct hdac_ext_stream *hext_stream = NULL;
struct hdac_stream *s;
spin_lock_irq(&bus->reg_lock);
@@ -165,10 +194,10 @@ hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction)
/* get an unused stream */
list_for_each_entry(s, &bus->stream_list, list) {
if (s->direction == direction && !s->opened) {
- stream = stream_to_hdac_ext_stream(s);
- hda_stream = container_of(stream,
+ hext_stream = stream_to_hdac_ext_stream(s);
+ hda_stream = container_of(hext_stream,
struct sof_intel_hda_stream,
- hda_stream);
+ hext_stream);
/* check if the host DMA channel is reserved */
if (hda_stream->host_reserved)
continue;
@@ -181,83 +210,140 @@ hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction)
spin_unlock_irq(&bus->reg_lock);
/* stream found ? */
- if (!stream)
+ if (!hext_stream) {
dev_err(sdev->dev, "error: no free %s streams\n",
direction == SNDRV_PCM_STREAM_PLAYBACK ?
"playback" : "capture");
+ return hext_stream;
+ }
+
+ hda_stream->flags = flags;
/*
- * Disable DMI Link L1 entry when capture stream is opened.
+ * Prevent DMI Link L1 entry for streams that don't support it.
* Workaround to address a known issue with host DMA that results
- * in xruns during pause/release in capture scenarios.
+ * in xruns during pause/release in capture scenarios. This is not needed for the ACE IP.
*/
- if (!IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_ALWAYS_ENABLE_DMI_L1))
- if (stream && direction == SNDRV_PCM_STREAM_CAPTURE)
- snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
- HDA_VS_INTEL_EM2,
- HDA_VS_INTEL_EM2_L1SEN, 0);
+ if (chip_info->hw_ip_version < SOF_INTEL_ACE_1_0 &&
+ !(flags & SOF_HDA_STREAM_DMI_L1_COMPATIBLE)) {
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
+ HDA_VS_INTEL_EM2,
+ HDA_VS_INTEL_EM2_L1SEN, 0);
+ hda->l1_disabled = true;
+ }
- return stream;
+ return hext_stream;
}
/* free a stream */
int hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stream_tag)
{
+ const struct sof_intel_dsp_desc *chip_info = get_chip_info(sdev->pdata);
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
struct hdac_bus *bus = sof_to_bus(sdev);
+ struct sof_intel_hda_stream *hda_stream;
+ struct hdac_ext_stream *hext_stream;
struct hdac_stream *s;
- bool active_capture_stream = false;
+ bool dmi_l1_enable = true;
bool found = false;
spin_lock_irq(&bus->reg_lock);
/*
- * close stream matching the stream tag
- * and check if there are any open capture streams.
+ * close stream matching the stream tag and check if there are any open streams
+ * that are DMI L1 incompatible.
*/
list_for_each_entry(s, &bus->stream_list, list) {
+ hext_stream = stream_to_hdac_ext_stream(s);
+ hda_stream = container_of(hext_stream, struct sof_intel_hda_stream, hext_stream);
+
if (!s->opened)
continue;
if (s->direction == direction && s->stream_tag == stream_tag) {
s->opened = false;
found = true;
- } else if (s->direction == SNDRV_PCM_STREAM_CAPTURE) {
- active_capture_stream = true;
+ } else if (!(hda_stream->flags & SOF_HDA_STREAM_DMI_L1_COMPATIBLE)) {
+ dmi_l1_enable = false;
}
}
spin_unlock_irq(&bus->reg_lock);
- /* Enable DMI L1 entry if there are no capture streams open */
- if (!IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_ALWAYS_ENABLE_DMI_L1))
- if (!active_capture_stream)
- snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
- HDA_VS_INTEL_EM2,
- HDA_VS_INTEL_EM2_L1SEN,
- HDA_VS_INTEL_EM2_L1SEN);
+ /* Enable DMI L1 if permitted */
+ if (chip_info->hw_ip_version < SOF_INTEL_ACE_1_0 && dmi_l1_enable) {
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_EM2,
+ HDA_VS_INTEL_EM2_L1SEN, HDA_VS_INTEL_EM2_L1SEN);
+ hda->l1_disabled = false;
+ }
if (!found) {
- dev_dbg(sdev->dev, "stream_tag %d not opened!\n", stream_tag);
+ dev_err(sdev->dev, "%s: stream_tag %d not opened!\n",
+ __func__, stream_tag);
return -ENODEV;
}
return 0;
}
+static int hda_dsp_stream_reset(struct snd_sof_dev *sdev, struct hdac_stream *hstream)
+{
+ int sd_offset = SOF_STREAM_SD_OFFSET(hstream);
+ int timeout = HDA_DSP_STREAM_RESET_TIMEOUT;
+ u32 val;
+
+ /* enter stream reset */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, SOF_STREAM_SD_OFFSET_CRST,
+ SOF_STREAM_SD_OFFSET_CRST);
+ do {
+ val = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, sd_offset);
+ if (val & SOF_STREAM_SD_OFFSET_CRST)
+ break;
+ } while (--timeout);
+ if (timeout == 0) {
+ dev_err(sdev->dev, "timeout waiting for stream reset\n");
+ return -ETIMEDOUT;
+ }
+
+ timeout = HDA_DSP_STREAM_RESET_TIMEOUT;
+
+ /* exit stream reset and wait to read a zero before reading any other register */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, SOF_STREAM_SD_OFFSET_CRST, 0x0);
+
+ /* wait for hardware to report that stream is out of reset */
+ udelay(3);
+ do {
+ val = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, sd_offset);
+ if ((val & SOF_STREAM_SD_OFFSET_CRST) == 0)
+ break;
+ } while (--timeout);
+ if (timeout == 0) {
+ dev_err(sdev->dev, "timeout waiting for stream to exit reset\n");
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
int hda_dsp_stream_trigger(struct snd_sof_dev *sdev,
- struct hdac_ext_stream *stream, int cmd)
+ struct hdac_ext_stream *hext_stream, int cmd)
{
- struct hdac_stream *hstream = &stream->hstream;
+ struct hdac_stream *hstream = &hext_stream->hstream;
int sd_offset = SOF_STREAM_SD_OFFSET(hstream);
u32 dma_start = SOF_HDA_SD_CTL_DMA_START;
- int ret;
+ int ret = 0;
u32 run;
/* cmd must be for audio stream */
switch (cmd) {
- case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (!sdev->dspless_mode_selected)
+ break;
+ fallthrough;
case SNDRV_PCM_TRIGGER_START:
+ if (hstream->running)
+ break;
+
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL,
1 << hstream->index,
1 << hstream->index);
@@ -276,17 +362,15 @@ int hda_dsp_stream_trigger(struct snd_sof_dev *sdev,
HDA_DSP_REG_POLL_INTERVAL_US,
HDA_DSP_STREAM_RUN_TIMEOUT);
- if (ret < 0) {
- dev_err(sdev->dev,
- "error: %s: cmd %d: timeout on STREAM_SD_OFFSET read\n",
- __func__, cmd);
- return ret;
- }
+ if (ret >= 0)
+ hstream->running = true;
- hstream->running = true;
break;
- case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (!sdev->dspless_mode_selected)
+ break;
+ fallthrough;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_STOP:
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
sd_offset,
@@ -299,26 +383,103 @@ int hda_dsp_stream_trigger(struct snd_sof_dev *sdev,
HDA_DSP_REG_POLL_INTERVAL_US,
HDA_DSP_STREAM_RUN_TIMEOUT);
- if (ret < 0) {
- dev_err(sdev->dev,
- "error: %s: cmd %d: timeout on STREAM_SD_OFFSET read\n",
- __func__, cmd);
- return ret;
- }
-
- snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, sd_offset +
- SOF_HDA_ADSP_REG_CL_SD_STS,
- SOF_HDA_CL_DMA_SD_INT_MASK);
+ if (ret >= 0) {
+ snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_SD_STS,
+ SOF_HDA_CL_DMA_SD_INT_MASK);
- hstream->running = false;
- snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL,
- 1 << hstream->index, 0x0);
+ hstream->running = false;
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
+ SOF_HDA_INTCTL,
+ 1 << hstream->index, 0x0);
+ }
break;
default:
dev_err(sdev->dev, "error: unknown command: %d\n", cmd);
return -EINVAL;
}
+ if (ret < 0) {
+ char *stream_name = hda_hstream_dbg_get_stream_info_str(hstream);
+
+ dev_err(sdev->dev,
+ "%s: cmd %d on %s: timeout on STREAM_SD_OFFSET read\n",
+ __func__, cmd, stream_name ? stream_name : "unknown stream");
+ kfree(stream_name);
+ }
+
+ return ret;
+}
+
+/* minimal recommended programming for ICCMAX stream */
+int hda_dsp_iccmax_stream_hw_params(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream,
+ struct snd_dma_buffer *dmab,
+ struct snd_pcm_hw_params *params)
+{
+ struct hdac_stream *hstream = &hext_stream->hstream;
+ int sd_offset = SOF_STREAM_SD_OFFSET(hstream);
+ int ret;
+ u32 mask = 0x1 << hstream->index;
+
+ if (!hext_stream) {
+ dev_err(sdev->dev, "error: no stream available\n");
+ return -ENODEV;
+ }
+
+ if (!dmab) {
+ dev_err(sdev->dev, "error: no dma buffer allocated!\n");
+ return -ENODEV;
+ }
+
+ if (hstream->posbuf)
+ *hstream->posbuf = 0;
+
+ /* reset BDL address */
+ snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_SD_BDLPL,
+ 0x0);
+ snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_SD_BDLPU,
+ 0x0);
+
+ hstream->frags = 0;
+
+ ret = hda_dsp_stream_setup_bdl(sdev, dmab, hstream);
+ if (ret < 0) {
+ dev_err(sdev->dev, "error: set up of BDL failed\n");
+ return ret;
+ }
+
+ /* program BDL address */
+ snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_SD_BDLPL,
+ (u32)hstream->bdl.addr);
+ snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_SD_BDLPU,
+ upper_32_bits(hstream->bdl.addr));
+
+ /* program cyclic buffer length */
+ snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_SD_CBL,
+ hstream->bufsize);
+
+ /* program last valid index */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_SD_LVI,
+ 0xffff, (hstream->frags - 1));
+
+ /* decouple host and link DMA, enable DSP features */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL,
+ mask, mask);
+
+ /* Follow HW recommendation to set the guardband value to 95us during FW boot */
+ snd_sof_dsp_update8(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_LTRP,
+ HDA_VS_INTEL_LTRP_GB_MASK, HDA_LTRP_GB_VALUE_US);
+
+ /* start DMA */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset,
+ SOF_HDA_SD_CTL_DMA_START, SOF_HDA_SD_CTL_DMA_START);
+
return 0;
}
@@ -327,33 +488,37 @@ int hda_dsp_stream_trigger(struct snd_sof_dev *sdev,
* and normal stream.
*/
int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev,
- struct hdac_ext_stream *stream,
+ struct hdac_ext_stream *hext_stream,
struct snd_dma_buffer *dmab,
struct snd_pcm_hw_params *params)
{
+ const struct sof_intel_dsp_desc *chip = get_chip_info(sdev->pdata);
struct hdac_bus *bus = sof_to_bus(sdev);
- struct hdac_stream *hstream = &stream->hstream;
- int sd_offset = SOF_STREAM_SD_OFFSET(hstream);
- int ret, timeout = HDA_DSP_STREAM_RESET_TIMEOUT;
+ struct hdac_stream *hstream;
+ int sd_offset, ret;
u32 dma_start = SOF_HDA_SD_CTL_DMA_START;
- u32 val, mask;
+ u32 mask;
u32 run;
- if (!stream) {
+ if (!hext_stream) {
dev_err(sdev->dev, "error: no stream available\n");
return -ENODEV;
}
- /* decouple host and link DMA */
- mask = 0x1 << hstream->index;
- snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL,
- mask, mask);
-
if (!dmab) {
dev_err(sdev->dev, "error: no dma buffer allocated!\n");
return -ENODEV;
}
+ hstream = &hext_stream->hstream;
+ sd_offset = SOF_STREAM_SD_OFFSET(hstream);
+ mask = BIT(hstream->index);
+
+ /* decouple host and link DMA if the DSP is used */
+ if (!sdev->dspless_mode_selected)
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL,
+ mask, mask);
+
/* clear stream status */
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset,
SOF_HDA_CL_DMA_SD_INT_MASK |
@@ -366,58 +531,34 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev,
HDA_DSP_STREAM_RUN_TIMEOUT);
if (ret < 0) {
+ char *stream_name = hda_hstream_dbg_get_stream_info_str(hstream);
+
dev_err(sdev->dev,
- "error: %s: timeout on STREAM_SD_OFFSET read1\n",
- __func__);
+ "%s: on %s: timeout on STREAM_SD_OFFSET read1\n",
+ __func__, stream_name ? stream_name : "unknown stream");
+ kfree(stream_name);
return ret;
}
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
- sd_offset + SOF_HDA_ADSP_REG_CL_SD_STS,
+ sd_offset + SOF_HDA_ADSP_REG_SD_STS,
SOF_HDA_CL_DMA_SD_INT_MASK,
SOF_HDA_CL_DMA_SD_INT_MASK);
/* stream reset */
- snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, 0x1,
- 0x1);
- udelay(3);
- do {
- val = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR,
- sd_offset);
- if (val & 0x1)
- break;
- } while (--timeout);
- if (timeout == 0) {
- dev_err(sdev->dev, "error: stream reset failed\n");
- return -ETIMEDOUT;
- }
-
- timeout = HDA_DSP_STREAM_RESET_TIMEOUT;
- snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, 0x1,
- 0x0);
-
- /* wait for hardware to report that stream is out of reset */
- udelay(3);
- do {
- val = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR,
- sd_offset);
- if ((val & 0x1) == 0)
- break;
- } while (--timeout);
- if (timeout == 0) {
- dev_err(sdev->dev, "error: timeout waiting for stream reset\n");
- return -ETIMEDOUT;
- }
+ ret = hda_dsp_stream_reset(sdev, hstream);
+ if (ret < 0)
+ return ret;
if (hstream->posbuf)
*hstream->posbuf = 0;
/* reset BDL address */
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
- sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPL,
+ sd_offset + SOF_HDA_ADSP_REG_SD_BDLPL,
0x0);
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
- sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPU,
+ sd_offset + SOF_HDA_ADSP_REG_SD_BDLPU,
0x0);
/* clear stream status */
@@ -432,14 +573,17 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev,
HDA_DSP_STREAM_RUN_TIMEOUT);
if (ret < 0) {
+ char *stream_name = hda_hstream_dbg_get_stream_info_str(hstream);
+
dev_err(sdev->dev,
- "error: %s: timeout on STREAM_SD_OFFSET read2\n",
- __func__);
+ "%s: on %s: timeout on STREAM_SD_OFFSET read1\n",
+ __func__, stream_name ? stream_name : "unknown stream");
+ kfree(stream_name);
return ret;
}
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
- sd_offset + SOF_HDA_ADSP_REG_CL_SD_STS,
+ sd_offset + SOF_HDA_ADSP_REG_SD_STS,
SOF_HDA_CL_DMA_SD_INT_MASK,
SOF_HDA_CL_DMA_SD_INT_MASK);
@@ -459,11 +603,12 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev,
/* program cyclic buffer length */
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
- sd_offset + SOF_HDA_ADSP_REG_CL_SD_CBL,
+ sd_offset + SOF_HDA_ADSP_REG_SD_CBL,
hstream->bufsize);
/*
* Recommended hardware programming sequence for HDAudio DMA format
+ * on earlier platforms - this is not needed on newer platforms
*
* 1. Put DMA into coupled mode by clearing PPCTL.PROCEN bit
* for corresponding stream index before the time of writing
@@ -473,36 +618,39 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev,
* enable decoupled mode
*/
- /* couple host and link DMA, disable DSP features */
- snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL,
- mask, 0);
+ if (!sdev->dspless_mode_selected && (chip->quirks & SOF_INTEL_PROCEN_FMT_QUIRK))
+ /* couple host and link DMA, disable DSP features */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL,
+ mask, 0);
/* program stream format */
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
sd_offset +
- SOF_HDA_ADSP_REG_CL_SD_FORMAT,
+ SOF_HDA_ADSP_REG_SD_FORMAT,
0xffff, hstream->format_val);
- /* decouple host and link DMA, enable DSP features */
- snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL,
- mask, mask);
+ if (!sdev->dspless_mode_selected && (chip->quirks & SOF_INTEL_PROCEN_FMT_QUIRK))
+ /* decouple host and link DMA, enable DSP features */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL,
+ mask, mask);
/* program last valid index */
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
- sd_offset + SOF_HDA_ADSP_REG_CL_SD_LVI,
+ sd_offset + SOF_HDA_ADSP_REG_SD_LVI,
0xffff, (hstream->frags - 1));
/* program BDL address */
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
- sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPL,
+ sd_offset + SOF_HDA_ADSP_REG_SD_BDLPL,
(u32)hstream->bdl.addr);
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
- sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPU,
+ sd_offset + SOF_HDA_ADSP_REG_SD_BDLPU,
upper_32_bits(hstream->bdl.addr));
- /* enable position buffer */
- if (!(snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_ADSP_DPLBASE)
- & SOF_HDA_ADSP_DPLBASE_ENABLE)) {
+ /* enable position buffer, if needed */
+ if (bus->use_posbuf && bus->posbuf.addr &&
+ !(snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_ADSP_DPLBASE)
+ & SOF_HDA_ADSP_DPLBASE_ENABLE)) {
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_ADSP_DPUBASE,
upper_32_bits(bus->posbuf.addr));
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_ADSP_DPLBASE,
@@ -520,8 +668,8 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev,
hstream->fifo_size =
snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR,
sd_offset +
- SOF_HDA_ADSP_REG_CL_SD_FIFOSIZE);
- hstream->fifo_size &= 0xffff;
+ SOF_HDA_ADSP_REG_SD_FIFOSIZE);
+ hstream->fifo_size &= SOF_HDA_SD_FIFOSIZE_FIFOS_MASK;
hstream->fifo_size += 1;
} else {
hstream->fifo_size = 0;
@@ -533,21 +681,31 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev,
int hda_dsp_stream_hw_free(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream)
{
- struct hdac_stream *stream = substream->runtime->private_data;
- struct hdac_ext_stream *link_dev = container_of(stream,
- struct hdac_ext_stream,
- hstream);
- struct hdac_bus *bus = sof_to_bus(sdev);
- u32 mask = 0x1 << stream->index;
+ struct hdac_stream *hstream = substream->runtime->private_data;
+ struct hdac_ext_stream *hext_stream = container_of(hstream,
+ struct hdac_ext_stream,
+ hstream);
+ int ret;
- spin_lock_irq(&bus->reg_lock);
- /* couple host and link DMA if link DMA channel is idle */
- if (!link_dev->link_locked)
- snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR,
- SOF_HDA_REG_PP_PPCTL, mask, 0);
- spin_unlock_irq(&bus->reg_lock);
+ ret = hda_dsp_stream_reset(sdev, hstream);
+ if (ret < 0)
+ return ret;
+
+ if (!sdev->dspless_mode_selected) {
+ struct hdac_bus *bus = sof_to_bus(sdev);
+ u32 mask = BIT(hstream->index);
+
+ spin_lock_irq(&bus->reg_lock);
+ /* couple host and link DMA if link DMA channel is idle */
+ if (!hext_stream->link_locked)
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR,
+ SOF_HDA_REG_PP_PPCTL, mask, 0);
+ spin_unlock_irq(&bus->reg_lock);
+ }
- stream->substream = NULL;
+ hda_dsp_stream_spib_config(sdev, hext_stream, HDA_DSP_SPIB_DISABLE, 0);
+
+ hstream->substream = NULL;
return 0;
}
@@ -561,8 +719,9 @@ bool hda_dsp_check_stream_irq(struct snd_sof_dev *sdev)
/* The function can be called at irq thread, so use spin_lock_irq */
spin_lock_irq(&bus->reg_lock);
- status = snd_hdac_chip_readl(bus, INTSTS);
- dev_vdbg(bus->dev, "stream irq, INTSTS status: 0x%x\n", status);
+ status = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTSTS);
+
+ trace_sof_intel_hda_dsp_check_stream_irq(sdev, status);
/* if Register inaccessible, ignore it.*/
if (status != 0xffffffff)
@@ -574,12 +733,13 @@ bool hda_dsp_check_stream_irq(struct snd_sof_dev *sdev)
}
static void
-hda_dsp_set_bytes_transferred(struct hdac_stream *hstream, u64 buffer_size)
+hda_dsp_compr_bytes_transferred(struct hdac_stream *hstream, int direction)
{
+ u64 buffer_size = hstream->bufsize;
u64 prev_pos, pos, num_bytes;
div64_u64_rem(hstream->curr_pos, buffer_size, &prev_pos);
- pos = snd_hdac_stream_get_pos_posbuf(hstream);
+ pos = hda_dsp_stream_get_position(hstream, direction, false);
if (pos < prev_pos)
num_bytes = (buffer_size - prev_pos) + pos;
@@ -598,12 +758,11 @@ static bool hda_dsp_stream_check(struct hdac_bus *bus, u32 status)
list_for_each_entry(s, &bus->stream_list, list) {
if (status & BIT(s->index) && s->opened) {
- sd_status = snd_hdac_stream_readb(s, SD_STS);
+ sd_status = readb(s->sd_addr + SOF_HDA_ADSP_REG_SD_STS);
- dev_vdbg(bus->dev, "stream %d status 0x%x\n",
- s->index, sd_status);
+ trace_sof_intel_hda_dsp_stream_status(bus->dev, s, sd_status);
- snd_hdac_stream_writeb(s, SD_STS, sd_status);
+ writeb(sd_status, s->sd_addr + SOF_HDA_ADSP_REG_SD_STS);
active = true;
if ((!s->substream && !s->cstream) ||
@@ -615,8 +774,7 @@ static bool hda_dsp_stream_check(struct hdac_bus *bus, u32 status)
if (s->substream && sof_hda->no_ipc_position) {
snd_sof_pcm_period_elapsed(s->substream);
} else if (s->cstream) {
- hda_dsp_set_bytes_transferred(s,
- s->cstream->runtime->buffer_size);
+ hda_dsp_compr_bytes_transferred(s, s->cstream->direction);
snd_compr_fragment_elapsed(s->cstream);
}
}
@@ -629,9 +787,6 @@ irqreturn_t hda_dsp_stream_threaded_handler(int irq, void *context)
{
struct snd_sof_dev *sdev = context;
struct hdac_bus *bus = sof_to_bus(sdev);
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
- u32 rirb_status;
-#endif
bool active;
u32 status;
int i;
@@ -643,29 +798,15 @@ irqreturn_t hda_dsp_stream_threaded_handler(int irq, void *context)
for (i = 0, active = true; i < 10 && active; i++) {
spin_lock_irq(&bus->reg_lock);
- status = snd_hdac_chip_readl(bus, INTSTS);
+ status = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTSTS);
/* check streams */
active = hda_dsp_stream_check(bus, status);
/* check and clear RIRB interrupt */
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
if (status & AZX_INT_CTRL_EN) {
- rirb_status = snd_hdac_chip_readb(bus, RIRBSTS);
- if (rirb_status & RIRB_INT_MASK) {
- /*
- * Clearing the interrupt status here ensures
- * that no interrupt gets masked after the RIRB
- * wp is read in snd_hdac_bus_update_rirb.
- */
- snd_hdac_chip_writeb(bus, RIRBSTS,
- RIRB_INT_MASK);
- active = true;
- if (rirb_status & RIRB_INT_RESPONSE)
- snd_hdac_bus_update_rirb(bus);
- }
+ active |= hda_codec_check_rirb_status(sdev);
}
-#endif
spin_unlock_irq(&bus->reg_lock);
}
@@ -675,7 +816,7 @@ irqreturn_t hda_dsp_stream_threaded_handler(int irq, void *context)
int hda_dsp_stream_init(struct snd_sof_dev *sdev)
{
struct hdac_bus *bus = sof_to_bus(sdev);
- struct hdac_ext_stream *stream;
+ struct hdac_ext_stream *hext_stream;
struct hdac_stream *hstream;
struct pci_dev *pci = to_pci_dev(sdev->dev);
struct sof_intel_hda_dev *sof_hda = bus_to_sof_hda(bus);
@@ -718,18 +859,19 @@ int hda_dsp_stream_init(struct snd_sof_dev *sdev)
return -ENOMEM;
}
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
- /* mem alloc for the CORB/RIRB ringbuffers */
+ /*
+ * mem alloc for the CORB/RIRB ringbuffers - this will be used only for
+ * HDAudio codecs
+ */
ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev,
PAGE_SIZE, &bus->rb);
if (ret < 0) {
dev_err(sdev->dev, "error: RB alloc failed\n");
return -ENOMEM;
}
-#endif
- /* create capture streams */
- for (i = 0; i < num_capture; i++) {
+ /* create capture and playback streams */
+ for (i = 0; i < num_total; i++) {
struct sof_intel_hda_stream *hda_stream;
hda_stream = devm_kzalloc(sdev->dev, sizeof(*hda_stream),
@@ -739,92 +881,45 @@ int hda_dsp_stream_init(struct snd_sof_dev *sdev)
hda_stream->sdev = sdev;
- stream = &hda_stream->hda_stream;
-
- stream->pphc_addr = sdev->bar[HDA_DSP_PP_BAR] +
- SOF_HDA_PPHC_BASE + SOF_HDA_PPHC_INTERVAL * i;
+ hext_stream = &hda_stream->hext_stream;
- stream->pplc_addr = sdev->bar[HDA_DSP_PP_BAR] +
- SOF_HDA_PPLC_BASE + SOF_HDA_PPLC_MULTI * num_total +
- SOF_HDA_PPLC_INTERVAL * i;
+ if (sdev->bar[HDA_DSP_PP_BAR]) {
+ hext_stream->pphc_addr = sdev->bar[HDA_DSP_PP_BAR] +
+ SOF_HDA_PPHC_BASE + SOF_HDA_PPHC_INTERVAL * i;
- /* do we support SPIB */
- if (sdev->bar[HDA_DSP_SPIB_BAR]) {
- stream->spib_addr = sdev->bar[HDA_DSP_SPIB_BAR] +
- SOF_HDA_SPIB_BASE + SOF_HDA_SPIB_INTERVAL * i +
- SOF_HDA_SPIB_SPIB;
-
- stream->fifo_addr = sdev->bar[HDA_DSP_SPIB_BAR] +
- SOF_HDA_SPIB_BASE + SOF_HDA_SPIB_INTERVAL * i +
- SOF_HDA_SPIB_MAXFIFO;
+ hext_stream->pplc_addr = sdev->bar[HDA_DSP_PP_BAR] +
+ SOF_HDA_PPLC_BASE + SOF_HDA_PPLC_MULTI * num_total +
+ SOF_HDA_PPLC_INTERVAL * i;
}
- hstream = &stream->hstream;
- hstream->bus = bus;
- hstream->sd_int_sta_mask = 1 << i;
- hstream->index = i;
- sd_offset = SOF_STREAM_SD_OFFSET(hstream);
- hstream->sd_addr = sdev->bar[HDA_DSP_HDA_BAR] + sd_offset;
- hstream->stream_tag = i + 1;
- hstream->opened = false;
- hstream->running = false;
- hstream->direction = SNDRV_PCM_STREAM_CAPTURE;
-
- /* memory alloc for stream BDL */
- ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev,
- HDA_DSP_BDL_SIZE, &hstream->bdl);
- if (ret < 0) {
- dev_err(sdev->dev, "error: stream bdl dma alloc failed\n");
- return -ENOMEM;
- }
- hstream->posbuf = (__le32 *)(bus->posbuf.area +
- (hstream->index) * 8);
-
- list_add_tail(&hstream->list, &bus->stream_list);
- }
-
- /* create playback streams */
- for (i = num_capture; i < num_total; i++) {
- struct sof_intel_hda_stream *hda_stream;
-
- hda_stream = devm_kzalloc(sdev->dev, sizeof(*hda_stream),
- GFP_KERNEL);
- if (!hda_stream)
- return -ENOMEM;
-
- hda_stream->sdev = sdev;
-
- stream = &hda_stream->hda_stream;
-
- /* we always have DSP support */
- stream->pphc_addr = sdev->bar[HDA_DSP_PP_BAR] +
- SOF_HDA_PPHC_BASE + SOF_HDA_PPHC_INTERVAL * i;
-
- stream->pplc_addr = sdev->bar[HDA_DSP_PP_BAR] +
- SOF_HDA_PPLC_BASE + SOF_HDA_PPLC_MULTI * num_total +
- SOF_HDA_PPLC_INTERVAL * i;
+ hstream = &hext_stream->hstream;
/* do we support SPIB */
if (sdev->bar[HDA_DSP_SPIB_BAR]) {
- stream->spib_addr = sdev->bar[HDA_DSP_SPIB_BAR] +
+ hstream->spib_addr = sdev->bar[HDA_DSP_SPIB_BAR] +
SOF_HDA_SPIB_BASE + SOF_HDA_SPIB_INTERVAL * i +
SOF_HDA_SPIB_SPIB;
- stream->fifo_addr = sdev->bar[HDA_DSP_SPIB_BAR] +
+ hstream->fifo_addr = sdev->bar[HDA_DSP_SPIB_BAR] +
SOF_HDA_SPIB_BASE + SOF_HDA_SPIB_INTERVAL * i +
SOF_HDA_SPIB_MAXFIFO;
}
- hstream = &stream->hstream;
hstream->bus = bus;
hstream->sd_int_sta_mask = 1 << i;
hstream->index = i;
sd_offset = SOF_STREAM_SD_OFFSET(hstream);
hstream->sd_addr = sdev->bar[HDA_DSP_HDA_BAR] + sd_offset;
- hstream->stream_tag = i - num_capture + 1;
hstream->opened = false;
hstream->running = false;
- hstream->direction = SNDRV_PCM_STREAM_PLAYBACK;
+
+ if (i < num_capture) {
+ hstream->stream_tag = i + 1;
+ hstream->direction = SNDRV_PCM_STREAM_CAPTURE;
+ } else {
+ hstream->stream_tag = i - num_capture + 1;
+ hstream->direction = SNDRV_PCM_STREAM_PLAYBACK;
+ }
/* mem alloc for stream BDL */
ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, &pci->dev,
@@ -843,6 +938,14 @@ int hda_dsp_stream_init(struct snd_sof_dev *sdev)
/* store total stream count (playback + capture) from GCAP */
sof_hda->stream_max = num_total;
+ /* store stream count from GCAP required for CHAIN_DMA */
+ if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4) {
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+
+ ipc4_data->num_playback_streams = num_playback;
+ ipc4_data->num_capture_streams = num_capture;
+ }
+
return 0;
}
@@ -850,18 +953,16 @@ void hda_dsp_stream_free(struct snd_sof_dev *sdev)
{
struct hdac_bus *bus = sof_to_bus(sdev);
struct hdac_stream *s, *_s;
- struct hdac_ext_stream *stream;
+ struct hdac_ext_stream *hext_stream;
struct sof_intel_hda_stream *hda_stream;
/* free position buffer */
if (bus->posbuf.area)
snd_dma_free_pages(&bus->posbuf);
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
- /* free position buffer */
+ /* free CORB/RIRB buffer - only used for HDaudio codecs */
if (bus->rb.area)
snd_dma_free_pages(&bus->rb);
-#endif
list_for_each_entry_safe(s, _s, &bus->stream_list, list) {
/* TODO: decouple */
@@ -870,9 +971,95 @@ void hda_dsp_stream_free(struct snd_sof_dev *sdev)
if (s->bdl.area)
snd_dma_free_pages(&s->bdl);
list_del(&s->list);
- stream = stream_to_hdac_ext_stream(s);
- hda_stream = container_of(stream, struct sof_intel_hda_stream,
- hda_stream);
+ hext_stream = stream_to_hdac_ext_stream(s);
+ hda_stream = container_of(hext_stream, struct sof_intel_hda_stream,
+ hext_stream);
devm_kfree(sdev->dev, hda_stream);
}
}
+
+snd_pcm_uframes_t hda_dsp_stream_get_position(struct hdac_stream *hstream,
+ int direction, bool can_sleep)
+{
+ struct hdac_ext_stream *hext_stream = stream_to_hdac_ext_stream(hstream);
+ struct sof_intel_hda_stream *hda_stream = hstream_to_sof_hda_stream(hext_stream);
+ struct snd_sof_dev *sdev = hda_stream->sdev;
+ snd_pcm_uframes_t pos;
+
+ switch (sof_hda_position_quirk) {
+ case SOF_HDA_POSITION_QUIRK_USE_SKYLAKE_LEGACY:
+ /*
+ * This legacy code, inherited from the Skylake driver,
+ * mixes DPIB registers and DPIB DDR updates and
+ * does not seem to follow any known hardware recommendations.
+ * It's not clear e.g. why there is a different flow
+ * for capture and playback, the only information that matters is
+ * what traffic class is used, and on all SOF-enabled platforms
+ * only VC0 is supported so the work-around was likely not necessary
+ * and quite possibly wrong.
+ */
+
+ /* DPIB/posbuf position mode:
+ * For Playback, Use DPIB register from HDA space which
+ * reflects the actual data transferred.
+ * For Capture, Use the position buffer for pointer, as DPIB
+ * is not accurate enough, its update may be completed
+ * earlier than the data written to DDR.
+ */
+ if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
+ pos = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR,
+ AZX_REG_VS_SDXDPIB_XBASE +
+ (AZX_REG_VS_SDXDPIB_XINTERVAL *
+ hstream->index));
+ } else {
+ /*
+ * For capture stream, we need more workaround to fix the
+ * position incorrect issue:
+ *
+ * 1. Wait at least 20us before reading position buffer after
+ * the interrupt generated(IOC), to make sure position update
+ * happens on frame boundary i.e. 20.833uSec for 48KHz.
+ * 2. Perform a dummy Read to DPIB register to flush DMA
+ * position value.
+ * 3. Read the DMA Position from posbuf. Now the readback
+ * value should be >= period boundary.
+ */
+ if (can_sleep)
+ usleep_range(20, 21);
+
+ snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR,
+ AZX_REG_VS_SDXDPIB_XBASE +
+ (AZX_REG_VS_SDXDPIB_XINTERVAL *
+ hstream->index));
+ pos = snd_hdac_stream_get_pos_posbuf(hstream);
+ }
+ break;
+ case SOF_HDA_POSITION_QUIRK_USE_DPIB_REGISTERS:
+ /*
+ * In case VC1 traffic is disabled this is the recommended option
+ */
+ pos = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR,
+ AZX_REG_VS_SDXDPIB_XBASE +
+ (AZX_REG_VS_SDXDPIB_XINTERVAL *
+ hstream->index));
+ break;
+ case SOF_HDA_POSITION_QUIRK_USE_DPIB_DDR_UPDATE:
+ /*
+ * This is the recommended option when VC1 is enabled.
+ * While this isn't needed for SOF platforms it's added for
+ * consistency and debug.
+ */
+ pos = snd_hdac_stream_get_pos_posbuf(hstream);
+ break;
+ default:
+ dev_err_once(sdev->dev, "hda_position_quirk value %d not supported\n",
+ sof_hda_position_quirk);
+ pos = 0;
+ break;
+ }
+
+ if (pos >= hstream->bufsize)
+ pos = 0;
+
+ return pos;
+}
diff --git a/sound/soc/sof/intel/hda-trace.c b/sound/soc/sof/intel/hda-trace.c
index 1eb746d5adeb..cbb9bd7770e6 100644
--- a/sound/soc/sof/intel/hda-trace.c
+++ b/sound/soc/sof/intel/hda-trace.c
@@ -19,31 +19,31 @@
#include "../ops.h"
#include "hda.h"
-static int hda_dsp_trace_prepare(struct snd_sof_dev *sdev)
+static int hda_dsp_trace_prepare(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
- struct hdac_ext_stream *stream = hda->dtrace_stream;
- struct hdac_stream *hstream = &stream->hstream;
- struct snd_dma_buffer *dmab = &sdev->dmatb;
+ struct hdac_ext_stream *hext_stream = hda->dtrace_stream;
+ struct hdac_stream *hstream = &hext_stream->hstream;
int ret;
hstream->period_bytes = 0;/* initialize period_bytes */
- hstream->bufsize = sdev->dmatb.bytes;
+ hstream->bufsize = dmab->bytes;
- ret = hda_dsp_stream_hw_params(sdev, stream, dmab, NULL);
+ ret = hda_dsp_stream_hw_params(sdev, hext_stream, dmab, NULL);
if (ret < 0)
- dev_err(sdev->dev, "error: hdac prepare failed: %x\n", ret);
+ dev_err(sdev->dev, "error: hdac prepare failed: %d\n", ret);
return ret;
}
-int hda_dsp_trace_init(struct snd_sof_dev *sdev, u32 *stream_tag)
+int hda_dsp_trace_init(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab,
+ struct sof_ipc_dma_trace_params_ext *dtrace_params)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
int ret;
- hda->dtrace_stream = hda_dsp_stream_get(sdev,
- SNDRV_PCM_STREAM_CAPTURE);
+ hda->dtrace_stream = hda_dsp_stream_get(sdev, SNDRV_PCM_STREAM_CAPTURE,
+ SOF_HDA_STREAM_DMI_L1_COMPATIBLE);
if (!hda->dtrace_stream) {
dev_err(sdev->dev,
@@ -51,18 +51,19 @@ int hda_dsp_trace_init(struct snd_sof_dev *sdev, u32 *stream_tag)
return -ENODEV;
}
- *stream_tag = hda->dtrace_stream->hstream.stream_tag;
+ dtrace_params->stream_tag = hda->dtrace_stream->hstream.stream_tag;
/*
* initialize capture stream, set BDL address and return corresponding
* stream tag which will be sent to the firmware by IPC message.
*/
- ret = hda_dsp_trace_prepare(sdev);
+ ret = hda_dsp_trace_prepare(sdev, dmab);
if (ret < 0) {
- dev_err(sdev->dev, "error: hdac trace init failed: %x\n", ret);
- hda_dsp_stream_put(sdev, SNDRV_PCM_STREAM_CAPTURE, *stream_tag);
+ dev_err(sdev->dev, "error: hdac trace init failed: %d\n", ret);
+ hda_dsp_stream_put(sdev, SNDRV_PCM_STREAM_CAPTURE,
+ dtrace_params->stream_tag);
hda->dtrace_stream = NULL;
- *stream_tag = 0;
+ dtrace_params->stream_tag = 0;
}
return ret;
diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c
index 63ca920c8e6e..7fe72b065451 100644
--- a/sound/soc/sof/intel/hda.c
+++ b/sound/soc/sof/intel/hda.c
@@ -22,12 +22,19 @@
#include <linux/module.h>
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_intel.h>
+#include <sound/intel-dsp-config.h>
#include <sound/intel-nhlt.h>
#include <sound/sof.h>
#include <sound/sof/xtensa.h>
+#include <sound/hda-mlink.h>
#include "../sof-audio.h"
+#include "../sof-pci-dev.h"
#include "../ops.h"
#include "hda.h"
+#include "telemetry.h"
+
+#define CREATE_TRACE_POINTS
+#include <trace/events/sof_intel.h>
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
#include <sound/soc-acpi-intel-match.h>
@@ -37,6 +44,84 @@
#include "shim.h"
#define EXCEPT_MAX_HDR_SIZE 0x400
+#define HDA_EXT_ROM_STATUS_SIZE 8
+
+static void hda_get_interfaces(struct snd_sof_dev *sdev, u32 *interface_mask)
+{
+ const struct sof_intel_dsp_desc *chip;
+
+ chip = get_chip_info(sdev->pdata);
+ switch (chip->hw_ip_version) {
+ case SOF_INTEL_TANGIER:
+ case SOF_INTEL_BAYTRAIL:
+ case SOF_INTEL_BROADWELL:
+ interface_mask[SOF_DAI_DSP_ACCESS] = BIT(SOF_DAI_INTEL_SSP);
+ break;
+ case SOF_INTEL_CAVS_1_5:
+ case SOF_INTEL_CAVS_1_5_PLUS:
+ interface_mask[SOF_DAI_DSP_ACCESS] =
+ BIT(SOF_DAI_INTEL_SSP) | BIT(SOF_DAI_INTEL_DMIC) | BIT(SOF_DAI_INTEL_HDA);
+ interface_mask[SOF_DAI_HOST_ACCESS] = BIT(SOF_DAI_INTEL_HDA);
+ break;
+ case SOF_INTEL_CAVS_1_8:
+ case SOF_INTEL_CAVS_2_0:
+ case SOF_INTEL_CAVS_2_5:
+ case SOF_INTEL_ACE_1_0:
+ interface_mask[SOF_DAI_DSP_ACCESS] =
+ BIT(SOF_DAI_INTEL_SSP) | BIT(SOF_DAI_INTEL_DMIC) |
+ BIT(SOF_DAI_INTEL_HDA) | BIT(SOF_DAI_INTEL_ALH);
+ interface_mask[SOF_DAI_HOST_ACCESS] = BIT(SOF_DAI_INTEL_HDA);
+ break;
+ case SOF_INTEL_ACE_2_0:
+ interface_mask[SOF_DAI_DSP_ACCESS] =
+ BIT(SOF_DAI_INTEL_SSP) | BIT(SOF_DAI_INTEL_DMIC) |
+ BIT(SOF_DAI_INTEL_HDA) | BIT(SOF_DAI_INTEL_ALH);
+ /* all interfaces accessible without DSP */
+ interface_mask[SOF_DAI_HOST_ACCESS] =
+ interface_mask[SOF_DAI_DSP_ACCESS];
+ break;
+ default:
+ break;
+ }
+}
+
+static u32 hda_get_interface_mask(struct snd_sof_dev *sdev)
+{
+ u32 interface_mask[SOF_DAI_ACCESS_NUM] = { 0 };
+
+ hda_get_interfaces(sdev, interface_mask);
+
+ return interface_mask[sdev->dspless_mode_selected];
+}
+
+bool hda_is_chain_dma_supported(struct snd_sof_dev *sdev, u32 dai_type)
+{
+ u32 interface_mask[SOF_DAI_ACCESS_NUM] = { 0 };
+ const struct sof_intel_dsp_desc *chip;
+
+ if (sdev->dspless_mode_selected)
+ return false;
+
+ hda_get_interfaces(sdev, interface_mask);
+
+ if (!(interface_mask[SOF_DAI_DSP_ACCESS] & BIT(dai_type)))
+ return false;
+
+ if (dai_type == SOF_DAI_INTEL_HDA)
+ return true;
+
+ switch (dai_type) {
+ case SOF_DAI_INTEL_SSP:
+ case SOF_DAI_INTEL_DMIC:
+ case SOF_DAI_INTEL_ALH:
+ chip = get_chip_info(sdev->pdata);
+ if (chip->hw_ip_version < SOF_INTEL_ACE_2_0)
+ return false;
+ return true;
+ default:
+ return false;
+ }
+}
#if IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE)
@@ -53,82 +138,85 @@ MODULE_PARM_DESC(sdw_clock_stop_quirks, "SOF SoundWire clock stop quirks");
static int sdw_params_stream(struct device *dev,
struct sdw_intel_stream_params_data *params_data)
{
- struct snd_sof_dev *sdev = dev_get_drvdata(dev);
struct snd_soc_dai *d = params_data->dai;
- struct sof_ipc_dai_config config;
- struct sof_ipc_reply reply;
- int link_id = params_data->link_id;
- int alh_stream_id = params_data->alh_stream_id;
- int ret;
- u32 size = sizeof(config);
-
- memset(&config, 0, size);
- config.hdr.size = size;
- config.hdr.cmd = SOF_IPC_GLB_DAI_MSG | SOF_IPC_DAI_CONFIG;
- config.type = SOF_DAI_INTEL_ALH;
- config.dai_index = (link_id << 8) | (d->id);
- config.alh.stream_id = alh_stream_id;
-
- /* send message to DSP */
- ret = sof_ipc_tx_message(sdev->ipc,
- config.hdr.cmd, &config, size, &reply,
- sizeof(reply));
- if (ret < 0) {
- dev_err(sdev->dev,
- "error: failed to set DAI hw_params for link %d dai->id %d ALH %d\n",
- link_id, d->id, alh_stream_id);
- }
+ struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(d, params_data->substream->stream);
+ struct snd_sof_dai_config_data data = { 0 };
- return ret;
+ data.dai_index = (params_data->link_id << 8) | d->id;
+ data.dai_data = params_data->alh_stream_id;
+
+ return hda_dai_config(w, SOF_DAI_CONFIG_FLAGS_HW_PARAMS, &data);
}
-static int sdw_free_stream(struct device *dev,
- struct sdw_intel_stream_free_data *free_data)
+struct sdw_intel_ops sdw_callback = {
+ .params_stream = sdw_params_stream,
+};
+
+static int sdw_ace2x_params_stream(struct device *dev,
+ struct sdw_intel_stream_params_data *params_data)
{
- struct snd_sof_dev *sdev = dev_get_drvdata(dev);
- struct snd_soc_dai *d = free_data->dai;
- struct sof_ipc_dai_config config;
- struct sof_ipc_reply reply;
- int link_id = free_data->link_id;
- int ret;
- u32 size = sizeof(config);
-
- memset(&config, 0, size);
- config.hdr.size = size;
- config.hdr.cmd = SOF_IPC_GLB_DAI_MSG | SOF_IPC_DAI_CONFIG;
- config.type = SOF_DAI_INTEL_ALH;
- config.dai_index = (link_id << 8) | d->id;
- config.alh.stream_id = 0xFFFF; /* invalid value on purpose */
-
- /* send message to DSP */
- ret = sof_ipc_tx_message(sdev->ipc,
- config.hdr.cmd, &config, size, &reply,
- sizeof(reply));
- if (ret < 0) {
- dev_err(sdev->dev,
- "error: failed to free stream for link %d dai->id %d\n",
- link_id, d->id);
- }
+ return sdw_hda_dai_hw_params(params_data->substream,
+ params_data->hw_params,
+ params_data->dai,
+ params_data->link_id);
+}
- return ret;
+static int sdw_ace2x_free_stream(struct device *dev,
+ struct sdw_intel_stream_free_data *free_data)
+{
+ return sdw_hda_dai_hw_free(free_data->substream,
+ free_data->dai,
+ free_data->link_id);
}
-static const struct sdw_intel_ops sdw_callback = {
- .params_stream = sdw_params_stream,
- .free_stream = sdw_free_stream,
+static int sdw_ace2x_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai)
+{
+ return sdw_hda_dai_trigger(substream, cmd, dai);
+}
+
+static struct sdw_intel_ops sdw_ace2x_callback = {
+ .params_stream = sdw_ace2x_params_stream,
+ .free_stream = sdw_ace2x_free_stream,
+ .trigger = sdw_ace2x_trigger,
};
+void hda_common_enable_sdw_irq(struct snd_sof_dev *sdev, bool enable)
+{
+ struct sof_intel_hda_dev *hdev;
+
+ hdev = sdev->pdata->hw_pdata;
+
+ if (!hdev->sdw)
+ return;
+
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIC2,
+ HDA_DSP_REG_ADSPIC2_SNDW,
+ enable ? HDA_DSP_REG_ADSPIC2_SNDW : 0);
+}
+
void hda_sdw_int_enable(struct snd_sof_dev *sdev, bool enable)
{
- sdw_intel_enable_irq(sdev->bar[HDA_DSP_BAR], enable);
+ u32 interface_mask = hda_get_interface_mask(sdev);
+ const struct sof_intel_dsp_desc *chip;
+
+ if (!(interface_mask & BIT(SOF_DAI_INTEL_ALH)))
+ return;
+
+ chip = get_chip_info(sdev->pdata);
+ if (chip && chip->enable_sdw_irq)
+ chip->enable_sdw_irq(sdev, enable);
}
static int hda_sdw_acpi_scan(struct snd_sof_dev *sdev)
{
+ u32 interface_mask = hda_get_interface_mask(sdev);
struct sof_intel_hda_dev *hdev;
acpi_handle handle;
int ret;
+ if (!(interface_mask & BIT(SOF_DAI_INTEL_ALH)))
+ return -EINVAL;
+
handle = ACPI_HANDLE(sdev->dev);
/* save ACPI info for the probe step */
@@ -143,6 +231,7 @@ static int hda_sdw_acpi_scan(struct snd_sof_dev *sdev)
static int hda_sdw_probe(struct snd_sof_dev *sdev)
{
+ const struct sof_intel_dsp_desc *chip;
struct sof_intel_hda_dev *hdev;
struct sdw_intel_res res;
void *sdw;
@@ -151,13 +240,41 @@ static int hda_sdw_probe(struct snd_sof_dev *sdev)
memset(&res, 0, sizeof(res));
- res.mmio_base = sdev->bar[HDA_DSP_BAR];
+ chip = get_chip_info(sdev->pdata);
+ if (chip->hw_ip_version < SOF_INTEL_ACE_2_0) {
+ res.mmio_base = sdev->bar[HDA_DSP_BAR];
+ res.hw_ops = &sdw_intel_cnl_hw_ops;
+ res.shim_base = hdev->desc->sdw_shim_base;
+ res.alh_base = hdev->desc->sdw_alh_base;
+ res.ext = false;
+ res.ops = &sdw_callback;
+ } else {
+ /*
+ * retrieve eml_lock needed to protect shared registers
+ * in the HDaudio multi-link areas
+ */
+ res.eml_lock = hdac_bus_eml_get_mutex(sof_to_bus(sdev), true,
+ AZX_REG_ML_LEPTR_ID_SDW);
+ if (!res.eml_lock)
+ return -ENODEV;
+
+ res.mmio_base = sdev->bar[HDA_DSP_HDA_BAR];
+ /*
+ * the SHIM and SoundWire register offsets are link-specific
+ * and will be determined when adding auxiliary devices
+ */
+ res.hw_ops = &sdw_intel_lnl_hw_ops;
+ res.ext = true;
+ res.ops = &sdw_ace2x_callback;
+
+ }
res.irq = sdev->ipc_irq;
res.handle = hdev->info.handle;
res.parent = sdev->dev;
- res.ops = &sdw_callback;
+
res.dev = sdev->dev;
res.clock_stop_quirks = sdw_clock_stop_quirks;
+ res.hbus = sof_to_bus(sdev);
/*
* ops and arg fields are not populated for now,
@@ -181,15 +298,83 @@ static int hda_sdw_probe(struct snd_sof_dev *sdev)
return 0;
}
+int hda_sdw_check_lcount_common(struct snd_sof_dev *sdev)
+{
+ struct sof_intel_hda_dev *hdev;
+ struct sdw_intel_ctx *ctx;
+ u32 caps;
+
+ hdev = sdev->pdata->hw_pdata;
+ ctx = hdev->sdw;
+
+ caps = snd_sof_dsp_read(sdev, HDA_DSP_BAR, ctx->shim_base + SDW_SHIM_LCAP);
+ caps &= SDW_SHIM_LCAP_LCOUNT_MASK;
+
+ /* Check HW supported vs property value */
+ if (caps < ctx->count) {
+ dev_err(sdev->dev,
+ "%s: BIOS master count %d is larger than hardware capabilities %d\n",
+ __func__, ctx->count, caps);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int hda_sdw_check_lcount_ext(struct snd_sof_dev *sdev)
+{
+ struct sof_intel_hda_dev *hdev;
+ struct sdw_intel_ctx *ctx;
+ struct hdac_bus *bus;
+ u32 slcount;
+
+ bus = sof_to_bus(sdev);
+
+ hdev = sdev->pdata->hw_pdata;
+ ctx = hdev->sdw;
+
+ slcount = hdac_bus_eml_get_count(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
+
+ /* Check HW supported vs property value */
+ if (slcount < ctx->count) {
+ dev_err(sdev->dev,
+ "%s: BIOS master count %d is larger than hardware capabilities %d\n",
+ __func__, ctx->count, slcount);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int hda_sdw_check_lcount(struct snd_sof_dev *sdev)
+{
+ const struct sof_intel_dsp_desc *chip;
+
+ chip = get_chip_info(sdev->pdata);
+ if (chip && chip->read_sdw_lcount)
+ return chip->read_sdw_lcount(sdev);
+
+ return 0;
+}
+
int hda_sdw_startup(struct snd_sof_dev *sdev)
{
struct sof_intel_hda_dev *hdev;
+ struct snd_sof_pdata *pdata = sdev->pdata;
+ int ret;
hdev = sdev->pdata->hw_pdata;
if (!hdev->sdw)
return 0;
+ if (pdata->machine && !pdata->machine->mach_params.link_mask)
+ return 0;
+
+ ret = hda_sdw_check_lcount(sdev);
+ if (ret < 0)
+ return ret;
+
return sdw_intel_startup(hdev->sdw);
}
@@ -208,7 +393,7 @@ static int hda_sdw_exit(struct snd_sof_dev *sdev)
return 0;
}
-static bool hda_dsp_check_sdw_irq(struct snd_sof_dev *sdev)
+bool hda_common_check_sdw_irq(struct snd_sof_dev *sdev)
{
struct sof_intel_hda_dev *hdev;
bool ret = false;
@@ -234,28 +419,62 @@ out:
return ret;
}
+static bool hda_dsp_check_sdw_irq(struct snd_sof_dev *sdev)
+{
+ u32 interface_mask = hda_get_interface_mask(sdev);
+ const struct sof_intel_dsp_desc *chip;
+
+ if (!(interface_mask & BIT(SOF_DAI_INTEL_ALH)))
+ return false;
+
+ chip = get_chip_info(sdev->pdata);
+ if (chip && chip->check_sdw_irq)
+ return chip->check_sdw_irq(sdev);
+
+ return false;
+}
+
static irqreturn_t hda_dsp_sdw_thread(int irq, void *context)
{
return sdw_intel_thread(irq, context);
}
-static bool hda_sdw_check_wakeen_irq(struct snd_sof_dev *sdev)
+bool hda_sdw_check_wakeen_irq_common(struct snd_sof_dev *sdev)
{
struct sof_intel_hda_dev *hdev;
hdev = sdev->pdata->hw_pdata;
if (hdev->sdw &&
snd_sof_dsp_read(sdev, HDA_DSP_BAR,
- HDA_DSP_REG_SNDW_WAKE_STS))
+ hdev->desc->sdw_shim_base + SDW_SHIM_WAKESTS))
return true;
return false;
}
+static bool hda_sdw_check_wakeen_irq(struct snd_sof_dev *sdev)
+{
+ u32 interface_mask = hda_get_interface_mask(sdev);
+ const struct sof_intel_dsp_desc *chip;
+
+ if (!(interface_mask & BIT(SOF_DAI_INTEL_ALH)))
+ return false;
+
+ chip = get_chip_info(sdev->pdata);
+ if (chip && chip->check_sdw_wakeen_irq)
+ return chip->check_sdw_wakeen_irq(sdev);
+
+ return false;
+}
+
void hda_sdw_process_wakeen(struct snd_sof_dev *sdev)
{
+ u32 interface_mask = hda_get_interface_mask(sdev);
struct sof_intel_hda_dev *hdev;
+ if (!(interface_mask & BIT(SOF_DAI_INTEL_ALH)))
+ return;
+
hdev = sdev->pdata->hw_pdata;
if (!hdev->sdw)
return;
@@ -263,7 +482,38 @@ void hda_sdw_process_wakeen(struct snd_sof_dev *sdev)
sdw_intel_process_wakeen_event(hdev->sdw);
}
-#endif
+#else /* IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE) */
+static inline int hda_sdw_acpi_scan(struct snd_sof_dev *sdev)
+{
+ return 0;
+}
+
+static inline int hda_sdw_probe(struct snd_sof_dev *sdev)
+{
+ return 0;
+}
+
+static inline int hda_sdw_exit(struct snd_sof_dev *sdev)
+{
+ return 0;
+}
+
+static inline bool hda_dsp_check_sdw_irq(struct snd_sof_dev *sdev)
+{
+ return false;
+}
+
+static inline irqreturn_t hda_dsp_sdw_thread(int irq, void *context)
+{
+ return IRQ_HANDLED;
+}
+
+static inline bool hda_sdw_check_wakeen_irq(struct snd_sof_dev *sdev)
+{
+ return false;
+}
+
+#endif /* IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE) */
/*
* Debug
@@ -271,33 +521,34 @@ void hda_sdw_process_wakeen(struct snd_sof_dev *sdev)
struct hda_dsp_msg_code {
u32 code;
- const char *msg;
+ const char *text;
};
-static bool hda_use_msi = IS_ENABLED(CONFIG_PCI);
#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG)
+static bool hda_use_msi = true;
module_param_named(use_msi, hda_use_msi, bool, 0444);
MODULE_PARM_DESC(use_msi, "SOF HDA use PCI MSI mode");
+#else
+#define hda_use_msi (1)
#endif
+int sof_hda_position_quirk = SOF_HDA_POSITION_QUIRK_USE_DPIB_REGISTERS;
+module_param_named(position_quirk, sof_hda_position_quirk, int, 0444);
+MODULE_PARM_DESC(position_quirk, "SOF HDaudio position quirk");
+
static char *hda_model;
module_param(hda_model, charp, 0444);
MODULE_PARM_DESC(hda_model, "Use the given HDA board model.");
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
-static int hda_dmic_num = -1;
-module_param_named(dmic_num, hda_dmic_num, int, 0444);
+static int dmic_num_override = -1;
+module_param_named(dmic_num, dmic_num_override, int, 0444);
MODULE_PARM_DESC(dmic_num, "SOF HDA DMIC number");
-static bool hda_codec_use_common_hdmi = IS_ENABLED(CONFIG_SND_HDA_CODEC_HDMI);
-module_param_named(use_common_hdmi, hda_codec_use_common_hdmi, bool, 0444);
-MODULE_PARM_DESC(use_common_hdmi, "SOF HDA use common HDMI codec driver");
-#endif
+static int mclk_id_override = -1;
+module_param_named(mclk_id, mclk_id_override, int, 0444);
+MODULE_PARM_DESC(mclk_id, "SOF SSP mclk_id");
-static const struct hda_dsp_msg_code hda_dsp_rom_msg[] = {
- {HDA_DSP_ROM_FW_MANIFEST_LOADED, "status: manifest loaded"},
- {HDA_DSP_ROM_FW_FW_LOADED, "status: fw loaded"},
- {HDA_DSP_ROM_FW_ENTERED, "status: fw entered"},
+static const struct hda_dsp_msg_code hda_dsp_rom_fw_error_texts[] = {
{HDA_DSP_ROM_CSE_ERROR, "error: cse error"},
{HDA_DSP_ROM_CSE_WRONG_RESPONSE, "error: cse wrong response"},
{HDA_DSP_ROM_IMR_TO_SMALL, "error: IMR too small"},
@@ -316,44 +567,136 @@ static const struct hda_dsp_msg_code hda_dsp_rom_msg[] = {
{HDA_DSP_ROM_NULL_FW_ENTRY, "error: null FW entry point"},
};
-static void hda_dsp_get_status_skl(struct snd_sof_dev *sdev)
+#define FSR_ROM_STATE_ENTRY(state) {FSR_STATE_ROM_##state, #state}
+static const struct hda_dsp_msg_code fsr_rom_state_names[] = {
+ FSR_ROM_STATE_ENTRY(INIT),
+ FSR_ROM_STATE_ENTRY(INIT_DONE),
+ FSR_ROM_STATE_ENTRY(CSE_MANIFEST_LOADED),
+ FSR_ROM_STATE_ENTRY(FW_MANIFEST_LOADED),
+ FSR_ROM_STATE_ENTRY(FW_FW_LOADED),
+ FSR_ROM_STATE_ENTRY(FW_ENTERED),
+ FSR_ROM_STATE_ENTRY(VERIFY_FEATURE_MASK),
+ FSR_ROM_STATE_ENTRY(GET_LOAD_OFFSET),
+ FSR_ROM_STATE_ENTRY(FETCH_ROM_EXT),
+ FSR_ROM_STATE_ENTRY(FETCH_ROM_EXT_DONE),
+ /* CSE states */
+ FSR_ROM_STATE_ENTRY(CSE_IMR_REQUEST),
+ FSR_ROM_STATE_ENTRY(CSE_IMR_GRANTED),
+ FSR_ROM_STATE_ENTRY(CSE_VALIDATE_IMAGE_REQUEST),
+ FSR_ROM_STATE_ENTRY(CSE_IMAGE_VALIDATED),
+ FSR_ROM_STATE_ENTRY(CSE_IPC_IFACE_INIT),
+ FSR_ROM_STATE_ENTRY(CSE_IPC_RESET_PHASE_1),
+ FSR_ROM_STATE_ENTRY(CSE_IPC_OPERATIONAL_ENTRY),
+ FSR_ROM_STATE_ENTRY(CSE_IPC_OPERATIONAL),
+ FSR_ROM_STATE_ENTRY(CSE_IPC_DOWN),
+};
+
+#define FSR_BRINGUP_STATE_ENTRY(state) {FSR_STATE_BRINGUP_##state, #state}
+static const struct hda_dsp_msg_code fsr_bringup_state_names[] = {
+ FSR_BRINGUP_STATE_ENTRY(INIT),
+ FSR_BRINGUP_STATE_ENTRY(INIT_DONE),
+ FSR_BRINGUP_STATE_ENTRY(HPSRAM_LOAD),
+ FSR_BRINGUP_STATE_ENTRY(UNPACK_START),
+ FSR_BRINGUP_STATE_ENTRY(IMR_RESTORE),
+ FSR_BRINGUP_STATE_ENTRY(FW_ENTERED),
+};
+
+#define FSR_WAIT_STATE_ENTRY(state) {FSR_WAIT_FOR_##state, #state}
+static const struct hda_dsp_msg_code fsr_wait_state_names[] = {
+ FSR_WAIT_STATE_ENTRY(IPC_BUSY),
+ FSR_WAIT_STATE_ENTRY(IPC_DONE),
+ FSR_WAIT_STATE_ENTRY(CACHE_INVALIDATION),
+ FSR_WAIT_STATE_ENTRY(LP_SRAM_OFF),
+ FSR_WAIT_STATE_ENTRY(DMA_BUFFER_FULL),
+ FSR_WAIT_STATE_ENTRY(CSE_CSR),
+};
+
+#define FSR_MODULE_NAME_ENTRY(mod) [FSR_MOD_##mod] = #mod
+static const char * const fsr_module_names[] = {
+ FSR_MODULE_NAME_ENTRY(ROM),
+ FSR_MODULE_NAME_ENTRY(ROM_BYP),
+ FSR_MODULE_NAME_ENTRY(BASE_FW),
+ FSR_MODULE_NAME_ENTRY(LP_BOOT),
+ FSR_MODULE_NAME_ENTRY(BRNGUP),
+ FSR_MODULE_NAME_ENTRY(ROM_EXT),
+};
+
+static const char *
+hda_dsp_get_state_text(u32 code, const struct hda_dsp_msg_code *msg_code,
+ size_t array_size)
{
- u32 status;
int i;
- status = snd_sof_dsp_read(sdev, HDA_DSP_BAR,
- HDA_ADSP_FW_STATUS_SKL);
-
- for (i = 0; i < ARRAY_SIZE(hda_dsp_rom_msg); i++) {
- if (status == hda_dsp_rom_msg[i].code) {
- dev_err(sdev->dev, "%s - code %8.8x\n",
- hda_dsp_rom_msg[i].msg, status);
- return;
- }
+ for (i = 0; i < array_size; i++) {
+ if (code == msg_code[i].code)
+ return msg_code[i].text;
}
- /* not for us, must be generic sof message */
- dev_dbg(sdev->dev, "unknown ROM status value %8.8x\n", status);
+ return NULL;
}
-static void hda_dsp_get_status(struct snd_sof_dev *sdev)
+static void hda_dsp_get_state(struct snd_sof_dev *sdev, const char *level)
{
- u32 status;
- int i;
+ const struct sof_intel_dsp_desc *chip = get_chip_info(sdev->pdata);
+ const char *state_text, *error_text, *module_text;
+ u32 fsr, state, wait_state, module, error_code;
+
+ fsr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, chip->rom_status_reg);
+ state = FSR_TO_STATE_CODE(fsr);
+ wait_state = FSR_TO_WAIT_STATE_CODE(fsr);
+ module = FSR_TO_MODULE_CODE(fsr);
+
+ if (module > FSR_MOD_ROM_EXT)
+ module_text = "unknown";
+ else
+ module_text = fsr_module_names[module];
+
+ if (module == FSR_MOD_BRNGUP)
+ state_text = hda_dsp_get_state_text(state, fsr_bringup_state_names,
+ ARRAY_SIZE(fsr_bringup_state_names));
+ else
+ state_text = hda_dsp_get_state_text(state, fsr_rom_state_names,
+ ARRAY_SIZE(fsr_rom_state_names));
+
+ /* not for us, must be generic sof message */
+ if (!state_text) {
+ dev_printk(level, sdev->dev, "%#010x: unknown ROM status value\n", fsr);
+ return;
+ }
- status = snd_sof_dsp_read(sdev, HDA_DSP_BAR,
- HDA_DSP_SRAM_REG_ROM_STATUS);
+ if (wait_state) {
+ const char *wait_state_text;
- for (i = 0; i < ARRAY_SIZE(hda_dsp_rom_msg); i++) {
- if (status == hda_dsp_rom_msg[i].code) {
- dev_err(sdev->dev, "%s - code %8.8x\n",
- hda_dsp_rom_msg[i].msg, status);
- return;
- }
+ wait_state_text = hda_dsp_get_state_text(wait_state, fsr_wait_state_names,
+ ARRAY_SIZE(fsr_wait_state_names));
+ if (!wait_state_text)
+ wait_state_text = "unknown";
+
+ dev_printk(level, sdev->dev,
+ "%#010x: module: %s, state: %s, waiting for: %s, %s\n",
+ fsr, module_text, state_text, wait_state_text,
+ fsr & FSR_HALTED ? "not running" : "running");
+ } else {
+ dev_printk(level, sdev->dev, "%#010x: module: %s, state: %s, %s\n",
+ fsr, module_text, state_text,
+ fsr & FSR_HALTED ? "not running" : "running");
}
- /* not for us, must be generic sof message */
- dev_dbg(sdev->dev, "unknown ROM status value %8.8x\n", status);
+ error_code = snd_sof_dsp_read(sdev, HDA_DSP_BAR, chip->rom_status_reg + 4);
+ if (!error_code)
+ return;
+
+ error_text = hda_dsp_get_state_text(error_code, hda_dsp_rom_fw_error_texts,
+ ARRAY_SIZE(hda_dsp_rom_fw_error_texts));
+ if (!error_text)
+ error_text = "unknown";
+
+ if (state == FSR_STATE_FW_ENTERED)
+ dev_printk(level, sdev->dev, "status code: %#x (%s)\n", error_code,
+ error_text);
+ else
+ dev_printk(level, sdev->dev, "error code: %#x (%s)\n", error_code,
+ error_text);
}
static void hda_dsp_get_registers(struct snd_sof_dev *sdev,
@@ -384,66 +727,76 @@ static void hda_dsp_get_registers(struct snd_sof_dev *sdev,
stack_words * sizeof(u32));
}
-void hda_dsp_dump_skl(struct snd_sof_dev *sdev, u32 flags)
+/* dump the first 8 dwords representing the extended ROM status */
+static void hda_dsp_dump_ext_rom_status(struct snd_sof_dev *sdev, const char *level,
+ u32 flags)
{
- struct sof_ipc_dsp_oops_xtensa xoops;
- struct sof_ipc_panic_info panic_info;
- u32 stack[HDA_DSP_STACK_DUMP_SIZE];
- u32 status, panic;
-
- /* try APL specific status message types first */
- hda_dsp_get_status_skl(sdev);
+ const struct sof_intel_dsp_desc *chip;
+ char msg[128];
+ int len = 0;
+ u32 value;
+ int i;
- /* now try generic SOF status messages */
- status = snd_sof_dsp_read(sdev, HDA_DSP_BAR,
- HDA_ADSP_ERROR_CODE_SKL);
+ chip = get_chip_info(sdev->pdata);
+ for (i = 0; i < HDA_EXT_ROM_STATUS_SIZE; i++) {
+ value = snd_sof_dsp_read(sdev, HDA_DSP_BAR, chip->rom_status_reg + i * 0x4);
+ len += scnprintf(msg + len, sizeof(msg) - len, " 0x%x", value);
+ }
- /*TODO: Check: there is no define in spec, but it is used in the code*/
- panic = snd_sof_dsp_read(sdev, HDA_DSP_BAR,
- HDA_ADSP_ERROR_CODE_SKL + 0x4);
+ dev_printk(level, sdev->dev, "extended rom status: %s", msg);
- if (sdev->fw_state == SOF_FW_BOOT_COMPLETE) {
- hda_dsp_get_registers(sdev, &xoops, &panic_info, stack,
- HDA_DSP_STACK_DUMP_SIZE);
- snd_sof_get_status(sdev, status, panic, &xoops, &panic_info,
- stack, HDA_DSP_STACK_DUMP_SIZE);
- } else {
- dev_err(sdev->dev, "error: status = 0x%8.8x panic = 0x%8.8x\n",
- status, panic);
- hda_dsp_get_status_skl(sdev);
- }
}
void hda_dsp_dump(struct snd_sof_dev *sdev, u32 flags)
{
+ char *level = (flags & SOF_DBG_DUMP_OPTIONAL) ? KERN_DEBUG : KERN_ERR;
struct sof_ipc_dsp_oops_xtensa xoops;
struct sof_ipc_panic_info panic_info;
u32 stack[HDA_DSP_STACK_DUMP_SIZE];
- u32 status, panic;
- /* try APL specific status message types first */
- hda_dsp_get_status(sdev);
+ /* print ROM/FW status */
+ hda_dsp_get_state(sdev, level);
- /* now try generic SOF status messages */
- status = snd_sof_dsp_read(sdev, HDA_DSP_BAR,
- HDA_DSP_SRAM_REG_FW_STATUS);
- panic = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_SRAM_REG_FW_TRACEP);
+ /* The firmware register dump only available with IPC3 */
+ if (flags & SOF_DBG_DUMP_REGS && sdev->pdata->ipc_type == SOF_IPC_TYPE_3) {
+ u32 status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_SRAM_REG_FW_STATUS);
+ u32 panic = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_SRAM_REG_FW_TRACEP);
- if (sdev->fw_state == SOF_FW_BOOT_COMPLETE) {
hda_dsp_get_registers(sdev, &xoops, &panic_info, stack,
HDA_DSP_STACK_DUMP_SIZE);
- snd_sof_get_status(sdev, status, panic, &xoops, &panic_info,
- stack, HDA_DSP_STACK_DUMP_SIZE);
+ sof_print_oops_and_stack(sdev, level, status, panic, &xoops,
+ &panic_info, stack, HDA_DSP_STACK_DUMP_SIZE);
} else {
- dev_err(sdev->dev, "error: status = 0x%8.8x panic = 0x%8.8x\n",
- status, panic);
- hda_dsp_get_status(sdev);
+ hda_dsp_dump_ext_rom_status(sdev, level, flags);
}
}
+void hda_ipc4_dsp_dump(struct snd_sof_dev *sdev, u32 flags)
+{
+ char *level = (flags & SOF_DBG_DUMP_OPTIONAL) ? KERN_DEBUG : KERN_ERR;
+
+ /* print ROM/FW status */
+ hda_dsp_get_state(sdev, level);
+
+ if (flags & SOF_DBG_DUMP_REGS)
+ sof_ipc4_intel_dump_telemetry_state(sdev, flags);
+ else
+ hda_dsp_dump_ext_rom_status(sdev, level, flags);
+}
+
+static bool hda_check_ipc_irq(struct snd_sof_dev *sdev)
+{
+ const struct sof_intel_dsp_desc *chip;
+
+ chip = get_chip_info(sdev->pdata);
+ if (chip && chip->check_ipc_irq)
+ return chip->check_ipc_irq(sdev);
+
+ return false;
+}
+
void hda_ipc_irq_dump(struct snd_sof_dev *sdev)
{
- struct hdac_bus *bus = sof_to_bus(sdev);
u32 adspis;
u32 intsts;
u32 intctl;
@@ -455,14 +808,11 @@ void hda_ipc_irq_dump(struct snd_sof_dev *sdev)
intsts = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTSTS);
intctl = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL);
ppsts = snd_sof_dsp_read(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPSTS);
- rirbsts = snd_hdac_chip_readb(bus, RIRBSTS);
+ rirbsts = snd_sof_dsp_read8(sdev, HDA_DSP_HDA_BAR, AZX_REG_RIRBSTS);
- dev_err(sdev->dev,
- "error: hda irq intsts 0x%8.8x intlctl 0x%8.8x rirb %2.2x\n",
+ dev_err(sdev->dev, "hda irq intsts 0x%8.8x intlctl 0x%8.8x rirb %2.2x\n",
intsts, intctl, rirbsts);
- dev_err(sdev->dev,
- "error: dsp irq ppsts 0x%8.8x adspis 0x%8.8x\n",
- ppsts, adspis);
+ dev_err(sdev->dev, "dsp irq ppsts 0x%8.8x adspis 0x%8.8x\n", ppsts, adspis);
}
void hda_ipc_dump(struct snd_sof_dev *sdev)
@@ -480,11 +830,39 @@ void hda_ipc_dump(struct snd_sof_dev *sdev)
/* dump the IPC regs */
/* TODO: parse the raw msg */
- dev_err(sdev->dev,
- "error: host status 0x%8.8x dsp status 0x%8.8x mask 0x%8.8x\n",
+ dev_err(sdev->dev, "host status 0x%8.8x dsp status 0x%8.8x mask 0x%8.8x\n",
hipcie, hipct, hipcctl);
}
+void hda_ipc4_dump(struct snd_sof_dev *sdev)
+{
+ u32 hipci, hipcie, hipct, hipcte, hipcctl;
+
+ hda_ipc_irq_dump(sdev);
+
+ hipci = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCI);
+ hipcie = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCIE);
+ hipct = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCT);
+ hipcte = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCTE);
+ hipcctl = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCCTL);
+
+ /* dump the IPC regs */
+ /* TODO: parse the raw msg */
+ dev_err(sdev->dev, "Host IPC initiator: %#x|%#x, target: %#x|%#x, ctl: %#x\n",
+ hipci, hipcie, hipct, hipcte, hipcctl);
+}
+
+bool hda_ipc4_tx_is_busy(struct snd_sof_dev *sdev)
+{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+ const struct sof_intel_dsp_desc *chip = hda->desc;
+ u32 val;
+
+ val = snd_sof_dsp_read(sdev, HDA_DSP_BAR, chip->ipc_req);
+
+ return !!(val & chip->ipc_req_mask);
+}
+
static int hda_init(struct snd_sof_dev *sdev)
{
struct hda_bus *hbus;
@@ -496,9 +874,12 @@ static int hda_init(struct snd_sof_dev *sdev)
bus = sof_to_bus(sdev);
/* HDA bus init */
- sof_hda_bus_init(bus, &pci->dev);
+ sof_hda_bus_init(sdev, &pci->dev);
- bus->use_posbuf = 1;
+ if (sof_hda_position_quirk == SOF_HDA_POSITION_QUIRK_USE_DPIB_REGISTERS)
+ bus->use_posbuf = 0;
+ else
+ bus->use_posbuf = 1;
bus->bdl_pos_adj = 0;
bus->sync_write = 1;
@@ -509,9 +890,7 @@ static int hda_init(struct snd_sof_dev *sdev)
/* initialise hdac bus */
bus->addr = pci_resource_start(pci, 0);
-#if IS_ENABLED(CONFIG_PCI)
bus->remap_addr = pci_ioremap_bar(pci, 0);
-#endif
if (!bus->remap_addr) {
dev_err(bus->dev, "error: ioremap error\n");
return -ENXIO;
@@ -522,87 +901,185 @@ static int hda_init(struct snd_sof_dev *sdev)
/* init i915 and HDMI codecs */
ret = hda_codec_i915_init(sdev);
- if (ret < 0)
- dev_warn(sdev->dev, "init of i915 and HDMI codec failed\n");
+ if (ret < 0 && ret != -ENODEV) {
+ dev_err_probe(sdev->dev, ret, "init of i915 and HDMI codec failed\n");
+ goto out;
+ }
/* get controller capabilities */
ret = hda_dsp_ctrl_get_caps(sdev);
- if (ret < 0)
+ if (ret < 0) {
dev_err(sdev->dev, "error: get caps error\n");
+ hda_codec_i915_exit(sdev);
+ }
+
+out:
+ if (ret < 0)
+ iounmap(sof_to_bus(sdev)->remap_addr);
return ret;
}
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
-
-static int check_nhlt_dmic(struct snd_sof_dev *sdev)
+static int check_dmic_num(struct snd_sof_dev *sdev)
{
+ struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
struct nhlt_acpi_table *nhlt;
- int dmic_num;
+ int dmic_num = 0;
- nhlt = intel_nhlt_init(sdev->dev);
- if (nhlt) {
+ nhlt = hdev->nhlt;
+ if (nhlt)
dmic_num = intel_nhlt_get_dmic_geo(sdev->dev, nhlt);
- intel_nhlt_free(nhlt);
- if (dmic_num == 2 || dmic_num == 4)
- return dmic_num;
+
+ /* allow for module parameter override */
+ if (dmic_num_override != -1) {
+ dev_dbg(sdev->dev,
+ "overriding DMICs detected in NHLT tables %d by kernel param %d\n",
+ dmic_num, dmic_num_override);
+ dmic_num = dmic_num_override;
}
- return 0;
+ if (dmic_num < 0 || dmic_num > 4) {
+ dev_dbg(sdev->dev, "invalid dmic_number %d\n", dmic_num);
+ dmic_num = 0;
+ }
+
+ return dmic_num;
}
+static int check_nhlt_ssp_mask(struct snd_sof_dev *sdev)
+{
+ struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
+ struct nhlt_acpi_table *nhlt;
+ int ssp_mask = 0;
+
+ nhlt = hdev->nhlt;
+ if (!nhlt)
+ return ssp_mask;
+
+ if (intel_nhlt_has_endpoint_type(nhlt, NHLT_LINK_SSP)) {
+ ssp_mask = intel_nhlt_ssp_endpoint_mask(nhlt, NHLT_DEVICE_I2S);
+ if (ssp_mask)
+ dev_info(sdev->dev, "NHLT_DEVICE_I2S detected, ssp_mask %#x\n", ssp_mask);
+ }
+
+ return ssp_mask;
+}
+
+static int check_nhlt_ssp_mclk_mask(struct snd_sof_dev *sdev, int ssp_num)
+{
+ struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
+ struct nhlt_acpi_table *nhlt;
+
+ nhlt = hdev->nhlt;
+ if (!nhlt)
+ return 0;
+
+ return intel_nhlt_ssp_mclk_mask(nhlt, ssp_num);
+}
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) || IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE)
+
static const char *fixup_tplg_name(struct snd_sof_dev *sdev,
const char *sof_tplg_filename,
const char *idisp_str,
const char *dmic_str)
{
const char *tplg_filename = NULL;
- char *filename;
- char *split_ext;
+ char *filename, *tmp;
+ const char *split_ext;
- filename = devm_kstrdup(sdev->dev, sof_tplg_filename, GFP_KERNEL);
+ filename = kstrdup(sof_tplg_filename, GFP_KERNEL);
if (!filename)
return NULL;
/* this assumes a .tplg extension */
- split_ext = strsep(&filename, ".");
- if (split_ext) {
+ tmp = filename;
+ split_ext = strsep(&tmp, ".");
+ if (split_ext)
tplg_filename = devm_kasprintf(sdev->dev, GFP_KERNEL,
"%s%s%s.tplg",
split_ext, idisp_str, dmic_str);
- if (!tplg_filename)
- return NULL;
- }
+ kfree(filename);
+
return tplg_filename;
}
+static int dmic_detect_topology_fixup(struct snd_sof_dev *sdev,
+ const char **tplg_filename,
+ const char *idisp_str,
+ int *dmic_found,
+ bool tplg_fixup)
+{
+ const char *dmic_str;
+ int dmic_num;
+
+ /* first check for DMICs (using NHLT or module parameter) */
+ dmic_num = check_dmic_num(sdev);
+
+ switch (dmic_num) {
+ case 1:
+ dmic_str = "-1ch";
+ break;
+ case 2:
+ dmic_str = "-2ch";
+ break;
+ case 3:
+ dmic_str = "-3ch";
+ break;
+ case 4:
+ dmic_str = "-4ch";
+ break;
+ default:
+ dmic_num = 0;
+ dmic_str = "";
+ break;
+ }
+
+ if (tplg_fixup) {
+ const char *default_tplg_filename = *tplg_filename;
+ const char *fixed_tplg_filename;
+
+ fixed_tplg_filename = fixup_tplg_name(sdev, default_tplg_filename,
+ idisp_str, dmic_str);
+ if (!fixed_tplg_filename)
+ return -ENOMEM;
+ *tplg_filename = fixed_tplg_filename;
+ }
+
+ dev_info(sdev->dev, "DMICs detected in NHLT tables: %d\n", dmic_num);
+ *dmic_found = dmic_num;
+
+ return 0;
+}
#endif
static int hda_init_caps(struct snd_sof_dev *sdev)
{
+ u32 interface_mask = hda_get_interface_mask(sdev);
struct hdac_bus *bus = sof_to_bus(sdev);
struct snd_sof_pdata *pdata = sdev->pdata;
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
- struct hdac_ext_link *hlink;
-#endif
struct sof_intel_hda_dev *hdev = pdata->hw_pdata;
u32 link_mask;
int ret = 0;
- device_disable_async_suspend(bus->dev);
-
/* check if dsp is there */
if (bus->ppcap)
dev_dbg(sdev->dev, "PP capability, will probe DSP later.\n");
/* Init HDA controller after i915 init */
- ret = hda_dsp_ctrl_init_chip(sdev, true);
+ ret = hda_dsp_ctrl_init_chip(sdev);
if (ret < 0) {
dev_err(bus->dev, "error: init chip failed with ret: %d\n",
ret);
return ret;
}
+ hda_bus_ml_init(bus);
+
+ /* Skip SoundWire if it is not supported */
+ if (!(interface_mask & BIT(SOF_DAI_INTEL_ALH)))
+ goto skip_soundwire;
+
/* scan SoundWire capabilities exposed by DSDT */
ret = hda_sdw_acpi_scan(sdev);
if (ret < 0) {
@@ -631,34 +1108,15 @@ static int hda_init_caps(struct snd_sof_dev *sdev)
skip_soundwire:
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
- if (bus->mlcap)
- snd_hdac_ext_bus_get_ml_capabilities(bus);
-
/* create codec instances */
- hda_codec_probe_bus(sdev, hda_codec_use_common_hdmi);
+ hda_codec_probe_bus(sdev);
if (!HDA_IDISP_CODEC(bus->codec_mask))
hda_codec_i915_display_power(sdev, false);
- /*
- * we are done probing so decrement link counts
- */
- list_for_each_entry(hlink, &bus->hlink_list, list)
- snd_hdac_ext_bus_link_put(bus, hlink);
-#endif
- return 0;
-}
+ hda_bus_ml_put_all(bus);
-static const struct sof_intel_dsp_desc
- *get_chip_info(struct snd_sof_pdata *pdata)
-{
- const struct sof_dev_desc *desc = pdata->desc;
- const struct sof_intel_dsp_desc *chip_info;
-
- chip_info = desc->chip_info;
-
- return chip_info;
+ return 0;
}
static irqreturn_t hda_dsp_interrupt_handler(int irq, void *context)
@@ -690,17 +1148,27 @@ static irqreturn_t hda_dsp_interrupt_thread(int irq, void *context)
struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
/* deal with streams and controller first */
- if (hda_dsp_check_stream_irq(sdev))
+ if (hda_dsp_check_stream_irq(sdev)) {
+ trace_sof_intel_hda_irq(sdev, "stream");
hda_dsp_stream_threaded_handler(irq, sdev);
+ }
- if (hda_dsp_check_ipc_irq(sdev))
+ if (hda_check_ipc_irq(sdev)) {
+ trace_sof_intel_hda_irq(sdev, "ipc");
sof_ops(sdev)->irq_thread(irq, sdev);
+ }
- if (hda_dsp_check_sdw_irq(sdev))
+ if (hda_dsp_check_sdw_irq(sdev)) {
+ trace_sof_intel_hda_irq(sdev, "sdw");
hda_dsp_sdw_thread(irq, hdev->sdw);
+ }
- if (hda_sdw_check_wakeen_irq(sdev))
+ if (hda_sdw_check_wakeen_irq(sdev)) {
+ trace_sof_intel_hda_irq(sdev, "wakeen");
hda_sdw_process_wakeen(sdev);
+ }
+
+ hda_codec_check_for_state_change(sdev);
/* enable GIE interrupt */
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
@@ -711,29 +1179,32 @@ static irqreturn_t hda_dsp_interrupt_thread(int irq, void *context)
return IRQ_HANDLED;
}
-int hda_dsp_probe(struct snd_sof_dev *sdev)
+int hda_dsp_probe_early(struct snd_sof_dev *sdev)
{
struct pci_dev *pci = to_pci_dev(sdev->dev);
struct sof_intel_hda_dev *hdev;
- struct hdac_bus *bus;
const struct sof_intel_dsp_desc *chip;
int ret = 0;
- /*
- * detect DSP by checking class/subclass/prog-id information
- * class=04 subclass 03 prog-if 00: no DSP, legacy driver is required
- * class=04 subclass 01 prog-if 00: DSP is present
- * (and may be required e.g. for DMIC or SSP support)
- * class=04 subclass 03 prog-if 80: either of DSP or legacy mode works
- */
- if (pci->class == 0x040300) {
- dev_err(sdev->dev, "error: the DSP is not enabled on this platform, aborting probe\n");
- return -ENODEV;
- } else if (pci->class != 0x040100 && pci->class != 0x040380) {
- dev_err(sdev->dev, "error: unknown PCI class/subclass/prog-if 0x%06x found, aborting probe\n", pci->class);
- return -ENODEV;
+ if (!sdev->dspless_mode_selected) {
+ /*
+ * detect DSP by checking class/subclass/prog-id information
+ * class=04 subclass 03 prog-if 00: no DSP, legacy driver is required
+ * class=04 subclass 01 prog-if 00: DSP is present
+ * (and may be required e.g. for DMIC or SSP support)
+ * class=04 subclass 03 prog-if 80: either of DSP or legacy mode works
+ */
+ if (pci->class == 0x040300) {
+ dev_err(sdev->dev, "the DSP is not enabled on this platform, aborting probe\n");
+ return -ENODEV;
+ } else if (pci->class != 0x040100 && pci->class != 0x040380) {
+ dev_err(sdev->dev, "unknown PCI class/subclass/prog-if 0x%06x found, aborting probe\n",
+ pci->class);
+ return -ENODEV;
+ }
+ dev_info(sdev->dev, "DSP detected with PCI class/subclass/prog-if 0x%06x\n",
+ pci->class);
}
- dev_info(sdev->dev, "DSP detected with PCI class/subclass/prog-if 0x%06x\n", pci->class);
chip = get_chip_info(sdev->pdata);
if (!chip) {
@@ -743,11 +1214,25 @@ int hda_dsp_probe(struct snd_sof_dev *sdev)
goto err;
}
+ sdev->num_cores = chip->cores_num;
+
hdev = devm_kzalloc(sdev->dev, sizeof(*hdev), GFP_KERNEL);
if (!hdev)
return -ENOMEM;
sdev->pdata->hw_pdata = hdev;
hdev->desc = chip;
+ ret = hda_init(sdev);
+
+err:
+ return ret;
+}
+
+int hda_dsp_probe(struct snd_sof_dev *sdev)
+{
+ struct pci_dev *pci = to_pci_dev(sdev->dev);
+ struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
+ const struct sof_intel_dsp_desc *chip;
+ int ret = 0;
hdev->dmic_dev = platform_device_register_data(sdev->dev, "dmic-codec",
PLATFORM_DEVID_NONE,
@@ -767,16 +1252,14 @@ int hda_dsp_probe(struct snd_sof_dev *sdev)
hdev->no_ipc_position = sof_ops(sdev)->pcm_pointer ? 1 : 0;
#endif
- /* set up HDA base */
- bus = sof_to_bus(sdev);
- ret = hda_init(sdev);
- if (ret < 0)
- goto hdac_bus_unmap;
+ if (sdev->dspless_mode_selected)
+ hdev->no_ipc_position = 1;
+
+ if (sdev->dspless_mode_selected)
+ goto skip_dsp_setup;
/* DSP base */
-#if IS_ENABLED(CONFIG_PCI)
sdev->bar[HDA_DSP_BAR] = pci_ioremap_bar(pci, HDA_DSP_BAR);
-#endif
if (!sdev->bar[HDA_DSP_BAR]) {
dev_err(sdev->dev, "error: ioremap error\n");
ret = -ENXIO;
@@ -785,16 +1268,14 @@ int hda_dsp_probe(struct snd_sof_dev *sdev)
sdev->mmio_bar = HDA_DSP_BAR;
sdev->mailbox_bar = HDA_DSP_BAR;
+skip_dsp_setup:
/* allow 64bit DMA address if supported by H/W */
- if (!dma_set_mask(&pci->dev, DMA_BIT_MASK(64))) {
- dev_dbg(sdev->dev, "DMA mask is 64 bit\n");
- dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(64));
- } else {
+ if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(64))) {
dev_dbg(sdev->dev, "DMA mask is 32 bit\n");
- dma_set_mask(&pci->dev, DMA_BIT_MASK(32));
- dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(32));
+ dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(32));
}
+ dma_set_max_seg_size(&pci->dev, UINT_MAX);
/* init streams */
ret = hda_dsp_stream_init(sdev);
@@ -853,17 +1334,39 @@ int hda_dsp_probe(struct snd_sof_dev *sdev)
if (ret < 0)
goto free_ipc_irq;
- /* enable ppcap interrupt */
- hda_dsp_ctrl_ppcap_enable(sdev, true);
- hda_dsp_ctrl_ppcap_int_enable(sdev, true);
+ if (!sdev->dspless_mode_selected) {
+ /* enable ppcap interrupt */
+ hda_dsp_ctrl_ppcap_enable(sdev, true);
+ hda_dsp_ctrl_ppcap_int_enable(sdev, true);
+
+ /* set default mailbox offset for FW ready message */
+ sdev->dsp_box.offset = HDA_DSP_MBOX_UPLINK_OFFSET;
+
+ INIT_DELAYED_WORK(&hdev->d0i3_work, hda_dsp_d0i3_work);
+ }
+
+ chip = get_chip_info(sdev->pdata);
+ if (chip && chip->hw_ip_version >= SOF_INTEL_ACE_2_0) {
+ ret = hda_sdw_startup(sdev);
+ if (ret < 0) {
+ dev_err(sdev->dev, "could not startup SoundWire links\n");
+ goto disable_pp_cap;
+ }
+
+ hda_sdw_int_enable(sdev, true);
+ }
- /* set default mailbox offset for FW ready message */
- sdev->dsp_box.offset = HDA_DSP_MBOX_UPLINK_OFFSET;
+ init_waitqueue_head(&hdev->waitq);
- INIT_DELAYED_WORK(&hdev->d0i3_work, hda_dsp_d0i3_work);
+ hdev->nhlt = intel_nhlt_init(sdev->dev);
return 0;
+disable_pp_cap:
+ if (!sdev->dspless_mode_selected) {
+ hda_dsp_ctrl_ppcap_int_enable(sdev, false);
+ hda_dsp_ctrl_ppcap_enable(sdev, false);
+ }
free_ipc_irq:
free_irq(sdev->ipc_irq, sdev);
free_irq_vector:
@@ -872,72 +1375,85 @@ free_irq_vector:
free_streams:
hda_dsp_stream_free(sdev);
/* dsp_unmap: not currently used */
- iounmap(sdev->bar[HDA_DSP_BAR]);
+ if (!sdev->dspless_mode_selected)
+ iounmap(sdev->bar[HDA_DSP_BAR]);
hdac_bus_unmap:
- iounmap(bus->remap_addr);
- hda_codec_i915_exit(sdev);
-err:
+ platform_device_unregister(hdev->dmic_dev);
+
return ret;
}
-int hda_dsp_remove(struct snd_sof_dev *sdev)
+void hda_dsp_remove(struct snd_sof_dev *sdev)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
- struct hdac_bus *bus = sof_to_bus(sdev);
- struct pci_dev *pci = to_pci_dev(sdev->dev);
const struct sof_intel_dsp_desc *chip = hda->desc;
+ struct pci_dev *pci = to_pci_dev(sdev->dev);
+ struct nhlt_acpi_table *nhlt = hda->nhlt;
- /* cancel any attempt for DSP D0I3 */
- cancel_delayed_work_sync(&hda->d0i3_work);
+ if (nhlt)
+ intel_nhlt_free(nhlt);
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
- /* codec removal, invoke bus_device_remove */
- snd_hdac_ext_bus_device_remove(bus);
-#endif
+ if (!sdev->dspless_mode_selected)
+ /* cancel any attempt for DSP D0I3 */
+ cancel_delayed_work_sync(&hda->d0i3_work);
+
+ hda_codec_device_remove(sdev);
hda_sdw_exit(sdev);
if (!IS_ERR_OR_NULL(hda->dmic_dev))
platform_device_unregister(hda->dmic_dev);
- /* disable DSP IRQ */
- snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL,
- SOF_HDA_PPCTL_PIE, 0);
+ if (!sdev->dspless_mode_selected) {
+ /* disable DSP IRQ */
+ hda_dsp_ctrl_ppcap_int_enable(sdev, false);
+ }
/* disable CIE and GIE interrupts */
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL,
SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN, 0);
- /* disable cores */
- if (chip)
- hda_dsp_core_reset_power_down(sdev, chip->cores_mask);
+ if (sdev->dspless_mode_selected)
+ goto skip_disable_dsp;
+
+ /* no need to check for error as the DSP will be disabled anyway */
+ if (chip && chip->power_down_dsp)
+ chip->power_down_dsp(sdev);
/* disable DSP */
- snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL,
- SOF_HDA_PPCTL_GPROCEN, 0);
+ hda_dsp_ctrl_ppcap_enable(sdev, false);
+skip_disable_dsp:
free_irq(sdev->ipc_irq, sdev);
if (sdev->msi_enabled)
pci_free_irq_vectors(pci);
hda_dsp_stream_free(sdev);
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
- snd_hdac_link_free_all(bus);
-#endif
- iounmap(sdev->bar[HDA_DSP_BAR]);
- iounmap(bus->remap_addr);
+ hda_bus_ml_free(sof_to_bus(sdev));
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
- snd_hdac_ext_bus_exit(bus);
-#endif
+ if (!sdev->dspless_mode_selected)
+ iounmap(sdev->bar[HDA_DSP_BAR]);
+}
+
+void hda_dsp_remove_late(struct snd_sof_dev *sdev)
+{
+ iounmap(sof_to_bus(sdev)->remap_addr);
+ sof_hda_bus_exit(sdev);
hda_codec_i915_exit(sdev);
+}
- return 0;
+int hda_power_down_dsp(struct snd_sof_dev *sdev)
+{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+ const struct sof_intel_dsp_desc *chip = hda->desc;
+
+ return hda_dsp_core_reset_power_down(sdev, chip->host_managed_cores_mask);
}
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
-static int hda_generic_machine_select(struct snd_sof_dev *sdev)
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
+static void hda_generic_machine_select(struct snd_sof_dev *sdev,
+ struct snd_soc_acpi_mach **mach)
{
struct hdac_bus *bus = sof_to_bus(sdev);
struct snd_soc_acpi_mach_params *mach_params;
@@ -945,9 +1461,9 @@ static int hda_generic_machine_select(struct snd_sof_dev *sdev)
struct snd_sof_pdata *pdata = sdev->pdata;
const char *tplg_filename;
const char *idisp_str;
- const char *dmic_str;
int dmic_num = 0;
int codec_num = 0;
+ int ret;
int i;
/* codec detection */
@@ -969,12 +1485,10 @@ static int hda_generic_machine_select(struct snd_sof_dev *sdev)
* - one HDMI codec, and/or
* - one external HDAudio codec
*/
- if (!pdata->machine && codec_num <= 2) {
- hda_mach = snd_soc_acpi_intel_hda_machines;
+ if (!*mach && codec_num <= 2) {
+ bool tplg_fixup;
- /* topology: use the info from hda_machines */
- pdata->tplg_filename =
- hda_mach->sof_tplg_filename;
+ hda_mach = snd_soc_acpi_intel_hda_machines;
dev_info(bus->dev, "using HDA machine driver %s now\n",
hda_mach->drv_name);
@@ -984,112 +1498,64 @@ static int hda_generic_machine_select(struct snd_sof_dev *sdev)
else
idisp_str = "";
- /* first check NHLT for DMICs */
- dmic_num = check_nhlt_dmic(sdev);
-
- /* allow for module parameter override */
- if (hda_dmic_num != -1)
- dmic_num = hda_dmic_num;
-
- switch (dmic_num) {
- case 2:
- dmic_str = "-2ch";
- break;
- case 4:
- dmic_str = "-4ch";
- break;
- default:
- dmic_num = 0;
- dmic_str = "";
- break;
+ /* topology: use the info from hda_machines */
+ if (pdata->tplg_filename) {
+ tplg_fixup = false;
+ tplg_filename = pdata->tplg_filename;
+ } else {
+ tplg_fixup = true;
+ tplg_filename = hda_mach->sof_tplg_filename;
}
+ ret = dmic_detect_topology_fixup(sdev, &tplg_filename, idisp_str, &dmic_num,
+ tplg_fixup);
+ if (ret < 0)
+ return;
- tplg_filename = pdata->tplg_filename;
- tplg_filename = fixup_tplg_name(sdev, tplg_filename,
- idisp_str, dmic_str);
- if (!tplg_filename)
- return -EINVAL;
+ hda_mach->mach_params.dmic_num = dmic_num;
+ pdata->tplg_filename = tplg_filename;
+
+ if (codec_num == 2 ||
+ (codec_num == 1 && !HDA_IDISP_CODEC(bus->codec_mask))) {
+ /*
+ * Prevent SoundWire links from starting when an external
+ * HDaudio codec is used
+ */
+ hda_mach->mach_params.link_mask = 0;
+ } else {
+ /*
+ * Allow SoundWire links to start when no external HDaudio codec
+ * was detected. This will not create a SoundWire card but
+ * will help detect if any SoundWire codec reports as ATTACHED.
+ */
+ struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
- dev_info(bus->dev,
- "DMICs detected in NHLT tables: %d\n",
- dmic_num);
+ hda_mach->mach_params.link_mask = hdev->info.link_mask;
+ }
- pdata->machine = hda_mach;
- pdata->tplg_filename = tplg_filename;
+ *mach = hda_mach;
}
}
/* used by hda machine driver to create dai links */
- if (pdata->machine) {
- mach_params = (struct snd_soc_acpi_mach_params *)
- &pdata->machine->mach_params;
+ if (*mach) {
+ mach_params = &(*mach)->mach_params;
mach_params->codec_mask = bus->codec_mask;
- mach_params->common_hdmi_codec_drv = hda_codec_use_common_hdmi;
- mach_params->dmic_num = dmic_num;
+ mach_params->common_hdmi_codec_drv = true;
}
-
- return 0;
}
#else
-static int hda_generic_machine_select(struct snd_sof_dev *sdev)
+static void hda_generic_machine_select(struct snd_sof_dev *sdev,
+ struct snd_soc_acpi_mach **mach)
{
- return 0;
}
#endif
#if IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE)
-/* Check if all Slaves defined on the link can be found */
-static bool link_slaves_found(struct snd_sof_dev *sdev,
- const struct snd_soc_acpi_link_adr *link,
- struct sdw_intel_ctx *sdw)
-{
- struct hdac_bus *bus = sof_to_bus(sdev);
- struct sdw_intel_slave_id *ids = sdw->ids;
- int num_slaves = sdw->num_slaves;
- unsigned int part_id, link_id, unique_id, mfg_id;
- int i, j;
-
- for (i = 0; i < link->num_adr; i++) {
- u64 adr = link->adr_d[i].adr;
-
- mfg_id = SDW_MFG_ID(adr);
- part_id = SDW_PART_ID(adr);
- link_id = SDW_DISCO_LINK_ID(adr);
- for (j = 0; j < num_slaves; j++) {
- if (ids[j].link_id != link_id ||
- ids[j].id.part_id != part_id ||
- ids[j].id.mfg_id != mfg_id)
- continue;
- /*
- * we have to check unique id
- * if there is more than one
- * Slave on the link
- */
- unique_id = SDW_UNIQUE_ID(adr);
- if (link->num_adr == 1 ||
- ids[j].id.unique_id == SDW_IGNORED_UNIQUE_ID ||
- ids[j].id.unique_id == unique_id) {
- dev_dbg(bus->dev,
- "found %x at link %d\n",
- part_id, link_id);
- break;
- }
- }
- if (j == num_slaves) {
- dev_dbg(bus->dev,
- "Slave %x not found\n",
- part_id);
- return false;
- }
- }
- return true;
-}
-static int hda_sdw_machine_select(struct snd_sof_dev *sdev)
+static struct snd_soc_acpi_mach *hda_sdw_machine_select(struct snd_sof_dev *sdev)
{
struct snd_sof_pdata *pdata = sdev->pdata;
const struct snd_soc_acpi_link_adr *link;
- struct hdac_bus *bus = sof_to_bus(sdev);
struct snd_soc_acpi_mach *mach;
struct sof_intel_hda_dev *hdev;
u32 link_mask;
@@ -1104,7 +1570,7 @@ static int hda_sdw_machine_select(struct snd_sof_dev *sdev)
* machines, for mixed cases with I2C/I2S the detection relies
* on the HID list.
*/
- if (link_mask && !pdata->machine) {
+ if (link_mask) {
for (mach = pdata->desc->alt_machines;
mach && mach->link_mask; mach++) {
/*
@@ -1129,7 +1595,9 @@ static int hda_sdw_machine_select(struct snd_sof_dev *sdev)
* Try next machine if any expected Slaves
* are not found on this link.
*/
- if (!link_slaves_found(sdev, link, hdev->sdw))
+ if (!snd_soc_acpi_sdw_link_slaves_found(sdev->dev, link,
+ hdev->sdw->ids,
+ hdev->sdw->num_slaves))
break;
}
/* Found if all Slaves are checked */
@@ -1137,73 +1605,240 @@ static int hda_sdw_machine_select(struct snd_sof_dev *sdev)
break;
}
if (mach && mach->link_mask) {
- dev_dbg(bus->dev,
- "SoundWire machine driver %s topology %s\n",
- mach->drv_name,
- mach->sof_tplg_filename);
- pdata->machine = mach;
+ int dmic_num = 0;
+ bool tplg_fixup;
+ const char *tplg_filename;
+
mach->mach_params.links = mach->links;
mach->mach_params.link_mask = mach->link_mask;
mach->mach_params.platform = dev_name(sdev->dev);
- pdata->fw_filename = mach->sof_fw_filename;
- pdata->tplg_filename = mach->sof_tplg_filename;
- } else {
- dev_info(sdev->dev,
- "No SoundWire machine driver found\n");
+
+ if (pdata->tplg_filename) {
+ tplg_fixup = false;
+ } else {
+ tplg_fixup = true;
+ tplg_filename = mach->sof_tplg_filename;
+ }
+
+ /*
+ * DMICs use up to 4 pins and are typically pin-muxed with SoundWire
+ * link 2 and 3, or link 1 and 2, thus we only try to enable dmics
+ * if all conditions are true:
+ * a) 2 or fewer links are used by SoundWire
+ * b) the NHLT table reports the presence of microphones
+ */
+ if (hweight_long(mach->link_mask) <= 2) {
+ int ret;
+
+ ret = dmic_detect_topology_fixup(sdev, &tplg_filename, "",
+ &dmic_num, tplg_fixup);
+ if (ret < 0)
+ return NULL;
+ }
+ if (tplg_fixup)
+ pdata->tplg_filename = tplg_filename;
+ mach->mach_params.dmic_num = dmic_num;
+
+ dev_dbg(sdev->dev,
+ "SoundWire machine driver %s topology %s\n",
+ mach->drv_name,
+ pdata->tplg_filename);
+
+ return mach;
}
+
+ dev_info(sdev->dev, "No SoundWire machine driver found\n");
}
- return 0;
+ return NULL;
}
#else
-static int hda_sdw_machine_select(struct snd_sof_dev *sdev)
+static struct snd_soc_acpi_mach *hda_sdw_machine_select(struct snd_sof_dev *sdev)
{
- return 0;
+ return NULL;
}
#endif
-void hda_set_mach_params(const struct snd_soc_acpi_mach *mach,
- struct device *dev)
+void hda_set_mach_params(struct snd_soc_acpi_mach *mach,
+ struct snd_sof_dev *sdev)
{
+ struct snd_sof_pdata *pdata = sdev->pdata;
+ const struct sof_dev_desc *desc = pdata->desc;
struct snd_soc_acpi_mach_params *mach_params;
- mach_params = (struct snd_soc_acpi_mach_params *)&mach->mach_params;
- mach_params->platform = dev_name(dev);
+ mach_params = &mach->mach_params;
+ mach_params->platform = dev_name(sdev->dev);
+ if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
+ sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
+ mach_params->num_dai_drivers = SOF_SKL_NUM_DAIS_NOCODEC;
+ else
+ mach_params->num_dai_drivers = desc->ops->num_drv;
+ mach_params->dai_drivers = desc->ops->drv;
}
-void hda_machine_select(struct snd_sof_dev *sdev)
+struct snd_soc_acpi_mach *hda_machine_select(struct snd_sof_dev *sdev)
{
+ u32 interface_mask = hda_get_interface_mask(sdev);
struct snd_sof_pdata *sof_pdata = sdev->pdata;
const struct sof_dev_desc *desc = sof_pdata->desc;
- struct snd_soc_acpi_mach *mach;
+ struct snd_soc_acpi_mach *mach = NULL;
+ const char *tplg_filename;
+
+ /* Try I2S or DMIC if it is supported */
+ if (interface_mask & (BIT(SOF_DAI_INTEL_SSP) | BIT(SOF_DAI_INTEL_DMIC)))
+ mach = snd_soc_acpi_find_machine(desc->machines);
- mach = snd_soc_acpi_find_machine(desc->machines);
if (mach) {
- sof_pdata->tplg_filename = mach->sof_tplg_filename;
- sof_pdata->machine = mach;
+ bool add_extension = false;
+ bool tplg_fixup = false;
+
+ /*
+ * If tplg file name is overridden, use it instead of
+ * the one set in mach table
+ */
+ if (!sof_pdata->tplg_filename) {
+ sof_pdata->tplg_filename = mach->sof_tplg_filename;
+ tplg_fixup = true;
+ }
+
+ /* report to machine driver if any DMICs are found */
+ mach->mach_params.dmic_num = check_dmic_num(sdev);
+
+ if (tplg_fixup &&
+ mach->tplg_quirk_mask & SND_SOC_ACPI_TPLG_INTEL_DMIC_NUMBER &&
+ mach->mach_params.dmic_num) {
+ tplg_filename = devm_kasprintf(sdev->dev, GFP_KERNEL,
+ "%s%s%d%s",
+ sof_pdata->tplg_filename,
+ "-dmic",
+ mach->mach_params.dmic_num,
+ "ch");
+ if (!tplg_filename)
+ return NULL;
+
+ sof_pdata->tplg_filename = tplg_filename;
+ add_extension = true;
+ }
if (mach->link_mask) {
mach->mach_params.links = mach->links;
mach->mach_params.link_mask = mach->link_mask;
}
+
+ /* report SSP link mask to machine driver */
+ mach->mach_params.i2s_link_mask = check_nhlt_ssp_mask(sdev);
+
+ if (tplg_fixup &&
+ mach->tplg_quirk_mask & SND_SOC_ACPI_TPLG_INTEL_SSP_NUMBER &&
+ mach->mach_params.i2s_link_mask) {
+ const struct sof_intel_dsp_desc *chip = get_chip_info(sdev->pdata);
+ int ssp_num;
+ int mclk_mask;
+
+ if (hweight_long(mach->mach_params.i2s_link_mask) > 1 &&
+ !(mach->tplg_quirk_mask & SND_SOC_ACPI_TPLG_INTEL_SSP_MSB))
+ dev_warn(sdev->dev, "More than one SSP exposed by NHLT, choosing MSB\n");
+
+ /* fls returns 1-based results, SSPs indices are 0-based */
+ ssp_num = fls(mach->mach_params.i2s_link_mask) - 1;
+
+ if (ssp_num >= chip->ssp_count) {
+ dev_err(sdev->dev, "Invalid SSP %d, max on this platform is %d\n",
+ ssp_num, chip->ssp_count);
+ return NULL;
+ }
+
+ tplg_filename = devm_kasprintf(sdev->dev, GFP_KERNEL,
+ "%s%s%d",
+ sof_pdata->tplg_filename,
+ "-ssp",
+ ssp_num);
+ if (!tplg_filename)
+ return NULL;
+
+ sof_pdata->tplg_filename = tplg_filename;
+ add_extension = true;
+
+ mclk_mask = check_nhlt_ssp_mclk_mask(sdev, ssp_num);
+
+ if (mclk_mask < 0) {
+ dev_err(sdev->dev, "Invalid MCLK configuration\n");
+ return NULL;
+ }
+
+ dev_dbg(sdev->dev, "MCLK mask %#x found in NHLT\n", mclk_mask);
+
+ if (mclk_mask) {
+ dev_info(sdev->dev, "Overriding topology with MCLK mask %#x from NHLT\n", mclk_mask);
+ sdev->mclk_id_override = true;
+ sdev->mclk_id_quirk = (mclk_mask & BIT(0)) ? 0 : 1;
+ }
+ }
+
+ if (tplg_fixup && add_extension) {
+ tplg_filename = devm_kasprintf(sdev->dev, GFP_KERNEL,
+ "%s%s",
+ sof_pdata->tplg_filename,
+ ".tplg");
+ if (!tplg_filename)
+ return NULL;
+
+ sof_pdata->tplg_filename = tplg_filename;
+ }
+
+ /* check if mclk_id should be modified from topology defaults */
+ if (mclk_id_override >= 0) {
+ dev_info(sdev->dev, "Overriding topology with MCLK %d from kernel_parameter\n", mclk_id_override);
+ sdev->mclk_id_override = true;
+ sdev->mclk_id_quirk = mclk_id_override;
+ }
}
- /*
- * If I2S fails, try SoundWire
- */
- hda_sdw_machine_select(sdev);
+ /* If I2S fails, try SoundWire if it is supported */
+ if (!mach && (interface_mask & BIT(SOF_DAI_INTEL_ALH)))
+ mach = hda_sdw_machine_select(sdev);
/*
* Choose HDA generic machine driver if mach is NULL.
* Otherwise, set certain mach params.
*/
- hda_generic_machine_select(sdev);
-
- if (!sof_pdata->machine)
+ hda_generic_machine_select(sdev, &mach);
+ if (!mach)
dev_warn(sdev->dev, "warning: No matching ASoC machine driver found\n");
+
+ return mach;
+}
+
+int hda_pci_intel_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
+{
+ int ret;
+
+ ret = snd_intel_dsp_driver_probe(pci);
+ if (ret != SND_INTEL_DSP_DRIVER_ANY && ret != SND_INTEL_DSP_DRIVER_SOF) {
+ dev_dbg(&pci->dev, "SOF PCI driver not selected, aborting probe\n");
+ return -ENODEV;
+ }
+
+ return sof_pci_probe(pci, pci_id);
+}
+EXPORT_SYMBOL_NS(hda_pci_intel_probe, SND_SOC_SOF_INTEL_HDA_COMMON);
+
+int hda_register_clients(struct snd_sof_dev *sdev)
+{
+ return hda_probes_register(sdev);
+}
+
+void hda_unregister_clients(struct snd_sof_dev *sdev)
+{
+ hda_probes_unregister(sdev);
}
MODULE_LICENSE("Dual BSD/GPL");
+MODULE_IMPORT_NS(SND_SOC_SOF_PCI_DEV);
MODULE_IMPORT_NS(SND_SOC_SOF_HDA_AUDIO_CODEC);
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);
+MODULE_IMPORT_NS(SND_SOC_SOF_HDA_MLINK);
diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h
index fe452f0d0ec7..b36eb7c78913 100644
--- a/sound/soc/sof/intel/hda.h
+++ b/sound/soc/sof/intel/hda.h
@@ -16,6 +16,8 @@
#include <sound/compress_driver.h>
#include <sound/hda_codec.h>
#include <sound/hdaudio_ext.h>
+#include "../sof-client-probes.h"
+#include "../sof-audio.h"
#include "shim.h"
/* PCI registers */
@@ -120,19 +122,22 @@
#define SOF_HDA_ADSP_DPLBASE_ENABLE 0x01
/* Stream Registers */
-#define SOF_HDA_ADSP_REG_CL_SD_CTL 0x00
-#define SOF_HDA_ADSP_REG_CL_SD_STS 0x03
-#define SOF_HDA_ADSP_REG_CL_SD_LPIB 0x04
-#define SOF_HDA_ADSP_REG_CL_SD_CBL 0x08
-#define SOF_HDA_ADSP_REG_CL_SD_LVI 0x0C
-#define SOF_HDA_ADSP_REG_CL_SD_FIFOW 0x0E
-#define SOF_HDA_ADSP_REG_CL_SD_FIFOSIZE 0x10
-#define SOF_HDA_ADSP_REG_CL_SD_FORMAT 0x12
-#define SOF_HDA_ADSP_REG_CL_SD_FIFOL 0x14
-#define SOF_HDA_ADSP_REG_CL_SD_BDLPL 0x18
-#define SOF_HDA_ADSP_REG_CL_SD_BDLPU 0x1C
+#define SOF_HDA_ADSP_REG_SD_CTL 0x00
+#define SOF_HDA_ADSP_REG_SD_STS 0x03
+#define SOF_HDA_ADSP_REG_SD_LPIB 0x04
+#define SOF_HDA_ADSP_REG_SD_CBL 0x08
+#define SOF_HDA_ADSP_REG_SD_LVI 0x0C
+#define SOF_HDA_ADSP_REG_SD_FIFOW 0x0E
+#define SOF_HDA_ADSP_REG_SD_FIFOSIZE 0x10
+#define SOF_HDA_ADSP_REG_SD_FORMAT 0x12
+#define SOF_HDA_ADSP_REG_SD_FIFOL 0x14
+#define SOF_HDA_ADSP_REG_SD_BDLPL 0x18
+#define SOF_HDA_ADSP_REG_SD_BDLPU 0x1C
#define SOF_HDA_ADSP_SD_ENTRY_SIZE 0x20
+/* SDxFIFOS FIFOS */
+#define SOF_HDA_SD_FIFOSIZE_FIFOS_MASK GENMASK(15, 0)
+
/* CL: Software Position Based FIFO Capability Registers */
#define SOF_DSP_REG_CL_SPBFIFO \
(SOF_HDA_ADSP_LOADER_BASE + 0x20)
@@ -185,13 +190,71 @@
#define HDA_DSP_STACK_DUMP_SIZE 32
+/* ROM/FW status register */
+#define FSR_STATE_MASK GENMASK(23, 0)
+#define FSR_WAIT_STATE_MASK GENMASK(27, 24)
+#define FSR_MODULE_MASK GENMASK(30, 28)
+#define FSR_HALTED BIT(31)
+#define FSR_TO_STATE_CODE(x) ((x) & FSR_STATE_MASK)
+#define FSR_TO_WAIT_STATE_CODE(x) (((x) & FSR_WAIT_STATE_MASK) >> 24)
+#define FSR_TO_MODULE_CODE(x) (((x) & FSR_MODULE_MASK) >> 28)
+
+/* Wait states */
+#define FSR_WAIT_FOR_IPC_BUSY 0x1
+#define FSR_WAIT_FOR_IPC_DONE 0x2
+#define FSR_WAIT_FOR_CACHE_INVALIDATION 0x3
+#define FSR_WAIT_FOR_LP_SRAM_OFF 0x4
+#define FSR_WAIT_FOR_DMA_BUFFER_FULL 0x5
+#define FSR_WAIT_FOR_CSE_CSR 0x6
+
+/* Module codes */
+#define FSR_MOD_ROM 0x0
+#define FSR_MOD_ROM_BYP 0x1
+#define FSR_MOD_BASE_FW 0x2
+#define FSR_MOD_LP_BOOT 0x3
+#define FSR_MOD_BRNGUP 0x4
+#define FSR_MOD_ROM_EXT 0x5
+
+/* State codes (module dependent) */
+/* Module independent states */
+#define FSR_STATE_INIT 0x0
+#define FSR_STATE_INIT_DONE 0x1
+#define FSR_STATE_FW_ENTERED 0x5
+
+/* ROM states */
+#define FSR_STATE_ROM_INIT FSR_STATE_INIT
+#define FSR_STATE_ROM_INIT_DONE FSR_STATE_INIT_DONE
+#define FSR_STATE_ROM_CSE_MANIFEST_LOADED 0x2
+#define FSR_STATE_ROM_FW_MANIFEST_LOADED 0x3
+#define FSR_STATE_ROM_FW_FW_LOADED 0x4
+#define FSR_STATE_ROM_FW_ENTERED FSR_STATE_FW_ENTERED
+#define FSR_STATE_ROM_VERIFY_FEATURE_MASK 0x6
+#define FSR_STATE_ROM_GET_LOAD_OFFSET 0x7
+#define FSR_STATE_ROM_FETCH_ROM_EXT 0x8
+#define FSR_STATE_ROM_FETCH_ROM_EXT_DONE 0x9
+#define FSR_STATE_ROM_BASEFW_ENTERED 0xf /* SKL */
+
+/* (ROM) CSE states */
+#define FSR_STATE_ROM_CSE_IMR_REQUEST 0x10
+#define FSR_STATE_ROM_CSE_IMR_GRANTED 0x11
+#define FSR_STATE_ROM_CSE_VALIDATE_IMAGE_REQUEST 0x12
+#define FSR_STATE_ROM_CSE_IMAGE_VALIDATED 0x13
+
+#define FSR_STATE_ROM_CSE_IPC_IFACE_INIT 0x20
+#define FSR_STATE_ROM_CSE_IPC_RESET_PHASE_1 0x21
+#define FSR_STATE_ROM_CSE_IPC_OPERATIONAL_ENTRY 0x22
+#define FSR_STATE_ROM_CSE_IPC_OPERATIONAL 0x23
+#define FSR_STATE_ROM_CSE_IPC_DOWN 0x24
+
+/* BRINGUP (or BRNGUP) states */
+#define FSR_STATE_BRINGUP_INIT FSR_STATE_INIT
+#define FSR_STATE_BRINGUP_INIT_DONE FSR_STATE_INIT_DONE
+#define FSR_STATE_BRINGUP_HPSRAM_LOAD 0x2
+#define FSR_STATE_BRINGUP_UNPACK_START 0X3
+#define FSR_STATE_BRINGUP_IMR_RESTORE 0x4
+#define FSR_STATE_BRINGUP_FW_ENTERED FSR_STATE_FW_ENTERED
+
/* ROM status/error values */
-#define HDA_DSP_ROM_STS_MASK GENMASK(23, 0)
-#define HDA_DSP_ROM_INIT 0x1
-#define HDA_DSP_ROM_FW_MANIFEST_LOADED 0x3
-#define HDA_DSP_ROM_FW_FW_LOADED 0x4
-#define HDA_DSP_ROM_FW_ENTERED 0x5
-#define HDA_DSP_ROM_RFW_START 0xf
#define HDA_DSP_ROM_CSE_ERROR 40
#define HDA_DSP_ROM_CSE_WRONG_RESPONSE 41
#define HDA_DSP_ROM_IMR_TO_SMALL 42
@@ -208,7 +271,9 @@
#define HDA_DSP_ROM_USER_EXCEPTION 0xBEEF0000
#define HDA_DSP_ROM_UNEXPECTED_RESET 0xDECAF000
#define HDA_DSP_ROM_NULL_FW_ENTRY 0x4c4c4e55
-#define HDA_DSP_IPC_PURGE_FW 0x01004000
+
+#define HDA_DSP_ROM_IPC_CONTROL 0x01000000
+#define HDA_DSP_ROM_IPC_PURGE_FW 0x00004000
/* various timeout values */
#define HDA_DSP_PU_TIMEOUT 50
@@ -221,8 +286,8 @@
#define HDA_DSP_REG_POLL_INTERVAL_US 500 /* 0.5 msec */
#define HDA_DSP_REG_POLL_RETRY_COUNT 50
-#define HDA_DSP_ADSPIC_IPC 1
-#define HDA_DSP_ADSPIS_IPC 1
+#define HDA_DSP_ADSPIC_IPC BIT(0)
+#define HDA_DSP_ADSPIS_IPC BIT(0)
/* Intel HD Audio General DSP Registers */
#define HDA_DSP_GEN_BASE 0x0
@@ -232,8 +297,8 @@
#define HDA_DSP_REG_ADSPIC2 (HDA_DSP_GEN_BASE + 0x10)
#define HDA_DSP_REG_ADSPIS2 (HDA_DSP_GEN_BASE + 0x14)
+#define HDA_DSP_REG_ADSPIC2_SNDW BIT(5)
#define HDA_DSP_REG_ADSPIS2_SNDW BIT(5)
-#define HDA_DSP_REG_SNDW_WAKE_STS 0x2C192
/* Intel HD Audio Inter-Processor Communication Registers */
#define HDA_DSP_IPC_BASE 0x40
@@ -246,6 +311,8 @@
/* Intel Vendor Specific Registers */
#define HDA_VS_INTEL_EM2 0x1030
#define HDA_VS_INTEL_EM2_L1SEN BIT(13)
+#define HDA_VS_INTEL_LTRP 0x1048
+#define HDA_VS_INTEL_LTRP_GB_MASK 0x3F
/* HIPCI */
#define HDA_DSP_REG_HIPCI_BUSY BIT(31)
@@ -266,13 +333,14 @@
/* HIPCTE */
#define HDA_DSP_REG_HIPCTE_MSG_MASK 0x3FFFFFFF
-#define HDA_DSP_ADSPIC_CL_DMA 0x2
-#define HDA_DSP_ADSPIS_CL_DMA 0x2
+#define HDA_DSP_ADSPIC_CL_DMA BIT(1)
+#define HDA_DSP_ADSPIS_CL_DMA BIT(1)
/* Delay before scheduling D0i3 entry */
#define BXT_D0I3_DELAY 5000
#define FW_CL_STREAM_NUMBER 0x1
+#define HDA_FW_BOOT_ATTEMPTS 3
/* ADSPCS - Audio DSP Control & Status */
@@ -304,9 +372,6 @@
#define HDA_DSP_ADSPCS_CPA_SHIFT 24
#define HDA_DSP_ADSPCS_CPA_MASK(cm) ((cm) << HDA_DSP_ADSPCS_CPA_SHIFT)
-/* Mask for a given core index, c = 0.. number of supported cores - 1 */
-#define HDA_DSP_CORE_MASK(c) BIT(c)
-
/*
* Mask for a given number of cores
* nc = number of supported cores
@@ -352,19 +417,16 @@
(HDA_DSP_BDL_SIZE / sizeof(struct sof_intel_dsp_bdl))
/* Number of DAIs */
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
+#define SOF_SKL_NUM_DAIS_NOCODEC 8
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES)
-#define SOF_SKL_NUM_DAIS 16
-#else
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
#define SOF_SKL_NUM_DAIS 15
-#endif
-
#else
-#define SOF_SKL_NUM_DAIS 8
+#define SOF_SKL_NUM_DAIS SOF_SKL_NUM_DAIS_NOCODEC
#endif
/* Intel HD Audio SRAM Window 0*/
+#define HDA_DSP_SRAM_REG_ROM_STATUS_SKL 0x8000
#define HDA_ADSP_SRAM0_BASE_SKL 0x8000
/* Firmware status window */
@@ -382,14 +444,17 @@
#define APL_SSP_COUNT 6
#define CNL_SSP_COUNT 3
#define ICL_SSP_COUNT 6
+#define TGL_SSP_COUNT 3
+#define MTL_SSP_COUNT 3
/* SSP Registers */
#define SSP_SSC1_OFFSET 0x4
-#define SSP_SET_SCLK_SLAVE BIT(25)
-#define SSP_SET_SFRM_SLAVE BIT(24)
-#define SSP_SET_SLAVE (SSP_SET_SCLK_SLAVE | SSP_SET_SFRM_SLAVE)
+#define SSP_SET_SCLK_CONSUMER BIT(25)
+#define SSP_SET_SFRM_CONSUMER BIT(24)
+#define SSP_SET_CBP_CFP (SSP_SET_SCLK_CONSUMER | SSP_SET_SFRM_CONSUMER)
-#define HDA_IDISP_CODEC(x) ((x) & BIT(2))
+#define HDA_IDISP_ADDR 2
+#define HDA_IDISP_CODEC(x) ((x) & BIT(HDA_IDISP_ADDR))
struct sof_intel_dsp_bdl {
__le32 addr_l;
@@ -403,6 +468,9 @@ struct sof_intel_dsp_bdl {
#define SOF_HDA_PLAYBACK 0
#define SOF_HDA_CAPTURE 1
+/* stream flags */
+#define SOF_HDA_STREAM_DMI_L1_COMPATIBLE 1
+
/*
* Time in ms for opportunistic D0I3 entry delay.
* This has been deliberately chosen to be long to avoid race conditions.
@@ -418,6 +486,11 @@ enum sof_hda_D0_substate {
/* represents DSP HDA controller frontend - i.e. host facing control */
struct sof_intel_hda_dev {
+ bool imrboot_supported;
+ bool skip_imr_boot;
+ bool booted_from_imr;
+
+ int boot_iteration;
struct hda_bus hbus;
@@ -434,7 +507,7 @@ struct sof_intel_hda_dev {
u32 stream_max;
/* PM related */
- bool l1_support_changed;/* during suspend, is L1SEN changed or not */
+ bool l1_disabled;/* is DMI link L1 disabled? */
/* DMIC device */
struct platform_device *dmic_dev;
@@ -447,6 +520,23 @@ struct sof_intel_hda_dev {
/* sdw context allocated by SoundWire driver */
struct sdw_intel_ctx *sdw;
+
+ /* FW clock config, 0:HPRO, 1:LPRO */
+ bool clk_config_lpro;
+
+ wait_queue_head_t waitq;
+ bool code_loading;
+
+ /* Intel NHLT information */
+ struct nhlt_acpi_table *nhlt;
+
+ /*
+ * Pointing to the IPC message if immediate sending was not possible
+ * because the downlink communication channel was BUSY at the time.
+ * The message will be re-tried when the channel becomes free (the ACK
+ * is received from the DSP for the previous message)
+ */
+ struct snd_sof_ipc_msg *delayed_ipc_tx_msg;
};
static inline struct hdac_bus *sof_to_bus(struct snd_sof_dev *s)
@@ -465,13 +555,14 @@ static inline struct hda_bus *sof_to_hbus(struct snd_sof_dev *s)
struct sof_intel_hda_stream {
struct snd_sof_dev *sdev;
- struct hdac_ext_stream hda_stream;
- struct sof_intel_stream stream;
+ struct hdac_ext_stream hext_stream;
+ struct sof_intel_stream sof_intel_stream;
int host_reserved; /* reserve host DMA channel */
+ u32 flags;
};
#define hstream_to_sof_hda_stream(hstream) \
- container_of(hstream, struct sof_intel_hda_stream, hda_stream)
+ container_of(hstream, struct sof_intel_hda_stream, hext_stream)
#define bus_to_sof_hda(bus) \
container_of(bus, struct sof_intel_hda_dev, hbus.core)
@@ -480,41 +571,50 @@ struct sof_intel_hda_stream {
(SOF_HDA_ADSP_SD_ENTRY_SIZE * ((s)->index) \
+ SOF_HDA_ADSP_LOADER_BASE)
+#define SOF_STREAM_SD_OFFSET_CRST 0x1
+
+/*
+ * DAI support
+ */
+bool hda_is_chain_dma_supported(struct snd_sof_dev *sdev, u32 dai_type);
+
/*
* DSP Core services.
*/
+int hda_dsp_probe_early(struct snd_sof_dev *sdev);
int hda_dsp_probe(struct snd_sof_dev *sdev);
-int hda_dsp_remove(struct snd_sof_dev *sdev);
-int hda_dsp_core_reset_enter(struct snd_sof_dev *sdev,
- unsigned int core_mask);
-int hda_dsp_core_reset_leave(struct snd_sof_dev *sdev,
- unsigned int core_mask);
-int hda_dsp_core_stall_reset(struct snd_sof_dev *sdev, unsigned int core_mask);
-int hda_dsp_core_run(struct snd_sof_dev *sdev, unsigned int core_mask);
+void hda_dsp_remove(struct snd_sof_dev *sdev);
+void hda_dsp_remove_late(struct snd_sof_dev *sdev);
int hda_dsp_core_power_up(struct snd_sof_dev *sdev, unsigned int core_mask);
+int hda_dsp_core_run(struct snd_sof_dev *sdev, unsigned int core_mask);
int hda_dsp_enable_core(struct snd_sof_dev *sdev, unsigned int core_mask);
-int hda_dsp_core_power_down(struct snd_sof_dev *sdev, unsigned int core_mask);
-bool hda_dsp_core_is_enabled(struct snd_sof_dev *sdev,
- unsigned int core_mask);
int hda_dsp_core_reset_power_down(struct snd_sof_dev *sdev,
unsigned int core_mask);
+int hda_power_down_dsp(struct snd_sof_dev *sdev);
+int hda_dsp_core_get(struct snd_sof_dev *sdev, int core);
void hda_dsp_ipc_int_enable(struct snd_sof_dev *sdev);
void hda_dsp_ipc_int_disable(struct snd_sof_dev *sdev);
+bool hda_dsp_core_is_enabled(struct snd_sof_dev *sdev, unsigned int core_mask);
-int hda_dsp_set_power_state(struct snd_sof_dev *sdev,
- const struct sof_dsp_power_state *target_state);
+int hda_dsp_set_power_state_ipc3(struct snd_sof_dev *sdev,
+ const struct sof_dsp_power_state *target_state);
+int hda_dsp_set_power_state_ipc4(struct snd_sof_dev *sdev,
+ const struct sof_dsp_power_state *target_state);
int hda_dsp_suspend(struct snd_sof_dev *sdev, u32 target_state);
int hda_dsp_resume(struct snd_sof_dev *sdev);
int hda_dsp_runtime_suspend(struct snd_sof_dev *sdev);
int hda_dsp_runtime_resume(struct snd_sof_dev *sdev);
int hda_dsp_runtime_idle(struct snd_sof_dev *sdev);
+int hda_dsp_shutdown_dma_flush(struct snd_sof_dev *sdev);
+int hda_dsp_shutdown(struct snd_sof_dev *sdev);
int hda_dsp_set_hw_params_upon_resume(struct snd_sof_dev *sdev);
-void hda_dsp_dump_skl(struct snd_sof_dev *sdev, u32 flags);
void hda_dsp_dump(struct snd_sof_dev *sdev, u32 flags);
+void hda_ipc4_dsp_dump(struct snd_sof_dev *sdev, u32 flags);
void hda_ipc_dump(struct snd_sof_dev *sdev);
void hda_ipc_irq_dump(struct snd_sof_dev *sdev);
void hda_dsp_d0i3_work(struct work_struct *work);
+int hda_dsp_disable_interrupts(struct snd_sof_dev *sdev);
/*
* DSP PCM Operations.
@@ -528,13 +628,14 @@ int hda_dsp_pcm_close(struct snd_sof_dev *sdev,
int hda_dsp_pcm_hw_params(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
- struct sof_ipc_stream_params *ipc_params);
+ struct snd_sof_platform_stream_params *platform_params);
int hda_dsp_stream_hw_free(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream);
int hda_dsp_pcm_trigger(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream, int cmd);
snd_pcm_uframes_t hda_dsp_pcm_pointer(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream);
+int hda_dsp_pcm_ack(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream);
/*
* DSP Stream Operations.
@@ -543,54 +644,38 @@ snd_pcm_uframes_t hda_dsp_pcm_pointer(struct snd_sof_dev *sdev,
int hda_dsp_stream_init(struct snd_sof_dev *sdev);
void hda_dsp_stream_free(struct snd_sof_dev *sdev);
int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev,
- struct hdac_ext_stream *stream,
+ struct hdac_ext_stream *hext_stream,
struct snd_dma_buffer *dmab,
struct snd_pcm_hw_params *params);
+int hda_dsp_iccmax_stream_hw_params(struct snd_sof_dev *sdev,
+ struct hdac_ext_stream *hext_stream,
+ struct snd_dma_buffer *dmab,
+ struct snd_pcm_hw_params *params);
int hda_dsp_stream_trigger(struct snd_sof_dev *sdev,
- struct hdac_ext_stream *stream, int cmd);
+ struct hdac_ext_stream *hext_stream, int cmd);
irqreturn_t hda_dsp_stream_threaded_handler(int irq, void *context);
int hda_dsp_stream_setup_bdl(struct snd_sof_dev *sdev,
struct snd_dma_buffer *dmab,
- struct hdac_stream *stream);
+ struct hdac_stream *hstream);
bool hda_dsp_check_ipc_irq(struct snd_sof_dev *sdev);
bool hda_dsp_check_stream_irq(struct snd_sof_dev *sdev);
+snd_pcm_uframes_t hda_dsp_stream_get_position(struct hdac_stream *hstream,
+ int direction, bool can_sleep);
+
struct hdac_ext_stream *
- hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction);
+ hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction, u32 flags);
int hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stream_tag);
int hda_dsp_stream_spib_config(struct snd_sof_dev *sdev,
- struct hdac_ext_stream *stream,
+ struct hdac_ext_stream *hext_stream,
int enable, u32 size);
-void hda_ipc_msg_data(struct snd_sof_dev *sdev,
- struct snd_pcm_substream *substream,
- void *p, size_t sz);
-int hda_ipc_pcm_params(struct snd_sof_dev *sdev,
- struct snd_pcm_substream *substream,
- const struct sof_ipc_pcm_params_reply *reply);
-
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES)
-/*
- * Probe Compress Operations.
- */
-int hda_probe_compr_assign(struct snd_sof_dev *sdev,
- struct snd_compr_stream *cstream,
- struct snd_soc_dai *dai);
-int hda_probe_compr_free(struct snd_sof_dev *sdev,
- struct snd_compr_stream *cstream,
- struct snd_soc_dai *dai);
-int hda_probe_compr_set_params(struct snd_sof_dev *sdev,
- struct snd_compr_stream *cstream,
- struct snd_compr_params *params,
- struct snd_soc_dai *dai);
-int hda_probe_compr_trigger(struct snd_sof_dev *sdev,
- struct snd_compr_stream *cstream, int cmd,
- struct snd_soc_dai *dai);
-int hda_probe_compr_pointer(struct snd_sof_dev *sdev,
- struct snd_compr_stream *cstream,
- struct snd_compr_tstamp *tstamp,
- struct snd_soc_dai *dai);
-#endif
+int hda_ipc_msg_data(struct snd_sof_dev *sdev,
+ struct snd_sof_pcm_stream *sps,
+ void *p, size_t sz);
+int hda_set_stream_data_offset(struct snd_sof_dev *sdev,
+ struct snd_sof_pcm_stream *sps,
+ size_t posn_offset);
/*
* DSP IPC Operations.
@@ -608,12 +693,24 @@ int hda_dsp_ipc_cmd_done(struct snd_sof_dev *sdev, int dir);
* DSP Code loader.
*/
int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev);
-int hda_dsp_cl_boot_firmware_skl(struct snd_sof_dev *sdev);
+int hda_dsp_cl_boot_firmware_iccmax(struct snd_sof_dev *sdev);
+int hda_cl_copy_fw(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream);
+struct hdac_ext_stream *hda_cl_stream_prepare(struct snd_sof_dev *sdev, unsigned int format,
+ unsigned int size, struct snd_dma_buffer *dmab,
+ int direction);
+int hda_cl_cleanup(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab,
+ struct hdac_ext_stream *hext_stream);
+int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag, bool imr_boot);
+#define HDA_CL_STREAM_FORMAT 0x40
/* pre and post fw run ops */
int hda_dsp_pre_fw_run(struct snd_sof_dev *sdev);
int hda_dsp_post_fw_run(struct snd_sof_dev *sdev);
+/* parse platform specific ext manifest ops */
+int hda_dsp_ext_man_get_cavs_config_data(struct snd_sof_dev *sdev,
+ const struct sof_ext_man_elem_header *hdr);
+
/*
* HDA Controller Operations.
*/
@@ -623,27 +720,51 @@ void hda_dsp_ctrl_ppcap_int_enable(struct snd_sof_dev *sdev, bool enable);
int hda_dsp_ctrl_link_reset(struct snd_sof_dev *sdev, bool reset);
void hda_dsp_ctrl_misc_clock_gating(struct snd_sof_dev *sdev, bool enable);
int hda_dsp_ctrl_clock_power_gating(struct snd_sof_dev *sdev, bool enable);
-int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool full_reset);
+int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev);
void hda_dsp_ctrl_stop_chip(struct snd_sof_dev *sdev);
/*
* HDA bus operations.
*/
-void sof_hda_bus_init(struct hdac_bus *bus, struct device *dev);
+void sof_hda_bus_init(struct snd_sof_dev *sdev, struct device *dev);
+void sof_hda_bus_exit(struct snd_sof_dev *sdev);
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
/*
* HDA Codec operations.
*/
-void hda_codec_probe_bus(struct snd_sof_dev *sdev,
- bool hda_codec_use_common_hdmi);
-void hda_codec_jack_wake_enable(struct snd_sof_dev *sdev);
+void hda_codec_probe_bus(struct snd_sof_dev *sdev);
+void hda_codec_jack_wake_enable(struct snd_sof_dev *sdev, bool enable);
void hda_codec_jack_check(struct snd_sof_dev *sdev);
+void hda_codec_check_for_state_change(struct snd_sof_dev *sdev);
+void hda_codec_init_cmd_io(struct snd_sof_dev *sdev);
+void hda_codec_resume_cmd_io(struct snd_sof_dev *sdev);
+void hda_codec_stop_cmd_io(struct snd_sof_dev *sdev);
+void hda_codec_suspend_cmd_io(struct snd_sof_dev *sdev);
+void hda_codec_detect_mask(struct snd_sof_dev *sdev);
+void hda_codec_rirb_status_clear(struct snd_sof_dev *sdev);
+bool hda_codec_check_rirb_status(struct snd_sof_dev *sdev);
+void hda_codec_set_codec_wakeup(struct snd_sof_dev *sdev, bool status);
+void hda_codec_device_remove(struct snd_sof_dev *sdev);
-#endif /* CONFIG_SND_SOC_SOF_HDA */
+#else
+
+static inline void hda_codec_probe_bus(struct snd_sof_dev *sdev) { }
+static inline void hda_codec_jack_wake_enable(struct snd_sof_dev *sdev, bool enable) { }
+static inline void hda_codec_jack_check(struct snd_sof_dev *sdev) { }
+static inline void hda_codec_check_for_state_change(struct snd_sof_dev *sdev) { }
+static inline void hda_codec_init_cmd_io(struct snd_sof_dev *sdev) { }
+static inline void hda_codec_resume_cmd_io(struct snd_sof_dev *sdev) { }
+static inline void hda_codec_stop_cmd_io(struct snd_sof_dev *sdev) { }
+static inline void hda_codec_suspend_cmd_io(struct snd_sof_dev *sdev) { }
+static inline void hda_codec_detect_mask(struct snd_sof_dev *sdev) { }
+static inline void hda_codec_rirb_status_clear(struct snd_sof_dev *sdev) { }
+static inline bool hda_codec_check_rirb_status(struct snd_sof_dev *sdev) { return false; }
+static inline void hda_codec_set_codec_wakeup(struct snd_sof_dev *sdev, bool status) { }
+static inline void hda_codec_device_remove(struct snd_sof_dev *sdev) { }
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) && \
- (IS_ENABLED(CONFIG_SND_HDA_CODEC_HDMI) || \
- IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI))
+#endif /* CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC */
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) && IS_ENABLED(CONFIG_SND_HDA_CODEC_HDMI)
void hda_codec_i915_display_power(struct snd_sof_dev *sdev, bool enable);
int hda_codec_i915_init(struct snd_sof_dev *sdev);
@@ -651,8 +772,7 @@ int hda_codec_i915_exit(struct snd_sof_dev *sdev);
#else
-static inline void hda_codec_i915_display_power(struct snd_sof_dev *sdev,
- bool enable) { }
+static inline void hda_codec_i915_display_power(struct snd_sof_dev *sdev, bool enable) { }
static inline int hda_codec_i915_init(struct snd_sof_dev *sdev) { return 0; }
static inline int hda_codec_i915_exit(struct snd_sof_dev *sdev) { return 0; }
@@ -661,7 +781,8 @@ static inline int hda_codec_i915_exit(struct snd_sof_dev *sdev) { return 0; }
/*
* Trace Control.
*/
-int hda_dsp_trace_init(struct snd_sof_dev *sdev, u32 *stream_tag);
+int hda_dsp_trace_init(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab,
+ struct sof_ipc_dma_trace_params_ext *dtrace_params);
int hda_dsp_trace_release(struct snd_sof_dev *sdev);
int hda_dsp_trace_trigger(struct snd_sof_dev *sdev, int cmd);
@@ -670,18 +791,23 @@ int hda_dsp_trace_trigger(struct snd_sof_dev *sdev, int cmd);
*/
#if IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE)
+int hda_sdw_check_lcount_common(struct snd_sof_dev *sdev);
+int hda_sdw_check_lcount_ext(struct snd_sof_dev *sdev);
int hda_sdw_startup(struct snd_sof_dev *sdev);
+void hda_common_enable_sdw_irq(struct snd_sof_dev *sdev, bool enable);
void hda_sdw_int_enable(struct snd_sof_dev *sdev, bool enable);
+bool hda_sdw_check_wakeen_irq_common(struct snd_sof_dev *sdev);
void hda_sdw_process_wakeen(struct snd_sof_dev *sdev);
+bool hda_common_check_sdw_irq(struct snd_sof_dev *sdev);
#else
-static inline int hda_sdw_acpi_scan(struct snd_sof_dev *sdev)
+static inline int hda_sdw_check_lcount_common(struct snd_sof_dev *sdev)
{
return 0;
}
-static inline int hda_sdw_probe(struct snd_sof_dev *sdev)
+static inline int hda_sdw_check_lcount_ext(struct snd_sof_dev *sdev)
{
return 0;
}
@@ -691,55 +817,186 @@ static inline int hda_sdw_startup(struct snd_sof_dev *sdev)
return 0;
}
-static inline int hda_sdw_exit(struct snd_sof_dev *sdev)
+static inline void hda_common_enable_sdw_irq(struct snd_sof_dev *sdev, bool enable)
{
- return 0;
}
static inline void hda_sdw_int_enable(struct snd_sof_dev *sdev, bool enable)
{
}
-static inline bool hda_dsp_check_sdw_irq(struct snd_sof_dev *sdev)
+static inline bool hda_sdw_check_wakeen_irq_common(struct snd_sof_dev *sdev)
{
return false;
}
-static inline irqreturn_t hda_dsp_sdw_thread(int irq, void *context)
+static inline void hda_sdw_process_wakeen(struct snd_sof_dev *sdev)
{
- return IRQ_HANDLED;
}
-static inline bool hda_sdw_check_wakeen_irq(struct snd_sof_dev *sdev)
+static inline bool hda_common_check_sdw_irq(struct snd_sof_dev *sdev)
{
return false;
}
-static inline void hda_sdw_process_wakeen(struct snd_sof_dev *sdev)
-{
-}
#endif
+int sdw_hda_dai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai,
+ int link_id);
+
+int sdw_hda_dai_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai,
+ int link_id);
+
+int sdw_hda_dai_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *cpu_dai);
+
/* common dai driver */
extern struct snd_soc_dai_driver skl_dai[];
+int hda_dsp_dais_suspend(struct snd_sof_dev *sdev);
/*
* Platform Specific HW abstraction Ops.
*/
-extern const struct snd_sof_dsp_ops sof_apl_ops;
-extern const struct snd_sof_dsp_ops sof_cnl_ops;
+extern struct snd_sof_dsp_ops sof_hda_common_ops;
+
+extern struct snd_sof_dsp_ops sof_skl_ops;
+int sof_skl_ops_init(struct snd_sof_dev *sdev);
+extern struct snd_sof_dsp_ops sof_apl_ops;
+int sof_apl_ops_init(struct snd_sof_dev *sdev);
+extern struct snd_sof_dsp_ops sof_cnl_ops;
+int sof_cnl_ops_init(struct snd_sof_dev *sdev);
+extern struct snd_sof_dsp_ops sof_tgl_ops;
+int sof_tgl_ops_init(struct snd_sof_dev *sdev);
+extern struct snd_sof_dsp_ops sof_icl_ops;
+int sof_icl_ops_init(struct snd_sof_dev *sdev);
+extern struct snd_sof_dsp_ops sof_mtl_ops;
+int sof_mtl_ops_init(struct snd_sof_dev *sdev);
+extern struct snd_sof_dsp_ops sof_lnl_ops;
+int sof_lnl_ops_init(struct snd_sof_dev *sdev);
+extern const struct sof_intel_dsp_desc skl_chip_info;
extern const struct sof_intel_dsp_desc apl_chip_info;
extern const struct sof_intel_dsp_desc cnl_chip_info;
-extern const struct sof_intel_dsp_desc skl_chip_info;
extern const struct sof_intel_dsp_desc icl_chip_info;
extern const struct sof_intel_dsp_desc tgl_chip_info;
+extern const struct sof_intel_dsp_desc tglh_chip_info;
extern const struct sof_intel_dsp_desc ehl_chip_info;
extern const struct sof_intel_dsp_desc jsl_chip_info;
+extern const struct sof_intel_dsp_desc adls_chip_info;
+extern const struct sof_intel_dsp_desc mtl_chip_info;
+extern const struct sof_intel_dsp_desc arl_s_chip_info;
+extern const struct sof_intel_dsp_desc lnl_chip_info;
+
+/* Probes support */
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES)
+int hda_probes_register(struct snd_sof_dev *sdev);
+void hda_probes_unregister(struct snd_sof_dev *sdev);
+#else
+static inline int hda_probes_register(struct snd_sof_dev *sdev)
+{
+ return 0;
+}
+
+static inline void hda_probes_unregister(struct snd_sof_dev *sdev)
+{
+}
+#endif /* CONFIG_SND_SOC_SOF_HDA_PROBES */
+
+/* SOF client registration for HDA platforms */
+int hda_register_clients(struct snd_sof_dev *sdev);
+void hda_unregister_clients(struct snd_sof_dev *sdev);
/* machine driver select */
-void hda_machine_select(struct snd_sof_dev *sdev);
-void hda_set_mach_params(const struct snd_soc_acpi_mach *mach,
- struct device *dev);
+struct snd_soc_acpi_mach *hda_machine_select(struct snd_sof_dev *sdev);
+void hda_set_mach_params(struct snd_soc_acpi_mach *mach,
+ struct snd_sof_dev *sdev);
+
+/* PCI driver selection and probe */
+int hda_pci_intel_probe(struct pci_dev *pci, const struct pci_device_id *pci_id);
+
+struct snd_sof_dai;
+struct sof_ipc_dai_config;
+
+#define SOF_HDA_POSITION_QUIRK_USE_SKYLAKE_LEGACY (0) /* previous implementation */
+#define SOF_HDA_POSITION_QUIRK_USE_DPIB_REGISTERS (1) /* recommended if VC0 only */
+#define SOF_HDA_POSITION_QUIRK_USE_DPIB_DDR_UPDATE (2) /* recommended with VC0 or VC1 */
+
+extern int sof_hda_position_quirk;
+
+void hda_set_dai_drv_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *ops);
+void hda_ops_free(struct snd_sof_dev *sdev);
+
+/* SKL/KBL */
+int hda_dsp_cl_boot_firmware_skl(struct snd_sof_dev *sdev);
+int hda_dsp_core_stall_reset(struct snd_sof_dev *sdev, unsigned int core_mask);
+
+/* IPC4 */
+irqreturn_t cnl_ipc4_irq_thread(int irq, void *context);
+int cnl_ipc4_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg);
+irqreturn_t hda_dsp_ipc4_irq_thread(int irq, void *context);
+bool hda_ipc4_tx_is_busy(struct snd_sof_dev *sdev);
+void hda_dsp_ipc4_schedule_d0i3_work(struct sof_intel_hda_dev *hdev,
+ struct snd_sof_ipc_msg *msg);
+int hda_dsp_ipc4_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg);
+void hda_ipc4_dump(struct snd_sof_dev *sdev);
+extern struct sdw_intel_ops sdw_callback;
+
+struct sof_ipc4_fw_library;
+int hda_dsp_ipc4_load_library(struct snd_sof_dev *sdev,
+ struct sof_ipc4_fw_library *fw_lib, bool reload);
+
+/**
+ * struct hda_dai_widget_dma_ops - DAI DMA ops optional by default unless specified otherwise
+ * @get_hext_stream: Mandatory function pointer to get the saved pointer to struct hdac_ext_stream
+ * @assign_hext_stream: Function pointer to assign a hdac_ext_stream
+ * @release_hext_stream: Function pointer to release the hdac_ext_stream
+ * @setup_hext_stream: Function pointer for hdac_ext_stream setup
+ * @reset_hext_stream: Function pointer for hdac_ext_stream reset
+ * @pre_trigger: Function pointer for DAI DMA pre-trigger actions
+ * @trigger: Function pointer for DAI DMA trigger actions
+ * @post_trigger: Function pointer for DAI DMA post-trigger actions
+ * @codec_dai_set_stream: Function pointer to set codec-side stream information
+ * @calc_stream_format: Function pointer to determine stream format from hw_params and
+ * for HDaudio codec DAI from the .sig bits
+ * @get_hlink: Mandatory function pointer to retrieve hlink, mainly to program LOSIDV
+ * for legacy HDaudio links or program HDaudio Extended Link registers.
+ */
+struct hda_dai_widget_dma_ops {
+ struct hdac_ext_stream *(*get_hext_stream)(struct snd_sof_dev *sdev,
+ struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream);
+ struct hdac_ext_stream *(*assign_hext_stream)(struct snd_sof_dev *sdev,
+ struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream);
+ void (*release_hext_stream)(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream);
+ void (*setup_hext_stream)(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream,
+ unsigned int format_val);
+ void (*reset_hext_stream)(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_sream);
+ int (*pre_trigger)(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream, int cmd);
+ int (*trigger)(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream, int cmd);
+ int (*post_trigger)(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
+ struct snd_pcm_substream *substream, int cmd);
+ void (*codec_dai_set_stream)(struct snd_sof_dev *sdev,
+ struct snd_pcm_substream *substream,
+ struct hdac_stream *hstream);
+ unsigned int (*calc_stream_format)(struct snd_sof_dev *sdev,
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params);
+ struct hdac_ext_link * (*get_hlink)(struct snd_sof_dev *sdev,
+ struct snd_pcm_substream *substream);
+};
+
+const struct hda_dai_widget_dma_ops *
+hda_select_dai_widget_ops(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget);
+int hda_dai_config(struct snd_soc_dapm_widget *w, unsigned int flags,
+ struct snd_sof_dai_config_data *data);
+int hda_link_dma_cleanup(struct snd_pcm_substream *substream, struct hdac_ext_stream *hext_stream,
+ struct snd_soc_dai *cpu_dai);
#endif
diff --git a/sound/soc/sof/intel/icl.c b/sound/soc/sof/intel/icl.c
new file mode 100644
index 000000000000..040698591992
--- /dev/null
+++ b/sound/soc/sof/intel/icl.c
@@ -0,0 +1,198 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// Copyright(c) 2020 Intel Corporation. All rights reserved.
+//
+// Author: Fred Oh <fred.oh@linux.intel.com>
+//
+
+/*
+ * Hardware interface for audio DSP on IceLake.
+ */
+
+#include <linux/kernel.h>
+#include <linux/kconfig.h>
+#include <linux/export.h>
+#include <linux/bits.h>
+#include "../ipc4-priv.h"
+#include "../ops.h"
+#include "hda.h"
+#include "hda-ipc.h"
+#include "../sof-audio.h"
+
+#define ICL_DSP_HPRO_CORE_ID 3
+
+static const struct snd_sof_debugfs_map icl_dsp_debugfs[] = {
+ {"hda", HDA_DSP_HDA_BAR, 0, 0x4000, SOF_DEBUGFS_ACCESS_ALWAYS},
+ {"pp", HDA_DSP_PP_BAR, 0, 0x1000, SOF_DEBUGFS_ACCESS_ALWAYS},
+ {"dsp", HDA_DSP_BAR, 0, 0x10000, SOF_DEBUGFS_ACCESS_ALWAYS},
+};
+
+static int icl_dsp_core_stall(struct snd_sof_dev *sdev, unsigned int core_mask)
+{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+ const struct sof_intel_dsp_desc *chip = hda->desc;
+
+ /* make sure core_mask in host managed cores */
+ core_mask &= chip->host_managed_cores_mask;
+ if (!core_mask) {
+ dev_err(sdev->dev, "error: core_mask is not in host managed cores\n");
+ return -EINVAL;
+ }
+
+ /* stall core */
+ snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPCS,
+ HDA_DSP_ADSPCS_CSTALL_MASK(core_mask),
+ HDA_DSP_ADSPCS_CSTALL_MASK(core_mask));
+
+ return 0;
+}
+
+/*
+ * post fw run operation for ICL.
+ * Core 3 will be powered up and in stall when HPRO is enabled
+ */
+static int icl_dsp_post_fw_run(struct snd_sof_dev *sdev)
+{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+ int ret;
+
+ if (sdev->first_boot) {
+ struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
+
+ ret = hda_sdw_startup(sdev);
+ if (ret < 0) {
+ dev_err(sdev->dev, "error: could not startup SoundWire links\n");
+ return ret;
+ }
+
+ /* Check if IMR boot is usable */
+ if (!sof_debug_check_flag(SOF_DBG_IGNORE_D3_PERSISTENT) &&
+ sdev->fw_ready.flags & SOF_IPC_INFO_D3_PERSISTENT)
+ hdev->imrboot_supported = true;
+ }
+
+ hda_sdw_int_enable(sdev, true);
+
+ /*
+ * The recommended HW programming sequence for ICL is to
+ * power up core 3 and keep it in stall if HPRO is enabled.
+ */
+ if (!hda->clk_config_lpro) {
+ ret = hda_dsp_enable_core(sdev, BIT(ICL_DSP_HPRO_CORE_ID));
+ if (ret < 0) {
+ dev_err(sdev->dev, "error: dsp core power up failed on core %d\n",
+ ICL_DSP_HPRO_CORE_ID);
+ return ret;
+ }
+
+ sdev->enabled_cores_mask |= BIT(ICL_DSP_HPRO_CORE_ID);
+ sdev->dsp_core_ref_count[ICL_DSP_HPRO_CORE_ID]++;
+
+ snd_sof_dsp_stall(sdev, BIT(ICL_DSP_HPRO_CORE_ID));
+ }
+
+ /* re-enable clock gating and power gating */
+ return hda_dsp_ctrl_clock_power_gating(sdev, true);
+}
+
+/* Icelake ops */
+struct snd_sof_dsp_ops sof_icl_ops;
+EXPORT_SYMBOL_NS(sof_icl_ops, SND_SOC_SOF_INTEL_HDA_COMMON);
+
+int sof_icl_ops_init(struct snd_sof_dev *sdev)
+{
+ /* common defaults */
+ memcpy(&sof_icl_ops, &sof_hda_common_ops, sizeof(struct snd_sof_dsp_ops));
+
+ /* probe/remove/shutdown */
+ sof_icl_ops.shutdown = hda_dsp_shutdown;
+
+ if (sdev->pdata->ipc_type == SOF_IPC_TYPE_3) {
+ /* doorbell */
+ sof_icl_ops.irq_thread = cnl_ipc_irq_thread;
+
+ /* ipc */
+ sof_icl_ops.send_msg = cnl_ipc_send_msg;
+
+ /* debug */
+ sof_icl_ops.ipc_dump = cnl_ipc_dump;
+
+ sof_icl_ops.set_power_state = hda_dsp_set_power_state_ipc3;
+ }
+
+ if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4) {
+ struct sof_ipc4_fw_data *ipc4_data;
+
+ sdev->private = kzalloc(sizeof(*ipc4_data), GFP_KERNEL);
+ if (!sdev->private)
+ return -ENOMEM;
+
+ ipc4_data = sdev->private;
+ ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET;
+
+ ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_2;
+
+ /* External library loading support */
+ ipc4_data->load_library = hda_dsp_ipc4_load_library;
+
+ /* doorbell */
+ sof_icl_ops.irq_thread = cnl_ipc4_irq_thread;
+
+ /* ipc */
+ sof_icl_ops.send_msg = cnl_ipc4_send_msg;
+
+ /* debug */
+ sof_icl_ops.ipc_dump = cnl_ipc4_dump;
+
+ sof_icl_ops.set_power_state = hda_dsp_set_power_state_ipc4;
+ }
+
+ /* debug */
+ sof_icl_ops.debug_map = icl_dsp_debugfs;
+ sof_icl_ops.debug_map_count = ARRAY_SIZE(icl_dsp_debugfs);
+
+ /* pre/post fw run */
+ sof_icl_ops.post_fw_run = icl_dsp_post_fw_run;
+
+ /* firmware run */
+ sof_icl_ops.run = hda_dsp_cl_boot_firmware_iccmax;
+ sof_icl_ops.stall = icl_dsp_core_stall;
+
+ /* dsp core get/put */
+ sof_icl_ops.core_get = hda_dsp_core_get;
+
+ /* set DAI driver ops */
+ hda_set_dai_drv_ops(sdev, &sof_icl_ops);
+
+ return 0;
+};
+EXPORT_SYMBOL_NS(sof_icl_ops_init, SND_SOC_SOF_INTEL_HDA_COMMON);
+
+const struct sof_intel_dsp_desc icl_chip_info = {
+ /* Icelake */
+ .cores_num = 4,
+ .init_core_mask = 1,
+ .host_managed_cores_mask = GENMASK(3, 0),
+ .ipc_req = CNL_DSP_REG_HIPCIDR,
+ .ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY,
+ .ipc_ack = CNL_DSP_REG_HIPCIDA,
+ .ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE,
+ .ipc_ctl = CNL_DSP_REG_HIPCCTL,
+ .rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS,
+ .rom_init_timeout = 300,
+ .ssp_count = ICL_SSP_COUNT,
+ .ssp_base_offset = CNL_SSP_BASE_OFFSET,
+ .sdw_shim_base = SDW_SHIM_BASE,
+ .sdw_alh_base = SDW_ALH_BASE,
+ .d0i3_offset = SOF_HDA_VS_D0I3C,
+ .read_sdw_lcount = hda_sdw_check_lcount_common,
+ .enable_sdw_irq = hda_common_enable_sdw_irq,
+ .check_sdw_irq = hda_common_check_sdw_irq,
+ .check_sdw_wakeen_irq = hda_sdw_check_wakeen_irq_common,
+ .check_ipc_irq = hda_dsp_check_ipc_irq,
+ .cl_init = cl_dsp_init,
+ .power_down_dsp = hda_power_down_dsp,
+ .disable_interrupts = hda_dsp_disable_interrupts,
+ .hw_ip_version = SOF_INTEL_CAVS_2_0,
+};
+EXPORT_SYMBOL_NS(icl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
diff --git a/sound/soc/sof/intel/intel-ipc.c b/sound/soc/sof/intel/intel-ipc.c
deleted file mode 100644
index 310f9168c124..000000000000
--- a/sound/soc/sof/intel/intel-ipc.c
+++ /dev/null
@@ -1,92 +0,0 @@
-// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
-//
-// This file is provided under a dual BSD/GPLv2 license. When using or
-// redistributing this file, you may do so under either license.
-//
-// Copyright(c) 2019 Intel Corporation. All rights reserved.
-//
-// Authors: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
-
-/* Intel-specific SOF IPC code */
-
-#include <linux/device.h>
-#include <linux/export.h>
-#include <linux/module.h>
-#include <linux/types.h>
-
-#include <sound/pcm.h>
-#include <sound/sof/stream.h>
-
-#include "../ops.h"
-#include "../sof-priv.h"
-
-struct intel_stream {
- size_t posn_offset;
-};
-
-/* Mailbox-based Intel IPC implementation */
-void intel_ipc_msg_data(struct snd_sof_dev *sdev,
- struct snd_pcm_substream *substream,
- void *p, size_t sz)
-{
- if (!substream || !sdev->stream_box.size) {
- sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz);
- } else {
- struct intel_stream *stream = substream->runtime->private_data;
-
- /* The stream might already be closed */
- if (stream)
- sof_mailbox_read(sdev, stream->posn_offset, p, sz);
- }
-}
-EXPORT_SYMBOL_NS(intel_ipc_msg_data, SND_SOC_SOF_INTEL_HIFI_EP_IPC);
-
-int intel_ipc_pcm_params(struct snd_sof_dev *sdev,
- struct snd_pcm_substream *substream,
- const struct sof_ipc_pcm_params_reply *reply)
-{
- struct intel_stream *stream = substream->runtime->private_data;
- size_t posn_offset = reply->posn_offset;
-
- /* check if offset is overflow or it is not aligned */
- if (posn_offset > sdev->stream_box.size ||
- posn_offset % sizeof(struct sof_ipc_stream_posn) != 0)
- return -EINVAL;
-
- stream->posn_offset = sdev->stream_box.offset + posn_offset;
-
- dev_dbg(sdev->dev, "pcm: stream dir %d, posn mailbox offset is %zu",
- substream->stream, stream->posn_offset);
-
- return 0;
-}
-EXPORT_SYMBOL_NS(intel_ipc_pcm_params, SND_SOC_SOF_INTEL_HIFI_EP_IPC);
-
-int intel_pcm_open(struct snd_sof_dev *sdev,
- struct snd_pcm_substream *substream)
-{
- struct intel_stream *stream = kmalloc(sizeof(*stream), GFP_KERNEL);
-
- if (!stream)
- return -ENOMEM;
-
- /* binding pcm substream to hda stream */
- substream->runtime->private_data = stream;
-
- return 0;
-}
-EXPORT_SYMBOL_NS(intel_pcm_open, SND_SOC_SOF_INTEL_HIFI_EP_IPC);
-
-int intel_pcm_close(struct snd_sof_dev *sdev,
- struct snd_pcm_substream *substream)
-{
- struct intel_stream *stream = substream->runtime->private_data;
-
- substream->runtime->private_data = NULL;
- kfree(stream);
-
- return 0;
-}
-EXPORT_SYMBOL_NS(intel_pcm_close, SND_SOC_SOF_INTEL_HIFI_EP_IPC);
-
-MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/sof/intel/lnl.c b/sound/soc/sof/intel/lnl.c
new file mode 100644
index 000000000000..7ae017a00184
--- /dev/null
+++ b/sound/soc/sof/intel/lnl.c
@@ -0,0 +1,210 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// Copyright(c) 2023 Intel Corporation. All rights reserved.
+
+/*
+ * Hardware interface for audio DSP on LunarLake.
+ */
+
+#include <linux/firmware.h>
+#include <sound/hda_register.h>
+#include <sound/sof/ipc4/header.h>
+#include <trace/events/sof_intel.h>
+#include "../ipc4-priv.h"
+#include "../ops.h"
+#include "hda.h"
+#include "hda-ipc.h"
+#include "../sof-audio.h"
+#include "mtl.h"
+#include <sound/hda-mlink.h>
+
+/* LunarLake ops */
+struct snd_sof_dsp_ops sof_lnl_ops;
+EXPORT_SYMBOL_NS(sof_lnl_ops, SND_SOC_SOF_INTEL_HDA_COMMON);
+
+static const struct snd_sof_debugfs_map lnl_dsp_debugfs[] = {
+ {"hda", HDA_DSP_HDA_BAR, 0, 0x4000, SOF_DEBUGFS_ACCESS_ALWAYS},
+ {"pp", HDA_DSP_PP_BAR, 0, 0x1000, SOF_DEBUGFS_ACCESS_ALWAYS},
+ {"dsp", HDA_DSP_BAR, 0, 0x10000, SOF_DEBUGFS_ACCESS_ALWAYS},
+};
+
+/* this helps allows the DSP to setup DMIC/SSP */
+static int hdac_bus_offload_dmic_ssp(struct hdac_bus *bus)
+{
+ int ret;
+
+ ret = hdac_bus_eml_enable_offload(bus, true, AZX_REG_ML_LEPTR_ID_INTEL_SSP, true);
+ if (ret < 0)
+ return ret;
+
+ ret = hdac_bus_eml_enable_offload(bus, true, AZX_REG_ML_LEPTR_ID_INTEL_DMIC, true);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int lnl_hda_dsp_probe(struct snd_sof_dev *sdev)
+{
+ int ret;
+
+ ret = hda_dsp_probe(sdev);
+ if (ret < 0)
+ return ret;
+
+ return hdac_bus_offload_dmic_ssp(sof_to_bus(sdev));
+}
+
+static int lnl_hda_dsp_resume(struct snd_sof_dev *sdev)
+{
+ int ret;
+
+ ret = hda_dsp_resume(sdev);
+ if (ret < 0)
+ return ret;
+
+ return hdac_bus_offload_dmic_ssp(sof_to_bus(sdev));
+}
+
+static int lnl_hda_dsp_runtime_resume(struct snd_sof_dev *sdev)
+{
+ int ret;
+
+ ret = hda_dsp_runtime_resume(sdev);
+ if (ret < 0)
+ return ret;
+
+ return hdac_bus_offload_dmic_ssp(sof_to_bus(sdev));
+}
+
+static int lnl_dsp_post_fw_run(struct snd_sof_dev *sdev)
+{
+ if (sdev->first_boot) {
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+
+ /* Check if IMR boot is usable */
+ if (!sof_debug_check_flag(SOF_DBG_IGNORE_D3_PERSISTENT))
+ hda->imrboot_supported = true;
+ }
+
+ return 0;
+}
+
+int sof_lnl_ops_init(struct snd_sof_dev *sdev)
+{
+ struct sof_ipc4_fw_data *ipc4_data;
+
+ /* common defaults */
+ memcpy(&sof_lnl_ops, &sof_hda_common_ops, sizeof(struct snd_sof_dsp_ops));
+
+ /* probe */
+ if (!sdev->dspless_mode_selected)
+ sof_lnl_ops.probe = lnl_hda_dsp_probe;
+
+ /* shutdown */
+ sof_lnl_ops.shutdown = hda_dsp_shutdown;
+
+ /* doorbell */
+ sof_lnl_ops.irq_thread = mtl_ipc_irq_thread;
+
+ /* ipc */
+ sof_lnl_ops.send_msg = mtl_ipc_send_msg;
+ sof_lnl_ops.get_mailbox_offset = mtl_dsp_ipc_get_mailbox_offset;
+ sof_lnl_ops.get_window_offset = mtl_dsp_ipc_get_window_offset;
+
+ /* debug */
+ sof_lnl_ops.debug_map = lnl_dsp_debugfs;
+ sof_lnl_ops.debug_map_count = ARRAY_SIZE(lnl_dsp_debugfs);
+ sof_lnl_ops.dbg_dump = mtl_dsp_dump;
+ sof_lnl_ops.ipc_dump = mtl_ipc_dump;
+
+ /* pre/post fw run */
+ sof_lnl_ops.pre_fw_run = mtl_dsp_pre_fw_run;
+ sof_lnl_ops.post_fw_run = lnl_dsp_post_fw_run;
+
+ /* parse platform specific extended manifest */
+ sof_lnl_ops.parse_platform_ext_manifest = NULL;
+
+ /* dsp core get/put */
+ /* TODO: add core_get and core_put */
+
+ /* PM */
+ if (!sdev->dspless_mode_selected) {
+ sof_lnl_ops.resume = lnl_hda_dsp_resume;
+ sof_lnl_ops.runtime_resume = lnl_hda_dsp_runtime_resume;
+ }
+
+ sof_lnl_ops.get_stream_position = mtl_dsp_get_stream_hda_link_position;
+
+ /* dsp core get/put */
+ sof_lnl_ops.core_get = mtl_dsp_core_get;
+ sof_lnl_ops.core_put = mtl_dsp_core_put;
+
+ sdev->private = kzalloc(sizeof(struct sof_ipc4_fw_data), GFP_KERNEL);
+ if (!sdev->private)
+ return -ENOMEM;
+
+ ipc4_data = sdev->private;
+ ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET;
+
+ ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_2;
+
+ ipc4_data->fw_context_save = true;
+
+ /* External library loading support */
+ ipc4_data->load_library = hda_dsp_ipc4_load_library;
+
+ /* set DAI ops */
+ hda_set_dai_drv_ops(sdev, &sof_lnl_ops);
+
+ sof_lnl_ops.set_power_state = hda_dsp_set_power_state_ipc4;
+
+ return 0;
+};
+EXPORT_SYMBOL_NS(sof_lnl_ops_init, SND_SOC_SOF_INTEL_HDA_COMMON);
+
+/* Check if an SDW IRQ occurred */
+static bool lnl_dsp_check_sdw_irq(struct snd_sof_dev *sdev)
+{
+ struct hdac_bus *bus = sof_to_bus(sdev);
+
+ return hdac_bus_eml_check_interrupt(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
+}
+
+static void lnl_enable_sdw_irq(struct snd_sof_dev *sdev, bool enable)
+{
+ struct hdac_bus *bus = sof_to_bus(sdev);
+
+ hdac_bus_eml_enable_interrupt(bus, true, AZX_REG_ML_LEPTR_ID_SDW, enable);
+}
+
+static int lnl_dsp_disable_interrupts(struct snd_sof_dev *sdev)
+{
+ lnl_enable_sdw_irq(sdev, false);
+ mtl_disable_ipc_interrupts(sdev);
+ return mtl_enable_interrupts(sdev, false);
+}
+
+const struct sof_intel_dsp_desc lnl_chip_info = {
+ .cores_num = 5,
+ .init_core_mask = BIT(0),
+ .host_managed_cores_mask = BIT(0),
+ .ipc_req = MTL_DSP_REG_HFIPCXIDR,
+ .ipc_req_mask = MTL_DSP_REG_HFIPCXIDR_BUSY,
+ .ipc_ack = MTL_DSP_REG_HFIPCXIDA,
+ .ipc_ack_mask = MTL_DSP_REG_HFIPCXIDA_DONE,
+ .ipc_ctl = MTL_DSP_REG_HFIPCXCTL,
+ .rom_status_reg = MTL_DSP_ROM_STS,
+ .rom_init_timeout = 300,
+ .ssp_count = MTL_SSP_COUNT,
+ .d0i3_offset = MTL_HDA_VS_D0I3C,
+ .read_sdw_lcount = hda_sdw_check_lcount_ext,
+ .enable_sdw_irq = lnl_enable_sdw_irq,
+ .check_sdw_irq = lnl_dsp_check_sdw_irq,
+ .check_ipc_irq = mtl_dsp_check_ipc_irq,
+ .cl_init = mtl_dsp_cl_init,
+ .power_down_dsp = mtl_power_down_dsp,
+ .disable_interrupts = lnl_dsp_disable_interrupts,
+ .hw_ip_version = SOF_INTEL_ACE_2_0,
+};
+EXPORT_SYMBOL_NS(lnl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
diff --git a/sound/soc/sof/intel/mtl.c b/sound/soc/sof/intel/mtl.c
new file mode 100644
index 000000000000..df05dc77b8d5
--- /dev/null
+++ b/sound/soc/sof/intel/mtl.c
@@ -0,0 +1,789 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// Copyright(c) 2022 Intel Corporation. All rights reserved.
+//
+// Authors: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
+//
+
+/*
+ * Hardware interface for audio DSP on Meteorlake.
+ */
+
+#include <linux/firmware.h>
+#include <sound/sof/ipc4/header.h>
+#include <trace/events/sof_intel.h>
+#include "../ipc4-priv.h"
+#include "../ops.h"
+#include "hda.h"
+#include "hda-ipc.h"
+#include "../sof-audio.h"
+#include "mtl.h"
+#include "telemetry.h"
+
+static const struct snd_sof_debugfs_map mtl_dsp_debugfs[] = {
+ {"hda", HDA_DSP_HDA_BAR, 0, 0x4000, SOF_DEBUGFS_ACCESS_ALWAYS},
+ {"pp", HDA_DSP_PP_BAR, 0, 0x1000, SOF_DEBUGFS_ACCESS_ALWAYS},
+ {"dsp", HDA_DSP_BAR, 0, 0x10000, SOF_DEBUGFS_ACCESS_ALWAYS},
+};
+
+static void mtl_ipc_host_done(struct snd_sof_dev *sdev)
+{
+ /*
+ * clear busy interrupt to tell dsp controller this interrupt has been accepted,
+ * not trigger it again
+ */
+ snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXTDR,
+ MTL_DSP_REG_HFIPCXTDR_BUSY, MTL_DSP_REG_HFIPCXTDR_BUSY);
+ /*
+ * clear busy bit to ack dsp the msg has been processed and send reply msg to dsp
+ */
+ snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXTDA,
+ MTL_DSP_REG_HFIPCXTDA_BUSY, 0);
+}
+
+static void mtl_ipc_dsp_done(struct snd_sof_dev *sdev)
+{
+ /*
+ * set DONE bit - tell DSP we have received the reply msg from DSP, and processed it,
+ * don't send more reply to host
+ */
+ snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXIDA,
+ MTL_DSP_REG_HFIPCXIDA_DONE, MTL_DSP_REG_HFIPCXIDA_DONE);
+
+ /* unmask Done interrupt */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXCTL,
+ MTL_DSP_REG_HFIPCXCTL_DONE, MTL_DSP_REG_HFIPCXCTL_DONE);
+}
+
+/* Check if an IPC IRQ occurred */
+bool mtl_dsp_check_ipc_irq(struct snd_sof_dev *sdev)
+{
+ u32 irq_status;
+ u32 hfintipptr;
+
+ if (sdev->dspless_mode_selected)
+ return false;
+
+ /* read Interrupt IP Pointer */
+ hfintipptr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFINTIPPTR) & MTL_HFINTIPPTR_PTR_MASK;
+ irq_status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, hfintipptr + MTL_DSP_IRQSTS);
+
+ trace_sof_intel_hda_irq_ipc_check(sdev, irq_status);
+
+ if (irq_status != U32_MAX && (irq_status & MTL_DSP_IRQSTS_IPC))
+ return true;
+
+ return false;
+}
+
+/* Check if an SDW IRQ occurred */
+static bool mtl_dsp_check_sdw_irq(struct snd_sof_dev *sdev)
+{
+ u32 irq_status;
+ u32 hfintipptr;
+
+ /* read Interrupt IP Pointer */
+ hfintipptr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFINTIPPTR) & MTL_HFINTIPPTR_PTR_MASK;
+ irq_status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, hfintipptr + MTL_DSP_IRQSTS);
+
+ if (irq_status != U32_MAX && (irq_status & MTL_DSP_IRQSTS_SDW))
+ return true;
+
+ return false;
+}
+
+int mtl_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
+{
+ struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
+ struct sof_ipc4_msg *msg_data = msg->msg_data;
+
+ if (hda_ipc4_tx_is_busy(sdev)) {
+ hdev->delayed_ipc_tx_msg = msg;
+ return 0;
+ }
+
+ hdev->delayed_ipc_tx_msg = NULL;
+
+ /* send the message via mailbox */
+ if (msg_data->data_size)
+ sof_mailbox_write(sdev, sdev->host_box.offset, msg_data->data_ptr,
+ msg_data->data_size);
+
+ snd_sof_dsp_write(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXIDDY,
+ msg_data->extension);
+ snd_sof_dsp_write(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXIDR,
+ msg_data->primary | MTL_DSP_REG_HFIPCXIDR_BUSY);
+
+ hda_dsp_ipc4_schedule_d0i3_work(hdev, msg);
+
+ return 0;
+}
+
+void mtl_enable_ipc_interrupts(struct snd_sof_dev *sdev)
+{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+ const struct sof_intel_dsp_desc *chip = hda->desc;
+
+ if (sdev->dspless_mode_selected)
+ return;
+
+ /* enable IPC DONE and BUSY interrupts */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, chip->ipc_ctl,
+ MTL_DSP_REG_HFIPCXCTL_BUSY | MTL_DSP_REG_HFIPCXCTL_DONE,
+ MTL_DSP_REG_HFIPCXCTL_BUSY | MTL_DSP_REG_HFIPCXCTL_DONE);
+}
+
+void mtl_disable_ipc_interrupts(struct snd_sof_dev *sdev)
+{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+ const struct sof_intel_dsp_desc *chip = hda->desc;
+
+ if (sdev->dspless_mode_selected)
+ return;
+
+ /* disable IPC DONE and BUSY interrupts */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, chip->ipc_ctl,
+ MTL_DSP_REG_HFIPCXCTL_BUSY | MTL_DSP_REG_HFIPCXCTL_DONE, 0);
+}
+
+static void mtl_enable_sdw_irq(struct snd_sof_dev *sdev, bool enable)
+{
+ u32 hipcie;
+ u32 mask;
+ u32 val;
+ int ret;
+
+ if (sdev->dspless_mode_selected)
+ return;
+
+ /* Enable/Disable SoundWire interrupt */
+ mask = MTL_DSP_REG_HfSNDWIE_IE_MASK;
+ if (enable)
+ val = mask;
+ else
+ val = 0;
+
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfSNDWIE, mask, val);
+
+ /* check if operation was successful */
+ ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfSNDWIE, hipcie,
+ (hipcie & mask) == val,
+ HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_RESET_TIMEOUT_US);
+ if (ret < 0)
+ dev_err(sdev->dev, "failed to set SoundWire IPC interrupt %s\n",
+ enable ? "enable" : "disable");
+}
+
+int mtl_enable_interrupts(struct snd_sof_dev *sdev, bool enable)
+{
+ u32 hfintipptr;
+ u32 irqinten;
+ u32 hipcie;
+ u32 mask;
+ u32 val;
+ int ret;
+
+ if (sdev->dspless_mode_selected)
+ return 0;
+
+ /* read Interrupt IP Pointer */
+ hfintipptr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFINTIPPTR) & MTL_HFINTIPPTR_PTR_MASK;
+
+ /* Enable/Disable Host IPC and SOUNDWIRE */
+ mask = MTL_IRQ_INTEN_L_HOST_IPC_MASK | MTL_IRQ_INTEN_L_SOUNDWIRE_MASK;
+ if (enable)
+ val = mask;
+ else
+ val = 0;
+
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, hfintipptr, mask, val);
+
+ /* check if operation was successful */
+ ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, hfintipptr, irqinten,
+ (irqinten & mask) == val,
+ HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_RESET_TIMEOUT_US);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to %s Host IPC and/or SOUNDWIRE\n",
+ enable ? "enable" : "disable");
+ return ret;
+ }
+
+ /* Enable/Disable Host IPC interrupt*/
+ mask = MTL_DSP_REG_HfHIPCIE_IE_MASK;
+ if (enable)
+ val = mask;
+ else
+ val = 0;
+
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfHIPCIE, mask, val);
+
+ /* check if operation was successful */
+ ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfHIPCIE, hipcie,
+ (hipcie & mask) == val,
+ HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_RESET_TIMEOUT_US);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to set Host IPC interrupt %s\n",
+ enable ? "enable" : "disable");
+ return ret;
+ }
+
+ return ret;
+}
+
+/* pre fw run operations */
+int mtl_dsp_pre_fw_run(struct snd_sof_dev *sdev)
+{
+ struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
+ u32 dsphfpwrsts;
+ u32 dsphfdsscs;
+ u32 cpa;
+ u32 pgs;
+ int ret;
+
+ /* Set the DSP subsystem power on */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_HFDSSCS,
+ MTL_HFDSSCS_SPA_MASK, MTL_HFDSSCS_SPA_MASK);
+
+ /* Wait for unstable CPA read (1 then 0 then 1) just after setting SPA bit */
+ usleep_range(1000, 1010);
+
+ /* poll with timeout to check if operation successful */
+ cpa = MTL_HFDSSCS_CPA_MASK;
+ ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_HFDSSCS, dsphfdsscs,
+ (dsphfdsscs & cpa) == cpa, HDA_DSP_REG_POLL_INTERVAL_US,
+ HDA_DSP_RESET_TIMEOUT_US);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to enable DSP subsystem\n");
+ return ret;
+ }
+
+ /* Power up gated-DSP-0 domain in order to access the DSP shim register block. */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_HFPWRCTL,
+ MTL_HFPWRCTL_WPDSPHPXPG, MTL_HFPWRCTL_WPDSPHPXPG);
+
+ usleep_range(1000, 1010);
+
+ /* poll with timeout to check if operation successful */
+ pgs = MTL_HFPWRSTS_DSPHPXPGS_MASK;
+ ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_HFPWRSTS, dsphfpwrsts,
+ (dsphfpwrsts & pgs) == pgs,
+ HDA_DSP_REG_POLL_INTERVAL_US,
+ HDA_DSP_RESET_TIMEOUT_US);
+ if (ret < 0)
+ dev_err(sdev->dev, "failed to power up gated DSP domain\n");
+
+ /* if SoundWire is used, make sure it is not power-gated */
+ if (hdev->info.handle && hdev->info.link_mask > 0)
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_HFPWRCTL,
+ MTL_HfPWRCTL_WPIOXPG(1), MTL_HfPWRCTL_WPIOXPG(1));
+
+ return ret;
+}
+
+int mtl_dsp_post_fw_run(struct snd_sof_dev *sdev)
+{
+ int ret;
+
+ if (sdev->first_boot) {
+ struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
+
+ ret = hda_sdw_startup(sdev);
+ if (ret < 0) {
+ dev_err(sdev->dev, "could not startup SoundWire links\n");
+ return ret;
+ }
+
+ /* Check if IMR boot is usable */
+ if (!sof_debug_check_flag(SOF_DBG_IGNORE_D3_PERSISTENT))
+ hdev->imrboot_supported = true;
+ }
+
+ hda_sdw_int_enable(sdev, true);
+ return 0;
+}
+
+void mtl_dsp_dump(struct snd_sof_dev *sdev, u32 flags)
+{
+ char *level = (flags & SOF_DBG_DUMP_OPTIONAL) ? KERN_DEBUG : KERN_ERR;
+ u32 romdbgsts;
+ u32 romdbgerr;
+ u32 fwsts;
+ u32 fwlec;
+
+ fwsts = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_ROM_STS);
+ fwlec = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_ROM_ERROR);
+ romdbgsts = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFFLGPXQWY);
+ romdbgerr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFFLGPXQWY_ERROR);
+
+ dev_err(sdev->dev, "ROM status: %#x, ROM error: %#x\n", fwsts, fwlec);
+ dev_err(sdev->dev, "ROM debug status: %#x, ROM debug error: %#x\n", romdbgsts,
+ romdbgerr);
+ romdbgsts = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFFLGPXQWY + 0x8 * 3);
+ dev_printk(level, sdev->dev, "ROM feature bit%s enabled\n",
+ romdbgsts & BIT(24) ? "" : " not");
+
+ sof_ipc4_intel_dump_telemetry_state(sdev, flags);
+}
+
+static bool mtl_dsp_primary_core_is_enabled(struct snd_sof_dev *sdev)
+{
+ int val;
+
+ val = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP2CXCTL_PRIMARY_CORE);
+ if (val != U32_MAX && val & MTL_DSP2CXCTL_PRIMARY_CORE_CPA_MASK)
+ return true;
+
+ return false;
+}
+
+static int mtl_dsp_core_power_up(struct snd_sof_dev *sdev, int core)
+{
+ unsigned int cpa;
+ u32 dspcxctl;
+ int ret;
+
+ /* Only the primary core can be powered up by the host */
+ if (core != SOF_DSP_PRIMARY_CORE || mtl_dsp_primary_core_is_enabled(sdev))
+ return 0;
+
+ /* Program the owner of the IP & shim registers (10: Host CPU) */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP2CXCTL_PRIMARY_CORE,
+ MTL_DSP2CXCTL_PRIMARY_CORE_OSEL,
+ 0x2 << MTL_DSP2CXCTL_PRIMARY_CORE_OSEL_SHIFT);
+
+ /* enable SPA bit */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP2CXCTL_PRIMARY_CORE,
+ MTL_DSP2CXCTL_PRIMARY_CORE_SPA_MASK,
+ MTL_DSP2CXCTL_PRIMARY_CORE_SPA_MASK);
+
+ /* Wait for unstable CPA read (1 then 0 then 1) just after setting SPA bit */
+ usleep_range(1000, 1010);
+
+ /* poll with timeout to check if operation successful */
+ cpa = MTL_DSP2CXCTL_PRIMARY_CORE_CPA_MASK;
+ ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_DSP2CXCTL_PRIMARY_CORE, dspcxctl,
+ (dspcxctl & cpa) == cpa, HDA_DSP_REG_POLL_INTERVAL_US,
+ HDA_DSP_RESET_TIMEOUT_US);
+ if (ret < 0) {
+ dev_err(sdev->dev, "%s: timeout on MTL_DSP2CXCTL_PRIMARY_CORE read\n",
+ __func__);
+ return ret;
+ }
+
+ /* set primary core mask and refcount to 1 */
+ sdev->enabled_cores_mask = BIT(SOF_DSP_PRIMARY_CORE);
+ sdev->dsp_core_ref_count[SOF_DSP_PRIMARY_CORE] = 1;
+
+ return 0;
+}
+
+static int mtl_dsp_core_power_down(struct snd_sof_dev *sdev, int core)
+{
+ u32 dspcxctl;
+ int ret;
+
+ /* Only the primary core can be powered down by the host */
+ if (core != SOF_DSP_PRIMARY_CORE || !mtl_dsp_primary_core_is_enabled(sdev))
+ return 0;
+
+ /* disable SPA bit */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP2CXCTL_PRIMARY_CORE,
+ MTL_DSP2CXCTL_PRIMARY_CORE_SPA_MASK, 0);
+
+ /* Wait for unstable CPA read (0 then 1 then 0) just after setting SPA bit */
+ usleep_range(1000, 1010);
+
+ ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_DSP2CXCTL_PRIMARY_CORE, dspcxctl,
+ !(dspcxctl & MTL_DSP2CXCTL_PRIMARY_CORE_CPA_MASK),
+ HDA_DSP_REG_POLL_INTERVAL_US,
+ HDA_DSP_PD_TIMEOUT * USEC_PER_MSEC);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to power down primary core\n");
+ return ret;
+ }
+
+ sdev->enabled_cores_mask = 0;
+ sdev->dsp_core_ref_count[SOF_DSP_PRIMARY_CORE] = 0;
+
+ return 0;
+}
+
+int mtl_power_down_dsp(struct snd_sof_dev *sdev)
+{
+ u32 dsphfdsscs, cpa;
+ int ret;
+
+ /* first power down core */
+ ret = mtl_dsp_core_power_down(sdev, SOF_DSP_PRIMARY_CORE);
+ if (ret) {
+ dev_err(sdev->dev, "mtl dsp power down error, %d\n", ret);
+ return ret;
+ }
+
+ /* Set the DSP subsystem power down */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_HFDSSCS,
+ MTL_HFDSSCS_SPA_MASK, 0);
+
+ /* Wait for unstable CPA read (0 then 1 then 0) just after setting SPA bit */
+ usleep_range(1000, 1010);
+
+ /* poll with timeout to check if operation successful */
+ cpa = MTL_HFDSSCS_CPA_MASK;
+ dsphfdsscs = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFDSSCS);
+ return snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_HFDSSCS, dsphfdsscs,
+ (dsphfdsscs & cpa) == 0, HDA_DSP_REG_POLL_INTERVAL_US,
+ HDA_DSP_RESET_TIMEOUT_US);
+}
+
+int mtl_dsp_cl_init(struct snd_sof_dev *sdev, int stream_tag, bool imr_boot)
+{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+ const struct sof_intel_dsp_desc *chip = hda->desc;
+ unsigned int status;
+ u32 ipc_hdr, flags;
+ char *dump_msg;
+ int ret;
+
+ /* step 1: purge FW request */
+ ipc_hdr = chip->ipc_req_mask | HDA_DSP_ROM_IPC_CONTROL;
+ if (!imr_boot)
+ ipc_hdr |= HDA_DSP_ROM_IPC_PURGE_FW | ((stream_tag - 1) << 9);
+
+ snd_sof_dsp_write(sdev, HDA_DSP_BAR, chip->ipc_req, ipc_hdr);
+
+ /* step 2: power up primary core */
+ ret = mtl_dsp_core_power_up(sdev, SOF_DSP_PRIMARY_CORE);
+ if (ret < 0) {
+ if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS)
+ dev_err(sdev->dev, "dsp core 0/1 power up failed\n");
+ goto err;
+ }
+
+ dev_dbg(sdev->dev, "Primary core power up successful\n");
+
+ /* step 3: wait for IPC DONE bit from ROM */
+ ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, chip->ipc_ack, status,
+ ((status & chip->ipc_ack_mask) == chip->ipc_ack_mask),
+ HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_INIT_TIMEOUT_US);
+ if (ret < 0) {
+ if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS)
+ dev_err(sdev->dev, "timeout waiting for purge IPC done\n");
+ goto err;
+ }
+
+ /* set DONE bit to clear the reply IPC message */
+ snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, chip->ipc_ack, chip->ipc_ack_mask,
+ chip->ipc_ack_mask);
+
+ /* step 4: enable interrupts */
+ ret = mtl_enable_interrupts(sdev, true);
+ if (ret < 0) {
+ if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS)
+ dev_err(sdev->dev, "%s: failed to enable interrupts\n", __func__);
+ goto err;
+ }
+
+ mtl_enable_ipc_interrupts(sdev);
+
+ /*
+ * ACE workaround: don't wait for ROM INIT.
+ * The platform cannot catch ROM_INIT_DONE because of a very short
+ * timing window. Follow the recommendations and skip this part.
+ */
+
+ return 0;
+
+err:
+ flags = SOF_DBG_DUMP_PCI | SOF_DBG_DUMP_MBOX | SOF_DBG_DUMP_OPTIONAL;
+
+ /* after max boot attempts make sure that the dump is printed */
+ if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS)
+ flags &= ~SOF_DBG_DUMP_OPTIONAL;
+
+ dump_msg = kasprintf(GFP_KERNEL, "Boot iteration failed: %d/%d",
+ hda->boot_iteration, HDA_FW_BOOT_ATTEMPTS);
+ snd_sof_dsp_dbg_dump(sdev, dump_msg, flags);
+ mtl_dsp_core_power_down(sdev, SOF_DSP_PRIMARY_CORE);
+
+ kfree(dump_msg);
+ return ret;
+}
+
+irqreturn_t mtl_ipc_irq_thread(int irq, void *context)
+{
+ struct sof_ipc4_msg notification_data = {{ 0 }};
+ struct snd_sof_dev *sdev = context;
+ bool ack_received = false;
+ bool ipc_irq = false;
+ u32 hipcida;
+ u32 hipctdr;
+
+ hipcida = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXIDA);
+ hipctdr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXTDR);
+
+ /* reply message from DSP */
+ if (hipcida & MTL_DSP_REG_HFIPCXIDA_DONE) {
+ /* DSP received the message */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXCTL,
+ MTL_DSP_REG_HFIPCXCTL_DONE, 0);
+
+ mtl_ipc_dsp_done(sdev);
+
+ ipc_irq = true;
+ ack_received = true;
+ }
+
+ if (hipctdr & MTL_DSP_REG_HFIPCXTDR_BUSY) {
+ /* Message from DSP (reply or notification) */
+ u32 extension = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXTDDY);
+ u32 primary = hipctdr & MTL_DSP_REG_HFIPCXTDR_MSG_MASK;
+
+ /*
+ * ACE fw sends a new fw ipc message to host to
+ * notify the status of the last host ipc message
+ */
+ if (primary & SOF_IPC4_MSG_DIR_MASK) {
+ /* Reply received */
+ if (likely(sdev->fw_state == SOF_FW_BOOT_COMPLETE)) {
+ struct sof_ipc4_msg *data = sdev->ipc->msg.reply_data;
+
+ data->primary = primary;
+ data->extension = extension;
+
+ spin_lock_irq(&sdev->ipc_lock);
+
+ snd_sof_ipc_get_reply(sdev);
+ mtl_ipc_host_done(sdev);
+ snd_sof_ipc_reply(sdev, data->primary);
+
+ spin_unlock_irq(&sdev->ipc_lock);
+ } else {
+ dev_dbg_ratelimited(sdev->dev,
+ "IPC reply before FW_READY: %#x|%#x\n",
+ primary, extension);
+ }
+ } else {
+ /* Notification received */
+ notification_data.primary = primary;
+ notification_data.extension = extension;
+
+ sdev->ipc->msg.rx_data = &notification_data;
+ snd_sof_ipc_msgs_rx(sdev);
+ sdev->ipc->msg.rx_data = NULL;
+
+ mtl_ipc_host_done(sdev);
+ }
+
+ ipc_irq = true;
+ }
+
+ if (!ipc_irq) {
+ /* This interrupt is not shared so no need to return IRQ_NONE. */
+ dev_dbg_ratelimited(sdev->dev, "nothing to do in IPC IRQ thread\n");
+ }
+
+ if (ack_received) {
+ struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
+
+ if (hdev->delayed_ipc_tx_msg)
+ mtl_ipc_send_msg(sdev, hdev->delayed_ipc_tx_msg);
+ }
+
+ return IRQ_HANDLED;
+}
+
+int mtl_dsp_ipc_get_mailbox_offset(struct snd_sof_dev *sdev)
+{
+ return MTL_DSP_MBOX_UPLINK_OFFSET;
+}
+
+int mtl_dsp_ipc_get_window_offset(struct snd_sof_dev *sdev, u32 id)
+{
+ return MTL_SRAM_WINDOW_OFFSET(id);
+}
+
+void mtl_ipc_dump(struct snd_sof_dev *sdev)
+{
+ u32 hipcidr, hipcidd, hipcida, hipctdr, hipctdd, hipctda, hipcctl;
+
+ hipcidr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXIDR);
+ hipcidd = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXIDDY);
+ hipcida = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXIDA);
+ hipctdr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXTDR);
+ hipctdd = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXTDDY);
+ hipctda = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXTDA);
+ hipcctl = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXCTL);
+
+ dev_err(sdev->dev,
+ "Host IPC initiator: %#x|%#x|%#x, target: %#x|%#x|%#x, ctl: %#x\n",
+ hipcidr, hipcidd, hipcida, hipctdr, hipctdd, hipctda, hipcctl);
+}
+
+static int mtl_dsp_disable_interrupts(struct snd_sof_dev *sdev)
+{
+ mtl_enable_sdw_irq(sdev, false);
+ mtl_disable_ipc_interrupts(sdev);
+ return mtl_enable_interrupts(sdev, false);
+}
+
+u64 mtl_dsp_get_stream_hda_link_position(struct snd_sof_dev *sdev,
+ struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct hdac_stream *hstream = substream->runtime->private_data;
+ u32 llp_l, llp_u;
+
+ llp_l = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, MTL_PPLCLLPL(hstream->index));
+ llp_u = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, MTL_PPLCLLPU(hstream->index));
+ return ((u64)llp_u << 32) | llp_l;
+}
+
+int mtl_dsp_core_get(struct snd_sof_dev *sdev, int core)
+{
+ const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm;
+
+ if (core == SOF_DSP_PRIMARY_CORE)
+ return mtl_dsp_core_power_up(sdev, SOF_DSP_PRIMARY_CORE);
+
+ if (pm_ops->set_core_state)
+ return pm_ops->set_core_state(sdev, core, true);
+
+ return 0;
+}
+
+int mtl_dsp_core_put(struct snd_sof_dev *sdev, int core)
+{
+ const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm;
+ int ret;
+
+ if (pm_ops->set_core_state) {
+ ret = pm_ops->set_core_state(sdev, core, false);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (core == SOF_DSP_PRIMARY_CORE)
+ return mtl_dsp_core_power_down(sdev, SOF_DSP_PRIMARY_CORE);
+
+ return 0;
+}
+
+/* Meteorlake ops */
+struct snd_sof_dsp_ops sof_mtl_ops;
+EXPORT_SYMBOL_NS(sof_mtl_ops, SND_SOC_SOF_INTEL_HDA_COMMON);
+
+int sof_mtl_ops_init(struct snd_sof_dev *sdev)
+{
+ struct sof_ipc4_fw_data *ipc4_data;
+
+ /* common defaults */
+ memcpy(&sof_mtl_ops, &sof_hda_common_ops, sizeof(struct snd_sof_dsp_ops));
+
+ /* shutdown */
+ sof_mtl_ops.shutdown = hda_dsp_shutdown;
+
+ /* doorbell */
+ sof_mtl_ops.irq_thread = mtl_ipc_irq_thread;
+
+ /* ipc */
+ sof_mtl_ops.send_msg = mtl_ipc_send_msg;
+ sof_mtl_ops.get_mailbox_offset = mtl_dsp_ipc_get_mailbox_offset;
+ sof_mtl_ops.get_window_offset = mtl_dsp_ipc_get_window_offset;
+
+ /* debug */
+ sof_mtl_ops.debug_map = mtl_dsp_debugfs;
+ sof_mtl_ops.debug_map_count = ARRAY_SIZE(mtl_dsp_debugfs);
+ sof_mtl_ops.dbg_dump = mtl_dsp_dump;
+ sof_mtl_ops.ipc_dump = mtl_ipc_dump;
+
+ /* pre/post fw run */
+ sof_mtl_ops.pre_fw_run = mtl_dsp_pre_fw_run;
+ sof_mtl_ops.post_fw_run = mtl_dsp_post_fw_run;
+
+ /* parse platform specific extended manifest */
+ sof_mtl_ops.parse_platform_ext_manifest = NULL;
+
+ /* dsp core get/put */
+ sof_mtl_ops.core_get = mtl_dsp_core_get;
+ sof_mtl_ops.core_put = mtl_dsp_core_put;
+
+ sof_mtl_ops.get_stream_position = mtl_dsp_get_stream_hda_link_position;
+
+ sdev->private = kzalloc(sizeof(struct sof_ipc4_fw_data), GFP_KERNEL);
+ if (!sdev->private)
+ return -ENOMEM;
+
+ ipc4_data = sdev->private;
+ ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET;
+
+ ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_2;
+
+ ipc4_data->fw_context_save = true;
+
+ /* External library loading support */
+ ipc4_data->load_library = hda_dsp_ipc4_load_library;
+
+ /* set DAI ops */
+ hda_set_dai_drv_ops(sdev, &sof_mtl_ops);
+
+ sof_mtl_ops.set_power_state = hda_dsp_set_power_state_ipc4;
+
+ return 0;
+};
+EXPORT_SYMBOL_NS(sof_mtl_ops_init, SND_SOC_SOF_INTEL_HDA_COMMON);
+
+const struct sof_intel_dsp_desc mtl_chip_info = {
+ .cores_num = 3,
+ .init_core_mask = BIT(0),
+ .host_managed_cores_mask = BIT(0),
+ .ipc_req = MTL_DSP_REG_HFIPCXIDR,
+ .ipc_req_mask = MTL_DSP_REG_HFIPCXIDR_BUSY,
+ .ipc_ack = MTL_DSP_REG_HFIPCXIDA,
+ .ipc_ack_mask = MTL_DSP_REG_HFIPCXIDA_DONE,
+ .ipc_ctl = MTL_DSP_REG_HFIPCXCTL,
+ .rom_status_reg = MTL_DSP_ROM_STS,
+ .rom_init_timeout = 300,
+ .ssp_count = MTL_SSP_COUNT,
+ .ssp_base_offset = CNL_SSP_BASE_OFFSET,
+ .sdw_shim_base = SDW_SHIM_BASE_ACE,
+ .sdw_alh_base = SDW_ALH_BASE_ACE,
+ .d0i3_offset = MTL_HDA_VS_D0I3C,
+ .read_sdw_lcount = hda_sdw_check_lcount_common,
+ .enable_sdw_irq = mtl_enable_sdw_irq,
+ .check_sdw_irq = mtl_dsp_check_sdw_irq,
+ .check_sdw_wakeen_irq = hda_sdw_check_wakeen_irq_common,
+ .check_ipc_irq = mtl_dsp_check_ipc_irq,
+ .cl_init = mtl_dsp_cl_init,
+ .power_down_dsp = mtl_power_down_dsp,
+ .disable_interrupts = mtl_dsp_disable_interrupts,
+ .hw_ip_version = SOF_INTEL_ACE_1_0,
+};
+EXPORT_SYMBOL_NS(mtl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
+
+const struct sof_intel_dsp_desc arl_s_chip_info = {
+ .cores_num = 2,
+ .init_core_mask = BIT(0),
+ .host_managed_cores_mask = BIT(0),
+ .ipc_req = MTL_DSP_REG_HFIPCXIDR,
+ .ipc_req_mask = MTL_DSP_REG_HFIPCXIDR_BUSY,
+ .ipc_ack = MTL_DSP_REG_HFIPCXIDA,
+ .ipc_ack_mask = MTL_DSP_REG_HFIPCXIDA_DONE,
+ .ipc_ctl = MTL_DSP_REG_HFIPCXCTL,
+ .rom_status_reg = MTL_DSP_ROM_STS,
+ .rom_init_timeout = 300,
+ .ssp_count = MTL_SSP_COUNT,
+ .ssp_base_offset = CNL_SSP_BASE_OFFSET,
+ .sdw_shim_base = SDW_SHIM_BASE_ACE,
+ .sdw_alh_base = SDW_ALH_BASE_ACE,
+ .d0i3_offset = MTL_HDA_VS_D0I3C,
+ .read_sdw_lcount = hda_sdw_check_lcount_common,
+ .enable_sdw_irq = mtl_enable_sdw_irq,
+ .check_sdw_irq = mtl_dsp_check_sdw_irq,
+ .check_sdw_wakeen_irq = hda_sdw_check_wakeen_irq_common,
+ .check_ipc_irq = mtl_dsp_check_ipc_irq,
+ .cl_init = mtl_dsp_cl_init,
+ .power_down_dsp = mtl_power_down_dsp,
+ .disable_interrupts = mtl_dsp_disable_interrupts,
+ .hw_ip_version = SOF_INTEL_ACE_1_0,
+};
+EXPORT_SYMBOL_NS(arl_s_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
diff --git a/sound/soc/sof/intel/mtl.h b/sound/soc/sof/intel/mtl.h
new file mode 100644
index 000000000000..cc5a1f46fd09
--- /dev/null
+++ b/sound/soc/sof/intel/mtl.h
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+/*
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * Copyright(c) 2020-2022 Intel Corporation. All rights reserved.
+ */
+
+/* HDA Registers */
+#define MTL_PPLCLLPL_BASE 0x948
+#define MTL_PPLCLLPU_STRIDE 0x10
+#define MTL_PPLCLLPL(x) (MTL_PPLCLLPL_BASE + (x) * MTL_PPLCLLPU_STRIDE)
+#define MTL_PPLCLLPU(x) (MTL_PPLCLLPL_BASE + 0x4 + (x) * MTL_PPLCLLPU_STRIDE)
+
+/* DSP Registers */
+#define MTL_HFDSSCS 0x1000
+#define MTL_HFDSSCS_SPA_MASK BIT(16)
+#define MTL_HFDSSCS_CPA_MASK BIT(24)
+#define MTL_HFSNDWIE 0x114C
+#define MTL_HFPWRCTL 0x1D18
+#define MTL_HfPWRCTL_WPIOXPG(x) BIT((x) + 8)
+#define MTL_HFPWRCTL_WPDSPHPXPG BIT(0)
+#define MTL_HFPWRSTS 0x1D1C
+#define MTL_HFPWRSTS_DSPHPXPGS_MASK BIT(0)
+#define MTL_HFINTIPPTR 0x1108
+#define MTL_IRQ_INTEN_L_HOST_IPC_MASK BIT(0)
+#define MTL_IRQ_INTEN_L_SOUNDWIRE_MASK BIT(6)
+#define MTL_HFINTIPPTR_PTR_MASK GENMASK(20, 0)
+
+#define MTL_HDA_VS_D0I3C 0x1D4A
+
+#define MTL_DSP2CXCAP_PRIMARY_CORE 0x178D00
+#define MTL_DSP2CXCTL_PRIMARY_CORE 0x178D04
+#define MTL_DSP2CXCTL_PRIMARY_CORE_SPA_MASK BIT(0)
+#define MTL_DSP2CXCTL_PRIMARY_CORE_CPA_MASK BIT(8)
+#define MTL_DSP2CXCTL_PRIMARY_CORE_OSEL GENMASK(25, 24)
+#define MTL_DSP2CXCTL_PRIMARY_CORE_OSEL_SHIFT 24
+
+/* IPC Registers */
+#define MTL_DSP_REG_HFIPCXTDR 0x73200
+#define MTL_DSP_REG_HFIPCXTDR_BUSY BIT(31)
+#define MTL_DSP_REG_HFIPCXTDR_MSG_MASK GENMASK(30, 0)
+#define MTL_DSP_REG_HFIPCXTDA 0x73204
+#define MTL_DSP_REG_HFIPCXTDA_BUSY BIT(31)
+#define MTL_DSP_REG_HFIPCXIDR 0x73210
+#define MTL_DSP_REG_HFIPCXIDR_BUSY BIT(31)
+#define MTL_DSP_REG_HFIPCXIDR_MSG_MASK GENMASK(30, 0)
+#define MTL_DSP_REG_HFIPCXIDA 0x73214
+#define MTL_DSP_REG_HFIPCXIDA_DONE BIT(31)
+#define MTL_DSP_REG_HFIPCXIDA_MSG_MASK GENMASK(30, 0)
+#define MTL_DSP_REG_HFIPCXCTL 0x73228
+#define MTL_DSP_REG_HFIPCXCTL_BUSY BIT(0)
+#define MTL_DSP_REG_HFIPCXCTL_DONE BIT(1)
+#define MTL_DSP_REG_HFIPCXTDDY 0x73300
+#define MTL_DSP_REG_HFIPCXIDDY 0x73380
+#define MTL_DSP_REG_HfHIPCIE 0x1140
+#define MTL_DSP_REG_HfHIPCIE_IE_MASK BIT(0)
+#define MTL_DSP_REG_HfSNDWIE 0x114C
+#define MTL_DSP_REG_HfSNDWIE_IE_MASK GENMASK(3, 0)
+
+#define MTL_DSP_IRQSTS 0x20
+#define MTL_DSP_IRQSTS_IPC BIT(0)
+#define MTL_DSP_IRQSTS_SDW BIT(6)
+
+#define MTL_DSP_REG_POLL_INTERVAL_US 10 /* 10 us */
+
+/* Memory windows */
+#define MTL_SRAM_WINDOW_OFFSET(x) (0x180000 + 0x8000 * (x))
+
+#define MTL_DSP_MBOX_UPLINK_OFFSET (MTL_SRAM_WINDOW_OFFSET(0) + 0x1000)
+#define MTL_DSP_MBOX_UPLINK_SIZE 0x1000
+#define MTL_DSP_MBOX_DOWNLINK_OFFSET MTL_SRAM_WINDOW_OFFSET(1)
+#define MTL_DSP_MBOX_DOWNLINK_SIZE 0x1000
+
+/* FW registers */
+#define MTL_DSP_ROM_STS MTL_SRAM_WINDOW_OFFSET(0) /* ROM status */
+#define MTL_DSP_ROM_ERROR (MTL_SRAM_WINDOW_OFFSET(0) + 0x4) /* ROM error code */
+
+#define MTL_DSP_REG_HFFLGPXQWY 0x163200 /* ROM debug status */
+#define MTL_DSP_REG_HFFLGPXQWY_ERROR 0x163204 /* ROM debug error code */
+#define MTL_DSP_REG_HfIMRIS1 0x162088
+#define MTL_DSP_REG_HfIMRIS1_IU_MASK BIT(0)
+
+bool mtl_dsp_check_ipc_irq(struct snd_sof_dev *sdev);
+int mtl_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg);
+
+void mtl_enable_ipc_interrupts(struct snd_sof_dev *sdev);
+void mtl_disable_ipc_interrupts(struct snd_sof_dev *sdev);
+
+int mtl_enable_interrupts(struct snd_sof_dev *sdev, bool enable);
+
+int mtl_dsp_pre_fw_run(struct snd_sof_dev *sdev);
+int mtl_dsp_post_fw_run(struct snd_sof_dev *sdev);
+void mtl_dsp_dump(struct snd_sof_dev *sdev, u32 flags);
+
+int mtl_power_down_dsp(struct snd_sof_dev *sdev);
+int mtl_dsp_cl_init(struct snd_sof_dev *sdev, int stream_tag, bool imr_boot);
+
+irqreturn_t mtl_ipc_irq_thread(int irq, void *context);
+
+int mtl_dsp_ipc_get_mailbox_offset(struct snd_sof_dev *sdev);
+int mtl_dsp_ipc_get_window_offset(struct snd_sof_dev *sdev, u32 id);
+
+void mtl_ipc_dump(struct snd_sof_dev *sdev);
+
+u64 mtl_dsp_get_stream_hda_link_position(struct snd_sof_dev *sdev,
+ struct snd_soc_component *component,
+ struct snd_pcm_substream *substream);
+
+int mtl_dsp_core_get(struct snd_sof_dev *sdev, int core);
+int mtl_dsp_core_put(struct snd_sof_dev *sdev, int core);
diff --git a/sound/soc/sof/intel/pci-apl.c b/sound/soc/sof/intel/pci-apl.c
new file mode 100644
index 000000000000..4b287b5e9077
--- /dev/null
+++ b/sound/soc/sof/intel/pci-apl.c
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2018-2021 Intel Corporation. All rights reserved.
+//
+// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
+//
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+#include <sound/sof.h>
+#include "../ops.h"
+#include "../sof-pci-dev.h"
+
+/* platform specific devices */
+#include "hda.h"
+
+static const struct sof_dev_desc bxt_desc = {
+ .machines = snd_soc_acpi_intel_bxt_machines,
+ .use_acpi_target_states = true,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = -1,
+ .resindex_imr_base = -1,
+ .irqindex_host_ipc = -1,
+ .chip_info = &apl_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof",
+ [SOF_IPC_TYPE_4] = "intel/avs/apl",
+ },
+ .default_lib_path = {
+ [SOF_IPC_TYPE_4] = "intel/avs-lib/apl",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof-tplg",
+ [SOF_IPC_TYPE_4] = "intel/avs-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-apl.ri",
+ [SOF_IPC_TYPE_4] = "dsp_basefw.bin",
+ },
+ .nocodec_tplg_filename = "sof-apl-nocodec.tplg",
+ .ops = &sof_apl_ops,
+ .ops_init = sof_apl_ops_init,
+ .ops_free = hda_ops_free,
+};
+
+static const struct sof_dev_desc glk_desc = {
+ .machines = snd_soc_acpi_intel_glk_machines,
+ .use_acpi_target_states = true,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = -1,
+ .resindex_imr_base = -1,
+ .irqindex_host_ipc = -1,
+ .chip_info = &apl_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof",
+ [SOF_IPC_TYPE_4] = "intel/avs/glk",
+ },
+ .default_lib_path = {
+ [SOF_IPC_TYPE_4] = "intel/avs-lib/glk",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof-tplg",
+ [SOF_IPC_TYPE_4] = "intel/avs-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-glk.ri",
+ [SOF_IPC_TYPE_4] = "dsp_basefw.bin",
+ },
+ .nocodec_tplg_filename = "sof-glk-nocodec.tplg",
+ .ops = &sof_apl_ops,
+ .ops_init = sof_apl_ops_init,
+ .ops_free = hda_ops_free,
+};
+
+/* PCI IDs */
+static const struct pci_device_id sof_pci_ids[] = {
+ { PCI_DEVICE_DATA(INTEL, HDA_APL, &bxt_desc) },
+ { PCI_DEVICE_DATA(INTEL, HDA_GML, &glk_desc) },
+ { 0, }
+};
+MODULE_DEVICE_TABLE(pci, sof_pci_ids);
+
+/* pci_driver definition */
+static struct pci_driver snd_sof_pci_intel_apl_driver = {
+ .name = "sof-audio-pci-intel-apl",
+ .id_table = sof_pci_ids,
+ .probe = hda_pci_intel_probe,
+ .remove = sof_pci_remove,
+ .shutdown = sof_pci_shutdown,
+ .driver = {
+ .pm = &sof_pci_pm,
+ },
+};
+module_pci_driver(snd_sof_pci_intel_apl_driver);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_HDA_COMMON);
+MODULE_IMPORT_NS(SND_SOC_SOF_PCI_DEV);
diff --git a/sound/soc/sof/intel/pci-cnl.c b/sound/soc/sof/intel/pci-cnl.c
new file mode 100644
index 000000000000..9fa0cd2eae79
--- /dev/null
+++ b/sound/soc/sof/intel/pci-cnl.c
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2018 Intel Corporation. All rights reserved.
+//
+// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
+//
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+#include <sound/sof.h>
+#include "../ops.h"
+#include "../sof-pci-dev.h"
+
+/* platform specific devices */
+#include "hda.h"
+
+static const struct sof_dev_desc cnl_desc = {
+ .machines = snd_soc_acpi_intel_cnl_machines,
+ .alt_machines = snd_soc_acpi_intel_cnl_sdw_machines,
+ .use_acpi_target_states = true,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = -1,
+ .resindex_imr_base = -1,
+ .irqindex_host_ipc = -1,
+ .chip_info = &cnl_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof",
+ [SOF_IPC_TYPE_4] = "intel/avs/cnl",
+ },
+ .default_lib_path = {
+ [SOF_IPC_TYPE_4] = "intel/avs-lib/cnl",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof-tplg",
+ [SOF_IPC_TYPE_4] = "intel/avs-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-cnl.ri",
+ [SOF_IPC_TYPE_4] = "dsp_basefw.bin",
+ },
+ .nocodec_tplg_filename = "sof-cnl-nocodec.tplg",
+ .ops = &sof_cnl_ops,
+ .ops_init = sof_cnl_ops_init,
+ .ops_free = hda_ops_free,
+};
+
+static const struct sof_dev_desc cfl_desc = {
+ .machines = snd_soc_acpi_intel_cfl_machines,
+ .alt_machines = snd_soc_acpi_intel_cfl_sdw_machines,
+ .use_acpi_target_states = true,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = -1,
+ .resindex_imr_base = -1,
+ .irqindex_host_ipc = -1,
+ .chip_info = &cnl_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof",
+ [SOF_IPC_TYPE_4] = "intel/avs/cnl",
+ },
+ .default_lib_path = {
+ [SOF_IPC_TYPE_4] = "intel/avs-lib/cnl",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof-tplg",
+ [SOF_IPC_TYPE_4] = "intel/avs-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-cfl.ri",
+ [SOF_IPC_TYPE_4] = "dsp_basefw.bin",
+ },
+ .nocodec_tplg_filename = "sof-cnl-nocodec.tplg",
+ .ops = &sof_cnl_ops,
+ .ops_init = sof_cnl_ops_init,
+ .ops_free = hda_ops_free,
+};
+
+static const struct sof_dev_desc cml_desc = {
+ .machines = snd_soc_acpi_intel_cml_machines,
+ .alt_machines = snd_soc_acpi_intel_cml_sdw_machines,
+ .use_acpi_target_states = true,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = -1,
+ .resindex_imr_base = -1,
+ .irqindex_host_ipc = -1,
+ .chip_info = &cnl_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof",
+ [SOF_IPC_TYPE_4] = "intel/avs/cnl",
+ },
+ .default_lib_path = {
+ [SOF_IPC_TYPE_4] = "intel/avs-lib/cnl",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof-tplg",
+ [SOF_IPC_TYPE_4] = "intel/avs-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-cml.ri",
+ [SOF_IPC_TYPE_4] = "dsp_basefw.bin",
+ },
+ .nocodec_tplg_filename = "sof-cnl-nocodec.tplg",
+ .ops = &sof_cnl_ops,
+ .ops_init = sof_cnl_ops_init,
+ .ops_free = hda_ops_free,
+};
+
+/* PCI IDs */
+static const struct pci_device_id sof_pci_ids[] = {
+ { PCI_DEVICE_DATA(INTEL, HDA_CNL_LP, &cnl_desc) },
+ { PCI_DEVICE_DATA(INTEL, HDA_CNL_H, &cfl_desc) },
+ { PCI_DEVICE_DATA(INTEL, HDA_CML_LP, &cml_desc) },
+ { PCI_DEVICE_DATA(INTEL, HDA_CML_H, &cml_desc) },
+ { PCI_DEVICE_DATA(INTEL, HDA_CML_S, &cml_desc) },
+ { 0, }
+};
+MODULE_DEVICE_TABLE(pci, sof_pci_ids);
+
+/* pci_driver definition */
+static struct pci_driver snd_sof_pci_intel_cnl_driver = {
+ .name = "sof-audio-pci-intel-cnl",
+ .id_table = sof_pci_ids,
+ .probe = hda_pci_intel_probe,
+ .remove = sof_pci_remove,
+ .shutdown = sof_pci_shutdown,
+ .driver = {
+ .pm = &sof_pci_pm,
+ },
+};
+module_pci_driver(snd_sof_pci_intel_cnl_driver);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_HDA_COMMON);
+MODULE_IMPORT_NS(SND_SOC_SOF_PCI_DEV);
diff --git a/sound/soc/sof/intel/pci-icl.c b/sound/soc/sof/intel/pci-icl.c
new file mode 100644
index 000000000000..b99c7c9aad7d
--- /dev/null
+++ b/sound/soc/sof/intel/pci-icl.c
@@ -0,0 +1,112 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2018-2021 Intel Corporation. All rights reserved.
+//
+// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
+//
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+#include <sound/sof.h>
+#include "../ops.h"
+#include "../sof-pci-dev.h"
+
+/* platform specific devices */
+#include "hda.h"
+
+static const struct sof_dev_desc icl_desc = {
+ .machines = snd_soc_acpi_intel_icl_machines,
+ .alt_machines = snd_soc_acpi_intel_icl_sdw_machines,
+ .use_acpi_target_states = true,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = -1,
+ .resindex_imr_base = -1,
+ .irqindex_host_ipc = -1,
+ .chip_info = &icl_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof",
+ [SOF_IPC_TYPE_4] = "intel/avs/icl",
+ },
+ .default_lib_path = {
+ [SOF_IPC_TYPE_4] = "intel/avs-lib/icl",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof-tplg",
+ [SOF_IPC_TYPE_4] = "intel/avs-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-icl.ri",
+ [SOF_IPC_TYPE_4] = "dsp_basefw.bin",
+ },
+ .nocodec_tplg_filename = "sof-icl-nocodec.tplg",
+ .ops = &sof_icl_ops,
+ .ops_init = sof_icl_ops_init,
+ .ops_free = hda_ops_free,
+};
+
+static const struct sof_dev_desc jsl_desc = {
+ .machines = snd_soc_acpi_intel_jsl_machines,
+ .use_acpi_target_states = true,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = -1,
+ .resindex_imr_base = -1,
+ .irqindex_host_ipc = -1,
+ .chip_info = &jsl_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof",
+ [SOF_IPC_TYPE_4] = "intel/avs/jsl",
+ },
+ .default_lib_path = {
+ [SOF_IPC_TYPE_4] = "intel/avs-lib/jsl",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof-tplg",
+ [SOF_IPC_TYPE_4] = "intel/avs-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-jsl.ri",
+ [SOF_IPC_TYPE_4] = "dsp_basefw.bin",
+ },
+ .nocodec_tplg_filename = "sof-jsl-nocodec.tplg",
+ .ops = &sof_cnl_ops,
+ .ops_init = sof_cnl_ops_init,
+ .ops_free = hda_ops_free,
+};
+
+/* PCI IDs */
+static const struct pci_device_id sof_pci_ids[] = {
+ { PCI_DEVICE_DATA(INTEL, HDA_ICL_LP, &icl_desc) },
+ { PCI_DEVICE_DATA(INTEL, HDA_ICL_H, &icl_desc) },
+ { PCI_DEVICE_DATA(INTEL, HDA_ICL_N, &jsl_desc) },
+ { PCI_DEVICE_DATA(INTEL, HDA_JSL_N, &jsl_desc) },
+ { 0, }
+};
+MODULE_DEVICE_TABLE(pci, sof_pci_ids);
+
+/* pci_driver definition */
+static struct pci_driver snd_sof_pci_intel_icl_driver = {
+ .name = "sof-audio-pci-intel-icl",
+ .id_table = sof_pci_ids,
+ .probe = hda_pci_intel_probe,
+ .remove = sof_pci_remove,
+ .shutdown = sof_pci_shutdown,
+ .driver = {
+ .pm = &sof_pci_pm,
+ },
+};
+module_pci_driver(snd_sof_pci_intel_icl_driver);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_HDA_COMMON);
+MODULE_IMPORT_NS(SND_SOC_SOF_PCI_DEV);
diff --git a/sound/soc/sof/intel/pci-lnl.c b/sound/soc/sof/intel/pci-lnl.c
new file mode 100644
index 000000000000..b26ffe767fab
--- /dev/null
+++ b/sound/soc/sof/intel/pci-lnl.c
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2023 Intel Corporation. All rights reserved.
+//
+// Author: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
+//
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+#include <sound/sof.h>
+#include "../ops.h"
+#include "../sof-pci-dev.h"
+
+/* platform specific devices */
+#include "hda.h"
+#include "mtl.h"
+
+static const struct sof_dev_desc lnl_desc = {
+ .use_acpi_target_states = true,
+ .machines = snd_soc_acpi_intel_lnl_machines,
+ .alt_machines = snd_soc_acpi_intel_lnl_sdw_machines,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = -1,
+ .resindex_imr_base = -1,
+ .irqindex_host_ipc = -1,
+ .chip_info = &lnl_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_4),
+ .ipc_default = SOF_IPC_TYPE_4,
+ .dspless_mode_supported = true,
+ .default_fw_path = {
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4/lnl",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_4] = "sof-lnl.ri",
+ },
+ .nocodec_tplg_filename = "sof-lnl-nocodec.tplg",
+ .ops = &sof_lnl_ops,
+ .ops_init = sof_lnl_ops_init,
+};
+
+/* PCI IDs */
+static const struct pci_device_id sof_pci_ids[] = {
+ { PCI_DEVICE_DATA(INTEL, HDA_LNL_P, &lnl_desc) }, /* LNL-P */
+ { 0, }
+};
+MODULE_DEVICE_TABLE(pci, sof_pci_ids);
+
+/* pci_driver definition */
+static struct pci_driver snd_sof_pci_intel_lnl_driver = {
+ .name = "sof-audio-pci-intel-lnl",
+ .id_table = sof_pci_ids,
+ .probe = hda_pci_intel_probe,
+ .remove = sof_pci_remove,
+ .shutdown = sof_pci_shutdown,
+ .driver = {
+ .pm = &sof_pci_pm,
+ },
+};
+module_pci_driver(snd_sof_pci_intel_lnl_driver);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_HDA_COMMON);
+MODULE_IMPORT_NS(SND_SOC_SOF_PCI_DEV);
diff --git a/sound/soc/sof/intel/pci-mtl.c b/sound/soc/sof/intel/pci-mtl.c
new file mode 100644
index 000000000000..cacc985d80f4
--- /dev/null
+++ b/sound/soc/sof/intel/pci-mtl.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2018-2022 Intel Corporation. All rights reserved.
+//
+// Author: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
+//
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+#include <sound/sof.h>
+#include "../ops.h"
+#include "../sof-pci-dev.h"
+
+/* platform specific devices */
+#include "hda.h"
+#include "mtl.h"
+
+static const struct sof_dev_desc mtl_desc = {
+ .use_acpi_target_states = true,
+ .machines = snd_soc_acpi_intel_mtl_machines,
+ .alt_machines = snd_soc_acpi_intel_mtl_sdw_machines,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = -1,
+ .resindex_imr_base = -1,
+ .irqindex_host_ipc = -1,
+ .chip_info = &mtl_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_4),
+ .ipc_default = SOF_IPC_TYPE_4,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
+ .default_fw_path = {
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4/mtl",
+ },
+ .default_lib_path = {
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4-lib/mtl",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_4] = "intel/sof-ace-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_4] = "sof-mtl.ri",
+ },
+ .nocodec_tplg_filename = "sof-mtl-nocodec.tplg",
+ .ops = &sof_mtl_ops,
+ .ops_init = sof_mtl_ops_init,
+ .ops_free = hda_ops_free,
+};
+
+static const struct sof_dev_desc arl_desc = {
+ .use_acpi_target_states = true,
+ .machines = snd_soc_acpi_intel_arl_machines,
+ .alt_machines = snd_soc_acpi_intel_arl_sdw_machines,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = -1,
+ .resindex_imr_base = -1,
+ .irqindex_host_ipc = -1,
+ .chip_info = &mtl_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_4),
+ .ipc_default = SOF_IPC_TYPE_4,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
+ .default_fw_path = {
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4/arl",
+ },
+ .default_lib_path = {
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4-lib/arl",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_4] = "intel/sof-ace-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_4] = "sof-arl.ri",
+ },
+ .nocodec_tplg_filename = "sof-arl-nocodec.tplg",
+ .ops = &sof_mtl_ops,
+ .ops_init = sof_mtl_ops_init,
+ .ops_free = hda_ops_free,
+};
+
+static const struct sof_dev_desc arl_s_desc = {
+ .use_acpi_target_states = true,
+ .machines = snd_soc_acpi_intel_arl_machines,
+ .alt_machines = snd_soc_acpi_intel_arl_sdw_machines,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = -1,
+ .resindex_imr_base = -1,
+ .irqindex_host_ipc = -1,
+ .chip_info = &arl_s_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_4),
+ .ipc_default = SOF_IPC_TYPE_4,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
+ .default_fw_path = {
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4/arl-s",
+ },
+ .default_lib_path = {
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4-lib/arl-s",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_4] = "intel/sof-ace-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_4] = "sof-arl-s.ri",
+ },
+ .nocodec_tplg_filename = "sof-arl-nocodec.tplg",
+ .ops = &sof_mtl_ops,
+ .ops_init = sof_mtl_ops_init,
+ .ops_free = hda_ops_free,
+};
+
+/* PCI IDs */
+static const struct pci_device_id sof_pci_ids[] = {
+ { PCI_DEVICE_DATA(INTEL, HDA_MTL, &mtl_desc) },
+ { PCI_DEVICE_DATA(INTEL, HDA_ARL_S, &arl_s_desc) },
+ { PCI_DEVICE_DATA(INTEL, HDA_ARL, &arl_desc) },
+ { 0, }
+};
+MODULE_DEVICE_TABLE(pci, sof_pci_ids);
+
+/* pci_driver definition */
+static struct pci_driver snd_sof_pci_intel_mtl_driver = {
+ .name = "sof-audio-pci-intel-mtl",
+ .id_table = sof_pci_ids,
+ .probe = hda_pci_intel_probe,
+ .remove = sof_pci_remove,
+ .shutdown = sof_pci_shutdown,
+ .driver = {
+ .pm = &sof_pci_pm,
+ },
+};
+module_pci_driver(snd_sof_pci_intel_mtl_driver);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_HDA_COMMON);
+MODULE_IMPORT_NS(SND_SOC_SOF_PCI_DEV);
diff --git a/sound/soc/sof/intel/pci-skl.c b/sound/soc/sof/intel/pci-skl.c
new file mode 100644
index 000000000000..9dde439a0b0f
--- /dev/null
+++ b/sound/soc/sof/intel/pci-skl.c
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2018-2022 Intel Corporation. All rights reserved.
+//
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+#include <sound/sof.h>
+#include "../ops.h"
+#include "../sof-pci-dev.h"
+
+/* platform specific devices */
+#include "hda.h"
+
+static struct sof_dev_desc skl_desc = {
+ .machines = snd_soc_acpi_intel_skl_machines,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = -1,
+ .resindex_imr_base = -1,
+ .chip_info = &skl_chip_info,
+ .irqindex_host_ipc = -1,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_4),
+ .ipc_default = SOF_IPC_TYPE_4,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
+ .default_fw_path = {
+ [SOF_IPC_TYPE_4] = "intel/avs/skl",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_4] = "intel/avs-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_4] = "dsp_basefw.bin",
+ },
+ .nocodec_tplg_filename = "sof-skl-nocodec.tplg",
+ .ops = &sof_skl_ops,
+ .ops_init = sof_skl_ops_init,
+ .ops_free = hda_ops_free,
+};
+
+static struct sof_dev_desc kbl_desc = {
+ .machines = snd_soc_acpi_intel_kbl_machines,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = -1,
+ .resindex_imr_base = -1,
+ .chip_info = &skl_chip_info,
+ .irqindex_host_ipc = -1,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_4),
+ .ipc_default = SOF_IPC_TYPE_4,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
+ .default_fw_path = {
+ [SOF_IPC_TYPE_4] = "intel/avs/kbl",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_4] = "intel/avs-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_4] = "dsp_basefw.bin",
+ },
+ .nocodec_tplg_filename = "sof-kbl-nocodec.tplg",
+ .ops = &sof_skl_ops,
+ .ops_init = sof_skl_ops_init,
+ .ops_free = hda_ops_free,
+};
+
+/* PCI IDs */
+static const struct pci_device_id sof_pci_ids[] = {
+ { PCI_DEVICE_DATA(INTEL, HDA_SKL_LP, &skl_desc) },
+ { PCI_DEVICE_DATA(INTEL, HDA_KBL_LP, &kbl_desc) },
+ { 0, }
+};
+MODULE_DEVICE_TABLE(pci, sof_pci_ids);
+
+/* pci_driver definition */
+static struct pci_driver snd_sof_pci_intel_skl_driver = {
+ .name = "sof-audio-pci-intel-skl",
+ .id_table = sof_pci_ids,
+ .probe = hda_pci_intel_probe,
+ .remove = sof_pci_remove,
+ .shutdown = sof_pci_shutdown,
+ .driver = {
+ .pm = &sof_pci_pm,
+ },
+};
+module_pci_driver(snd_sof_pci_intel_skl_driver);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_HDA_COMMON);
+MODULE_IMPORT_NS(SND_SOC_SOF_PCI_DEV);
diff --git a/sound/soc/sof/intel/pci-tgl.c b/sound/soc/sof/intel/pci-tgl.c
new file mode 100644
index 000000000000..a361ee9d1107
--- /dev/null
+++ b/sound/soc/sof/intel/pci-tgl.c
@@ -0,0 +1,321 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2018-2021 Intel Corporation. All rights reserved.
+//
+// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
+//
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+#include <sound/sof.h>
+#include "../ops.h"
+#include "../sof-pci-dev.h"
+
+/* platform specific devices */
+#include "hda.h"
+
+static const struct sof_dev_desc tgl_desc = {
+ .machines = snd_soc_acpi_intel_tgl_machines,
+ .alt_machines = snd_soc_acpi_intel_tgl_sdw_machines,
+ .use_acpi_target_states = true,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = -1,
+ .resindex_imr_base = -1,
+ .irqindex_host_ipc = -1,
+ .chip_info = &tgl_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof",
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4/tgl",
+ },
+ .default_lib_path = {
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4-lib/tgl",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof-tplg",
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-tgl.ri",
+ [SOF_IPC_TYPE_4] = "sof-tgl.ri",
+ },
+ .nocodec_tplg_filename = "sof-tgl-nocodec.tplg",
+ .ops = &sof_tgl_ops,
+ .ops_init = sof_tgl_ops_init,
+ .ops_free = hda_ops_free,
+};
+
+static const struct sof_dev_desc tglh_desc = {
+ .machines = snd_soc_acpi_intel_tgl_machines,
+ .alt_machines = snd_soc_acpi_intel_tgl_sdw_machines,
+ .use_acpi_target_states = true,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = -1,
+ .resindex_imr_base = -1,
+ .irqindex_host_ipc = -1,
+ .chip_info = &tglh_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof",
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4/tgl-h",
+ },
+ .default_lib_path = {
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4-lib/tgl-h",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof-tplg",
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-tgl-h.ri",
+ [SOF_IPC_TYPE_4] = "sof-tgl-h.ri",
+ },
+ .nocodec_tplg_filename = "sof-tgl-nocodec.tplg",
+ .ops = &sof_tgl_ops,
+ .ops_init = sof_tgl_ops_init,
+ .ops_free = hda_ops_free,
+};
+
+static const struct sof_dev_desc ehl_desc = {
+ .machines = snd_soc_acpi_intel_ehl_machines,
+ .use_acpi_target_states = true,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = -1,
+ .resindex_imr_base = -1,
+ .irqindex_host_ipc = -1,
+ .chip_info = &ehl_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof",
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4/ehl",
+ },
+ .default_lib_path = {
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4-lib/ehl",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof-tplg",
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-ehl.ri",
+ [SOF_IPC_TYPE_4] = "sof-ehl.ri",
+ },
+ .nocodec_tplg_filename = "sof-ehl-nocodec.tplg",
+ .ops = &sof_tgl_ops,
+ .ops_init = sof_tgl_ops_init,
+ .ops_free = hda_ops_free,
+};
+
+static const struct sof_dev_desc adls_desc = {
+ .machines = snd_soc_acpi_intel_adl_machines,
+ .alt_machines = snd_soc_acpi_intel_adl_sdw_machines,
+ .use_acpi_target_states = true,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = -1,
+ .resindex_imr_base = -1,
+ .irqindex_host_ipc = -1,
+ .chip_info = &adls_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof",
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4/adl-s",
+ },
+ .default_lib_path = {
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4-lib/adl-s",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof-tplg",
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-adl-s.ri",
+ [SOF_IPC_TYPE_4] = "sof-adl-s.ri",
+ },
+ .nocodec_tplg_filename = "sof-adl-nocodec.tplg",
+ .ops = &sof_tgl_ops,
+ .ops_init = sof_tgl_ops_init,
+ .ops_free = hda_ops_free,
+};
+
+static const struct sof_dev_desc adl_desc = {
+ .machines = snd_soc_acpi_intel_adl_machines,
+ .alt_machines = snd_soc_acpi_intel_adl_sdw_machines,
+ .use_acpi_target_states = true,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = -1,
+ .resindex_imr_base = -1,
+ .irqindex_host_ipc = -1,
+ .chip_info = &tgl_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof",
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4/adl",
+ },
+ .default_lib_path = {
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4-lib/adl",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof-tplg",
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-adl.ri",
+ [SOF_IPC_TYPE_4] = "sof-adl.ri",
+ },
+ .nocodec_tplg_filename = "sof-adl-nocodec.tplg",
+ .ops = &sof_tgl_ops,
+ .ops_init = sof_tgl_ops_init,
+ .ops_free = hda_ops_free,
+};
+
+static const struct sof_dev_desc adl_n_desc = {
+ .machines = snd_soc_acpi_intel_adl_machines,
+ .alt_machines = snd_soc_acpi_intel_adl_sdw_machines,
+ .use_acpi_target_states = true,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = -1,
+ .resindex_imr_base = -1,
+ .irqindex_host_ipc = -1,
+ .chip_info = &tgl_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof",
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4/adl-n",
+ },
+ .default_lib_path = {
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4-lib/adl-n",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof-tplg",
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-adl-n.ri",
+ [SOF_IPC_TYPE_4] = "sof-adl-n.ri",
+ },
+ .nocodec_tplg_filename = "sof-adl-nocodec.tplg",
+ .ops = &sof_tgl_ops,
+ .ops_init = sof_tgl_ops_init,
+ .ops_free = hda_ops_free,
+};
+
+static const struct sof_dev_desc rpls_desc = {
+ .machines = snd_soc_acpi_intel_rpl_machines,
+ .alt_machines = snd_soc_acpi_intel_rpl_sdw_machines,
+ .use_acpi_target_states = true,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = -1,
+ .resindex_imr_base = -1,
+ .irqindex_host_ipc = -1,
+ .chip_info = &adls_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof",
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4/rpl-s",
+ },
+ .default_lib_path = {
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4-lib/rpl-s",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof-tplg",
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-rpl-s.ri",
+ [SOF_IPC_TYPE_4] = "sof-rpl-s.ri",
+ },
+ .nocodec_tplg_filename = "sof-rpl-nocodec.tplg",
+ .ops = &sof_tgl_ops,
+ .ops_init = sof_tgl_ops_init,
+ .ops_free = hda_ops_free,
+};
+
+static const struct sof_dev_desc rpl_desc = {
+ .machines = snd_soc_acpi_intel_rpl_machines,
+ .alt_machines = snd_soc_acpi_intel_rpl_sdw_machines,
+ .use_acpi_target_states = true,
+ .resindex_lpe_base = 0,
+ .resindex_pcicfg_base = -1,
+ .resindex_imr_base = -1,
+ .irqindex_host_ipc = -1,
+ .chip_info = &tgl_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .dspless_mode_supported = true, /* Only supported for HDaudio */
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof",
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4/rpl",
+ },
+ .default_lib_path = {
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4-lib/rpl",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof-tplg",
+ [SOF_IPC_TYPE_4] = "intel/sof-ipc4-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-rpl.ri",
+ [SOF_IPC_TYPE_4] = "sof-rpl.ri",
+ },
+ .nocodec_tplg_filename = "sof-rpl-nocodec.tplg",
+ .ops = &sof_tgl_ops,
+ .ops_init = sof_tgl_ops_init,
+ .ops_free = hda_ops_free,
+};
+
+/* PCI IDs */
+static const struct pci_device_id sof_pci_ids[] = {
+ { PCI_DEVICE_DATA(INTEL, HDA_TGL_LP, &tgl_desc) },
+ { PCI_DEVICE_DATA(INTEL, HDA_TGL_H, &tglh_desc) },
+ { PCI_DEVICE_DATA(INTEL, HDA_EHL_0, &ehl_desc) },
+ { PCI_DEVICE_DATA(INTEL, HDA_EHL_3, &ehl_desc) },
+ { PCI_DEVICE_DATA(INTEL, HDA_ADL_S, &adls_desc) },
+ { PCI_DEVICE_DATA(INTEL, HDA_RPL_S, &rpls_desc) },
+ { PCI_DEVICE_DATA(INTEL, HDA_ADL_P, &adl_desc) },
+ { PCI_DEVICE_DATA(INTEL, HDA_ADL_PS, &adl_desc) },
+ { PCI_DEVICE_DATA(INTEL, HDA_RPL_P_0, &rpl_desc) },
+ { PCI_DEVICE_DATA(INTEL, HDA_RPL_P_1, &rpl_desc) },
+ { PCI_DEVICE_DATA(INTEL, HDA_ADL_M, &adl_desc) },
+ { PCI_DEVICE_DATA(INTEL, HDA_ADL_PX, &adl_desc) },
+ { PCI_DEVICE_DATA(INTEL, HDA_RPL_M, &rpl_desc) },
+ { PCI_DEVICE_DATA(INTEL, HDA_RPL_PX, &rpl_desc) },
+ { PCI_DEVICE_DATA(INTEL, HDA_ADL_N, &adl_n_desc) },
+ { 0, }
+};
+MODULE_DEVICE_TABLE(pci, sof_pci_ids);
+
+/* pci_driver definition */
+static struct pci_driver snd_sof_pci_intel_tgl_driver = {
+ .name = "sof-audio-pci-intel-tgl",
+ .id_table = sof_pci_ids,
+ .probe = hda_pci_intel_probe,
+ .remove = sof_pci_remove,
+ .shutdown = sof_pci_shutdown,
+ .driver = {
+ .pm = &sof_pci_pm,
+ },
+};
+module_pci_driver(snd_sof_pci_intel_tgl_driver);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_HDA_COMMON);
+MODULE_IMPORT_NS(SND_SOC_SOF_PCI_DEV);
diff --git a/sound/soc/sof/intel/pci-tng.c b/sound/soc/sof/intel/pci-tng.c
new file mode 100644
index 000000000000..c90173003c2b
--- /dev/null
+++ b/sound/soc/sof/intel/pci-tng.c
@@ -0,0 +1,250 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2018-2021 Intel Corporation. All rights reserved.
+//
+// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
+//
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-acpi-intel-match.h>
+#include <sound/sof.h>
+#include "../ops.h"
+#include "atom.h"
+#include "../sof-pci-dev.h"
+#include "../sof-audio.h"
+
+/* platform specific devices */
+#include "shim.h"
+
+static struct snd_soc_acpi_mach sof_tng_machines[] = {
+ {
+ .id = "INT343A",
+ .drv_name = "edison",
+ .sof_tplg_filename = "sof-byt.tplg",
+ },
+ {}
+};
+
+static const struct snd_sof_debugfs_map tng_debugfs[] = {
+ {"dmac0", DSP_BAR, DMAC0_OFFSET, DMAC_SIZE,
+ SOF_DEBUGFS_ACCESS_ALWAYS},
+ {"dmac1", DSP_BAR, DMAC1_OFFSET, DMAC_SIZE,
+ SOF_DEBUGFS_ACCESS_ALWAYS},
+ {"ssp0", DSP_BAR, SSP0_OFFSET, SSP_SIZE,
+ SOF_DEBUGFS_ACCESS_ALWAYS},
+ {"ssp1", DSP_BAR, SSP1_OFFSET, SSP_SIZE,
+ SOF_DEBUGFS_ACCESS_ALWAYS},
+ {"ssp2", DSP_BAR, SSP2_OFFSET, SSP_SIZE,
+ SOF_DEBUGFS_ACCESS_ALWAYS},
+ {"iram", DSP_BAR, IRAM_OFFSET, IRAM_SIZE,
+ SOF_DEBUGFS_ACCESS_D0_ONLY},
+ {"dram", DSP_BAR, DRAM_OFFSET, DRAM_SIZE,
+ SOF_DEBUGFS_ACCESS_D0_ONLY},
+ {"shim", DSP_BAR, SHIM_OFFSET, SHIM_SIZE_BYT,
+ SOF_DEBUGFS_ACCESS_ALWAYS},
+};
+
+static int tangier_pci_probe(struct snd_sof_dev *sdev)
+{
+ struct snd_sof_pdata *pdata = sdev->pdata;
+ const struct sof_dev_desc *desc = pdata->desc;
+ struct pci_dev *pci = to_pci_dev(sdev->dev);
+ const struct sof_intel_dsp_desc *chip;
+ u32 base, size;
+ int ret;
+
+ chip = get_chip_info(sdev->pdata);
+ if (!chip) {
+ dev_err(sdev->dev, "error: no such device supported\n");
+ return -EIO;
+ }
+
+ sdev->num_cores = chip->cores_num;
+
+ /* DSP DMA can only access low 31 bits of host memory */
+ ret = dma_coerce_mask_and_coherent(&pci->dev, DMA_BIT_MASK(31));
+ if (ret < 0) {
+ dev_err(sdev->dev, "error: failed to set DMA mask %d\n", ret);
+ return ret;
+ }
+
+ /* LPE base */
+ base = pci_resource_start(pci, desc->resindex_lpe_base) - IRAM_OFFSET;
+ size = PCI_BAR_SIZE;
+
+ dev_dbg(sdev->dev, "LPE PHY base at 0x%x size 0x%x", base, size);
+ sdev->bar[DSP_BAR] = devm_ioremap(sdev->dev, base, size);
+ if (!sdev->bar[DSP_BAR]) {
+ dev_err(sdev->dev, "error: failed to ioremap LPE base 0x%x size 0x%x\n",
+ base, size);
+ return -ENODEV;
+ }
+ dev_dbg(sdev->dev, "LPE VADDR %p\n", sdev->bar[DSP_BAR]);
+
+ /* IMR base - optional */
+ if (desc->resindex_imr_base == -1)
+ goto irq;
+
+ base = pci_resource_start(pci, desc->resindex_imr_base);
+ size = pci_resource_len(pci, desc->resindex_imr_base);
+
+ /* some BIOSes don't map IMR */
+ if (base == 0x55aa55aa || base == 0x0) {
+ dev_info(sdev->dev, "IMR not set by BIOS. Ignoring\n");
+ goto irq;
+ }
+
+ dev_dbg(sdev->dev, "IMR base at 0x%x size 0x%x", base, size);
+ sdev->bar[IMR_BAR] = devm_ioremap(sdev->dev, base, size);
+ if (!sdev->bar[IMR_BAR]) {
+ dev_err(sdev->dev, "error: failed to ioremap IMR base 0x%x size 0x%x\n",
+ base, size);
+ return -ENODEV;
+ }
+ dev_dbg(sdev->dev, "IMR VADDR %p\n", sdev->bar[IMR_BAR]);
+
+irq:
+ /* register our IRQ */
+ sdev->ipc_irq = pci->irq;
+ dev_dbg(sdev->dev, "using IRQ %d\n", sdev->ipc_irq);
+ ret = devm_request_threaded_irq(sdev->dev, sdev->ipc_irq,
+ atom_irq_handler, atom_irq_thread,
+ 0, "AudioDSP", sdev);
+ if (ret < 0) {
+ dev_err(sdev->dev, "error: failed to register IRQ %d\n",
+ sdev->ipc_irq);
+ return ret;
+ }
+
+ /* enable BUSY and disable DONE Interrupt by default */
+ snd_sof_dsp_update_bits64(sdev, DSP_BAR, SHIM_IMRX,
+ SHIM_IMRX_BUSY | SHIM_IMRX_DONE,
+ SHIM_IMRX_DONE);
+
+ /* set default mailbox offset for FW ready message */
+ sdev->dsp_box.offset = MBOX_OFFSET;
+
+ return ret;
+}
+
+struct snd_sof_dsp_ops sof_tng_ops = {
+ /* device init */
+ .probe = tangier_pci_probe,
+
+ /* DSP core boot / reset */
+ .run = atom_run,
+ .reset = atom_reset,
+
+ /* Register IO uses direct mmio */
+
+ /* Block IO */
+ .block_read = sof_block_read,
+ .block_write = sof_block_write,
+
+ /* Mailbox IO */
+ .mailbox_read = sof_mailbox_read,
+ .mailbox_write = sof_mailbox_write,
+
+ /* doorbell */
+ .irq_handler = atom_irq_handler,
+ .irq_thread = atom_irq_thread,
+
+ /* ipc */
+ .send_msg = atom_send_msg,
+ .get_mailbox_offset = atom_get_mailbox_offset,
+ .get_window_offset = atom_get_window_offset,
+
+ .ipc_msg_data = sof_ipc_msg_data,
+ .set_stream_data_offset = sof_set_stream_data_offset,
+
+ /* machine driver */
+ .machine_select = atom_machine_select,
+ .machine_register = sof_machine_register,
+ .machine_unregister = sof_machine_unregister,
+ .set_mach_params = atom_set_mach_params,
+
+ /* debug */
+ .debug_map = tng_debugfs,
+ .debug_map_count = ARRAY_SIZE(tng_debugfs),
+ .dbg_dump = atom_dump,
+ .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem,
+
+ /* stream callbacks */
+ .pcm_open = sof_stream_pcm_open,
+ .pcm_close = sof_stream_pcm_close,
+
+ /*Firmware loading */
+ .load_firmware = snd_sof_load_firmware_memcpy,
+
+ /* DAI drivers */
+ .drv = atom_dai,
+ .num_drv = 3, /* we have only 3 SSPs on byt*/
+
+ /* ALSA HW info flags */
+ .hw_info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_BATCH,
+
+ .dsp_arch_ops = &sof_xtensa_arch_ops,
+};
+
+const struct sof_intel_dsp_desc tng_chip_info = {
+ .cores_num = 1,
+ .host_managed_cores_mask = 1,
+ .hw_ip_version = SOF_INTEL_TANGIER,
+};
+
+static const struct sof_dev_desc tng_desc = {
+ .machines = sof_tng_machines,
+ .resindex_lpe_base = 3, /* IRAM, but subtract IRAM offset */
+ .resindex_pcicfg_base = -1,
+ .resindex_imr_base = 0,
+ .irqindex_host_ipc = -1,
+ .chip_info = &tng_chip_info,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "intel/sof-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-byt.ri",
+ },
+ .nocodec_tplg_filename = "sof-byt.tplg",
+ .ops = &sof_tng_ops,
+};
+
+/* PCI IDs */
+static const struct pci_device_id sof_pci_ids[] = {
+ { PCI_DEVICE_DATA(INTEL, SST_TNG, &tng_desc) },
+ { 0, }
+};
+MODULE_DEVICE_TABLE(pci, sof_pci_ids);
+
+/* pci_driver definition */
+static struct pci_driver snd_sof_pci_intel_tng_driver = {
+ .name = "sof-audio-pci-intel-tng",
+ .id_table = sof_pci_ids,
+ .probe = sof_pci_probe,
+ .remove = sof_pci_remove,
+ .shutdown = sof_pci_shutdown,
+ .driver = {
+ .pm = &sof_pci_pm,
+ },
+};
+module_pci_driver(snd_sof_pci_intel_tng_driver);
+
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_HIFI_EP_IPC);
+MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA);
+MODULE_IMPORT_NS(SND_SOC_SOF_PCI_DEV);
+MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_ATOM_HIFI_EP);
diff --git a/sound/soc/sof/intel/shim.h b/sound/soc/sof/intel/shim.h
index 6fe8b004b50e..9515d753c816 100644
--- a/sound/soc/sof/intel/shim.h
+++ b/sound/soc/sof/intel/shim.h
@@ -11,6 +11,19 @@
#ifndef __SOF_INTEL_SHIM_H
#define __SOF_INTEL_SHIM_H
+enum sof_intel_hw_ip_version {
+ SOF_INTEL_TANGIER,
+ SOF_INTEL_BAYTRAIL,
+ SOF_INTEL_BROADWELL,
+ SOF_INTEL_CAVS_1_5, /* SkyLake, KabyLake, AmberLake */
+ SOF_INTEL_CAVS_1_5_PLUS,/* ApolloLake, GeminiLake */
+ SOF_INTEL_CAVS_1_8, /* CannonLake, CometLake, CoffeeLake */
+ SOF_INTEL_CAVS_2_0, /* IceLake, JasperLake */
+ SOF_INTEL_CAVS_2_5, /* TigerLake, AlderLake */
+ SOF_INTEL_ACE_1_0, /* MeteorLake */
+ SOF_INTEL_ACE_2_0, /* LunarLake */
+};
+
/*
* SHIM registers for BYT, BSW, CHT, BDW
*/
@@ -151,33 +164,51 @@
#define PCI_PMCS 0x84
#define PCI_PMCS_PS_MASK 0x3
+/* Intel quirks */
+#define SOF_INTEL_PROCEN_FMT_QUIRK BIT(0)
+
/* DSP hardware descriptor */
struct sof_intel_dsp_desc {
int cores_num;
- int cores_mask;
+ int host_managed_cores_mask;
int init_core_mask; /* cores available after fw boot */
int ipc_req;
int ipc_req_mask;
int ipc_ack;
int ipc_ack_mask;
int ipc_ctl;
+ int rom_status_reg;
int rom_init_timeout;
int ssp_count; /* ssp count of the platform */
int ssp_base_offset; /* base address of the SSPs */
+ u32 sdw_shim_base;
+ u32 sdw_alh_base;
+ u32 d0i3_offset;
+ u32 quirks;
+ enum sof_intel_hw_ip_version hw_ip_version;
+ int (*read_sdw_lcount)(struct snd_sof_dev *sdev);
+ void (*enable_sdw_irq)(struct snd_sof_dev *sdev, bool enable);
+ bool (*check_sdw_irq)(struct snd_sof_dev *sdev);
+ bool (*check_sdw_wakeen_irq)(struct snd_sof_dev *sdev);
+ bool (*check_ipc_irq)(struct snd_sof_dev *sdev);
+ int (*power_down_dsp)(struct snd_sof_dev *sdev);
+ int (*disable_interrupts)(struct snd_sof_dev *sdev);
+ int (*cl_init)(struct snd_sof_dev *sdev, int stream_tag, bool imr_boot);
};
-extern const struct snd_sof_dsp_ops sof_tng_ops;
-extern const struct snd_sof_dsp_ops sof_byt_ops;
-extern const struct snd_sof_dsp_ops sof_cht_ops;
-extern const struct snd_sof_dsp_ops sof_bdw_ops;
+extern struct snd_sof_dsp_ops sof_tng_ops;
-extern const struct sof_intel_dsp_desc byt_chip_info;
-extern const struct sof_intel_dsp_desc cht_chip_info;
-extern const struct sof_intel_dsp_desc bdw_chip_info;
extern const struct sof_intel_dsp_desc tng_chip_info;
struct sof_intel_stream {
size_t posn_offset;
};
+static inline const struct sof_intel_dsp_desc *get_chip_info(struct snd_sof_pdata *pdata)
+{
+ const struct sof_dev_desc *desc = pdata->desc;
+
+ return desc->chip_info;
+}
+
#endif
diff --git a/sound/soc/sof/intel/skl.c b/sound/soc/sof/intel/skl.c
new file mode 100644
index 000000000000..93824e6ce573
--- /dev/null
+++ b/sound/soc/sof/intel/skl.c
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2018-2022 Intel Corporation. All rights reserved.
+//
+
+/*
+ * Hardware interface for audio DSP on Skylake and Kabylake.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/firmware.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <sound/hdaudio_ext.h>
+#include <sound/pcm_params.h>
+#include <sound/sof.h>
+#include <sound/sof/ext_manifest4.h>
+
+#include "../sof-priv.h"
+#include "../ipc4-priv.h"
+#include "../ops.h"
+#include "hda.h"
+#include "../sof-audio.h"
+
+#define SRAM_MEMORY_WINDOW_BASE 0x8000
+
+static const __maybe_unused struct snd_sof_debugfs_map skl_dsp_debugfs[] = {
+ {"hda", HDA_DSP_HDA_BAR, 0, 0x4000},
+ {"pp", HDA_DSP_PP_BAR, 0, 0x1000},
+ {"dsp", HDA_DSP_BAR, 0, 0x10000},
+};
+
+static int skl_dsp_ipc_get_window_offset(struct snd_sof_dev *sdev, u32 id)
+{
+ return SRAM_MEMORY_WINDOW_BASE + (0x2000 * id);
+}
+
+static int skl_dsp_ipc_get_mailbox_offset(struct snd_sof_dev *sdev)
+{
+ return SRAM_MEMORY_WINDOW_BASE + 0x1000;
+}
+
+/* skylake ops */
+struct snd_sof_dsp_ops sof_skl_ops;
+EXPORT_SYMBOL_NS(sof_skl_ops, SND_SOC_SOF_INTEL_HDA_COMMON);
+
+int sof_skl_ops_init(struct snd_sof_dev *sdev)
+{
+ struct sof_ipc4_fw_data *ipc4_data;
+
+ /* common defaults */
+ memcpy(&sof_skl_ops, &sof_hda_common_ops, sizeof(struct snd_sof_dsp_ops));
+
+ /* probe/remove/shutdown */
+ sof_skl_ops.shutdown = hda_dsp_shutdown;
+
+ sdev->private = kzalloc(sizeof(*ipc4_data), GFP_KERNEL);
+ if (!sdev->private)
+ return -ENOMEM;
+
+ ipc4_data = sdev->private;
+ ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET_CAVS_1_5;
+
+ ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_1_5;
+
+ sof_skl_ops.get_window_offset = skl_dsp_ipc_get_window_offset;
+ sof_skl_ops.get_mailbox_offset = skl_dsp_ipc_get_mailbox_offset;
+
+ /* doorbell */
+ sof_skl_ops.irq_thread = hda_dsp_ipc4_irq_thread;
+
+ /* ipc */
+ sof_skl_ops.send_msg = hda_dsp_ipc4_send_msg;
+
+ /* set DAI driver ops */
+ hda_set_dai_drv_ops(sdev, &sof_skl_ops);
+
+ /* debug */
+ sof_skl_ops.debug_map = skl_dsp_debugfs;
+ sof_skl_ops.debug_map_count = ARRAY_SIZE(skl_dsp_debugfs);
+ sof_skl_ops.ipc_dump = hda_ipc4_dump;
+
+ /* firmware run */
+ sof_skl_ops.run = hda_dsp_cl_boot_firmware_skl;
+
+ /* pre/post fw run */
+ sof_skl_ops.post_fw_run = hda_dsp_post_fw_run;
+
+ return 0;
+};
+EXPORT_SYMBOL_NS(sof_skl_ops_init, SND_SOC_SOF_INTEL_HDA_COMMON);
+
+const struct sof_intel_dsp_desc skl_chip_info = {
+ .cores_num = 2,
+ .init_core_mask = 1,
+ .host_managed_cores_mask = GENMASK(1, 0),
+ .ipc_req = HDA_DSP_REG_HIPCI,
+ .ipc_req_mask = HDA_DSP_REG_HIPCI_BUSY,
+ .ipc_ack = HDA_DSP_REG_HIPCIE,
+ .ipc_ack_mask = HDA_DSP_REG_HIPCIE_DONE,
+ .ipc_ctl = HDA_DSP_REG_HIPCCTL,
+ .rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS_SKL,
+ .rom_init_timeout = 300,
+ .check_ipc_irq = hda_dsp_check_ipc_irq,
+ .power_down_dsp = hda_power_down_dsp,
+ .disable_interrupts = hda_dsp_disable_interrupts,
+ .hw_ip_version = SOF_INTEL_CAVS_1_5,
+};
+EXPORT_SYMBOL_NS(skl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
diff --git a/sound/soc/sof/intel/telemetry.c b/sound/soc/sof/intel/telemetry.c
new file mode 100644
index 000000000000..1a3b5c28a6f0
--- /dev/null
+++ b/sound/soc/sof/intel/telemetry.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2023 Intel Corporation. All rights reserved.
+
+/* telemetry data queried from debug window */
+
+#include <sound/sof/ipc4/header.h>
+#include <sound/sof/xtensa.h>
+#include "../ipc4-priv.h"
+#include "../sof-priv.h"
+#include "hda.h"
+#include "telemetry.h"
+
+void sof_ipc4_intel_dump_telemetry_state(struct snd_sof_dev *sdev, u32 flags)
+{
+ static const char invalid_slot_msg[] = "Core dump is not available due to";
+ struct sof_ipc4_telemetry_slot_data *telemetry_data;
+ struct sof_ipc_dsp_oops_xtensa *xoops;
+ struct xtensa_arch_block *block;
+ u32 slot_offset;
+ char *level;
+
+ level = (flags & SOF_DBG_DUMP_OPTIONAL) ? KERN_DEBUG : KERN_ERR;
+
+ slot_offset = sof_ipc4_find_debug_slot_offset_by_type(sdev, SOF_IPC4_DEBUG_SLOT_TELEMETRY);
+ if (!slot_offset)
+ return;
+
+ telemetry_data = kmalloc(sizeof(*telemetry_data), GFP_KERNEL);
+ if (!telemetry_data)
+ return;
+ sof_mailbox_read(sdev, slot_offset, telemetry_data, sizeof(*telemetry_data));
+ if (telemetry_data->separator != XTENSA_CORE_DUMP_SEPARATOR) {
+ dev_err(sdev->dev, "%s invalid separator %#x\n", invalid_slot_msg,
+ telemetry_data->separator);
+ goto free_telemetry_data;
+ }
+
+ block = kmalloc(sizeof(*block), GFP_KERNEL);
+ if (!block)
+ goto free_telemetry_data;
+
+ sof_mailbox_read(sdev, slot_offset + sizeof(*telemetry_data), block, sizeof(*block));
+ if (block->soc != XTENSA_SOC_INTEL_ADSP) {
+ dev_err(sdev->dev, "%s invalid SOC %d\n", invalid_slot_msg, block->soc);
+ goto free_block;
+ }
+
+ if (telemetry_data->hdr.id[0] != COREDUMP_HDR_ID0 ||
+ telemetry_data->hdr.id[1] != COREDUMP_HDR_ID1 ||
+ telemetry_data->arch_hdr.id != COREDUMP_ARCH_HDR_ID) {
+ dev_err(sdev->dev, "%s invalid coredump header %c%c, arch hdr %c\n",
+ invalid_slot_msg, telemetry_data->hdr.id[0],
+ telemetry_data->hdr.id[1],
+ telemetry_data->arch_hdr.id);
+ goto free_block;
+ }
+
+ switch (block->toolchain) {
+ case XTENSA_TOOL_CHAIN_ZEPHYR:
+ dev_printk(level, sdev->dev, "FW is built with Zephyr toolchain\n");
+ break;
+ case XTENSA_TOOL_CHAIN_XCC:
+ dev_printk(level, sdev->dev, "FW is built with XCC toolchain\n");
+ break;
+ default:
+ dev_printk(level, sdev->dev, "Unknown toolchain is used\n");
+ break;
+ }
+
+ xoops = kzalloc(struct_size(xoops, ar, XTENSA_CORE_AR_REGS_COUNT), GFP_KERNEL);
+ if (!xoops)
+ goto free_block;
+
+ xoops->exccause = block->exccause;
+ xoops->excvaddr = block->excvaddr;
+ xoops->epc1 = block->pc;
+ xoops->ps = block->ps;
+ xoops->sar = block->sar;
+
+ xoops->plat_hdr.numaregs = XTENSA_CORE_AR_REGS_COUNT;
+ memcpy((void *)xoops->ar, block->ar, XTENSA_CORE_AR_REGS_COUNT * sizeof(u32));
+
+ sof_oops(sdev, level, xoops);
+ sof_stack(sdev, level, xoops, NULL, 0);
+
+ kfree(xoops);
+free_block:
+ kfree(block);
+free_telemetry_data:
+ kfree(telemetry_data);
+}
diff --git a/sound/soc/sof/intel/telemetry.h b/sound/soc/sof/intel/telemetry.h
new file mode 100644
index 000000000000..3c2b23c75f5d
--- /dev/null
+++ b/sound/soc/sof/intel/telemetry.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+/*
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * Copyright(c) 2023 Intel Corporation. All rights reserved.
+ *
+ * telemetry data in debug windows
+ */
+
+#ifndef _SOF_INTEL_TELEMETRY_H
+#define _SOF_INTEL_TELEMETRY_H
+
+#include "../ipc4-telemetry.h"
+
+struct xtensa_arch_block {
+ u8 soc; /* should be equal to XTENSA_SOC_INTEL_ADSP */
+ u16 version;
+ u8 toolchain; /* ZEPHYR or XCC */
+
+ u32 pc;
+ u32 exccause;
+ u32 excvaddr;
+ u32 sar;
+ u32 ps;
+ u32 scompare1;
+ u32 ar[XTENSA_CORE_AR_REGS_COUNT];
+ u32 lbeg;
+ u32 lend;
+ u32 lcount;
+} __packed;
+
+void sof_ipc4_intel_dump_telemetry_state(struct snd_sof_dev *sdev, u32 flags);
+
+#endif /* _SOF_INTEL_TELEMETRY_H */
diff --git a/sound/soc/sof/intel/tgl.c b/sound/soc/sof/intel/tgl.c
new file mode 100644
index 000000000000..c2bb04c89b9d
--- /dev/null
+++ b/sound/soc/sof/intel/tgl.c
@@ -0,0 +1,247 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// Copyright(c) 2020 Intel Corporation. All rights reserved.
+//
+// Authors: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
+//
+
+/*
+ * Hardware interface for audio DSP on Tigerlake.
+ */
+
+#include <sound/sof/ext_manifest4.h>
+#include "../ipc4-priv.h"
+#include "../ops.h"
+#include "hda.h"
+#include "hda-ipc.h"
+#include "../sof-audio.h"
+
+static const struct snd_sof_debugfs_map tgl_dsp_debugfs[] = {
+ {"hda", HDA_DSP_HDA_BAR, 0, 0x4000, SOF_DEBUGFS_ACCESS_ALWAYS},
+ {"pp", HDA_DSP_PP_BAR, 0, 0x1000, SOF_DEBUGFS_ACCESS_ALWAYS},
+ {"dsp", HDA_DSP_BAR, 0, 0x10000, SOF_DEBUGFS_ACCESS_ALWAYS},
+};
+
+static int tgl_dsp_core_get(struct snd_sof_dev *sdev, int core)
+{
+ const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm;
+
+ /* power up primary core if not already powered up and return */
+ if (core == SOF_DSP_PRIMARY_CORE)
+ return hda_dsp_enable_core(sdev, BIT(core));
+
+ if (pm_ops->set_core_state)
+ return pm_ops->set_core_state(sdev, core, true);
+
+ return 0;
+}
+
+static int tgl_dsp_core_put(struct snd_sof_dev *sdev, int core)
+{
+ const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm;
+ int ret;
+
+ if (pm_ops->set_core_state) {
+ ret = pm_ops->set_core_state(sdev, core, false);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* power down primary core and return */
+ if (core == SOF_DSP_PRIMARY_CORE)
+ return hda_dsp_core_reset_power_down(sdev, BIT(core));
+
+ return 0;
+}
+
+/* Tigerlake ops */
+struct snd_sof_dsp_ops sof_tgl_ops;
+EXPORT_SYMBOL_NS(sof_tgl_ops, SND_SOC_SOF_INTEL_HDA_COMMON);
+
+int sof_tgl_ops_init(struct snd_sof_dev *sdev)
+{
+ /* common defaults */
+ memcpy(&sof_tgl_ops, &sof_hda_common_ops, sizeof(struct snd_sof_dsp_ops));
+
+ /* probe/remove/shutdown */
+ sof_tgl_ops.shutdown = hda_dsp_shutdown_dma_flush;
+
+ if (sdev->pdata->ipc_type == SOF_IPC_TYPE_3) {
+ /* doorbell */
+ sof_tgl_ops.irq_thread = cnl_ipc_irq_thread;
+
+ /* ipc */
+ sof_tgl_ops.send_msg = cnl_ipc_send_msg;
+
+ /* debug */
+ sof_tgl_ops.ipc_dump = cnl_ipc_dump;
+
+ sof_tgl_ops.set_power_state = hda_dsp_set_power_state_ipc3;
+ }
+
+ if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4) {
+ struct sof_ipc4_fw_data *ipc4_data;
+
+ sdev->private = kzalloc(sizeof(*ipc4_data), GFP_KERNEL);
+ if (!sdev->private)
+ return -ENOMEM;
+
+ ipc4_data = sdev->private;
+ ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET;
+
+ ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_2;
+
+ ipc4_data->fw_context_save = true;
+
+ /* External library loading support */
+ ipc4_data->load_library = hda_dsp_ipc4_load_library;
+
+ /* doorbell */
+ sof_tgl_ops.irq_thread = cnl_ipc4_irq_thread;
+
+ /* ipc */
+ sof_tgl_ops.send_msg = cnl_ipc4_send_msg;
+
+ /* debug */
+ sof_tgl_ops.ipc_dump = cnl_ipc4_dump;
+ sof_tgl_ops.dbg_dump = hda_ipc4_dsp_dump;
+
+ sof_tgl_ops.set_power_state = hda_dsp_set_power_state_ipc4;
+ }
+
+ /* set DAI driver ops */
+ hda_set_dai_drv_ops(sdev, &sof_tgl_ops);
+
+ /* debug */
+ sof_tgl_ops.debug_map = tgl_dsp_debugfs;
+ sof_tgl_ops.debug_map_count = ARRAY_SIZE(tgl_dsp_debugfs);
+
+ /* pre/post fw run */
+ sof_tgl_ops.post_fw_run = hda_dsp_post_fw_run;
+
+ /* firmware run */
+ sof_tgl_ops.run = hda_dsp_cl_boot_firmware_iccmax;
+
+ /* dsp core get/put */
+ sof_tgl_ops.core_get = tgl_dsp_core_get;
+ sof_tgl_ops.core_put = tgl_dsp_core_put;
+
+ return 0;
+};
+EXPORT_SYMBOL_NS(sof_tgl_ops_init, SND_SOC_SOF_INTEL_HDA_COMMON);
+
+const struct sof_intel_dsp_desc tgl_chip_info = {
+ /* Tigerlake , Alderlake */
+ .cores_num = 4,
+ .init_core_mask = 1,
+ .host_managed_cores_mask = BIT(0),
+ .ipc_req = CNL_DSP_REG_HIPCIDR,
+ .ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY,
+ .ipc_ack = CNL_DSP_REG_HIPCIDA,
+ .ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE,
+ .ipc_ctl = CNL_DSP_REG_HIPCCTL,
+ .rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS,
+ .rom_init_timeout = 300,
+ .ssp_count = TGL_SSP_COUNT,
+ .ssp_base_offset = CNL_SSP_BASE_OFFSET,
+ .sdw_shim_base = SDW_SHIM_BASE,
+ .sdw_alh_base = SDW_ALH_BASE,
+ .d0i3_offset = SOF_HDA_VS_D0I3C,
+ .read_sdw_lcount = hda_sdw_check_lcount_common,
+ .enable_sdw_irq = hda_common_enable_sdw_irq,
+ .check_sdw_irq = hda_common_check_sdw_irq,
+ .check_sdw_wakeen_irq = hda_sdw_check_wakeen_irq_common,
+ .check_ipc_irq = hda_dsp_check_ipc_irq,
+ .cl_init = cl_dsp_init,
+ .power_down_dsp = hda_power_down_dsp,
+ .disable_interrupts = hda_dsp_disable_interrupts,
+ .hw_ip_version = SOF_INTEL_CAVS_2_5,
+};
+EXPORT_SYMBOL_NS(tgl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
+
+const struct sof_intel_dsp_desc tglh_chip_info = {
+ /* Tigerlake-H */
+ .cores_num = 2,
+ .init_core_mask = 1,
+ .host_managed_cores_mask = BIT(0),
+ .ipc_req = CNL_DSP_REG_HIPCIDR,
+ .ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY,
+ .ipc_ack = CNL_DSP_REG_HIPCIDA,
+ .ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE,
+ .ipc_ctl = CNL_DSP_REG_HIPCCTL,
+ .rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS,
+ .rom_init_timeout = 300,
+ .ssp_count = TGL_SSP_COUNT,
+ .ssp_base_offset = CNL_SSP_BASE_OFFSET,
+ .sdw_shim_base = SDW_SHIM_BASE,
+ .sdw_alh_base = SDW_ALH_BASE,
+ .d0i3_offset = SOF_HDA_VS_D0I3C,
+ .read_sdw_lcount = hda_sdw_check_lcount_common,
+ .enable_sdw_irq = hda_common_enable_sdw_irq,
+ .check_sdw_irq = hda_common_check_sdw_irq,
+ .check_sdw_wakeen_irq = hda_sdw_check_wakeen_irq_common,
+ .check_ipc_irq = hda_dsp_check_ipc_irq,
+ .cl_init = cl_dsp_init,
+ .power_down_dsp = hda_power_down_dsp,
+ .disable_interrupts = hda_dsp_disable_interrupts,
+ .hw_ip_version = SOF_INTEL_CAVS_2_5,
+};
+EXPORT_SYMBOL_NS(tglh_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
+
+const struct sof_intel_dsp_desc ehl_chip_info = {
+ /* Elkhartlake */
+ .cores_num = 4,
+ .init_core_mask = 1,
+ .host_managed_cores_mask = BIT(0),
+ .ipc_req = CNL_DSP_REG_HIPCIDR,
+ .ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY,
+ .ipc_ack = CNL_DSP_REG_HIPCIDA,
+ .ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE,
+ .ipc_ctl = CNL_DSP_REG_HIPCCTL,
+ .rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS,
+ .rom_init_timeout = 300,
+ .ssp_count = TGL_SSP_COUNT,
+ .ssp_base_offset = CNL_SSP_BASE_OFFSET,
+ .sdw_shim_base = SDW_SHIM_BASE,
+ .sdw_alh_base = SDW_ALH_BASE,
+ .d0i3_offset = SOF_HDA_VS_D0I3C,
+ .read_sdw_lcount = hda_sdw_check_lcount_common,
+ .enable_sdw_irq = hda_common_enable_sdw_irq,
+ .check_sdw_irq = hda_common_check_sdw_irq,
+ .check_sdw_wakeen_irq = hda_sdw_check_wakeen_irq_common,
+ .check_ipc_irq = hda_dsp_check_ipc_irq,
+ .cl_init = cl_dsp_init,
+ .power_down_dsp = hda_power_down_dsp,
+ .disable_interrupts = hda_dsp_disable_interrupts,
+ .hw_ip_version = SOF_INTEL_CAVS_2_5,
+};
+EXPORT_SYMBOL_NS(ehl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
+
+const struct sof_intel_dsp_desc adls_chip_info = {
+ /* Alderlake-S */
+ .cores_num = 2,
+ .init_core_mask = BIT(0),
+ .host_managed_cores_mask = BIT(0),
+ .ipc_req = CNL_DSP_REG_HIPCIDR,
+ .ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY,
+ .ipc_ack = CNL_DSP_REG_HIPCIDA,
+ .ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE,
+ .ipc_ctl = CNL_DSP_REG_HIPCCTL,
+ .rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS,
+ .rom_init_timeout = 300,
+ .ssp_count = TGL_SSP_COUNT,
+ .ssp_base_offset = CNL_SSP_BASE_OFFSET,
+ .sdw_shim_base = SDW_SHIM_BASE,
+ .sdw_alh_base = SDW_ALH_BASE,
+ .d0i3_offset = SOF_HDA_VS_D0I3C,
+ .read_sdw_lcount = hda_sdw_check_lcount_common,
+ .enable_sdw_irq = hda_common_enable_sdw_irq,
+ .check_sdw_irq = hda_common_check_sdw_irq,
+ .check_sdw_wakeen_irq = hda_sdw_check_wakeen_irq_common,
+ .check_ipc_irq = hda_dsp_check_ipc_irq,
+ .cl_init = cl_dsp_init,
+ .power_down_dsp = hda_power_down_dsp,
+ .disable_interrupts = hda_dsp_disable_interrupts,
+ .hw_ip_version = SOF_INTEL_CAVS_2_5,
+};
+EXPORT_SYMBOL_NS(adls_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
diff --git a/sound/soc/sof/iomem-utils.c b/sound/soc/sof/iomem-utils.c
new file mode 100644
index 000000000000..3f57f6cf6542
--- /dev/null
+++ b/sound/soc/sof/iomem-utils.c
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2018-2022 Intel Corporation. All rights reserved.
+//
+// Author: Keyon Jie <yang.jie@linux.intel.com>
+//
+
+#include <linux/io-64-nonatomic-lo-hi.h>
+#include <linux/platform_device.h>
+#include <asm/unaligned.h>
+#include <sound/soc.h>
+#include <sound/sof.h>
+#include "sof-priv.h"
+#include "ops.h"
+
+/*
+ * Register IO
+ *
+ * The sof_io_xyz() wrappers are typically referenced in snd_sof_dsp_ops
+ * structures and cannot be inlined.
+ */
+
+void sof_io_write(struct snd_sof_dev *sdev, void __iomem *addr, u32 value)
+{
+ writel(value, addr);
+}
+EXPORT_SYMBOL(sof_io_write);
+
+u32 sof_io_read(struct snd_sof_dev *sdev, void __iomem *addr)
+{
+ return readl(addr);
+}
+EXPORT_SYMBOL(sof_io_read);
+
+void sof_io_write64(struct snd_sof_dev *sdev, void __iomem *addr, u64 value)
+{
+ writeq(value, addr);
+}
+EXPORT_SYMBOL(sof_io_write64);
+
+u64 sof_io_read64(struct snd_sof_dev *sdev, void __iomem *addr)
+{
+ return readq(addr);
+}
+EXPORT_SYMBOL(sof_io_read64);
+
+/*
+ * IPC Mailbox IO
+ */
+
+void sof_mailbox_write(struct snd_sof_dev *sdev, u32 offset,
+ void *message, size_t bytes)
+{
+ void __iomem *dest = sdev->bar[sdev->mailbox_bar] + offset;
+
+ memcpy_toio(dest, message, bytes);
+}
+EXPORT_SYMBOL(sof_mailbox_write);
+
+void sof_mailbox_read(struct snd_sof_dev *sdev, u32 offset,
+ void *message, size_t bytes)
+{
+ void __iomem *src = sdev->bar[sdev->mailbox_bar] + offset;
+
+ memcpy_fromio(message, src, bytes);
+}
+EXPORT_SYMBOL(sof_mailbox_read);
+
+/*
+ * Memory copy.
+ */
+
+int sof_block_write(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type,
+ u32 offset, void *src, size_t size)
+{
+ int bar = snd_sof_dsp_get_bar_index(sdev, blk_type);
+ const u8 *src_byte = src;
+ void __iomem *dest;
+ u32 affected_mask;
+ u32 tmp;
+ int m, n;
+
+ if (bar < 0)
+ return bar;
+
+ dest = sdev->bar[bar] + offset;
+
+ m = size / 4;
+ n = size % 4;
+
+ /* __iowrite32_copy use 32bit size values so divide by 4 */
+ __iowrite32_copy(dest, src, m);
+
+ if (n) {
+ affected_mask = (1 << (8 * n)) - 1;
+
+ /* first read the 32bit data of dest, then change affected
+ * bytes, and write back to dest. For unaffected bytes, it
+ * should not be changed
+ */
+ tmp = ioread32(dest + m * 4);
+ tmp &= ~affected_mask;
+
+ tmp |= *(u32 *)(src_byte + m * 4) & affected_mask;
+ iowrite32(tmp, dest + m * 4);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(sof_block_write);
+
+int sof_block_read(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type,
+ u32 offset, void *dest, size_t size)
+{
+ int bar = snd_sof_dsp_get_bar_index(sdev, blk_type);
+
+ if (bar < 0)
+ return bar;
+
+ memcpy_fromio(dest, sdev->bar[bar] + offset, size);
+
+ return 0;
+}
+EXPORT_SYMBOL(sof_block_read);
diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c
index 36e2d4d43da4..febe372f9aa8 100644
--- a/sound/soc/sof/ipc.c
+++ b/sound/soc/sof/ipc.c
@@ -18,248 +18,47 @@
#include "sof-audio.h"
#include "ops.h"
-static void ipc_trace_message(struct snd_sof_dev *sdev, u32 msg_id);
-static void ipc_stream_message(struct snd_sof_dev *sdev, u32 msg_cmd);
-
-/*
- * IPC message Tx/Rx message handling.
+/**
+ * sof_ipc_send_msg - generic function to prepare and send one IPC message
+ * @sdev: pointer to SOF core device struct
+ * @msg_data: pointer to a message to send
+ * @msg_bytes: number of bytes in the message
+ * @reply_bytes: number of bytes available for the reply.
+ * The buffer for the reply data is not passed to this
+ * function, the available size is an information for the
+ * reply handling functions.
+ *
+ * On success the function returns 0, otherwise negative error number.
+ *
+ * Note: higher level sdev->ipc->tx_mutex must be held to make sure that
+ * transfers are synchronized.
*/
-
-/* SOF generic IPC data */
-struct snd_sof_ipc {
- struct snd_sof_dev *sdev;
-
- /* protects messages and the disable flag */
- struct mutex tx_mutex;
- /* disables further sending of ipc's */
- bool disable_ipc_tx;
-
- struct snd_sof_ipc_msg msg;
-};
-
-struct sof_ipc_ctrl_data_params {
- size_t msg_bytes;
- size_t hdr_bytes;
- size_t pl_size;
- size_t elems;
- u32 num_msg;
- u8 *src;
- u8 *dst;
-};
-
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_VERBOSE_IPC)
-static void ipc_log_header(struct device *dev, u8 *text, u32 cmd)
-{
- u8 *str;
- u8 *str2 = NULL;
- u32 glb;
- u32 type;
-
- glb = cmd & SOF_GLB_TYPE_MASK;
- type = cmd & SOF_CMD_TYPE_MASK;
-
- switch (glb) {
- case SOF_IPC_GLB_REPLY:
- str = "GLB_REPLY"; break;
- case SOF_IPC_GLB_COMPOUND:
- str = "GLB_COMPOUND"; break;
- case SOF_IPC_GLB_TPLG_MSG:
- str = "GLB_TPLG_MSG";
- switch (type) {
- case SOF_IPC_TPLG_COMP_NEW:
- str2 = "COMP_NEW"; break;
- case SOF_IPC_TPLG_COMP_FREE:
- str2 = "COMP_FREE"; break;
- case SOF_IPC_TPLG_COMP_CONNECT:
- str2 = "COMP_CONNECT"; break;
- case SOF_IPC_TPLG_PIPE_NEW:
- str2 = "PIPE_NEW"; break;
- case SOF_IPC_TPLG_PIPE_FREE:
- str2 = "PIPE_FREE"; break;
- case SOF_IPC_TPLG_PIPE_CONNECT:
- str2 = "PIPE_CONNECT"; break;
- case SOF_IPC_TPLG_PIPE_COMPLETE:
- str2 = "PIPE_COMPLETE"; break;
- case SOF_IPC_TPLG_BUFFER_NEW:
- str2 = "BUFFER_NEW"; break;
- case SOF_IPC_TPLG_BUFFER_FREE:
- str2 = "BUFFER_FREE"; break;
- default:
- str2 = "unknown type"; break;
- }
- break;
- case SOF_IPC_GLB_PM_MSG:
- str = "GLB_PM_MSG";
- switch (type) {
- case SOF_IPC_PM_CTX_SAVE:
- str2 = "CTX_SAVE"; break;
- case SOF_IPC_PM_CTX_RESTORE:
- str2 = "CTX_RESTORE"; break;
- case SOF_IPC_PM_CTX_SIZE:
- str2 = "CTX_SIZE"; break;
- case SOF_IPC_PM_CLK_SET:
- str2 = "CLK_SET"; break;
- case SOF_IPC_PM_CLK_GET:
- str2 = "CLK_GET"; break;
- case SOF_IPC_PM_CLK_REQ:
- str2 = "CLK_REQ"; break;
- case SOF_IPC_PM_CORE_ENABLE:
- str2 = "CORE_ENABLE"; break;
- default:
- str2 = "unknown type"; break;
- }
- break;
- case SOF_IPC_GLB_COMP_MSG:
- str = "GLB_COMP_MSG";
- switch (type) {
- case SOF_IPC_COMP_SET_VALUE:
- str2 = "SET_VALUE"; break;
- case SOF_IPC_COMP_GET_VALUE:
- str2 = "GET_VALUE"; break;
- case SOF_IPC_COMP_SET_DATA:
- str2 = "SET_DATA"; break;
- case SOF_IPC_COMP_GET_DATA:
- str2 = "GET_DATA"; break;
- default:
- str2 = "unknown type"; break;
- }
- break;
- case SOF_IPC_GLB_STREAM_MSG:
- str = "GLB_STREAM_MSG";
- switch (type) {
- case SOF_IPC_STREAM_PCM_PARAMS:
- str2 = "PCM_PARAMS"; break;
- case SOF_IPC_STREAM_PCM_PARAMS_REPLY:
- str2 = "PCM_REPLY"; break;
- case SOF_IPC_STREAM_PCM_FREE:
- str2 = "PCM_FREE"; break;
- case SOF_IPC_STREAM_TRIG_START:
- str2 = "TRIG_START"; break;
- case SOF_IPC_STREAM_TRIG_STOP:
- str2 = "TRIG_STOP"; break;
- case SOF_IPC_STREAM_TRIG_PAUSE:
- str2 = "TRIG_PAUSE"; break;
- case SOF_IPC_STREAM_TRIG_RELEASE:
- str2 = "TRIG_RELEASE"; break;
- case SOF_IPC_STREAM_TRIG_DRAIN:
- str2 = "TRIG_DRAIN"; break;
- case SOF_IPC_STREAM_TRIG_XRUN:
- str2 = "TRIG_XRUN"; break;
- case SOF_IPC_STREAM_POSITION:
- str2 = "POSITION"; break;
- case SOF_IPC_STREAM_VORBIS_PARAMS:
- str2 = "VORBIS_PARAMS"; break;
- case SOF_IPC_STREAM_VORBIS_FREE:
- str2 = "VORBIS_FREE"; break;
- default:
- str2 = "unknown type"; break;
- }
- break;
- case SOF_IPC_FW_READY:
- str = "FW_READY"; break;
- case SOF_IPC_GLB_DAI_MSG:
- str = "GLB_DAI_MSG";
- switch (type) {
- case SOF_IPC_DAI_CONFIG:
- str2 = "CONFIG"; break;
- case SOF_IPC_DAI_LOOPBACK:
- str2 = "LOOPBACK"; break;
- default:
- str2 = "unknown type"; break;
- }
- break;
- case SOF_IPC_GLB_TRACE_MSG:
- str = "GLB_TRACE_MSG"; break;
- case SOF_IPC_GLB_TEST_MSG:
- str = "GLB_TEST_MSG";
- switch (type) {
- case SOF_IPC_TEST_IPC_FLOOD:
- str2 = "IPC_FLOOD"; break;
- default:
- str2 = "unknown type"; break;
- }
- break;
- default:
- str = "unknown GLB command"; break;
- }
-
- if (str2)
- dev_dbg(dev, "%s: 0x%x: %s: %s\n", text, cmd, str, str2);
- else
- dev_dbg(dev, "%s: 0x%x: %s\n", text, cmd, str);
-}
-#else
-static inline void ipc_log_header(struct device *dev, u8 *text, u32 cmd)
+int sof_ipc_send_msg(struct snd_sof_dev *sdev, void *msg_data, size_t msg_bytes,
+ size_t reply_bytes)
{
- if ((cmd & SOF_GLB_TYPE_MASK) != SOF_IPC_GLB_TRACE_MSG)
- dev_dbg(dev, "%s: 0x%x\n", text, cmd);
-}
-#endif
-
-/* wait for IPC message reply */
-static int tx_wait_done(struct snd_sof_ipc *ipc, struct snd_sof_ipc_msg *msg,
- void *reply_data)
-{
- struct snd_sof_dev *sdev = ipc->sdev;
- struct sof_ipc_cmd_hdr *hdr = msg->msg_data;
- int ret;
-
- /* wait for DSP IPC completion */
- ret = wait_event_timeout(msg->waitq, msg->ipc_complete,
- msecs_to_jiffies(sdev->ipc_timeout));
-
- if (ret == 0) {
- dev_err(sdev->dev, "error: ipc timed out for 0x%x size %d\n",
- hdr->cmd, hdr->size);
- snd_sof_handle_fw_exception(ipc->sdev);
- ret = -ETIMEDOUT;
- } else {
- ret = msg->reply_error;
- if (ret < 0) {
- dev_err(sdev->dev, "error: ipc error for 0x%x size %zu\n",
- hdr->cmd, msg->reply_size);
- } else {
- ipc_log_header(sdev->dev, "ipc tx succeeded", hdr->cmd);
- if (msg->reply_size)
- /* copy the data returned from DSP */
- memcpy(reply_data, msg->reply_data,
- msg->reply_size);
- }
- }
-
- return ret;
-}
-
-/* send IPC message from host to DSP */
-static int sof_ipc_tx_message_unlocked(struct snd_sof_ipc *ipc, u32 header,
- void *msg_data, size_t msg_bytes,
- void *reply_data, size_t reply_bytes)
-{
- struct snd_sof_dev *sdev = ipc->sdev;
+ struct snd_sof_ipc *ipc = sdev->ipc;
struct snd_sof_ipc_msg *msg;
int ret;
- if (ipc->disable_ipc_tx)
+ if (ipc->disable_ipc_tx || sdev->fw_state != SOF_FW_BOOT_COMPLETE)
return -ENODEV;
/*
- * The spin-lock is also still needed to protect message objects against
- * other atomic contexts.
+ * The spin-lock is needed to protect message objects against other
+ * atomic contexts.
*/
spin_lock_irq(&sdev->ipc_lock);
/* initialise the message */
msg = &ipc->msg;
- msg->header = header;
+ /* attach message data */
+ msg->msg_data = msg_data;
msg->msg_size = msg_bytes;
+
msg->reply_size = reply_bytes;
msg->reply_error = 0;
- /* attach any data */
- if (msg_bytes)
- memcpy(msg->msg_data, msg_data, msg_bytes);
-
sdev->msg = msg;
ret = snd_sof_dsp_send_msg(sdev, msg);
@@ -269,70 +68,63 @@ static int sof_ipc_tx_message_unlocked(struct snd_sof_ipc *ipc, u32 header,
spin_unlock_irq(&sdev->ipc_lock);
- if (ret < 0) {
- dev_err_ratelimited(sdev->dev,
- "error: ipc tx failed with error %d\n",
- ret);
- return ret;
- }
-
- ipc_log_header(sdev->dev, "ipc tx", msg->header);
-
- /* now wait for completion */
- if (!ret)
- ret = tx_wait_done(ipc, msg, reply_data);
-
return ret;
}
/* send IPC message from host to DSP */
-int sof_ipc_tx_message(struct snd_sof_ipc *ipc, u32 header,
- void *msg_data, size_t msg_bytes, void *reply_data,
- size_t reply_bytes)
+int sof_ipc_tx_message(struct snd_sof_ipc *ipc, void *msg_data, size_t msg_bytes,
+ void *reply_data, size_t reply_bytes)
{
- const struct sof_dsp_power_state target_state = {
- .state = SOF_DSP_PM_D0,
- };
- int ret;
-
- /* ensure the DSP is in D0 before sending a new IPC */
- ret = snd_sof_dsp_set_power_state(ipc->sdev, &target_state);
- if (ret < 0) {
- dev_err(ipc->sdev->dev, "error: resuming DSP %d\n", ret);
- return ret;
- }
+ if (msg_bytes > ipc->max_payload_size ||
+ reply_bytes > ipc->max_payload_size)
+ return -ENOBUFS;
- return sof_ipc_tx_message_no_pm(ipc, header, msg_data, msg_bytes,
- reply_data, reply_bytes);
+ return ipc->ops->tx_msg(ipc->sdev, msg_data, msg_bytes, reply_data,
+ reply_bytes, false);
}
EXPORT_SYMBOL(sof_ipc_tx_message);
+/* IPC set or get data from host to DSP */
+int sof_ipc_set_get_data(struct snd_sof_ipc *ipc, void *msg_data,
+ size_t msg_bytes, bool set)
+{
+ return ipc->ops->set_get_data(ipc->sdev, msg_data, msg_bytes, set);
+}
+EXPORT_SYMBOL(sof_ipc_set_get_data);
+
/*
* send IPC message from host to DSP without modifying the DSP state.
* This will be used for IPC's that can be handled by the DSP
* even in a low-power D0 substate.
*/
-int sof_ipc_tx_message_no_pm(struct snd_sof_ipc *ipc, u32 header,
- void *msg_data, size_t msg_bytes,
+int sof_ipc_tx_message_no_pm(struct snd_sof_ipc *ipc, void *msg_data, size_t msg_bytes,
void *reply_data, size_t reply_bytes)
{
- int ret;
-
- if (msg_bytes > SOF_IPC_MSG_MAX_SIZE ||
- reply_bytes > SOF_IPC_MSG_MAX_SIZE)
+ if (msg_bytes > ipc->max_payload_size ||
+ reply_bytes > ipc->max_payload_size)
return -ENOBUFS;
- /* Serialise IPC TX */
- mutex_lock(&ipc->tx_mutex);
-
- ret = sof_ipc_tx_message_unlocked(ipc, header, msg_data, msg_bytes,
- reply_data, reply_bytes);
+ return ipc->ops->tx_msg(ipc->sdev, msg_data, msg_bytes, reply_data,
+ reply_bytes, true);
+}
+EXPORT_SYMBOL(sof_ipc_tx_message_no_pm);
- mutex_unlock(&ipc->tx_mutex);
+/* Generic helper function to retrieve the reply */
+void snd_sof_ipc_get_reply(struct snd_sof_dev *sdev)
+{
+ /*
+ * Sometimes, there is unexpected reply ipc arriving. The reply
+ * ipc belongs to none of the ipcs sent from driver.
+ * In this case, the driver must ignore the ipc.
+ */
+ if (!sdev->msg) {
+ dev_warn(sdev->dev, "unexpected ipc interrupt raised!\n");
+ return;
+ }
- return ret;
+ sdev->msg->reply_error = sdev->ipc->ops->get_reply(sdev);
}
-EXPORT_SYMBOL(sof_ipc_tx_message_no_pm);
+EXPORT_SYMBOL(snd_sof_ipc_get_reply);
/* handle reply message from DSP */
void snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id)
@@ -352,494 +144,74 @@ void snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id)
}
EXPORT_SYMBOL(snd_sof_ipc_reply);
-/* DSP firmware has sent host a message */
-void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev)
+struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev)
{
- struct sof_ipc_cmd_hdr hdr;
- u32 cmd, type;
- int err = 0;
-
- /* read back header */
- snd_sof_ipc_msg_data(sdev, NULL, &hdr, sizeof(hdr));
- ipc_log_header(sdev->dev, "ipc rx", hdr.cmd);
-
- cmd = hdr.cmd & SOF_GLB_TYPE_MASK;
- type = hdr.cmd & SOF_CMD_TYPE_MASK;
+ struct snd_sof_ipc *ipc;
+ struct snd_sof_ipc_msg *msg;
+ const struct sof_ipc_ops *ops;
- /* check message type */
- switch (cmd) {
- case SOF_IPC_GLB_REPLY:
- dev_err(sdev->dev, "error: ipc reply unknown\n");
- break;
- case SOF_IPC_FW_READY:
- /* check for FW boot completion */
- if (sdev->fw_state == SOF_FW_BOOT_IN_PROGRESS) {
- err = sof_ops(sdev)->fw_ready(sdev, cmd);
- if (err < 0)
- sdev->fw_state = SOF_FW_BOOT_READY_FAILED;
- else
- sdev->fw_state = SOF_FW_BOOT_COMPLETE;
-
- /* wake up firmware loader */
- wake_up(&sdev->boot_wait);
- }
- break;
- case SOF_IPC_GLB_COMPOUND:
- case SOF_IPC_GLB_TPLG_MSG:
- case SOF_IPC_GLB_PM_MSG:
- case SOF_IPC_GLB_COMP_MSG:
- break;
- case SOF_IPC_GLB_STREAM_MSG:
- /* need to pass msg id into the function */
- ipc_stream_message(sdev, hdr.cmd);
- break;
- case SOF_IPC_GLB_TRACE_MSG:
- ipc_trace_message(sdev, type);
- break;
- default:
- dev_err(sdev->dev, "error: unknown DSP message 0x%x\n", cmd);
- break;
- }
+ ipc = devm_kzalloc(sdev->dev, sizeof(*ipc), GFP_KERNEL);
+ if (!ipc)
+ return NULL;
- ipc_log_header(sdev->dev, "ipc rx done", hdr.cmd);
-}
-EXPORT_SYMBOL(snd_sof_ipc_msgs_rx);
+ mutex_init(&ipc->tx_mutex);
+ ipc->sdev = sdev;
+ msg = &ipc->msg;
-/*
- * IPC trace mechanism.
- */
+ /* indicate that we aren't sending a message ATM */
+ msg->ipc_complete = true;
-static void ipc_trace_message(struct snd_sof_dev *sdev, u32 msg_id)
-{
- struct sof_ipc_dma_trace_posn posn;
+ init_waitqueue_head(&msg->waitq);
- switch (msg_id) {
- case SOF_IPC_TRACE_DMA_POSITION:
- /* read back full message */
- snd_sof_ipc_msg_data(sdev, NULL, &posn, sizeof(posn));
- snd_sof_trace_update_pos(sdev, &posn);
+ switch (sdev->pdata->ipc_type) {
+#if defined(CONFIG_SND_SOC_SOF_IPC3)
+ case SOF_IPC_TYPE_3:
+ ops = &ipc3_ops;
break;
- default:
- dev_err(sdev->dev, "error: unhandled trace message %x\n",
- msg_id);
- break;
- }
-}
-
-/*
- * IPC stream position.
- */
-
-static void ipc_period_elapsed(struct snd_sof_dev *sdev, u32 msg_id)
-{
- struct snd_soc_component *scomp = sdev->component;
- struct snd_sof_pcm_stream *stream;
- struct sof_ipc_stream_posn posn;
- struct snd_sof_pcm *spcm;
- int direction;
-
- spcm = snd_sof_find_spcm_comp(scomp, msg_id, &direction);
- if (!spcm) {
- dev_err(sdev->dev,
- "error: period elapsed for unknown stream, msg_id %d\n",
- msg_id);
- return;
- }
-
- stream = &spcm->stream[direction];
- snd_sof_ipc_msg_data(sdev, stream->substream, &posn, sizeof(posn));
-
- dev_dbg(sdev->dev, "posn : host 0x%llx dai 0x%llx wall 0x%llx\n",
- posn.host_posn, posn.dai_posn, posn.wallclock);
-
- memcpy(&stream->posn, &posn, sizeof(posn));
-
- /* only inform ALSA for period_wakeup mode */
- if (!stream->substream->runtime->no_period_wakeup)
- snd_sof_pcm_period_elapsed(stream->substream);
-}
-
-/* DSP notifies host of an XRUN within FW */
-static void ipc_xrun(struct snd_sof_dev *sdev, u32 msg_id)
-{
- struct snd_soc_component *scomp = sdev->component;
- struct snd_sof_pcm_stream *stream;
- struct sof_ipc_stream_posn posn;
- struct snd_sof_pcm *spcm;
- int direction;
-
- spcm = snd_sof_find_spcm_comp(scomp, msg_id, &direction);
- if (!spcm) {
- dev_err(sdev->dev, "error: XRUN for unknown stream, msg_id %d\n",
- msg_id);
- return;
- }
-
- stream = &spcm->stream[direction];
- snd_sof_ipc_msg_data(sdev, stream->substream, &posn, sizeof(posn));
-
- dev_dbg(sdev->dev, "posn XRUN: host %llx comp %d size %d\n",
- posn.host_posn, posn.xrun_comp_id, posn.xrun_size);
-
-#if defined(CONFIG_SND_SOC_SOF_DEBUG_XRUN_STOP)
- /* stop PCM on XRUN - used for pipeline debug */
- memcpy(&stream->posn, &posn, sizeof(posn));
- snd_pcm_stop_xrun(stream->substream);
#endif
-}
-
-/* stream notifications from DSP FW */
-static void ipc_stream_message(struct snd_sof_dev *sdev, u32 msg_cmd)
-{
- /* get msg cmd type and msd id */
- u32 msg_type = msg_cmd & SOF_CMD_TYPE_MASK;
- u32 msg_id = SOF_IPC_MESSAGE_ID(msg_cmd);
-
- switch (msg_type) {
- case SOF_IPC_STREAM_POSITION:
- ipc_period_elapsed(sdev, msg_id);
- break;
- case SOF_IPC_STREAM_TRIG_XRUN:
- ipc_xrun(sdev, msg_id);
- break;
- default:
- dev_err(sdev->dev, "error: unhandled stream message %x\n",
- msg_id);
- break;
- }
-}
-
-/* get stream position IPC - use faster MMIO method if available on platform */
-int snd_sof_ipc_stream_posn(struct snd_soc_component *scomp,
- struct snd_sof_pcm *spcm, int direction,
- struct sof_ipc_stream_posn *posn)
-{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct sof_ipc_stream stream;
- int err;
-
- /* read position via slower IPC */
- stream.hdr.size = sizeof(stream);
- stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_POSITION;
- stream.comp_id = spcm->stream[direction].comp_id;
-
- /* send IPC to the DSP */
- err = sof_ipc_tx_message(sdev->ipc,
- stream.hdr.cmd, &stream, sizeof(stream), posn,
- sizeof(*posn));
- if (err < 0) {
- dev_err(sdev->dev, "error: failed to get stream %d position\n",
- stream.comp_id);
- return err;
- }
-
- return 0;
-}
-EXPORT_SYMBOL(snd_sof_ipc_stream_posn);
-
-static int sof_get_ctrl_copy_params(enum sof_ipc_ctrl_type ctrl_type,
- struct sof_ipc_ctrl_data *src,
- struct sof_ipc_ctrl_data *dst,
- struct sof_ipc_ctrl_data_params *sparams)
-{
- switch (ctrl_type) {
- case SOF_CTRL_TYPE_VALUE_CHAN_GET:
- case SOF_CTRL_TYPE_VALUE_CHAN_SET:
- sparams->src = (u8 *)src->chanv;
- sparams->dst = (u8 *)dst->chanv;
- break;
- case SOF_CTRL_TYPE_VALUE_COMP_GET:
- case SOF_CTRL_TYPE_VALUE_COMP_SET:
- sparams->src = (u8 *)src->compv;
- sparams->dst = (u8 *)dst->compv;
- break;
- case SOF_CTRL_TYPE_DATA_GET:
- case SOF_CTRL_TYPE_DATA_SET:
- sparams->src = (u8 *)src->data->data;
- sparams->dst = (u8 *)dst->data->data;
- break;
- default:
- return -EINVAL;
- }
-
- /* calculate payload size and number of messages */
- sparams->pl_size = SOF_IPC_MSG_MAX_SIZE - sparams->hdr_bytes;
- sparams->num_msg = DIV_ROUND_UP(sparams->msg_bytes, sparams->pl_size);
-
- return 0;
-}
-
-static int sof_set_get_large_ctrl_data(struct snd_sof_dev *sdev,
- struct sof_ipc_ctrl_data *cdata,
- struct sof_ipc_ctrl_data_params *sparams,
- bool send)
-{
- struct sof_ipc_ctrl_data *partdata;
- size_t send_bytes;
- size_t offset = 0;
- size_t msg_bytes;
- size_t pl_size;
- int err;
- int i;
-
- /* allocate max ipc size because we have at least one */
- partdata = kzalloc(SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL);
- if (!partdata)
- return -ENOMEM;
-
- if (send)
- err = sof_get_ctrl_copy_params(cdata->type, cdata, partdata,
- sparams);
- else
- err = sof_get_ctrl_copy_params(cdata->type, partdata, cdata,
- sparams);
- if (err < 0) {
- kfree(partdata);
- return err;
- }
-
- msg_bytes = sparams->msg_bytes;
- pl_size = sparams->pl_size;
-
- /* copy the header data */
- memcpy(partdata, cdata, sparams->hdr_bytes);
-
- /* Serialise IPC TX */
- mutex_lock(&sdev->ipc->tx_mutex);
-
- /* copy the payload data in a loop */
- for (i = 0; i < sparams->num_msg; i++) {
- send_bytes = min(msg_bytes, pl_size);
- partdata->num_elems = send_bytes;
- partdata->rhdr.hdr.size = sparams->hdr_bytes + send_bytes;
- partdata->msg_index = i;
- msg_bytes -= send_bytes;
- partdata->elems_remaining = msg_bytes;
-
- if (send)
- memcpy(sparams->dst, sparams->src + offset, send_bytes);
-
- err = sof_ipc_tx_message_unlocked(sdev->ipc,
- partdata->rhdr.hdr.cmd,
- partdata,
- partdata->rhdr.hdr.size,
- partdata,
- partdata->rhdr.hdr.size);
- if (err < 0)
- break;
-
- if (!send)
- memcpy(sparams->dst + offset, sparams->src, send_bytes);
-
- offset += pl_size;
- }
-
- mutex_unlock(&sdev->ipc->tx_mutex);
-
- kfree(partdata);
- return err;
-}
-
-/*
- * IPC get()/set() for kcontrols.
- */
-int snd_sof_ipc_set_get_comp_data(struct snd_sof_control *scontrol,
- u32 ipc_cmd,
- enum sof_ipc_ctrl_type ctrl_type,
- enum sof_ipc_ctrl_cmd ctrl_cmd,
- bool send)
-{
- struct snd_soc_component *scomp = scontrol->scomp;
- struct sof_ipc_ctrl_data *cdata = scontrol->control_data;
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct sof_ipc_fw_ready *ready = &sdev->fw_ready;
- struct sof_ipc_fw_version *v = &ready->version;
- struct sof_ipc_ctrl_data_params sparams;
- size_t send_bytes;
- int err;
-
- /* read or write firmware volume */
- if (scontrol->readback_offset != 0) {
- /* write/read value header via mmaped region */
- send_bytes = sizeof(struct sof_ipc_ctrl_value_chan) *
- cdata->num_elems;
- if (send)
- snd_sof_dsp_block_write(sdev, sdev->mmio_bar,
- scontrol->readback_offset,
- cdata->chanv, send_bytes);
-
- else
- snd_sof_dsp_block_read(sdev, sdev->mmio_bar,
- scontrol->readback_offset,
- cdata->chanv, send_bytes);
- return 0;
- }
-
- cdata->rhdr.hdr.cmd = SOF_IPC_GLB_COMP_MSG | ipc_cmd;
- cdata->cmd = ctrl_cmd;
- cdata->type = ctrl_type;
- cdata->comp_id = scontrol->comp_id;
- cdata->msg_index = 0;
-
- /* calculate header and data size */
- switch (cdata->type) {
- case SOF_CTRL_TYPE_VALUE_CHAN_GET:
- case SOF_CTRL_TYPE_VALUE_CHAN_SET:
- sparams.msg_bytes = scontrol->num_channels *
- sizeof(struct sof_ipc_ctrl_value_chan);
- sparams.hdr_bytes = sizeof(struct sof_ipc_ctrl_data);
- sparams.elems = scontrol->num_channels;
- break;
- case SOF_CTRL_TYPE_VALUE_COMP_GET:
- case SOF_CTRL_TYPE_VALUE_COMP_SET:
- sparams.msg_bytes = scontrol->num_channels *
- sizeof(struct sof_ipc_ctrl_value_comp);
- sparams.hdr_bytes = sizeof(struct sof_ipc_ctrl_data);
- sparams.elems = scontrol->num_channels;
- break;
- case SOF_CTRL_TYPE_DATA_GET:
- case SOF_CTRL_TYPE_DATA_SET:
- sparams.msg_bytes = cdata->data->size;
- sparams.hdr_bytes = sizeof(struct sof_ipc_ctrl_data) +
- sizeof(struct sof_abi_hdr);
- sparams.elems = cdata->data->size;
+#if defined(CONFIG_SND_SOC_SOF_IPC4)
+ case SOF_IPC_TYPE_4:
+ ops = &ipc4_ops;
break;
+#endif
default:
- return -EINVAL;
- }
-
- cdata->rhdr.hdr.size = sparams.msg_bytes + sparams.hdr_bytes;
- cdata->num_elems = sparams.elems;
- cdata->elems_remaining = 0;
-
- /* send normal size ipc in one part */
- if (cdata->rhdr.hdr.size <= SOF_IPC_MSG_MAX_SIZE) {
- err = sof_ipc_tx_message(sdev->ipc, cdata->rhdr.hdr.cmd, cdata,
- cdata->rhdr.hdr.size, cdata,
- cdata->rhdr.hdr.size);
-
- if (err < 0)
- dev_err(sdev->dev, "error: set/get ctrl ipc comp %d\n",
- cdata->comp_id);
-
- return err;
- }
-
- /* data is bigger than max ipc size, chop into smaller pieces */
- dev_dbg(sdev->dev, "large ipc size %u, control size %u\n",
- cdata->rhdr.hdr.size, scontrol->size);
-
- /* large messages is only supported from ABI 3.3.0 onwards */
- if (v->abi_version < SOF_ABI_VER(3, 3, 0)) {
- dev_err(sdev->dev, "error: incompatible FW ABI version\n");
- return -EINVAL;
+ dev_err(sdev->dev, "Not supported IPC version: %d\n",
+ sdev->pdata->ipc_type);
+ return NULL;
}
- err = sof_set_get_large_ctrl_data(sdev, cdata, &sparams, send);
-
- if (err < 0)
- dev_err(sdev->dev, "error: set/get large ctrl ipc comp %d\n",
- cdata->comp_id);
-
- return err;
-}
-EXPORT_SYMBOL(snd_sof_ipc_set_get_comp_data);
-
-/*
- * IPC layer enumeration.
- */
-
-int snd_sof_dsp_mailbox_init(struct snd_sof_dev *sdev, u32 dspbox,
- size_t dspbox_size, u32 hostbox,
- size_t hostbox_size)
-{
- sdev->dsp_box.offset = dspbox;
- sdev->dsp_box.size = dspbox_size;
- sdev->host_box.offset = hostbox;
- sdev->host_box.size = hostbox_size;
- return 0;
-}
-EXPORT_SYMBOL(snd_sof_dsp_mailbox_init);
-
-int snd_sof_ipc_valid(struct snd_sof_dev *sdev)
-{
- struct sof_ipc_fw_ready *ready = &sdev->fw_ready;
- struct sof_ipc_fw_version *v = &ready->version;
-
- dev_info(sdev->dev,
- "Firmware info: version %d:%d:%d-%s\n", v->major, v->minor,
- v->micro, v->tag);
- dev_info(sdev->dev,
- "Firmware: ABI %d:%d:%d Kernel ABI %d:%d:%d\n",
- SOF_ABI_VERSION_MAJOR(v->abi_version),
- SOF_ABI_VERSION_MINOR(v->abi_version),
- SOF_ABI_VERSION_PATCH(v->abi_version),
- SOF_ABI_MAJOR, SOF_ABI_MINOR, SOF_ABI_PATCH);
-
- if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, v->abi_version)) {
- dev_err(sdev->dev, "error: incompatible FW ABI version\n");
- return -EINVAL;
+ /* check for mandatory ops */
+ if (!ops->tx_msg || !ops->rx_msg || !ops->set_get_data || !ops->get_reply) {
+ dev_err(sdev->dev, "Missing IPC message handling ops\n");
+ return NULL;
}
- if (v->abi_version > SOF_ABI_VERSION) {
- if (!IS_ENABLED(CONFIG_SND_SOC_SOF_STRICT_ABI_CHECKS)) {
- dev_warn(sdev->dev, "warn: FW ABI is more recent than kernel\n");
- } else {
- dev_err(sdev->dev, "error: FW ABI is more recent than kernel\n");
- return -EINVAL;
- }
+ if (!ops->fw_loader || !ops->fw_loader->validate ||
+ !ops->fw_loader->parse_ext_manifest) {
+ dev_err(sdev->dev, "Missing IPC firmware loading ops\n");
+ return NULL;
}
- if (ready->flags & SOF_IPC_INFO_BUILD) {
- dev_info(sdev->dev,
- "Firmware debug build %d on %s-%s - options:\n"
- " GDB: %s\n"
- " lock debug: %s\n"
- " lock vdebug: %s\n",
- v->build, v->date, v->time,
- (ready->flags & SOF_IPC_INFO_GDB) ?
- "enabled" : "disabled",
- (ready->flags & SOF_IPC_INFO_LOCKS) ?
- "enabled" : "disabled",
- (ready->flags & SOF_IPC_INFO_LOCKSV) ?
- "enabled" : "disabled");
+ if (!ops->pcm) {
+ dev_err(sdev->dev, "Missing IPC PCM ops\n");
+ return NULL;
}
- /* copy the fw_version into debugfs at first boot */
- memcpy(&sdev->fw_version, v, sizeof(*v));
-
- return 0;
-}
-EXPORT_SYMBOL(snd_sof_ipc_valid);
-
-struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev)
-{
- struct snd_sof_ipc *ipc;
- struct snd_sof_ipc_msg *msg;
-
- ipc = devm_kzalloc(sdev->dev, sizeof(*ipc), GFP_KERNEL);
- if (!ipc)
+ if (!ops->tplg || !ops->tplg->widget || !ops->tplg->control) {
+ dev_err(sdev->dev, "Missing IPC topology ops\n");
return NULL;
+ }
- mutex_init(&ipc->tx_mutex);
- ipc->sdev = sdev;
- msg = &ipc->msg;
-
- /* indicate that we aren't sending a message ATM */
- msg->ipc_complete = true;
-
- /* pre-allocate message data */
- msg->msg_data = devm_kzalloc(sdev->dev, SOF_IPC_MSG_MAX_SIZE,
- GFP_KERNEL);
- if (!msg->msg_data)
+ if (ops->fw_tracing && (!ops->fw_tracing->init || !ops->fw_tracing->suspend ||
+ !ops->fw_tracing->resume)) {
+ dev_err(sdev->dev, "Missing firmware tracing ops\n");
return NULL;
+ }
- msg->reply_data = devm_kzalloc(sdev->dev, SOF_IPC_MSG_MAX_SIZE,
- GFP_KERNEL);
- if (!msg->reply_data)
+ if (ops->init && ops->init(sdev))
return NULL;
- init_waitqueue_head(&msg->waitq);
+ ipc->ops = ops;
return ipc;
}
@@ -856,5 +228,8 @@ void snd_sof_ipc_free(struct snd_sof_dev *sdev)
mutex_lock(&ipc->tx_mutex);
ipc->disable_ipc_tx = true;
mutex_unlock(&ipc->tx_mutex);
+
+ if (ipc->ops->exit)
+ ipc->ops->exit(sdev);
}
EXPORT_SYMBOL(snd_sof_ipc_free);
diff --git a/sound/soc/sof/ipc3-control.c b/sound/soc/sof/ipc3-control.c
new file mode 100644
index 000000000000..a8deec7dc021
--- /dev/null
+++ b/sound/soc/sof/ipc3-control.c
@@ -0,0 +1,731 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2021 Intel Corporation. All rights reserved.
+//
+//
+
+#include "sof-priv.h"
+#include "sof-audio.h"
+#include "ipc3-priv.h"
+
+/* IPC set()/get() for kcontrols. */
+static int sof_ipc3_set_get_kcontrol_data(struct snd_sof_control *scontrol,
+ bool set, bool lock)
+{
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scontrol->scomp);
+ struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
+ const struct sof_ipc_ops *iops = sdev->ipc->ops;
+ enum sof_ipc_ctrl_type ctrl_type;
+ struct snd_sof_widget *swidget;
+ bool widget_found = false;
+ u32 ipc_cmd, msg_bytes;
+ int ret = 0;
+
+ list_for_each_entry(swidget, &sdev->widget_list, list) {
+ if (swidget->comp_id == scontrol->comp_id) {
+ widget_found = true;
+ break;
+ }
+ }
+
+ if (!widget_found) {
+ dev_err(sdev->dev, "%s: can't find widget with id %d\n", __func__,
+ scontrol->comp_id);
+ return -EINVAL;
+ }
+
+ if (lock)
+ mutex_lock(&swidget->setup_mutex);
+ else
+ lockdep_assert_held(&swidget->setup_mutex);
+
+ /*
+ * Volatile controls should always be part of static pipelines and the
+ * widget use_count would always be > 0 in this case. For the others,
+ * just return the cached value if the widget is not set up.
+ */
+ if (!swidget->use_count)
+ goto unlock;
+
+ /*
+ * Select the IPC cmd and the ctrl_type based on the ctrl_cmd and the
+ * direction
+ * Note: SOF_CTRL_TYPE_VALUE_COMP_* is not used and supported currently
+ * for ctrl_type
+ */
+ if (cdata->cmd == SOF_CTRL_CMD_BINARY) {
+ ipc_cmd = set ? SOF_IPC_COMP_SET_DATA : SOF_IPC_COMP_GET_DATA;
+ ctrl_type = set ? SOF_CTRL_TYPE_DATA_SET : SOF_CTRL_TYPE_DATA_GET;
+ } else {
+ ipc_cmd = set ? SOF_IPC_COMP_SET_VALUE : SOF_IPC_COMP_GET_VALUE;
+ ctrl_type = set ? SOF_CTRL_TYPE_VALUE_CHAN_SET : SOF_CTRL_TYPE_VALUE_CHAN_GET;
+ }
+
+ cdata->rhdr.hdr.cmd = SOF_IPC_GLB_COMP_MSG | ipc_cmd;
+ cdata->type = ctrl_type;
+ cdata->comp_id = scontrol->comp_id;
+ cdata->msg_index = 0;
+
+ /* calculate header and data size */
+ switch (cdata->type) {
+ case SOF_CTRL_TYPE_VALUE_CHAN_GET:
+ case SOF_CTRL_TYPE_VALUE_CHAN_SET:
+ cdata->num_elems = scontrol->num_channels;
+
+ msg_bytes = scontrol->num_channels *
+ sizeof(struct sof_ipc_ctrl_value_chan);
+ msg_bytes += sizeof(struct sof_ipc_ctrl_data);
+ break;
+ case SOF_CTRL_TYPE_DATA_GET:
+ case SOF_CTRL_TYPE_DATA_SET:
+ cdata->num_elems = cdata->data->size;
+
+ msg_bytes = cdata->data->size;
+ msg_bytes += sizeof(struct sof_ipc_ctrl_data) +
+ sizeof(struct sof_abi_hdr);
+ break;
+ default:
+ ret = -EINVAL;
+ goto unlock;
+ }
+
+ cdata->rhdr.hdr.size = msg_bytes;
+ cdata->elems_remaining = 0;
+
+ ret = iops->set_get_data(sdev, cdata, cdata->rhdr.hdr.size, set);
+ if (!set)
+ goto unlock;
+
+ /* It is a set-data operation, and we have a backup that we can restore */
+ if (ret < 0) {
+ if (!scontrol->old_ipc_control_data)
+ goto unlock;
+ /*
+ * Current ipc_control_data is not valid, we use the last known good
+ * configuration
+ */
+ memcpy(scontrol->ipc_control_data, scontrol->old_ipc_control_data,
+ scontrol->max_size);
+ kfree(scontrol->old_ipc_control_data);
+ scontrol->old_ipc_control_data = NULL;
+ /* Send the last known good configuration to firmware */
+ ret = iops->set_get_data(sdev, cdata, cdata->rhdr.hdr.size, set);
+ if (ret < 0)
+ goto unlock;
+ }
+
+unlock:
+ if (lock)
+ mutex_unlock(&swidget->setup_mutex);
+
+ return ret;
+}
+
+static void sof_ipc3_refresh_control(struct snd_sof_control *scontrol)
+{
+ struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ int ret;
+
+ if (!scontrol->comp_data_dirty)
+ return;
+
+ if (!pm_runtime_active(scomp->dev))
+ return;
+
+ /* set the ABI header values */
+ cdata->data->magic = SOF_ABI_MAGIC;
+ cdata->data->abi = SOF_ABI_VERSION;
+
+ /* refresh the component data from DSP */
+ scontrol->comp_data_dirty = false;
+ ret = sof_ipc3_set_get_kcontrol_data(scontrol, false, true);
+ if (ret < 0) {
+ dev_err(scomp->dev, "Failed to get control data: %d\n", ret);
+
+ /* Set the flag to re-try next time to get the data */
+ scontrol->comp_data_dirty = true;
+ }
+}
+
+static int sof_ipc3_volume_get(struct snd_sof_control *scontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
+ unsigned int channels = scontrol->num_channels;
+ unsigned int i;
+
+ sof_ipc3_refresh_control(scontrol);
+
+ /* read back each channel */
+ for (i = 0; i < channels; i++)
+ ucontrol->value.integer.value[i] = ipc_to_mixer(cdata->chanv[i].value,
+ scontrol->volume_table,
+ scontrol->max + 1);
+
+ return 0;
+}
+
+static bool sof_ipc3_volume_put(struct snd_sof_control *scontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ unsigned int channels = scontrol->num_channels;
+ unsigned int i;
+ bool change = false;
+
+ /* update each channel */
+ for (i = 0; i < channels; i++) {
+ u32 value = mixer_to_ipc(ucontrol->value.integer.value[i],
+ scontrol->volume_table, scontrol->max + 1);
+
+ change = change || (value != cdata->chanv[i].value);
+ cdata->chanv[i].channel = i;
+ cdata->chanv[i].value = value;
+ }
+
+ /* notify DSP of mixer updates */
+ if (pm_runtime_active(scomp->dev)) {
+ int ret = sof_ipc3_set_get_kcontrol_data(scontrol, true, true);
+
+ if (ret < 0) {
+ dev_err(scomp->dev, "Failed to set mixer updates for %s\n",
+ scontrol->name);
+ return false;
+ }
+ }
+
+ return change;
+}
+
+static int sof_ipc3_switch_get(struct snd_sof_control *scontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
+ unsigned int channels = scontrol->num_channels;
+ unsigned int i;
+
+ sof_ipc3_refresh_control(scontrol);
+
+ /* read back each channel */
+ for (i = 0; i < channels; i++)
+ ucontrol->value.integer.value[i] = cdata->chanv[i].value;
+
+ return 0;
+}
+
+static bool sof_ipc3_switch_put(struct snd_sof_control *scontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ unsigned int channels = scontrol->num_channels;
+ unsigned int i;
+ bool change = false;
+ u32 value;
+
+ /* update each channel */
+ for (i = 0; i < channels; i++) {
+ value = ucontrol->value.integer.value[i];
+ change = change || (value != cdata->chanv[i].value);
+ cdata->chanv[i].channel = i;
+ cdata->chanv[i].value = value;
+ }
+
+ /* notify DSP of mixer updates */
+ if (pm_runtime_active(scomp->dev)) {
+ int ret = sof_ipc3_set_get_kcontrol_data(scontrol, true, true);
+
+ if (ret < 0) {
+ dev_err(scomp->dev, "Failed to set mixer updates for %s\n",
+ scontrol->name);
+ return false;
+ }
+ }
+
+ return change;
+}
+
+static int sof_ipc3_enum_get(struct snd_sof_control *scontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
+ unsigned int channels = scontrol->num_channels;
+ unsigned int i;
+
+ sof_ipc3_refresh_control(scontrol);
+
+ /* read back each channel */
+ for (i = 0; i < channels; i++)
+ ucontrol->value.enumerated.item[i] = cdata->chanv[i].value;
+
+ return 0;
+}
+
+static bool sof_ipc3_enum_put(struct snd_sof_control *scontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ unsigned int channels = scontrol->num_channels;
+ unsigned int i;
+ bool change = false;
+ u32 value;
+
+ /* update each channel */
+ for (i = 0; i < channels; i++) {
+ value = ucontrol->value.enumerated.item[i];
+ change = change || (value != cdata->chanv[i].value);
+ cdata->chanv[i].channel = i;
+ cdata->chanv[i].value = value;
+ }
+
+ /* notify DSP of enum updates */
+ if (pm_runtime_active(scomp->dev)) {
+ int ret = sof_ipc3_set_get_kcontrol_data(scontrol, true, true);
+
+ if (ret < 0) {
+ dev_err(scomp->dev, "Failed to set enum updates for %s\n",
+ scontrol->name);
+ return false;
+ }
+ }
+
+ return change;
+}
+
+static int sof_ipc3_bytes_get(struct snd_sof_control *scontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ struct sof_abi_hdr *data = cdata->data;
+ size_t size;
+
+ sof_ipc3_refresh_control(scontrol);
+
+ if (scontrol->max_size > sizeof(ucontrol->value.bytes.data)) {
+ dev_err_ratelimited(scomp->dev, "data max %zu exceeds ucontrol data array size\n",
+ scontrol->max_size);
+ return -EINVAL;
+ }
+
+ /* be->max has been verified to be >= sizeof(struct sof_abi_hdr) */
+ if (data->size > scontrol->max_size - sizeof(*data)) {
+ dev_err_ratelimited(scomp->dev,
+ "%u bytes of control data is invalid, max is %zu\n",
+ data->size, scontrol->max_size - sizeof(*data));
+ return -EINVAL;
+ }
+
+ size = data->size + sizeof(*data);
+
+ /* copy back to kcontrol */
+ memcpy(ucontrol->value.bytes.data, data, size);
+
+ return 0;
+}
+
+static int sof_ipc3_bytes_put(struct snd_sof_control *scontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ struct sof_abi_hdr *data = cdata->data;
+ size_t size;
+
+ if (scontrol->max_size > sizeof(ucontrol->value.bytes.data)) {
+ dev_err_ratelimited(scomp->dev, "data max %zu exceeds ucontrol data array size\n",
+ scontrol->max_size);
+ return -EINVAL;
+ }
+
+ /* scontrol->max_size has been verified to be >= sizeof(struct sof_abi_hdr) */
+ if (data->size > scontrol->max_size - sizeof(*data)) {
+ dev_err_ratelimited(scomp->dev, "data size too big %u bytes max is %zu\n",
+ data->size, scontrol->max_size - sizeof(*data));
+ return -EINVAL;
+ }
+
+ size = data->size + sizeof(*data);
+
+ /* copy from kcontrol */
+ memcpy(data, ucontrol->value.bytes.data, size);
+
+ /* notify DSP of byte control updates */
+ if (pm_runtime_active(scomp->dev))
+ return sof_ipc3_set_get_kcontrol_data(scontrol, true, true);
+
+ return 0;
+}
+
+static int sof_ipc3_bytes_ext_put(struct snd_sof_control *scontrol,
+ const unsigned int __user *binary_data,
+ unsigned int size)
+{
+ const struct snd_ctl_tlv __user *tlvd = (const struct snd_ctl_tlv __user *)binary_data;
+ struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ struct snd_ctl_tlv header;
+ int ret = -EINVAL;
+
+ /*
+ * The beginning of bytes data contains a header from where
+ * the length (as bytes) is needed to know the correct copy
+ * length of data from tlvd->tlv.
+ */
+ if (copy_from_user(&header, tlvd, sizeof(struct snd_ctl_tlv)))
+ return -EFAULT;
+
+ /* make sure TLV info is consistent */
+ if (header.length + sizeof(struct snd_ctl_tlv) > size) {
+ dev_err_ratelimited(scomp->dev, "Inconsistent TLV, data %d + header %zu > %d\n",
+ header.length, sizeof(struct snd_ctl_tlv), size);
+ return -EINVAL;
+ }
+
+ /* be->max is coming from topology */
+ if (header.length > scontrol->max_size) {
+ dev_err_ratelimited(scomp->dev, "Bytes data size %d exceeds max %zu\n",
+ header.length, scontrol->max_size);
+ return -EINVAL;
+ }
+
+ /* Check that header id matches the command */
+ if (header.numid != cdata->cmd) {
+ dev_err_ratelimited(scomp->dev, "Incorrect command for bytes put %d\n",
+ header.numid);
+ return -EINVAL;
+ }
+
+ if (!scontrol->old_ipc_control_data) {
+ /* Create a backup of the current, valid bytes control */
+ scontrol->old_ipc_control_data = kmemdup(scontrol->ipc_control_data,
+ scontrol->max_size, GFP_KERNEL);
+ if (!scontrol->old_ipc_control_data)
+ return -ENOMEM;
+ }
+
+ if (copy_from_user(cdata->data, tlvd->tlv, header.length)) {
+ ret = -EFAULT;
+ goto err_restore;
+ }
+
+ if (cdata->data->magic != SOF_ABI_MAGIC) {
+ dev_err_ratelimited(scomp->dev, "Wrong ABI magic 0x%08x\n", cdata->data->magic);
+ goto err_restore;
+ }
+
+ if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, cdata->data->abi)) {
+ dev_err_ratelimited(scomp->dev, "Incompatible ABI version 0x%08x\n",
+ cdata->data->abi);
+ goto err_restore;
+ }
+
+ /* be->max has been verified to be >= sizeof(struct sof_abi_hdr) */
+ if (cdata->data->size > scontrol->max_size - sizeof(struct sof_abi_hdr)) {
+ dev_err_ratelimited(scomp->dev, "Mismatch in ABI data size (truncated?)\n");
+ goto err_restore;
+ }
+
+ /* notify DSP of byte control updates */
+ if (pm_runtime_active(scomp->dev)) {
+ /* Actually send the data to the DSP; this is an opportunity to validate the data */
+ return sof_ipc3_set_get_kcontrol_data(scontrol, true, true);
+ }
+
+ return 0;
+
+err_restore:
+ /* If we have an issue, we restore the old, valid bytes control data */
+ if (scontrol->old_ipc_control_data) {
+ memcpy(cdata->data, scontrol->old_ipc_control_data, scontrol->max_size);
+ kfree(scontrol->old_ipc_control_data);
+ scontrol->old_ipc_control_data = NULL;
+ }
+ return ret;
+}
+
+static int _sof_ipc3_bytes_ext_get(struct snd_sof_control *scontrol,
+ const unsigned int __user *binary_data,
+ unsigned int size, bool from_dsp)
+{
+ struct snd_ctl_tlv __user *tlvd = (struct snd_ctl_tlv __user *)binary_data;
+ struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ struct snd_ctl_tlv header;
+ size_t data_size;
+
+ /*
+ * Decrement the limit by ext bytes header size to
+ * ensure the user space buffer is not exceeded.
+ */
+ if (size < sizeof(struct snd_ctl_tlv))
+ return -ENOSPC;
+
+ size -= sizeof(struct snd_ctl_tlv);
+
+ /* set the ABI header values */
+ cdata->data->magic = SOF_ABI_MAGIC;
+ cdata->data->abi = SOF_ABI_VERSION;
+
+ /* get all the component data from DSP */
+ if (from_dsp) {
+ int ret = sof_ipc3_set_get_kcontrol_data(scontrol, false, true);
+
+ if (ret < 0)
+ return ret;
+ }
+
+ /* check data size doesn't exceed max coming from topology */
+ if (cdata->data->size > scontrol->max_size - sizeof(struct sof_abi_hdr)) {
+ dev_err_ratelimited(scomp->dev, "User data size %d exceeds max size %zu\n",
+ cdata->data->size,
+ scontrol->max_size - sizeof(struct sof_abi_hdr));
+ return -EINVAL;
+ }
+
+ data_size = cdata->data->size + sizeof(struct sof_abi_hdr);
+
+ /* make sure we don't exceed size provided by user space for data */
+ if (data_size > size)
+ return -ENOSPC;
+
+ header.numid = cdata->cmd;
+ header.length = data_size;
+ if (copy_to_user(tlvd, &header, sizeof(struct snd_ctl_tlv)))
+ return -EFAULT;
+
+ if (copy_to_user(tlvd->tlv, cdata->data, data_size))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int sof_ipc3_bytes_ext_get(struct snd_sof_control *scontrol,
+ const unsigned int __user *binary_data, unsigned int size)
+{
+ return _sof_ipc3_bytes_ext_get(scontrol, binary_data, size, false);
+}
+
+static int sof_ipc3_bytes_ext_volatile_get(struct snd_sof_control *scontrol,
+ const unsigned int __user *binary_data,
+ unsigned int size)
+{
+ return _sof_ipc3_bytes_ext_get(scontrol, binary_data, size, true);
+}
+
+static void snd_sof_update_control(struct snd_sof_control *scontrol,
+ struct sof_ipc_ctrl_data *cdata)
+{
+ struct snd_soc_component *scomp = scontrol->scomp;
+ struct sof_ipc_ctrl_data *local_cdata;
+ int i;
+
+ local_cdata = scontrol->ipc_control_data;
+
+ if (cdata->cmd == SOF_CTRL_CMD_BINARY) {
+ if (cdata->num_elems != local_cdata->data->size) {
+ dev_err(scomp->dev, "cdata binary size mismatch %u - %u\n",
+ cdata->num_elems, local_cdata->data->size);
+ return;
+ }
+
+ /* copy the new binary data */
+ memcpy(local_cdata->data, cdata->data, cdata->num_elems);
+ } else if (cdata->num_elems != scontrol->num_channels) {
+ dev_err(scomp->dev, "cdata channel count mismatch %u - %d\n",
+ cdata->num_elems, scontrol->num_channels);
+ } else {
+ /* copy the new values */
+ for (i = 0; i < cdata->num_elems; i++)
+ local_cdata->chanv[i].value = cdata->chanv[i].value;
+ }
+}
+
+static void sof_ipc3_control_update(struct snd_sof_dev *sdev, void *ipc_control_message)
+{
+ struct sof_ipc_ctrl_data *cdata = ipc_control_message;
+ struct snd_soc_dapm_widget *widget;
+ struct snd_sof_control *scontrol;
+ struct snd_sof_widget *swidget;
+ struct snd_kcontrol *kc = NULL;
+ struct soc_mixer_control *sm;
+ struct soc_bytes_ext *be;
+ size_t expected_size;
+ struct soc_enum *se;
+ bool found = false;
+ int i, type;
+
+ if (cdata->type == SOF_CTRL_TYPE_VALUE_COMP_GET ||
+ cdata->type == SOF_CTRL_TYPE_VALUE_COMP_SET) {
+ dev_err(sdev->dev, "Component data is not supported in control notification\n");
+ return;
+ }
+
+ /* Find the swidget first */
+ list_for_each_entry(swidget, &sdev->widget_list, list) {
+ if (swidget->comp_id == cdata->comp_id) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ return;
+
+ /* Translate SOF cmd to TPLG type */
+ switch (cdata->cmd) {
+ case SOF_CTRL_CMD_VOLUME:
+ case SOF_CTRL_CMD_SWITCH:
+ type = SND_SOC_TPLG_TYPE_MIXER;
+ break;
+ case SOF_CTRL_CMD_BINARY:
+ type = SND_SOC_TPLG_TYPE_BYTES;
+ break;
+ case SOF_CTRL_CMD_ENUM:
+ type = SND_SOC_TPLG_TYPE_ENUM;
+ break;
+ default:
+ dev_err(sdev->dev, "Unknown cmd %u in %s\n", cdata->cmd, __func__);
+ return;
+ }
+
+ widget = swidget->widget;
+ for (i = 0; i < widget->num_kcontrols; i++) {
+ /* skip non matching types or non matching indexes within type */
+ if (widget->dobj.widget.kcontrol_type[i] == type &&
+ widget->kcontrol_news[i].index == cdata->index) {
+ kc = widget->kcontrols[i];
+ break;
+ }
+ }
+
+ if (!kc)
+ return;
+
+ switch (cdata->cmd) {
+ case SOF_CTRL_CMD_VOLUME:
+ case SOF_CTRL_CMD_SWITCH:
+ sm = (struct soc_mixer_control *)kc->private_value;
+ scontrol = sm->dobj.private;
+ break;
+ case SOF_CTRL_CMD_BINARY:
+ be = (struct soc_bytes_ext *)kc->private_value;
+ scontrol = be->dobj.private;
+ break;
+ case SOF_CTRL_CMD_ENUM:
+ se = (struct soc_enum *)kc->private_value;
+ scontrol = se->dobj.private;
+ break;
+ default:
+ return;
+ }
+
+ expected_size = sizeof(struct sof_ipc_ctrl_data);
+ switch (cdata->type) {
+ case SOF_CTRL_TYPE_VALUE_CHAN_GET:
+ case SOF_CTRL_TYPE_VALUE_CHAN_SET:
+ expected_size += cdata->num_elems *
+ sizeof(struct sof_ipc_ctrl_value_chan);
+ break;
+ case SOF_CTRL_TYPE_DATA_GET:
+ case SOF_CTRL_TYPE_DATA_SET:
+ expected_size += cdata->num_elems + sizeof(struct sof_abi_hdr);
+ break;
+ default:
+ return;
+ }
+
+ if (cdata->rhdr.hdr.size != expected_size) {
+ dev_err(sdev->dev, "Component notification size mismatch\n");
+ return;
+ }
+
+ if (cdata->num_elems)
+ /*
+ * The message includes the updated value/data, update the
+ * control's local cache using the received notification
+ */
+ snd_sof_update_control(scontrol, cdata);
+ else
+ /* Mark the scontrol that the value/data is changed in SOF */
+ scontrol->comp_data_dirty = true;
+
+ snd_ctl_notify_one(swidget->scomp->card->snd_card, SNDRV_CTL_EVENT_MASK_VALUE, kc, 0);
+}
+
+static int sof_ipc3_widget_kcontrol_setup(struct snd_sof_dev *sdev,
+ struct snd_sof_widget *swidget)
+{
+ struct snd_sof_control *scontrol;
+ int ret;
+
+ /* set up all controls for the widget */
+ list_for_each_entry(scontrol, &sdev->kcontrol_list, list)
+ if (scontrol->comp_id == swidget->comp_id) {
+ /* set kcontrol data in DSP */
+ ret = sof_ipc3_set_get_kcontrol_data(scontrol, true, false);
+ if (ret < 0) {
+ dev_err(sdev->dev,
+ "kcontrol %d set up failed for widget %s\n",
+ scontrol->comp_id, swidget->widget->name);
+ return ret;
+ }
+
+ /*
+ * Read back the data from the DSP for static widgets.
+ * This is particularly useful for binary kcontrols
+ * associated with static pipeline widgets to initialize
+ * the data size to match that in the DSP.
+ */
+ if (swidget->dynamic_pipeline_widget)
+ continue;
+
+ ret = sof_ipc3_set_get_kcontrol_data(scontrol, false, false);
+ if (ret < 0)
+ dev_warn(sdev->dev,
+ "kcontrol %d read failed for widget %s\n",
+ scontrol->comp_id, swidget->widget->name);
+ }
+
+ return 0;
+}
+
+static int
+sof_ipc3_set_up_volume_table(struct snd_sof_control *scontrol, int tlv[SOF_TLV_ITEMS], int size)
+{
+ int i;
+
+ /* init the volume table */
+ scontrol->volume_table = kcalloc(size, sizeof(u32), GFP_KERNEL);
+ if (!scontrol->volume_table)
+ return -ENOMEM;
+
+ /* populate the volume table */
+ for (i = 0; i < size ; i++)
+ scontrol->volume_table[i] = vol_compute_gain(i, tlv);
+
+ return 0;
+}
+
+const struct sof_ipc_tplg_control_ops tplg_ipc3_control_ops = {
+ .volume_put = sof_ipc3_volume_put,
+ .volume_get = sof_ipc3_volume_get,
+ .switch_put = sof_ipc3_switch_put,
+ .switch_get = sof_ipc3_switch_get,
+ .enum_put = sof_ipc3_enum_put,
+ .enum_get = sof_ipc3_enum_get,
+ .bytes_put = sof_ipc3_bytes_put,
+ .bytes_get = sof_ipc3_bytes_get,
+ .bytes_ext_put = sof_ipc3_bytes_ext_put,
+ .bytes_ext_get = sof_ipc3_bytes_ext_get,
+ .bytes_ext_volatile_get = sof_ipc3_bytes_ext_volatile_get,
+ .update = sof_ipc3_control_update,
+ .widget_kcontrol_setup = sof_ipc3_widget_kcontrol_setup,
+ .set_up_volume_table = sof_ipc3_set_up_volume_table,
+};
diff --git a/sound/soc/sof/ipc3-dtrace.c b/sound/soc/sof/ipc3-dtrace.c
new file mode 100644
index 000000000000..0dca139322f3
--- /dev/null
+++ b/sound/soc/sof/ipc3-dtrace.c
@@ -0,0 +1,665 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2022 Intel Corporation. All rights reserved.
+//
+// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
+
+#include <linux/debugfs.h>
+#include <linux/sched/signal.h>
+#include "sof-priv.h"
+#include "sof-audio.h"
+#include "ops.h"
+#include "sof-utils.h"
+#include "ipc3-priv.h"
+
+#define TRACE_FILTER_ELEMENTS_PER_ENTRY 4
+#define TRACE_FILTER_MAX_CONFIG_STRING_LENGTH 1024
+
+enum sof_dtrace_state {
+ SOF_DTRACE_DISABLED,
+ SOF_DTRACE_STOPPED,
+ SOF_DTRACE_INITIALIZING,
+ SOF_DTRACE_ENABLED,
+};
+
+struct sof_dtrace_priv {
+ struct snd_dma_buffer dmatb;
+ struct snd_dma_buffer dmatp;
+ int dma_trace_pages;
+ wait_queue_head_t trace_sleep;
+ u32 host_offset;
+ bool dtrace_error;
+ bool dtrace_draining;
+ enum sof_dtrace_state dtrace_state;
+};
+
+static bool trace_pos_update_expected(struct sof_dtrace_priv *priv)
+{
+ if (priv->dtrace_state == SOF_DTRACE_ENABLED ||
+ priv->dtrace_state == SOF_DTRACE_INITIALIZING)
+ return true;
+
+ return false;
+}
+
+static int trace_filter_append_elem(struct snd_sof_dev *sdev, u32 key, u32 value,
+ struct sof_ipc_trace_filter_elem *elem_list,
+ int capacity, int *counter)
+{
+ if (*counter >= capacity)
+ return -ENOMEM;
+
+ elem_list[*counter].key = key;
+ elem_list[*counter].value = value;
+ ++*counter;
+
+ return 0;
+}
+
+static int trace_filter_parse_entry(struct snd_sof_dev *sdev, const char *line,
+ struct sof_ipc_trace_filter_elem *elem,
+ int capacity, int *counter)
+{
+ int log_level, pipe_id, comp_id, read, ret;
+ int len = strlen(line);
+ int cnt = *counter;
+ u32 uuid_id;
+
+ /* ignore empty content */
+ ret = sscanf(line, " %n", &read);
+ if (!ret && read == len)
+ return len;
+
+ ret = sscanf(line, " %d %x %d %d %n", &log_level, &uuid_id, &pipe_id, &comp_id, &read);
+ if (ret != TRACE_FILTER_ELEMENTS_PER_ENTRY || read != len) {
+ dev_err(sdev->dev, "Invalid trace filter entry '%s'\n", line);
+ return -EINVAL;
+ }
+
+ if (uuid_id > 0) {
+ ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_BY_UUID,
+ uuid_id, elem, capacity, &cnt);
+ if (ret)
+ return ret;
+ }
+ if (pipe_id >= 0) {
+ ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_BY_PIPE,
+ pipe_id, elem, capacity, &cnt);
+ if (ret)
+ return ret;
+ }
+ if (comp_id >= 0) {
+ ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_BY_COMP,
+ comp_id, elem, capacity, &cnt);
+ if (ret)
+ return ret;
+ }
+
+ ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_SET_LEVEL |
+ SOF_IPC_TRACE_FILTER_ELEM_FIN,
+ log_level, elem, capacity, &cnt);
+ if (ret)
+ return ret;
+
+ /* update counter only when parsing whole entry passed */
+ *counter = cnt;
+
+ return len;
+}
+
+static int trace_filter_parse(struct snd_sof_dev *sdev, char *string,
+ int *out_elem_cnt,
+ struct sof_ipc_trace_filter_elem **out)
+{
+ static const char entry_delimiter[] = ";";
+ char *entry = string;
+ int capacity = 0;
+ int entry_len;
+ int cnt = 0;
+
+ /*
+ * Each entry contains at least 1, up to TRACE_FILTER_ELEMENTS_PER_ENTRY
+ * IPC elements, depending on content. Calculate IPC elements capacity
+ * for the input string where each element is set.
+ */
+ while (entry) {
+ capacity += TRACE_FILTER_ELEMENTS_PER_ENTRY;
+ entry = strchr(entry + 1, entry_delimiter[0]);
+ }
+ *out = kmalloc(capacity * sizeof(**out), GFP_KERNEL);
+ if (!*out)
+ return -ENOMEM;
+
+ /* split input string by ';', and parse each entry separately in trace_filter_parse_entry */
+ while ((entry = strsep(&string, entry_delimiter))) {
+ entry_len = trace_filter_parse_entry(sdev, entry, *out, capacity, &cnt);
+ if (entry_len < 0) {
+ dev_err(sdev->dev,
+ "Parsing filter entry '%s' failed with %d\n",
+ entry, entry_len);
+ return -EINVAL;
+ }
+ }
+
+ *out_elem_cnt = cnt;
+
+ return 0;
+}
+
+static int ipc3_trace_update_filter(struct snd_sof_dev *sdev, int num_elems,
+ struct sof_ipc_trace_filter_elem *elems)
+{
+ struct sof_ipc_trace_filter *msg;
+ size_t size;
+ int ret;
+
+ size = struct_size(msg, elems, num_elems);
+ if (size > SOF_IPC_MSG_MAX_SIZE)
+ return -EINVAL;
+
+ msg = kmalloc(size, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ msg->hdr.size = size;
+ msg->hdr.cmd = SOF_IPC_GLB_TRACE_MSG | SOF_IPC_TRACE_FILTER_UPDATE;
+ msg->elem_cnt = num_elems;
+ memcpy(&msg->elems[0], elems, num_elems * sizeof(*elems));
+
+ ret = pm_runtime_resume_and_get(sdev->dev);
+ if (ret < 0 && ret != -EACCES) {
+ dev_err(sdev->dev, "enabling device failed: %d\n", ret);
+ goto error;
+ }
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, msg, msg->hdr.size);
+ pm_runtime_mark_last_busy(sdev->dev);
+ pm_runtime_put_autosuspend(sdev->dev);
+
+error:
+ kfree(msg);
+ return ret;
+}
+
+static ssize_t dfsentry_trace_filter_write(struct file *file, const char __user *from,
+ size_t count, loff_t *ppos)
+{
+ struct snd_sof_dfsentry *dfse = file->private_data;
+ struct sof_ipc_trace_filter_elem *elems = NULL;
+ struct snd_sof_dev *sdev = dfse->sdev;
+ int num_elems;
+ char *string;
+ int ret;
+
+ if (count > TRACE_FILTER_MAX_CONFIG_STRING_LENGTH) {
+ dev_err(sdev->dev, "%s too long input, %zu > %d\n", __func__, count,
+ TRACE_FILTER_MAX_CONFIG_STRING_LENGTH);
+ return -EINVAL;
+ }
+
+ string = memdup_user_nul(from, count);
+ if (IS_ERR(string))
+ return PTR_ERR(string);
+
+ ret = trace_filter_parse(sdev, string, &num_elems, &elems);
+ if (ret < 0)
+ goto error;
+
+ if (num_elems) {
+ ret = ipc3_trace_update_filter(sdev, num_elems, elems);
+ if (ret < 0) {
+ dev_err(sdev->dev, "Filter update failed: %d\n", ret);
+ goto error;
+ }
+ }
+ ret = count;
+error:
+ kfree(string);
+ kfree(elems);
+ return ret;
+}
+
+static const struct file_operations sof_dfs_trace_filter_fops = {
+ .open = simple_open,
+ .write = dfsentry_trace_filter_write,
+ .llseek = default_llseek,
+};
+
+static int debugfs_create_trace_filter(struct snd_sof_dev *sdev)
+{
+ struct snd_sof_dfsentry *dfse;
+
+ dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL);
+ if (!dfse)
+ return -ENOMEM;
+
+ dfse->sdev = sdev;
+ dfse->type = SOF_DFSENTRY_TYPE_BUF;
+
+ debugfs_create_file("filter", 0200, sdev->debugfs_root, dfse,
+ &sof_dfs_trace_filter_fops);
+ /* add to dfsentry list */
+ list_add(&dfse->list, &sdev->dfsentry_list);
+
+ return 0;
+}
+
+static bool sof_dtrace_set_host_offset(struct sof_dtrace_priv *priv, u32 new_offset)
+{
+ u32 host_offset = READ_ONCE(priv->host_offset);
+
+ if (host_offset != new_offset) {
+ /* This is a bit paranoid and unlikely that it is needed */
+ u32 ret = cmpxchg(&priv->host_offset, host_offset, new_offset);
+
+ if (ret == host_offset)
+ return true;
+ }
+
+ return false;
+}
+
+static size_t sof_dtrace_avail(struct snd_sof_dev *sdev,
+ loff_t pos, size_t buffer_size)
+{
+ struct sof_dtrace_priv *priv = sdev->fw_trace_data;
+ loff_t host_offset = READ_ONCE(priv->host_offset);
+
+ /*
+ * If host offset is less than local pos, it means write pointer of
+ * host DMA buffer has been wrapped. We should output the trace data
+ * at the end of host DMA buffer at first.
+ */
+ if (host_offset < pos)
+ return buffer_size - pos;
+
+ /* If there is available trace data now, it is unnecessary to wait. */
+ if (host_offset > pos)
+ return host_offset - pos;
+
+ return 0;
+}
+
+static size_t sof_wait_dtrace_avail(struct snd_sof_dev *sdev, loff_t pos,
+ size_t buffer_size)
+{
+ size_t ret = sof_dtrace_avail(sdev, pos, buffer_size);
+ struct sof_dtrace_priv *priv = sdev->fw_trace_data;
+ wait_queue_entry_t wait;
+
+ /* data immediately available */
+ if (ret)
+ return ret;
+
+ if (priv->dtrace_draining && !trace_pos_update_expected(priv)) {
+ /*
+ * tracing has ended and all traces have been
+ * read by client, return EOF
+ */
+ priv->dtrace_draining = false;
+ return 0;
+ }
+
+ /* wait for available trace data from FW */
+ init_waitqueue_entry(&wait, current);
+ set_current_state(TASK_INTERRUPTIBLE);
+ add_wait_queue(&priv->trace_sleep, &wait);
+
+ if (!signal_pending(current)) {
+ /* set timeout to max value, no error code */
+ schedule_timeout(MAX_SCHEDULE_TIMEOUT);
+ }
+ remove_wait_queue(&priv->trace_sleep, &wait);
+
+ return sof_dtrace_avail(sdev, pos, buffer_size);
+}
+
+static ssize_t dfsentry_dtrace_read(struct file *file, char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct snd_sof_dfsentry *dfse = file->private_data;
+ struct snd_sof_dev *sdev = dfse->sdev;
+ struct sof_dtrace_priv *priv = sdev->fw_trace_data;
+ unsigned long rem;
+ loff_t lpos = *ppos;
+ size_t avail, buffer_size = dfse->size;
+ u64 lpos_64;
+
+ /* make sure we know about any failures on the DSP side */
+ priv->dtrace_error = false;
+
+ /* check pos and count */
+ if (lpos < 0)
+ return -EINVAL;
+ if (!count)
+ return 0;
+
+ /* check for buffer wrap and count overflow */
+ lpos_64 = lpos;
+ lpos = do_div(lpos_64, buffer_size);
+
+ /* get available count based on current host offset */
+ avail = sof_wait_dtrace_avail(sdev, lpos, buffer_size);
+ if (priv->dtrace_error) {
+ dev_err(sdev->dev, "trace IO error\n");
+ return -EIO;
+ }
+
+ /* no new trace data */
+ if (!avail)
+ return 0;
+
+ /* make sure count is <= avail */
+ if (count > avail)
+ count = avail;
+
+ /*
+ * make sure that all trace data is available for the CPU as the trace
+ * data buffer might be allocated from non consistent memory.
+ * Note: snd_dma_buffer_sync() is called for normal audio playback and
+ * capture streams also.
+ */
+ snd_dma_buffer_sync(&priv->dmatb, SNDRV_DMA_SYNC_CPU);
+ /* copy available trace data to debugfs */
+ rem = copy_to_user(buffer, ((u8 *)(dfse->buf) + lpos), count);
+ if (rem)
+ return -EFAULT;
+
+ *ppos += count;
+
+ /* move debugfs reading position */
+ return count;
+}
+
+static int dfsentry_dtrace_release(struct inode *inode, struct file *file)
+{
+ struct snd_sof_dfsentry *dfse = inode->i_private;
+ struct snd_sof_dev *sdev = dfse->sdev;
+ struct sof_dtrace_priv *priv = sdev->fw_trace_data;
+
+ /* avoid duplicate traces at next open */
+ if (priv->dtrace_state != SOF_DTRACE_ENABLED)
+ sof_dtrace_set_host_offset(priv, 0);
+
+ return 0;
+}
+
+static const struct file_operations sof_dfs_dtrace_fops = {
+ .open = simple_open,
+ .read = dfsentry_dtrace_read,
+ .llseek = default_llseek,
+ .release = dfsentry_dtrace_release,
+};
+
+static int debugfs_create_dtrace(struct snd_sof_dev *sdev)
+{
+ struct sof_dtrace_priv *priv;
+ struct snd_sof_dfsentry *dfse;
+ int ret;
+
+ if (!sdev)
+ return -EINVAL;
+
+ priv = sdev->fw_trace_data;
+
+ ret = debugfs_create_trace_filter(sdev);
+ if (ret < 0)
+ dev_warn(sdev->dev, "failed to create filter debugfs file: %d", ret);
+
+ dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL);
+ if (!dfse)
+ return -ENOMEM;
+
+ dfse->type = SOF_DFSENTRY_TYPE_BUF;
+ dfse->buf = priv->dmatb.area;
+ dfse->size = priv->dmatb.bytes;
+ dfse->sdev = sdev;
+
+ debugfs_create_file("trace", 0444, sdev->debugfs_root, dfse,
+ &sof_dfs_dtrace_fops);
+
+ return 0;
+}
+
+static int ipc3_dtrace_enable(struct snd_sof_dev *sdev)
+{
+ struct sof_dtrace_priv *priv = sdev->fw_trace_data;
+ struct sof_ipc_fw_ready *ready = &sdev->fw_ready;
+ struct sof_ipc_fw_version *v = &ready->version;
+ struct sof_ipc_dma_trace_params_ext params;
+ int ret;
+
+ if (!sdev->fw_trace_is_supported)
+ return 0;
+
+ if (priv->dtrace_state == SOF_DTRACE_ENABLED || !priv->dma_trace_pages)
+ return -EINVAL;
+
+ if (priv->dtrace_state == SOF_DTRACE_STOPPED)
+ goto start;
+
+ /* set IPC parameters */
+ params.hdr.cmd = SOF_IPC_GLB_TRACE_MSG;
+ /* PARAMS_EXT is only supported from ABI 3.7.0 onwards */
+ if (v->abi_version >= SOF_ABI_VER(3, 7, 0)) {
+ params.hdr.size = sizeof(struct sof_ipc_dma_trace_params_ext);
+ params.hdr.cmd |= SOF_IPC_TRACE_DMA_PARAMS_EXT;
+ params.timestamp_ns = ktime_get(); /* in nanosecond */
+ } else {
+ params.hdr.size = sizeof(struct sof_ipc_dma_trace_params);
+ params.hdr.cmd |= SOF_IPC_TRACE_DMA_PARAMS;
+ }
+ params.buffer.phy_addr = priv->dmatp.addr;
+ params.buffer.size = priv->dmatb.bytes;
+ params.buffer.pages = priv->dma_trace_pages;
+ params.stream_tag = 0;
+
+ sof_dtrace_set_host_offset(priv, 0);
+ priv->dtrace_draining = false;
+
+ ret = sof_dtrace_host_init(sdev, &priv->dmatb, &params);
+ if (ret < 0) {
+ dev_err(sdev->dev, "Host dtrace init failed: %d\n", ret);
+ return ret;
+ }
+ dev_dbg(sdev->dev, "stream_tag: %d\n", params.stream_tag);
+
+ /* send IPC to the DSP */
+ priv->dtrace_state = SOF_DTRACE_INITIALIZING;
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, &params, sizeof(params));
+ if (ret < 0) {
+ dev_err(sdev->dev, "can't set params for DMA for trace %d\n", ret);
+ goto trace_release;
+ }
+
+start:
+ priv->dtrace_state = SOF_DTRACE_ENABLED;
+
+ ret = sof_dtrace_host_trigger(sdev, SNDRV_PCM_TRIGGER_START);
+ if (ret < 0) {
+ dev_err(sdev->dev, "Host dtrace trigger start failed: %d\n", ret);
+ goto trace_release;
+ }
+
+ return 0;
+
+trace_release:
+ priv->dtrace_state = SOF_DTRACE_DISABLED;
+ sof_dtrace_host_release(sdev);
+ return ret;
+}
+
+static int ipc3_dtrace_init(struct snd_sof_dev *sdev)
+{
+ struct sof_dtrace_priv *priv;
+ int ret;
+
+ /* dtrace is only supported with SOF_IPC */
+ if (sdev->pdata->ipc_type != SOF_IPC_TYPE_3)
+ return -EOPNOTSUPP;
+
+ if (sdev->fw_trace_data) {
+ dev_err(sdev->dev, "fw_trace_data has been already allocated\n");
+ return -EBUSY;
+ }
+
+ priv = devm_kzalloc(sdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ sdev->fw_trace_data = priv;
+
+ /* set false before start initialization */
+ priv->dtrace_state = SOF_DTRACE_DISABLED;
+
+ /* allocate trace page table buffer */
+ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->dev,
+ PAGE_SIZE, &priv->dmatp);
+ if (ret < 0) {
+ dev_err(sdev->dev, "can't alloc page table for trace %d\n", ret);
+ return ret;
+ }
+
+ /* allocate trace data buffer */
+ ret = snd_dma_alloc_dir_pages(SNDRV_DMA_TYPE_DEV_SG, sdev->dev,
+ DMA_FROM_DEVICE, DMA_BUF_SIZE_FOR_TRACE,
+ &priv->dmatb);
+ if (ret < 0) {
+ dev_err(sdev->dev, "can't alloc buffer for trace %d\n", ret);
+ goto page_err;
+ }
+
+ /* create compressed page table for audio firmware */
+ ret = snd_sof_create_page_table(sdev->dev, &priv->dmatb,
+ priv->dmatp.area, priv->dmatb.bytes);
+ if (ret < 0)
+ goto table_err;
+
+ priv->dma_trace_pages = ret;
+ dev_dbg(sdev->dev, "dma_trace_pages: %d\n", priv->dma_trace_pages);
+
+ if (sdev->first_boot) {
+ ret = debugfs_create_dtrace(sdev);
+ if (ret < 0)
+ goto table_err;
+ }
+
+ init_waitqueue_head(&priv->trace_sleep);
+
+ ret = ipc3_dtrace_enable(sdev);
+ if (ret < 0)
+ goto table_err;
+
+ return 0;
+table_err:
+ priv->dma_trace_pages = 0;
+ snd_dma_free_pages(&priv->dmatb);
+page_err:
+ snd_dma_free_pages(&priv->dmatp);
+ return ret;
+}
+
+int ipc3_dtrace_posn_update(struct snd_sof_dev *sdev,
+ struct sof_ipc_dma_trace_posn *posn)
+{
+ struct sof_dtrace_priv *priv = sdev->fw_trace_data;
+
+ if (!sdev->fw_trace_is_supported)
+ return 0;
+
+ if (trace_pos_update_expected(priv) &&
+ sof_dtrace_set_host_offset(priv, posn->host_offset))
+ wake_up(&priv->trace_sleep);
+
+ if (posn->overflow != 0)
+ dev_err(sdev->dev,
+ "DSP trace buffer overflow %u bytes. Total messages %d\n",
+ posn->overflow, posn->messages);
+
+ return 0;
+}
+
+/* an error has occurred within the DSP that prevents further trace */
+static void ipc3_dtrace_fw_crashed(struct snd_sof_dev *sdev)
+{
+ struct sof_dtrace_priv *priv = sdev->fw_trace_data;
+
+ if (priv->dtrace_state == SOF_DTRACE_ENABLED) {
+ priv->dtrace_error = true;
+ wake_up(&priv->trace_sleep);
+ }
+}
+
+static void ipc3_dtrace_release(struct snd_sof_dev *sdev, bool only_stop)
+{
+ struct sof_dtrace_priv *priv = sdev->fw_trace_data;
+ struct sof_ipc_fw_ready *ready = &sdev->fw_ready;
+ struct sof_ipc_fw_version *v = &ready->version;
+ struct sof_ipc_cmd_hdr hdr;
+ int ret;
+
+ if (!sdev->fw_trace_is_supported || priv->dtrace_state == SOF_DTRACE_DISABLED)
+ return;
+
+ ret = sof_dtrace_host_trigger(sdev, SNDRV_PCM_TRIGGER_STOP);
+ if (ret < 0)
+ dev_err(sdev->dev, "Host dtrace trigger stop failed: %d\n", ret);
+ priv->dtrace_state = SOF_DTRACE_STOPPED;
+
+ /*
+ * stop and free trace DMA in the DSP. TRACE_DMA_FREE is only supported from
+ * ABI 3.20.0 onwards
+ */
+ if (v->abi_version >= SOF_ABI_VER(3, 20, 0)) {
+ hdr.size = sizeof(hdr);
+ hdr.cmd = SOF_IPC_GLB_TRACE_MSG | SOF_IPC_TRACE_DMA_FREE;
+
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, &hdr, hdr.size);
+ if (ret < 0)
+ dev_err(sdev->dev, "DMA_TRACE_FREE failed with error: %d\n", ret);
+ }
+
+ if (only_stop)
+ goto out;
+
+ ret = sof_dtrace_host_release(sdev);
+ if (ret < 0)
+ dev_err(sdev->dev, "Host dtrace release failed %d\n", ret);
+
+ priv->dtrace_state = SOF_DTRACE_DISABLED;
+
+out:
+ priv->dtrace_draining = true;
+ wake_up(&priv->trace_sleep);
+}
+
+static void ipc3_dtrace_suspend(struct snd_sof_dev *sdev, pm_message_t pm_state)
+{
+ ipc3_dtrace_release(sdev, pm_state.event == SOF_DSP_PM_D0);
+}
+
+static int ipc3_dtrace_resume(struct snd_sof_dev *sdev)
+{
+ return ipc3_dtrace_enable(sdev);
+}
+
+static void ipc3_dtrace_free(struct snd_sof_dev *sdev)
+{
+ struct sof_dtrace_priv *priv = sdev->fw_trace_data;
+
+ /* release trace */
+ ipc3_dtrace_release(sdev, false);
+
+ if (priv->dma_trace_pages) {
+ snd_dma_free_pages(&priv->dmatb);
+ snd_dma_free_pages(&priv->dmatp);
+ priv->dma_trace_pages = 0;
+ }
+}
+
+const struct sof_ipc_fw_tracing_ops ipc3_dtrace_ops = {
+ .init = ipc3_dtrace_init,
+ .free = ipc3_dtrace_free,
+ .fw_crashed = ipc3_dtrace_fw_crashed,
+ .suspend = ipc3_dtrace_suspend,
+ .resume = ipc3_dtrace_resume,
+};
diff --git a/sound/soc/sof/ipc3-loader.c b/sound/soc/sof/ipc3-loader.c
new file mode 100644
index 000000000000..6e3ef0672110
--- /dev/null
+++ b/sound/soc/sof/ipc3-loader.c
@@ -0,0 +1,416 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2022 Intel Corporation. All rights reserved.
+
+#include <linux/firmware.h>
+#include "sof-priv.h"
+#include "sof-audio.h"
+#include "ipc3-priv.h"
+#include "ops.h"
+
+static int ipc3_fw_ext_man_get_version(struct snd_sof_dev *sdev,
+ const struct sof_ext_man_elem_header *hdr)
+{
+ const struct sof_ext_man_fw_version *v =
+ container_of(hdr, struct sof_ext_man_fw_version, hdr);
+
+ memcpy(&sdev->fw_ready.version, &v->version, sizeof(v->version));
+ sdev->fw_ready.flags = v->flags;
+
+ /* log ABI versions and check FW compatibility */
+ return sof_ipc3_validate_fw_version(sdev);
+}
+
+static int ipc3_fw_ext_man_get_windows(struct snd_sof_dev *sdev,
+ const struct sof_ext_man_elem_header *hdr)
+{
+ const struct sof_ext_man_window *w;
+
+ w = container_of(hdr, struct sof_ext_man_window, hdr);
+
+ return sof_ipc3_get_ext_windows(sdev, &w->ipc_window.ext_hdr);
+}
+
+static int ipc3_fw_ext_man_get_cc_info(struct snd_sof_dev *sdev,
+ const struct sof_ext_man_elem_header *hdr)
+{
+ const struct sof_ext_man_cc_version *cc;
+
+ cc = container_of(hdr, struct sof_ext_man_cc_version, hdr);
+
+ return sof_ipc3_get_cc_info(sdev, &cc->cc_version.ext_hdr);
+}
+
+static int ipc3_fw_ext_man_get_dbg_abi_info(struct snd_sof_dev *sdev,
+ const struct sof_ext_man_elem_header *hdr)
+{
+ const struct ext_man_dbg_abi *dbg_abi =
+ container_of(hdr, struct ext_man_dbg_abi, hdr);
+
+ if (sdev->first_boot)
+ dev_dbg(sdev->dev,
+ "Firmware: DBG_ABI %d:%d:%d\n",
+ SOF_ABI_VERSION_MAJOR(dbg_abi->dbg_abi.abi_dbg_version),
+ SOF_ABI_VERSION_MINOR(dbg_abi->dbg_abi.abi_dbg_version),
+ SOF_ABI_VERSION_PATCH(dbg_abi->dbg_abi.abi_dbg_version));
+
+ return 0;
+}
+
+static int ipc3_fw_ext_man_get_config_data(struct snd_sof_dev *sdev,
+ const struct sof_ext_man_elem_header *hdr)
+{
+ const struct sof_ext_man_config_data *config =
+ container_of(hdr, struct sof_ext_man_config_data, hdr);
+ const struct sof_config_elem *elem;
+ int elems_counter;
+ int elems_size;
+ int ret = 0;
+ int i;
+
+ /* calculate elements counter */
+ elems_size = config->hdr.size - sizeof(struct sof_ext_man_elem_header);
+ elems_counter = elems_size / sizeof(struct sof_config_elem);
+
+ dev_dbg(sdev->dev, "manifest can hold up to %d config elements\n", elems_counter);
+
+ for (i = 0; i < elems_counter; ++i) {
+ elem = &config->elems[i];
+ dev_dbg(sdev->dev, "get index %d token %d val %d\n",
+ i, elem->token, elem->value);
+ switch (elem->token) {
+ case SOF_EXT_MAN_CONFIG_EMPTY:
+ /* unused memory space is zero filled - mapped to EMPTY elements */
+ break;
+ case SOF_EXT_MAN_CONFIG_IPC_MSG_SIZE:
+ /* TODO: use ipc msg size from config data */
+ break;
+ case SOF_EXT_MAN_CONFIG_MEMORY_USAGE_SCAN:
+ if (sdev->first_boot && elem->value)
+ ret = snd_sof_dbg_memory_info_init(sdev);
+ break;
+ default:
+ dev_info(sdev->dev,
+ "Unknown firmware configuration token %d value %d",
+ elem->token, elem->value);
+ break;
+ }
+ if (ret < 0) {
+ dev_err(sdev->dev,
+ "%s: processing failed for token %d value %#x, %d\n",
+ __func__, elem->token, elem->value, ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static ssize_t ipc3_fw_ext_man_size(struct snd_sof_dev *sdev, const struct firmware *fw)
+{
+ const struct sof_ext_man_header *head;
+
+ head = (struct sof_ext_man_header *)fw->data;
+
+ /*
+ * assert fw size is big enough to contain extended manifest header,
+ * it prevents from reading unallocated memory from `head` in following
+ * step.
+ */
+ if (fw->size < sizeof(*head))
+ return -EINVAL;
+
+ /*
+ * When fw points to extended manifest,
+ * then first u32 must be equal SOF_EXT_MAN_MAGIC_NUMBER.
+ */
+ if (head->magic == SOF_EXT_MAN_MAGIC_NUMBER)
+ return head->full_size;
+
+ /* otherwise given fw don't have an extended manifest */
+ dev_dbg(sdev->dev, "Unexpected extended manifest magic number: %#x\n",
+ head->magic);
+ return 0;
+}
+
+static size_t sof_ipc3_fw_parse_ext_man(struct snd_sof_dev *sdev)
+{
+ const struct firmware *fw = sdev->basefw.fw;
+ const struct sof_ext_man_elem_header *elem_hdr;
+ const struct sof_ext_man_header *head;
+ ssize_t ext_man_size;
+ ssize_t remaining;
+ uintptr_t iptr;
+ int ret = 0;
+
+ head = (struct sof_ext_man_header *)fw->data;
+ remaining = head->full_size - head->header_size;
+ if (remaining < 0 || remaining > sdev->basefw.fw->size)
+ return -EINVAL;
+ ext_man_size = ipc3_fw_ext_man_size(sdev, fw);
+
+ /* Assert firmware starts with extended manifest */
+ if (ext_man_size <= 0)
+ return ext_man_size;
+
+ /* incompatible version */
+ if (SOF_EXT_MAN_VERSION_INCOMPATIBLE(SOF_EXT_MAN_VERSION,
+ head->header_version)) {
+ dev_err(sdev->dev,
+ "extended manifest version %#x differ from used %#x\n",
+ head->header_version, SOF_EXT_MAN_VERSION);
+ return -EINVAL;
+ }
+
+ /* get first extended manifest element header */
+ iptr = (uintptr_t)fw->data + head->header_size;
+
+ while (remaining > sizeof(*elem_hdr)) {
+ elem_hdr = (struct sof_ext_man_elem_header *)iptr;
+
+ dev_dbg(sdev->dev, "found sof_ext_man header type %d size %#x\n",
+ elem_hdr->type, elem_hdr->size);
+
+ if (elem_hdr->size < sizeof(*elem_hdr) ||
+ elem_hdr->size > remaining) {
+ dev_err(sdev->dev,
+ "invalid sof_ext_man header size, type %d size %#x\n",
+ elem_hdr->type, elem_hdr->size);
+ return -EINVAL;
+ }
+
+ /* process structure data */
+ switch (elem_hdr->type) {
+ case SOF_EXT_MAN_ELEM_FW_VERSION:
+ ret = ipc3_fw_ext_man_get_version(sdev, elem_hdr);
+ break;
+ case SOF_EXT_MAN_ELEM_WINDOW:
+ ret = ipc3_fw_ext_man_get_windows(sdev, elem_hdr);
+ break;
+ case SOF_EXT_MAN_ELEM_CC_VERSION:
+ ret = ipc3_fw_ext_man_get_cc_info(sdev, elem_hdr);
+ break;
+ case SOF_EXT_MAN_ELEM_DBG_ABI:
+ ret = ipc3_fw_ext_man_get_dbg_abi_info(sdev, elem_hdr);
+ break;
+ case SOF_EXT_MAN_ELEM_CONFIG_DATA:
+ ret = ipc3_fw_ext_man_get_config_data(sdev, elem_hdr);
+ break;
+ case SOF_EXT_MAN_ELEM_PLATFORM_CONFIG_DATA:
+ ret = snd_sof_dsp_parse_platform_ext_manifest(sdev, elem_hdr);
+ break;
+ default:
+ dev_info(sdev->dev,
+ "unknown sof_ext_man header type %d size %#x\n",
+ elem_hdr->type, elem_hdr->size);
+ break;
+ }
+
+ if (ret < 0) {
+ dev_err(sdev->dev,
+ "failed to parse sof_ext_man header type %d size %#x\n",
+ elem_hdr->type, elem_hdr->size);
+ return ret;
+ }
+
+ remaining -= elem_hdr->size;
+ iptr += elem_hdr->size;
+ }
+
+ if (remaining) {
+ dev_err(sdev->dev, "error: sof_ext_man header is inconsistent\n");
+ return -EINVAL;
+ }
+
+ return ext_man_size;
+}
+
+/* generic module parser for mmaped DSPs */
+static int sof_ipc3_parse_module_memcpy(struct snd_sof_dev *sdev,
+ struct snd_sof_mod_hdr *module)
+{
+ struct snd_sof_blk_hdr *block;
+ int count, ret;
+ u32 offset;
+ size_t remaining;
+
+ dev_dbg(sdev->dev, "new module size %#x blocks %#x type %#x\n",
+ module->size, module->num_blocks, module->type);
+
+ block = (struct snd_sof_blk_hdr *)((u8 *)module + sizeof(*module));
+
+ /* module->size doesn't include header size */
+ remaining = module->size;
+ for (count = 0; count < module->num_blocks; count++) {
+ /* check for wrap */
+ if (remaining < sizeof(*block)) {
+ dev_err(sdev->dev, "not enough data remaining\n");
+ return -EINVAL;
+ }
+
+ /* minus header size of block */
+ remaining -= sizeof(*block);
+
+ if (block->size == 0) {
+ dev_warn(sdev->dev,
+ "warning: block %d size zero\n", count);
+ dev_warn(sdev->dev, " type %#x offset %#x\n",
+ block->type, block->offset);
+ continue;
+ }
+
+ switch (block->type) {
+ case SOF_FW_BLK_TYPE_RSRVD0:
+ case SOF_FW_BLK_TYPE_ROM...SOF_FW_BLK_TYPE_RSRVD14:
+ continue; /* not handled atm */
+ case SOF_FW_BLK_TYPE_IRAM:
+ case SOF_FW_BLK_TYPE_DRAM:
+ case SOF_FW_BLK_TYPE_SRAM:
+ offset = block->offset;
+ break;
+ default:
+ dev_err(sdev->dev, "%s: bad type %#x for block %#x\n",
+ __func__, block->type, count);
+ return -EINVAL;
+ }
+
+ dev_dbg(sdev->dev, "block %d type %#x size %#x ==> offset %#x\n",
+ count, block->type, block->size, offset);
+
+ /* checking block->size to avoid unaligned access */
+ if (block->size % sizeof(u32)) {
+ dev_err(sdev->dev, "%s: invalid block size %#x\n",
+ __func__, block->size);
+ return -EINVAL;
+ }
+ ret = snd_sof_dsp_block_write(sdev, block->type, offset,
+ block + 1, block->size);
+ if (ret < 0) {
+ dev_err(sdev->dev, "%s: write to block type %#x failed\n",
+ __func__, block->type);
+ return ret;
+ }
+
+ if (remaining < block->size) {
+ dev_err(sdev->dev, "%s: not enough data remaining\n", __func__);
+ return -EINVAL;
+ }
+
+ /* minus body size of block */
+ remaining -= block->size;
+ /* next block */
+ block = (struct snd_sof_blk_hdr *)((u8 *)block + sizeof(*block)
+ + block->size);
+ }
+
+ return 0;
+}
+
+static int sof_ipc3_load_fw_to_dsp(struct snd_sof_dev *sdev)
+{
+ u32 payload_offset = sdev->basefw.payload_offset;
+ const struct firmware *fw = sdev->basefw.fw;
+ struct snd_sof_fw_header *header;
+ struct snd_sof_mod_hdr *module;
+ int (*load_module)(struct snd_sof_dev *sof_dev, struct snd_sof_mod_hdr *hdr);
+ size_t remaining;
+ int ret, count;
+
+ if (!fw)
+ return -EINVAL;
+
+ header = (struct snd_sof_fw_header *)(fw->data + payload_offset);
+ load_module = sof_ops(sdev)->load_module;
+ if (!load_module) {
+ dev_dbg(sdev->dev, "Using generic module loading\n");
+ load_module = sof_ipc3_parse_module_memcpy;
+ } else {
+ dev_dbg(sdev->dev, "Using custom module loading\n");
+ }
+
+ /* parse each module */
+ module = (struct snd_sof_mod_hdr *)(fw->data + payload_offset + sizeof(*header));
+ remaining = fw->size - sizeof(*header) - payload_offset;
+ /* check for wrap */
+ if (remaining > fw->size) {
+ dev_err(sdev->dev, "%s: fw size smaller than header size\n", __func__);
+ return -EINVAL;
+ }
+
+ for (count = 0; count < header->num_modules; count++) {
+ /* check for wrap */
+ if (remaining < sizeof(*module)) {
+ dev_err(sdev->dev, "%s: not enough data for a module\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ /* minus header size of module */
+ remaining -= sizeof(*module);
+
+ /* module */
+ ret = load_module(sdev, module);
+ if (ret < 0) {
+ dev_err(sdev->dev, "%s: invalid module %d\n", __func__, count);
+ return ret;
+ }
+
+ if (remaining < module->size) {
+ dev_err(sdev->dev, "%s: not enough data remaining\n", __func__);
+ return -EINVAL;
+ }
+
+ /* minus body size of module */
+ remaining -= module->size;
+ module = (struct snd_sof_mod_hdr *)((u8 *)module +
+ sizeof(*module) + module->size);
+ }
+
+ return 0;
+}
+
+static int sof_ipc3_validate_firmware(struct snd_sof_dev *sdev)
+{
+ u32 payload_offset = sdev->basefw.payload_offset;
+ const struct firmware *fw = sdev->basefw.fw;
+ struct snd_sof_fw_header *header;
+ size_t fw_size = fw->size - payload_offset;
+
+ if (fw->size <= payload_offset) {
+ dev_err(sdev->dev,
+ "firmware size must be greater than firmware offset\n");
+ return -EINVAL;
+ }
+
+ /* Read the header information from the data pointer */
+ header = (struct snd_sof_fw_header *)(fw->data + payload_offset);
+
+ /* verify FW sig */
+ if (strncmp(header->sig, SND_SOF_FW_SIG, SND_SOF_FW_SIG_SIZE) != 0) {
+ dev_err(sdev->dev, "invalid firmware signature\n");
+ return -EINVAL;
+ }
+
+ /* check size is valid */
+ if (fw_size != header->file_size + sizeof(*header)) {
+ dev_err(sdev->dev,
+ "invalid filesize mismatch got 0x%zx expected 0x%zx\n",
+ fw_size, header->file_size + sizeof(*header));
+ return -EINVAL;
+ }
+
+ dev_dbg(sdev->dev, "header size=0x%x modules=0x%x abi=0x%x size=%zu\n",
+ header->file_size, header->num_modules,
+ header->abi, sizeof(*header));
+
+ return 0;
+}
+
+const struct sof_ipc_fw_loader_ops ipc3_loader_ops = {
+ .validate = sof_ipc3_validate_firmware,
+ .parse_ext_manifest = sof_ipc3_fw_parse_ext_man,
+ .load_fw_to_dsp = sof_ipc3_load_fw_to_dsp,
+};
diff --git a/sound/soc/sof/ipc3-pcm.c b/sound/soc/sof/ipc3-pcm.c
new file mode 100644
index 000000000000..35769dd7905e
--- /dev/null
+++ b/sound/soc/sof/ipc3-pcm.c
@@ -0,0 +1,437 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2021 Intel Corporation. All rights reserved.
+//
+//
+
+#include <sound/pcm_params.h>
+#include "ipc3-priv.h"
+#include "ops.h"
+#include "sof-priv.h"
+#include "sof-audio.h"
+
+static int sof_ipc3_pcm_hw_free(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct sof_ipc_stream stream;
+ struct snd_sof_pcm *spcm;
+
+ spcm = snd_sof_find_spcm_dai(component, rtd);
+ if (!spcm)
+ return -EINVAL;
+
+ if (!spcm->prepared[substream->stream])
+ return 0;
+
+ stream.hdr.size = sizeof(stream);
+ stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_FREE;
+ stream.comp_id = spcm->stream[substream->stream].comp_id;
+
+ /* send IPC to the DSP */
+ return sof_ipc_tx_message_no_reply(sdev->ipc, &stream, sizeof(stream));
+}
+
+static int sof_ipc3_pcm_hw_params(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_sof_platform_stream_params *platform_params)
+{
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct sof_ipc_fw_version *v = &sdev->fw_ready.version;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sof_ipc_pcm_params_reply ipc_params_reply;
+ struct sof_ipc_pcm_params pcm;
+ struct snd_sof_pcm *spcm;
+ int ret;
+
+ spcm = snd_sof_find_spcm_dai(component, rtd);
+ if (!spcm)
+ return -EINVAL;
+
+ memset(&pcm, 0, sizeof(pcm));
+
+ /* number of pages should be rounded up */
+ pcm.params.buffer.pages = PFN_UP(runtime->dma_bytes);
+
+ /* set IPC PCM parameters */
+ pcm.hdr.size = sizeof(pcm);
+ pcm.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS;
+ pcm.comp_id = spcm->stream[substream->stream].comp_id;
+ pcm.params.hdr.size = sizeof(pcm.params);
+ pcm.params.buffer.phy_addr = spcm->stream[substream->stream].page_table.addr;
+ pcm.params.buffer.size = runtime->dma_bytes;
+ pcm.params.direction = substream->stream;
+ pcm.params.sample_valid_bytes = params_width(params) >> 3;
+ pcm.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED;
+ pcm.params.rate = params_rate(params);
+ pcm.params.channels = params_channels(params);
+ pcm.params.host_period_bytes = params_period_bytes(params);
+
+ /* container size */
+ ret = snd_pcm_format_physical_width(params_format(params));
+ if (ret < 0)
+ return ret;
+ pcm.params.sample_container_bytes = ret >> 3;
+
+ /* format */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16:
+ pcm.params.frame_fmt = SOF_IPC_FRAME_S16_LE;
+ break;
+ case SNDRV_PCM_FORMAT_S24:
+ pcm.params.frame_fmt = SOF_IPC_FRAME_S24_4LE;
+ break;
+ case SNDRV_PCM_FORMAT_S32:
+ pcm.params.frame_fmt = SOF_IPC_FRAME_S32_LE;
+ break;
+ case SNDRV_PCM_FORMAT_FLOAT:
+ pcm.params.frame_fmt = SOF_IPC_FRAME_FLOAT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Update the IPC message with information from the platform */
+ pcm.params.stream_tag = platform_params->stream_tag;
+
+ if (platform_params->use_phy_address)
+ pcm.params.buffer.phy_addr = platform_params->phy_addr;
+
+ if (platform_params->no_ipc_position) {
+ /* For older ABIs set host_period_bytes to zero to inform
+ * FW we don't want position updates. Newer versions use
+ * no_stream_position for this purpose.
+ */
+ if (v->abi_version < SOF_ABI_VER(3, 10, 0))
+ pcm.params.host_period_bytes = 0;
+ else
+ pcm.params.no_stream_position = 1;
+ }
+
+ if (platform_params->cont_update_posn)
+ pcm.params.cont_update_posn = 1;
+
+ dev_dbg(component->dev, "stream_tag %d", pcm.params.stream_tag);
+
+ /* send hw_params IPC to the DSP */
+ ret = sof_ipc_tx_message(sdev->ipc, &pcm, sizeof(pcm),
+ &ipc_params_reply, sizeof(ipc_params_reply));
+ if (ret < 0) {
+ dev_err(component->dev, "HW params ipc failed for stream %d\n",
+ pcm.params.stream_tag);
+ return ret;
+ }
+
+ ret = snd_sof_set_stream_data_offset(sdev, &spcm->stream[substream->stream],
+ ipc_params_reply.posn_offset);
+ if (ret < 0) {
+ dev_err(component->dev, "%s: invalid stream data offset for PCM %d\n",
+ __func__, spcm->pcm.pcm_id);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int sof_ipc3_pcm_trigger(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+ struct sof_ipc_stream stream;
+ struct snd_sof_pcm *spcm;
+
+ spcm = snd_sof_find_spcm_dai(component, rtd);
+ if (!spcm)
+ return -EINVAL;
+
+ stream.hdr.size = sizeof(stream);
+ stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG;
+ stream.comp_id = spcm->stream[substream->stream].comp_id;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_PAUSE;
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_RELEASE;
+ break;
+ case SNDRV_PCM_TRIGGER_START:
+ stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_START;
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ fallthrough;
+ case SNDRV_PCM_TRIGGER_STOP:
+ stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_STOP;
+ break;
+ default:
+ dev_err(component->dev, "Unhandled trigger cmd %d\n", cmd);
+ return -EINVAL;
+ }
+
+ /* send IPC to the DSP */
+ return sof_ipc_tx_message_no_reply(sdev->ipc, &stream, sizeof(stream));
+}
+
+static void ssp_dai_config_pcm_params_match(struct snd_sof_dev *sdev, const char *link_name,
+ struct snd_pcm_hw_params *params)
+{
+ struct sof_ipc_dai_config *config;
+ struct snd_sof_dai *dai;
+ int i;
+
+ /*
+ * Search for all matching DAIs as we can have both playback and capture DAI
+ * associated with the same link.
+ */
+ list_for_each_entry(dai, &sdev->dai_list, list) {
+ if (!dai->name || strcmp(link_name, dai->name))
+ continue;
+ for (i = 0; i < dai->number_configs; i++) {
+ struct sof_dai_private_data *private = dai->private;
+
+ config = &private->dai_config[i];
+ if (config->ssp.fsync_rate == params_rate(params)) {
+ dev_dbg(sdev->dev, "DAI config %d matches pcm hw params\n", i);
+ dai->current_config = i;
+ break;
+ }
+ }
+ }
+}
+
+static int sof_ipc3_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME);
+ struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct snd_sof_dai *dai = snd_sof_find_dai(component, (char *)rtd->dai_link->name);
+ struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+ struct sof_dai_private_data *private;
+ struct snd_soc_dpcm *dpcm;
+
+ if (!dai) {
+ dev_err(component->dev, "%s: No DAI found with name %s\n", __func__,
+ rtd->dai_link->name);
+ return -EINVAL;
+ }
+
+ private = dai->private;
+ if (!private) {
+ dev_err(component->dev, "%s: No private data found for DAI %s\n", __func__,
+ rtd->dai_link->name);
+ return -EINVAL;
+ }
+
+ /* read format from topology */
+ snd_mask_none(fmt);
+
+ switch (private->comp_dai->config.frame_fmt) {
+ case SOF_IPC_FRAME_S16_LE:
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE);
+ break;
+ case SOF_IPC_FRAME_S24_4LE:
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE);
+ break;
+ case SOF_IPC_FRAME_S32_LE:
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE);
+ break;
+ default:
+ dev_err(component->dev, "No available DAI format!\n");
+ return -EINVAL;
+ }
+
+ /* read rate and channels from topology */
+ switch (private->dai_config->type) {
+ case SOF_DAI_INTEL_SSP:
+ /* search for config to pcm params match, if not found use default */
+ ssp_dai_config_pcm_params_match(sdev, (char *)rtd->dai_link->name, params);
+
+ rate->min = private->dai_config[dai->current_config].ssp.fsync_rate;
+ rate->max = private->dai_config[dai->current_config].ssp.fsync_rate;
+ channels->min = private->dai_config[dai->current_config].ssp.tdm_slots;
+ channels->max = private->dai_config[dai->current_config].ssp.tdm_slots;
+
+ dev_dbg(component->dev, "rate_min: %d rate_max: %d\n", rate->min, rate->max);
+ dev_dbg(component->dev, "channels_min: %d channels_max: %d\n",
+ channels->min, channels->max);
+
+ break;
+ case SOF_DAI_INTEL_DMIC:
+ /* DMIC only supports 16 or 32 bit formats */
+ if (private->comp_dai->config.frame_fmt == SOF_IPC_FRAME_S24_4LE) {
+ dev_err(component->dev, "Invalid fmt %d for DAI type %d\n",
+ private->comp_dai->config.frame_fmt,
+ private->dai_config->type);
+ }
+ break;
+ case SOF_DAI_INTEL_HDA:
+ /*
+ * HDAudio does not follow the default trigger
+ * sequence due to firmware implementation
+ */
+ for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_PLAYBACK, dpcm) {
+ struct snd_soc_pcm_runtime *fe = dpcm->fe;
+
+ fe->dai_link->trigger[SNDRV_PCM_STREAM_PLAYBACK] =
+ SND_SOC_DPCM_TRIGGER_POST;
+ }
+ break;
+ case SOF_DAI_INTEL_ALH:
+ /*
+ * Dai could run with different channel count compared with
+ * front end, so get dai channel count from topology
+ */
+ channels->min = private->dai_config->alh.channels;
+ channels->max = private->dai_config->alh.channels;
+ break;
+ case SOF_DAI_IMX_ESAI:
+ rate->min = private->dai_config->esai.fsync_rate;
+ rate->max = private->dai_config->esai.fsync_rate;
+ channels->min = private->dai_config->esai.tdm_slots;
+ channels->max = private->dai_config->esai.tdm_slots;
+
+ dev_dbg(component->dev, "rate_min: %d rate_max: %d\n", rate->min, rate->max);
+ dev_dbg(component->dev, "channels_min: %d channels_max: %d\n",
+ channels->min, channels->max);
+ break;
+ case SOF_DAI_MEDIATEK_AFE:
+ rate->min = private->dai_config->afe.rate;
+ rate->max = private->dai_config->afe.rate;
+ channels->min = private->dai_config->afe.channels;
+ channels->max = private->dai_config->afe.channels;
+
+ snd_mask_none(fmt);
+
+ switch (private->dai_config->afe.format) {
+ case SOF_IPC_FRAME_S16_LE:
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE);
+ break;
+ case SOF_IPC_FRAME_S24_4LE:
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE);
+ break;
+ case SOF_IPC_FRAME_S32_LE:
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE);
+ break;
+ default:
+ dev_err(component->dev, "Not available format!\n");
+ return -EINVAL;
+ }
+
+ dev_dbg(component->dev, "rate_min: %d rate_max: %d\n", rate->min, rate->max);
+ dev_dbg(component->dev, "channels_min: %d channels_max: %d\n",
+ channels->min, channels->max);
+ break;
+ case SOF_DAI_IMX_SAI:
+ rate->min = private->dai_config->sai.fsync_rate;
+ rate->max = private->dai_config->sai.fsync_rate;
+ channels->min = private->dai_config->sai.tdm_slots;
+ channels->max = private->dai_config->sai.tdm_slots;
+
+ dev_dbg(component->dev, "rate_min: %d rate_max: %d\n", rate->min, rate->max);
+ dev_dbg(component->dev, "channels_min: %d channels_max: %d\n",
+ channels->min, channels->max);
+ break;
+ case SOF_DAI_AMD_BT:
+ rate->min = private->dai_config->acpbt.fsync_rate;
+ rate->max = private->dai_config->acpbt.fsync_rate;
+ channels->min = private->dai_config->acpbt.tdm_slots;
+ channels->max = private->dai_config->acpbt.tdm_slots;
+
+ dev_dbg(component->dev,
+ "AMD_BT rate_min: %d rate_max: %d\n", rate->min, rate->max);
+ dev_dbg(component->dev, "AMD_BT channels_min: %d channels_max: %d\n",
+ channels->min, channels->max);
+ break;
+ case SOF_DAI_AMD_SP:
+ case SOF_DAI_AMD_SP_VIRTUAL:
+ rate->min = private->dai_config->acpsp.fsync_rate;
+ rate->max = private->dai_config->acpsp.fsync_rate;
+ channels->min = private->dai_config->acpsp.tdm_slots;
+ channels->max = private->dai_config->acpsp.tdm_slots;
+
+ dev_dbg(component->dev,
+ "AMD_SP rate_min: %d rate_max: %d\n", rate->min, rate->max);
+ dev_dbg(component->dev, "AMD_SP channels_min: %d channels_max: %d\n",
+ channels->min, channels->max);
+ break;
+ case SOF_DAI_AMD_HS:
+ case SOF_DAI_AMD_HS_VIRTUAL:
+ rate->min = private->dai_config->acphs.fsync_rate;
+ rate->max = private->dai_config->acphs.fsync_rate;
+ channels->min = private->dai_config->acphs.tdm_slots;
+ channels->max = private->dai_config->acphs.tdm_slots;
+
+ dev_dbg(component->dev,
+ "AMD_HS channel_max: %d rate_max: %d\n", channels->max, rate->max);
+ break;
+ case SOF_DAI_AMD_DMIC:
+ rate->min = private->dai_config->acpdmic.pdm_rate;
+ rate->max = private->dai_config->acpdmic.pdm_rate;
+ channels->min = private->dai_config->acpdmic.pdm_ch;
+ channels->max = private->dai_config->acpdmic.pdm_ch;
+
+ dev_dbg(component->dev,
+ "AMD_DMIC rate_min: %d rate_max: %d\n", rate->min, rate->max);
+ dev_dbg(component->dev, "AMD_DMIC channels_min: %d channels_max: %d\n",
+ channels->min, channels->max);
+ break;
+ case SOF_DAI_IMX_MICFIL:
+ rate->min = private->dai_config->micfil.pdm_rate;
+ rate->max = private->dai_config->micfil.pdm_rate;
+ channels->min = private->dai_config->micfil.pdm_ch;
+ channels->max = private->dai_config->micfil.pdm_ch;
+
+ dev_dbg(component->dev,
+ "MICFIL PDM rate_min: %d rate_max: %d\n", rate->min, rate->max);
+ dev_dbg(component->dev, "MICFIL PDM channels_min: %d channels_max: %d\n",
+ channels->min, channels->max);
+ break;
+ case SOF_DAI_AMD_SDW:
+ /* change the default trigger sequence as per HW implementation */
+ for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_PLAYBACK, dpcm) {
+ struct snd_soc_pcm_runtime *fe = dpcm->fe;
+
+ fe->dai_link->trigger[SNDRV_PCM_STREAM_PLAYBACK] =
+ SND_SOC_DPCM_TRIGGER_POST;
+ }
+
+ for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_CAPTURE, dpcm) {
+ struct snd_soc_pcm_runtime *fe = dpcm->fe;
+
+ fe->dai_link->trigger[SNDRV_PCM_STREAM_CAPTURE] =
+ SND_SOC_DPCM_TRIGGER_POST;
+ }
+ rate->min = private->dai_config->acp_sdw.rate;
+ rate->max = private->dai_config->acp_sdw.rate;
+ channels->min = private->dai_config->acp_sdw.channels;
+ channels->max = private->dai_config->acp_sdw.channels;
+
+ dev_dbg(component->dev,
+ "AMD_SDW rate_min: %d rate_max: %d\n", rate->min, rate->max);
+ dev_dbg(component->dev, "AMD_SDW channels_min: %d channels_max: %d\n",
+ channels->min, channels->max);
+ break;
+ default:
+ dev_err(component->dev, "Invalid DAI type %d\n", private->dai_config->type);
+ break;
+ }
+
+ return 0;
+}
+
+const struct sof_ipc_pcm_ops ipc3_pcm_ops = {
+ .hw_params = sof_ipc3_pcm_hw_params,
+ .hw_free = sof_ipc3_pcm_hw_free,
+ .trigger = sof_ipc3_pcm_trigger,
+ .dai_link_fixup = sof_ipc3_pcm_dai_link_fixup,
+ .reset_hw_params_during_stop = true,
+};
diff --git a/sound/soc/sof/ipc3-priv.h b/sound/soc/sof/ipc3-priv.h
new file mode 100644
index 000000000000..0bbca418e67e
--- /dev/null
+++ b/sound/soc/sof/ipc3-priv.h
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+/*
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * Copyright(c) 2021 Intel Corporation. All rights reserved.
+ */
+
+#ifndef __SOUND_SOC_SOF_IPC3_PRIV_H
+#define __SOUND_SOC_SOF_IPC3_PRIV_H
+
+#include "sof-priv.h"
+
+/* IPC3 specific ops */
+extern const struct sof_ipc_pcm_ops ipc3_pcm_ops;
+extern const struct sof_ipc_tplg_ops ipc3_tplg_ops;
+extern const struct sof_ipc_tplg_control_ops tplg_ipc3_control_ops;
+extern const struct sof_ipc_fw_loader_ops ipc3_loader_ops;
+extern const struct sof_ipc_fw_tracing_ops ipc3_dtrace_ops;
+
+/* helpers for fw_ready and ext_manifest parsing */
+int sof_ipc3_get_ext_windows(struct snd_sof_dev *sdev,
+ const struct sof_ipc_ext_data_hdr *ext_hdr);
+int sof_ipc3_get_cc_info(struct snd_sof_dev *sdev,
+ const struct sof_ipc_ext_data_hdr *ext_hdr);
+int sof_ipc3_validate_fw_version(struct snd_sof_dev *sdev);
+
+/* dtrace position update */
+int ipc3_dtrace_posn_update(struct snd_sof_dev *sdev,
+ struct sof_ipc_dma_trace_posn *posn);
+/* RX handler backend */
+void sof_ipc3_do_rx_work(struct snd_sof_dev *sdev, struct sof_ipc_cmd_hdr *hdr, void *msg_buf);
+
+/* dtrace platform callback wrappers */
+static inline int sof_dtrace_host_init(struct snd_sof_dev *sdev,
+ struct snd_dma_buffer *dmatb,
+ struct sof_ipc_dma_trace_params_ext *dtrace_params)
+{
+ struct snd_sof_dsp_ops *dsp_ops = sdev->pdata->desc->ops;
+
+ if (dsp_ops->trace_init)
+ return dsp_ops->trace_init(sdev, dmatb, dtrace_params);
+
+ return 0;
+}
+
+static inline int sof_dtrace_host_release(struct snd_sof_dev *sdev)
+{
+ struct snd_sof_dsp_ops *dsp_ops = sdev->pdata->desc->ops;
+
+ if (dsp_ops->trace_release)
+ return dsp_ops->trace_release(sdev);
+
+ return 0;
+}
+
+static inline int sof_dtrace_host_trigger(struct snd_sof_dev *sdev, int cmd)
+{
+ struct snd_sof_dsp_ops *dsp_ops = sdev->pdata->desc->ops;
+
+ if (dsp_ops->trace_trigger)
+ return dsp_ops->trace_trigger(sdev, cmd);
+
+ return 0;
+}
+
+#endif
diff --git a/sound/soc/sof/ipc3-topology.c b/sound/soc/sof/ipc3-topology.c
new file mode 100644
index 000000000000..ab7f46a162da
--- /dev/null
+++ b/sound/soc/sof/ipc3-topology.c
@@ -0,0 +1,2700 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2021 Intel Corporation. All rights reserved.
+//
+//
+
+#include <uapi/sound/sof/tokens.h>
+#include <sound/pcm_params.h>
+#include "sof-priv.h"
+#include "sof-audio.h"
+#include "ipc3-priv.h"
+#include "ops.h"
+
+/* Full volume for default values */
+#define VOL_ZERO_DB BIT(VOLUME_FWL)
+
+/* size of tplg ABI in bytes */
+#define SOF_IPC3_TPLG_ABI_SIZE 3
+
+struct sof_widget_data {
+ int ctrl_type;
+ int ipc_cmd;
+ void *pdata;
+ size_t pdata_size;
+ struct snd_sof_control *control;
+};
+
+struct sof_process_types {
+ const char *name;
+ enum sof_ipc_process_type type;
+ enum sof_comp_type comp_type;
+};
+
+static const struct sof_process_types sof_process[] = {
+ {"EQFIR", SOF_PROCESS_EQFIR, SOF_COMP_EQ_FIR},
+ {"EQIIR", SOF_PROCESS_EQIIR, SOF_COMP_EQ_IIR},
+ {"KEYWORD_DETECT", SOF_PROCESS_KEYWORD_DETECT, SOF_COMP_KEYWORD_DETECT},
+ {"KPB", SOF_PROCESS_KPB, SOF_COMP_KPB},
+ {"CHAN_SELECTOR", SOF_PROCESS_CHAN_SELECTOR, SOF_COMP_SELECTOR},
+ {"MUX", SOF_PROCESS_MUX, SOF_COMP_MUX},
+ {"DEMUX", SOF_PROCESS_DEMUX, SOF_COMP_DEMUX},
+ {"DCBLOCK", SOF_PROCESS_DCBLOCK, SOF_COMP_DCBLOCK},
+ {"SMART_AMP", SOF_PROCESS_SMART_AMP, SOF_COMP_SMART_AMP},
+};
+
+static enum sof_ipc_process_type find_process(const char *name)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(sof_process); i++) {
+ if (strcmp(name, sof_process[i].name) == 0)
+ return sof_process[i].type;
+ }
+
+ return SOF_PROCESS_NONE;
+}
+
+static int get_token_process_type(void *elem, void *object, u32 offset)
+{
+ u32 *val = (u32 *)((u8 *)object + offset);
+
+ *val = find_process((const char *)elem);
+ return 0;
+}
+
+/* Buffers */
+static const struct sof_topology_token buffer_tokens[] = {
+ {SOF_TKN_BUF_SIZE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_buffer, size)},
+ {SOF_TKN_BUF_CAPS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_buffer, caps)},
+ {SOF_TKN_BUF_FLAGS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_buffer, flags)},
+};
+
+/* DAI */
+static const struct sof_topology_token dai_tokens[] = {
+ {SOF_TKN_DAI_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_dai_type,
+ offsetof(struct sof_ipc_comp_dai, type)},
+ {SOF_TKN_DAI_INDEX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_comp_dai, dai_index)},
+ {SOF_TKN_DAI_DIRECTION, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_comp_dai, direction)},
+};
+
+/* BE DAI link */
+static const struct sof_topology_token dai_link_tokens[] = {
+ {SOF_TKN_DAI_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_dai_type,
+ offsetof(struct sof_ipc_dai_config, type)},
+ {SOF_TKN_DAI_INDEX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_config, dai_index)},
+};
+
+/* scheduling */
+static const struct sof_topology_token sched_tokens[] = {
+ {SOF_TKN_SCHED_PERIOD, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_pipe_new, period)},
+ {SOF_TKN_SCHED_PRIORITY, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_pipe_new, priority)},
+ {SOF_TKN_SCHED_MIPS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_pipe_new, period_mips)},
+ {SOF_TKN_SCHED_CORE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_pipe_new, core)},
+ {SOF_TKN_SCHED_FRAMES, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_pipe_new, frames_per_sched)},
+ {SOF_TKN_SCHED_TIME_DOMAIN, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_pipe_new, time_domain)},
+};
+
+static const struct sof_topology_token pipeline_tokens[] = {
+ {SOF_TKN_SCHED_DYNAMIC_PIPELINE, SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16,
+ offsetof(struct snd_sof_widget, dynamic_pipeline_widget)},
+
+};
+
+/* volume */
+static const struct sof_topology_token volume_tokens[] = {
+ {SOF_TKN_VOLUME_RAMP_STEP_TYPE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_comp_volume, ramp)},
+ {SOF_TKN_VOLUME_RAMP_STEP_MS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_comp_volume, initial_ramp)},
+};
+
+/* SRC */
+static const struct sof_topology_token src_tokens[] = {
+ {SOF_TKN_SRC_RATE_IN, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_comp_src, source_rate)},
+ {SOF_TKN_SRC_RATE_OUT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_comp_src, sink_rate)},
+};
+
+/* ASRC */
+static const struct sof_topology_token asrc_tokens[] = {
+ {SOF_TKN_ASRC_RATE_IN, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_comp_asrc, source_rate)},
+ {SOF_TKN_ASRC_RATE_OUT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_comp_asrc, sink_rate)},
+ {SOF_TKN_ASRC_ASYNCHRONOUS_MODE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_comp_asrc, asynchronous_mode)},
+ {SOF_TKN_ASRC_OPERATION_MODE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_comp_asrc, operation_mode)},
+};
+
+/* EFFECT */
+static const struct sof_topology_token process_tokens[] = {
+ {SOF_TKN_PROCESS_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_process_type,
+ offsetof(struct sof_ipc_comp_process, type)},
+};
+
+/* PCM */
+static const struct sof_topology_token pcm_tokens[] = {
+ {SOF_TKN_PCM_DMAC_CONFIG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_comp_host, dmac_config)},
+};
+
+/* Generic components */
+static const struct sof_topology_token comp_tokens[] = {
+ {SOF_TKN_COMP_PERIOD_SINK_COUNT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_comp_config, periods_sink)},
+ {SOF_TKN_COMP_PERIOD_SOURCE_COUNT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_comp_config, periods_source)},
+ {SOF_TKN_COMP_FORMAT,
+ SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_comp_format,
+ offsetof(struct sof_ipc_comp_config, frame_fmt)},
+};
+
+/* SSP */
+static const struct sof_topology_token ssp_tokens[] = {
+ {SOF_TKN_INTEL_SSP_CLKS_CONTROL, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_ssp_params, clks_control)},
+ {SOF_TKN_INTEL_SSP_MCLK_ID, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
+ offsetof(struct sof_ipc_dai_ssp_params, mclk_id)},
+ {SOF_TKN_INTEL_SSP_SAMPLE_BITS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_ssp_params, sample_valid_bits)},
+ {SOF_TKN_INTEL_SSP_FRAME_PULSE_WIDTH, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
+ offsetof(struct sof_ipc_dai_ssp_params, frame_pulse_width)},
+ {SOF_TKN_INTEL_SSP_QUIRKS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_ssp_params, quirks)},
+ {SOF_TKN_INTEL_SSP_TDM_PADDING_PER_SLOT, SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16,
+ offsetof(struct sof_ipc_dai_ssp_params, tdm_per_slot_padding_flag)},
+ {SOF_TKN_INTEL_SSP_BCLK_DELAY, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_ssp_params, bclk_delay)},
+};
+
+/* ALH */
+static const struct sof_topology_token alh_tokens[] = {
+ {SOF_TKN_INTEL_ALH_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_alh_params, rate)},
+ {SOF_TKN_INTEL_ALH_CH, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_alh_params, channels)},
+};
+
+/* DMIC */
+static const struct sof_topology_token dmic_tokens[] = {
+ {SOF_TKN_INTEL_DMIC_DRIVER_VERSION, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_dmic_params, driver_ipc_version)},
+ {SOF_TKN_INTEL_DMIC_CLK_MIN, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_dmic_params, pdmclk_min)},
+ {SOF_TKN_INTEL_DMIC_CLK_MAX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_dmic_params, pdmclk_max)},
+ {SOF_TKN_INTEL_DMIC_SAMPLE_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_dmic_params, fifo_fs)},
+ {SOF_TKN_INTEL_DMIC_DUTY_MIN, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
+ offsetof(struct sof_ipc_dai_dmic_params, duty_min)},
+ {SOF_TKN_INTEL_DMIC_DUTY_MAX, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
+ offsetof(struct sof_ipc_dai_dmic_params, duty_max)},
+ {SOF_TKN_INTEL_DMIC_NUM_PDM_ACTIVE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_dmic_params, num_pdm_active)},
+ {SOF_TKN_INTEL_DMIC_FIFO_WORD_LENGTH, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
+ offsetof(struct sof_ipc_dai_dmic_params, fifo_bits)},
+ {SOF_TKN_INTEL_DMIC_UNMUTE_RAMP_TIME_MS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_dmic_params, unmute_ramp_time)},
+};
+
+/* ESAI */
+static const struct sof_topology_token esai_tokens[] = {
+ {SOF_TKN_IMX_ESAI_MCLK_ID, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
+ offsetof(struct sof_ipc_dai_esai_params, mclk_id)},
+};
+
+/* SAI */
+static const struct sof_topology_token sai_tokens[] = {
+ {SOF_TKN_IMX_SAI_MCLK_ID, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
+ offsetof(struct sof_ipc_dai_sai_params, mclk_id)},
+};
+
+/*
+ * DMIC PDM Tokens
+ * SOF_TKN_INTEL_DMIC_PDM_CTRL_ID should be the first token
+ * as it increments the index while parsing the array of pdm tokens
+ * and determines the correct offset
+ */
+static const struct sof_topology_token dmic_pdm_tokens[] = {
+ {SOF_TKN_INTEL_DMIC_PDM_CTRL_ID, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
+ offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, id)},
+ {SOF_TKN_INTEL_DMIC_PDM_MIC_A_Enable, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
+ offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, enable_mic_a)},
+ {SOF_TKN_INTEL_DMIC_PDM_MIC_B_Enable, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
+ offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, enable_mic_b)},
+ {SOF_TKN_INTEL_DMIC_PDM_POLARITY_A, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
+ offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, polarity_mic_a)},
+ {SOF_TKN_INTEL_DMIC_PDM_POLARITY_B, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
+ offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, polarity_mic_b)},
+ {SOF_TKN_INTEL_DMIC_PDM_CLK_EDGE, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
+ offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, clk_edge)},
+ {SOF_TKN_INTEL_DMIC_PDM_SKEW, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
+ offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, skew)},
+};
+
+/* HDA */
+static const struct sof_topology_token hda_tokens[] = {
+ {SOF_TKN_INTEL_HDA_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_hda_params, rate)},
+ {SOF_TKN_INTEL_HDA_CH, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_hda_params, channels)},
+};
+
+/* AFE */
+static const struct sof_topology_token afe_tokens[] = {
+ {SOF_TKN_MEDIATEK_AFE_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_mtk_afe_params, rate)},
+ {SOF_TKN_MEDIATEK_AFE_CH, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_mtk_afe_params, channels)},
+ {SOF_TKN_MEDIATEK_AFE_FORMAT, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_comp_format,
+ offsetof(struct sof_ipc_dai_mtk_afe_params, format)},
+};
+
+/* ACPDMIC */
+static const struct sof_topology_token acpdmic_tokens[] = {
+ {SOF_TKN_AMD_ACPDMIC_RATE,
+ SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_acpdmic_params, pdm_rate)},
+ {SOF_TKN_AMD_ACPDMIC_CH,
+ SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_acpdmic_params, pdm_ch)},
+};
+
+/* ACPI2S */
+static const struct sof_topology_token acpi2s_tokens[] = {
+ {SOF_TKN_AMD_ACPI2S_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_acp_params, fsync_rate)},
+ {SOF_TKN_AMD_ACPI2S_CH, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_acp_params, tdm_slots)},
+ {SOF_TKN_AMD_ACPI2S_TDM_MODE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_acp_params, tdm_mode)},
+};
+
+/* MICFIL PDM */
+static const struct sof_topology_token micfil_pdm_tokens[] = {
+ {SOF_TKN_IMX_MICFIL_RATE,
+ SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_micfil_params, pdm_rate)},
+ {SOF_TKN_IMX_MICFIL_CH,
+ SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_micfil_params, pdm_ch)},
+};
+
+/* ACP_SDW */
+static const struct sof_topology_token acp_sdw_tokens[] = {
+ {SOF_TKN_AMD_ACP_SDW_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_acp_sdw_params, rate)},
+ {SOF_TKN_AMD_ACP_SDW_CH, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_dai_acp_sdw_params, channels)},
+};
+
+/* Core tokens */
+static const struct sof_topology_token core_tokens[] = {
+ {SOF_TKN_COMP_CORE_ID, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc_comp, core)},
+};
+
+/* Component extended tokens */
+static const struct sof_topology_token comp_ext_tokens[] = {
+ {SOF_TKN_COMP_UUID, SND_SOC_TPLG_TUPLE_TYPE_UUID, get_token_uuid,
+ offsetof(struct snd_sof_widget, uuid)},
+};
+
+static const struct sof_token_info ipc3_token_list[SOF_TOKEN_COUNT] = {
+ [SOF_PCM_TOKENS] = {"PCM tokens", pcm_tokens, ARRAY_SIZE(pcm_tokens)},
+ [SOF_PIPELINE_TOKENS] = {"Pipeline tokens", pipeline_tokens, ARRAY_SIZE(pipeline_tokens)},
+ [SOF_SCHED_TOKENS] = {"Scheduler tokens", sched_tokens, ARRAY_SIZE(sched_tokens)},
+ [SOF_COMP_TOKENS] = {"Comp tokens", comp_tokens, ARRAY_SIZE(comp_tokens)},
+ [SOF_CORE_TOKENS] = {"Core tokens", core_tokens, ARRAY_SIZE(core_tokens)},
+ [SOF_COMP_EXT_TOKENS] = {"AFE tokens", comp_ext_tokens, ARRAY_SIZE(comp_ext_tokens)},
+ [SOF_BUFFER_TOKENS] = {"Buffer tokens", buffer_tokens, ARRAY_SIZE(buffer_tokens)},
+ [SOF_VOLUME_TOKENS] = {"Volume tokens", volume_tokens, ARRAY_SIZE(volume_tokens)},
+ [SOF_SRC_TOKENS] = {"SRC tokens", src_tokens, ARRAY_SIZE(src_tokens)},
+ [SOF_ASRC_TOKENS] = {"ASRC tokens", asrc_tokens, ARRAY_SIZE(asrc_tokens)},
+ [SOF_PROCESS_TOKENS] = {"Process tokens", process_tokens, ARRAY_SIZE(process_tokens)},
+ [SOF_DAI_TOKENS] = {"DAI tokens", dai_tokens, ARRAY_SIZE(dai_tokens)},
+ [SOF_DAI_LINK_TOKENS] = {"DAI link tokens", dai_link_tokens, ARRAY_SIZE(dai_link_tokens)},
+ [SOF_HDA_TOKENS] = {"HDA tokens", hda_tokens, ARRAY_SIZE(hda_tokens)},
+ [SOF_SSP_TOKENS] = {"SSP tokens", ssp_tokens, ARRAY_SIZE(ssp_tokens)},
+ [SOF_ALH_TOKENS] = {"ALH tokens", alh_tokens, ARRAY_SIZE(alh_tokens)},
+ [SOF_DMIC_TOKENS] = {"DMIC tokens", dmic_tokens, ARRAY_SIZE(dmic_tokens)},
+ [SOF_DMIC_PDM_TOKENS] = {"DMIC PDM tokens", dmic_pdm_tokens, ARRAY_SIZE(dmic_pdm_tokens)},
+ [SOF_ESAI_TOKENS] = {"ESAI tokens", esai_tokens, ARRAY_SIZE(esai_tokens)},
+ [SOF_SAI_TOKENS] = {"SAI tokens", sai_tokens, ARRAY_SIZE(sai_tokens)},
+ [SOF_AFE_TOKENS] = {"AFE tokens", afe_tokens, ARRAY_SIZE(afe_tokens)},
+ [SOF_ACPDMIC_TOKENS] = {"ACPDMIC tokens", acpdmic_tokens, ARRAY_SIZE(acpdmic_tokens)},
+ [SOF_ACPI2S_TOKENS] = {"ACPI2S tokens", acpi2s_tokens, ARRAY_SIZE(acpi2s_tokens)},
+ [SOF_MICFIL_TOKENS] = {"MICFIL PDM tokens",
+ micfil_pdm_tokens, ARRAY_SIZE(micfil_pdm_tokens)},
+ [SOF_ACP_SDW_TOKENS] = {"ACP_SDW tokens", acp_sdw_tokens, ARRAY_SIZE(acp_sdw_tokens)},
+};
+
+/**
+ * sof_comp_alloc - allocate and initialize buffer for a new component
+ * @swidget: pointer to struct snd_sof_widget containing extended data
+ * @ipc_size: IPC payload size that will be updated depending on valid
+ * extended data.
+ * @index: ID of the pipeline the component belongs to
+ *
+ * Return: The pointer to the new allocated component, NULL if failed.
+ */
+static void *sof_comp_alloc(struct snd_sof_widget *swidget, size_t *ipc_size,
+ int index)
+{
+ struct sof_ipc_comp *comp;
+ size_t total_size = *ipc_size;
+ size_t ext_size = sizeof(swidget->uuid);
+
+ /* only non-zero UUID is valid */
+ if (!guid_is_null(&swidget->uuid))
+ total_size += ext_size;
+
+ comp = kzalloc(total_size, GFP_KERNEL);
+ if (!comp)
+ return NULL;
+
+ /* configure comp new IPC message */
+ comp->hdr.size = total_size;
+ comp->hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW;
+ comp->id = swidget->comp_id;
+ comp->pipeline_id = index;
+ comp->core = swidget->core;
+
+ /* handle the extended data if needed */
+ if (total_size > *ipc_size) {
+ /* append extended data to the end of the component */
+ memcpy((u8 *)comp + *ipc_size, &swidget->uuid, ext_size);
+ comp->ext_data_length = ext_size;
+ }
+
+ /* update ipc_size and return */
+ *ipc_size = total_size;
+ return comp;
+}
+
+static void sof_dbg_comp_config(struct snd_soc_component *scomp, struct sof_ipc_comp_config *config)
+{
+ dev_dbg(scomp->dev, " config: periods snk %d src %d fmt %d\n",
+ config->periods_sink, config->periods_source,
+ config->frame_fmt);
+}
+
+static int sof_ipc3_widget_setup_comp_host(struct snd_sof_widget *swidget)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct sof_ipc_comp_host *host;
+ size_t ipc_size = sizeof(*host);
+ int ret;
+
+ host = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id);
+ if (!host)
+ return -ENOMEM;
+ swidget->private = host;
+
+ /* configure host comp IPC message */
+ host->comp.type = SOF_COMP_HOST;
+ host->config.hdr.size = sizeof(host->config);
+
+ if (swidget->id == snd_soc_dapm_aif_out)
+ host->direction = SOF_IPC_STREAM_CAPTURE;
+ else
+ host->direction = SOF_IPC_STREAM_PLAYBACK;
+
+ /* parse one set of pcm_tokens */
+ ret = sof_update_ipc_object(scomp, host, SOF_PCM_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(*host), 1);
+ if (ret < 0)
+ goto err;
+
+ /* parse one set of comp_tokens */
+ ret = sof_update_ipc_object(scomp, &host->config, SOF_COMP_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(host->config), 1);
+ if (ret < 0)
+ goto err;
+
+ dev_dbg(scomp->dev, "loaded host %s\n", swidget->widget->name);
+ sof_dbg_comp_config(scomp, &host->config);
+
+ return 0;
+err:
+ kfree(swidget->private);
+ swidget->private = NULL;
+
+ return ret;
+}
+
+static void sof_ipc3_widget_free_comp(struct snd_sof_widget *swidget)
+{
+ kfree(swidget->private);
+}
+
+static int sof_ipc3_widget_setup_comp_tone(struct snd_sof_widget *swidget)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct sof_ipc_comp_tone *tone;
+ size_t ipc_size = sizeof(*tone);
+ int ret;
+
+ tone = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id);
+ if (!tone)
+ return -ENOMEM;
+
+ swidget->private = tone;
+
+ /* configure siggen IPC message */
+ tone->comp.type = SOF_COMP_TONE;
+ tone->config.hdr.size = sizeof(tone->config);
+
+ /* parse one set of comp tokens */
+ ret = sof_update_ipc_object(scomp, &tone->config, SOF_COMP_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(tone->config), 1);
+ if (ret < 0) {
+ kfree(swidget->private);
+ swidget->private = NULL;
+ return ret;
+ }
+
+ dev_dbg(scomp->dev, "tone %s: frequency %d amplitude %d\n",
+ swidget->widget->name, tone->frequency, tone->amplitude);
+ sof_dbg_comp_config(scomp, &tone->config);
+
+ return 0;
+}
+
+static int sof_ipc3_widget_setup_comp_mixer(struct snd_sof_widget *swidget)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct sof_ipc_comp_mixer *mixer;
+ size_t ipc_size = sizeof(*mixer);
+ int ret;
+
+ mixer = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id);
+ if (!mixer)
+ return -ENOMEM;
+
+ swidget->private = mixer;
+
+ /* configure mixer IPC message */
+ mixer->comp.type = SOF_COMP_MIXER;
+ mixer->config.hdr.size = sizeof(mixer->config);
+
+ /* parse one set of comp tokens */
+ ret = sof_update_ipc_object(scomp, &mixer->config, SOF_COMP_TOKENS,
+ swidget->tuples, swidget->num_tuples,
+ sizeof(mixer->config), 1);
+ if (ret < 0) {
+ kfree(swidget->private);
+ swidget->private = NULL;
+
+ return ret;
+ }
+
+ dev_dbg(scomp->dev, "loaded mixer %s\n", swidget->widget->name);
+ sof_dbg_comp_config(scomp, &mixer->config);
+
+ return 0;
+}
+
+static int sof_ipc3_widget_setup_comp_pipeline(struct snd_sof_widget *swidget)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct snd_sof_pipeline *spipe = swidget->spipe;
+ struct sof_ipc_pipe_new *pipeline;
+ struct snd_sof_widget *comp_swidget;
+ int ret;
+
+ pipeline = kzalloc(sizeof(*pipeline), GFP_KERNEL);
+ if (!pipeline)
+ return -ENOMEM;
+
+ /* configure pipeline IPC message */
+ pipeline->hdr.size = sizeof(*pipeline);
+ pipeline->hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_PIPE_NEW;
+ pipeline->pipeline_id = swidget->pipeline_id;
+ pipeline->comp_id = swidget->comp_id;
+
+ swidget->private = pipeline;
+
+ /* component at start of pipeline is our stream id */
+ comp_swidget = snd_sof_find_swidget(scomp, swidget->widget->sname);
+ if (!comp_swidget) {
+ dev_err(scomp->dev, "scheduler %s refers to non existent widget %s\n",
+ swidget->widget->name, swidget->widget->sname);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ pipeline->sched_id = comp_swidget->comp_id;
+
+ /* parse one set of scheduler tokens */
+ ret = sof_update_ipc_object(scomp, pipeline, SOF_SCHED_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(*pipeline), 1);
+ if (ret < 0)
+ goto err;
+
+ /* parse one set of pipeline tokens */
+ ret = sof_update_ipc_object(scomp, swidget, SOF_PIPELINE_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(*swidget), 1);
+ if (ret < 0)
+ goto err;
+
+ if (sof_debug_check_flag(SOF_DBG_DISABLE_MULTICORE))
+ pipeline->core = SOF_DSP_PRIMARY_CORE;
+
+ if (sof_debug_check_flag(SOF_DBG_DYNAMIC_PIPELINES_OVERRIDE))
+ swidget->dynamic_pipeline_widget =
+ sof_debug_check_flag(SOF_DBG_DYNAMIC_PIPELINES_ENABLE);
+
+ dev_dbg(scomp->dev, "pipeline %s: period %d pri %d mips %d core %d frames %d dynamic %d\n",
+ swidget->widget->name, pipeline->period, pipeline->priority,
+ pipeline->period_mips, pipeline->core, pipeline->frames_per_sched,
+ swidget->dynamic_pipeline_widget);
+
+ swidget->core = pipeline->core;
+ spipe->core_mask |= BIT(pipeline->core);
+
+ return 0;
+
+err:
+ kfree(swidget->private);
+ swidget->private = NULL;
+
+ return ret;
+}
+
+static int sof_ipc3_widget_setup_comp_buffer(struct snd_sof_widget *swidget)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct sof_ipc_buffer *buffer;
+ int ret;
+
+ buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
+ if (!buffer)
+ return -ENOMEM;
+
+ swidget->private = buffer;
+
+ /* configure dai IPC message */
+ buffer->comp.hdr.size = sizeof(*buffer);
+ buffer->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_BUFFER_NEW;
+ buffer->comp.id = swidget->comp_id;
+ buffer->comp.type = SOF_COMP_BUFFER;
+ buffer->comp.pipeline_id = swidget->pipeline_id;
+ buffer->comp.core = swidget->core;
+
+ /* parse one set of buffer tokens */
+ ret = sof_update_ipc_object(scomp, buffer, SOF_BUFFER_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(*buffer), 1);
+ if (ret < 0) {
+ kfree(swidget->private);
+ swidget->private = NULL;
+ return ret;
+ }
+
+ dev_dbg(scomp->dev, "buffer %s: size %d caps 0x%x\n",
+ swidget->widget->name, buffer->size, buffer->caps);
+
+ return 0;
+}
+
+static int sof_ipc3_widget_setup_comp_src(struct snd_sof_widget *swidget)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct sof_ipc_comp_src *src;
+ size_t ipc_size = sizeof(*src);
+ int ret;
+
+ src = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id);
+ if (!src)
+ return -ENOMEM;
+
+ swidget->private = src;
+
+ /* configure src IPC message */
+ src->comp.type = SOF_COMP_SRC;
+ src->config.hdr.size = sizeof(src->config);
+
+ /* parse one set of src tokens */
+ ret = sof_update_ipc_object(scomp, src, SOF_SRC_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(*src), 1);
+ if (ret < 0)
+ goto err;
+
+ /* parse one set of comp tokens */
+ ret = sof_update_ipc_object(scomp, &src->config, SOF_COMP_TOKENS,
+ swidget->tuples, swidget->num_tuples, sizeof(src->config), 1);
+ if (ret < 0)
+ goto err;
+
+ dev_dbg(scomp->dev, "src %s: source rate %d sink rate %d\n",
+ swidget->widget->name, src->source_rate, src->sink_rate);
+ sof_dbg_comp_config(scomp, &src->config);
+
+ return 0;
+err:
+ kfree(swidget->private);
+ swidget->private = NULL;
+
+ return ret;
+}
+
+static int sof_ipc3_widget_setup_comp_asrc(struct snd_sof_widget *swidget)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct sof_ipc_comp_asrc *asrc;
+ size_t ipc_size = sizeof(*asrc);
+ int ret;
+
+ asrc = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id);
+ if (!asrc)
+ return -ENOMEM;
+
+ swidget->private = asrc;
+
+ /* configure ASRC IPC message */
+ asrc->comp.type = SOF_COMP_ASRC;
+ asrc->config.hdr.size = sizeof(asrc->config);
+
+ /* parse one set of asrc tokens */
+ ret = sof_update_ipc_object(scomp, asrc, SOF_ASRC_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(*asrc), 1);
+ if (ret < 0)
+ goto err;
+
+ /* parse one set of comp tokens */
+ ret = sof_update_ipc_object(scomp, &asrc->config, SOF_COMP_TOKENS,
+ swidget->tuples, swidget->num_tuples, sizeof(asrc->config), 1);
+ if (ret < 0)
+ goto err;
+
+ dev_dbg(scomp->dev, "asrc %s: source rate %d sink rate %d asynch %d operation %d\n",
+ swidget->widget->name, asrc->source_rate, asrc->sink_rate,
+ asrc->asynchronous_mode, asrc->operation_mode);
+
+ sof_dbg_comp_config(scomp, &asrc->config);
+
+ return 0;
+err:
+ kfree(swidget->private);
+ swidget->private = NULL;
+
+ return ret;
+}
+
+/*
+ * Mux topology
+ */
+static int sof_ipc3_widget_setup_comp_mux(struct snd_sof_widget *swidget)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct sof_ipc_comp_mux *mux;
+ size_t ipc_size = sizeof(*mux);
+ int ret;
+
+ mux = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id);
+ if (!mux)
+ return -ENOMEM;
+
+ swidget->private = mux;
+
+ /* configure mux IPC message */
+ mux->comp.type = SOF_COMP_MUX;
+ mux->config.hdr.size = sizeof(mux->config);
+
+ /* parse one set of comp tokens */
+ ret = sof_update_ipc_object(scomp, &mux->config, SOF_COMP_TOKENS,
+ swidget->tuples, swidget->num_tuples, sizeof(mux->config), 1);
+ if (ret < 0) {
+ kfree(swidget->private);
+ swidget->private = NULL;
+ return ret;
+ }
+
+ dev_dbg(scomp->dev, "loaded mux %s\n", swidget->widget->name);
+ sof_dbg_comp_config(scomp, &mux->config);
+
+ return 0;
+}
+
+/*
+ * PGA Topology
+ */
+
+static int sof_ipc3_widget_setup_comp_pga(struct snd_sof_widget *swidget)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct sof_ipc_comp_volume *volume;
+ struct snd_sof_control *scontrol;
+ size_t ipc_size = sizeof(*volume);
+ int min_step, max_step;
+ int ret;
+
+ volume = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id);
+ if (!volume)
+ return -ENOMEM;
+
+ swidget->private = volume;
+
+ /* configure volume IPC message */
+ volume->comp.type = SOF_COMP_VOLUME;
+ volume->config.hdr.size = sizeof(volume->config);
+
+ /* parse one set of volume tokens */
+ ret = sof_update_ipc_object(scomp, volume, SOF_VOLUME_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(*volume), 1);
+ if (ret < 0)
+ goto err;
+
+ /* parse one set of comp tokens */
+ ret = sof_update_ipc_object(scomp, &volume->config, SOF_COMP_TOKENS,
+ swidget->tuples, swidget->num_tuples,
+ sizeof(volume->config), 1);
+ if (ret < 0)
+ goto err;
+
+ dev_dbg(scomp->dev, "loaded PGA %s\n", swidget->widget->name);
+ sof_dbg_comp_config(scomp, &volume->config);
+
+ list_for_each_entry(scontrol, &sdev->kcontrol_list, list) {
+ if (scontrol->comp_id == swidget->comp_id &&
+ scontrol->volume_table) {
+ min_step = scontrol->min_volume_step;
+ max_step = scontrol->max_volume_step;
+ volume->min_value = scontrol->volume_table[min_step];
+ volume->max_value = scontrol->volume_table[max_step];
+ volume->channels = scontrol->num_channels;
+ break;
+ }
+ }
+
+ return 0;
+err:
+ kfree(swidget->private);
+ swidget->private = NULL;
+
+ return ret;
+}
+
+static int sof_get_control_data(struct snd_soc_component *scomp,
+ struct snd_soc_dapm_widget *widget,
+ struct sof_widget_data *wdata, size_t *size)
+{
+ const struct snd_kcontrol_new *kc;
+ struct sof_ipc_ctrl_data *cdata;
+ struct soc_mixer_control *sm;
+ struct soc_bytes_ext *sbe;
+ struct soc_enum *se;
+ int i;
+
+ *size = 0;
+
+ for (i = 0; i < widget->num_kcontrols; i++) {
+ kc = &widget->kcontrol_news[i];
+
+ switch (widget->dobj.widget.kcontrol_type[i]) {
+ case SND_SOC_TPLG_TYPE_MIXER:
+ sm = (struct soc_mixer_control *)kc->private_value;
+ wdata[i].control = sm->dobj.private;
+ break;
+ case SND_SOC_TPLG_TYPE_BYTES:
+ sbe = (struct soc_bytes_ext *)kc->private_value;
+ wdata[i].control = sbe->dobj.private;
+ break;
+ case SND_SOC_TPLG_TYPE_ENUM:
+ se = (struct soc_enum *)kc->private_value;
+ wdata[i].control = se->dobj.private;
+ break;
+ default:
+ dev_err(scomp->dev, "Unknown kcontrol type %u in widget %s\n",
+ widget->dobj.widget.kcontrol_type[i], widget->name);
+ return -EINVAL;
+ }
+
+ if (!wdata[i].control) {
+ dev_err(scomp->dev, "No scontrol for widget %s\n", widget->name);
+ return -EINVAL;
+ }
+
+ cdata = wdata[i].control->ipc_control_data;
+
+ if (widget->dobj.widget.kcontrol_type[i] == SND_SOC_TPLG_TYPE_BYTES) {
+ /* make sure data is valid - data can be updated at runtime */
+ if (cdata->data->magic != SOF_ABI_MAGIC)
+ return -EINVAL;
+
+ wdata[i].pdata = cdata->data->data;
+ wdata[i].pdata_size = cdata->data->size;
+ } else {
+ /* points to the control data union */
+ wdata[i].pdata = cdata->chanv;
+ /*
+ * wdata[i].control->size is calculated with struct_size
+ * and includes the size of struct sof_ipc_ctrl_data
+ */
+ wdata[i].pdata_size = wdata[i].control->size -
+ sizeof(struct sof_ipc_ctrl_data);
+ }
+
+ *size += wdata[i].pdata_size;
+
+ /* get data type */
+ switch (cdata->cmd) {
+ case SOF_CTRL_CMD_VOLUME:
+ case SOF_CTRL_CMD_ENUM:
+ case SOF_CTRL_CMD_SWITCH:
+ wdata[i].ipc_cmd = SOF_IPC_COMP_SET_VALUE;
+ wdata[i].ctrl_type = SOF_CTRL_TYPE_VALUE_CHAN_SET;
+ break;
+ case SOF_CTRL_CMD_BINARY:
+ wdata[i].ipc_cmd = SOF_IPC_COMP_SET_DATA;
+ wdata[i].ctrl_type = SOF_CTRL_TYPE_DATA_SET;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int sof_process_load(struct snd_soc_component *scomp,
+ struct snd_sof_widget *swidget, int type)
+{
+ struct snd_soc_dapm_widget *widget = swidget->widget;
+ struct sof_ipc_comp_process *process;
+ struct sof_widget_data *wdata = NULL;
+ size_t ipc_data_size = 0;
+ size_t ipc_size;
+ int offset = 0;
+ int ret;
+ int i;
+
+ /* allocate struct for widget control data sizes and types */
+ if (widget->num_kcontrols) {
+ wdata = kcalloc(widget->num_kcontrols, sizeof(*wdata), GFP_KERNEL);
+ if (!wdata)
+ return -ENOMEM;
+
+ /* get possible component controls and get size of all pdata */
+ ret = sof_get_control_data(scomp, widget, wdata, &ipc_data_size);
+ if (ret < 0)
+ goto out;
+ }
+
+ ipc_size = sizeof(struct sof_ipc_comp_process) + ipc_data_size;
+
+ /* we are exceeding max ipc size, config needs to be sent separately */
+ if (ipc_size > SOF_IPC_MSG_MAX_SIZE) {
+ ipc_size -= ipc_data_size;
+ ipc_data_size = 0;
+ }
+
+ process = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id);
+ if (!process) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ swidget->private = process;
+
+ /* configure iir IPC message */
+ process->comp.type = type;
+ process->config.hdr.size = sizeof(process->config);
+
+ /* parse one set of comp tokens */
+ ret = sof_update_ipc_object(scomp, &process->config, SOF_COMP_TOKENS,
+ swidget->tuples, swidget->num_tuples,
+ sizeof(process->config), 1);
+ if (ret < 0)
+ goto err;
+
+ dev_dbg(scomp->dev, "loaded process %s\n", swidget->widget->name);
+ sof_dbg_comp_config(scomp, &process->config);
+
+ /*
+ * found private data in control, so copy it.
+ * get possible component controls - get size of all pdata,
+ * then memcpy with headers
+ */
+ if (ipc_data_size) {
+ for (i = 0; i < widget->num_kcontrols; i++) {
+ if (!wdata[i].pdata_size)
+ continue;
+
+ memcpy(&process->data[offset], wdata[i].pdata,
+ wdata[i].pdata_size);
+ offset += wdata[i].pdata_size;
+ }
+ }
+
+ process->size = ipc_data_size;
+
+ kfree(wdata);
+
+ return 0;
+err:
+ kfree(swidget->private);
+ swidget->private = NULL;
+out:
+ kfree(wdata);
+ return ret;
+}
+
+static enum sof_comp_type find_process_comp_type(enum sof_ipc_process_type type)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(sof_process); i++) {
+ if (sof_process[i].type == type)
+ return sof_process[i].comp_type;
+ }
+
+ return SOF_COMP_NONE;
+}
+
+/*
+ * Processing Component Topology - can be "effect", "codec", or general
+ * "processing".
+ */
+
+static int sof_widget_update_ipc_comp_process(struct snd_sof_widget *swidget)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct sof_ipc_comp_process config;
+ int ret;
+
+ memset(&config, 0, sizeof(config));
+ config.comp.core = swidget->core;
+
+ /* parse one set of process tokens */
+ ret = sof_update_ipc_object(scomp, &config, SOF_PROCESS_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(config), 1);
+ if (ret < 0)
+ return ret;
+
+ /* now load process specific data and send IPC */
+ return sof_process_load(scomp, swidget, find_process_comp_type(config.type));
+}
+
+static int sof_link_hda_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink,
+ struct sof_ipc_dai_config *config, struct snd_sof_dai *dai)
+{
+ struct sof_dai_private_data *private = dai->private;
+ u32 size = sizeof(*config);
+ int ret;
+
+ /* init IPC */
+ memset(&config->hda, 0, sizeof(config->hda));
+ config->hdr.size = size;
+
+ /* parse one set of HDA tokens */
+ ret = sof_update_ipc_object(scomp, &config->hda, SOF_HDA_TOKENS, slink->tuples,
+ slink->num_tuples, size, 1);
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(scomp->dev, "HDA config rate %d channels %d\n",
+ config->hda.rate, config->hda.channels);
+
+ config->hda.link_dma_ch = DMA_CHAN_INVALID;
+
+ dai->number_configs = 1;
+ dai->current_config = 0;
+ private->dai_config = kmemdup(config, size, GFP_KERNEL);
+ if (!private->dai_config)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static void sof_dai_set_format(struct snd_soc_tplg_hw_config *hw_config,
+ struct sof_ipc_dai_config *config)
+{
+ /* clock directions wrt codec */
+ config->format &= ~SOF_DAI_FMT_CLOCK_PROVIDER_MASK;
+ if (hw_config->bclk_provider == SND_SOC_TPLG_BCLK_CP) {
+ /* codec is bclk provider */
+ if (hw_config->fsync_provider == SND_SOC_TPLG_FSYNC_CP)
+ config->format |= SOF_DAI_FMT_CBP_CFP;
+ else
+ config->format |= SOF_DAI_FMT_CBP_CFC;
+ } else {
+ /* codec is bclk consumer */
+ if (hw_config->fsync_provider == SND_SOC_TPLG_FSYNC_CP)
+ config->format |= SOF_DAI_FMT_CBC_CFP;
+ else
+ config->format |= SOF_DAI_FMT_CBC_CFC;
+ }
+
+ /* inverted clocks ? */
+ config->format &= ~SOF_DAI_FMT_INV_MASK;
+ if (hw_config->invert_bclk) {
+ if (hw_config->invert_fsync)
+ config->format |= SOF_DAI_FMT_IB_IF;
+ else
+ config->format |= SOF_DAI_FMT_IB_NF;
+ } else {
+ if (hw_config->invert_fsync)
+ config->format |= SOF_DAI_FMT_NB_IF;
+ else
+ config->format |= SOF_DAI_FMT_NB_NF;
+ }
+}
+
+static int sof_link_sai_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink,
+ struct sof_ipc_dai_config *config, struct snd_sof_dai *dai)
+{
+ struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs;
+ struct sof_dai_private_data *private = dai->private;
+ u32 size = sizeof(*config);
+ int ret;
+
+ /* handle master/slave and inverted clocks */
+ sof_dai_set_format(hw_config, config);
+
+ /* init IPC */
+ memset(&config->sai, 0, sizeof(config->sai));
+ config->hdr.size = size;
+
+ /* parse one set of SAI tokens */
+ ret = sof_update_ipc_object(scomp, &config->sai, SOF_SAI_TOKENS, slink->tuples,
+ slink->num_tuples, size, 1);
+ if (ret < 0)
+ return ret;
+
+ config->sai.mclk_rate = le32_to_cpu(hw_config->mclk_rate);
+ config->sai.bclk_rate = le32_to_cpu(hw_config->bclk_rate);
+ config->sai.fsync_rate = le32_to_cpu(hw_config->fsync_rate);
+ config->sai.mclk_direction = hw_config->mclk_direction;
+
+ config->sai.tdm_slots = le32_to_cpu(hw_config->tdm_slots);
+ config->sai.tdm_slot_width = le32_to_cpu(hw_config->tdm_slot_width);
+ config->sai.rx_slots = le32_to_cpu(hw_config->rx_slots);
+ config->sai.tx_slots = le32_to_cpu(hw_config->tx_slots);
+
+ dev_info(scomp->dev,
+ "tplg: config SAI%d fmt 0x%x mclk %d width %d slots %d mclk id %d\n",
+ config->dai_index, config->format,
+ config->sai.mclk_rate, config->sai.tdm_slot_width,
+ config->sai.tdm_slots, config->sai.mclk_id);
+
+ if (config->sai.tdm_slots < 1 || config->sai.tdm_slots > 8) {
+ dev_err(scomp->dev, "Invalid channel count for SAI%d\n", config->dai_index);
+ return -EINVAL;
+ }
+
+ dai->number_configs = 1;
+ dai->current_config = 0;
+ private->dai_config = kmemdup(config, size, GFP_KERNEL);
+ if (!private->dai_config)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int sof_link_esai_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink,
+ struct sof_ipc_dai_config *config, struct snd_sof_dai *dai)
+{
+ struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs;
+ struct sof_dai_private_data *private = dai->private;
+ u32 size = sizeof(*config);
+ int ret;
+
+ /* handle master/slave and inverted clocks */
+ sof_dai_set_format(hw_config, config);
+
+ /* init IPC */
+ memset(&config->esai, 0, sizeof(config->esai));
+ config->hdr.size = size;
+
+ /* parse one set of ESAI tokens */
+ ret = sof_update_ipc_object(scomp, &config->esai, SOF_ESAI_TOKENS, slink->tuples,
+ slink->num_tuples, size, 1);
+ if (ret < 0)
+ return ret;
+
+ config->esai.mclk_rate = le32_to_cpu(hw_config->mclk_rate);
+ config->esai.bclk_rate = le32_to_cpu(hw_config->bclk_rate);
+ config->esai.fsync_rate = le32_to_cpu(hw_config->fsync_rate);
+ config->esai.mclk_direction = hw_config->mclk_direction;
+ config->esai.tdm_slots = le32_to_cpu(hw_config->tdm_slots);
+ config->esai.tdm_slot_width = le32_to_cpu(hw_config->tdm_slot_width);
+ config->esai.rx_slots = le32_to_cpu(hw_config->rx_slots);
+ config->esai.tx_slots = le32_to_cpu(hw_config->tx_slots);
+
+ dev_info(scomp->dev,
+ "tplg: config ESAI%d fmt 0x%x mclk %d width %d slots %d mclk id %d\n",
+ config->dai_index, config->format,
+ config->esai.mclk_rate, config->esai.tdm_slot_width,
+ config->esai.tdm_slots, config->esai.mclk_id);
+
+ if (config->esai.tdm_slots < 1 || config->esai.tdm_slots > 8) {
+ dev_err(scomp->dev, "Invalid channel count for ESAI%d\n", config->dai_index);
+ return -EINVAL;
+ }
+
+ dai->number_configs = 1;
+ dai->current_config = 0;
+ private->dai_config = kmemdup(config, size, GFP_KERNEL);
+ if (!private->dai_config)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int sof_link_micfil_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink,
+ struct sof_ipc_dai_config *config, struct snd_sof_dai *dai)
+{
+ struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs;
+ struct sof_dai_private_data *private = dai->private;
+ u32 size = sizeof(*config);
+ int ret;
+
+ /* handle master/slave and inverted clocks */
+ sof_dai_set_format(hw_config, config);
+
+ config->hdr.size = size;
+
+ /* parse the required set of MICFIL PDM tokens based on num_hw_cfgs */
+ ret = sof_update_ipc_object(scomp, &config->micfil, SOF_MICFIL_TOKENS, slink->tuples,
+ slink->num_tuples, size, slink->num_hw_configs);
+ if (ret < 0)
+ return ret;
+
+ dev_info(scomp->dev, "MICFIL PDM config dai_index %d channel %d rate %d\n",
+ config->dai_index, config->micfil.pdm_ch, config->micfil.pdm_rate);
+
+ dai->number_configs = 1;
+ dai->current_config = 0;
+ private->dai_config = kmemdup(config, size, GFP_KERNEL);
+ if (!private->dai_config)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int sof_link_acp_dmic_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink,
+ struct sof_ipc_dai_config *config, struct snd_sof_dai *dai)
+{
+ struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs;
+ struct sof_dai_private_data *private = dai->private;
+ u32 size = sizeof(*config);
+ int ret;
+
+ /* handle master/slave and inverted clocks */
+ sof_dai_set_format(hw_config, config);
+
+ config->hdr.size = size;
+
+ /* parse the required set of ACPDMIC tokens based on num_hw_cfgs */
+ ret = sof_update_ipc_object(scomp, &config->acpdmic, SOF_ACPDMIC_TOKENS, slink->tuples,
+ slink->num_tuples, size, slink->num_hw_configs);
+ if (ret < 0)
+ return ret;
+
+ dev_info(scomp->dev, "ACP_DMIC config ACP%d channel %d rate %d\n",
+ config->dai_index, config->acpdmic.pdm_ch,
+ config->acpdmic.pdm_rate);
+
+ dai->number_configs = 1;
+ dai->current_config = 0;
+ private->dai_config = kmemdup(config, size, GFP_KERNEL);
+ if (!private->dai_config)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int sof_link_acp_bt_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink,
+ struct sof_ipc_dai_config *config, struct snd_sof_dai *dai)
+{
+ struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs;
+ struct sof_dai_private_data *private = dai->private;
+ u32 size = sizeof(*config);
+ int ret;
+
+ /* handle master/slave and inverted clocks */
+ sof_dai_set_format(hw_config, config);
+
+ /* init IPC */
+ memset(&config->acpbt, 0, sizeof(config->acpbt));
+ config->hdr.size = size;
+
+ ret = sof_update_ipc_object(scomp, &config->acpbt, SOF_ACPI2S_TOKENS, slink->tuples,
+ slink->num_tuples, size, slink->num_hw_configs);
+ if (ret < 0)
+ return ret;
+
+ dev_info(scomp->dev, "ACP_BT config ACP%d channel %d rate %d tdm_mode %d\n",
+ config->dai_index, config->acpbt.tdm_slots,
+ config->acpbt.fsync_rate, config->acpbt.tdm_mode);
+
+ dai->number_configs = 1;
+ dai->current_config = 0;
+ private->dai_config = kmemdup(config, size, GFP_KERNEL);
+ if (!private->dai_config)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int sof_link_acp_sp_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink,
+ struct sof_ipc_dai_config *config, struct snd_sof_dai *dai)
+{
+ struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs;
+ struct sof_dai_private_data *private = dai->private;
+ u32 size = sizeof(*config);
+ int ret;
+
+ /* handle master/slave and inverted clocks */
+ sof_dai_set_format(hw_config, config);
+
+ /* init IPC */
+ memset(&config->acpsp, 0, sizeof(config->acpsp));
+ config->hdr.size = size;
+
+ ret = sof_update_ipc_object(scomp, &config->acpsp, SOF_ACPI2S_TOKENS, slink->tuples,
+ slink->num_tuples, size, slink->num_hw_configs);
+ if (ret < 0)
+ return ret;
+
+
+ dev_info(scomp->dev, "ACP_SP config ACP%d channel %d rate %d tdm_mode %d\n",
+ config->dai_index, config->acpsp.tdm_slots,
+ config->acpsp.fsync_rate, config->acpsp.tdm_mode);
+
+ dai->number_configs = 1;
+ dai->current_config = 0;
+ private->dai_config = kmemdup(config, size, GFP_KERNEL);
+ if (!private->dai_config)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int sof_link_acp_hs_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink,
+ struct sof_ipc_dai_config *config, struct snd_sof_dai *dai)
+{
+ struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs;
+ struct sof_dai_private_data *private = dai->private;
+ u32 size = sizeof(*config);
+ int ret;
+
+ /* Configures the DAI hardware format and inverted clocks */
+ sof_dai_set_format(hw_config, config);
+
+ /* init IPC */
+ memset(&config->acphs, 0, sizeof(config->acphs));
+ config->hdr.size = size;
+
+ ret = sof_update_ipc_object(scomp, &config->acphs, SOF_ACPI2S_TOKENS, slink->tuples,
+ slink->num_tuples, size, slink->num_hw_configs);
+ if (ret < 0)
+ return ret;
+
+ dev_info(scomp->dev, "ACP_HS config ACP%d channel %d rate %d tdm_mode %d\n",
+ config->dai_index, config->acphs.tdm_slots,
+ config->acphs.fsync_rate, config->acphs.tdm_mode);
+
+ dai->number_configs = 1;
+ dai->current_config = 0;
+ private->dai_config = kmemdup(config, size, GFP_KERNEL);
+ if (!private->dai_config)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int sof_link_acp_sdw_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink,
+ struct sof_ipc_dai_config *config, struct snd_sof_dai *dai)
+{
+ struct sof_dai_private_data *private = dai->private;
+ u32 size = sizeof(*config);
+ int ret;
+
+ /* parse the required set of ACP_SDW tokens based on num_hw_cfgs */
+ ret = sof_update_ipc_object(scomp, &config->acp_sdw, SOF_ACP_SDW_TOKENS, slink->tuples,
+ slink->num_tuples, size, slink->num_hw_configs);
+ if (ret < 0)
+ return ret;
+
+ /* init IPC */
+ config->hdr.size = size;
+ dev_dbg(scomp->dev, "ACP SDW config rate %d channels %d\n",
+ config->acp_sdw.rate, config->acp_sdw.channels);
+
+ /* set config for all DAI's with name matching the link name */
+ dai->number_configs = 1;
+ dai->current_config = 0;
+ private->dai_config = kmemdup(config, size, GFP_KERNEL);
+ if (!private->dai_config)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int sof_link_afe_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink,
+ struct sof_ipc_dai_config *config, struct snd_sof_dai *dai)
+{
+ struct sof_dai_private_data *private = dai->private;
+ u32 size = sizeof(*config);
+ int ret;
+
+ config->hdr.size = size;
+
+ /* parse the required set of AFE tokens based on num_hw_cfgs */
+ ret = sof_update_ipc_object(scomp, &config->afe, SOF_AFE_TOKENS, slink->tuples,
+ slink->num_tuples, size, slink->num_hw_configs);
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(scomp->dev, "AFE config rate %d channels %d format:%d\n",
+ config->afe.rate, config->afe.channels, config->afe.format);
+
+ config->afe.stream_id = DMA_CHAN_INVALID;
+
+ dai->number_configs = 1;
+ dai->current_config = 0;
+ private->dai_config = kmemdup(config, size, GFP_KERNEL);
+ if (!private->dai_config)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int sof_link_ssp_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink,
+ struct sof_ipc_dai_config *config, struct snd_sof_dai *dai)
+{
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs;
+ struct sof_dai_private_data *private = dai->private;
+ u32 size = sizeof(*config);
+ int current_config = 0;
+ int i, ret;
+
+ /*
+ * Parse common data, we should have 1 common data per hw_config.
+ */
+ ret = sof_update_ipc_object(scomp, &config->ssp, SOF_SSP_TOKENS, slink->tuples,
+ slink->num_tuples, size, slink->num_hw_configs);
+ if (ret < 0)
+ return ret;
+
+ /* process all possible hw configs */
+ for (i = 0; i < slink->num_hw_configs; i++) {
+ if (le32_to_cpu(hw_config[i].id) == slink->default_hw_cfg_id)
+ current_config = i;
+
+ /* handle master/slave and inverted clocks */
+ sof_dai_set_format(&hw_config[i], &config[i]);
+
+ config[i].hdr.size = size;
+
+ if (sdev->mclk_id_override) {
+ dev_dbg(scomp->dev, "tplg: overriding topology mclk_id %d by quirk %d\n",
+ config[i].ssp.mclk_id, sdev->mclk_id_quirk);
+ config[i].ssp.mclk_id = sdev->mclk_id_quirk;
+ }
+
+ /* copy differentiating hw configs to ipc structs */
+ config[i].ssp.mclk_rate = le32_to_cpu(hw_config[i].mclk_rate);
+ config[i].ssp.bclk_rate = le32_to_cpu(hw_config[i].bclk_rate);
+ config[i].ssp.fsync_rate = le32_to_cpu(hw_config[i].fsync_rate);
+ config[i].ssp.tdm_slots = le32_to_cpu(hw_config[i].tdm_slots);
+ config[i].ssp.tdm_slot_width = le32_to_cpu(hw_config[i].tdm_slot_width);
+ config[i].ssp.mclk_direction = hw_config[i].mclk_direction;
+ config[i].ssp.rx_slots = le32_to_cpu(hw_config[i].rx_slots);
+ config[i].ssp.tx_slots = le32_to_cpu(hw_config[i].tx_slots);
+
+ dev_dbg(scomp->dev, "tplg: config SSP%d fmt %#x mclk %d bclk %d fclk %d width (%d)%d slots %d mclk id %d quirks %d clks_control %#x\n",
+ config[i].dai_index, config[i].format,
+ config[i].ssp.mclk_rate, config[i].ssp.bclk_rate,
+ config[i].ssp.fsync_rate, config[i].ssp.sample_valid_bits,
+ config[i].ssp.tdm_slot_width, config[i].ssp.tdm_slots,
+ config[i].ssp.mclk_id, config[i].ssp.quirks, config[i].ssp.clks_control);
+
+ /* validate SSP fsync rate and channel count */
+ if (config[i].ssp.fsync_rate < 8000 || config[i].ssp.fsync_rate > 192000) {
+ dev_err(scomp->dev, "Invalid fsync rate for SSP%d\n", config[i].dai_index);
+ return -EINVAL;
+ }
+
+ if (config[i].ssp.tdm_slots < 1 || config[i].ssp.tdm_slots > 8) {
+ dev_err(scomp->dev, "Invalid channel count for SSP%d\n",
+ config[i].dai_index);
+ return -EINVAL;
+ }
+ }
+
+ dai->number_configs = slink->num_hw_configs;
+ dai->current_config = current_config;
+ private->dai_config = kmemdup(config, size * slink->num_hw_configs, GFP_KERNEL);
+ if (!private->dai_config)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int sof_link_dmic_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink,
+ struct sof_ipc_dai_config *config, struct snd_sof_dai *dai)
+{
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct sof_dai_private_data *private = dai->private;
+ struct sof_ipc_fw_ready *ready = &sdev->fw_ready;
+ struct sof_ipc_fw_version *v = &ready->version;
+ size_t size = sizeof(*config);
+ int i, ret;
+
+ /* Ensure the entire DMIC config struct is zeros */
+ memset(&config->dmic, 0, sizeof(config->dmic));
+
+ /* parse the required set of DMIC tokens based on num_hw_cfgs */
+ ret = sof_update_ipc_object(scomp, &config->dmic, SOF_DMIC_TOKENS, slink->tuples,
+ slink->num_tuples, size, slink->num_hw_configs);
+ if (ret < 0)
+ return ret;
+
+ /* parse the required set of DMIC PDM tokens based on number of active PDM's */
+ ret = sof_update_ipc_object(scomp, &config->dmic.pdm[0], SOF_DMIC_PDM_TOKENS,
+ slink->tuples, slink->num_tuples,
+ sizeof(struct sof_ipc_dai_dmic_pdm_ctrl),
+ config->dmic.num_pdm_active);
+ if (ret < 0)
+ return ret;
+
+ /* set IPC header size */
+ config->hdr.size = size;
+
+ /* debug messages */
+ dev_dbg(scomp->dev, "tplg: config DMIC%d driver version %d\n",
+ config->dai_index, config->dmic.driver_ipc_version);
+ dev_dbg(scomp->dev, "pdmclk_min %d pdm_clkmax %d duty_min %d\n",
+ config->dmic.pdmclk_min, config->dmic.pdmclk_max,
+ config->dmic.duty_min);
+ dev_dbg(scomp->dev, "duty_max %d fifo_fs %d num_pdms active %d\n",
+ config->dmic.duty_max, config->dmic.fifo_fs,
+ config->dmic.num_pdm_active);
+ dev_dbg(scomp->dev, "fifo word length %d\n", config->dmic.fifo_bits);
+
+ for (i = 0; i < config->dmic.num_pdm_active; i++) {
+ dev_dbg(scomp->dev, "pdm %d mic a %d mic b %d\n",
+ config->dmic.pdm[i].id,
+ config->dmic.pdm[i].enable_mic_a,
+ config->dmic.pdm[i].enable_mic_b);
+ dev_dbg(scomp->dev, "pdm %d polarity a %d polarity b %d\n",
+ config->dmic.pdm[i].id,
+ config->dmic.pdm[i].polarity_mic_a,
+ config->dmic.pdm[i].polarity_mic_b);
+ dev_dbg(scomp->dev, "pdm %d clk_edge %d skew %d\n",
+ config->dmic.pdm[i].id,
+ config->dmic.pdm[i].clk_edge,
+ config->dmic.pdm[i].skew);
+ }
+
+ /*
+ * this takes care of backwards compatible handling of fifo_bits_b.
+ * It is deprecated since firmware ABI version 3.0.1.
+ */
+ if (SOF_ABI_VER(v->major, v->minor, v->micro) < SOF_ABI_VER(3, 0, 1))
+ config->dmic.fifo_bits_b = config->dmic.fifo_bits;
+
+ dai->number_configs = 1;
+ dai->current_config = 0;
+ private->dai_config = kmemdup(config, size, GFP_KERNEL);
+ if (!private->dai_config)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int sof_link_alh_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink,
+ struct sof_ipc_dai_config *config, struct snd_sof_dai *dai)
+{
+ struct sof_dai_private_data *private = dai->private;
+ u32 size = sizeof(*config);
+ int ret;
+
+ /* parse the required set of ALH tokens based on num_hw_cfgs */
+ ret = sof_update_ipc_object(scomp, &config->alh, SOF_ALH_TOKENS, slink->tuples,
+ slink->num_tuples, size, slink->num_hw_configs);
+ if (ret < 0)
+ return ret;
+
+ /* init IPC */
+ config->hdr.size = size;
+
+ /* set config for all DAI's with name matching the link name */
+ dai->number_configs = 1;
+ dai->current_config = 0;
+ private->dai_config = kmemdup(config, size, GFP_KERNEL);
+ if (!private->dai_config)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int sof_ipc3_widget_setup_comp_dai(struct snd_sof_widget *swidget)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct snd_sof_dai *dai = swidget->private;
+ struct sof_dai_private_data *private;
+ struct sof_ipc_comp_dai *comp_dai;
+ size_t ipc_size = sizeof(*comp_dai);
+ struct sof_ipc_dai_config *config;
+ struct snd_sof_dai_link *slink;
+ int ret;
+
+ private = kzalloc(sizeof(*private), GFP_KERNEL);
+ if (!private)
+ return -ENOMEM;
+
+ dai->private = private;
+
+ private->comp_dai = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id);
+ if (!private->comp_dai) {
+ ret = -ENOMEM;
+ goto free;
+ }
+
+ /* configure dai IPC message */
+ comp_dai = private->comp_dai;
+ comp_dai->comp.type = SOF_COMP_DAI;
+ comp_dai->config.hdr.size = sizeof(comp_dai->config);
+
+ /* parse one set of DAI tokens */
+ ret = sof_update_ipc_object(scomp, comp_dai, SOF_DAI_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(*comp_dai), 1);
+ if (ret < 0)
+ goto free;
+
+ /* update comp_tokens */
+ ret = sof_update_ipc_object(scomp, &comp_dai->config, SOF_COMP_TOKENS,
+ swidget->tuples, swidget->num_tuples,
+ sizeof(comp_dai->config), 1);
+ if (ret < 0)
+ goto free;
+
+ dev_dbg(scomp->dev, "dai %s: type %d index %d\n",
+ swidget->widget->name, comp_dai->type, comp_dai->dai_index);
+ sof_dbg_comp_config(scomp, &comp_dai->config);
+
+ /* now update DAI config */
+ list_for_each_entry(slink, &sdev->dai_link_list, list) {
+ struct sof_ipc_dai_config common_config;
+ int i;
+
+ if (strcmp(slink->link->name, dai->name))
+ continue;
+
+ /* Reserve memory for all hw configs, eventually freed by widget */
+ config = kcalloc(slink->num_hw_configs, sizeof(*config), GFP_KERNEL);
+ if (!config) {
+ ret = -ENOMEM;
+ goto free_comp;
+ }
+
+ /* parse one set of DAI link tokens */
+ ret = sof_update_ipc_object(scomp, &common_config, SOF_DAI_LINK_TOKENS,
+ slink->tuples, slink->num_tuples,
+ sizeof(common_config), 1);
+ if (ret < 0)
+ goto free_config;
+
+ for (i = 0; i < slink->num_hw_configs; i++) {
+ config[i].hdr.cmd = SOF_IPC_GLB_DAI_MSG | SOF_IPC_DAI_CONFIG;
+ config[i].format = le32_to_cpu(slink->hw_configs[i].fmt);
+ config[i].type = common_config.type;
+ config[i].dai_index = comp_dai->dai_index;
+ }
+
+ switch (common_config.type) {
+ case SOF_DAI_INTEL_SSP:
+ ret = sof_link_ssp_load(scomp, slink, config, dai);
+ break;
+ case SOF_DAI_INTEL_DMIC:
+ ret = sof_link_dmic_load(scomp, slink, config, dai);
+ break;
+ case SOF_DAI_INTEL_HDA:
+ ret = sof_link_hda_load(scomp, slink, config, dai);
+ break;
+ case SOF_DAI_INTEL_ALH:
+ ret = sof_link_alh_load(scomp, slink, config, dai);
+ break;
+ case SOF_DAI_IMX_SAI:
+ ret = sof_link_sai_load(scomp, slink, config, dai);
+ break;
+ case SOF_DAI_IMX_ESAI:
+ ret = sof_link_esai_load(scomp, slink, config, dai);
+ break;
+ case SOF_DAI_IMX_MICFIL:
+ ret = sof_link_micfil_load(scomp, slink, config, dai);
+ break;
+ case SOF_DAI_AMD_BT:
+ ret = sof_link_acp_bt_load(scomp, slink, config, dai);
+ break;
+ case SOF_DAI_AMD_SP:
+ case SOF_DAI_AMD_SP_VIRTUAL:
+ ret = sof_link_acp_sp_load(scomp, slink, config, dai);
+ break;
+ case SOF_DAI_AMD_HS:
+ case SOF_DAI_AMD_HS_VIRTUAL:
+ ret = sof_link_acp_hs_load(scomp, slink, config, dai);
+ break;
+ case SOF_DAI_AMD_DMIC:
+ ret = sof_link_acp_dmic_load(scomp, slink, config, dai);
+ break;
+ case SOF_DAI_MEDIATEK_AFE:
+ ret = sof_link_afe_load(scomp, slink, config, dai);
+ break;
+ case SOF_DAI_AMD_SDW:
+ ret = sof_link_acp_sdw_load(scomp, slink, config, dai);
+ break;
+ default:
+ break;
+ }
+ if (ret < 0) {
+ dev_err(scomp->dev, "failed to load config for dai %s\n", dai->name);
+ goto free_config;
+ }
+
+ kfree(config);
+ }
+
+ return 0;
+free_config:
+ kfree(config);
+free_comp:
+ kfree(comp_dai);
+free:
+ kfree(private);
+ dai->private = NULL;
+ return ret;
+}
+
+static void sof_ipc3_widget_free_comp_dai(struct snd_sof_widget *swidget)
+{
+ switch (swidget->id) {
+ case snd_soc_dapm_dai_in:
+ case snd_soc_dapm_dai_out:
+ {
+ struct snd_sof_dai *dai = swidget->private;
+ struct sof_dai_private_data *dai_data;
+
+ if (!dai)
+ return;
+
+ dai_data = dai->private;
+ if (dai_data) {
+ kfree(dai_data->comp_dai);
+ kfree(dai_data->dai_config);
+ kfree(dai_data);
+ }
+ kfree(dai);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+static int sof_ipc3_route_setup(struct snd_sof_dev *sdev, struct snd_sof_route *sroute)
+{
+ struct sof_ipc_pipe_comp_connect connect;
+ int ret;
+
+ connect.hdr.size = sizeof(connect);
+ connect.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_CONNECT;
+ connect.source_id = sroute->src_widget->comp_id;
+ connect.sink_id = sroute->sink_widget->comp_id;
+
+ dev_dbg(sdev->dev, "setting up route %s -> %s\n",
+ sroute->src_widget->widget->name,
+ sroute->sink_widget->widget->name);
+
+ /* send ipc */
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, &connect, sizeof(connect));
+ if (ret < 0)
+ dev_err(sdev->dev, "%s: route %s -> %s failed\n", __func__,
+ sroute->src_widget->widget->name, sroute->sink_widget->widget->name);
+
+ return ret;
+}
+
+static int sof_ipc3_control_load_bytes(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol)
+{
+ struct sof_ipc_ctrl_data *cdata;
+ size_t priv_size_check;
+ int ret;
+
+ if (scontrol->max_size < (sizeof(*cdata) + sizeof(struct sof_abi_hdr))) {
+ dev_err(sdev->dev, "%s: insufficient size for a bytes control: %zu.\n",
+ __func__, scontrol->max_size);
+ return -EINVAL;
+ }
+
+ if (scontrol->priv_size > scontrol->max_size - sizeof(*cdata)) {
+ dev_err(sdev->dev,
+ "%s: bytes data size %zu exceeds max %zu.\n", __func__,
+ scontrol->priv_size, scontrol->max_size - sizeof(*cdata));
+ return -EINVAL;
+ }
+
+ scontrol->ipc_control_data = kzalloc(scontrol->max_size, GFP_KERNEL);
+ if (!scontrol->ipc_control_data)
+ return -ENOMEM;
+
+ scontrol->size = sizeof(struct sof_ipc_ctrl_data) + scontrol->priv_size;
+
+ cdata = scontrol->ipc_control_data;
+ cdata->cmd = SOF_CTRL_CMD_BINARY;
+ cdata->index = scontrol->index;
+
+ if (scontrol->priv_size > 0) {
+ memcpy(cdata->data, scontrol->priv, scontrol->priv_size);
+ kfree(scontrol->priv);
+ scontrol->priv = NULL;
+
+ if (cdata->data->magic != SOF_ABI_MAGIC) {
+ dev_err(sdev->dev, "Wrong ABI magic 0x%08x.\n", cdata->data->magic);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, cdata->data->abi)) {
+ dev_err(sdev->dev, "Incompatible ABI version 0x%08x.\n",
+ cdata->data->abi);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ priv_size_check = cdata->data->size + sizeof(struct sof_abi_hdr);
+ if (priv_size_check != scontrol->priv_size) {
+ dev_err(sdev->dev, "Conflict in bytes (%zu) vs. priv size (%zu).\n",
+ priv_size_check, scontrol->priv_size);
+ ret = -EINVAL;
+ goto err;
+ }
+ }
+
+ return 0;
+err:
+ kfree(scontrol->ipc_control_data);
+ scontrol->ipc_control_data = NULL;
+ return ret;
+}
+
+static int sof_ipc3_control_load_volume(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol)
+{
+ struct sof_ipc_ctrl_data *cdata;
+ int i;
+
+ /* init the volume get/put data */
+ scontrol->size = struct_size(cdata, chanv, scontrol->num_channels);
+
+ scontrol->ipc_control_data = kzalloc(scontrol->size, GFP_KERNEL);
+ if (!scontrol->ipc_control_data)
+ return -ENOMEM;
+
+ cdata = scontrol->ipc_control_data;
+ cdata->index = scontrol->index;
+
+ /* set cmd for mixer control */
+ if (scontrol->max == 1) {
+ cdata->cmd = SOF_CTRL_CMD_SWITCH;
+ return 0;
+ }
+
+ cdata->cmd = SOF_CTRL_CMD_VOLUME;
+
+ /* set default volume values to 0dB in control */
+ for (i = 0; i < scontrol->num_channels; i++) {
+ cdata->chanv[i].channel = i;
+ cdata->chanv[i].value = VOL_ZERO_DB;
+ }
+
+ return 0;
+}
+
+static int sof_ipc3_control_load_enum(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol)
+{
+ struct sof_ipc_ctrl_data *cdata;
+
+ /* init the enum get/put data */
+ scontrol->size = struct_size(cdata, chanv, scontrol->num_channels);
+
+ scontrol->ipc_control_data = kzalloc(scontrol->size, GFP_KERNEL);
+ if (!scontrol->ipc_control_data)
+ return -ENOMEM;
+
+ cdata = scontrol->ipc_control_data;
+ cdata->index = scontrol->index;
+ cdata->cmd = SOF_CTRL_CMD_ENUM;
+
+ return 0;
+}
+
+static int sof_ipc3_control_setup(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol)
+{
+ switch (scontrol->info_type) {
+ case SND_SOC_TPLG_CTL_VOLSW:
+ case SND_SOC_TPLG_CTL_VOLSW_SX:
+ case SND_SOC_TPLG_CTL_VOLSW_XR_SX:
+ return sof_ipc3_control_load_volume(sdev, scontrol);
+ case SND_SOC_TPLG_CTL_BYTES:
+ return sof_ipc3_control_load_bytes(sdev, scontrol);
+ case SND_SOC_TPLG_CTL_ENUM:
+ case SND_SOC_TPLG_CTL_ENUM_VALUE:
+ return sof_ipc3_control_load_enum(sdev, scontrol);
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int sof_ipc3_control_free(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol)
+{
+ struct sof_ipc_free fcomp;
+
+ fcomp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_FREE;
+ fcomp.hdr.size = sizeof(fcomp);
+ fcomp.id = scontrol->comp_id;
+
+ /* send IPC to the DSP */
+ return sof_ipc_tx_message_no_reply(sdev->ipc, &fcomp, sizeof(fcomp));
+}
+
+/* send pcm params ipc */
+static int sof_ipc3_keyword_detect_pcm_params(struct snd_sof_widget *swidget, int dir)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct snd_pcm_hw_params *params;
+ struct sof_ipc_pcm_params pcm;
+ struct snd_sof_pcm *spcm;
+ int ret;
+
+ /* get runtime PCM params using widget's stream name */
+ spcm = snd_sof_find_spcm_name(scomp, swidget->widget->sname);
+ if (!spcm) {
+ dev_err(scomp->dev, "Cannot find PCM for %s\n", swidget->widget->name);
+ return -EINVAL;
+ }
+
+ params = &spcm->params[dir];
+
+ /* set IPC PCM params */
+ memset(&pcm, 0, sizeof(pcm));
+ pcm.hdr.size = sizeof(pcm);
+ pcm.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS;
+ pcm.comp_id = swidget->comp_id;
+ pcm.params.hdr.size = sizeof(pcm.params);
+ pcm.params.direction = dir;
+ pcm.params.sample_valid_bytes = params_width(params) >> 3;
+ pcm.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED;
+ pcm.params.rate = params_rate(params);
+ pcm.params.channels = params_channels(params);
+ pcm.params.host_period_bytes = params_period_bytes(params);
+
+ /* set format */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16:
+ pcm.params.frame_fmt = SOF_IPC_FRAME_S16_LE;
+ break;
+ case SNDRV_PCM_FORMAT_S24:
+ pcm.params.frame_fmt = SOF_IPC_FRAME_S24_4LE;
+ break;
+ case SNDRV_PCM_FORMAT_S32:
+ pcm.params.frame_fmt = SOF_IPC_FRAME_S32_LE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* send IPC to the DSP */
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, &pcm, sizeof(pcm));
+ if (ret < 0)
+ dev_err(scomp->dev, "%s: PCM params failed for %s\n", __func__,
+ swidget->widget->name);
+
+ return ret;
+}
+
+ /* send stream trigger ipc */
+static int sof_ipc3_keyword_detect_trigger(struct snd_sof_widget *swidget, int cmd)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct sof_ipc_stream stream;
+ int ret;
+
+ /* set IPC stream params */
+ stream.hdr.size = sizeof(stream);
+ stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | cmd;
+ stream.comp_id = swidget->comp_id;
+
+ /* send IPC to the DSP */
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, &stream, sizeof(stream));
+ if (ret < 0)
+ dev_err(scomp->dev, "%s: Failed to trigger %s\n", __func__, swidget->widget->name);
+
+ return ret;
+}
+
+static int sof_ipc3_keyword_dapm_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_sof_widget *swidget = w->dobj.private;
+ struct snd_soc_component *scomp;
+ int stream = SNDRV_PCM_STREAM_CAPTURE;
+ struct snd_sof_pcm *spcm;
+ int ret = 0;
+
+ if (!swidget)
+ return 0;
+
+ scomp = swidget->scomp;
+
+ dev_dbg(scomp->dev, "received event %d for widget %s\n",
+ event, w->name);
+
+ /* get runtime PCM params using widget's stream name */
+ spcm = snd_sof_find_spcm_name(scomp, swidget->widget->sname);
+ if (!spcm) {
+ dev_err(scomp->dev, "%s: Cannot find PCM for %s\n", __func__,
+ swidget->widget->name);
+ return -EINVAL;
+ }
+
+ /* process events */
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ if (spcm->stream[stream].suspend_ignored) {
+ dev_dbg(scomp->dev, "PRE_PMU event ignored, KWD pipeline is already RUNNING\n");
+ return 0;
+ }
+
+ /* set pcm params */
+ ret = sof_ipc3_keyword_detect_pcm_params(swidget, stream);
+ if (ret < 0) {
+ dev_err(scomp->dev, "%s: Failed to set pcm params for widget %s\n",
+ __func__, swidget->widget->name);
+ break;
+ }
+
+ /* start trigger */
+ ret = sof_ipc3_keyword_detect_trigger(swidget, SOF_IPC_STREAM_TRIG_START);
+ if (ret < 0)
+ dev_err(scomp->dev, "%s: Failed to trigger widget %s\n", __func__,
+ swidget->widget->name);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ if (spcm->stream[stream].suspend_ignored) {
+ dev_dbg(scomp->dev,
+ "POST_PMD event ignored, KWD pipeline will remain RUNNING\n");
+ return 0;
+ }
+
+ /* stop trigger */
+ ret = sof_ipc3_keyword_detect_trigger(swidget, SOF_IPC_STREAM_TRIG_STOP);
+ if (ret < 0)
+ dev_err(scomp->dev, "%s: Failed to trigger widget %s\n", __func__,
+ swidget->widget->name);
+
+ /* pcm free */
+ ret = sof_ipc3_keyword_detect_trigger(swidget, SOF_IPC_STREAM_PCM_FREE);
+ if (ret < 0)
+ dev_err(scomp->dev, "%s: Failed to free PCM for widget %s\n", __func__,
+ swidget->widget->name);
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+/* event handlers for keyword detect component */
+static const struct snd_soc_tplg_widget_events sof_kwd_events[] = {
+ {SOF_KEYWORD_DETECT_DAPM_EVENT, sof_ipc3_keyword_dapm_event},
+};
+
+static int sof_ipc3_widget_bind_event(struct snd_soc_component *scomp,
+ struct snd_sof_widget *swidget, u16 event_type)
+{
+ struct sof_ipc_comp *ipc_comp;
+
+ /* validate widget event type */
+ switch (event_type) {
+ case SOF_KEYWORD_DETECT_DAPM_EVENT:
+ /* only KEYWORD_DETECT comps should handle this */
+ if (swidget->id != snd_soc_dapm_effect)
+ break;
+
+ ipc_comp = swidget->private;
+ if (ipc_comp && ipc_comp->type != SOF_COMP_KEYWORD_DETECT)
+ break;
+
+ /* bind event to keyword detect comp */
+ return snd_soc_tplg_widget_bind_event(swidget->widget, sof_kwd_events,
+ ARRAY_SIZE(sof_kwd_events), event_type);
+ default:
+ break;
+ }
+
+ dev_err(scomp->dev, "Invalid event type %d for widget %s\n", event_type,
+ swidget->widget->name);
+
+ return -EINVAL;
+}
+
+static int sof_ipc3_complete_pipeline(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
+{
+ struct sof_ipc_pipe_ready ready;
+ int ret;
+
+ dev_dbg(sdev->dev, "tplg: complete pipeline %s id %d\n",
+ swidget->widget->name, swidget->comp_id);
+
+ memset(&ready, 0, sizeof(ready));
+ ready.hdr.size = sizeof(ready);
+ ready.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_PIPE_COMPLETE;
+ ready.comp_id = swidget->comp_id;
+
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, &ready, sizeof(ready));
+ if (ret < 0)
+ return ret;
+
+ return 1;
+}
+
+static int sof_ipc3_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
+{
+ struct sof_ipc_free ipc_free = {
+ .hdr = {
+ .size = sizeof(ipc_free),
+ .cmd = SOF_IPC_GLB_TPLG_MSG,
+ },
+ .id = swidget->comp_id,
+ };
+ int ret;
+
+ if (!swidget->private)
+ return 0;
+
+ switch (swidget->id) {
+ case snd_soc_dapm_scheduler:
+ {
+ ipc_free.hdr.cmd |= SOF_IPC_TPLG_PIPE_FREE;
+ break;
+ }
+ case snd_soc_dapm_buffer:
+ ipc_free.hdr.cmd |= SOF_IPC_TPLG_BUFFER_FREE;
+ break;
+ default:
+ ipc_free.hdr.cmd |= SOF_IPC_TPLG_COMP_FREE;
+ break;
+ }
+
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, &ipc_free, sizeof(ipc_free));
+ if (ret < 0)
+ dev_err(sdev->dev, "failed to free widget %s\n", swidget->widget->name);
+
+ return ret;
+}
+
+static int sof_ipc3_dai_config(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget,
+ unsigned int flags, struct snd_sof_dai_config_data *data)
+{
+ struct sof_ipc_fw_version *v = &sdev->fw_ready.version;
+ struct snd_sof_dai *dai = swidget->private;
+ struct sof_dai_private_data *private;
+ struct sof_ipc_dai_config *config;
+ int ret = 0;
+
+ if (!dai || !dai->private) {
+ dev_err(sdev->dev, "No private data for DAI %s\n", swidget->widget->name);
+ return -EINVAL;
+ }
+
+ private = dai->private;
+ if (!private->dai_config) {
+ dev_err(sdev->dev, "No config for DAI %s\n", dai->name);
+ return -EINVAL;
+ }
+
+ config = &private->dai_config[dai->current_config];
+ if (!config) {
+ dev_err(sdev->dev, "Invalid current config for DAI %s\n", dai->name);
+ return -EINVAL;
+ }
+
+ switch (config->type) {
+ case SOF_DAI_INTEL_SSP:
+ /*
+ * DAI_CONFIG IPC during hw_params/hw_free for SSP DAI's is not supported in older
+ * firmware
+ */
+ if (v->abi_version < SOF_ABI_VER(3, 18, 0) &&
+ ((flags & SOF_DAI_CONFIG_FLAGS_HW_PARAMS) ||
+ (flags & SOF_DAI_CONFIG_FLAGS_HW_FREE)))
+ return 0;
+ break;
+ case SOF_DAI_INTEL_HDA:
+ if (data)
+ config->hda.link_dma_ch = data->dai_data;
+ break;
+ case SOF_DAI_INTEL_ALH:
+ if (data) {
+ /* save the dai_index during hw_params and reuse it for hw_free */
+ if (flags & SOF_DAI_CONFIG_FLAGS_HW_PARAMS)
+ config->dai_index = data->dai_index;
+ config->alh.stream_id = data->dai_data;
+ }
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * The dai_config op is invoked several times and the flags argument varies as below:
+ * BE DAI hw_params: When the op is invoked during the BE DAI hw_params, flags contains
+ * SOF_DAI_CONFIG_FLAGS_HW_PARAMS along with quirks
+ * FE DAI hw_params: When invoked during FE DAI hw_params after the DAI widget has
+ * just been set up in the DSP, flags is set to SOF_DAI_CONFIG_FLAGS_HW_PARAMS with no
+ * quirks
+ * BE DAI trigger: When invoked during the BE DAI trigger, flags is set to
+ * SOF_DAI_CONFIG_FLAGS_PAUSE and contains no quirks
+ * BE DAI hw_free: When invoked during the BE DAI hw_free, flags is set to
+ * SOF_DAI_CONFIG_FLAGS_HW_FREE and contains no quirks
+ * FE DAI hw_free: When invoked during the FE DAI hw_free, flags is set to
+ * SOF_DAI_CONFIG_FLAGS_HW_FREE and contains no quirks
+ *
+ * The DAI_CONFIG IPC is sent to the DSP, only after the widget is set up during the FE
+ * DAI hw_params. But since the BE DAI hw_params precedes the FE DAI hw_params, the quirks
+ * need to be preserved when assigning the flags before sending the IPC.
+ * For the case of PAUSE/HW_FREE, since there are no quirks, flags can be used as is.
+ */
+
+ if (flags & SOF_DAI_CONFIG_FLAGS_HW_PARAMS) {
+ /* Clear stale command */
+ config->flags &= ~SOF_DAI_CONFIG_FLAGS_CMD_MASK;
+ config->flags |= flags;
+ } else {
+ config->flags = flags;
+ }
+
+ /* only send the IPC if the widget is set up in the DSP */
+ if (swidget->use_count > 0) {
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, config, config->hdr.size);
+ if (ret < 0)
+ dev_err(sdev->dev, "Failed to set dai config for %s\n", dai->name);
+
+ /* clear the flags once the IPC has been sent even if it fails */
+ config->flags = SOF_DAI_CONFIG_FLAGS_NONE;
+ }
+
+ return ret;
+}
+
+static int sof_ipc3_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
+{
+ int ret;
+
+ if (!swidget->private)
+ return 0;
+
+ switch (swidget->id) {
+ case snd_soc_dapm_dai_in:
+ case snd_soc_dapm_dai_out:
+ {
+ struct snd_sof_dai *dai = swidget->private;
+ struct sof_dai_private_data *dai_data = dai->private;
+ struct sof_ipc_comp *comp = &dai_data->comp_dai->comp;
+
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, dai_data->comp_dai, comp->hdr.size);
+ break;
+ }
+ case snd_soc_dapm_scheduler:
+ {
+ struct sof_ipc_pipe_new *pipeline;
+
+ pipeline = swidget->private;
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, pipeline, sizeof(*pipeline));
+ break;
+ }
+ default:
+ {
+ struct sof_ipc_cmd_hdr *hdr;
+
+ hdr = swidget->private;
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, swidget->private, hdr->size);
+ break;
+ }
+ }
+ if (ret < 0)
+ dev_err(sdev->dev, "Failed to setup widget %s\n", swidget->widget->name);
+
+ return ret;
+}
+
+static int sof_ipc3_set_up_all_pipelines(struct snd_sof_dev *sdev, bool verify)
+{
+ struct sof_ipc_fw_version *v = &sdev->fw_ready.version;
+ struct snd_sof_widget *swidget;
+ struct snd_sof_route *sroute;
+ int ret;
+
+ /* restore pipeline components */
+ list_for_each_entry(swidget, &sdev->widget_list, list) {
+ /* only set up the widgets belonging to static pipelines */
+ if (!verify && swidget->dynamic_pipeline_widget)
+ continue;
+
+ /*
+ * For older firmware, skip scheduler widgets in this loop,
+ * sof_widget_setup() will be called in the 'complete pipeline' loop
+ */
+ if (v->abi_version < SOF_ABI_VER(3, 19, 0) &&
+ swidget->id == snd_soc_dapm_scheduler)
+ continue;
+
+ /* update DAI config. The IPC will be sent in sof_widget_setup() */
+ if (WIDGET_IS_DAI(swidget->id)) {
+ struct snd_sof_dai *dai = swidget->private;
+ struct sof_dai_private_data *private;
+ struct sof_ipc_dai_config *config;
+
+ if (!dai || !dai->private)
+ continue;
+ private = dai->private;
+ if (!private->dai_config)
+ continue;
+
+ config = private->dai_config;
+ /*
+ * The link DMA channel would be invalidated for running
+ * streams but not for streams that were in the PAUSED
+ * state during suspend. So invalidate it here before setting
+ * the dai config in the DSP.
+ */
+ if (config->type == SOF_DAI_INTEL_HDA)
+ config->hda.link_dma_ch = DMA_CHAN_INVALID;
+ }
+
+ ret = sof_widget_setup(sdev, swidget);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* restore pipeline connections */
+ list_for_each_entry(sroute, &sdev->route_list, list) {
+ /* only set up routes belonging to static pipelines */
+ if (!verify && (sroute->src_widget->dynamic_pipeline_widget ||
+ sroute->sink_widget->dynamic_pipeline_widget))
+ continue;
+
+ /*
+ * For virtual routes, both sink and source are not buffer. IPC3 only supports
+ * connections between a buffer and a component. Ignore the rest.
+ */
+ if (sroute->src_widget->id != snd_soc_dapm_buffer &&
+ sroute->sink_widget->id != snd_soc_dapm_buffer)
+ continue;
+
+ ret = sof_route_setup(sdev, sroute->src_widget->widget,
+ sroute->sink_widget->widget);
+ if (ret < 0) {
+ dev_err(sdev->dev, "%s: route set up failed\n", __func__);
+ return ret;
+ }
+ }
+
+ /* complete pipeline */
+ list_for_each_entry(swidget, &sdev->widget_list, list) {
+ switch (swidget->id) {
+ case snd_soc_dapm_scheduler:
+ /* only complete static pipelines */
+ if (!verify && swidget->dynamic_pipeline_widget)
+ continue;
+
+ if (v->abi_version < SOF_ABI_VER(3, 19, 0)) {
+ ret = sof_widget_setup(sdev, swidget);
+ if (ret < 0)
+ return ret;
+ }
+
+ swidget->spipe->complete = sof_ipc3_complete_pipeline(sdev, swidget);
+ if (swidget->spipe->complete < 0)
+ return swidget->spipe->complete;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Free the PCM, its associated widgets and set the prepared flag to false for all PCMs that
+ * did not get suspended(ex: paused streams) so the widgets can be set up again during resume.
+ */
+static int sof_tear_down_left_over_pipelines(struct snd_sof_dev *sdev)
+{
+ struct snd_sof_widget *swidget;
+ struct snd_sof_pcm *spcm;
+ int dir, ret;
+
+ /*
+ * free all PCMs and their associated DAPM widgets if their connected DAPM widget
+ * list is not NULL. This should only be true for paused streams at this point.
+ * This is equivalent to the handling of FE DAI suspend trigger for running streams.
+ */
+ list_for_each_entry(spcm, &sdev->pcm_list, list) {
+ for_each_pcm_streams(dir) {
+ struct snd_pcm_substream *substream = spcm->stream[dir].substream;
+
+ if (!substream || !substream->runtime || spcm->stream[dir].suspend_ignored)
+ continue;
+
+ if (spcm->stream[dir].list) {
+ ret = sof_pcm_stream_free(sdev, substream, spcm, dir, true);
+ if (ret < 0)
+ return ret;
+ }
+ }
+ }
+
+ /*
+ * free any left over DAI widgets. This is equivalent to the handling of suspend trigger
+ * for the BE DAI for running streams.
+ */
+ list_for_each_entry(swidget, &sdev->widget_list, list)
+ if (WIDGET_IS_DAI(swidget->id) && swidget->use_count == 1) {
+ ret = sof_widget_free(sdev, swidget);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sof_ipc3_free_widgets_in_list(struct snd_sof_dev *sdev, bool include_scheduler,
+ bool *dyn_widgets, bool verify)
+{
+ struct sof_ipc_fw_version *v = &sdev->fw_ready.version;
+ struct snd_sof_widget *swidget;
+ int ret;
+
+ list_for_each_entry(swidget, &sdev->widget_list, list) {
+ if (swidget->dynamic_pipeline_widget) {
+ *dyn_widgets = true;
+ continue;
+ }
+
+ /* Do not free widgets for static pipelines with FW older than SOF2.2 */
+ if (!verify && !swidget->dynamic_pipeline_widget &&
+ SOF_FW_VER(v->major, v->minor, v->micro) < SOF_FW_VER(2, 2, 0)) {
+ mutex_lock(&swidget->setup_mutex);
+ swidget->use_count = 0;
+ mutex_unlock(&swidget->setup_mutex);
+ if (swidget->spipe)
+ swidget->spipe->complete = 0;
+ continue;
+ }
+
+ if (include_scheduler && swidget->id != snd_soc_dapm_scheduler)
+ continue;
+
+ if (!include_scheduler && swidget->id == snd_soc_dapm_scheduler)
+ continue;
+
+ ret = sof_widget_free(sdev, swidget);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+/*
+ * For older firmware, this function doesn't free widgets for static pipelines during suspend.
+ * It only resets use_count for all widgets.
+ */
+static int sof_ipc3_tear_down_all_pipelines(struct snd_sof_dev *sdev, bool verify)
+{
+ struct sof_ipc_fw_version *v = &sdev->fw_ready.version;
+ struct snd_sof_widget *swidget;
+ struct snd_sof_route *sroute;
+ bool dyn_widgets = false;
+ int ret;
+
+ /*
+ * This function is called during suspend and for one-time topology verification during
+ * first boot. In both cases, there is no need to protect swidget->use_count and
+ * sroute->setup because during suspend all running streams are suspended and during
+ * topology loading the sound card unavailable to open PCMs. Do not free the scheduler
+ * widgets yet so that the secondary cores do not get powered down before all the widgets
+ * associated with the scheduler are freed.
+ */
+ ret = sof_ipc3_free_widgets_in_list(sdev, false, &dyn_widgets, verify);
+ if (ret < 0)
+ return ret;
+
+ /* free all the scheduler widgets now */
+ ret = sof_ipc3_free_widgets_in_list(sdev, true, &dyn_widgets, verify);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Tear down all pipelines associated with PCMs that did not get suspended
+ * and unset the prepare flag so that they can be set up again during resume.
+ * Skip this step for older firmware unless topology has any
+ * dynamic pipeline (in which case the step is mandatory).
+ */
+ if (!verify && (dyn_widgets || SOF_FW_VER(v->major, v->minor, v->micro) >=
+ SOF_FW_VER(2, 2, 0))) {
+ ret = sof_tear_down_left_over_pipelines(sdev);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to tear down paused pipelines\n");
+ return ret;
+ }
+ }
+
+ list_for_each_entry(sroute, &sdev->route_list, list)
+ sroute->setup = false;
+
+ /*
+ * before suspending, make sure the refcounts are all zeroed out. There's no way
+ * to recover at this point but this will help root cause bad sequences leading to
+ * more issues on resume
+ */
+ list_for_each_entry(swidget, &sdev->widget_list, list) {
+ if (swidget->use_count != 0) {
+ dev_err(sdev->dev, "%s: widget %s is still in use: count %d\n",
+ __func__, swidget->widget->name, swidget->use_count);
+ }
+ }
+
+ return 0;
+}
+
+static int sof_ipc3_dai_get_clk(struct snd_sof_dev *sdev, struct snd_sof_dai *dai, int clk_type)
+{
+ struct sof_dai_private_data *private = dai->private;
+
+ if (!private || !private->dai_config)
+ return 0;
+
+ switch (private->dai_config->type) {
+ case SOF_DAI_INTEL_SSP:
+ switch (clk_type) {
+ case SOF_DAI_CLK_INTEL_SSP_MCLK:
+ return private->dai_config->ssp.mclk_rate;
+ case SOF_DAI_CLK_INTEL_SSP_BCLK:
+ return private->dai_config->ssp.bclk_rate;
+ default:
+ break;
+ }
+ dev_err(sdev->dev, "fail to get SSP clk %d rate\n", clk_type);
+ break;
+ default:
+ /* not yet implemented for platforms other than the above */
+ dev_err(sdev->dev, "DAI type %d not supported yet!\n", private->dai_config->type);
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static int sof_ipc3_parse_manifest(struct snd_soc_component *scomp, int index,
+ struct snd_soc_tplg_manifest *man)
+{
+ u32 size = le32_to_cpu(man->priv.size);
+ u32 abi_version;
+
+ /* backward compatible with tplg without ABI info */
+ if (!size) {
+ dev_dbg(scomp->dev, "No topology ABI info\n");
+ return 0;
+ }
+
+ if (size != SOF_IPC3_TPLG_ABI_SIZE) {
+ dev_err(scomp->dev, "%s: Invalid topology ABI size: %u\n",
+ __func__, size);
+ return -EINVAL;
+ }
+
+ dev_info(scomp->dev,
+ "Topology: ABI %d:%d:%d Kernel ABI %d:%d:%d\n",
+ man->priv.data[0], man->priv.data[1], man->priv.data[2],
+ SOF_ABI_MAJOR, SOF_ABI_MINOR, SOF_ABI_PATCH);
+
+ abi_version = SOF_ABI_VER(man->priv.data[0], man->priv.data[1], man->priv.data[2]);
+
+ if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, abi_version)) {
+ dev_err(scomp->dev, "%s: Incompatible topology ABI version\n", __func__);
+ return -EINVAL;
+ }
+
+ if (IS_ENABLED(CONFIG_SND_SOC_SOF_STRICT_ABI_CHECKS) &&
+ SOF_ABI_VERSION_MINOR(abi_version) > SOF_ABI_MINOR) {
+ dev_err(scomp->dev, "%s: Topology ABI is more recent than kernel\n", __func__);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sof_ipc3_link_setup(struct snd_sof_dev *sdev, struct snd_soc_dai_link *link)
+{
+ if (link->no_pcm)
+ return 0;
+
+ /*
+ * set default trigger order for all links. Exceptions to
+ * the rule will be handled in sof_pcm_dai_link_fixup()
+ * For playback, the sequence is the following: start FE,
+ * start BE, stop BE, stop FE; for Capture the sequence is
+ * inverted start BE, start FE, stop FE, stop BE
+ */
+ link->trigger[SNDRV_PCM_STREAM_PLAYBACK] = SND_SOC_DPCM_TRIGGER_PRE;
+ link->trigger[SNDRV_PCM_STREAM_CAPTURE] = SND_SOC_DPCM_TRIGGER_POST;
+
+ return 0;
+}
+
+/* token list for each topology object */
+static enum sof_tokens host_token_list[] = {
+ SOF_CORE_TOKENS,
+ SOF_COMP_EXT_TOKENS,
+ SOF_PCM_TOKENS,
+ SOF_COMP_TOKENS,
+};
+
+static enum sof_tokens comp_generic_token_list[] = {
+ SOF_CORE_TOKENS,
+ SOF_COMP_EXT_TOKENS,
+ SOF_COMP_TOKENS,
+};
+
+static enum sof_tokens buffer_token_list[] = {
+ SOF_BUFFER_TOKENS,
+};
+
+static enum sof_tokens pipeline_token_list[] = {
+ SOF_CORE_TOKENS,
+ SOF_COMP_EXT_TOKENS,
+ SOF_PIPELINE_TOKENS,
+ SOF_SCHED_TOKENS,
+};
+
+static enum sof_tokens asrc_token_list[] = {
+ SOF_CORE_TOKENS,
+ SOF_COMP_EXT_TOKENS,
+ SOF_ASRC_TOKENS,
+ SOF_COMP_TOKENS,
+};
+
+static enum sof_tokens src_token_list[] = {
+ SOF_CORE_TOKENS,
+ SOF_COMP_EXT_TOKENS,
+ SOF_SRC_TOKENS,
+ SOF_COMP_TOKENS
+};
+
+static enum sof_tokens pga_token_list[] = {
+ SOF_CORE_TOKENS,
+ SOF_COMP_EXT_TOKENS,
+ SOF_VOLUME_TOKENS,
+ SOF_COMP_TOKENS,
+};
+
+static enum sof_tokens dai_token_list[] = {
+ SOF_CORE_TOKENS,
+ SOF_COMP_EXT_TOKENS,
+ SOF_DAI_TOKENS,
+ SOF_COMP_TOKENS,
+};
+
+static enum sof_tokens process_token_list[] = {
+ SOF_CORE_TOKENS,
+ SOF_COMP_EXT_TOKENS,
+ SOF_PROCESS_TOKENS,
+ SOF_COMP_TOKENS,
+};
+
+static const struct sof_ipc_tplg_widget_ops tplg_ipc3_widget_ops[SND_SOC_DAPM_TYPE_COUNT] = {
+ [snd_soc_dapm_aif_in] = {sof_ipc3_widget_setup_comp_host, sof_ipc3_widget_free_comp,
+ host_token_list, ARRAY_SIZE(host_token_list), NULL},
+ [snd_soc_dapm_aif_out] = {sof_ipc3_widget_setup_comp_host, sof_ipc3_widget_free_comp,
+ host_token_list, ARRAY_SIZE(host_token_list), NULL},
+
+ [snd_soc_dapm_dai_in] = {sof_ipc3_widget_setup_comp_dai, sof_ipc3_widget_free_comp_dai,
+ dai_token_list, ARRAY_SIZE(dai_token_list), NULL},
+ [snd_soc_dapm_dai_out] = {sof_ipc3_widget_setup_comp_dai, sof_ipc3_widget_free_comp_dai,
+ dai_token_list, ARRAY_SIZE(dai_token_list), NULL},
+ [snd_soc_dapm_buffer] = {sof_ipc3_widget_setup_comp_buffer, sof_ipc3_widget_free_comp,
+ buffer_token_list, ARRAY_SIZE(buffer_token_list), NULL},
+ [snd_soc_dapm_mixer] = {sof_ipc3_widget_setup_comp_mixer, sof_ipc3_widget_free_comp,
+ comp_generic_token_list, ARRAY_SIZE(comp_generic_token_list),
+ NULL},
+ [snd_soc_dapm_src] = {sof_ipc3_widget_setup_comp_src, sof_ipc3_widget_free_comp,
+ src_token_list, ARRAY_SIZE(src_token_list), NULL},
+ [snd_soc_dapm_asrc] = {sof_ipc3_widget_setup_comp_asrc, sof_ipc3_widget_free_comp,
+ asrc_token_list, ARRAY_SIZE(asrc_token_list), NULL},
+ [snd_soc_dapm_siggen] = {sof_ipc3_widget_setup_comp_tone, sof_ipc3_widget_free_comp,
+ comp_generic_token_list, ARRAY_SIZE(comp_generic_token_list),
+ NULL},
+ [snd_soc_dapm_scheduler] = {sof_ipc3_widget_setup_comp_pipeline, sof_ipc3_widget_free_comp,
+ pipeline_token_list, ARRAY_SIZE(pipeline_token_list), NULL},
+ [snd_soc_dapm_pga] = {sof_ipc3_widget_setup_comp_pga, sof_ipc3_widget_free_comp,
+ pga_token_list, ARRAY_SIZE(pga_token_list), NULL},
+ [snd_soc_dapm_mux] = {sof_ipc3_widget_setup_comp_mux, sof_ipc3_widget_free_comp,
+ comp_generic_token_list, ARRAY_SIZE(comp_generic_token_list), NULL},
+ [snd_soc_dapm_demux] = {sof_ipc3_widget_setup_comp_mux, sof_ipc3_widget_free_comp,
+ comp_generic_token_list, ARRAY_SIZE(comp_generic_token_list),
+ NULL},
+ [snd_soc_dapm_effect] = {sof_widget_update_ipc_comp_process, sof_ipc3_widget_free_comp,
+ process_token_list, ARRAY_SIZE(process_token_list),
+ sof_ipc3_widget_bind_event},
+};
+
+const struct sof_ipc_tplg_ops ipc3_tplg_ops = {
+ .widget = tplg_ipc3_widget_ops,
+ .control = &tplg_ipc3_control_ops,
+ .route_setup = sof_ipc3_route_setup,
+ .control_setup = sof_ipc3_control_setup,
+ .control_free = sof_ipc3_control_free,
+ .pipeline_complete = sof_ipc3_complete_pipeline,
+ .token_list = ipc3_token_list,
+ .widget_free = sof_ipc3_widget_free,
+ .widget_setup = sof_ipc3_widget_setup,
+ .dai_config = sof_ipc3_dai_config,
+ .dai_get_clk = sof_ipc3_dai_get_clk,
+ .set_up_all_pipelines = sof_ipc3_set_up_all_pipelines,
+ .tear_down_all_pipelines = sof_ipc3_tear_down_all_pipelines,
+ .parse_manifest = sof_ipc3_parse_manifest,
+ .link_setup = sof_ipc3_link_setup,
+};
diff --git a/sound/soc/sof/ipc3.c b/sound/soc/sof/ipc3.c
new file mode 100644
index 000000000000..c03dd513fbff
--- /dev/null
+++ b/sound/soc/sof/ipc3.c
@@ -0,0 +1,1161 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2021 Intel Corporation. All rights reserved.
+//
+//
+
+#include <sound/sof/stream.h>
+#include <sound/sof/control.h>
+#include <trace/events/sof.h>
+#include "sof-priv.h"
+#include "sof-audio.h"
+#include "ipc3-priv.h"
+#include "ops.h"
+
+typedef void (*ipc3_rx_callback)(struct snd_sof_dev *sdev, void *msg_buf);
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_VERBOSE_IPC)
+static void ipc3_log_header(struct device *dev, u8 *text, u32 cmd)
+{
+ u8 *str;
+ u8 *str2 = NULL;
+ u32 glb;
+ u32 type;
+ bool is_sof_ipc_stream_position = false;
+
+ glb = cmd & SOF_GLB_TYPE_MASK;
+ type = cmd & SOF_CMD_TYPE_MASK;
+
+ switch (glb) {
+ case SOF_IPC_GLB_REPLY:
+ str = "GLB_REPLY"; break;
+ case SOF_IPC_GLB_COMPOUND:
+ str = "GLB_COMPOUND"; break;
+ case SOF_IPC_GLB_TPLG_MSG:
+ str = "GLB_TPLG_MSG";
+ switch (type) {
+ case SOF_IPC_TPLG_COMP_NEW:
+ str2 = "COMP_NEW"; break;
+ case SOF_IPC_TPLG_COMP_FREE:
+ str2 = "COMP_FREE"; break;
+ case SOF_IPC_TPLG_COMP_CONNECT:
+ str2 = "COMP_CONNECT"; break;
+ case SOF_IPC_TPLG_PIPE_NEW:
+ str2 = "PIPE_NEW"; break;
+ case SOF_IPC_TPLG_PIPE_FREE:
+ str2 = "PIPE_FREE"; break;
+ case SOF_IPC_TPLG_PIPE_CONNECT:
+ str2 = "PIPE_CONNECT"; break;
+ case SOF_IPC_TPLG_PIPE_COMPLETE:
+ str2 = "PIPE_COMPLETE"; break;
+ case SOF_IPC_TPLG_BUFFER_NEW:
+ str2 = "BUFFER_NEW"; break;
+ case SOF_IPC_TPLG_BUFFER_FREE:
+ str2 = "BUFFER_FREE"; break;
+ default:
+ str2 = "unknown type"; break;
+ }
+ break;
+ case SOF_IPC_GLB_PM_MSG:
+ str = "GLB_PM_MSG";
+ switch (type) {
+ case SOF_IPC_PM_CTX_SAVE:
+ str2 = "CTX_SAVE"; break;
+ case SOF_IPC_PM_CTX_RESTORE:
+ str2 = "CTX_RESTORE"; break;
+ case SOF_IPC_PM_CTX_SIZE:
+ str2 = "CTX_SIZE"; break;
+ case SOF_IPC_PM_CLK_SET:
+ str2 = "CLK_SET"; break;
+ case SOF_IPC_PM_CLK_GET:
+ str2 = "CLK_GET"; break;
+ case SOF_IPC_PM_CLK_REQ:
+ str2 = "CLK_REQ"; break;
+ case SOF_IPC_PM_CORE_ENABLE:
+ str2 = "CORE_ENABLE"; break;
+ case SOF_IPC_PM_GATE:
+ str2 = "GATE"; break;
+ default:
+ str2 = "unknown type"; break;
+ }
+ break;
+ case SOF_IPC_GLB_COMP_MSG:
+ str = "GLB_COMP_MSG";
+ switch (type) {
+ case SOF_IPC_COMP_SET_VALUE:
+ str2 = "SET_VALUE"; break;
+ case SOF_IPC_COMP_GET_VALUE:
+ str2 = "GET_VALUE"; break;
+ case SOF_IPC_COMP_SET_DATA:
+ str2 = "SET_DATA"; break;
+ case SOF_IPC_COMP_GET_DATA:
+ str2 = "GET_DATA"; break;
+ default:
+ str2 = "unknown type"; break;
+ }
+ break;
+ case SOF_IPC_GLB_STREAM_MSG:
+ str = "GLB_STREAM_MSG";
+ switch (type) {
+ case SOF_IPC_STREAM_PCM_PARAMS:
+ str2 = "PCM_PARAMS"; break;
+ case SOF_IPC_STREAM_PCM_PARAMS_REPLY:
+ str2 = "PCM_REPLY"; break;
+ case SOF_IPC_STREAM_PCM_FREE:
+ str2 = "PCM_FREE"; break;
+ case SOF_IPC_STREAM_TRIG_START:
+ str2 = "TRIG_START"; break;
+ case SOF_IPC_STREAM_TRIG_STOP:
+ str2 = "TRIG_STOP"; break;
+ case SOF_IPC_STREAM_TRIG_PAUSE:
+ str2 = "TRIG_PAUSE"; break;
+ case SOF_IPC_STREAM_TRIG_RELEASE:
+ str2 = "TRIG_RELEASE"; break;
+ case SOF_IPC_STREAM_TRIG_DRAIN:
+ str2 = "TRIG_DRAIN"; break;
+ case SOF_IPC_STREAM_TRIG_XRUN:
+ str2 = "TRIG_XRUN"; break;
+ case SOF_IPC_STREAM_POSITION:
+ is_sof_ipc_stream_position = true;
+ str2 = "POSITION"; break;
+ case SOF_IPC_STREAM_VORBIS_PARAMS:
+ str2 = "VORBIS_PARAMS"; break;
+ case SOF_IPC_STREAM_VORBIS_FREE:
+ str2 = "VORBIS_FREE"; break;
+ default:
+ str2 = "unknown type"; break;
+ }
+ break;
+ case SOF_IPC_FW_READY:
+ str = "FW_READY"; break;
+ case SOF_IPC_GLB_DAI_MSG:
+ str = "GLB_DAI_MSG";
+ switch (type) {
+ case SOF_IPC_DAI_CONFIG:
+ str2 = "CONFIG"; break;
+ case SOF_IPC_DAI_LOOPBACK:
+ str2 = "LOOPBACK"; break;
+ default:
+ str2 = "unknown type"; break;
+ }
+ break;
+ case SOF_IPC_GLB_TRACE_MSG:
+ str = "GLB_TRACE_MSG";
+ switch (type) {
+ case SOF_IPC_TRACE_DMA_PARAMS:
+ str2 = "DMA_PARAMS"; break;
+ case SOF_IPC_TRACE_DMA_POSITION:
+ if (!sof_debug_check_flag(SOF_DBG_PRINT_DMA_POSITION_UPDATE_LOGS))
+ return;
+ str2 = "DMA_POSITION"; break;
+ case SOF_IPC_TRACE_DMA_PARAMS_EXT:
+ str2 = "DMA_PARAMS_EXT"; break;
+ case SOF_IPC_TRACE_FILTER_UPDATE:
+ str2 = "FILTER_UPDATE"; break;
+ case SOF_IPC_TRACE_DMA_FREE:
+ str2 = "DMA_FREE"; break;
+ default:
+ str2 = "unknown type"; break;
+ }
+ break;
+ case SOF_IPC_GLB_TEST_MSG:
+ str = "GLB_TEST_MSG";
+ switch (type) {
+ case SOF_IPC_TEST_IPC_FLOOD:
+ str2 = "IPC_FLOOD"; break;
+ default:
+ str2 = "unknown type"; break;
+ }
+ break;
+ case SOF_IPC_GLB_DEBUG:
+ str = "GLB_DEBUG";
+ switch (type) {
+ case SOF_IPC_DEBUG_MEM_USAGE:
+ str2 = "MEM_USAGE"; break;
+ default:
+ str2 = "unknown type"; break;
+ }
+ break;
+ case SOF_IPC_GLB_PROBE:
+ str = "GLB_PROBE";
+ switch (type) {
+ case SOF_IPC_PROBE_INIT:
+ str2 = "INIT"; break;
+ case SOF_IPC_PROBE_DEINIT:
+ str2 = "DEINIT"; break;
+ case SOF_IPC_PROBE_DMA_ADD:
+ str2 = "DMA_ADD"; break;
+ case SOF_IPC_PROBE_DMA_INFO:
+ str2 = "DMA_INFO"; break;
+ case SOF_IPC_PROBE_DMA_REMOVE:
+ str2 = "DMA_REMOVE"; break;
+ case SOF_IPC_PROBE_POINT_ADD:
+ str2 = "POINT_ADD"; break;
+ case SOF_IPC_PROBE_POINT_INFO:
+ str2 = "POINT_INFO"; break;
+ case SOF_IPC_PROBE_POINT_REMOVE:
+ str2 = "POINT_REMOVE"; break;
+ default:
+ str2 = "unknown type"; break;
+ }
+ break;
+ default:
+ str = "unknown GLB command"; break;
+ }
+
+ if (str2) {
+ if (is_sof_ipc_stream_position)
+ trace_sof_stream_position_ipc_rx(dev);
+ else
+ dev_dbg(dev, "%s: 0x%x: %s: %s\n", text, cmd, str, str2);
+ } else {
+ dev_dbg(dev, "%s: 0x%x: %s\n", text, cmd, str);
+ }
+}
+#else
+static inline void ipc3_log_header(struct device *dev, u8 *text, u32 cmd)
+{
+ if ((cmd & SOF_GLB_TYPE_MASK) != SOF_IPC_GLB_TRACE_MSG)
+ dev_dbg(dev, "%s: 0x%x\n", text, cmd);
+}
+#endif
+
+static void sof_ipc3_dump_payload(struct snd_sof_dev *sdev,
+ void *ipc_data, size_t size)
+{
+ printk(KERN_DEBUG "Size of payload following the header: %zu\n", size);
+ print_hex_dump_debug("Message payload: ", DUMP_PREFIX_OFFSET,
+ 16, 4, ipc_data, size, false);
+}
+
+static int sof_ipc3_get_reply(struct snd_sof_dev *sdev)
+{
+ struct snd_sof_ipc_msg *msg = sdev->msg;
+ struct sof_ipc_reply *reply;
+ int ret = 0;
+
+ /* get the generic reply */
+ reply = msg->reply_data;
+ snd_sof_dsp_mailbox_read(sdev, sdev->host_box.offset, reply, sizeof(*reply));
+
+ if (reply->error < 0)
+ return reply->error;
+
+ if (!reply->hdr.size) {
+ /* Reply should always be >= sizeof(struct sof_ipc_reply) */
+ if (msg->reply_size)
+ dev_err(sdev->dev,
+ "empty reply received, expected %zu bytes\n",
+ msg->reply_size);
+ else
+ dev_err(sdev->dev, "empty reply received\n");
+
+ return -EINVAL;
+ }
+
+ if (msg->reply_size > 0) {
+ if (reply->hdr.size == msg->reply_size) {
+ ret = 0;
+ } else if (reply->hdr.size < msg->reply_size) {
+ dev_dbg(sdev->dev,
+ "reply size (%u) is less than expected (%zu)\n",
+ reply->hdr.size, msg->reply_size);
+
+ msg->reply_size = reply->hdr.size;
+ ret = 0;
+ } else {
+ dev_err(sdev->dev,
+ "reply size (%u) exceeds the buffer size (%zu)\n",
+ reply->hdr.size, msg->reply_size);
+ ret = -EINVAL;
+ }
+
+ /*
+ * get the full message if reply->hdr.size <= msg->reply_size
+ * and the reply->hdr.size > sizeof(struct sof_ipc_reply)
+ */
+ if (!ret && msg->reply_size > sizeof(*reply))
+ snd_sof_dsp_mailbox_read(sdev, sdev->host_box.offset,
+ msg->reply_data, msg->reply_size);
+ }
+
+ return ret;
+}
+
+/* wait for IPC message reply */
+static int ipc3_wait_tx_done(struct snd_sof_ipc *ipc, void *reply_data)
+{
+ struct snd_sof_ipc_msg *msg = &ipc->msg;
+ struct sof_ipc_cmd_hdr *hdr = msg->msg_data;
+ struct snd_sof_dev *sdev = ipc->sdev;
+ int ret;
+
+ /* wait for DSP IPC completion */
+ ret = wait_event_timeout(msg->waitq, msg->ipc_complete,
+ msecs_to_jiffies(sdev->ipc_timeout));
+
+ if (ret == 0) {
+ dev_err(sdev->dev,
+ "ipc tx timed out for %#x (msg/reply size: %d/%zu)\n",
+ hdr->cmd, hdr->size, msg->reply_size);
+ snd_sof_handle_fw_exception(ipc->sdev, "IPC timeout");
+ ret = -ETIMEDOUT;
+ } else {
+ ret = msg->reply_error;
+ if (ret < 0) {
+ dev_err(sdev->dev,
+ "ipc tx error for %#x (msg/reply size: %d/%zu): %d\n",
+ hdr->cmd, hdr->size, msg->reply_size, ret);
+ } else {
+ if (sof_debug_check_flag(SOF_DBG_PRINT_IPC_SUCCESS_LOGS))
+ ipc3_log_header(sdev->dev, "ipc tx succeeded", hdr->cmd);
+ if (reply_data && msg->reply_size)
+ /* copy the data returned from DSP */
+ memcpy(reply_data, msg->reply_data,
+ msg->reply_size);
+ }
+
+ /* re-enable dumps after successful IPC tx */
+ if (sdev->ipc_dump_printed) {
+ sdev->dbg_dump_printed = false;
+ sdev->ipc_dump_printed = false;
+ }
+ }
+
+ return ret;
+}
+
+/* send IPC message from host to DSP */
+static int ipc3_tx_msg_unlocked(struct snd_sof_ipc *ipc,
+ void *msg_data, size_t msg_bytes,
+ void *reply_data, size_t reply_bytes)
+{
+ struct sof_ipc_cmd_hdr *hdr = msg_data;
+ struct snd_sof_dev *sdev = ipc->sdev;
+ int ret;
+
+ ipc3_log_header(sdev->dev, "ipc tx", hdr->cmd);
+
+ ret = sof_ipc_send_msg(sdev, msg_data, msg_bytes, reply_bytes);
+
+ if (ret) {
+ dev_err_ratelimited(sdev->dev,
+ "%s: ipc message send for %#x failed: %d\n",
+ __func__, hdr->cmd, ret);
+ return ret;
+ }
+
+ /* now wait for completion */
+ return ipc3_wait_tx_done(ipc, reply_data);
+}
+
+static int sof_ipc3_tx_msg(struct snd_sof_dev *sdev, void *msg_data, size_t msg_bytes,
+ void *reply_data, size_t reply_bytes, bool no_pm)
+{
+ struct snd_sof_ipc *ipc = sdev->ipc;
+ int ret;
+
+ if (!msg_data || msg_bytes < sizeof(struct sof_ipc_cmd_hdr)) {
+ dev_err_ratelimited(sdev->dev, "No IPC message to send\n");
+ return -EINVAL;
+ }
+
+ if (!no_pm) {
+ const struct sof_dsp_power_state target_state = {
+ .state = SOF_DSP_PM_D0,
+ };
+
+ /* ensure the DSP is in D0 before sending a new IPC */
+ ret = snd_sof_dsp_set_power_state(sdev, &target_state);
+ if (ret < 0) {
+ dev_err(sdev->dev, "%s: resuming DSP failed: %d\n",
+ __func__, ret);
+ return ret;
+ }
+ }
+
+ /* Serialise IPC TX */
+ mutex_lock(&ipc->tx_mutex);
+
+ ret = ipc3_tx_msg_unlocked(ipc, msg_data, msg_bytes, reply_data, reply_bytes);
+
+ if (sof_debug_check_flag(SOF_DBG_DUMP_IPC_MESSAGE_PAYLOAD)) {
+ size_t payload_bytes, header_bytes;
+ char *payload = NULL;
+
+ /* payload is indicated by non zero msg/reply_bytes */
+ if (msg_bytes > sizeof(struct sof_ipc_cmd_hdr)) {
+ payload = msg_data;
+
+ header_bytes = sizeof(struct sof_ipc_cmd_hdr);
+ payload_bytes = msg_bytes - header_bytes;
+ } else if (reply_bytes > sizeof(struct sof_ipc_reply)) {
+ payload = reply_data;
+
+ header_bytes = sizeof(struct sof_ipc_reply);
+ payload_bytes = reply_bytes - header_bytes;
+ }
+
+ if (payload) {
+ payload += header_bytes;
+ sof_ipc3_dump_payload(sdev, payload, payload_bytes);
+ }
+ }
+
+ mutex_unlock(&ipc->tx_mutex);
+
+ return ret;
+}
+
+static int sof_ipc3_set_get_data(struct snd_sof_dev *sdev, void *data, size_t data_bytes,
+ bool set)
+{
+ size_t msg_bytes, hdr_bytes, payload_size, send_bytes;
+ struct sof_ipc_ctrl_data *cdata = data;
+ struct sof_ipc_ctrl_data *cdata_chunk;
+ struct snd_sof_ipc *ipc = sdev->ipc;
+ size_t offset = 0;
+ u8 *src, *dst;
+ u32 num_msg;
+ int ret = 0;
+ int i;
+
+ if (!cdata || data_bytes < sizeof(*cdata))
+ return -EINVAL;
+
+ if ((cdata->rhdr.hdr.cmd & SOF_GLB_TYPE_MASK) != SOF_IPC_GLB_COMP_MSG) {
+ dev_err(sdev->dev, "%s: Not supported message type of %#x\n",
+ __func__, cdata->rhdr.hdr.cmd);
+ return -EINVAL;
+ }
+
+ /* send normal size ipc in one part */
+ if (cdata->rhdr.hdr.size <= ipc->max_payload_size)
+ return sof_ipc3_tx_msg(sdev, cdata, cdata->rhdr.hdr.size,
+ cdata, cdata->rhdr.hdr.size, false);
+
+ cdata_chunk = kzalloc(ipc->max_payload_size, GFP_KERNEL);
+ if (!cdata_chunk)
+ return -ENOMEM;
+
+ switch (cdata->type) {
+ case SOF_CTRL_TYPE_VALUE_CHAN_GET:
+ case SOF_CTRL_TYPE_VALUE_CHAN_SET:
+ hdr_bytes = sizeof(struct sof_ipc_ctrl_data);
+ if (set) {
+ src = (u8 *)cdata->chanv;
+ dst = (u8 *)cdata_chunk->chanv;
+ } else {
+ src = (u8 *)cdata_chunk->chanv;
+ dst = (u8 *)cdata->chanv;
+ }
+ break;
+ case SOF_CTRL_TYPE_DATA_GET:
+ case SOF_CTRL_TYPE_DATA_SET:
+ hdr_bytes = sizeof(struct sof_ipc_ctrl_data) + sizeof(struct sof_abi_hdr);
+ if (set) {
+ src = (u8 *)cdata->data->data;
+ dst = (u8 *)cdata_chunk->data->data;
+ } else {
+ src = (u8 *)cdata_chunk->data->data;
+ dst = (u8 *)cdata->data->data;
+ }
+ break;
+ default:
+ kfree(cdata_chunk);
+ return -EINVAL;
+ }
+
+ msg_bytes = cdata->rhdr.hdr.size - hdr_bytes;
+ payload_size = ipc->max_payload_size - hdr_bytes;
+ num_msg = DIV_ROUND_UP(msg_bytes, payload_size);
+
+ /* copy the header data */
+ memcpy(cdata_chunk, cdata, hdr_bytes);
+
+ /* Serialise IPC TX */
+ mutex_lock(&sdev->ipc->tx_mutex);
+
+ /* copy the payload data in a loop */
+ for (i = 0; i < num_msg; i++) {
+ send_bytes = min(msg_bytes, payload_size);
+ cdata_chunk->num_elems = send_bytes;
+ cdata_chunk->rhdr.hdr.size = hdr_bytes + send_bytes;
+ cdata_chunk->msg_index = i;
+ msg_bytes -= send_bytes;
+ cdata_chunk->elems_remaining = msg_bytes;
+
+ if (set)
+ memcpy(dst, src + offset, send_bytes);
+
+ ret = ipc3_tx_msg_unlocked(sdev->ipc,
+ cdata_chunk, cdata_chunk->rhdr.hdr.size,
+ cdata_chunk, cdata_chunk->rhdr.hdr.size);
+ if (ret < 0)
+ break;
+
+ if (!set)
+ memcpy(dst + offset, src, send_bytes);
+
+ offset += payload_size;
+ }
+
+ if (sof_debug_check_flag(SOF_DBG_DUMP_IPC_MESSAGE_PAYLOAD)) {
+ size_t header_bytes = sizeof(struct sof_ipc_reply);
+ char *payload = (char *)cdata;
+
+ payload += header_bytes;
+ sof_ipc3_dump_payload(sdev, payload, data_bytes - header_bytes);
+ }
+
+ mutex_unlock(&sdev->ipc->tx_mutex);
+
+ kfree(cdata_chunk);
+
+ return ret;
+}
+
+int sof_ipc3_get_ext_windows(struct snd_sof_dev *sdev,
+ const struct sof_ipc_ext_data_hdr *ext_hdr)
+{
+ const struct sof_ipc_window *w =
+ container_of(ext_hdr, struct sof_ipc_window, ext_hdr);
+
+ if (w->num_windows == 0 || w->num_windows > SOF_IPC_MAX_ELEMS)
+ return -EINVAL;
+
+ if (sdev->info_window) {
+ if (memcmp(sdev->info_window, w, ext_hdr->hdr.size)) {
+ dev_err(sdev->dev, "mismatch between window descriptor from extended manifest and mailbox");
+ return -EINVAL;
+ }
+ return 0;
+ }
+
+ /* keep a local copy of the data */
+ sdev->info_window = devm_kmemdup(sdev->dev, w, ext_hdr->hdr.size, GFP_KERNEL);
+ if (!sdev->info_window)
+ return -ENOMEM;
+
+ return 0;
+}
+
+int sof_ipc3_get_cc_info(struct snd_sof_dev *sdev,
+ const struct sof_ipc_ext_data_hdr *ext_hdr)
+{
+ int ret;
+
+ const struct sof_ipc_cc_version *cc =
+ container_of(ext_hdr, struct sof_ipc_cc_version, ext_hdr);
+
+ if (sdev->cc_version) {
+ if (memcmp(sdev->cc_version, cc, cc->ext_hdr.hdr.size)) {
+ dev_err(sdev->dev,
+ "Receive diverged cc_version descriptions");
+ return -EINVAL;
+ }
+ return 0;
+ }
+
+ dev_dbg(sdev->dev,
+ "Firmware info: used compiler %s %d:%d:%d%s used optimization flags %s\n",
+ cc->name, cc->major, cc->minor, cc->micro, cc->desc, cc->optim);
+
+ /* create read-only cc_version debugfs to store compiler version info */
+ /* use local copy of the cc_version to prevent data corruption */
+ if (sdev->first_boot) {
+ sdev->cc_version = devm_kmemdup(sdev->dev, cc, cc->ext_hdr.hdr.size, GFP_KERNEL);
+ if (!sdev->cc_version)
+ return -ENOMEM;
+
+ ret = snd_sof_debugfs_buf_item(sdev, sdev->cc_version,
+ cc->ext_hdr.hdr.size,
+ "cc_version", 0444);
+
+ /* errors are only due to memory allocation, not debugfs */
+ if (ret < 0) {
+ dev_err(sdev->dev, "snd_sof_debugfs_buf_item failed\n");
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+/* parse the extended FW boot data structures from FW boot message */
+static int ipc3_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 offset)
+{
+ struct sof_ipc_ext_data_hdr *ext_hdr;
+ void *ext_data;
+ int ret = 0;
+
+ ext_data = kzalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!ext_data)
+ return -ENOMEM;
+
+ /* get first header */
+ snd_sof_dsp_block_read(sdev, SOF_FW_BLK_TYPE_SRAM, offset, ext_data,
+ sizeof(*ext_hdr));
+ ext_hdr = ext_data;
+
+ while (ext_hdr->hdr.cmd == SOF_IPC_FW_READY) {
+ /* read in ext structure */
+ snd_sof_dsp_block_read(sdev, SOF_FW_BLK_TYPE_SRAM,
+ offset + sizeof(*ext_hdr),
+ (void *)((u8 *)ext_data + sizeof(*ext_hdr)),
+ ext_hdr->hdr.size - sizeof(*ext_hdr));
+
+ dev_dbg(sdev->dev, "found ext header type %d size 0x%x\n",
+ ext_hdr->type, ext_hdr->hdr.size);
+
+ /* process structure data */
+ switch (ext_hdr->type) {
+ case SOF_IPC_EXT_WINDOW:
+ ret = sof_ipc3_get_ext_windows(sdev, ext_hdr);
+ break;
+ case SOF_IPC_EXT_CC_INFO:
+ ret = sof_ipc3_get_cc_info(sdev, ext_hdr);
+ break;
+ case SOF_IPC_EXT_UNUSED:
+ case SOF_IPC_EXT_PROBE_INFO:
+ case SOF_IPC_EXT_USER_ABI_INFO:
+ /* They are supported but we don't do anything here */
+ break;
+ default:
+ dev_info(sdev->dev, "unknown ext header type %d size 0x%x\n",
+ ext_hdr->type, ext_hdr->hdr.size);
+ ret = 0;
+ break;
+ }
+
+ if (ret < 0) {
+ dev_err(sdev->dev, "Failed to parse ext data type %d\n",
+ ext_hdr->type);
+ break;
+ }
+
+ /* move to next header */
+ offset += ext_hdr->hdr.size;
+ snd_sof_dsp_block_read(sdev, SOF_FW_BLK_TYPE_SRAM, offset, ext_data,
+ sizeof(*ext_hdr));
+ ext_hdr = ext_data;
+ }
+
+ kfree(ext_data);
+ return ret;
+}
+
+static void ipc3_get_windows(struct snd_sof_dev *sdev)
+{
+ struct sof_ipc_window_elem *elem;
+ u32 outbox_offset = 0;
+ u32 stream_offset = 0;
+ u32 inbox_offset = 0;
+ u32 outbox_size = 0;
+ u32 stream_size = 0;
+ u32 inbox_size = 0;
+ u32 debug_size = 0;
+ u32 debug_offset = 0;
+ int window_offset;
+ int i;
+
+ if (!sdev->info_window) {
+ dev_err(sdev->dev, "%s: No window info present\n", __func__);
+ return;
+ }
+
+ for (i = 0; i < sdev->info_window->num_windows; i++) {
+ elem = &sdev->info_window->window[i];
+
+ window_offset = snd_sof_dsp_get_window_offset(sdev, elem->id);
+ if (window_offset < 0) {
+ dev_warn(sdev->dev, "No offset for window %d\n", elem->id);
+ continue;
+ }
+
+ switch (elem->type) {
+ case SOF_IPC_REGION_UPBOX:
+ inbox_offset = window_offset + elem->offset;
+ inbox_size = elem->size;
+ snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM,
+ inbox_offset,
+ elem->size, "inbox",
+ SOF_DEBUGFS_ACCESS_D0_ONLY);
+ break;
+ case SOF_IPC_REGION_DOWNBOX:
+ outbox_offset = window_offset + elem->offset;
+ outbox_size = elem->size;
+ snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM,
+ outbox_offset,
+ elem->size, "outbox",
+ SOF_DEBUGFS_ACCESS_D0_ONLY);
+ break;
+ case SOF_IPC_REGION_TRACE:
+ snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM,
+ window_offset + elem->offset,
+ elem->size, "etrace",
+ SOF_DEBUGFS_ACCESS_D0_ONLY);
+ break;
+ case SOF_IPC_REGION_DEBUG:
+ debug_offset = window_offset + elem->offset;
+ debug_size = elem->size;
+ snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM,
+ window_offset + elem->offset,
+ elem->size, "debug",
+ SOF_DEBUGFS_ACCESS_D0_ONLY);
+ break;
+ case SOF_IPC_REGION_STREAM:
+ stream_offset = window_offset + elem->offset;
+ stream_size = elem->size;
+ snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM,
+ stream_offset,
+ elem->size, "stream",
+ SOF_DEBUGFS_ACCESS_D0_ONLY);
+ break;
+ case SOF_IPC_REGION_REGS:
+ snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM,
+ window_offset + elem->offset,
+ elem->size, "regs",
+ SOF_DEBUGFS_ACCESS_D0_ONLY);
+ break;
+ case SOF_IPC_REGION_EXCEPTION:
+ sdev->dsp_oops_offset = window_offset + elem->offset;
+ snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM,
+ window_offset + elem->offset,
+ elem->size, "exception",
+ SOF_DEBUGFS_ACCESS_D0_ONLY);
+ break;
+ default:
+ dev_err(sdev->dev, "%s: Illegal window info: %u\n",
+ __func__, elem->type);
+ return;
+ }
+ }
+
+ if (outbox_size == 0 || inbox_size == 0) {
+ dev_err(sdev->dev, "%s: Illegal mailbox window\n", __func__);
+ return;
+ }
+
+ sdev->dsp_box.offset = inbox_offset;
+ sdev->dsp_box.size = inbox_size;
+
+ sdev->host_box.offset = outbox_offset;
+ sdev->host_box.size = outbox_size;
+
+ sdev->stream_box.offset = stream_offset;
+ sdev->stream_box.size = stream_size;
+
+ sdev->debug_box.offset = debug_offset;
+ sdev->debug_box.size = debug_size;
+
+ dev_dbg(sdev->dev, " mailbox upstream 0x%x - size 0x%x\n",
+ inbox_offset, inbox_size);
+ dev_dbg(sdev->dev, " mailbox downstream 0x%x - size 0x%x\n",
+ outbox_offset, outbox_size);
+ dev_dbg(sdev->dev, " stream region 0x%x - size 0x%x\n",
+ stream_offset, stream_size);
+ dev_dbg(sdev->dev, " debug region 0x%x - size 0x%x\n",
+ debug_offset, debug_size);
+}
+
+static int ipc3_init_reply_data_buffer(struct snd_sof_dev *sdev)
+{
+ struct snd_sof_ipc_msg *msg = &sdev->ipc->msg;
+
+ msg->reply_data = devm_kzalloc(sdev->dev, SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL);
+ if (!msg->reply_data)
+ return -ENOMEM;
+
+ sdev->ipc->max_payload_size = SOF_IPC_MSG_MAX_SIZE;
+
+ return 0;
+}
+
+int sof_ipc3_validate_fw_version(struct snd_sof_dev *sdev)
+{
+ struct sof_ipc_fw_ready *ready = &sdev->fw_ready;
+ struct sof_ipc_fw_version *v = &ready->version;
+
+ dev_info(sdev->dev,
+ "Firmware info: version %d:%d:%d-%s\n", v->major, v->minor,
+ v->micro, v->tag);
+ dev_info(sdev->dev,
+ "Firmware: ABI %d:%d:%d Kernel ABI %d:%d:%d\n",
+ SOF_ABI_VERSION_MAJOR(v->abi_version),
+ SOF_ABI_VERSION_MINOR(v->abi_version),
+ SOF_ABI_VERSION_PATCH(v->abi_version),
+ SOF_ABI_MAJOR, SOF_ABI_MINOR, SOF_ABI_PATCH);
+
+ if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, v->abi_version)) {
+ dev_err(sdev->dev, "incompatible FW ABI version\n");
+ return -EINVAL;
+ }
+
+ if (IS_ENABLED(CONFIG_SND_SOC_SOF_STRICT_ABI_CHECKS) &&
+ SOF_ABI_VERSION_MINOR(v->abi_version) > SOF_ABI_MINOR) {
+ dev_err(sdev->dev, "FW ABI is more recent than kernel\n");
+ return -EINVAL;
+ }
+
+ if (ready->flags & SOF_IPC_INFO_BUILD) {
+ dev_info(sdev->dev,
+ "Firmware debug build %d on %s-%s - options:\n"
+ " GDB: %s\n"
+ " lock debug: %s\n"
+ " lock vdebug: %s\n",
+ v->build, v->date, v->time,
+ (ready->flags & SOF_IPC_INFO_GDB) ?
+ "enabled" : "disabled",
+ (ready->flags & SOF_IPC_INFO_LOCKS) ?
+ "enabled" : "disabled",
+ (ready->flags & SOF_IPC_INFO_LOCKSV) ?
+ "enabled" : "disabled");
+ }
+
+ /* copy the fw_version into debugfs at first boot */
+ memcpy(&sdev->fw_version, v, sizeof(*v));
+
+ return 0;
+}
+
+static int ipc3_fw_ready(struct snd_sof_dev *sdev, u32 cmd)
+{
+ struct sof_ipc_fw_ready *fw_ready = &sdev->fw_ready;
+ int offset;
+ int ret;
+
+ /* mailbox must be on 4k boundary */
+ offset = snd_sof_dsp_get_mailbox_offset(sdev);
+ if (offset < 0) {
+ dev_err(sdev->dev, "%s: no mailbox offset\n", __func__);
+ return offset;
+ }
+
+ dev_dbg(sdev->dev, "DSP is ready 0x%8.8x offset 0x%x\n", cmd, offset);
+
+ /* no need to re-check version/ABI for subsequent boots */
+ if (!sdev->first_boot)
+ return 0;
+
+ /*
+ * copy data from the DSP FW ready offset
+ * Subsequent error handling is not needed for BLK_TYPE_SRAM
+ */
+ ret = snd_sof_dsp_block_read(sdev, SOF_FW_BLK_TYPE_SRAM, offset, fw_ready,
+ sizeof(*fw_ready));
+ if (ret) {
+ dev_err(sdev->dev,
+ "Unable to read fw_ready, read from TYPE_SRAM failed\n");
+ return ret;
+ }
+
+ /* make sure ABI version is compatible */
+ ret = sof_ipc3_validate_fw_version(sdev);
+ if (ret < 0)
+ return ret;
+
+ /* now check for extended data */
+ ipc3_fw_parse_ext_data(sdev, offset + sizeof(struct sof_ipc_fw_ready));
+
+ ipc3_get_windows(sdev);
+
+ return ipc3_init_reply_data_buffer(sdev);
+}
+
+/* IPC stream position. */
+static void ipc3_period_elapsed(struct snd_sof_dev *sdev, u32 msg_id)
+{
+ struct snd_soc_component *scomp = sdev->component;
+ struct snd_sof_pcm_stream *stream;
+ struct sof_ipc_stream_posn posn;
+ struct snd_sof_pcm *spcm;
+ int direction, ret;
+
+ spcm = snd_sof_find_spcm_comp(scomp, msg_id, &direction);
+ if (!spcm) {
+ dev_err(sdev->dev, "period elapsed for unknown stream, msg_id %d\n",
+ msg_id);
+ return;
+ }
+
+ stream = &spcm->stream[direction];
+ ret = snd_sof_ipc_msg_data(sdev, stream, &posn, sizeof(posn));
+ if (ret < 0) {
+ dev_warn(sdev->dev, "failed to read stream position: %d\n", ret);
+ return;
+ }
+
+ trace_sof_ipc3_period_elapsed_position(sdev, &posn);
+
+ memcpy(&stream->posn, &posn, sizeof(posn));
+
+ if (spcm->pcm.compress)
+ snd_sof_compr_fragment_elapsed(stream->cstream);
+ else if (stream->substream->runtime &&
+ !stream->substream->runtime->no_period_wakeup)
+ /* only inform ALSA for period_wakeup mode */
+ snd_sof_pcm_period_elapsed(stream->substream);
+}
+
+/* DSP notifies host of an XRUN within FW */
+static void ipc3_xrun(struct snd_sof_dev *sdev, u32 msg_id)
+{
+ struct snd_soc_component *scomp = sdev->component;
+ struct snd_sof_pcm_stream *stream;
+ struct sof_ipc_stream_posn posn;
+ struct snd_sof_pcm *spcm;
+ int direction, ret;
+
+ spcm = snd_sof_find_spcm_comp(scomp, msg_id, &direction);
+ if (!spcm) {
+ dev_err(sdev->dev, "XRUN for unknown stream, msg_id %d\n",
+ msg_id);
+ return;
+ }
+
+ stream = &spcm->stream[direction];
+ ret = snd_sof_ipc_msg_data(sdev, stream, &posn, sizeof(posn));
+ if (ret < 0) {
+ dev_warn(sdev->dev, "failed to read overrun position: %d\n", ret);
+ return;
+ }
+
+ dev_dbg(sdev->dev, "posn XRUN: host %llx comp %d size %d\n",
+ posn.host_posn, posn.xrun_comp_id, posn.xrun_size);
+
+#if defined(CONFIG_SND_SOC_SOF_DEBUG_XRUN_STOP)
+ /* stop PCM on XRUN - used for pipeline debug */
+ memcpy(&stream->posn, &posn, sizeof(posn));
+ snd_pcm_stop_xrun(stream->substream);
+#endif
+}
+
+/* stream notifications from firmware */
+static void ipc3_stream_message(struct snd_sof_dev *sdev, void *msg_buf)
+{
+ struct sof_ipc_cmd_hdr *hdr = msg_buf;
+ u32 msg_type = hdr->cmd & SOF_CMD_TYPE_MASK;
+ u32 msg_id = SOF_IPC_MESSAGE_ID(hdr->cmd);
+
+ switch (msg_type) {
+ case SOF_IPC_STREAM_POSITION:
+ ipc3_period_elapsed(sdev, msg_id);
+ break;
+ case SOF_IPC_STREAM_TRIG_XRUN:
+ ipc3_xrun(sdev, msg_id);
+ break;
+ default:
+ dev_err(sdev->dev, "unhandled stream message %#x\n",
+ msg_id);
+ break;
+ }
+}
+
+/* component notifications from firmware */
+static void ipc3_comp_notification(struct snd_sof_dev *sdev, void *msg_buf)
+{
+ const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
+ struct sof_ipc_cmd_hdr *hdr = msg_buf;
+ u32 msg_type = hdr->cmd & SOF_CMD_TYPE_MASK;
+
+ switch (msg_type) {
+ case SOF_IPC_COMP_GET_VALUE:
+ case SOF_IPC_COMP_GET_DATA:
+ break;
+ default:
+ dev_err(sdev->dev, "unhandled component message %#x\n", msg_type);
+ return;
+ }
+
+ if (tplg_ops->control->update)
+ tplg_ops->control->update(sdev, msg_buf);
+}
+
+static void ipc3_trace_message(struct snd_sof_dev *sdev, void *msg_buf)
+{
+ struct sof_ipc_cmd_hdr *hdr = msg_buf;
+ u32 msg_type = hdr->cmd & SOF_CMD_TYPE_MASK;
+
+ switch (msg_type) {
+ case SOF_IPC_TRACE_DMA_POSITION:
+ ipc3_dtrace_posn_update(sdev, msg_buf);
+ break;
+ default:
+ dev_err(sdev->dev, "unhandled trace message %#x\n", msg_type);
+ break;
+ }
+}
+
+void sof_ipc3_do_rx_work(struct snd_sof_dev *sdev, struct sof_ipc_cmd_hdr *hdr, void *msg_buf)
+{
+ ipc3_rx_callback rx_callback = NULL;
+ u32 cmd;
+ int err;
+
+ ipc3_log_header(sdev->dev, "ipc rx", hdr->cmd);
+
+ if (hdr->size < sizeof(*hdr) || hdr->size > SOF_IPC_MSG_MAX_SIZE) {
+ dev_err(sdev->dev, "The received message size is invalid: %u\n",
+ hdr->size);
+ return;
+ }
+
+ cmd = hdr->cmd & SOF_GLB_TYPE_MASK;
+
+ /* check message type */
+ switch (cmd) {
+ case SOF_IPC_GLB_REPLY:
+ dev_err(sdev->dev, "ipc reply unknown\n");
+ break;
+ case SOF_IPC_FW_READY:
+ /* check for FW boot completion */
+ if (sdev->fw_state == SOF_FW_BOOT_IN_PROGRESS) {
+ err = ipc3_fw_ready(sdev, cmd);
+ if (err < 0)
+ sof_set_fw_state(sdev, SOF_FW_BOOT_READY_FAILED);
+ else
+ sof_set_fw_state(sdev, SOF_FW_BOOT_READY_OK);
+
+ /* wake up firmware loader */
+ wake_up(&sdev->boot_wait);
+ }
+ break;
+ case SOF_IPC_GLB_COMPOUND:
+ case SOF_IPC_GLB_TPLG_MSG:
+ case SOF_IPC_GLB_PM_MSG:
+ break;
+ case SOF_IPC_GLB_COMP_MSG:
+ rx_callback = ipc3_comp_notification;
+ break;
+ case SOF_IPC_GLB_STREAM_MSG:
+ rx_callback = ipc3_stream_message;
+ break;
+ case SOF_IPC_GLB_TRACE_MSG:
+ rx_callback = ipc3_trace_message;
+ break;
+ default:
+ dev_err(sdev->dev, "%s: Unknown DSP message: 0x%x\n", __func__, cmd);
+ break;
+ }
+
+ /* Call local handler for the message */
+ if (rx_callback)
+ rx_callback(sdev, msg_buf);
+
+ /* Notify registered clients */
+ sof_client_ipc_rx_dispatcher(sdev, msg_buf);
+
+ ipc3_log_header(sdev->dev, "ipc rx done", hdr->cmd);
+}
+EXPORT_SYMBOL(sof_ipc3_do_rx_work);
+
+/* DSP firmware has sent host a message */
+static void sof_ipc3_rx_msg(struct snd_sof_dev *sdev)
+{
+ struct sof_ipc_cmd_hdr hdr;
+ void *msg_buf;
+ int err;
+
+ /* read back header */
+ err = snd_sof_ipc_msg_data(sdev, NULL, &hdr, sizeof(hdr));
+ if (err < 0) {
+ dev_warn(sdev->dev, "failed to read IPC header: %d\n", err);
+ return;
+ }
+
+ if (hdr.size < sizeof(hdr) || hdr.size > SOF_IPC_MSG_MAX_SIZE) {
+ dev_err(sdev->dev, "The received message size is invalid\n");
+ return;
+ }
+
+ /* read the full message */
+ msg_buf = kmalloc(hdr.size, GFP_KERNEL);
+ if (!msg_buf)
+ return;
+
+ err = snd_sof_ipc_msg_data(sdev, NULL, msg_buf, hdr.size);
+ if (err < 0) {
+ dev_err(sdev->dev, "%s: Failed to read message: %d\n", __func__, err);
+ kfree(msg_buf);
+ return;
+ }
+
+ sof_ipc3_do_rx_work(sdev, &hdr, msg_buf);
+
+ kfree(msg_buf);
+}
+
+static int sof_ipc3_set_core_state(struct snd_sof_dev *sdev, int core_idx, bool on)
+{
+ struct sof_ipc_pm_core_config core_cfg = {
+ .hdr.size = sizeof(core_cfg),
+ .hdr.cmd = SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CORE_ENABLE,
+ };
+
+ if (on)
+ core_cfg.enable_mask = sdev->enabled_cores_mask | BIT(core_idx);
+ else
+ core_cfg.enable_mask = sdev->enabled_cores_mask & ~BIT(core_idx);
+
+ return sof_ipc3_tx_msg(sdev, &core_cfg, sizeof(core_cfg), NULL, 0, false);
+}
+
+static int sof_ipc3_ctx_ipc(struct snd_sof_dev *sdev, int cmd)
+{
+ struct sof_ipc_pm_ctx pm_ctx = {
+ .hdr.size = sizeof(pm_ctx),
+ .hdr.cmd = SOF_IPC_GLB_PM_MSG | cmd,
+ };
+
+ /* send ctx save ipc to dsp */
+ return sof_ipc3_tx_msg(sdev, &pm_ctx, sizeof(pm_ctx), NULL, 0, false);
+}
+
+static int sof_ipc3_ctx_save(struct snd_sof_dev *sdev)
+{
+ return sof_ipc3_ctx_ipc(sdev, SOF_IPC_PM_CTX_SAVE);
+}
+
+static int sof_ipc3_ctx_restore(struct snd_sof_dev *sdev)
+{
+ return sof_ipc3_ctx_ipc(sdev, SOF_IPC_PM_CTX_RESTORE);
+}
+
+static int sof_ipc3_set_pm_gate(struct snd_sof_dev *sdev, u32 flags)
+{
+ struct sof_ipc_pm_gate pm_gate;
+
+ memset(&pm_gate, 0, sizeof(pm_gate));
+
+ /* configure pm_gate ipc message */
+ pm_gate.hdr.size = sizeof(pm_gate);
+ pm_gate.hdr.cmd = SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_GATE;
+ pm_gate.flags = flags;
+
+ /* send pm_gate ipc to dsp */
+ return sof_ipc_tx_message_no_pm_no_reply(sdev->ipc, &pm_gate, sizeof(pm_gate));
+}
+
+static const struct sof_ipc_pm_ops ipc3_pm_ops = {
+ .ctx_save = sof_ipc3_ctx_save,
+ .ctx_restore = sof_ipc3_ctx_restore,
+ .set_core_state = sof_ipc3_set_core_state,
+ .set_pm_gate = sof_ipc3_set_pm_gate,
+};
+
+const struct sof_ipc_ops ipc3_ops = {
+ .tplg = &ipc3_tplg_ops,
+ .pm = &ipc3_pm_ops,
+ .pcm = &ipc3_pcm_ops,
+ .fw_loader = &ipc3_loader_ops,
+ .fw_tracing = &ipc3_dtrace_ops,
+
+ .tx_msg = sof_ipc3_tx_msg,
+ .rx_msg = sof_ipc3_rx_msg,
+ .set_get_data = sof_ipc3_set_get_data,
+ .get_reply = sof_ipc3_get_reply,
+};
diff --git a/sound/soc/sof/ipc4-control.c b/sound/soc/sof/ipc4-control.c
new file mode 100644
index 000000000000..1be9519de909
--- /dev/null
+++ b/sound/soc/sof/ipc4-control.c
@@ -0,0 +1,858 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2022 Intel Corporation. All rights reserved.
+//
+//
+
+#include "sof-priv.h"
+#include "sof-audio.h"
+#include "ipc4-priv.h"
+#include "ipc4-topology.h"
+
+static int sof_ipc4_set_get_kcontrol_data(struct snd_sof_control *scontrol,
+ bool set, bool lock)
+{
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ const struct sof_ipc_ops *iops = sdev->ipc->ops;
+ struct sof_ipc4_msg *msg = &cdata->msg;
+ struct snd_sof_widget *swidget;
+ bool widget_found = false;
+ int ret = 0;
+
+ /* find widget associated with the control */
+ list_for_each_entry(swidget, &sdev->widget_list, list) {
+ if (swidget->comp_id == scontrol->comp_id) {
+ widget_found = true;
+ break;
+ }
+ }
+
+ if (!widget_found) {
+ dev_err(scomp->dev, "Failed to find widget for kcontrol %s\n", scontrol->name);
+ return -ENOENT;
+ }
+
+ if (lock)
+ mutex_lock(&swidget->setup_mutex);
+ else
+ lockdep_assert_held(&swidget->setup_mutex);
+
+ /*
+ * Volatile controls should always be part of static pipelines and the
+ * widget use_count would always be > 0 in this case. For the others,
+ * just return the cached value if the widget is not set up.
+ */
+ if (!swidget->use_count)
+ goto unlock;
+
+ msg->primary &= ~SOF_IPC4_MOD_INSTANCE_MASK;
+ msg->primary |= SOF_IPC4_MOD_INSTANCE(swidget->instance_id);
+
+ ret = iops->set_get_data(sdev, msg, msg->data_size, set);
+ if (!set)
+ goto unlock;
+
+ /* It is a set-data operation, and we have a valid backup that we can restore */
+ if (ret < 0) {
+ if (!scontrol->old_ipc_control_data)
+ goto unlock;
+ /*
+ * Current ipc_control_data is not valid, we use the last known good
+ * configuration
+ */
+ memcpy(scontrol->ipc_control_data, scontrol->old_ipc_control_data,
+ scontrol->max_size);
+ kfree(scontrol->old_ipc_control_data);
+ scontrol->old_ipc_control_data = NULL;
+ /* Send the last known good configuration to firmware */
+ ret = iops->set_get_data(sdev, msg, msg->data_size, set);
+ if (ret < 0)
+ goto unlock;
+ }
+
+unlock:
+ if (lock)
+ mutex_unlock(&swidget->setup_mutex);
+
+ return ret;
+}
+
+static int
+sof_ipc4_set_volume_data(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget,
+ struct snd_sof_control *scontrol, bool lock)
+{
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ struct sof_ipc4_gain *gain = swidget->private;
+ struct sof_ipc4_msg *msg = &cdata->msg;
+ struct sof_ipc4_gain_params params;
+ bool all_channels_equal = true;
+ u32 value;
+ int ret, i;
+
+ /* check if all channel values are equal */
+ value = cdata->chanv[0].value;
+ for (i = 1; i < scontrol->num_channels; i++) {
+ if (cdata->chanv[i].value != value) {
+ all_channels_equal = false;
+ break;
+ }
+ }
+
+ /*
+ * notify DSP with a single IPC message if all channel values are equal. Otherwise send
+ * a separate IPC for each channel.
+ */
+ for (i = 0; i < scontrol->num_channels; i++) {
+ if (all_channels_equal) {
+ params.channels = SOF_IPC4_GAIN_ALL_CHANNELS_MASK;
+ params.init_val = cdata->chanv[0].value;
+ } else {
+ params.channels = cdata->chanv[i].channel;
+ params.init_val = cdata->chanv[i].value;
+ }
+
+ /* set curve type and duration from topology */
+ params.curve_duration_l = gain->data.params.curve_duration_l;
+ params.curve_duration_h = gain->data.params.curve_duration_h;
+ params.curve_type = gain->data.params.curve_type;
+
+ msg->data_ptr = &params;
+ msg->data_size = sizeof(params);
+
+ ret = sof_ipc4_set_get_kcontrol_data(scontrol, true, lock);
+ msg->data_ptr = NULL;
+ msg->data_size = 0;
+ if (ret < 0) {
+ dev_err(sdev->dev, "Failed to set volume update for %s\n",
+ scontrol->name);
+ return ret;
+ }
+
+ if (all_channels_equal)
+ break;
+ }
+
+ return 0;
+}
+
+static bool sof_ipc4_volume_put(struct snd_sof_control *scontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ unsigned int channels = scontrol->num_channels;
+ struct snd_sof_widget *swidget;
+ bool widget_found = false;
+ bool change = false;
+ unsigned int i;
+ int ret;
+
+ /* update each channel */
+ for (i = 0; i < channels; i++) {
+ u32 value = mixer_to_ipc(ucontrol->value.integer.value[i],
+ scontrol->volume_table, scontrol->max + 1);
+
+ change = change || (value != cdata->chanv[i].value);
+ cdata->chanv[i].channel = i;
+ cdata->chanv[i].value = value;
+ }
+
+ if (!pm_runtime_active(scomp->dev))
+ return change;
+
+ /* find widget associated with the control */
+ list_for_each_entry(swidget, &sdev->widget_list, list) {
+ if (swidget->comp_id == scontrol->comp_id) {
+ widget_found = true;
+ break;
+ }
+ }
+
+ if (!widget_found) {
+ dev_err(scomp->dev, "Failed to find widget for kcontrol %s\n", scontrol->name);
+ return false;
+ }
+
+ ret = sof_ipc4_set_volume_data(sdev, swidget, scontrol, true);
+ if (ret < 0)
+ return false;
+
+ return change;
+}
+
+static int sof_ipc4_volume_get(struct snd_sof_control *scontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ unsigned int channels = scontrol->num_channels;
+ unsigned int i;
+
+ for (i = 0; i < channels; i++)
+ ucontrol->value.integer.value[i] = ipc_to_mixer(cdata->chanv[i].value,
+ scontrol->volume_table,
+ scontrol->max + 1);
+
+ return 0;
+}
+
+static int
+sof_ipc4_set_generic_control_data(struct snd_sof_dev *sdev,
+ struct snd_sof_widget *swidget,
+ struct snd_sof_control *scontrol, bool lock)
+{
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ struct sof_ipc4_control_msg_payload *data;
+ struct sof_ipc4_msg *msg = &cdata->msg;
+ size_t data_size;
+ unsigned int i;
+ int ret;
+
+ data_size = struct_size(data, chanv, scontrol->num_channels);
+ data = kzalloc(data_size, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->id = cdata->index;
+ data->num_elems = scontrol->num_channels;
+ for (i = 0; i < scontrol->num_channels; i++) {
+ data->chanv[i].channel = cdata->chanv[i].channel;
+ data->chanv[i].value = cdata->chanv[i].value;
+ }
+
+ msg->data_ptr = data;
+ msg->data_size = data_size;
+
+ ret = sof_ipc4_set_get_kcontrol_data(scontrol, true, lock);
+ msg->data_ptr = NULL;
+ msg->data_size = 0;
+ if (ret < 0)
+ dev_err(sdev->dev, "Failed to set control update for %s\n",
+ scontrol->name);
+
+ kfree(data);
+
+ return ret;
+}
+
+static void sof_ipc4_refresh_generic_control(struct snd_sof_control *scontrol)
+{
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ struct sof_ipc4_control_msg_payload *data;
+ struct sof_ipc4_msg *msg = &cdata->msg;
+ size_t data_size;
+ unsigned int i;
+ int ret;
+
+ if (!scontrol->comp_data_dirty)
+ return;
+
+ if (!pm_runtime_active(scomp->dev))
+ return;
+
+ data_size = struct_size(data, chanv, scontrol->num_channels);
+ data = kmalloc(data_size, GFP_KERNEL);
+ if (!data)
+ return;
+
+ data->id = cdata->index;
+ data->num_elems = scontrol->num_channels;
+ msg->data_ptr = data;
+ msg->data_size = data_size;
+
+ scontrol->comp_data_dirty = false;
+ ret = sof_ipc4_set_get_kcontrol_data(scontrol, false, true);
+ msg->data_ptr = NULL;
+ msg->data_size = 0;
+ if (!ret) {
+ for (i = 0; i < scontrol->num_channels; i++) {
+ cdata->chanv[i].channel = data->chanv[i].channel;
+ cdata->chanv[i].value = data->chanv[i].value;
+ }
+ } else {
+ dev_err(scomp->dev, "Failed to read control data for %s\n",
+ scontrol->name);
+ scontrol->comp_data_dirty = true;
+ }
+
+ kfree(data);
+}
+
+static bool sof_ipc4_switch_put(struct snd_sof_control *scontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct snd_sof_widget *swidget;
+ bool widget_found = false;
+ bool change = false;
+ unsigned int i;
+ u32 value;
+ int ret;
+
+ /* update each channel */
+ for (i = 0; i < scontrol->num_channels; i++) {
+ value = ucontrol->value.integer.value[i];
+ change = change || (value != cdata->chanv[i].value);
+ cdata->chanv[i].channel = i;
+ cdata->chanv[i].value = value;
+ }
+
+ if (!pm_runtime_active(scomp->dev))
+ return change;
+
+ /* find widget associated with the control */
+ list_for_each_entry(swidget, &sdev->widget_list, list) {
+ if (swidget->comp_id == scontrol->comp_id) {
+ widget_found = true;
+ break;
+ }
+ }
+
+ if (!widget_found) {
+ dev_err(scomp->dev, "Failed to find widget for kcontrol %s\n", scontrol->name);
+ return false;
+ }
+
+ ret = sof_ipc4_set_generic_control_data(sdev, swidget, scontrol, true);
+ if (ret < 0)
+ return false;
+
+ return change;
+}
+
+static int sof_ipc4_switch_get(struct snd_sof_control *scontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ unsigned int i;
+
+ sof_ipc4_refresh_generic_control(scontrol);
+
+ /* read back each channel */
+ for (i = 0; i < scontrol->num_channels; i++)
+ ucontrol->value.integer.value[i] = cdata->chanv[i].value;
+
+ return 0;
+}
+
+static bool sof_ipc4_enum_put(struct snd_sof_control *scontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct snd_sof_widget *swidget;
+ bool widget_found = false;
+ bool change = false;
+ unsigned int i;
+ u32 value;
+ int ret;
+
+ /* update each channel */
+ for (i = 0; i < scontrol->num_channels; i++) {
+ value = ucontrol->value.enumerated.item[i];
+ change = change || (value != cdata->chanv[i].value);
+ cdata->chanv[i].channel = i;
+ cdata->chanv[i].value = value;
+ }
+
+ if (!pm_runtime_active(scomp->dev))
+ return change;
+
+ /* find widget associated with the control */
+ list_for_each_entry(swidget, &sdev->widget_list, list) {
+ if (swidget->comp_id == scontrol->comp_id) {
+ widget_found = true;
+ break;
+ }
+ }
+
+ if (!widget_found) {
+ dev_err(scomp->dev, "Failed to find widget for kcontrol %s\n", scontrol->name);
+ return false;
+ }
+
+ ret = sof_ipc4_set_generic_control_data(sdev, swidget, scontrol, true);
+ if (ret < 0)
+ return false;
+
+ return change;
+}
+
+static int sof_ipc4_enum_get(struct snd_sof_control *scontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ unsigned int i;
+
+ sof_ipc4_refresh_generic_control(scontrol);
+
+ /* read back each channel */
+ for (i = 0; i < scontrol->num_channels; i++)
+ ucontrol->value.enumerated.item[i] = cdata->chanv[i].value;
+
+ return 0;
+}
+
+static int sof_ipc4_set_get_bytes_data(struct snd_sof_dev *sdev,
+ struct snd_sof_control *scontrol,
+ bool set, bool lock)
+{
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ struct sof_abi_hdr *data = cdata->data;
+ struct sof_ipc4_msg *msg = &cdata->msg;
+ int ret = 0;
+
+ /* Send the new data to the firmware only if it is powered up */
+ if (set && !pm_runtime_active(sdev->dev))
+ return 0;
+
+ msg->extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(data->type);
+
+ msg->data_ptr = data->data;
+ msg->data_size = data->size;
+
+ ret = sof_ipc4_set_get_kcontrol_data(scontrol, set, lock);
+ if (ret < 0)
+ dev_err(sdev->dev, "Failed to %s for %s\n",
+ set ? "set bytes update" : "get bytes",
+ scontrol->name);
+
+ msg->data_ptr = NULL;
+ msg->data_size = 0;
+
+ return ret;
+}
+
+static int sof_ipc4_bytes_put(struct snd_sof_control *scontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct sof_abi_hdr *data = cdata->data;
+ size_t size;
+
+ if (scontrol->max_size > sizeof(ucontrol->value.bytes.data)) {
+ dev_err_ratelimited(scomp->dev,
+ "data max %zu exceeds ucontrol data array size\n",
+ scontrol->max_size);
+ return -EINVAL;
+ }
+
+ /* scontrol->max_size has been verified to be >= sizeof(struct sof_abi_hdr) */
+ if (data->size > scontrol->max_size - sizeof(*data)) {
+ dev_err_ratelimited(scomp->dev,
+ "data size too big %u bytes max is %zu\n",
+ data->size, scontrol->max_size - sizeof(*data));
+ return -EINVAL;
+ }
+
+ size = data->size + sizeof(*data);
+
+ /* copy from kcontrol */
+ memcpy(data, ucontrol->value.bytes.data, size);
+
+ sof_ipc4_set_get_bytes_data(sdev, scontrol, true, true);
+
+ return 0;
+}
+
+static int sof_ipc4_bytes_get(struct snd_sof_control *scontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ struct sof_abi_hdr *data = cdata->data;
+ size_t size;
+
+ if (scontrol->max_size > sizeof(ucontrol->value.bytes.data)) {
+ dev_err_ratelimited(scomp->dev, "data max %zu exceeds ucontrol data array size\n",
+ scontrol->max_size);
+ return -EINVAL;
+ }
+
+ if (data->size > scontrol->max_size - sizeof(*data)) {
+ dev_err_ratelimited(scomp->dev,
+ "%u bytes of control data is invalid, max is %zu\n",
+ data->size, scontrol->max_size - sizeof(*data));
+ return -EINVAL;
+ }
+
+ size = data->size + sizeof(*data);
+
+ /* copy back to kcontrol */
+ memcpy(ucontrol->value.bytes.data, data, size);
+
+ return 0;
+}
+
+static int sof_ipc4_bytes_ext_put(struct snd_sof_control *scontrol,
+ const unsigned int __user *binary_data,
+ unsigned int size)
+{
+ struct snd_ctl_tlv __user *tlvd = (struct snd_ctl_tlv __user *)binary_data;
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct sof_abi_hdr *data = cdata->data;
+ struct sof_abi_hdr abi_hdr;
+ struct snd_ctl_tlv header;
+
+ /*
+ * The beginning of bytes data contains a header from where
+ * the length (as bytes) is needed to know the correct copy
+ * length of data from tlvd->tlv.
+ */
+ if (copy_from_user(&header, tlvd, sizeof(struct snd_ctl_tlv)))
+ return -EFAULT;
+
+ /* make sure TLV info is consistent */
+ if (header.length + sizeof(struct snd_ctl_tlv) > size) {
+ dev_err_ratelimited(scomp->dev,
+ "Inconsistent TLV, data %d + header %zu > %d\n",
+ header.length, sizeof(struct snd_ctl_tlv), size);
+ return -EINVAL;
+ }
+
+ /* be->max is coming from topology */
+ if (header.length > scontrol->max_size) {
+ dev_err_ratelimited(scomp->dev,
+ "Bytes data size %d exceeds max %zu\n",
+ header.length, scontrol->max_size);
+ return -EINVAL;
+ }
+
+ /* Verify the ABI header first */
+ if (copy_from_user(&abi_hdr, tlvd->tlv, sizeof(abi_hdr)))
+ return -EFAULT;
+
+ if (abi_hdr.magic != SOF_IPC4_ABI_MAGIC) {
+ dev_err_ratelimited(scomp->dev, "Wrong ABI magic 0x%08x\n",
+ abi_hdr.magic);
+ return -EINVAL;
+ }
+
+ if (abi_hdr.size > scontrol->max_size - sizeof(abi_hdr)) {
+ dev_err_ratelimited(scomp->dev,
+ "%u bytes of control data is invalid, max is %zu\n",
+ abi_hdr.size, scontrol->max_size - sizeof(abi_hdr));
+ return -EINVAL;
+ }
+
+ if (!scontrol->old_ipc_control_data) {
+ /* Create a backup of the current, valid bytes control */
+ scontrol->old_ipc_control_data = kmemdup(scontrol->ipc_control_data,
+ scontrol->max_size, GFP_KERNEL);
+ if (!scontrol->old_ipc_control_data)
+ return -ENOMEM;
+ }
+
+ /* Copy the whole binary data which includes the ABI header and the payload */
+ if (copy_from_user(data, tlvd->tlv, header.length)) {
+ memcpy(scontrol->ipc_control_data, scontrol->old_ipc_control_data,
+ scontrol->max_size);
+ kfree(scontrol->old_ipc_control_data);
+ scontrol->old_ipc_control_data = NULL;
+ return -EFAULT;
+ }
+
+ return sof_ipc4_set_get_bytes_data(sdev, scontrol, true, true);
+}
+
+static int _sof_ipc4_bytes_ext_get(struct snd_sof_control *scontrol,
+ const unsigned int __user *binary_data,
+ unsigned int size, bool from_dsp)
+{
+ struct snd_ctl_tlv __user *tlvd = (struct snd_ctl_tlv __user *)binary_data;
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ struct sof_abi_hdr *data = cdata->data;
+ struct snd_ctl_tlv header;
+ size_t data_size;
+
+ /*
+ * Decrement the limit by ext bytes header size to ensure the user space
+ * buffer is not exceeded.
+ */
+ if (size < sizeof(struct snd_ctl_tlv))
+ return -ENOSPC;
+
+ size -= sizeof(struct snd_ctl_tlv);
+
+ /* get all the component data from DSP */
+ if (from_dsp) {
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ int ret = sof_ipc4_set_get_bytes_data(sdev, scontrol, false, true);
+
+ if (ret < 0)
+ return ret;
+
+ /* Set the ABI magic (if the control is not initialized) */
+ data->magic = SOF_IPC4_ABI_MAGIC;
+ }
+
+ if (data->size > scontrol->max_size - sizeof(*data)) {
+ dev_err_ratelimited(scomp->dev,
+ "%u bytes of control data is invalid, max is %zu\n",
+ data->size, scontrol->max_size - sizeof(*data));
+ return -EINVAL;
+ }
+
+ data_size = data->size + sizeof(struct sof_abi_hdr);
+
+ /* make sure we don't exceed size provided by user space for data */
+ if (data_size > size)
+ return -ENOSPC;
+
+ header.numid = scontrol->comp_id;
+ header.length = data_size;
+
+ if (copy_to_user(tlvd, &header, sizeof(struct snd_ctl_tlv)))
+ return -EFAULT;
+
+ if (copy_to_user(tlvd->tlv, data, data_size))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int sof_ipc4_bytes_ext_get(struct snd_sof_control *scontrol,
+ const unsigned int __user *binary_data,
+ unsigned int size)
+{
+ return _sof_ipc4_bytes_ext_get(scontrol, binary_data, size, false);
+}
+
+static int sof_ipc4_bytes_ext_volatile_get(struct snd_sof_control *scontrol,
+ const unsigned int __user *binary_data,
+ unsigned int size)
+{
+ return _sof_ipc4_bytes_ext_get(scontrol, binary_data, size, true);
+}
+
+static int
+sof_ipc4_volsw_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget,
+ struct snd_sof_control *scontrol)
+{
+ if (scontrol->max == 1)
+ return sof_ipc4_set_generic_control_data(sdev, swidget, scontrol, false);
+
+ return sof_ipc4_set_volume_data(sdev, swidget, scontrol, false);
+}
+
+#define PARAM_ID_FROM_EXTENSION(_ext) (((_ext) & SOF_IPC4_MOD_EXT_MSG_PARAM_ID_MASK) \
+ >> SOF_IPC4_MOD_EXT_MSG_PARAM_ID_SHIFT)
+
+static void sof_ipc4_control_update(struct snd_sof_dev *sdev, void *ipc_message)
+{
+ struct sof_ipc4_msg *ipc4_msg = ipc_message;
+ struct sof_ipc4_notify_module_data *ndata = ipc4_msg->data_ptr;
+ struct sof_ipc4_control_msg_payload *msg_data;
+ struct sof_ipc4_control_data *cdata;
+ struct snd_soc_dapm_widget *widget;
+ struct snd_sof_control *scontrol;
+ struct snd_sof_widget *swidget;
+ struct snd_kcontrol *kc = NULL;
+ bool scontrol_found = false;
+ u32 event_param_id;
+ int i, type;
+
+ if (ndata->event_data_size < sizeof(*msg_data)) {
+ dev_err(sdev->dev,
+ "%s: Invalid event data size for module %u.%u: %u\n",
+ __func__, ndata->module_id, ndata->instance_id,
+ ndata->event_data_size);
+ return;
+ }
+
+ event_param_id = ndata->event_id & SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_PARAMID_MASK;
+ switch (event_param_id) {
+ case SOF_IPC4_SWITCH_CONTROL_PARAM_ID:
+ type = SND_SOC_TPLG_TYPE_MIXER;
+ break;
+ case SOF_IPC4_ENUM_CONTROL_PARAM_ID:
+ type = SND_SOC_TPLG_TYPE_ENUM;
+ break;
+ default:
+ dev_err(sdev->dev,
+ "%s: Invalid control type for module %u.%u: %u\n",
+ __func__, ndata->module_id, ndata->instance_id,
+ event_param_id);
+ return;
+ }
+
+ /* Find the swidget based on ndata->module_id and ndata->instance_id */
+ swidget = sof_ipc4_find_swidget_by_ids(sdev, ndata->module_id,
+ ndata->instance_id);
+ if (!swidget) {
+ dev_err(sdev->dev, "%s: Failed to find widget for module %u.%u\n",
+ __func__, ndata->module_id, ndata->instance_id);
+ return;
+ }
+
+ /* Find the scontrol which is the source of the notification */
+ msg_data = (struct sof_ipc4_control_msg_payload *)ndata->event_data;
+ list_for_each_entry(scontrol, &sdev->kcontrol_list, list) {
+ if (scontrol->comp_id == swidget->comp_id) {
+ u32 local_param_id;
+
+ cdata = scontrol->ipc_control_data;
+ /*
+ * The scontrol's param_id is stored in the IPC message
+ * template's extension
+ */
+ local_param_id = PARAM_ID_FROM_EXTENSION(cdata->msg.extension);
+ if (local_param_id == event_param_id &&
+ msg_data->id == cdata->index) {
+ scontrol_found = true;
+ break;
+ }
+ }
+ }
+
+ if (!scontrol_found) {
+ dev_err(sdev->dev,
+ "%s: Failed to find control on widget %s: %u:%u\n",
+ __func__, swidget->widget->name, ndata->event_id & 0xffff,
+ msg_data->id);
+ return;
+ }
+
+ if (msg_data->num_elems) {
+ /*
+ * The message includes the updated value/data, update the
+ * control's local cache using the received notification
+ */
+ for (i = 0; i < msg_data->num_elems; i++) {
+ u32 channel = msg_data->chanv[i].channel;
+
+ if (channel >= scontrol->num_channels) {
+ dev_warn(sdev->dev,
+ "Invalid channel index for %s: %u\n",
+ scontrol->name, i);
+
+ /*
+ * Mark the scontrol as dirty to force a refresh
+ * on next read
+ */
+ scontrol->comp_data_dirty = true;
+ break;
+ }
+
+ cdata->chanv[channel].value = msg_data->chanv[i].value;
+ }
+ } else {
+ /*
+ * Mark the scontrol as dirty because the value/data is changed
+ * in firmware, forcing a refresh on next read access
+ */
+ scontrol->comp_data_dirty = true;
+ }
+
+ /*
+ * Look up the ALSA kcontrol of the scontrol to be able to send a
+ * notification to user space
+ */
+ widget = swidget->widget;
+ for (i = 0; i < widget->num_kcontrols; i++) {
+ /* skip non matching types or non matching indexes within type */
+ if (widget->dobj.widget.kcontrol_type[i] == type &&
+ widget->kcontrol_news[i].index == cdata->index) {
+ kc = widget->kcontrols[i];
+ break;
+ }
+ }
+
+ if (!kc)
+ return;
+
+ snd_ctl_notify_one(swidget->scomp->card->snd_card,
+ SNDRV_CTL_EVENT_MASK_VALUE, kc, 0);
+}
+
+/* set up all controls for the widget */
+static int sof_ipc4_widget_kcontrol_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
+{
+ struct snd_sof_control *scontrol;
+ int ret = 0;
+
+ list_for_each_entry(scontrol, &sdev->kcontrol_list, list) {
+ if (scontrol->comp_id == swidget->comp_id) {
+ switch (scontrol->info_type) {
+ case SND_SOC_TPLG_CTL_VOLSW:
+ case SND_SOC_TPLG_CTL_VOLSW_SX:
+ case SND_SOC_TPLG_CTL_VOLSW_XR_SX:
+ ret = sof_ipc4_volsw_setup(sdev, swidget, scontrol);
+ break;
+ case SND_SOC_TPLG_CTL_BYTES:
+ ret = sof_ipc4_set_get_bytes_data(sdev, scontrol,
+ true, false);
+ break;
+ case SND_SOC_TPLG_CTL_ENUM:
+ case SND_SOC_TPLG_CTL_ENUM_VALUE:
+ ret = sof_ipc4_set_generic_control_data(sdev, swidget,
+ scontrol, false);
+ break;
+ default:
+ break;
+ }
+
+ if (ret < 0) {
+ dev_err(sdev->dev,
+ "kcontrol %d set up failed for widget %s\n",
+ scontrol->comp_id, swidget->widget->name);
+ return ret;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int
+sof_ipc4_set_up_volume_table(struct snd_sof_control *scontrol, int tlv[SOF_TLV_ITEMS], int size)
+{
+ int i;
+
+ /* init the volume table */
+ scontrol->volume_table = kcalloc(size, sizeof(u32), GFP_KERNEL);
+ if (!scontrol->volume_table)
+ return -ENOMEM;
+
+ /* populate the volume table */
+ for (i = 0; i < size ; i++) {
+ u32 val = vol_compute_gain(i, tlv);
+ u64 q31val = ((u64)val) << 15; /* Can be over Q1.31, need to saturate */
+
+ scontrol->volume_table[i] = q31val > SOF_IPC4_VOL_ZERO_DB ?
+ SOF_IPC4_VOL_ZERO_DB : q31val;
+ }
+
+ return 0;
+}
+
+const struct sof_ipc_tplg_control_ops tplg_ipc4_control_ops = {
+ .volume_put = sof_ipc4_volume_put,
+ .volume_get = sof_ipc4_volume_get,
+ .switch_put = sof_ipc4_switch_put,
+ .switch_get = sof_ipc4_switch_get,
+ .enum_put = sof_ipc4_enum_put,
+ .enum_get = sof_ipc4_enum_get,
+ .bytes_put = sof_ipc4_bytes_put,
+ .bytes_get = sof_ipc4_bytes_get,
+ .bytes_ext_put = sof_ipc4_bytes_ext_put,
+ .bytes_ext_get = sof_ipc4_bytes_ext_get,
+ .bytes_ext_volatile_get = sof_ipc4_bytes_ext_volatile_get,
+ .update = sof_ipc4_control_update,
+ .widget_kcontrol_setup = sof_ipc4_widget_kcontrol_setup,
+ .set_up_volume_table = sof_ipc4_set_up_volume_table,
+};
diff --git a/sound/soc/sof/ipc4-fw-reg.h b/sound/soc/sof/ipc4-fw-reg.h
new file mode 100644
index 000000000000..7226161e57e1
--- /dev/null
+++ b/sound/soc/sof/ipc4-fw-reg.h
@@ -0,0 +1,155 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+/*
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * Copyright(c) 2022 Intel Corporation. All rights reserved.
+ */
+
+#ifndef __IPC4_FW_REG_H__
+#define __IPC4_FW_REG_H__
+
+#define SOF_IPC4_INVALID_STREAM_POSITION ULLONG_MAX
+
+/**
+ * struct sof_ipc4_pipeline_registers - Pipeline start and end information in fw
+ * @stream_start_offset: Stream start offset (LPIB) reported by mixin
+ * module allocated on pipeline attached to Host Output Gateway when
+ * first data is being mixed to mixout module. When data is not mixed
+ * (right after creation/after reset) value "(u64)-1" is reported
+ * @stream_end_offset: Stream end offset (LPIB) reported by mixin
+ * module allocated on pipeline attached to Host Output Gateway
+ * during transition from RUNNING to PAUSED. When data is not mixed
+ * (right after creation or after reset) value "(u64)-1" is reported.
+ * When first data is mixed then value "0"is reported.
+ */
+struct sof_ipc4_pipeline_registers {
+ u64 stream_start_offset;
+ u64 stream_end_offset;
+} __packed __aligned(4);
+
+#define SOF_IPC4_PV_MAX_SUPPORTED_CHANNELS 8
+
+/**
+ * struct sof_ipc4_peak_volume_regs - Volume information in fw
+ * @peak_meter: Peak volume value in fw
+ * @current_volume: Current volume value in fw
+ * @target_volume: Target volume value in fw
+ */
+struct sof_ipc4_peak_volume_regs {
+ u32 peak_meter[SOF_IPC4_PV_MAX_SUPPORTED_CHANNELS];
+ u32 current_volume[SOF_IPC4_PV_MAX_SUPPORTED_CHANNELS];
+ u32 target_volume[SOF_IPC4_PV_MAX_SUPPORTED_CHANNELS];
+} __packed __aligned(4);
+
+/**
+ * struct sof_ipc4_llp_reading - Llp information in fw
+ * @llp_l: Lower part of 64-bit LLP
+ * @llp_u: Upper part of 64-bit LLP
+ * @wclk_l: Lower part of 64-bit Wallclock
+ * @wclk_u: Upper part of 64-bit Wallclock
+ */
+struct sof_ipc4_llp_reading {
+ u32 llp_l;
+ u32 llp_u;
+ u32 wclk_l;
+ u32 wclk_u;
+} __packed __aligned(4);
+
+/**
+ * struct of sof_ipc4_llp_reading_extended - Extended llp info
+ * @llp_reading: Llp information in memory window
+ * @tpd_low: Total processed data (low part)
+ * @tpd_high: Total processed data (high part)
+ */
+struct sof_ipc4_llp_reading_extended {
+ struct sof_ipc4_llp_reading llp_reading;
+ u32 tpd_low;
+ u32 tpd_high;
+} __packed __aligned(4);
+
+/**
+ * struct sof_ipc4_llp_reading_slot - Llp slot information in memory window
+ * @node_id: Dai gateway node id
+ * @reading: Llp information in memory window
+ */
+struct sof_ipc4_llp_reading_slot {
+ u32 node_id;
+ struct sof_ipc4_llp_reading reading;
+} __packed __aligned(4);
+
+/* ROM information */
+#define SOF_IPC4_FW_FUSE_VALUE_MASK GENMASK(7, 0)
+#define SOF_IPC4_FW_LOAD_METHOD_MASK BIT(8)
+#define SOF_IPC4_FW_DOWNLINK_IPC_USE_DMA_MASK BIT(9)
+#define SOF_IPC4_FW_LOAD_METHOD_REV_MASK GENMASK(11, 10)
+#define SOF_IPC4_FW_REVISION_MIN_MASK GENMASK(15, 12)
+#define SOF_IPC4_FW_REVISION_MAJ_MASK GENMASK(19, 16)
+#define SOF_IPC4_FW_VERSION_MIN_MASK GENMASK(23, 20)
+#define SOF_IPC4_FW_VERSION_MAJ_MASK GENMASK(27, 24)
+
+/* Number of dsp core supported in FW Regs. */
+#define SOF_IPC4_MAX_SUPPORTED_ADSP_CORES 8
+
+/* Number of host pipeline registers slots in FW Regs. */
+#define SOF_IPC4_MAX_PIPELINE_REG_SLOTS 16
+
+/* Number of PeakVol registers slots in FW Regs. */
+#define SOF_IPC4_MAX_PEAK_VOL_REG_SLOTS 16
+
+/* Number of GPDMA LLP Reading slots in FW Regs. */
+#define SOF_IPC4_MAX_LLP_GPDMA_READING_SLOTS 24
+
+/* Number of Aggregated SNDW Reading slots in FW Regs. */
+#define SOF_IPC4_MAX_LLP_SNDW_READING_SLOTS 15
+
+/* Current ABI version of the Fw registers layout. */
+#define SOF_IPC4_FW_REGS_ABI_VER 1
+
+/**
+ * struct sof_ipc4_fw_registers - FW Registers exposes additional
+ * DSP / FW state information to the driver
+ * @fw_status: Current ROM / FW status
+ * @lec: Last ROM / FW error code
+ * @fps: Current DSP clock status
+ * @lnec: Last Native Error Code(from external library)
+ * @ltr: Copy of LTRC HW register value(FW only)
+ * @rsvd0: Reserved0
+ * @rom_info: ROM info
+ * @abi_ver: Version of the layout, set to the current FW_REGS_ABI_VER
+ * @slave_core_sts: Slave core states
+ * @rsvd2: Reserved2
+ * @pipeline_regs: State of pipelines attached to host output gateways
+ * @peak_vol_regs: State of PeakVol instances indexed by the PeakVol's instance_id
+ * @llp_gpdma_reading_slots: LLP Readings for single link gateways
+ * @llp_sndw_reading_slots: SNDW aggregated link gateways
+ * @llp_evad_reading_slot: LLP Readings for EVAD gateway
+ */
+struct sof_ipc4_fw_registers {
+ u32 fw_status;
+ u32 lec;
+ u32 fps;
+ u32 lnec;
+ u32 ltr;
+ u32 rsvd0;
+ u32 rom_info;
+ u32 abi_ver;
+ u8 slave_core_sts[SOF_IPC4_MAX_SUPPORTED_ADSP_CORES];
+ u32 rsvd2[6];
+
+ struct sof_ipc4_pipeline_registers
+ pipeline_regs[SOF_IPC4_MAX_PIPELINE_REG_SLOTS];
+
+ struct sof_ipc4_peak_volume_regs
+ peak_vol_regs[SOF_IPC4_MAX_PEAK_VOL_REG_SLOTS];
+
+ struct sof_ipc4_llp_reading_slot
+ llp_gpdma_reading_slots[SOF_IPC4_MAX_LLP_GPDMA_READING_SLOTS];
+
+ struct sof_ipc4_llp_reading_slot
+ llp_sndw_reading_slots[SOF_IPC4_MAX_LLP_SNDW_READING_SLOTS];
+
+ struct sof_ipc4_llp_reading_slot llp_evad_reading_slot;
+} __packed __aligned(4);
+
+#endif
diff --git a/sound/soc/sof/ipc4-loader.c b/sound/soc/sof/ipc4-loader.c
new file mode 100644
index 000000000000..c79479afa8d0
--- /dev/null
+++ b/sound/soc/sof/ipc4-loader.c
@@ -0,0 +1,494 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2022 Intel Corporation. All rights reserved.
+
+#include <linux/firmware.h>
+#include <sound/sof/ext_manifest4.h>
+#include <sound/sof/ipc4/header.h>
+#include <trace/events/sof.h>
+#include "ipc4-priv.h"
+#include "sof-audio.h"
+#include "sof-priv.h"
+#include "ops.h"
+
+/* The module ID includes the id of the library it is part of at offset 12 */
+#define SOF_IPC4_MOD_LIB_ID_SHIFT 12
+
+static ssize_t sof_ipc4_fw_parse_ext_man(struct snd_sof_dev *sdev,
+ struct sof_ipc4_fw_library *fw_lib)
+{
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+ const struct firmware *fw = fw_lib->sof_fw.fw;
+ struct sof_man4_fw_binary_header *fw_header;
+ struct sof_ext_manifest4_hdr *ext_man_hdr;
+ struct sof_man4_module_config *fm_config;
+ struct sof_ipc4_fw_module *fw_module;
+ struct sof_man4_module *fm_entry;
+ ssize_t remaining;
+ u32 fw_hdr_offset;
+ int i;
+
+ if (!ipc4_data) {
+ dev_err(sdev->dev, "%s: ipc4_data is not available\n", __func__);
+ return -EINVAL;
+ }
+
+ remaining = fw->size;
+ if (remaining <= sizeof(*ext_man_hdr)) {
+ dev_err(sdev->dev, "Firmware size is too small: %zu\n", remaining);
+ return -EINVAL;
+ }
+
+ ext_man_hdr = (struct sof_ext_manifest4_hdr *)fw->data;
+
+ /*
+ * At the start of the firmware image we must have an extended manifest.
+ * Verify that the magic number is correct.
+ */
+ if (ext_man_hdr->id != SOF_EXT_MAN4_MAGIC_NUMBER) {
+ dev_err(sdev->dev,
+ "Unexpected extended manifest magic number: %#x\n",
+ ext_man_hdr->id);
+ return -EINVAL;
+ }
+
+ fw_hdr_offset = ipc4_data->manifest_fw_hdr_offset;
+ if (!fw_hdr_offset)
+ return -EINVAL;
+
+ if (remaining <= ext_man_hdr->len + fw_hdr_offset + sizeof(*fw_header)) {
+ dev_err(sdev->dev, "Invalid firmware size %zu, should be at least %zu\n",
+ remaining, ext_man_hdr->len + fw_hdr_offset + sizeof(*fw_header));
+ return -EINVAL;
+ }
+
+ fw_header = (struct sof_man4_fw_binary_header *)
+ (fw->data + ext_man_hdr->len + fw_hdr_offset);
+ remaining -= (ext_man_hdr->len + fw_hdr_offset);
+
+ if (remaining <= fw_header->len) {
+ dev_err(sdev->dev, "Invalid fw_header->len %u\n", fw_header->len);
+ return -EINVAL;
+ }
+
+ dev_info(sdev->dev, "Loaded firmware library: %s, version: %u.%u.%u.%u\n",
+ fw_header->name, fw_header->major_version, fw_header->minor_version,
+ fw_header->hotfix_version, fw_header->build_version);
+ dev_dbg(sdev->dev, "Header length: %u, module count: %u\n",
+ fw_header->len, fw_header->num_module_entries);
+
+ fw_lib->modules = devm_kmalloc_array(sdev->dev, fw_header->num_module_entries,
+ sizeof(*fw_module), GFP_KERNEL);
+ if (!fw_lib->modules)
+ return -ENOMEM;
+
+ fw_lib->name = fw_header->name;
+ fw_lib->num_modules = fw_header->num_module_entries;
+ fw_module = fw_lib->modules;
+
+ fm_entry = (struct sof_man4_module *)((u8 *)fw_header + fw_header->len);
+ remaining -= fw_header->len;
+
+ if (remaining < fw_header->num_module_entries * sizeof(*fm_entry)) {
+ dev_err(sdev->dev, "Invalid num_module_entries %u\n",
+ fw_header->num_module_entries);
+ return -EINVAL;
+ }
+
+ fm_config = (struct sof_man4_module_config *)
+ (fm_entry + fw_header->num_module_entries);
+ remaining -= (fw_header->num_module_entries * sizeof(*fm_entry));
+ for (i = 0; i < fw_header->num_module_entries; i++) {
+ memcpy(&fw_module->man4_module_entry, fm_entry, sizeof(*fm_entry));
+
+ if (fm_entry->cfg_count) {
+ if (remaining < (fm_entry->cfg_offset + fm_entry->cfg_count) *
+ sizeof(*fm_config)) {
+ dev_err(sdev->dev, "Invalid module cfg_offset %u\n",
+ fm_entry->cfg_offset);
+ return -EINVAL;
+ }
+
+ fw_module->fw_mod_cfg = &fm_config[fm_entry->cfg_offset];
+
+ dev_dbg(sdev->dev,
+ "module %s: UUID %pUL cfg_count: %u, bss_size: %#x\n",
+ fm_entry->name, &fm_entry->uuid, fm_entry->cfg_count,
+ fm_config[fm_entry->cfg_offset].is_bytes);
+ } else {
+ dev_dbg(sdev->dev, "module %s: UUID %pUL\n", fm_entry->name,
+ &fm_entry->uuid);
+ }
+
+ fw_module->man4_module_entry.id = i;
+ ida_init(&fw_module->m_ida);
+ fw_module->private = NULL;
+
+ fw_module++;
+ fm_entry++;
+ }
+
+ return ext_man_hdr->len;
+}
+
+static size_t sof_ipc4_fw_parse_basefw_ext_man(struct snd_sof_dev *sdev)
+{
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+ struct sof_ipc4_fw_library *fw_lib;
+ ssize_t payload_offset;
+ int ret;
+
+ fw_lib = devm_kzalloc(sdev->dev, sizeof(*fw_lib), GFP_KERNEL);
+ if (!fw_lib)
+ return -ENOMEM;
+
+ fw_lib->sof_fw.fw = sdev->basefw.fw;
+
+ payload_offset = sof_ipc4_fw_parse_ext_man(sdev, fw_lib);
+ if (payload_offset > 0) {
+ fw_lib->sof_fw.payload_offset = payload_offset;
+
+ /* basefw ID is 0 */
+ fw_lib->id = 0;
+ ret = xa_insert(&ipc4_data->fw_lib_xa, 0, fw_lib, GFP_KERNEL);
+ if (ret)
+ return ret;
+ }
+
+ return payload_offset;
+}
+
+static int sof_ipc4_load_library_by_uuid(struct snd_sof_dev *sdev,
+ unsigned long lib_id, const guid_t *uuid)
+{
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+ struct sof_ipc4_fw_library *fw_lib;
+ const char *fw_filename;
+ ssize_t payload_offset;
+ int ret, i, err;
+
+ if (!sdev->pdata->fw_lib_prefix) {
+ dev_err(sdev->dev,
+ "Library loading is not supported due to not set library path\n");
+ return -EINVAL;
+ }
+
+ if (!ipc4_data->load_library) {
+ dev_err(sdev->dev, "Library loading is not supported on this platform\n");
+ return -EOPNOTSUPP;
+ }
+
+ fw_lib = devm_kzalloc(sdev->dev, sizeof(*fw_lib), GFP_KERNEL);
+ if (!fw_lib)
+ return -ENOMEM;
+
+ fw_filename = kasprintf(GFP_KERNEL, "%s/%pUL.bin",
+ sdev->pdata->fw_lib_prefix, uuid);
+ if (!fw_filename) {
+ ret = -ENOMEM;
+ goto free_fw_lib;
+ }
+
+ ret = request_firmware(&fw_lib->sof_fw.fw, fw_filename, sdev->dev);
+ if (ret < 0) {
+ dev_err(sdev->dev, "Library file '%s' is missing\n", fw_filename);
+ goto free_filename;
+ } else {
+ dev_dbg(sdev->dev, "Library file '%s' loaded\n", fw_filename);
+ }
+
+ payload_offset = sof_ipc4_fw_parse_ext_man(sdev, fw_lib);
+ if (payload_offset <= 0) {
+ if (!payload_offset)
+ ret = -EINVAL;
+ else
+ ret = payload_offset;
+
+ goto release;
+ }
+
+ fw_lib->sof_fw.payload_offset = payload_offset;
+ fw_lib->id = lib_id;
+
+ /* Fix up the module ID numbers within the library */
+ for (i = 0; i < fw_lib->num_modules; i++)
+ fw_lib->modules[i].man4_module_entry.id |= (lib_id << SOF_IPC4_MOD_LIB_ID_SHIFT);
+
+ /*
+ * Make sure that the DSP is booted and stays up while attempting the
+ * loading the library for the first time
+ */
+ ret = pm_runtime_resume_and_get(sdev->dev);
+ if (ret < 0 && ret != -EACCES) {
+ dev_err_ratelimited(sdev->dev, "%s: pm_runtime resume failed: %d\n",
+ __func__, ret);
+ goto release;
+ }
+
+ ret = ipc4_data->load_library(sdev, fw_lib, false);
+
+ pm_runtime_mark_last_busy(sdev->dev);
+ err = pm_runtime_put_autosuspend(sdev->dev);
+ if (err < 0)
+ dev_err_ratelimited(sdev->dev, "%s: pm_runtime idle failed: %d\n",
+ __func__, err);
+
+ if (ret)
+ goto release;
+
+ ret = xa_insert(&ipc4_data->fw_lib_xa, lib_id, fw_lib, GFP_KERNEL);
+ if (unlikely(ret))
+ goto release;
+
+ kfree(fw_filename);
+
+ return 0;
+
+release:
+ release_firmware(fw_lib->sof_fw.fw);
+ /* Allocated within sof_ipc4_fw_parse_ext_man() */
+ devm_kfree(sdev->dev, fw_lib->modules);
+free_filename:
+ kfree(fw_filename);
+free_fw_lib:
+ devm_kfree(sdev->dev, fw_lib);
+
+ return ret;
+}
+
+struct sof_ipc4_fw_module *sof_ipc4_find_module_by_uuid(struct snd_sof_dev *sdev,
+ const guid_t *uuid)
+{
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+ struct sof_ipc4_fw_library *fw_lib;
+ unsigned long lib_id;
+ int i, ret;
+
+ if (guid_is_null(uuid))
+ return NULL;
+
+ xa_for_each(&ipc4_data->fw_lib_xa, lib_id, fw_lib) {
+ for (i = 0; i < fw_lib->num_modules; i++) {
+ if (guid_equal(uuid, &fw_lib->modules[i].man4_module_entry.uuid))
+ return &fw_lib->modules[i];
+ }
+ }
+
+ /*
+ * Do not attempt to load external library in case the maximum number of
+ * firmware libraries have been already loaded
+ */
+ if ((lib_id + 1) == ipc4_data->max_libs_count) {
+ dev_err(sdev->dev,
+ "%s: Maximum allowed number of libraries reached (%u)\n",
+ __func__, ipc4_data->max_libs_count);
+ return NULL;
+ }
+
+ /* The module cannot be found, try to load it as a library */
+ ret = sof_ipc4_load_library_by_uuid(sdev, lib_id + 1, uuid);
+ if (ret)
+ return NULL;
+
+ /* Look for the module in the newly loaded library, it should be available now */
+ xa_for_each_start(&ipc4_data->fw_lib_xa, lib_id, fw_lib, lib_id) {
+ for (i = 0; i < fw_lib->num_modules; i++) {
+ if (guid_equal(uuid, &fw_lib->modules[i].man4_module_entry.uuid))
+ return &fw_lib->modules[i];
+ }
+ }
+
+ return NULL;
+}
+
+static int sof_ipc4_validate_firmware(struct snd_sof_dev *sdev)
+{
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+ u32 fw_hdr_offset = ipc4_data->manifest_fw_hdr_offset;
+ struct sof_man4_fw_binary_header *fw_header;
+ const struct firmware *fw = sdev->basefw.fw;
+ struct sof_ext_manifest4_hdr *ext_man_hdr;
+
+ ext_man_hdr = (struct sof_ext_manifest4_hdr *)fw->data;
+ fw_header = (struct sof_man4_fw_binary_header *)
+ (fw->data + ext_man_hdr->len + fw_hdr_offset);
+
+ /* TODO: Add firmware verification code here */
+
+ dev_dbg(sdev->dev, "Validated firmware version: %u.%u.%u.%u\n",
+ fw_header->major_version, fw_header->minor_version,
+ fw_header->hotfix_version, fw_header->build_version);
+
+ return 0;
+}
+
+int sof_ipc4_query_fw_configuration(struct snd_sof_dev *sdev)
+{
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+ const struct sof_ipc_ops *iops = sdev->ipc->ops;
+ struct sof_ipc4_fw_version *fw_ver;
+ struct sof_ipc4_tuple *tuple;
+ struct sof_ipc4_msg msg;
+ size_t offset = 0;
+ int ret;
+
+ /* Get the firmware configuration */
+ msg.primary = SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG);
+ msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ msg.primary |= SOF_IPC4_MOD_ID(SOF_IPC4_MOD_INIT_BASEFW_MOD_ID);
+ msg.primary |= SOF_IPC4_MOD_INSTANCE(SOF_IPC4_MOD_INIT_BASEFW_INSTANCE_ID);
+ msg.extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(SOF_IPC4_FW_PARAM_FW_CONFIG);
+
+ msg.data_size = sdev->ipc->max_payload_size;
+ msg.data_ptr = kzalloc(msg.data_size, GFP_KERNEL);
+ if (!msg.data_ptr)
+ return -ENOMEM;
+
+ ret = iops->set_get_data(sdev, &msg, msg.data_size, false);
+ if (ret)
+ goto out;
+
+ while (offset < msg.data_size) {
+ tuple = (struct sof_ipc4_tuple *)((u8 *)msg.data_ptr + offset);
+
+ switch (tuple->type) {
+ case SOF_IPC4_FW_CFG_FW_VERSION:
+ fw_ver = (struct sof_ipc4_fw_version *)tuple->value;
+
+ dev_info(sdev->dev,
+ "Booted firmware version: %u.%u.%u.%u\n",
+ fw_ver->major, fw_ver->minor, fw_ver->hotfix,
+ fw_ver->build);
+ break;
+ case SOF_IPC4_FW_CFG_DL_MAILBOX_BYTES:
+ trace_sof_ipc4_fw_config(sdev, "DL mailbox size", *tuple->value);
+ break;
+ case SOF_IPC4_FW_CFG_UL_MAILBOX_BYTES:
+ trace_sof_ipc4_fw_config(sdev, "UL mailbox size", *tuple->value);
+ break;
+ case SOF_IPC4_FW_CFG_TRACE_LOG_BYTES:
+ trace_sof_ipc4_fw_config(sdev, "Trace log size", *tuple->value);
+ ipc4_data->mtrace_log_bytes = *tuple->value;
+ break;
+ case SOF_IPC4_FW_CFG_MAX_LIBS_COUNT:
+ trace_sof_ipc4_fw_config(sdev, "maximum number of libraries",
+ *tuple->value);
+ ipc4_data->max_libs_count = *tuple->value;
+ if (!ipc4_data->max_libs_count)
+ ipc4_data->max_libs_count = 1;
+ break;
+ case SOF_IPC4_FW_CFG_MAX_PPL_COUNT:
+ ipc4_data->max_num_pipelines = *tuple->value;
+ trace_sof_ipc4_fw_config(sdev, "Max PPL count %d",
+ ipc4_data->max_num_pipelines);
+ if (ipc4_data->max_num_pipelines <= 0) {
+ dev_err(sdev->dev, "Invalid max_num_pipelines %d",
+ ipc4_data->max_num_pipelines);
+ ret = -EINVAL;
+ goto out;
+ }
+ break;
+ case SOF_IPC4_FW_CONTEXT_SAVE:
+ ipc4_data->fw_context_save = *tuple->value;
+ break;
+ default:
+ break;
+ }
+
+ offset += sizeof(*tuple) + tuple->size;
+ }
+
+out:
+ kfree(msg.data_ptr);
+
+ return ret;
+}
+
+int sof_ipc4_reload_fw_libraries(struct snd_sof_dev *sdev)
+{
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+ struct sof_ipc4_fw_library *fw_lib;
+ unsigned long lib_id;
+ int ret = 0;
+
+ xa_for_each_start(&ipc4_data->fw_lib_xa, lib_id, fw_lib, 1) {
+ ret = ipc4_data->load_library(sdev, fw_lib, true);
+ if (ret) {
+ dev_err(sdev->dev, "%s: Failed to reload library: %s, %d\n",
+ __func__, fw_lib->name, ret);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * sof_ipc4_update_cpc_from_manifest - Update the cpc in base config from manifest
+ * @sdev: SOF device
+ * @fw_module: pointer struct sof_ipc4_fw_module to parse
+ * @basecfg: Pointer to the base_config to update
+ */
+void sof_ipc4_update_cpc_from_manifest(struct snd_sof_dev *sdev,
+ struct sof_ipc4_fw_module *fw_module,
+ struct sof_ipc4_base_module_cfg *basecfg)
+{
+ const struct sof_man4_module_config *fw_mod_cfg;
+ u32 cpc_pick = 0;
+ u32 max_cpc = 0;
+ const char *msg;
+ int i;
+
+ if (!fw_module->fw_mod_cfg) {
+ msg = "No mod_cfg available for CPC lookup in the firmware file's manifest";
+ goto no_cpc;
+ }
+
+ /*
+ * Find the best matching (highest) CPC value based on the module's
+ * IBS/OBS configuration inferred from the audio format selection.
+ *
+ * The CPC value in each module config entry has been measured and
+ * recorded as a IBS/OBS/CPC triplet and stored in the firmware file's
+ * manifest
+ */
+ fw_mod_cfg = fw_module->fw_mod_cfg;
+ for (i = 0; i < fw_module->man4_module_entry.cfg_count; i++) {
+ if (basecfg->obs == fw_mod_cfg[i].obs &&
+ basecfg->ibs == fw_mod_cfg[i].ibs &&
+ cpc_pick < fw_mod_cfg[i].cpc)
+ cpc_pick = fw_mod_cfg[i].cpc;
+
+ if (max_cpc < fw_mod_cfg[i].cpc)
+ max_cpc = fw_mod_cfg[i].cpc;
+ }
+
+ basecfg->cpc = cpc_pick;
+
+ /* We have a matching configuration for CPC */
+ if (basecfg->cpc)
+ return;
+
+ /*
+ * No matching IBS/OBS found, the firmware manifest is missing
+ * information in the module's module configuration table.
+ */
+ if (!max_cpc)
+ msg = "No CPC value available in the firmware file's manifest";
+ else if (!cpc_pick)
+ msg = "No CPC match in the firmware file's manifest";
+
+no_cpc:
+ dev_dbg(sdev->dev, "%s (UUID: %pUL): %s (ibs/obs: %u/%u)\n",
+ fw_module->man4_module_entry.name,
+ &fw_module->man4_module_entry.uuid, msg, basecfg->ibs,
+ basecfg->obs);
+}
+
+const struct sof_ipc_fw_loader_ops ipc4_loader_ops = {
+ .validate = sof_ipc4_validate_firmware,
+ .parse_ext_manifest = sof_ipc4_fw_parse_basefw_ext_man,
+};
diff --git a/sound/soc/sof/ipc4-mtrace.c b/sound/soc/sof/ipc4-mtrace.c
new file mode 100644
index 000000000000..9f1e33ee8826
--- /dev/null
+++ b/sound/soc/sof/ipc4-mtrace.c
@@ -0,0 +1,669 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2022 Intel Corporation. All rights reserved.
+
+#include <linux/debugfs.h>
+#include <linux/sched/signal.h>
+#include <sound/sof/ipc4/header.h>
+#include "sof-priv.h"
+#include "ipc4-priv.h"
+
+/*
+ * debug info window is organized in 16 (equal sized) pages:
+ *
+ * ------------------------
+ * | Page0 - descriptors |
+ * ------------------------
+ * | Page1 - slot0 |
+ * ------------------------
+ * | Page2 - slot1 |
+ * ------------------------
+ * | ... |
+ * ------------------------
+ * | Page14 - slot13 |
+ * ------------------------
+ * | Page15 - slot14 |
+ * ------------------------
+ *
+ * The slot size == page size
+ *
+ * The first page contains descriptors for the remaining 15 cores
+ * The slot descriptor is:
+ * u32 res_id;
+ * u32 type;
+ * u32 vma;
+ *
+ * Log buffer slots have the following layout:
+ * u32 host_read_ptr;
+ * u32 dsp_write_ptr;
+ * u8 buffer[];
+ *
+ * The two pointers are offsets within the buffer.
+ */
+
+#define FW_EPOCH_DELTA 11644473600LL
+
+#define MAX_ALLOWED_LIBRARIES 16
+
+#define SOF_IPC4_INVALID_SLOT_OFFSET 0xffffffff
+
+ /* for debug and critical types */
+#define SOF_MTRACE_SLOT_CORE_MASK GENMASK(7, 0)
+#define SOF_MTRACE_SLOT_TYPE_MASK GENMASK(31, 8)
+
+#define DEFAULT_AGING_TIMER_PERIOD_MS 0x100
+#define DEFAULT_FIFO_FULL_TIMER_PERIOD_MS 0x1000
+
+/* ipc4 log level and source definitions for logs_priorities_mask */
+#define SOF_MTRACE_LOG_LEVEL_CRITICAL BIT(0)
+#define SOF_MTRACE_LOG_LEVEL_ERROR BIT(1)
+#define SOF_MTRACE_LOG_LEVEL_WARNING BIT(2)
+#define SOF_MTRACE_LOG_LEVEL_INFO BIT(3)
+#define SOF_MTRACE_LOG_LEVEL_VERBOSE BIT(4)
+#define SOF_MTRACE_LOG_SOURCE_INFRA BIT(5) /* log source 0 */
+#define SOF_MTRACE_LOG_SOURCE_HAL BIT(6)
+#define SOF_MTRACE_LOG_SOURCE_MODULE BIT(7)
+#define SOF_MTRACE_LOG_SOURCE_AUDIO BIT(8)
+#define SOF_MTRACE_LOG_SOURCE_SCHEDULER BIT(9)
+#define SOF_MTRACE_LOG_SOURCE_ULP_INFRA BIT(10)
+#define SOF_MTRACE_LOG_SOURCE_ULP_MODULE BIT(11)
+#define SOF_MTRACE_LOG_SOURCE_VISION BIT(12) /* log source 7 */
+#define DEFAULT_LOGS_PRIORITIES_MASK (SOF_MTRACE_LOG_LEVEL_CRITICAL | \
+ SOF_MTRACE_LOG_LEVEL_ERROR | \
+ SOF_MTRACE_LOG_LEVEL_WARNING | \
+ SOF_MTRACE_LOG_LEVEL_INFO | \
+ SOF_MTRACE_LOG_SOURCE_INFRA | \
+ SOF_MTRACE_LOG_SOURCE_HAL | \
+ SOF_MTRACE_LOG_SOURCE_MODULE | \
+ SOF_MTRACE_LOG_SOURCE_AUDIO)
+
+struct sof_log_state_info {
+ u32 aging_timer_period;
+ u32 fifo_full_timer_period;
+ u32 enable;
+ u32 logs_priorities_mask[MAX_ALLOWED_LIBRARIES];
+} __packed;
+
+enum sof_mtrace_state {
+ SOF_MTRACE_DISABLED,
+ SOF_MTRACE_INITIALIZING,
+ SOF_MTRACE_ENABLED,
+};
+
+struct sof_mtrace_core_data {
+ struct snd_sof_dev *sdev;
+
+ int id;
+ u32 slot_offset;
+ void *log_buffer;
+ struct mutex buffer_lock; /* for log_buffer alloc/free */
+ u32 host_read_ptr;
+ u32 dsp_write_ptr;
+ /* pos update IPC arrived before the slot offset is known, queried */
+ bool delayed_pos_update;
+ wait_queue_head_t trace_sleep;
+};
+
+struct sof_mtrace_priv {
+ struct snd_sof_dev *sdev;
+ enum sof_mtrace_state mtrace_state;
+ struct sof_log_state_info state_info;
+
+ struct sof_mtrace_core_data cores[];
+};
+
+static int sof_ipc4_mtrace_dfs_open(struct inode *inode, struct file *file)
+{
+ struct sof_mtrace_core_data *core_data = inode->i_private;
+ int ret;
+
+ mutex_lock(&core_data->buffer_lock);
+
+ if (core_data->log_buffer) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ ret = debugfs_file_get(file->f_path.dentry);
+ if (unlikely(ret))
+ goto out;
+
+ core_data->log_buffer = kmalloc(SOF_IPC4_DEBUG_SLOT_SIZE, GFP_KERNEL);
+ if (!core_data->log_buffer) {
+ debugfs_file_put(file->f_path.dentry);
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ ret = simple_open(inode, file);
+ if (ret) {
+ kfree(core_data->log_buffer);
+ debugfs_file_put(file->f_path.dentry);
+ }
+
+out:
+ mutex_unlock(&core_data->buffer_lock);
+
+ return ret;
+}
+
+static bool sof_wait_mtrace_avail(struct sof_mtrace_core_data *core_data)
+{
+ wait_queue_entry_t wait;
+
+ /* data immediately available */
+ if (core_data->host_read_ptr != core_data->dsp_write_ptr)
+ return true;
+
+ /* wait for available trace data from FW */
+ init_waitqueue_entry(&wait, current);
+ set_current_state(TASK_INTERRUPTIBLE);
+ add_wait_queue(&core_data->trace_sleep, &wait);
+
+ if (!signal_pending(current)) {
+ /* set timeout to max value, no error code */
+ schedule_timeout(MAX_SCHEDULE_TIMEOUT);
+ }
+ remove_wait_queue(&core_data->trace_sleep, &wait);
+
+ if (core_data->host_read_ptr != core_data->dsp_write_ptr)
+ return true;
+
+ return false;
+}
+
+static ssize_t sof_ipc4_mtrace_dfs_read(struct file *file, char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct sof_mtrace_core_data *core_data = file->private_data;
+ u32 log_buffer_offset, log_buffer_size, read_ptr, write_ptr;
+ struct snd_sof_dev *sdev = core_data->sdev;
+ struct sof_mtrace_priv *priv = sdev->fw_trace_data;
+ void *log_buffer = core_data->log_buffer;
+ loff_t lpos = *ppos;
+ u32 avail;
+ int ret;
+
+ /* check pos and count */
+ if (lpos < 0)
+ return -EINVAL;
+ if (!count || count < sizeof(avail))
+ return 0;
+
+ /* get available count based on current host offset */
+ if (!sof_wait_mtrace_avail(core_data)) {
+ /* No data available */
+ avail = 0;
+ if (copy_to_user(buffer, &avail, sizeof(avail)))
+ return -EFAULT;
+
+ return 0;
+ }
+
+ if (core_data->slot_offset == SOF_IPC4_INVALID_SLOT_OFFSET)
+ return 0;
+
+ /* The log data buffer starts after the two pointer in the slot */
+ log_buffer_offset = core_data->slot_offset + (sizeof(u32) * 2);
+ /* The log data size excludes the pointers */
+ log_buffer_size = SOF_IPC4_DEBUG_SLOT_SIZE - (sizeof(u32) * 2);
+
+ read_ptr = core_data->host_read_ptr;
+ write_ptr = core_data->dsp_write_ptr;
+
+ if (read_ptr < write_ptr)
+ avail = write_ptr - read_ptr;
+ else
+ avail = log_buffer_size - read_ptr + write_ptr;
+
+ if (!avail)
+ return 0;
+
+ if (avail > log_buffer_size)
+ avail = log_buffer_size;
+
+ /* Need space for the initial u32 of the avail */
+ if (avail > count - sizeof(avail))
+ avail = count - sizeof(avail);
+
+ if (sof_debug_check_flag(SOF_DBG_PRINT_DMA_POSITION_UPDATE_LOGS))
+ dev_dbg(sdev->dev,
+ "core%d, host read: %#x, dsp write: %#x, avail: %#x\n",
+ core_data->id, read_ptr, write_ptr, avail);
+
+ if (read_ptr < write_ptr) {
+ /* Read data between read pointer and write pointer */
+ sof_mailbox_read(sdev, log_buffer_offset + read_ptr, log_buffer, avail);
+ } else {
+ /* read from read pointer to end of the slot */
+ sof_mailbox_read(sdev, log_buffer_offset + read_ptr, log_buffer,
+ avail - write_ptr);
+ /* read from slot start to write pointer */
+ if (write_ptr)
+ sof_mailbox_read(sdev, log_buffer_offset,
+ (u8 *)(log_buffer) + avail - write_ptr,
+ write_ptr);
+ }
+
+ /* first write the number of bytes we have gathered */
+ ret = copy_to_user(buffer, &avail, sizeof(avail));
+ if (ret)
+ return -EFAULT;
+
+ /* Followed by the data itself */
+ ret = copy_to_user(buffer + sizeof(avail), log_buffer, avail);
+ if (ret)
+ return -EFAULT;
+
+ /* Update the host_read_ptr in the slot for this core */
+ read_ptr += avail;
+ if (read_ptr >= log_buffer_size)
+ read_ptr -= log_buffer_size;
+ sof_mailbox_write(sdev, core_data->slot_offset, &read_ptr, sizeof(read_ptr));
+
+ /* Only update the host_read_ptr if mtrace is enabled */
+ if (priv->mtrace_state != SOF_MTRACE_DISABLED)
+ core_data->host_read_ptr = read_ptr;
+
+ /*
+ * Ask for a new buffer from user space for the next chunk, not
+ * streaming due to the heading number of bytes value.
+ */
+ *ppos += count;
+
+ return count;
+}
+
+static int sof_ipc4_mtrace_dfs_release(struct inode *inode, struct file *file)
+{
+ struct sof_mtrace_core_data *core_data = inode->i_private;
+
+ debugfs_file_put(file->f_path.dentry);
+
+ mutex_lock(&core_data->buffer_lock);
+ kfree(core_data->log_buffer);
+ core_data->log_buffer = NULL;
+ mutex_unlock(&core_data->buffer_lock);
+
+ return 0;
+}
+
+static const struct file_operations sof_dfs_mtrace_fops = {
+ .open = sof_ipc4_mtrace_dfs_open,
+ .read = sof_ipc4_mtrace_dfs_read,
+ .llseek = default_llseek,
+ .release = sof_ipc4_mtrace_dfs_release,
+
+ .owner = THIS_MODULE,
+};
+
+static ssize_t sof_ipc4_priority_mask_dfs_read(struct file *file, char __user *to,
+ size_t count, loff_t *ppos)
+{
+ struct sof_mtrace_priv *priv = file->private_data;
+ int i, ret, offset, remaining;
+ char *buf;
+
+ /*
+ * one entry (14 char + new line = 15):
+ * " 0: 000001ef"
+ *
+ * 16 * 15 + 1 = 241
+ */
+ buf = kzalloc(241, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ for (i = 0; i < MAX_ALLOWED_LIBRARIES; i++) {
+ offset = strlen(buf);
+ remaining = 241 - offset;
+ snprintf(buf + offset, remaining, "%2d: 0x%08x\n", i,
+ priv->state_info.logs_priorities_mask[i]);
+ }
+
+ ret = simple_read_from_buffer(to, count, ppos, buf, strlen(buf));
+
+ kfree(buf);
+ return ret;
+}
+
+static ssize_t sof_ipc4_priority_mask_dfs_write(struct file *file,
+ const char __user *from,
+ size_t count, loff_t *ppos)
+{
+ struct sof_mtrace_priv *priv = file->private_data;
+ unsigned int id;
+ char *buf;
+ u32 mask;
+ int ret;
+
+ /*
+ * To update Nth mask entry, write:
+ * "N,0x1234" or "N,1234" to the debugfs file
+ * The mask will be interpreted as hexadecimal number
+ */
+ buf = memdup_user_nul(from, count);
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ ret = sscanf(buf, "%u,0x%x", &id, &mask);
+ if (ret != 2) {
+ ret = sscanf(buf, "%u,%x", &id, &mask);
+ if (ret != 2) {
+ ret = -EINVAL;
+ goto out;
+ }
+ }
+
+ if (id >= MAX_ALLOWED_LIBRARIES) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ priv->state_info.logs_priorities_mask[id] = mask;
+ ret = count;
+
+out:
+ kfree(buf);
+ return ret;
+}
+
+static const struct file_operations sof_dfs_priority_mask_fops = {
+ .open = simple_open,
+ .read = sof_ipc4_priority_mask_dfs_read,
+ .write = sof_ipc4_priority_mask_dfs_write,
+ .llseek = default_llseek,
+
+ .owner = THIS_MODULE,
+};
+
+static int mtrace_debugfs_create(struct snd_sof_dev *sdev)
+{
+ struct sof_mtrace_priv *priv = sdev->fw_trace_data;
+ struct dentry *dfs_root;
+ char dfs_name[100];
+ int i;
+
+ dfs_root = debugfs_create_dir("mtrace", sdev->debugfs_root);
+ if (IS_ERR_OR_NULL(dfs_root))
+ return 0;
+
+ /* Create files for the logging parameters */
+ debugfs_create_u32("aging_timer_period", 0644, dfs_root,
+ &priv->state_info.aging_timer_period);
+ debugfs_create_u32("fifo_full_timer_period", 0644, dfs_root,
+ &priv->state_info.fifo_full_timer_period);
+ debugfs_create_file("logs_priorities_mask", 0644, dfs_root, priv,
+ &sof_dfs_priority_mask_fops);
+
+ /* Separate log files per core */
+ for (i = 0; i < sdev->num_cores; i++) {
+ snprintf(dfs_name, sizeof(dfs_name), "core%d", i);
+ debugfs_create_file(dfs_name, 0444, dfs_root, &priv->cores[i],
+ &sof_dfs_mtrace_fops);
+ }
+
+ return 0;
+}
+
+static int ipc4_mtrace_enable(struct snd_sof_dev *sdev)
+{
+ struct sof_mtrace_priv *priv = sdev->fw_trace_data;
+ const struct sof_ipc_ops *iops = sdev->ipc->ops;
+ struct sof_ipc4_msg msg;
+ u64 system_time;
+ ktime_t kt;
+ int ret;
+
+ if (priv->mtrace_state != SOF_MTRACE_DISABLED)
+ return 0;
+
+ msg.primary = SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG);
+ msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ msg.primary |= SOF_IPC4_MOD_ID(SOF_IPC4_MOD_INIT_BASEFW_MOD_ID);
+ msg.primary |= SOF_IPC4_MOD_INSTANCE(SOF_IPC4_MOD_INIT_BASEFW_INSTANCE_ID);
+ msg.extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(SOF_IPC4_FW_PARAM_SYSTEM_TIME);
+
+ /* The system time is in usec, UTC, epoch is 1601-01-01 00:00:00 */
+ kt = ktime_add_us(ktime_get_real(), FW_EPOCH_DELTA * USEC_PER_SEC);
+ system_time = ktime_to_us(kt);
+ msg.data_size = sizeof(system_time);
+ msg.data_ptr = &system_time;
+ ret = iops->set_get_data(sdev, &msg, msg.data_size, true);
+ if (ret)
+ return ret;
+
+ msg.extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(SOF_IPC4_FW_PARAM_ENABLE_LOGS);
+
+ priv->state_info.enable = 1;
+
+ msg.data_size = sizeof(priv->state_info);
+ msg.data_ptr = &priv->state_info;
+
+ priv->mtrace_state = SOF_MTRACE_INITIALIZING;
+ ret = iops->set_get_data(sdev, &msg, msg.data_size, true);
+ if (ret) {
+ priv->mtrace_state = SOF_MTRACE_DISABLED;
+ return ret;
+ }
+
+ priv->mtrace_state = SOF_MTRACE_ENABLED;
+
+ return 0;
+}
+
+static void ipc4_mtrace_disable(struct snd_sof_dev *sdev)
+{
+ struct sof_mtrace_priv *priv = sdev->fw_trace_data;
+ const struct sof_ipc_ops *iops = sdev->ipc->ops;
+ struct sof_ipc4_msg msg;
+ int i;
+
+ if (priv->mtrace_state == SOF_MTRACE_DISABLED)
+ return;
+
+ msg.primary = SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG);
+ msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ msg.primary |= SOF_IPC4_MOD_ID(SOF_IPC4_MOD_INIT_BASEFW_MOD_ID);
+ msg.primary |= SOF_IPC4_MOD_INSTANCE(SOF_IPC4_MOD_INIT_BASEFW_INSTANCE_ID);
+ msg.extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(SOF_IPC4_FW_PARAM_ENABLE_LOGS);
+
+ priv->state_info.enable = 0;
+
+ msg.data_size = sizeof(priv->state_info);
+ msg.data_ptr = &priv->state_info;
+ iops->set_get_data(sdev, &msg, msg.data_size, true);
+
+ priv->mtrace_state = SOF_MTRACE_DISABLED;
+
+ for (i = 0; i < sdev->num_cores; i++) {
+ struct sof_mtrace_core_data *core_data = &priv->cores[i];
+
+ core_data->host_read_ptr = 0;
+ core_data->dsp_write_ptr = 0;
+ wake_up(&core_data->trace_sleep);
+ }
+}
+
+/*
+ * Each DSP core logs to a dedicated slot.
+ * Parse the slot descriptors at debug_box offset to find the debug log slots
+ * and map them to cores.
+ * There are 15 slots and therefore 15 descriptors to check (MAX_MTRACE_SLOTS)
+ */
+static void sof_mtrace_find_core_slots(struct snd_sof_dev *sdev)
+{
+ struct sof_mtrace_priv *priv = sdev->fw_trace_data;
+ struct sof_mtrace_core_data *core_data;
+ u32 slot_desc_type_offset, type, core;
+ int i;
+
+ for (i = 0; i < SOF_IPC4_MAX_DEBUG_SLOTS; i++) {
+ /* The type is the second u32 in the slot descriptor */
+ slot_desc_type_offset = sdev->debug_box.offset;
+ slot_desc_type_offset += SOF_IPC4_DEBUG_DESCRIPTOR_SIZE * i + sizeof(u32);
+ sof_mailbox_read(sdev, slot_desc_type_offset, &type, sizeof(type));
+
+ if ((type & SOF_MTRACE_SLOT_TYPE_MASK) == SOF_IPC4_DEBUG_SLOT_DEBUG_LOG) {
+ core = type & SOF_MTRACE_SLOT_CORE_MASK;
+
+ if (core >= sdev->num_cores) {
+ dev_dbg(sdev->dev, "core%u is invalid for slot%d\n",
+ core, i);
+ continue;
+ }
+
+ core_data = &priv->cores[core];
+ /*
+ * The area reserved for descriptors have the same size
+ * as a slot.
+ * In other words: slot0 starts at
+ * debug_box + SOF_MTRACE_SLOT_SIZE offset
+ */
+ core_data->slot_offset = sdev->debug_box.offset;
+ core_data->slot_offset += SOF_IPC4_DEBUG_SLOT_SIZE * (i + 1);
+ dev_dbg(sdev->dev, "slot%d is used for core%u\n", i, core);
+ if (core_data->delayed_pos_update) {
+ sof_ipc4_mtrace_update_pos(sdev, core);
+ core_data->delayed_pos_update = false;
+ }
+ } else if (type) {
+ dev_dbg(sdev->dev, "slot%d is not a log slot (%#x)\n", i, type);
+ }
+ }
+}
+
+static int ipc4_mtrace_init(struct snd_sof_dev *sdev)
+{
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+ struct sof_mtrace_priv *priv;
+ int i, ret;
+
+ if (sdev->fw_trace_data) {
+ dev_err(sdev->dev, "fw_trace_data has been already allocated\n");
+ return -EBUSY;
+ }
+
+ if (!ipc4_data->mtrace_log_bytes ||
+ ipc4_data->mtrace_type != SOF_IPC4_MTRACE_INTEL_CAVS_2) {
+ sdev->fw_trace_is_supported = false;
+ return 0;
+ }
+
+ priv = devm_kzalloc(sdev->dev, struct_size(priv, cores, sdev->num_cores),
+ GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ sdev->fw_trace_data = priv;
+
+ /* Set initial values for mtrace parameters */
+ priv->state_info.aging_timer_period = DEFAULT_AGING_TIMER_PERIOD_MS;
+ priv->state_info.fifo_full_timer_period = DEFAULT_FIFO_FULL_TIMER_PERIOD_MS;
+ /* Only enable basefw logs initially (index 0 is always basefw) */
+ priv->state_info.logs_priorities_mask[0] = DEFAULT_LOGS_PRIORITIES_MASK;
+
+ for (i = 0; i < sdev->num_cores; i++) {
+ struct sof_mtrace_core_data *core_data = &priv->cores[i];
+
+ init_waitqueue_head(&core_data->trace_sleep);
+ mutex_init(&core_data->buffer_lock);
+ core_data->sdev = sdev;
+ core_data->id = i;
+ }
+
+ ret = ipc4_mtrace_enable(sdev);
+ if (ret) {
+ /*
+ * Mark firmware tracing as not supported and return 0 to not
+ * block the whole audio stack
+ */
+ sdev->fw_trace_is_supported = false;
+ dev_dbg(sdev->dev, "initialization failed, fw tracing is disabled\n");
+ return 0;
+ }
+
+ sof_mtrace_find_core_slots(sdev);
+
+ ret = mtrace_debugfs_create(sdev);
+ if (ret)
+ ipc4_mtrace_disable(sdev);
+
+ return ret;
+}
+
+static void ipc4_mtrace_free(struct snd_sof_dev *sdev)
+{
+ ipc4_mtrace_disable(sdev);
+}
+
+static int sof_ipc4_mtrace_update_pos_all_cores(struct snd_sof_dev *sdev)
+{
+ int i;
+
+ for (i = 0; i < sdev->num_cores; i++)
+ sof_ipc4_mtrace_update_pos(sdev, i);
+
+ return 0;
+}
+
+int sof_ipc4_mtrace_update_pos(struct snd_sof_dev *sdev, int core)
+{
+ struct sof_mtrace_priv *priv = sdev->fw_trace_data;
+ struct sof_mtrace_core_data *core_data;
+
+ if (!sdev->fw_trace_is_supported ||
+ priv->mtrace_state == SOF_MTRACE_DISABLED)
+ return 0;
+
+ if (core >= sdev->num_cores)
+ return -EINVAL;
+
+ core_data = &priv->cores[core];
+
+ if (core_data->slot_offset == SOF_IPC4_INVALID_SLOT_OFFSET) {
+ core_data->delayed_pos_update = true;
+ return 0;
+ }
+
+ /* Read out the dsp_write_ptr from the slot for this core */
+ sof_mailbox_read(sdev, core_data->slot_offset + sizeof(u32),
+ &core_data->dsp_write_ptr, 4);
+ core_data->dsp_write_ptr -= core_data->dsp_write_ptr % 4;
+
+ if (sof_debug_check_flag(SOF_DBG_PRINT_DMA_POSITION_UPDATE_LOGS))
+ dev_dbg(sdev->dev, "core%d, host read: %#x, dsp write: %#x",
+ core, core_data->host_read_ptr, core_data->dsp_write_ptr);
+
+ wake_up(&core_data->trace_sleep);
+
+ return 0;
+}
+
+static void ipc4_mtrace_fw_crashed(struct snd_sof_dev *sdev)
+{
+ /*
+ * The DSP might not be able to send SOF_IPC4_NOTIFY_LOG_BUFFER_STATUS
+ * messages anymore, so check the log buffer status on all
+ * cores and process any pending messages.
+ */
+ sof_ipc4_mtrace_update_pos_all_cores(sdev);
+}
+
+static int ipc4_mtrace_resume(struct snd_sof_dev *sdev)
+{
+ return ipc4_mtrace_enable(sdev);
+}
+
+static void ipc4_mtrace_suspend(struct snd_sof_dev *sdev, pm_message_t pm_state)
+{
+ ipc4_mtrace_disable(sdev);
+}
+
+const struct sof_ipc_fw_tracing_ops ipc4_mtrace_ops = {
+ .init = ipc4_mtrace_init,
+ .free = ipc4_mtrace_free,
+ .fw_crashed = ipc4_mtrace_fw_crashed,
+ .suspend = ipc4_mtrace_suspend,
+ .resume = ipc4_mtrace_resume,
+};
diff --git a/sound/soc/sof/ipc4-pcm.c b/sound/soc/sof/ipc4-pcm.c
new file mode 100644
index 000000000000..0f332c8cdbe6
--- /dev/null
+++ b/sound/soc/sof/ipc4-pcm.c
@@ -0,0 +1,947 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2022 Intel Corporation. All rights reserved.
+//
+
+#include <sound/pcm_params.h>
+#include <sound/sof/ipc4/header.h>
+#include "sof-audio.h"
+#include "sof-priv.h"
+#include "ops.h"
+#include "ipc4-priv.h"
+#include "ipc4-topology.h"
+#include "ipc4-fw-reg.h"
+
+static int sof_ipc4_set_multi_pipeline_state(struct snd_sof_dev *sdev, u32 state,
+ struct ipc4_pipeline_set_state_data *trigger_list)
+{
+ struct sof_ipc4_msg msg = {{ 0 }};
+ u32 primary, ipc_size;
+
+ /* trigger a single pipeline */
+ if (trigger_list->count == 1)
+ return sof_ipc4_set_pipeline_state(sdev, trigger_list->pipeline_instance_ids[0],
+ state);
+
+ primary = state;
+ primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_SET_PIPELINE_STATE);
+ primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG);
+ msg.primary = primary;
+
+ /* trigger multiple pipelines with a single IPC */
+ msg.extension = SOF_IPC4_GLB_PIPE_STATE_EXT_MULTI;
+
+ /* ipc_size includes the count and the pipeline IDs for the number of pipelines */
+ ipc_size = sizeof(u32) * (trigger_list->count + 1);
+ msg.data_size = ipc_size;
+ msg.data_ptr = trigger_list;
+
+ return sof_ipc_tx_message_no_reply(sdev->ipc, &msg, ipc_size);
+}
+
+int sof_ipc4_set_pipeline_state(struct snd_sof_dev *sdev, u32 instance_id, u32 state)
+{
+ struct sof_ipc4_msg msg = {{ 0 }};
+ u32 primary;
+
+ dev_dbg(sdev->dev, "ipc4 set pipeline instance %d state %d", instance_id, state);
+
+ primary = state;
+ primary |= SOF_IPC4_GLB_PIPE_STATE_ID(instance_id);
+ primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_SET_PIPELINE_STATE);
+ primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG);
+
+ msg.primary = primary;
+
+ return sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0);
+}
+EXPORT_SYMBOL(sof_ipc4_set_pipeline_state);
+
+static void sof_ipc4_add_pipeline_by_priority(struct ipc4_pipeline_set_state_data *trigger_list,
+ struct snd_sof_widget *pipe_widget,
+ s8 *pipe_priority, bool ascend)
+{
+ struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
+ int i, j;
+
+ for (i = 0; i < trigger_list->count; i++) {
+ /* add pipeline from low priority to high */
+ if (ascend && pipeline->priority < pipe_priority[i])
+ break;
+ /* add pipeline from high priority to low */
+ else if (!ascend && pipeline->priority > pipe_priority[i])
+ break;
+ }
+
+ for (j = trigger_list->count - 1; j >= i; j--) {
+ trigger_list->pipeline_instance_ids[j + 1] = trigger_list->pipeline_instance_ids[j];
+ pipe_priority[j + 1] = pipe_priority[j];
+ }
+
+ trigger_list->pipeline_instance_ids[i] = pipe_widget->instance_id;
+ trigger_list->count++;
+ pipe_priority[i] = pipeline->priority;
+}
+
+static void
+sof_ipc4_add_pipeline_to_trigger_list(struct snd_sof_dev *sdev, int state,
+ struct snd_sof_pipeline *spipe,
+ struct ipc4_pipeline_set_state_data *trigger_list,
+ s8 *pipe_priority)
+{
+ struct snd_sof_widget *pipe_widget = spipe->pipe_widget;
+ struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
+
+ if (pipeline->skip_during_fe_trigger && state != SOF_IPC4_PIPE_RESET)
+ return;
+
+ switch (state) {
+ case SOF_IPC4_PIPE_RUNNING:
+ /*
+ * Trigger pipeline if all PCMs containing it are paused or if it is RUNNING
+ * for the first time
+ */
+ if (spipe->started_count == spipe->paused_count)
+ sof_ipc4_add_pipeline_by_priority(trigger_list, pipe_widget, pipe_priority,
+ false);
+ break;
+ case SOF_IPC4_PIPE_RESET:
+ /* RESET if the pipeline is neither running nor paused */
+ if (!spipe->started_count && !spipe->paused_count)
+ sof_ipc4_add_pipeline_by_priority(trigger_list, pipe_widget, pipe_priority,
+ true);
+ break;
+ case SOF_IPC4_PIPE_PAUSED:
+ /* Pause the pipeline only when its started_count is 1 more than paused_count */
+ if (spipe->paused_count == (spipe->started_count - 1))
+ sof_ipc4_add_pipeline_by_priority(trigger_list, pipe_widget, pipe_priority,
+ true);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+sof_ipc4_update_pipeline_state(struct snd_sof_dev *sdev, int state, int cmd,
+ struct snd_sof_pipeline *spipe,
+ struct ipc4_pipeline_set_state_data *trigger_list)
+{
+ struct snd_sof_widget *pipe_widget = spipe->pipe_widget;
+ struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
+ int i;
+
+ if (pipeline->skip_during_fe_trigger && state != SOF_IPC4_PIPE_RESET)
+ return;
+
+ /* set state for pipeline if it was just triggered */
+ for (i = 0; i < trigger_list->count; i++) {
+ if (trigger_list->pipeline_instance_ids[i] == pipe_widget->instance_id) {
+ pipeline->state = state;
+ break;
+ }
+ }
+
+ switch (state) {
+ case SOF_IPC4_PIPE_PAUSED:
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ /*
+ * increment paused_count if the PAUSED is the final state during
+ * the PAUSE trigger
+ */
+ spipe->paused_count++;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ /*
+ * decrement started_count if PAUSED is the final state during the
+ * STOP trigger
+ */
+ spipe->started_count--;
+ break;
+ default:
+ break;
+ }
+ break;
+ case SOF_IPC4_PIPE_RUNNING:
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ /* decrement paused_count for RELEASE */
+ spipe->paused_count--;
+ break;
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ /* increment started_count for START/RESUME */
+ spipe->started_count++;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * The picture below represents the pipeline state machine wrt PCM actions corresponding to the
+ * triggers and ioctls
+ * +---------------+
+ * | |
+ * | INIT |
+ * | |
+ * +-------+-------+
+ * |
+ * |
+ * | START
+ * |
+ * |
+ * +----------------+ +------v-------+ +-------------+
+ * | | START | | HW_FREE | |
+ * | RUNNING <-------------+ PAUSED +--------------> + RESET |
+ * | | PAUSE | | | |
+ * +------+---------+ RELEASE +---------+----+ +-------------+
+ * | ^
+ * | |
+ * | |
+ * | |
+ * | PAUSE |
+ * +---------------------------------+
+ * STOP/SUSPEND
+ *
+ * Note that during system suspend, the suspend trigger is followed by a hw_free in
+ * sof_pcm_trigger(). So, the final state during suspend would be RESET.
+ * Also, since the SOF driver doesn't support full resume, streams would be restarted with the
+ * prepare ioctl before the START trigger.
+ */
+
+/*
+ * Chained DMA is a special case where there is no processing on
+ * DSP. The samples are just moved over by host side DMA to a single
+ * buffer on DSP and directly from there to link DMA. However, the
+ * model on SOF driver has two notional pipelines, one at host DAI,
+ * and another at link DAI. They both shall have the use_chain_dma
+ * attribute.
+ */
+
+static int sof_ipc4_chain_dma_trigger(struct snd_sof_dev *sdev,
+ int direction,
+ struct snd_sof_pcm_stream_pipeline_list *pipeline_list,
+ int state, int cmd)
+{
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+ bool allocate, enable, set_fifo_size;
+ struct sof_ipc4_msg msg = {{ 0 }};
+ int i;
+
+ switch (state) {
+ case SOF_IPC4_PIPE_RUNNING: /* Allocate and start chained dma */
+ allocate = true;
+ enable = true;
+ /*
+ * SOF assumes creation of a new stream from the presence of fifo_size
+ * in the message, so we must leave it out in pause release case.
+ */
+ if (cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE)
+ set_fifo_size = false;
+ else
+ set_fifo_size = true;
+ break;
+ case SOF_IPC4_PIPE_PAUSED: /* Disable chained DMA. */
+ allocate = true;
+ enable = false;
+ set_fifo_size = false;
+ break;
+ case SOF_IPC4_PIPE_RESET: /* Disable and free chained DMA. */
+ allocate = false;
+ enable = false;
+ set_fifo_size = false;
+ break;
+ default:
+ dev_err(sdev->dev, "Unexpected state %d", state);
+ return -EINVAL;
+ }
+
+ msg.primary = SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_CHAIN_DMA);
+ msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ msg.primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG);
+
+ /*
+ * To set-up the DMA chain, the host DMA ID and SCS setting
+ * are retrieved from the host pipeline configuration. Likewise
+ * the link DMA ID and fifo_size are retrieved from the link
+ * pipeline configuration.
+ */
+ for (i = 0; i < pipeline_list->count; i++) {
+ struct snd_sof_pipeline *spipe = pipeline_list->pipelines[i];
+ struct snd_sof_widget *pipe_widget = spipe->pipe_widget;
+ struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
+
+ if (!pipeline->use_chain_dma) {
+ dev_err(sdev->dev,
+ "All pipelines in chained DMA stream should have use_chain_dma attribute set.");
+ return -EINVAL;
+ }
+
+ msg.primary |= pipeline->msg.primary;
+
+ /* Add fifo_size (actually DMA buffer size) field to the message */
+ if (set_fifo_size)
+ msg.extension |= pipeline->msg.extension;
+ }
+
+ if (direction == SNDRV_PCM_STREAM_CAPTURE) {
+ /*
+ * For ChainDMA the DMA ids are unique with the following mapping:
+ * playback: 0 - (num_playback_streams - 1)
+ * capture: num_playback_streams - (num_playback_streams +
+ * num_capture_streams - 1)
+ *
+ * Add the num_playback_streams offset to the DMA ids stored in
+ * msg.primary in case capture
+ */
+ msg.primary += SOF_IPC4_GLB_CHAIN_DMA_HOST_ID(ipc4_data->num_playback_streams);
+ msg.primary += SOF_IPC4_GLB_CHAIN_DMA_LINK_ID(ipc4_data->num_playback_streams);
+ }
+
+ if (allocate)
+ msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_ALLOCATE_MASK;
+
+ if (enable)
+ msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_ENABLE_MASK;
+
+ return sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0);
+}
+
+static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream, int state, int cmd)
+{
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_sof_pcm_stream_pipeline_list *pipeline_list;
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+ struct ipc4_pipeline_set_state_data *trigger_list;
+ struct snd_sof_widget *pipe_widget;
+ struct sof_ipc4_pipeline *pipeline;
+ struct snd_sof_pipeline *spipe;
+ struct snd_sof_pcm *spcm;
+ u8 *pipe_priority;
+ int ret;
+ int i;
+
+ dev_dbg(sdev->dev, "trigger cmd: %d state: %d\n", cmd, state);
+
+ spcm = snd_sof_find_spcm_dai(component, rtd);
+ if (!spcm)
+ return -EINVAL;
+
+ pipeline_list = &spcm->stream[substream->stream].pipeline_list;
+
+ /* nothing to trigger if the list is empty */
+ if (!pipeline_list->pipelines || !pipeline_list->count)
+ return 0;
+
+ spipe = pipeline_list->pipelines[0];
+ pipe_widget = spipe->pipe_widget;
+ pipeline = pipe_widget->private;
+
+ /*
+ * If use_chain_dma attribute is set we proceed to chained DMA
+ * trigger function that handles the rest for the substream.
+ */
+ if (pipeline->use_chain_dma)
+ return sof_ipc4_chain_dma_trigger(sdev, substream->stream,
+ pipeline_list, state, cmd);
+
+ /* allocate memory for the pipeline data */
+ trigger_list = kzalloc(struct_size(trigger_list, pipeline_instance_ids,
+ pipeline_list->count), GFP_KERNEL);
+ if (!trigger_list)
+ return -ENOMEM;
+
+ pipe_priority = kzalloc(pipeline_list->count, GFP_KERNEL);
+ if (!pipe_priority) {
+ kfree(trigger_list);
+ return -ENOMEM;
+ }
+
+ mutex_lock(&ipc4_data->pipeline_state_mutex);
+
+ /*
+ * IPC4 requires pipelines to be triggered in order starting at the sink and
+ * walking all the way to the source. So traverse the pipeline_list in the order
+ * sink->source when starting PCM's and in the reverse order to pause/stop PCM's.
+ * Skip the pipelines that have their skip_during_fe_trigger flag set. If there is a fork
+ * in the pipeline, the order of triggering between the left/right paths will be
+ * indeterministic. But the sink->source trigger order sink->source would still be
+ * guaranteed for each fork independently.
+ */
+ if (state == SOF_IPC4_PIPE_RUNNING || state == SOF_IPC4_PIPE_RESET)
+ for (i = pipeline_list->count - 1; i >= 0; i--) {
+ spipe = pipeline_list->pipelines[i];
+ sof_ipc4_add_pipeline_to_trigger_list(sdev, state, spipe, trigger_list,
+ pipe_priority);
+ }
+ else
+ for (i = 0; i < pipeline_list->count; i++) {
+ spipe = pipeline_list->pipelines[i];
+ sof_ipc4_add_pipeline_to_trigger_list(sdev, state, spipe, trigger_list,
+ pipe_priority);
+ }
+
+ /* return if all pipelines are in the requested state already */
+ if (!trigger_list->count) {
+ ret = 0;
+ goto free;
+ }
+
+ /* no need to pause before reset or before pause release */
+ if (state == SOF_IPC4_PIPE_RESET || cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE)
+ goto skip_pause_transition;
+
+ /*
+ * set paused state for pipelines if the final state is PAUSED or when the pipeline
+ * is set to RUNNING for the first time after the PCM is started.
+ */
+ ret = sof_ipc4_set_multi_pipeline_state(sdev, SOF_IPC4_PIPE_PAUSED, trigger_list);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to pause all pipelines\n");
+ goto free;
+ }
+
+ /* update PAUSED state for all pipelines just triggered */
+ for (i = 0; i < pipeline_list->count ; i++) {
+ spipe = pipeline_list->pipelines[i];
+ sof_ipc4_update_pipeline_state(sdev, SOF_IPC4_PIPE_PAUSED, cmd, spipe,
+ trigger_list);
+ }
+
+ /* return if this is the final state */
+ if (state == SOF_IPC4_PIPE_PAUSED)
+ goto free;
+skip_pause_transition:
+ /* else set the RUNNING/RESET state in the DSP */
+ ret = sof_ipc4_set_multi_pipeline_state(sdev, state, trigger_list);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to set final state %d for all pipelines\n", state);
+ /*
+ * workaround: if the firmware is crashed while setting the
+ * pipelines to reset state we must ignore the error code and
+ * reset it to 0.
+ * Since the firmware is crashed we will not send IPC messages
+ * and we are going to see errors printed, but the state of the
+ * widgets will be correct for the next boot.
+ */
+ if (sdev->fw_state != SOF_FW_CRASHED || state != SOF_IPC4_PIPE_RESET)
+ goto free;
+
+ ret = 0;
+ }
+
+ /* update RUNNING/RESET state for all pipelines that were just triggered */
+ for (i = 0; i < pipeline_list->count; i++) {
+ spipe = pipeline_list->pipelines[i];
+ sof_ipc4_update_pipeline_state(sdev, state, cmd, spipe, trigger_list);
+ }
+
+free:
+ mutex_unlock(&ipc4_data->pipeline_state_mutex);
+ kfree(trigger_list);
+ kfree(pipe_priority);
+ return ret;
+}
+
+static int sof_ipc4_pcm_trigger(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream, int cmd)
+{
+ int state;
+
+ /* determine the pipeline state */
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ state = SOF_IPC4_PIPE_PAUSED;
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_START:
+ state = SOF_IPC4_PIPE_RUNNING;
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ state = SOF_IPC4_PIPE_PAUSED;
+ break;
+ default:
+ dev_err(component->dev, "%s: unhandled trigger cmd %d\n", __func__, cmd);
+ return -EINVAL;
+ }
+
+ /* set the pipeline state */
+ return sof_ipc4_trigger_pipelines(component, substream, state, cmd);
+}
+
+static int sof_ipc4_pcm_hw_free(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ /* command is not relevant with RESET, so just pass 0 */
+ return sof_ipc4_trigger_pipelines(component, substream, SOF_IPC4_PIPE_RESET, 0);
+}
+
+static void ipc4_ssp_dai_config_pcm_params_match(struct snd_sof_dev *sdev, const char *link_name,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_sof_dai_link *slink;
+ struct snd_sof_dai *dai;
+ bool dai_link_found = false;
+ int i;
+
+ list_for_each_entry(slink, &sdev->dai_link_list, list) {
+ if (!strcmp(slink->link->name, link_name)) {
+ dai_link_found = true;
+ break;
+ }
+ }
+
+ if (!dai_link_found)
+ return;
+
+ for (i = 0; i < slink->num_hw_configs; i++) {
+ struct snd_soc_tplg_hw_config *hw_config = &slink->hw_configs[i];
+
+ if (params_rate(params) == le32_to_cpu(hw_config->fsync_rate)) {
+ /* set current config for all DAI's with matching name */
+ list_for_each_entry(dai, &sdev->dai_list, list)
+ if (!strcmp(slink->link->name, dai->name))
+ dai->current_config = le32_to_cpu(hw_config->id);
+ break;
+ }
+ }
+}
+
+/*
+ * Fixup DAI link parameters for sampling rate based on
+ * DAI copier configuration.
+ */
+static int sof_ipc4_pcm_dai_link_fixup_rate(struct snd_sof_dev *sdev,
+ struct snd_pcm_hw_params *params,
+ struct sof_ipc4_copier *ipc4_copier)
+{
+ struct sof_ipc4_pin_format *pin_fmts = ipc4_copier->available_fmt.input_pin_fmts;
+ struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ int num_input_formats = ipc4_copier->available_fmt.num_input_formats;
+ unsigned int fe_rate = params_rate(params);
+ bool fe_be_rate_match = false;
+ bool single_be_rate = true;
+ unsigned int be_rate;
+ int i;
+
+ /*
+ * Copier does not change sampling rate, so we
+ * need to only consider the input pin information.
+ */
+ for (i = 0; i < num_input_formats; i++) {
+ unsigned int val = pin_fmts[i].audio_fmt.sampling_frequency;
+
+ if (i == 0)
+ be_rate = val;
+ else if (val != be_rate)
+ single_be_rate = false;
+
+ if (val == fe_rate) {
+ fe_be_rate_match = true;
+ break;
+ }
+ }
+
+ /*
+ * If rate is different than FE rate, topology must
+ * contain an SRC. But we do require topology to
+ * define a single rate in the DAI copier config in
+ * this case (FE rate may be variable).
+ */
+ if (!fe_be_rate_match) {
+ if (!single_be_rate) {
+ dev_err(sdev->dev, "Unable to select sampling rate for DAI link\n");
+ return -EINVAL;
+ }
+
+ rate->min = be_rate;
+ rate->max = rate->min;
+ }
+
+ return 0;
+}
+
+static int sof_ipc4_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME);
+ struct snd_sof_dai *dai = snd_sof_find_dai(component, rtd->dai_link->name);
+ struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct sof_ipc4_audio_format *ipc4_fmt;
+ struct sof_ipc4_copier *ipc4_copier;
+ bool single_fmt = false;
+ u32 valid_bits = 0;
+ int dir, ret;
+
+ if (!dai) {
+ dev_err(component->dev, "%s: No DAI found with name %s\n", __func__,
+ rtd->dai_link->name);
+ return -EINVAL;
+ }
+
+ ipc4_copier = dai->private;
+ if (!ipc4_copier) {
+ dev_err(component->dev, "%s: No private data found for DAI %s\n",
+ __func__, rtd->dai_link->name);
+ return -EINVAL;
+ }
+
+ for_each_pcm_streams(dir) {
+ struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(cpu_dai, dir);
+
+ if (w) {
+ struct sof_ipc4_available_audio_format *available_fmt =
+ &ipc4_copier->available_fmt;
+ struct snd_sof_widget *swidget = w->dobj.private;
+ struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget;
+ struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
+
+ /* Chain DMA does not use copiers, so no fixup needed */
+ if (pipeline->use_chain_dma)
+ return 0;
+
+ if (dir == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (sof_ipc4_copier_is_single_format(sdev,
+ available_fmt->output_pin_fmts,
+ available_fmt->num_output_formats)) {
+ ipc4_fmt = &available_fmt->output_pin_fmts->audio_fmt;
+ single_fmt = true;
+ }
+ } else {
+ if (sof_ipc4_copier_is_single_format(sdev,
+ available_fmt->input_pin_fmts,
+ available_fmt->num_input_formats)) {
+ ipc4_fmt = &available_fmt->input_pin_fmts->audio_fmt;
+ single_fmt = true;
+ }
+ }
+ }
+ }
+
+ ret = sof_ipc4_pcm_dai_link_fixup_rate(sdev, params, ipc4_copier);
+ if (ret)
+ return ret;
+
+ if (single_fmt) {
+ snd_mask_none(fmt);
+ valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(ipc4_fmt->fmt_cfg);
+ dev_dbg(component->dev, "Set %s to %d bit format\n", dai->name, valid_bits);
+ }
+
+ /* Set format if it is specified */
+ switch (valid_bits) {
+ case 16:
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE);
+ break;
+ case 24:
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE);
+ break;
+ case 32:
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE);
+ break;
+ default:
+ break;
+ }
+
+ switch (ipc4_copier->dai_type) {
+ case SOF_DAI_INTEL_SSP:
+ ipc4_ssp_dai_config_pcm_params_match(sdev, (char *)rtd->dai_link->name, params);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static void sof_ipc4_pcm_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm)
+{
+ struct snd_sof_pcm_stream_pipeline_list *pipeline_list;
+ int stream;
+
+ for_each_pcm_streams(stream) {
+ pipeline_list = &spcm->stream[stream].pipeline_list;
+ kfree(pipeline_list->pipelines);
+ pipeline_list->pipelines = NULL;
+ kfree(spcm->stream[stream].private);
+ spcm->stream[stream].private = NULL;
+ }
+}
+
+static int sof_ipc4_pcm_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm)
+{
+ struct snd_sof_pcm_stream_pipeline_list *pipeline_list;
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+ struct sof_ipc4_timestamp_info *stream_info;
+ bool support_info = true;
+ u32 abi_version;
+ u32 abi_offset;
+ int stream;
+
+ abi_offset = offsetof(struct sof_ipc4_fw_registers, abi_ver);
+ sof_mailbox_read(sdev, sdev->fw_info_box.offset + abi_offset, &abi_version,
+ sizeof(abi_version));
+
+ if (abi_version < SOF_IPC4_FW_REGS_ABI_VER)
+ support_info = false;
+
+ for_each_pcm_streams(stream) {
+ pipeline_list = &spcm->stream[stream].pipeline_list;
+
+ /* allocate memory for max number of pipeline IDs */
+ pipeline_list->pipelines = kcalloc(ipc4_data->max_num_pipelines,
+ sizeof(struct snd_sof_widget *), GFP_KERNEL);
+ if (!pipeline_list->pipelines) {
+ sof_ipc4_pcm_free(sdev, spcm);
+ return -ENOMEM;
+ }
+
+ if (!support_info)
+ continue;
+
+ stream_info = kzalloc(sizeof(*stream_info), GFP_KERNEL);
+ if (!stream_info) {
+ sof_ipc4_pcm_free(sdev, spcm);
+ return -ENOMEM;
+ }
+
+ spcm->stream[stream].private = stream_info;
+ }
+
+ return 0;
+}
+
+static void sof_ipc4_build_time_info(struct snd_sof_dev *sdev, struct snd_sof_pcm_stream *spcm)
+{
+ struct sof_ipc4_copier *host_copier = NULL;
+ struct sof_ipc4_copier *dai_copier = NULL;
+ struct sof_ipc4_llp_reading_slot llp_slot;
+ struct sof_ipc4_timestamp_info *info;
+ struct snd_soc_dapm_widget *widget;
+ struct snd_sof_dai *dai;
+ int i;
+
+ /* find host & dai to locate info in memory window */
+ for_each_dapm_widgets(spcm->list, i, widget) {
+ struct snd_sof_widget *swidget = widget->dobj.private;
+
+ if (!swidget)
+ continue;
+
+ if (WIDGET_IS_AIF(swidget->widget->id)) {
+ host_copier = swidget->private;
+ } else if (WIDGET_IS_DAI(swidget->widget->id)) {
+ dai = swidget->private;
+ dai_copier = dai->private;
+ }
+ }
+
+ /* both host and dai copier must be valid for time_info */
+ if (!host_copier || !dai_copier) {
+ dev_err(sdev->dev, "host or dai copier are not found\n");
+ return;
+ }
+
+ info = spcm->private;
+ info->host_copier = host_copier;
+ info->dai_copier = dai_copier;
+ info->llp_offset = offsetof(struct sof_ipc4_fw_registers, llp_gpdma_reading_slots) +
+ sdev->fw_info_box.offset;
+
+ /* find llp slot used by current dai */
+ for (i = 0; i < SOF_IPC4_MAX_LLP_GPDMA_READING_SLOTS; i++) {
+ sof_mailbox_read(sdev, info->llp_offset, &llp_slot, sizeof(llp_slot));
+ if (llp_slot.node_id == dai_copier->data.gtw_cfg.node_id)
+ break;
+
+ info->llp_offset += sizeof(llp_slot);
+ }
+
+ if (i < SOF_IPC4_MAX_LLP_GPDMA_READING_SLOTS)
+ return;
+
+ /* if no llp gpdma slot is used, check aggregated sdw slot */
+ info->llp_offset = offsetof(struct sof_ipc4_fw_registers, llp_sndw_reading_slots) +
+ sdev->fw_info_box.offset;
+ for (i = 0; i < SOF_IPC4_MAX_LLP_SNDW_READING_SLOTS; i++) {
+ sof_mailbox_read(sdev, info->llp_offset, &llp_slot, sizeof(llp_slot));
+ if (llp_slot.node_id == dai_copier->data.gtw_cfg.node_id)
+ break;
+
+ info->llp_offset += sizeof(llp_slot);
+ }
+
+ if (i < SOF_IPC4_MAX_LLP_SNDW_READING_SLOTS)
+ return;
+
+ /* check EVAD slot */
+ info->llp_offset = offsetof(struct sof_ipc4_fw_registers, llp_evad_reading_slot) +
+ sdev->fw_info_box.offset;
+ sof_mailbox_read(sdev, info->llp_offset, &llp_slot, sizeof(llp_slot));
+ if (llp_slot.node_id != dai_copier->data.gtw_cfg.node_id)
+ info->llp_offset = 0;
+}
+
+static int sof_ipc4_pcm_hw_params(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_sof_platform_stream_params *platform_params)
+{
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct sof_ipc4_timestamp_info *time_info;
+ struct snd_sof_pcm *spcm;
+
+ spcm = snd_sof_find_spcm_dai(component, rtd);
+ if (!spcm)
+ return -EINVAL;
+
+ time_info = spcm->stream[substream->stream].private;
+ /* delay calculation is not supported by current fw_reg ABI */
+ if (!time_info)
+ return 0;
+
+ time_info->stream_start_offset = SOF_IPC4_INVALID_STREAM_POSITION;
+ time_info->llp_offset = 0;
+
+ sof_ipc4_build_time_info(sdev, &spcm->stream[substream->stream]);
+
+ return 0;
+}
+
+static int sof_ipc4_get_stream_start_offset(struct snd_sof_dev *sdev,
+ struct snd_pcm_substream *substream,
+ struct snd_sof_pcm_stream *stream,
+ struct sof_ipc4_timestamp_info *time_info)
+{
+ struct sof_ipc4_copier *host_copier = time_info->host_copier;
+ struct sof_ipc4_copier *dai_copier = time_info->dai_copier;
+ struct sof_ipc4_pipeline_registers ppl_reg;
+ u64 stream_start_position;
+ u32 dai_sample_size;
+ u32 ch, node_index;
+ u32 offset;
+
+ if (!host_copier || !dai_copier)
+ return -EINVAL;
+
+ if (host_copier->data.gtw_cfg.node_id == SOF_IPC4_INVALID_NODE_ID)
+ return -EINVAL;
+
+ node_index = SOF_IPC4_NODE_INDEX(host_copier->data.gtw_cfg.node_id);
+ offset = offsetof(struct sof_ipc4_fw_registers, pipeline_regs) + node_index * sizeof(ppl_reg);
+ sof_mailbox_read(sdev, sdev->fw_info_box.offset + offset, &ppl_reg, sizeof(ppl_reg));
+ if (ppl_reg.stream_start_offset == SOF_IPC4_INVALID_STREAM_POSITION)
+ return -EINVAL;
+
+ stream_start_position = ppl_reg.stream_start_offset;
+ ch = dai_copier->data.out_format.fmt_cfg;
+ ch = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(ch);
+ dai_sample_size = (dai_copier->data.out_format.bit_depth >> 3) * ch;
+ /* convert offset to sample count */
+ do_div(stream_start_position, dai_sample_size);
+ time_info->stream_start_offset = stream_start_position;
+
+ return 0;
+}
+
+static snd_pcm_sframes_t sof_ipc4_pcm_delay(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct sof_ipc4_timestamp_info *time_info;
+ struct sof_ipc4_llp_reading_slot llp;
+ snd_pcm_uframes_t head_ptr, tail_ptr;
+ struct snd_sof_pcm_stream *stream;
+ struct snd_sof_pcm *spcm;
+ u64 tmp_ptr;
+ int ret;
+
+ spcm = snd_sof_find_spcm_dai(component, rtd);
+ if (!spcm)
+ return 0;
+
+ stream = &spcm->stream[substream->stream];
+ time_info = stream->private;
+ if (!time_info)
+ return 0;
+
+ /*
+ * stream_start_offset is updated to memory window by FW based on
+ * pipeline statistics and it may be invalid if host query happens before
+ * the statistics is complete. And it will not change after the first initiailization.
+ */
+ if (time_info->stream_start_offset == SOF_IPC4_INVALID_STREAM_POSITION) {
+ ret = sof_ipc4_get_stream_start_offset(sdev, substream, stream, time_info);
+ if (ret < 0)
+ return 0;
+ }
+
+ /*
+ * HDaudio links don't support the LLP counter reported by firmware
+ * the link position is read directly from hardware registers.
+ */
+ if (!time_info->llp_offset) {
+ tmp_ptr = snd_sof_pcm_get_stream_position(sdev, component, substream);
+ if (!tmp_ptr)
+ return 0;
+ } else {
+ sof_mailbox_read(sdev, time_info->llp_offset, &llp, sizeof(llp));
+ tmp_ptr = ((u64)llp.reading.llp_u << 32) | llp.reading.llp_l;
+ }
+
+ /* In two cases dai dma position is not accurate
+ * (1) dai pipeline is started before host pipeline
+ * (2) multiple streams mixed into one. Each stream has the same dai dma position
+ *
+ * Firmware calculates correct stream_start_offset for all cases including above two.
+ * Driver subtracts stream_start_offset from dai dma position to get accurate one
+ */
+ tmp_ptr -= time_info->stream_start_offset;
+
+ /* Calculate the delay taking into account that both pointer can wrap */
+ div64_u64_rem(tmp_ptr, substream->runtime->boundary, &tmp_ptr);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ head_ptr = substream->runtime->status->hw_ptr;
+ tail_ptr = tmp_ptr;
+ } else {
+ head_ptr = tmp_ptr;
+ tail_ptr = substream->runtime->status->hw_ptr;
+ }
+
+ if (head_ptr < tail_ptr)
+ return substream->runtime->boundary - tail_ptr + head_ptr;
+
+ return head_ptr - tail_ptr;
+}
+
+const struct sof_ipc_pcm_ops ipc4_pcm_ops = {
+ .hw_params = sof_ipc4_pcm_hw_params,
+ .trigger = sof_ipc4_pcm_trigger,
+ .hw_free = sof_ipc4_pcm_hw_free,
+ .dai_link_fixup = sof_ipc4_pcm_dai_link_fixup,
+ .pcm_setup = sof_ipc4_pcm_setup,
+ .pcm_free = sof_ipc4_pcm_free,
+ .delay = sof_ipc4_pcm_delay,
+ .ipc_first_on_start = true,
+ .platform_stop_during_hw_free = true,
+};
diff --git a/sound/soc/sof/ipc4-priv.h b/sound/soc/sof/ipc4-priv.h
new file mode 100644
index 000000000000..f3b908b093f9
--- /dev/null
+++ b/sound/soc/sof/ipc4-priv.h
@@ -0,0 +1,134 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+/*
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * Copyright(c) 2022 Intel Corporation. All rights reserved.
+ */
+
+#ifndef __SOUND_SOC_SOF_IPC4_PRIV_H
+#define __SOUND_SOC_SOF_IPC4_PRIV_H
+
+#include <linux/idr.h>
+#include <sound/sof/ext_manifest4.h>
+#include "sof-priv.h"
+
+/* The DSP window indices are fixed */
+#define SOF_IPC4_INBOX_WINDOW_IDX 0
+#define SOF_IPC4_OUTBOX_WINDOW_IDX 1
+#define SOF_IPC4_DEBUG_WINDOW_IDX 2
+
+enum sof_ipc4_mtrace_type {
+ SOF_IPC4_MTRACE_NOT_AVAILABLE = 0,
+ SOF_IPC4_MTRACE_INTEL_CAVS_1_5,
+ SOF_IPC4_MTRACE_INTEL_CAVS_1_8,
+ SOF_IPC4_MTRACE_INTEL_CAVS_2,
+};
+
+/**
+ * struct sof_ipc4_fw_module - IPC4 module info
+ * @sof_man4_module: Module info
+ * @fw_mod_cfg: Pointer to the module config start of the module
+ * @m_ida: Module instance identifier
+ * @private: Module private data
+ */
+struct sof_ipc4_fw_module {
+ struct sof_man4_module man4_module_entry;
+ const struct sof_man4_module_config *fw_mod_cfg;
+ struct ida m_ida;
+ void *private;
+};
+
+/**
+ * struct sof_ipc4_fw_library - IPC4 library information
+ * @sof_fw: SOF Firmware of the library
+ * @id: Library ID. 0 is reserved for basefw, external libraries must have unique
+ * ID number between 1 and (sof_ipc4_fw_data.max_libs_count - 1)
+ * Note: sof_ipc4_fw_data.max_libs_count == 1 implies that external libraries
+ * are not supported
+ * @num_modules : Number of FW modules in the library
+ * @modules: Array of FW modules
+ */
+struct sof_ipc4_fw_library {
+ struct sof_firmware sof_fw;
+ const char *name;
+ u32 id;
+ int num_modules;
+ struct sof_ipc4_fw_module *modules;
+};
+
+/**
+ * struct sof_ipc4_fw_data - IPC4-specific data
+ * @manifest_fw_hdr_offset: FW header offset in the manifest
+ * @fw_lib_xa: XArray for firmware libraries, including basefw (ID = 0)
+ * Used to store the FW libraries and to manage the unique IDs of the
+ * libraries.
+ * @nhlt: NHLT table either from the BIOS or the topology manifest
+ * @mtrace_type: mtrace type supported on the booted platform
+ * @mtrace_log_bytes: log bytes as reported by the firmware via fw_config reply
+ * @num_playback_streams: max number of playback DMAs, needed for CHAIN_DMA offset
+ * @num_capture_streams: max number of capture DMAs
+ * @max_num_pipelines: max number of pipelines
+ * @max_libs_count: Maximum number of libraries support by the FW including the
+ * base firmware
+ *
+ * @load_library: Callback function for platform dependent library loading
+ * @pipeline_state_mutex: Mutex to protect pipeline triggers, ref counts, states and deletion
+ */
+struct sof_ipc4_fw_data {
+ u32 manifest_fw_hdr_offset;
+ struct xarray fw_lib_xa;
+ void *nhlt;
+ enum sof_ipc4_mtrace_type mtrace_type;
+ u32 mtrace_log_bytes;
+ int num_playback_streams;
+ int num_capture_streams;
+ int max_num_pipelines;
+ u32 max_libs_count;
+ bool fw_context_save;
+
+ int (*load_library)(struct snd_sof_dev *sdev,
+ struct sof_ipc4_fw_library *fw_lib, bool reload);
+ struct mutex pipeline_state_mutex; /* protect pipeline triggers, ref counts and states */
+};
+
+/**
+ * struct sof_ipc4_timestamp_info - IPC4 timestamp info
+ * @host_copier: the host copier of the pcm stream
+ * @dai_copier: the dai copier of the pcm stream
+ * @stream_start_offset: reported by fw in memory window
+ * @llp_offset: llp offset in memory window
+ */
+struct sof_ipc4_timestamp_info {
+ struct sof_ipc4_copier *host_copier;
+ struct sof_ipc4_copier *dai_copier;
+ u64 stream_start_offset;
+ u32 llp_offset;
+};
+
+extern const struct sof_ipc_fw_loader_ops ipc4_loader_ops;
+extern const struct sof_ipc_tplg_ops ipc4_tplg_ops;
+extern const struct sof_ipc_tplg_control_ops tplg_ipc4_control_ops;
+extern const struct sof_ipc_pcm_ops ipc4_pcm_ops;
+extern const struct sof_ipc_fw_tracing_ops ipc4_mtrace_ops;
+
+int sof_ipc4_set_pipeline_state(struct snd_sof_dev *sdev, u32 id, u32 state);
+int sof_ipc4_mtrace_update_pos(struct snd_sof_dev *sdev, int core);
+
+int sof_ipc4_query_fw_configuration(struct snd_sof_dev *sdev);
+int sof_ipc4_reload_fw_libraries(struct snd_sof_dev *sdev);
+struct sof_ipc4_fw_module *sof_ipc4_find_module_by_uuid(struct snd_sof_dev *sdev,
+ const guid_t *uuid);
+
+struct snd_sof_widget *sof_ipc4_find_swidget_by_ids(struct snd_sof_dev *sdev,
+ u32 module_id, int instance_id);
+
+struct sof_ipc4_base_module_cfg;
+void sof_ipc4_update_cpc_from_manifest(struct snd_sof_dev *sdev,
+ struct sof_ipc4_fw_module *fw_module,
+ struct sof_ipc4_base_module_cfg *basecfg);
+
+size_t sof_ipc4_find_debug_slot_offset_by_type(struct snd_sof_dev *sdev,
+ u32 slot_type);
+
+#endif
diff --git a/sound/soc/sof/ipc4-telemetry.c b/sound/soc/sof/ipc4-telemetry.c
new file mode 100644
index 000000000000..ec4ae9674364
--- /dev/null
+++ b/sound/soc/sof/ipc4-telemetry.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2018-2023 Intel Corporation. All rights reserved.
+//
+
+#include <linux/debugfs.h>
+#include <linux/io.h>
+#include <linux/pm_runtime.h>
+#include <sound/sof/debug.h>
+#include <sound/sof/ipc4/header.h>
+#include "sof-priv.h"
+#include "ops.h"
+#include "ipc4-telemetry.h"
+#include "ipc4-priv.h"
+
+static void __iomem *sof_ipc4_query_exception_address(struct snd_sof_dev *sdev)
+{
+ u32 type = SOF_IPC4_DEBUG_SLOT_TELEMETRY;
+ size_t telemetry_slot_offset;
+ u32 offset;
+
+ telemetry_slot_offset = sof_ipc4_find_debug_slot_offset_by_type(sdev, type);
+ if (!telemetry_slot_offset)
+ return NULL;
+
+ /* skip the first separator magic number */
+ offset = telemetry_slot_offset + sizeof(u32);
+
+ return sdev->bar[sdev->mailbox_bar] + offset;
+}
+
+static ssize_t sof_telemetry_entry_read(struct file *file, char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct snd_sof_dfsentry *dfse = file->private_data;
+ struct snd_sof_dev *sdev = dfse->sdev;
+ void __iomem *io_addr;
+ loff_t pos = *ppos;
+ size_t size_ret;
+ u8 *buf;
+
+ if (pos < 0)
+ return -EINVAL;
+ /* skip the first separator magic number */
+ if (pos >= SOF_IPC4_DEBUG_SLOT_SIZE - 4 || !count)
+ return 0;
+ if (count > SOF_IPC4_DEBUG_SLOT_SIZE - 4 - pos)
+ count = SOF_IPC4_DEBUG_SLOT_SIZE - 4 - pos;
+
+ io_addr = sof_ipc4_query_exception_address(sdev);
+ if (!io_addr)
+ return -EFAULT;
+
+ buf = kzalloc(SOF_IPC4_DEBUG_SLOT_SIZE - 4, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ memcpy_fromio(buf, io_addr, SOF_IPC4_DEBUG_SLOT_SIZE - 4);
+ size_ret = copy_to_user(buffer, buf + pos, count);
+ if (size_ret) {
+ kfree(buf);
+ return -EFAULT;
+ }
+
+ *ppos = pos + count;
+ kfree(buf);
+
+ return count;
+}
+
+static const struct file_operations sof_telemetry_fops = {
+ .open = simple_open,
+ .read = sof_telemetry_entry_read,
+};
+
+void sof_ipc4_create_exception_debugfs_node(struct snd_sof_dev *sdev)
+{
+ struct snd_sof_dfsentry *dfse;
+
+ dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL);
+ if (!dfse)
+ return;
+
+ dfse->type = SOF_DFSENTRY_TYPE_IOMEM;
+ dfse->size = SOF_IPC4_DEBUG_SLOT_SIZE - 4;
+ dfse->access_type = SOF_DEBUGFS_ACCESS_ALWAYS;
+ dfse->sdev = sdev;
+
+ list_add(&dfse->list, &sdev->dfsentry_list);
+
+ debugfs_create_file("exception", 0444, sdev->debugfs_root, dfse, &sof_telemetry_fops);
+}
diff --git a/sound/soc/sof/ipc4-telemetry.h b/sound/soc/sof/ipc4-telemetry.h
new file mode 100644
index 000000000000..ab3599e3d87d
--- /dev/null
+++ b/sound/soc/sof/ipc4-telemetry.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+/*
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * Copyright(c) 2023 Intel Corporation. All rights reserved.
+ */
+
+#ifndef __SOUND_SOC_SOF_IPC4_TELEMETRY_H
+#define __SOUND_SOC_SOF_IPC4_TELEMETRY_H
+
+/* Target code */
+enum sof_ipc4_coredump_tgt_code {
+ COREDUMP_TGT_UNKNOWN = 0,
+ COREDUMP_TGT_X86,
+ COREDUMP_TGT_X86_64,
+ COREDUMP_TGT_ARM_CORTEX_M,
+ COREDUMP_TGT_RISC_V,
+ COREDUMP_TGT_XTENSA,
+};
+
+#define COREDUMP_ARCH_HDR_ID 'A'
+#define COREDUMP_HDR_ID0 'Z'
+#define COREDUMP_HDR_ID1 'E'
+
+#define XTENSA_BLOCK_HDR_VER 2
+#define XTENSA_CORE_DUMP_SEPARATOR 0x0DEC0DEB
+#define XTENSA_CORE_AR_REGS_COUNT 16
+#define XTENSA_SOC_INTEL_ADSP 3
+#define XTENSA_TOOL_CHAIN_ZEPHYR 1
+#define XTENSA_TOOL_CHAIN_XCC 2
+
+/* Coredump header */
+struct sof_ipc4_coredump_hdr {
+ /* 'Z', 'E' as identifier of file */
+ char id[2];
+
+ /* Identify the version of the header */
+ u16 hdr_version;
+
+ /* Indicate which target (e.g. architecture or SoC) */
+ u16 tgt_code;
+
+ /* Size of uintptr_t in power of 2. (e.g. 5 for 32-bit, 6 for 64-bit) */
+ u8 ptr_size_bits;
+
+ u8 flag;
+
+ /* Reason for the fatal error */
+ u32 reason;
+} __packed;
+
+/* Architecture-specific block header */
+struct sof_ipc4_coredump_arch_hdr {
+ /* COREDUMP_ARCH_HDR_ID to indicate this is a architecture-specific block */
+ char id;
+
+ /* Identify the version of this block */
+ u16 hdr_version;
+
+ /* Number of bytes following the header */
+ u16 num_bytes;
+} __packed;
+
+struct sof_ipc4_telemetry_slot_data {
+ u32 separator;
+ struct sof_ipc4_coredump_hdr hdr;
+ struct sof_ipc4_coredump_arch_hdr arch_hdr;
+ u32 arch_data[];
+} __packed;
+
+void sof_ipc4_create_exception_debugfs_node(struct snd_sof_dev *sdev);
+#endif
diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c
new file mode 100644
index 000000000000..da4a83afb87a
--- /dev/null
+++ b/sound/soc/sof/ipc4-topology.c
@@ -0,0 +1,3127 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2022 Intel Corporation. All rights reserved.
+//
+//
+#include <linux/bitfield.h>
+#include <uapi/sound/sof/tokens.h>
+#include <sound/pcm_params.h>
+#include <sound/sof/ext_manifest4.h>
+#include <sound/intel-nhlt.h>
+#include "sof-priv.h"
+#include "sof-audio.h"
+#include "ipc4-priv.h"
+#include "ipc4-topology.h"
+#include "ops.h"
+
+/*
+ * The ignore_cpc flag can be used to ignore the CPC value for all modules by
+ * using 0 instead.
+ * The CPC is sent to the firmware along with the SOF_IPC4_MOD_INIT_INSTANCE
+ * message and it is used for clock scaling.
+ * 0 as CPC value will instruct the firmware to use maximum frequency, thus
+ * deactivating the clock scaling.
+ */
+static bool ignore_cpc;
+module_param_named(ipc4_ignore_cpc, ignore_cpc, bool, 0444);
+MODULE_PARM_DESC(ipc4_ignore_cpc,
+ "Ignore CPC values. This option will disable clock scaling in firmware.");
+
+#define SOF_IPC4_GAIN_PARAM_ID 0
+#define SOF_IPC4_TPLG_ABI_SIZE 6
+#define SOF_IPC4_CHAIN_DMA_BUF_SIZE_MS 2
+
+static DEFINE_IDA(alh_group_ida);
+static DEFINE_IDA(pipeline_ida);
+
+static const struct sof_topology_token ipc4_sched_tokens[] = {
+ {SOF_TKN_SCHED_LP_MODE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_pipeline, lp_mode)},
+ {SOF_TKN_SCHED_USE_CHAIN_DMA, SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16,
+ offsetof(struct sof_ipc4_pipeline, use_chain_dma)},
+ {SOF_TKN_SCHED_CORE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_pipeline, core_id)},
+ {SOF_TKN_SCHED_PRIORITY, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_pipeline, priority)},
+};
+
+static const struct sof_topology_token pipeline_tokens[] = {
+ {SOF_TKN_SCHED_DYNAMIC_PIPELINE, SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16,
+ offsetof(struct snd_sof_widget, dynamic_pipeline_widget)},
+};
+
+static const struct sof_topology_token ipc4_comp_tokens[] = {
+ {SOF_TKN_COMP_IS_PAGES, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_base_module_cfg, is_pages)},
+};
+
+static const struct sof_topology_token ipc4_in_audio_format_tokens[] = {
+ {SOF_TKN_CAVS_AUDIO_FORMAT_IN_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_pin_format, audio_fmt.sampling_frequency)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_IN_BIT_DEPTH, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_pin_format, audio_fmt.bit_depth)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_IN_CH_MAP, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_pin_format, audio_fmt.ch_map)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_IN_CH_CFG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_pin_format, audio_fmt.ch_cfg)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_IN_INTERLEAVING_STYLE, SND_SOC_TPLG_TUPLE_TYPE_WORD,
+ get_token_u32, offsetof(struct sof_ipc4_pin_format,
+ audio_fmt.interleaving_style)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_IN_FMT_CFG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_pin_format, audio_fmt.fmt_cfg)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_INPUT_PIN_INDEX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_pin_format, pin_index)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_IBS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_pin_format, buffer_size)},
+};
+
+static const struct sof_topology_token ipc4_out_audio_format_tokens[] = {
+ {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_pin_format, audio_fmt.sampling_frequency)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_BIT_DEPTH, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_pin_format, audio_fmt.bit_depth)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_CH_MAP, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_pin_format, audio_fmt.ch_map)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_CH_CFG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_pin_format, audio_fmt.ch_cfg)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_INTERLEAVING_STYLE, SND_SOC_TPLG_TUPLE_TYPE_WORD,
+ get_token_u32, offsetof(struct sof_ipc4_pin_format,
+ audio_fmt.interleaving_style)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_FMT_CFG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_pin_format, audio_fmt.fmt_cfg)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_OUTPUT_PIN_INDEX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_pin_format, pin_index)},
+ {SOF_TKN_CAVS_AUDIO_FORMAT_OBS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_pin_format, buffer_size)},
+};
+
+static const struct sof_topology_token ipc4_copier_deep_buffer_tokens[] = {
+ {SOF_TKN_INTEL_COPIER_DEEP_BUFFER_DMA_MS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, 0},
+};
+
+static const struct sof_topology_token ipc4_copier_tokens[] = {
+ {SOF_TKN_INTEL_COPIER_NODE_TYPE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, 0},
+};
+
+static const struct sof_topology_token ipc4_audio_fmt_num_tokens[] = {
+ {SOF_TKN_COMP_NUM_INPUT_AUDIO_FORMATS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_available_audio_format, num_input_formats)},
+ {SOF_TKN_COMP_NUM_OUTPUT_AUDIO_FORMATS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_available_audio_format, num_output_formats)},
+};
+
+static const struct sof_topology_token dai_tokens[] = {
+ {SOF_TKN_DAI_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_dai_type,
+ offsetof(struct sof_ipc4_copier, dai_type)},
+ {SOF_TKN_DAI_INDEX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_copier, dai_index)},
+};
+
+/* Component extended tokens */
+static const struct sof_topology_token comp_ext_tokens[] = {
+ {SOF_TKN_COMP_UUID, SND_SOC_TPLG_TUPLE_TYPE_UUID, get_token_uuid,
+ offsetof(struct snd_sof_widget, uuid)},
+ {SOF_TKN_COMP_CORE_ID, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct snd_sof_widget, core)},
+};
+
+static const struct sof_topology_token gain_tokens[] = {
+ {SOF_TKN_GAIN_RAMP_TYPE, SND_SOC_TPLG_TUPLE_TYPE_WORD,
+ get_token_u32, offsetof(struct sof_ipc4_gain_params, curve_type)},
+ {SOF_TKN_GAIN_RAMP_DURATION,
+ SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_gain_params, curve_duration_l)},
+ {SOF_TKN_GAIN_VAL, SND_SOC_TPLG_TUPLE_TYPE_WORD,
+ get_token_u32, offsetof(struct sof_ipc4_gain_params, init_val)},
+};
+
+/* SRC */
+static const struct sof_topology_token src_tokens[] = {
+ {SOF_TKN_SRC_RATE_OUT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct sof_ipc4_src_data, sink_rate)},
+};
+
+static const struct sof_token_info ipc4_token_list[SOF_TOKEN_COUNT] = {
+ [SOF_DAI_TOKENS] = {"DAI tokens", dai_tokens, ARRAY_SIZE(dai_tokens)},
+ [SOF_PIPELINE_TOKENS] = {"Pipeline tokens", pipeline_tokens, ARRAY_SIZE(pipeline_tokens)},
+ [SOF_SCHED_TOKENS] = {"Scheduler tokens", ipc4_sched_tokens,
+ ARRAY_SIZE(ipc4_sched_tokens)},
+ [SOF_COMP_EXT_TOKENS] = {"Comp extended tokens", comp_ext_tokens,
+ ARRAY_SIZE(comp_ext_tokens)},
+ [SOF_COMP_TOKENS] = {"IPC4 Component tokens",
+ ipc4_comp_tokens, ARRAY_SIZE(ipc4_comp_tokens)},
+ [SOF_IN_AUDIO_FORMAT_TOKENS] = {"IPC4 Input Audio format tokens",
+ ipc4_in_audio_format_tokens, ARRAY_SIZE(ipc4_in_audio_format_tokens)},
+ [SOF_OUT_AUDIO_FORMAT_TOKENS] = {"IPC4 Output Audio format tokens",
+ ipc4_out_audio_format_tokens, ARRAY_SIZE(ipc4_out_audio_format_tokens)},
+ [SOF_COPIER_DEEP_BUFFER_TOKENS] = {"IPC4 Copier deep buffer tokens",
+ ipc4_copier_deep_buffer_tokens, ARRAY_SIZE(ipc4_copier_deep_buffer_tokens)},
+ [SOF_COPIER_TOKENS] = {"IPC4 Copier tokens", ipc4_copier_tokens,
+ ARRAY_SIZE(ipc4_copier_tokens)},
+ [SOF_AUDIO_FMT_NUM_TOKENS] = {"IPC4 Audio format number tokens",
+ ipc4_audio_fmt_num_tokens, ARRAY_SIZE(ipc4_audio_fmt_num_tokens)},
+ [SOF_GAIN_TOKENS] = {"Gain tokens", gain_tokens, ARRAY_SIZE(gain_tokens)},
+ [SOF_SRC_TOKENS] = {"SRC tokens", src_tokens, ARRAY_SIZE(src_tokens)},
+};
+
+struct snd_sof_widget *sof_ipc4_find_swidget_by_ids(struct snd_sof_dev *sdev,
+ u32 module_id, int instance_id)
+{
+ struct snd_sof_widget *swidget;
+
+ list_for_each_entry(swidget, &sdev->widget_list, list) {
+ struct sof_ipc4_fw_module *fw_module = swidget->module_info;
+
+ /* Only active module instances have valid instance_id */
+ if (!swidget->use_count)
+ continue;
+
+ if (fw_module && fw_module->man4_module_entry.id == module_id &&
+ swidget->instance_id == instance_id)
+ return swidget;
+ }
+
+ return NULL;
+}
+
+static void sof_ipc4_dbg_audio_format(struct device *dev, struct sof_ipc4_pin_format *pin_fmt,
+ int num_formats)
+{
+ int i;
+
+ for (i = 0; i < num_formats; i++) {
+ struct sof_ipc4_audio_format *fmt = &pin_fmt[i].audio_fmt;
+ dev_dbg(dev,
+ "Pin index #%d: %uHz, %ubit (ch_map %#x ch_cfg %u interleaving_style %u fmt_cfg %#x) buffer size %d\n",
+ pin_fmt[i].pin_index, fmt->sampling_frequency, fmt->bit_depth, fmt->ch_map,
+ fmt->ch_cfg, fmt->interleaving_style, fmt->fmt_cfg,
+ pin_fmt[i].buffer_size);
+ }
+}
+
+static const struct sof_ipc4_audio_format *
+sof_ipc4_get_input_pin_audio_fmt(struct snd_sof_widget *swidget, int pin_index)
+{
+ struct sof_ipc4_base_module_cfg_ext *base_cfg_ext;
+ struct sof_ipc4_process *process;
+ int i;
+
+ if (swidget->id != snd_soc_dapm_effect) {
+ struct sof_ipc4_base_module_cfg *base = swidget->private;
+
+ /* For non-process modules, base module config format is used for all input pins */
+ return &base->audio_fmt;
+ }
+
+ process = swidget->private;
+ base_cfg_ext = process->base_config_ext;
+
+ /*
+ * If there are multiple input formats available for a pin, the first available format
+ * is chosen.
+ */
+ for (i = 0; i < base_cfg_ext->num_input_pin_fmts; i++) {
+ struct sof_ipc4_pin_format *pin_format = &base_cfg_ext->pin_formats[i];
+
+ if (pin_format->pin_index == pin_index)
+ return &pin_format->audio_fmt;
+ }
+
+ return NULL;
+}
+
+/**
+ * sof_ipc4_get_audio_fmt - get available audio formats from swidget->tuples
+ * @scomp: pointer to pointer to SOC component
+ * @swidget: pointer to struct snd_sof_widget containing tuples
+ * @available_fmt: pointer to struct sof_ipc4_available_audio_format being filling in
+ * @module_base_cfg: Pointer to the base_config in the module init IPC payload
+ *
+ * Return: 0 if successful
+ */
+static int sof_ipc4_get_audio_fmt(struct snd_soc_component *scomp,
+ struct snd_sof_widget *swidget,
+ struct sof_ipc4_available_audio_format *available_fmt,
+ struct sof_ipc4_base_module_cfg *module_base_cfg)
+{
+ struct sof_ipc4_pin_format *in_format = NULL;
+ struct sof_ipc4_pin_format *out_format;
+ int ret;
+
+ ret = sof_update_ipc_object(scomp, available_fmt,
+ SOF_AUDIO_FMT_NUM_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(*available_fmt), 1);
+ if (ret) {
+ dev_err(scomp->dev, "Failed to parse audio format token count\n");
+ return ret;
+ }
+
+ if (!available_fmt->num_input_formats && !available_fmt->num_output_formats) {
+ dev_err(scomp->dev, "No input/output pin formats set in topology\n");
+ return -EINVAL;
+ }
+
+ dev_dbg(scomp->dev,
+ "Number of input audio formats: %d. Number of output audio formats: %d\n",
+ available_fmt->num_input_formats, available_fmt->num_output_formats);
+
+ /* set is_pages in the module's base_config */
+ ret = sof_update_ipc_object(scomp, module_base_cfg, SOF_COMP_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(*module_base_cfg), 1);
+ if (ret) {
+ dev_err(scomp->dev, "parse comp tokens for %s failed, error: %d\n",
+ swidget->widget->name, ret);
+ return ret;
+ }
+
+ dev_dbg(scomp->dev, "widget %s: is_pages: %d\n", swidget->widget->name,
+ module_base_cfg->is_pages);
+
+ if (available_fmt->num_input_formats) {
+ in_format = kcalloc(available_fmt->num_input_formats,
+ sizeof(*in_format), GFP_KERNEL);
+ if (!in_format)
+ return -ENOMEM;
+ available_fmt->input_pin_fmts = in_format;
+
+ ret = sof_update_ipc_object(scomp, in_format,
+ SOF_IN_AUDIO_FORMAT_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(*in_format),
+ available_fmt->num_input_formats);
+ if (ret) {
+ dev_err(scomp->dev, "parse input audio fmt tokens failed %d\n", ret);
+ goto err_in;
+ }
+
+ dev_dbg(scomp->dev, "Input audio formats for %s\n", swidget->widget->name);
+ sof_ipc4_dbg_audio_format(scomp->dev, in_format,
+ available_fmt->num_input_formats);
+ }
+
+ if (available_fmt->num_output_formats) {
+ out_format = kcalloc(available_fmt->num_output_formats, sizeof(*out_format),
+ GFP_KERNEL);
+ if (!out_format) {
+ ret = -ENOMEM;
+ goto err_in;
+ }
+
+ ret = sof_update_ipc_object(scomp, out_format,
+ SOF_OUT_AUDIO_FORMAT_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(*out_format),
+ available_fmt->num_output_formats);
+ if (ret) {
+ dev_err(scomp->dev, "parse output audio fmt tokens failed\n");
+ goto err_out;
+ }
+
+ available_fmt->output_pin_fmts = out_format;
+ dev_dbg(scomp->dev, "Output audio formats for %s\n", swidget->widget->name);
+ sof_ipc4_dbg_audio_format(scomp->dev, out_format,
+ available_fmt->num_output_formats);
+ }
+
+ return 0;
+
+err_out:
+ kfree(out_format);
+err_in:
+ kfree(in_format);
+ available_fmt->input_pin_fmts = NULL;
+ return ret;
+}
+
+/* release the memory allocated in sof_ipc4_get_audio_fmt */
+static void sof_ipc4_free_audio_fmt(struct sof_ipc4_available_audio_format *available_fmt)
+
+{
+ kfree(available_fmt->output_pin_fmts);
+ available_fmt->output_pin_fmts = NULL;
+ kfree(available_fmt->input_pin_fmts);
+ available_fmt->input_pin_fmts = NULL;
+}
+
+static void sof_ipc4_widget_free_comp_pipeline(struct snd_sof_widget *swidget)
+{
+ kfree(swidget->private);
+}
+
+static int sof_ipc4_widget_set_module_info(struct snd_sof_widget *swidget)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+
+ swidget->module_info = sof_ipc4_find_module_by_uuid(sdev, &swidget->uuid);
+
+ if (swidget->module_info)
+ return 0;
+
+ dev_err(sdev->dev, "failed to find module info for widget %s with UUID %pUL\n",
+ swidget->widget->name, &swidget->uuid);
+ return -EINVAL;
+}
+
+static int sof_ipc4_widget_setup_msg(struct snd_sof_widget *swidget, struct sof_ipc4_msg *msg)
+{
+ struct sof_ipc4_fw_module *fw_module;
+ uint32_t type;
+ int ret;
+
+ ret = sof_ipc4_widget_set_module_info(swidget);
+ if (ret)
+ return ret;
+
+ fw_module = swidget->module_info;
+
+ msg->primary = fw_module->man4_module_entry.id;
+ msg->primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_INIT_INSTANCE);
+ msg->primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ msg->primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG);
+
+ msg->extension = SOF_IPC4_MOD_EXT_CORE_ID(swidget->core);
+
+ type = (fw_module->man4_module_entry.type & SOF_IPC4_MODULE_DP) ? 1 : 0;
+ msg->extension |= SOF_IPC4_MOD_EXT_DOMAIN(type);
+
+ return 0;
+}
+
+static void sof_ipc4_widget_update_kcontrol_module_id(struct snd_sof_widget *swidget)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct sof_ipc4_fw_module *fw_module = swidget->module_info;
+ struct snd_sof_control *scontrol;
+
+ /* update module ID for all kcontrols for this widget */
+ list_for_each_entry(scontrol, &sdev->kcontrol_list, list) {
+ if (scontrol->comp_id == swidget->comp_id) {
+ struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data;
+ struct sof_ipc4_msg *msg = &cdata->msg;
+
+ msg->primary |= fw_module->man4_module_entry.id;
+ }
+ }
+}
+
+static int sof_ipc4_widget_setup_pcm(struct snd_sof_widget *swidget)
+{
+ struct sof_ipc4_available_audio_format *available_fmt;
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct sof_ipc4_copier *ipc4_copier;
+ int node_type = 0;
+ int ret;
+
+ ipc4_copier = kzalloc(sizeof(*ipc4_copier), GFP_KERNEL);
+ if (!ipc4_copier)
+ return -ENOMEM;
+
+ swidget->private = ipc4_copier;
+ available_fmt = &ipc4_copier->available_fmt;
+
+ dev_dbg(scomp->dev, "Updating IPC structure for %s\n", swidget->widget->name);
+
+ ret = sof_ipc4_get_audio_fmt(scomp, swidget, available_fmt,
+ &ipc4_copier->data.base_config);
+ if (ret)
+ goto free_copier;
+
+ /*
+ * This callback is used by host copier and module-to-module copier,
+ * and only host copier needs to set gtw_cfg.
+ */
+ if (!WIDGET_IS_AIF(swidget->id))
+ goto skip_gtw_cfg;
+
+ ret = sof_update_ipc_object(scomp, &node_type,
+ SOF_COPIER_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(node_type), 1);
+
+ if (ret) {
+ dev_err(scomp->dev, "parse host copier node type token failed %d\n",
+ ret);
+ goto free_available_fmt;
+ }
+ dev_dbg(scomp->dev, "host copier '%s' node_type %u\n", swidget->widget->name, node_type);
+
+skip_gtw_cfg:
+ ipc4_copier->gtw_attr = kzalloc(sizeof(*ipc4_copier->gtw_attr), GFP_KERNEL);
+ if (!ipc4_copier->gtw_attr) {
+ ret = -ENOMEM;
+ goto free_available_fmt;
+ }
+
+ ipc4_copier->copier_config = (uint32_t *)ipc4_copier->gtw_attr;
+ ipc4_copier->data.gtw_cfg.config_length =
+ sizeof(struct sof_ipc4_gtw_attributes) >> 2;
+
+ switch (swidget->id) {
+ case snd_soc_dapm_aif_in:
+ case snd_soc_dapm_aif_out:
+ ipc4_copier->data.gtw_cfg.node_id = SOF_IPC4_NODE_TYPE(node_type);
+ break;
+ case snd_soc_dapm_buffer:
+ ipc4_copier->data.gtw_cfg.node_id = SOF_IPC4_INVALID_NODE_ID;
+ ipc4_copier->ipc_config_size = 0;
+ break;
+ default:
+ dev_err(scomp->dev, "invalid widget type %d\n", swidget->id);
+ ret = -EINVAL;
+ goto free_gtw_attr;
+ }
+
+ /* set up module info and message header */
+ ret = sof_ipc4_widget_setup_msg(swidget, &ipc4_copier->msg);
+ if (ret)
+ goto free_gtw_attr;
+
+ return 0;
+
+free_gtw_attr:
+ kfree(ipc4_copier->gtw_attr);
+free_available_fmt:
+ sof_ipc4_free_audio_fmt(available_fmt);
+free_copier:
+ kfree(ipc4_copier);
+ swidget->private = NULL;
+ return ret;
+}
+
+static void sof_ipc4_widget_free_comp_pcm(struct snd_sof_widget *swidget)
+{
+ struct sof_ipc4_copier *ipc4_copier = swidget->private;
+ struct sof_ipc4_available_audio_format *available_fmt;
+
+ if (!ipc4_copier)
+ return;
+
+ available_fmt = &ipc4_copier->available_fmt;
+ kfree(available_fmt->output_pin_fmts);
+ kfree(ipc4_copier->gtw_attr);
+ kfree(ipc4_copier);
+ swidget->private = NULL;
+}
+
+static int sof_ipc4_widget_setup_comp_dai(struct snd_sof_widget *swidget)
+{
+ struct sof_ipc4_available_audio_format *available_fmt;
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct snd_sof_dai *dai = swidget->private;
+ struct sof_ipc4_copier *ipc4_copier;
+ struct snd_sof_widget *pipe_widget;
+ struct sof_ipc4_pipeline *pipeline;
+ int node_type = 0;
+ int ret;
+
+ ipc4_copier = kzalloc(sizeof(*ipc4_copier), GFP_KERNEL);
+ if (!ipc4_copier)
+ return -ENOMEM;
+
+ available_fmt = &ipc4_copier->available_fmt;
+
+ dev_dbg(scomp->dev, "Updating IPC structure for %s\n", swidget->widget->name);
+
+ ret = sof_ipc4_get_audio_fmt(scomp, swidget, available_fmt,
+ &ipc4_copier->data.base_config);
+ if (ret)
+ goto free_copier;
+
+ ret = sof_update_ipc_object(scomp, &node_type,
+ SOF_COPIER_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(node_type), 1);
+ if (ret) {
+ dev_err(scomp->dev, "parse dai node type failed %d\n", ret);
+ goto free_available_fmt;
+ }
+
+ ret = sof_update_ipc_object(scomp, ipc4_copier,
+ SOF_DAI_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(u32), 1);
+ if (ret) {
+ dev_err(scomp->dev, "parse dai copier node token failed %d\n", ret);
+ goto free_available_fmt;
+ }
+
+ dev_dbg(scomp->dev, "dai %s node_type %u dai_type %u dai_index %d\n", swidget->widget->name,
+ node_type, ipc4_copier->dai_type, ipc4_copier->dai_index);
+
+ dai->type = ipc4_copier->dai_type;
+ ipc4_copier->data.gtw_cfg.node_id = SOF_IPC4_NODE_TYPE(node_type);
+
+ pipe_widget = swidget->spipe->pipe_widget;
+ pipeline = pipe_widget->private;
+
+ if (pipeline->use_chain_dma &&
+ !snd_sof_is_chain_dma_supported(sdev, ipc4_copier->dai_type)) {
+ dev_err(scomp->dev, "Bad DAI type '%d', Chain DMA is not supported\n",
+ ipc4_copier->dai_type);
+ ret = -ENODEV;
+ goto free_available_fmt;
+ }
+
+ switch (ipc4_copier->dai_type) {
+ case SOF_DAI_INTEL_ALH:
+ {
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct sof_ipc4_alh_configuration_blob *blob;
+ struct snd_soc_dapm_path *p;
+ struct snd_sof_widget *w;
+ int src_num = 0;
+
+ snd_soc_dapm_widget_for_each_source_path(swidget->widget, p)
+ src_num++;
+
+ if (swidget->id == snd_soc_dapm_dai_in && src_num == 0) {
+ /*
+ * The blob will not be used if the ALH copier is playback direction
+ * and doesn't connect to any source.
+ * It is fine to call kfree(ipc4_copier->copier_config) since
+ * ipc4_copier->copier_config is null.
+ */
+ ret = 0;
+ break;
+ }
+
+ blob = kzalloc(sizeof(*blob), GFP_KERNEL);
+ if (!blob) {
+ ret = -ENOMEM;
+ goto free_available_fmt;
+ }
+
+ list_for_each_entry(w, &sdev->widget_list, list) {
+ if (w->widget->sname &&
+ strcmp(w->widget->sname, swidget->widget->sname))
+ continue;
+
+ blob->alh_cfg.device_count++;
+ }
+
+ ipc4_copier->copier_config = (uint32_t *)blob;
+ /* set data.gtw_cfg.config_length based on device_count */
+ ipc4_copier->data.gtw_cfg.config_length = (sizeof(blob->gw_attr) +
+ sizeof(blob->alh_cfg.device_count) +
+ sizeof(*blob->alh_cfg.mapping) *
+ blob->alh_cfg.device_count) >> 2;
+ break;
+ }
+ case SOF_DAI_INTEL_SSP:
+ /* set SSP DAI index as the node_id */
+ ipc4_copier->data.gtw_cfg.node_id |=
+ SOF_IPC4_NODE_INDEX_INTEL_SSP(ipc4_copier->dai_index);
+ break;
+ case SOF_DAI_INTEL_DMIC:
+ /* set DMIC DAI index as the node_id */
+ ipc4_copier->data.gtw_cfg.node_id |=
+ SOF_IPC4_NODE_INDEX_INTEL_DMIC(ipc4_copier->dai_index);
+ break;
+ default:
+ ipc4_copier->gtw_attr = kzalloc(sizeof(*ipc4_copier->gtw_attr), GFP_KERNEL);
+ if (!ipc4_copier->gtw_attr) {
+ ret = -ENOMEM;
+ goto free_available_fmt;
+ }
+
+ ipc4_copier->copier_config = (uint32_t *)ipc4_copier->gtw_attr;
+ ipc4_copier->data.gtw_cfg.config_length =
+ sizeof(struct sof_ipc4_gtw_attributes) >> 2;
+ break;
+ }
+
+ dai->scomp = scomp;
+ dai->private = ipc4_copier;
+
+ /* set up module info and message header */
+ ret = sof_ipc4_widget_setup_msg(swidget, &ipc4_copier->msg);
+ if (ret)
+ goto free_copier_config;
+
+ return 0;
+
+free_copier_config:
+ kfree(ipc4_copier->copier_config);
+free_available_fmt:
+ sof_ipc4_free_audio_fmt(available_fmt);
+free_copier:
+ kfree(ipc4_copier);
+ dai->private = NULL;
+ dai->scomp = NULL;
+ return ret;
+}
+
+static void sof_ipc4_widget_free_comp_dai(struct snd_sof_widget *swidget)
+{
+ struct sof_ipc4_available_audio_format *available_fmt;
+ struct snd_sof_dai *dai = swidget->private;
+ struct sof_ipc4_copier *ipc4_copier;
+
+ if (!dai)
+ return;
+
+ if (!dai->private) {
+ kfree(dai);
+ swidget->private = NULL;
+ return;
+ }
+
+ ipc4_copier = dai->private;
+ available_fmt = &ipc4_copier->available_fmt;
+
+ kfree(available_fmt->output_pin_fmts);
+ if (ipc4_copier->dai_type != SOF_DAI_INTEL_SSP &&
+ ipc4_copier->dai_type != SOF_DAI_INTEL_DMIC)
+ kfree(ipc4_copier->copier_config);
+ kfree(dai->private);
+ kfree(dai);
+ swidget->private = NULL;
+}
+
+static int sof_ipc4_widget_setup_comp_pipeline(struct snd_sof_widget *swidget)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct sof_ipc4_pipeline *pipeline;
+ struct snd_sof_pipeline *spipe = swidget->spipe;
+ int ret;
+
+ pipeline = kzalloc(sizeof(*pipeline), GFP_KERNEL);
+ if (!pipeline)
+ return -ENOMEM;
+
+ ret = sof_update_ipc_object(scomp, pipeline, SOF_SCHED_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(*pipeline), 1);
+ if (ret) {
+ dev_err(scomp->dev, "parsing scheduler tokens failed\n");
+ goto err;
+ }
+
+ swidget->core = pipeline->core_id;
+ spipe->core_mask |= BIT(pipeline->core_id);
+
+ if (pipeline->use_chain_dma) {
+ dev_dbg(scomp->dev, "Set up chain DMA for %s\n", swidget->widget->name);
+ swidget->private = pipeline;
+ return 0;
+ }
+
+ /* parse one set of pipeline tokens */
+ ret = sof_update_ipc_object(scomp, swidget, SOF_PIPELINE_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(*swidget), 1);
+ if (ret) {
+ dev_err(scomp->dev, "parsing pipeline tokens failed\n");
+ goto err;
+ }
+
+ dev_dbg(scomp->dev, "pipeline '%s': id %d, pri %d, core_id %u, lp mode %d\n",
+ swidget->widget->name, swidget->pipeline_id,
+ pipeline->priority, pipeline->core_id, pipeline->lp_mode);
+
+ swidget->private = pipeline;
+
+ pipeline->msg.primary = SOF_IPC4_GLB_PIPE_PRIORITY(pipeline->priority);
+ pipeline->msg.primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_CREATE_PIPELINE);
+ pipeline->msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ pipeline->msg.primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG);
+
+ pipeline->msg.extension = pipeline->lp_mode;
+ pipeline->msg.extension |= SOF_IPC4_GLB_PIPE_EXT_CORE_ID(pipeline->core_id);
+ pipeline->state = SOF_IPC4_PIPE_UNINITIALIZED;
+
+ return 0;
+err:
+ kfree(pipeline);
+ return ret;
+}
+
+static int sof_ipc4_widget_setup_comp_pga(struct snd_sof_widget *swidget)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct sof_ipc4_gain *gain;
+ int ret;
+
+ gain = kzalloc(sizeof(*gain), GFP_KERNEL);
+ if (!gain)
+ return -ENOMEM;
+
+ swidget->private = gain;
+
+ gain->data.params.channels = SOF_IPC4_GAIN_ALL_CHANNELS_MASK;
+ gain->data.params.init_val = SOF_IPC4_VOL_ZERO_DB;
+
+ ret = sof_ipc4_get_audio_fmt(scomp, swidget, &gain->available_fmt, &gain->data.base_config);
+ if (ret)
+ goto err;
+
+ ret = sof_update_ipc_object(scomp, &gain->data.params, SOF_GAIN_TOKENS,
+ swidget->tuples, swidget->num_tuples, sizeof(gain->data), 1);
+ if (ret) {
+ dev_err(scomp->dev, "Parsing gain tokens failed\n");
+ goto err;
+ }
+
+ dev_dbg(scomp->dev,
+ "pga widget %s: ramp type: %d, ramp duration %d, initial gain value: %#x\n",
+ swidget->widget->name, gain->data.params.curve_type,
+ gain->data.params.curve_duration_l, gain->data.params.init_val);
+
+ ret = sof_ipc4_widget_setup_msg(swidget, &gain->msg);
+ if (ret)
+ goto err;
+
+ sof_ipc4_widget_update_kcontrol_module_id(swidget);
+
+ return 0;
+err:
+ sof_ipc4_free_audio_fmt(&gain->available_fmt);
+ kfree(gain);
+ swidget->private = NULL;
+ return ret;
+}
+
+static void sof_ipc4_widget_free_comp_pga(struct snd_sof_widget *swidget)
+{
+ struct sof_ipc4_gain *gain = swidget->private;
+
+ if (!gain)
+ return;
+
+ sof_ipc4_free_audio_fmt(&gain->available_fmt);
+ kfree(swidget->private);
+ swidget->private = NULL;
+}
+
+static int sof_ipc4_widget_setup_comp_mixer(struct snd_sof_widget *swidget)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct sof_ipc4_mixer *mixer;
+ int ret;
+
+ dev_dbg(scomp->dev, "Updating IPC structure for %s\n", swidget->widget->name);
+
+ mixer = kzalloc(sizeof(*mixer), GFP_KERNEL);
+ if (!mixer)
+ return -ENOMEM;
+
+ swidget->private = mixer;
+
+ ret = sof_ipc4_get_audio_fmt(scomp, swidget, &mixer->available_fmt,
+ &mixer->base_config);
+ if (ret)
+ goto err;
+
+ ret = sof_ipc4_widget_setup_msg(swidget, &mixer->msg);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ sof_ipc4_free_audio_fmt(&mixer->available_fmt);
+ kfree(mixer);
+ swidget->private = NULL;
+ return ret;
+}
+
+static int sof_ipc4_widget_setup_comp_src(struct snd_sof_widget *swidget)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct snd_sof_pipeline *spipe = swidget->spipe;
+ struct sof_ipc4_src *src;
+ int ret;
+
+ dev_dbg(scomp->dev, "Updating IPC structure for %s\n", swidget->widget->name);
+
+ src = kzalloc(sizeof(*src), GFP_KERNEL);
+ if (!src)
+ return -ENOMEM;
+
+ swidget->private = src;
+
+ ret = sof_ipc4_get_audio_fmt(scomp, swidget, &src->available_fmt,
+ &src->data.base_config);
+ if (ret)
+ goto err;
+
+ ret = sof_update_ipc_object(scomp, &src->data, SOF_SRC_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(*src), 1);
+ if (ret) {
+ dev_err(scomp->dev, "Parsing SRC tokens failed\n");
+ goto err;
+ }
+
+ spipe->core_mask |= BIT(swidget->core);
+
+ dev_dbg(scomp->dev, "SRC sink rate %d\n", src->data.sink_rate);
+
+ ret = sof_ipc4_widget_setup_msg(swidget, &src->msg);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ sof_ipc4_free_audio_fmt(&src->available_fmt);
+ kfree(src);
+ swidget->private = NULL;
+ return ret;
+}
+
+static void sof_ipc4_widget_free_comp_src(struct snd_sof_widget *swidget)
+{
+ struct sof_ipc4_src *src = swidget->private;
+
+ if (!src)
+ return;
+
+ sof_ipc4_free_audio_fmt(&src->available_fmt);
+ kfree(swidget->private);
+ swidget->private = NULL;
+}
+
+static void sof_ipc4_widget_free_comp_mixer(struct snd_sof_widget *swidget)
+{
+ struct sof_ipc4_mixer *mixer = swidget->private;
+
+ if (!mixer)
+ return;
+
+ sof_ipc4_free_audio_fmt(&mixer->available_fmt);
+ kfree(swidget->private);
+ swidget->private = NULL;
+}
+
+/*
+ * Add the process modules support. The process modules are defined as snd_soc_dapm_effect modules.
+ */
+static int sof_ipc4_widget_setup_comp_process(struct snd_sof_widget *swidget)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct sof_ipc4_fw_module *fw_module;
+ struct snd_sof_pipeline *spipe = swidget->spipe;
+ struct sof_ipc4_process *process;
+ void *cfg;
+ int ret;
+
+ process = kzalloc(sizeof(*process), GFP_KERNEL);
+ if (!process)
+ return -ENOMEM;
+
+ swidget->private = process;
+
+ ret = sof_ipc4_get_audio_fmt(scomp, swidget, &process->available_fmt,
+ &process->base_config);
+ if (ret)
+ goto err;
+
+ ret = sof_ipc4_widget_setup_msg(swidget, &process->msg);
+ if (ret)
+ goto err;
+
+ /* parse process init module payload config type from module info */
+ fw_module = swidget->module_info;
+ process->init_config = FIELD_GET(SOF_IPC4_MODULE_INIT_CONFIG_MASK,
+ fw_module->man4_module_entry.type);
+
+ process->ipc_config_size = sizeof(struct sof_ipc4_base_module_cfg);
+
+ /* allocate memory for base config extension if needed */
+ if (process->init_config == SOF_IPC4_MODULE_INIT_CONFIG_TYPE_BASE_CFG_WITH_EXT) {
+ struct sof_ipc4_base_module_cfg_ext *base_cfg_ext;
+ u32 ext_size = struct_size(base_cfg_ext, pin_formats,
+ size_add(swidget->num_input_pins,
+ swidget->num_output_pins));
+
+ base_cfg_ext = kzalloc(ext_size, GFP_KERNEL);
+ if (!base_cfg_ext) {
+ ret = -ENOMEM;
+ goto free_available_fmt;
+ }
+
+ base_cfg_ext->num_input_pin_fmts = swidget->num_input_pins;
+ base_cfg_ext->num_output_pin_fmts = swidget->num_output_pins;
+ process->base_config_ext = base_cfg_ext;
+ process->base_config_ext_size = ext_size;
+ process->ipc_config_size += ext_size;
+ }
+
+ cfg = kzalloc(process->ipc_config_size, GFP_KERNEL);
+ if (!cfg) {
+ ret = -ENOMEM;
+ goto free_base_cfg_ext;
+ }
+
+ process->ipc_config_data = cfg;
+
+ sof_ipc4_widget_update_kcontrol_module_id(swidget);
+
+ /* set pipeline core mask to keep track of the core the module is scheduled to run on */
+ spipe->core_mask |= BIT(swidget->core);
+
+ return 0;
+free_base_cfg_ext:
+ kfree(process->base_config_ext);
+ process->base_config_ext = NULL;
+free_available_fmt:
+ sof_ipc4_free_audio_fmt(&process->available_fmt);
+err:
+ kfree(process);
+ swidget->private = NULL;
+ return ret;
+}
+
+static void sof_ipc4_widget_free_comp_process(struct snd_sof_widget *swidget)
+{
+ struct sof_ipc4_process *process = swidget->private;
+
+ if (!process)
+ return;
+
+ kfree(process->ipc_config_data);
+ kfree(process->base_config_ext);
+ sof_ipc4_free_audio_fmt(&process->available_fmt);
+ kfree(swidget->private);
+ swidget->private = NULL;
+}
+
+static void
+sof_ipc4_update_resource_usage(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget,
+ struct sof_ipc4_base_module_cfg *base_config)
+{
+ struct sof_ipc4_fw_module *fw_module = swidget->module_info;
+ struct snd_sof_widget *pipe_widget;
+ struct sof_ipc4_pipeline *pipeline;
+ int task_mem, queue_mem;
+ int ibs, bss, total;
+
+ ibs = base_config->ibs;
+ bss = base_config->is_pages;
+
+ task_mem = SOF_IPC4_PIPELINE_OBJECT_SIZE;
+ task_mem += SOF_IPC4_MODULE_INSTANCE_LIST_ITEM_SIZE + bss;
+
+ if (fw_module->man4_module_entry.type & SOF_IPC4_MODULE_LL) {
+ task_mem += SOF_IPC4_FW_ROUNDUP(SOF_IPC4_LL_TASK_OBJECT_SIZE);
+ task_mem += SOF_IPC4_FW_MAX_QUEUE_COUNT * SOF_IPC4_MODULE_INSTANCE_LIST_ITEM_SIZE;
+ task_mem += SOF_IPC4_LL_TASK_LIST_ITEM_SIZE;
+ } else {
+ task_mem += SOF_IPC4_FW_ROUNDUP(SOF_IPC4_DP_TASK_OBJECT_SIZE);
+ task_mem += SOF_IPC4_DP_TASK_LIST_SIZE;
+ }
+
+ ibs = SOF_IPC4_FW_ROUNDUP(ibs);
+ queue_mem = SOF_IPC4_FW_MAX_QUEUE_COUNT * (SOF_IPC4_DATA_QUEUE_OBJECT_SIZE + ibs);
+
+ total = SOF_IPC4_FW_PAGE(task_mem + queue_mem);
+
+ pipe_widget = swidget->spipe->pipe_widget;
+ pipeline = pipe_widget->private;
+ pipeline->mem_usage += total;
+
+ /* Update base_config->cpc from the module manifest */
+ sof_ipc4_update_cpc_from_manifest(sdev, fw_module, base_config);
+
+ if (ignore_cpc) {
+ dev_dbg(sdev->dev, "%s: ibs / obs: %u / %u, forcing cpc to 0 from %u\n",
+ swidget->widget->name, base_config->ibs, base_config->obs,
+ base_config->cpc);
+ base_config->cpc = 0;
+ } else {
+ dev_dbg(sdev->dev, "%s: ibs / obs / cpc: %u / %u / %u\n",
+ swidget->widget->name, base_config->ibs, base_config->obs,
+ base_config->cpc);
+ }
+}
+
+static int sof_ipc4_widget_assign_instance_id(struct snd_sof_dev *sdev,
+ struct snd_sof_widget *swidget)
+{
+ struct sof_ipc4_fw_module *fw_module = swidget->module_info;
+ int max_instances = fw_module->man4_module_entry.instance_max_count;
+
+ swidget->instance_id = ida_alloc_max(&fw_module->m_ida, max_instances, GFP_KERNEL);
+ if (swidget->instance_id < 0) {
+ dev_err(sdev->dev, "failed to assign instance id for widget %s",
+ swidget->widget->name);
+ return swidget->instance_id;
+ }
+
+ return 0;
+}
+
+/* update hw_params based on the audio stream format */
+static int sof_ipc4_update_hw_params(struct snd_sof_dev *sdev, struct snd_pcm_hw_params *params,
+ struct sof_ipc4_audio_format *fmt)
+{
+ snd_pcm_format_t snd_fmt;
+ struct snd_interval *i;
+ struct snd_mask *m;
+ int valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg);
+ unsigned int channels, rate;
+
+ switch (valid_bits) {
+ case 16:
+ snd_fmt = SNDRV_PCM_FORMAT_S16_LE;
+ break;
+ case 24:
+ snd_fmt = SNDRV_PCM_FORMAT_S24_LE;
+ break;
+ case 32:
+ snd_fmt = SNDRV_PCM_FORMAT_S32_LE;
+ break;
+ default:
+ dev_err(sdev->dev, "invalid PCM valid_bits %d\n", valid_bits);
+ return -EINVAL;
+ }
+
+ m = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ snd_mask_none(m);
+ snd_mask_set_format(m, snd_fmt);
+
+ rate = fmt->sampling_frequency;
+ i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ i->min = rate;
+ i->max = rate;
+
+ channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg);
+ i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+ i->min = channels;
+ i->max = channels;
+
+ return 0;
+}
+
+static bool sof_ipc4_is_single_format(struct snd_sof_dev *sdev,
+ struct sof_ipc4_pin_format *pin_fmts, u32 pin_fmts_size)
+{
+ struct sof_ipc4_audio_format *fmt;
+ u32 rate, channels, valid_bits;
+ int i;
+
+ fmt = &pin_fmts[0].audio_fmt;
+ rate = fmt->sampling_frequency;
+ channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg);
+ valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg);
+
+ /* check if all output formats in topology are the same */
+ for (i = 1; i < pin_fmts_size; i++) {
+ u32 _rate, _channels, _valid_bits;
+
+ fmt = &pin_fmts[i].audio_fmt;
+ _rate = fmt->sampling_frequency;
+ _channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg);
+ _valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg);
+
+ if (_rate != rate || _channels != channels || _valid_bits != valid_bits)
+ return false;
+ }
+
+ return true;
+}
+
+static int sof_ipc4_init_output_audio_fmt(struct snd_sof_dev *sdev,
+ struct sof_ipc4_base_module_cfg *base_config,
+ struct sof_ipc4_available_audio_format *available_fmt,
+ u32 out_ref_rate, u32 out_ref_channels,
+ u32 out_ref_valid_bits)
+{
+ struct sof_ipc4_audio_format *out_fmt;
+ bool single_format;
+ int i;
+
+ if (!available_fmt->num_output_formats)
+ return -EINVAL;
+
+ single_format = sof_ipc4_is_single_format(sdev, available_fmt->output_pin_fmts,
+ available_fmt->num_output_formats);
+
+ /* pick the first format if there's only one available or if all formats are the same */
+ if (single_format) {
+ base_config->obs = available_fmt->output_pin_fmts[0].buffer_size;
+ return 0;
+ }
+
+ /*
+ * if there are multiple output formats, then choose the output format that matches
+ * the reference params
+ */
+ for (i = 0; i < available_fmt->num_output_formats; i++) {
+ u32 _out_rate, _out_channels, _out_valid_bits;
+
+ out_fmt = &available_fmt->output_pin_fmts[i].audio_fmt;
+ _out_rate = out_fmt->sampling_frequency;
+ _out_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(out_fmt->fmt_cfg);
+ _out_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(out_fmt->fmt_cfg);
+
+ if (_out_rate == out_ref_rate && _out_channels == out_ref_channels &&
+ _out_valid_bits == out_ref_valid_bits) {
+ base_config->obs = available_fmt->output_pin_fmts[i].buffer_size;
+ return i;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int sof_ipc4_get_valid_bits(struct snd_sof_dev *sdev, struct snd_pcm_hw_params *params)
+{
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ return 16;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ return 24;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ return 32;
+ default:
+ dev_err(sdev->dev, "invalid pcm frame format %d\n", params_format(params));
+ return -EINVAL;
+ }
+}
+
+static int sof_ipc4_init_input_audio_fmt(struct snd_sof_dev *sdev,
+ struct snd_sof_widget *swidget,
+ struct sof_ipc4_base_module_cfg *base_config,
+ struct snd_pcm_hw_params *params,
+ struct sof_ipc4_available_audio_format *available_fmt)
+{
+ struct sof_ipc4_pin_format *pin_fmts = available_fmt->input_pin_fmts;
+ u32 pin_fmts_size = available_fmt->num_input_formats;
+ u32 valid_bits;
+ u32 channels;
+ u32 rate;
+ bool single_format;
+ int sample_valid_bits;
+ int i = 0;
+
+ if (!available_fmt->num_input_formats) {
+ dev_err(sdev->dev, "no input formats for %s\n", swidget->widget->name);
+ return -EINVAL;
+ }
+
+ single_format = sof_ipc4_is_single_format(sdev, available_fmt->input_pin_fmts,
+ available_fmt->num_input_formats);
+ if (single_format)
+ goto in_fmt;
+
+ sample_valid_bits = sof_ipc4_get_valid_bits(sdev, params);
+ if (sample_valid_bits < 0)
+ return sample_valid_bits;
+
+ /*
+ * Search supported input audio formats with pin index 0 to match rate, channels and
+ * sample_valid_bits from reference params
+ */
+ for (i = 0; i < pin_fmts_size; i++) {
+ struct sof_ipc4_audio_format *fmt = &pin_fmts[i].audio_fmt;
+
+ if (pin_fmts[i].pin_index)
+ continue;
+
+ rate = fmt->sampling_frequency;
+ channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg);
+ valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg);
+ if (params_rate(params) == rate && params_channels(params) == channels &&
+ sample_valid_bits == valid_bits) {
+ dev_dbg(sdev->dev, "matched audio format index for %uHz, %ubit, %u channels: %d\n",
+ rate, valid_bits, channels, i);
+ break;
+ }
+ }
+
+ if (i == pin_fmts_size) {
+ dev_err(sdev->dev, "%s: Unsupported audio format: %uHz, %ubit, %u channels\n",
+ __func__, params_rate(params), sample_valid_bits, params_channels(params));
+ return -EINVAL;
+ }
+
+in_fmt:
+ /* copy input format */
+ if (available_fmt->num_input_formats && i < available_fmt->num_input_formats) {
+ memcpy(&base_config->audio_fmt, &available_fmt->input_pin_fmts[i].audio_fmt,
+ sizeof(struct sof_ipc4_audio_format));
+
+ /* set base_cfg ibs/obs */
+ base_config->ibs = available_fmt->input_pin_fmts[i].buffer_size;
+
+ dev_dbg(sdev->dev, "Init input audio formats for %s\n", swidget->widget->name);
+ sof_ipc4_dbg_audio_format(sdev->dev, &available_fmt->input_pin_fmts[i], 1);
+ }
+
+ return i;
+}
+
+static void sof_ipc4_unprepare_copier_module(struct snd_sof_widget *swidget)
+{
+ struct sof_ipc4_copier *ipc4_copier = NULL;
+ struct snd_sof_widget *pipe_widget;
+ struct sof_ipc4_pipeline *pipeline;
+
+ /* reset pipeline memory usage */
+ pipe_widget = swidget->spipe->pipe_widget;
+ pipeline = pipe_widget->private;
+ pipeline->mem_usage = 0;
+
+ if (WIDGET_IS_AIF(swidget->id) || swidget->id == snd_soc_dapm_buffer) {
+ if (pipeline->use_chain_dma) {
+ pipeline->msg.primary = 0;
+ pipeline->msg.extension = 0;
+ }
+ ipc4_copier = swidget->private;
+ } else if (WIDGET_IS_DAI(swidget->id)) {
+ struct snd_sof_dai *dai = swidget->private;
+
+ ipc4_copier = dai->private;
+
+ if (pipeline->use_chain_dma) {
+ pipeline->msg.primary = 0;
+ pipeline->msg.extension = 0;
+ }
+
+ if (ipc4_copier->dai_type == SOF_DAI_INTEL_ALH) {
+ struct sof_ipc4_copier_data *copier_data = &ipc4_copier->data;
+ struct sof_ipc4_alh_configuration_blob *blob;
+ unsigned int group_id;
+
+ blob = (struct sof_ipc4_alh_configuration_blob *)ipc4_copier->copier_config;
+ if (blob->alh_cfg.device_count > 1) {
+ group_id = SOF_IPC4_NODE_INDEX(ipc4_copier->data.gtw_cfg.node_id) -
+ ALH_MULTI_GTW_BASE;
+ ida_free(&alh_group_ida, group_id);
+ }
+
+ /* clear the node ID */
+ copier_data->gtw_cfg.node_id &= ~SOF_IPC4_NODE_INDEX_MASK;
+ }
+ }
+
+ if (ipc4_copier) {
+ kfree(ipc4_copier->ipc_config_data);
+ ipc4_copier->ipc_config_data = NULL;
+ ipc4_copier->ipc_config_size = 0;
+ }
+}
+
+#if IS_ENABLED(CONFIG_ACPI) && IS_ENABLED(CONFIG_SND_INTEL_NHLT)
+static int snd_sof_get_hw_config_params(struct snd_sof_dev *sdev, struct snd_sof_dai *dai,
+ int *sample_rate, int *channel_count, int *bit_depth)
+{
+ struct snd_soc_tplg_hw_config *hw_config;
+ struct snd_sof_dai_link *slink;
+ bool dai_link_found = false;
+ bool hw_cfg_found = false;
+ int i;
+
+ /* get current hw_config from link */
+ list_for_each_entry(slink, &sdev->dai_link_list, list) {
+ if (!strcmp(slink->link->name, dai->name)) {
+ dai_link_found = true;
+ break;
+ }
+ }
+
+ if (!dai_link_found) {
+ dev_err(sdev->dev, "%s: no DAI link found for DAI %s\n", __func__, dai->name);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < slink->num_hw_configs; i++) {
+ hw_config = &slink->hw_configs[i];
+ if (dai->current_config == le32_to_cpu(hw_config->id)) {
+ hw_cfg_found = true;
+ break;
+ }
+ }
+
+ if (!hw_cfg_found) {
+ dev_err(sdev->dev, "%s: no matching hw_config found for DAI %s\n", __func__,
+ dai->name);
+ return -EINVAL;
+ }
+
+ *bit_depth = le32_to_cpu(hw_config->tdm_slot_width);
+ *channel_count = le32_to_cpu(hw_config->tdm_slots);
+ *sample_rate = le32_to_cpu(hw_config->fsync_rate);
+
+ dev_dbg(sdev->dev, "sample rate: %d sample width: %d channels: %d\n",
+ *sample_rate, *bit_depth, *channel_count);
+
+ return 0;
+}
+
+static int snd_sof_get_nhlt_endpoint_data(struct snd_sof_dev *sdev, struct snd_sof_dai *dai,
+ struct snd_pcm_hw_params *params, u32 dai_index,
+ u32 linktype, u8 dir, u32 **dst, u32 *len)
+{
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+ struct nhlt_specific_cfg *cfg;
+ int sample_rate, channel_count;
+ int bit_depth, ret;
+ u32 nhlt_type;
+
+ /* convert to NHLT type */
+ switch (linktype) {
+ case SOF_DAI_INTEL_DMIC:
+ nhlt_type = NHLT_LINK_DMIC;
+ bit_depth = params_width(params);
+ channel_count = params_channels(params);
+ sample_rate = params_rate(params);
+ break;
+ case SOF_DAI_INTEL_SSP:
+ nhlt_type = NHLT_LINK_SSP;
+ ret = snd_sof_get_hw_config_params(sdev, dai, &sample_rate, &channel_count,
+ &bit_depth);
+ if (ret < 0)
+ return ret;
+ break;
+ default:
+ return 0;
+ }
+
+ dev_dbg(sdev->dev, "dai index %d nhlt type %d direction %d\n",
+ dai_index, nhlt_type, dir);
+
+ /* find NHLT blob with matching params */
+ cfg = intel_nhlt_get_endpoint_blob(sdev->dev, ipc4_data->nhlt, dai_index, nhlt_type,
+ bit_depth, bit_depth, channel_count, sample_rate,
+ dir, 0);
+
+ if (!cfg) {
+ dev_err(sdev->dev,
+ "no matching blob for sample rate: %d sample width: %d channels: %d\n",
+ sample_rate, bit_depth, channel_count);
+ return -EINVAL;
+ }
+
+ /* config length should be in dwords */
+ *len = cfg->size >> 2;
+ *dst = (u32 *)cfg->caps;
+
+ return 0;
+}
+#else
+static int snd_sof_get_nhlt_endpoint_data(struct snd_sof_dev *sdev, struct snd_sof_dai *dai,
+ struct snd_pcm_hw_params *params, u32 dai_index,
+ u32 linktype, u8 dir, u32 **dst, u32 *len)
+{
+ return 0;
+}
+#endif
+
+bool sof_ipc4_copier_is_single_format(struct snd_sof_dev *sdev,
+ struct sof_ipc4_pin_format *pin_fmts,
+ u32 pin_fmts_size)
+{
+ struct sof_ipc4_audio_format *fmt;
+ u32 valid_bits;
+ int i;
+
+ fmt = &pin_fmts[0].audio_fmt;
+ valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg);
+
+ /* check if all formats in topology are the same */
+ for (i = 1; i < pin_fmts_size; i++) {
+ u32 _valid_bits;
+
+ fmt = &pin_fmts[i].audio_fmt;
+ _valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg);
+
+ if (_valid_bits != valid_bits)
+ return false;
+ }
+
+ return true;
+}
+
+static int
+sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget,
+ struct snd_pcm_hw_params *fe_params,
+ struct snd_sof_platform_stream_params *platform_params,
+ struct snd_pcm_hw_params *pipeline_params, int dir)
+{
+ struct sof_ipc4_available_audio_format *available_fmt;
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct sof_ipc4_copier_data *copier_data;
+ struct snd_pcm_hw_params *ref_params;
+ struct sof_ipc4_copier *ipc4_copier;
+ struct snd_sof_dai *dai;
+ u32 gtw_cfg_config_length;
+ u32 dma_config_tlv_size = 0;
+ void **ipc_config_data;
+ int *ipc_config_size;
+ u32 **data;
+ int ipc_size, ret, out_ref_valid_bits;
+ u32 out_ref_rate, out_ref_channels;
+ u32 deep_buffer_dma_ms = 0;
+ int output_fmt_index;
+ bool single_output_format;
+
+ dev_dbg(sdev->dev, "copier %s, type %d", swidget->widget->name, swidget->id);
+
+ switch (swidget->id) {
+ case snd_soc_dapm_aif_in:
+ case snd_soc_dapm_aif_out:
+ {
+ struct sof_ipc4_gtw_attributes *gtw_attr;
+ struct snd_sof_widget *pipe_widget;
+ struct sof_ipc4_pipeline *pipeline;
+
+ /* parse the deep buffer dma size */
+ ret = sof_update_ipc_object(scomp, &deep_buffer_dma_ms,
+ SOF_COPIER_DEEP_BUFFER_TOKENS, swidget->tuples,
+ swidget->num_tuples, sizeof(u32), 1);
+ if (ret) {
+ dev_err(scomp->dev, "Failed to parse deep buffer dma size for %s\n",
+ swidget->widget->name);
+ return ret;
+ }
+
+ ipc4_copier = (struct sof_ipc4_copier *)swidget->private;
+ gtw_attr = ipc4_copier->gtw_attr;
+ copier_data = &ipc4_copier->data;
+ available_fmt = &ipc4_copier->available_fmt;
+
+ pipe_widget = swidget->spipe->pipe_widget;
+ pipeline = pipe_widget->private;
+
+ if (pipeline->use_chain_dma) {
+ u32 host_dma_id;
+ u32 fifo_size;
+
+ host_dma_id = platform_params->stream_tag - 1;
+ pipeline->msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_HOST_ID(host_dma_id);
+
+ /* Set SCS bit for S16_LE format only */
+ if (params_format(fe_params) == SNDRV_PCM_FORMAT_S16_LE)
+ pipeline->msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_SCS_MASK;
+
+ /*
+ * Despite its name the bitfield 'fifo_size' is used to define DMA buffer
+ * size. The expression calculates 2ms buffer size.
+ */
+ fifo_size = DIV_ROUND_UP((SOF_IPC4_CHAIN_DMA_BUF_SIZE_MS *
+ params_rate(fe_params) *
+ params_channels(fe_params) *
+ params_physical_width(fe_params)), 8000);
+ pipeline->msg.extension |= SOF_IPC4_GLB_EXT_CHAIN_DMA_FIFO_SIZE(fifo_size);
+
+ /*
+ * Chain DMA does not support stream timestamping, set node_id to invalid
+ * to skip the code in sof_ipc4_get_stream_start_offset().
+ */
+ copier_data->gtw_cfg.node_id = SOF_IPC4_INVALID_NODE_ID;
+
+ return 0;
+ }
+
+ /*
+ * Use the input_pin_fmts to match pcm params for playback and the output_pin_fmts
+ * for capture.
+ */
+ if (dir == SNDRV_PCM_STREAM_PLAYBACK)
+ ref_params = fe_params;
+ else
+ ref_params = pipeline_params;
+
+ copier_data->gtw_cfg.node_id &= ~SOF_IPC4_NODE_INDEX_MASK;
+ copier_data->gtw_cfg.node_id |=
+ SOF_IPC4_NODE_INDEX(platform_params->stream_tag - 1);
+
+ /* set gateway attributes */
+ gtw_attr->lp_buffer_alloc = pipeline->lp_mode;
+ break;
+ }
+ case snd_soc_dapm_dai_in:
+ case snd_soc_dapm_dai_out:
+ {
+ struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget;
+ struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
+
+ if (pipeline->use_chain_dma)
+ return 0;
+
+ dai = swidget->private;
+
+ ipc4_copier = (struct sof_ipc4_copier *)dai->private;
+ copier_data = &ipc4_copier->data;
+ available_fmt = &ipc4_copier->available_fmt;
+
+ /*
+ * When there is format conversion within a pipeline, the number of supported
+ * output formats is typically limited to just 1 for the DAI copiers. But when there
+ * is no format conversion, the DAI copiers input format must match that of the
+ * FE hw_params for capture and the pipeline params for playback.
+ */
+ if (dir == SNDRV_PCM_STREAM_PLAYBACK)
+ ref_params = pipeline_params;
+ else
+ ref_params = fe_params;
+
+ ret = snd_sof_get_nhlt_endpoint_data(sdev, dai, fe_params, ipc4_copier->dai_index,
+ ipc4_copier->dai_type, dir,
+ &ipc4_copier->copier_config,
+ &copier_data->gtw_cfg.config_length);
+ if (ret < 0)
+ return ret;
+
+ break;
+ }
+ case snd_soc_dapm_buffer:
+ {
+ ipc4_copier = (struct sof_ipc4_copier *)swidget->private;
+ copier_data = &ipc4_copier->data;
+ available_fmt = &ipc4_copier->available_fmt;
+ ref_params = pipeline_params;
+
+ break;
+ }
+ default:
+ dev_err(sdev->dev, "unsupported type %d for copier %s",
+ swidget->id, swidget->widget->name);
+ return -EINVAL;
+ }
+
+ /* set input and output audio formats */
+ ret = sof_ipc4_init_input_audio_fmt(sdev, swidget, &copier_data->base_config, ref_params,
+ available_fmt);
+ if (ret < 0)
+ return ret;
+
+ /* set the reference params for output format selection */
+ single_output_format = sof_ipc4_copier_is_single_format(sdev,
+ available_fmt->output_pin_fmts,
+ available_fmt->num_output_formats);
+ switch (swidget->id) {
+ case snd_soc_dapm_aif_in:
+ case snd_soc_dapm_dai_out:
+ case snd_soc_dapm_buffer:
+ {
+ struct sof_ipc4_audio_format *in_fmt;
+
+ in_fmt = &available_fmt->input_pin_fmts[ret].audio_fmt;
+ out_ref_rate = in_fmt->sampling_frequency;
+ out_ref_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_fmt->fmt_cfg);
+
+ if (!single_output_format)
+ out_ref_valid_bits =
+ SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_fmt->fmt_cfg);
+ break;
+ }
+ case snd_soc_dapm_aif_out:
+ case snd_soc_dapm_dai_in:
+ out_ref_rate = params_rate(fe_params);
+ out_ref_channels = params_channels(fe_params);
+ if (!single_output_format) {
+ out_ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params);
+ if (out_ref_valid_bits < 0)
+ return out_ref_valid_bits;
+ }
+ break;
+ default:
+ /*
+ * Unsupported type should be caught by the former switch default
+ * case, this should never happen in reality.
+ */
+ return -EINVAL;
+ }
+
+ /*
+ * if the output format is the same across all available output formats, choose
+ * that as the reference.
+ */
+ if (single_output_format) {
+ struct sof_ipc4_audio_format *out_fmt;
+
+ out_fmt = &available_fmt->output_pin_fmts[0].audio_fmt;
+ out_ref_valid_bits =
+ SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(out_fmt->fmt_cfg);
+ }
+
+ dev_dbg(sdev->dev, "copier %s: reference output rate %d, channels %d valid_bits %d\n",
+ swidget->widget->name, out_ref_rate, out_ref_channels, out_ref_valid_bits);
+
+ output_fmt_index = sof_ipc4_init_output_audio_fmt(sdev, &copier_data->base_config,
+ available_fmt, out_ref_rate,
+ out_ref_channels, out_ref_valid_bits);
+ if (output_fmt_index < 0) {
+ dev_err(sdev->dev, "Failed to initialize output format for %s",
+ swidget->widget->name);
+ return output_fmt_index;
+ }
+
+ /*
+ * Set the output format. Current topology defines pin 0 input and output formats in pairs.
+ * This assumes that the pin 0 formats are defined before all other pins.
+ * So pick the output audio format with the same index as the chosen
+ * input format. This logic will need to be updated when the format definitions
+ * in topology change.
+ */
+ memcpy(&copier_data->out_format,
+ &available_fmt->output_pin_fmts[output_fmt_index].audio_fmt,
+ sizeof(struct sof_ipc4_audio_format));
+ dev_dbg(sdev->dev, "Output audio format for %s\n", swidget->widget->name);
+ sof_ipc4_dbg_audio_format(sdev->dev, &available_fmt->output_pin_fmts[output_fmt_index], 1);
+
+ switch (swidget->id) {
+ case snd_soc_dapm_dai_in:
+ case snd_soc_dapm_dai_out:
+ {
+ /*
+ * Only SOF_DAI_INTEL_ALH needs copier_data to set blob.
+ * That's why only ALH dai's blob is set after sof_ipc4_init_input_audio_fmt
+ */
+ if (ipc4_copier->dai_type == SOF_DAI_INTEL_ALH) {
+ struct sof_ipc4_alh_configuration_blob *blob;
+ struct sof_ipc4_copier_data *alh_data;
+ struct sof_ipc4_copier *alh_copier;
+ struct snd_sof_widget *w;
+ u32 ch_count = 0;
+ u32 ch_mask = 0;
+ u32 ch_map;
+ u32 step;
+ u32 mask;
+ int i;
+
+ blob = (struct sof_ipc4_alh_configuration_blob *)ipc4_copier->copier_config;
+
+ blob->gw_attr.lp_buffer_alloc = 0;
+
+ /* Get channel_mask from ch_map */
+ ch_map = copier_data->base_config.audio_fmt.ch_map;
+ for (i = 0; ch_map; i++) {
+ if ((ch_map & 0xf) != 0xf) {
+ ch_mask |= BIT(i);
+ ch_count++;
+ }
+ ch_map >>= 4;
+ }
+
+ step = ch_count / blob->alh_cfg.device_count;
+ mask = GENMASK(step - 1, 0);
+ /*
+ * Set each gtw_cfg.node_id to blob->alh_cfg.mapping[]
+ * for all widgets with the same stream name
+ */
+ i = 0;
+ list_for_each_entry(w, &sdev->widget_list, list) {
+ if (w->widget->sname &&
+ strcmp(w->widget->sname, swidget->widget->sname))
+ continue;
+
+ dai = w->private;
+ alh_copier = (struct sof_ipc4_copier *)dai->private;
+ alh_data = &alh_copier->data;
+ blob->alh_cfg.mapping[i].device = alh_data->gtw_cfg.node_id;
+ /*
+ * Set the same channel mask for playback as the audio data is
+ * duplicated for all speakers. For capture, split the channels
+ * among the aggregated DAIs. For example, with 4 channels on 2
+ * aggregated DAIs, the channel_mask should be 0x3 and 0xc for the
+ * two DAI's.
+ * The channel masks used depend on the cpu_dais used in the
+ * dailink at the machine driver level, which actually comes from
+ * the tables in soc_acpi files depending on the _ADR and devID
+ * registers for each codec.
+ */
+ if (w->id == snd_soc_dapm_dai_in)
+ blob->alh_cfg.mapping[i].channel_mask = ch_mask;
+ else
+ blob->alh_cfg.mapping[i].channel_mask = mask << (step * i);
+
+ i++;
+ }
+ if (blob->alh_cfg.device_count > 1) {
+ int group_id;
+
+ group_id = ida_alloc_max(&alh_group_ida, ALH_MULTI_GTW_COUNT - 1,
+ GFP_KERNEL);
+
+ if (group_id < 0)
+ return group_id;
+
+ /* add multi-gateway base */
+ group_id += ALH_MULTI_GTW_BASE;
+ copier_data->gtw_cfg.node_id &= ~SOF_IPC4_NODE_INDEX_MASK;
+ copier_data->gtw_cfg.node_id |= SOF_IPC4_NODE_INDEX(group_id);
+ }
+ }
+ }
+ }
+
+ /* modify the input params for the next widget */
+ ret = sof_ipc4_update_hw_params(sdev, pipeline_params, &copier_data->out_format);
+ if (ret)
+ return ret;
+
+ /*
+ * Set the gateway dma_buffer_size to 2ms buffer size to meet the FW expectation. In the
+ * deep buffer case, set the dma_buffer_size depending on the deep_buffer_dma_ms set
+ * in topology.
+ */
+ switch (swidget->id) {
+ case snd_soc_dapm_dai_in:
+ copier_data->gtw_cfg.dma_buffer_size =
+ SOF_IPC4_MIN_DMA_BUFFER_SIZE * copier_data->base_config.ibs;
+ break;
+ case snd_soc_dapm_aif_in:
+ copier_data->gtw_cfg.dma_buffer_size =
+ max((u32)SOF_IPC4_MIN_DMA_BUFFER_SIZE, deep_buffer_dma_ms) *
+ copier_data->base_config.ibs;
+ dev_dbg(sdev->dev, "copier %s, dma buffer%s: %u ms (%u bytes)",
+ swidget->widget->name,
+ deep_buffer_dma_ms ? " (using Deep Buffer)" : "",
+ max((u32)SOF_IPC4_MIN_DMA_BUFFER_SIZE, deep_buffer_dma_ms),
+ copier_data->gtw_cfg.dma_buffer_size);
+ break;
+ case snd_soc_dapm_dai_out:
+ case snd_soc_dapm_aif_out:
+ copier_data->gtw_cfg.dma_buffer_size =
+ SOF_IPC4_MIN_DMA_BUFFER_SIZE * copier_data->base_config.obs;
+ break;
+ default:
+ break;
+ }
+
+ data = &ipc4_copier->copier_config;
+ ipc_config_size = &ipc4_copier->ipc_config_size;
+ ipc_config_data = &ipc4_copier->ipc_config_data;
+
+ /* config_length is DWORD based */
+ gtw_cfg_config_length = copier_data->gtw_cfg.config_length * 4;
+ ipc_size = sizeof(*copier_data) + gtw_cfg_config_length;
+
+ if (ipc4_copier->dma_config_tlv.type == SOF_IPC4_GTW_DMA_CONFIG_ID &&
+ ipc4_copier->dma_config_tlv.length) {
+ dma_config_tlv_size = sizeof(ipc4_copier->dma_config_tlv) +
+ ipc4_copier->dma_config_tlv.dma_config.dma_priv_config_size;
+
+ /* paranoia check on TLV size/length */
+ if (dma_config_tlv_size != ipc4_copier->dma_config_tlv.length +
+ sizeof(uint32_t) * 2) {
+ dev_err(sdev->dev, "Invalid configuration, TLV size %d length %d\n",
+ dma_config_tlv_size, ipc4_copier->dma_config_tlv.length);
+ return -EINVAL;
+ }
+
+ ipc_size += dma_config_tlv_size;
+
+ /* we also need to increase the size at the gtw level */
+ copier_data->gtw_cfg.config_length += dma_config_tlv_size / 4;
+ }
+
+ dev_dbg(sdev->dev, "copier %s, IPC size is %d", swidget->widget->name, ipc_size);
+
+ *ipc_config_data = kzalloc(ipc_size, GFP_KERNEL);
+ if (!*ipc_config_data)
+ return -ENOMEM;
+
+ *ipc_config_size = ipc_size;
+
+ /* update pipeline memory usage */
+ sof_ipc4_update_resource_usage(sdev, swidget, &copier_data->base_config);
+
+ /* copy IPC data */
+ memcpy(*ipc_config_data, (void *)copier_data, sizeof(*copier_data));
+ if (gtw_cfg_config_length)
+ memcpy(*ipc_config_data + sizeof(*copier_data),
+ *data, gtw_cfg_config_length);
+
+ /* add DMA Config TLV, if configured */
+ if (dma_config_tlv_size)
+ memcpy(*ipc_config_data + sizeof(*copier_data) +
+ gtw_cfg_config_length,
+ &ipc4_copier->dma_config_tlv, dma_config_tlv_size);
+
+ /*
+ * Restore gateway config length now that IPC payload is prepared. This avoids
+ * counting the DMA CONFIG TLV multiple times
+ */
+ copier_data->gtw_cfg.config_length = gtw_cfg_config_length / 4;
+
+ return 0;
+}
+
+static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget,
+ struct snd_pcm_hw_params *fe_params,
+ struct snd_sof_platform_stream_params *platform_params,
+ struct snd_pcm_hw_params *pipeline_params, int dir)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct sof_ipc4_gain *gain = swidget->private;
+ struct sof_ipc4_available_audio_format *available_fmt = &gain->available_fmt;
+ struct sof_ipc4_audio_format *in_fmt;
+ u32 out_ref_rate, out_ref_channels, out_ref_valid_bits;
+ int ret;
+
+ ret = sof_ipc4_init_input_audio_fmt(sdev, swidget, &gain->data.base_config,
+ pipeline_params, available_fmt);
+ if (ret < 0)
+ return ret;
+
+ in_fmt = &available_fmt->input_pin_fmts[ret].audio_fmt;
+ out_ref_rate = in_fmt->sampling_frequency;
+ out_ref_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_fmt->fmt_cfg);
+ out_ref_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_fmt->fmt_cfg);
+
+ ret = sof_ipc4_init_output_audio_fmt(sdev, &gain->data.base_config, available_fmt,
+ out_ref_rate, out_ref_channels, out_ref_valid_bits);
+ if (ret < 0) {
+ dev_err(sdev->dev, "Failed to initialize output format for %s",
+ swidget->widget->name);
+ return ret;
+ }
+
+ /* update pipeline memory usage */
+ sof_ipc4_update_resource_usage(sdev, swidget, &gain->data.base_config);
+
+ return 0;
+}
+
+static int sof_ipc4_prepare_mixer_module(struct snd_sof_widget *swidget,
+ struct snd_pcm_hw_params *fe_params,
+ struct snd_sof_platform_stream_params *platform_params,
+ struct snd_pcm_hw_params *pipeline_params, int dir)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct sof_ipc4_mixer *mixer = swidget->private;
+ struct sof_ipc4_available_audio_format *available_fmt = &mixer->available_fmt;
+ struct sof_ipc4_audio_format *in_fmt;
+ u32 out_ref_rate, out_ref_channels, out_ref_valid_bits;
+ int ret;
+
+ ret = sof_ipc4_init_input_audio_fmt(sdev, swidget, &mixer->base_config,
+ pipeline_params, available_fmt);
+ if (ret < 0)
+ return ret;
+
+ in_fmt = &available_fmt->input_pin_fmts[ret].audio_fmt;
+ out_ref_rate = in_fmt->sampling_frequency;
+ out_ref_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_fmt->fmt_cfg);
+ out_ref_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_fmt->fmt_cfg);
+
+ ret = sof_ipc4_init_output_audio_fmt(sdev, &mixer->base_config, available_fmt,
+ out_ref_rate, out_ref_channels, out_ref_valid_bits);
+ if (ret < 0) {
+ dev_err(sdev->dev, "Failed to initialize output format for %s",
+ swidget->widget->name);
+ return ret;
+ }
+
+ /* update pipeline memory usage */
+ sof_ipc4_update_resource_usage(sdev, swidget, &mixer->base_config);
+
+ return 0;
+}
+
+static int sof_ipc4_prepare_src_module(struct snd_sof_widget *swidget,
+ struct snd_pcm_hw_params *fe_params,
+ struct snd_sof_platform_stream_params *platform_params,
+ struct snd_pcm_hw_params *pipeline_params, int dir)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct sof_ipc4_src *src = swidget->private;
+ struct sof_ipc4_available_audio_format *available_fmt = &src->available_fmt;
+ struct sof_ipc4_audio_format *out_audio_fmt;
+ struct sof_ipc4_audio_format *in_audio_fmt;
+ u32 out_ref_rate, out_ref_channels, out_ref_valid_bits;
+ int output_format_index, input_format_index;
+
+ input_format_index = sof_ipc4_init_input_audio_fmt(sdev, swidget, &src->data.base_config,
+ pipeline_params, available_fmt);
+ if (input_format_index < 0)
+ return input_format_index;
+
+ /*
+ * For playback, the SRC sink rate will be configured based on the requested output
+ * format, which is restricted to only deal with DAI's with a single format for now.
+ */
+ if (dir == SNDRV_PCM_STREAM_PLAYBACK && available_fmt->num_output_formats > 1) {
+ dev_err(sdev->dev, "Invalid number of output formats: %d for SRC %s\n",
+ available_fmt->num_output_formats, swidget->widget->name);
+ return -EINVAL;
+ }
+
+ /*
+ * SRC does not perform format conversion, so the output channels and valid bit depth must
+ * be the same as that of the input.
+ */
+ in_audio_fmt = &available_fmt->input_pin_fmts[input_format_index].audio_fmt;
+ out_ref_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_audio_fmt->fmt_cfg);
+ out_ref_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_audio_fmt->fmt_cfg);
+
+ /*
+ * For capture, the SRC module should convert the rate to match the rate requested by the
+ * PCM hw_params. Set the reference params based on the fe_params unconditionally as it
+ * will be ignored for playback anyway.
+ */
+ out_ref_rate = params_rate(fe_params);
+
+ output_format_index = sof_ipc4_init_output_audio_fmt(sdev, &src->data.base_config,
+ available_fmt, out_ref_rate,
+ out_ref_channels, out_ref_valid_bits);
+ if (output_format_index < 0) {
+ dev_err(sdev->dev, "Failed to initialize output format for %s",
+ swidget->widget->name);
+ return output_format_index;
+ }
+
+ /* update pipeline memory usage */
+ sof_ipc4_update_resource_usage(sdev, swidget, &src->data.base_config);
+
+ out_audio_fmt = &available_fmt->output_pin_fmts[output_format_index].audio_fmt;
+ src->data.sink_rate = out_audio_fmt->sampling_frequency;
+
+ /* update pipeline_params for sink widgets */
+ return sof_ipc4_update_hw_params(sdev, pipeline_params, out_audio_fmt);
+}
+
+static int
+sof_ipc4_process_set_pin_formats(struct snd_sof_widget *swidget, int pin_type)
+{
+ struct sof_ipc4_process *process = swidget->private;
+ struct sof_ipc4_base_module_cfg_ext *base_cfg_ext = process->base_config_ext;
+ struct sof_ipc4_available_audio_format *available_fmt = &process->available_fmt;
+ struct sof_ipc4_pin_format *pin_format, *format_list_to_search;
+ struct snd_soc_component *scomp = swidget->scomp;
+ int num_pins, format_list_count;
+ int pin_format_offset = 0;
+ int i, j;
+
+ /* set number of pins, offset of pin format and format list to search based on pin type */
+ if (pin_type == SOF_PIN_TYPE_INPUT) {
+ num_pins = swidget->num_input_pins;
+ format_list_to_search = available_fmt->input_pin_fmts;
+ format_list_count = available_fmt->num_input_formats;
+ } else {
+ num_pins = swidget->num_output_pins;
+ pin_format_offset = swidget->num_input_pins;
+ format_list_to_search = available_fmt->output_pin_fmts;
+ format_list_count = available_fmt->num_output_formats;
+ }
+
+ for (i = pin_format_offset; i < num_pins + pin_format_offset; i++) {
+ pin_format = &base_cfg_ext->pin_formats[i];
+
+ /* Pin 0 audio formats are derived from the base config input/output format */
+ if (i == pin_format_offset) {
+ if (pin_type == SOF_PIN_TYPE_INPUT) {
+ pin_format->buffer_size = process->base_config.ibs;
+ pin_format->audio_fmt = process->base_config.audio_fmt;
+ } else {
+ pin_format->buffer_size = process->base_config.obs;
+ pin_format->audio_fmt = process->output_format;
+ }
+ continue;
+ }
+
+ /*
+ * For all other pins, find the pin formats from those set in topology. If there
+ * is more than one format specified for a pin, this will pick the first available
+ * one.
+ */
+ for (j = 0; j < format_list_count; j++) {
+ struct sof_ipc4_pin_format *pin_format_item = &format_list_to_search[j];
+
+ if (pin_format_item->pin_index == i - pin_format_offset) {
+ *pin_format = *pin_format_item;
+ break;
+ }
+ }
+
+ if (j == format_list_count) {
+ dev_err(scomp->dev, "%s pin %d format not found for %s\n",
+ (pin_type == SOF_PIN_TYPE_INPUT) ? "input" : "output",
+ i - pin_format_offset, swidget->widget->name);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int sof_ipc4_process_add_base_cfg_extn(struct snd_sof_widget *swidget)
+{
+ int ret, i;
+
+ /* copy input and output pin formats */
+ for (i = 0; i <= SOF_PIN_TYPE_OUTPUT; i++) {
+ ret = sof_ipc4_process_set_pin_formats(swidget, i);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget,
+ struct snd_pcm_hw_params *fe_params,
+ struct snd_sof_platform_stream_params *platform_params,
+ struct snd_pcm_hw_params *pipeline_params, int dir)
+{
+ struct snd_soc_component *scomp = swidget->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct sof_ipc4_process *process = swidget->private;
+ struct sof_ipc4_available_audio_format *available_fmt = &process->available_fmt;
+ struct sof_ipc4_audio_format *in_fmt;
+ u32 out_ref_rate, out_ref_channels, out_ref_valid_bits;
+ void *cfg = process->ipc_config_data;
+ int output_fmt_index;
+ int ret;
+
+ ret = sof_ipc4_init_input_audio_fmt(sdev, swidget, &process->base_config,
+ pipeline_params, available_fmt);
+ if (ret < 0)
+ return ret;
+
+ in_fmt = &available_fmt->input_pin_fmts[ret].audio_fmt;
+ out_ref_rate = in_fmt->sampling_frequency;
+ out_ref_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_fmt->fmt_cfg);
+ out_ref_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_fmt->fmt_cfg);
+
+ output_fmt_index = sof_ipc4_init_output_audio_fmt(sdev, &process->base_config,
+ available_fmt, out_ref_rate,
+ out_ref_channels, out_ref_valid_bits);
+ if (output_fmt_index < 0 && available_fmt->num_output_formats) {
+ dev_err(sdev->dev, "Failed to initialize output format for %s",
+ swidget->widget->name);
+ return output_fmt_index;
+ }
+
+ /* copy Pin 0 output format */
+ if (available_fmt->num_output_formats &&
+ output_fmt_index < available_fmt->num_output_formats &&
+ !available_fmt->output_pin_fmts[output_fmt_index].pin_index) {
+ memcpy(&process->output_format,
+ &available_fmt->output_pin_fmts[output_fmt_index].audio_fmt,
+ sizeof(struct sof_ipc4_audio_format));
+
+ /* modify the pipeline params with the pin 0 output format */
+ ret = sof_ipc4_update_hw_params(sdev, pipeline_params, &process->output_format);
+ if (ret)
+ return ret;
+ }
+
+ /* update pipeline memory usage */
+ sof_ipc4_update_resource_usage(sdev, swidget, &process->base_config);
+
+ /* ipc_config_data is composed of the base_config followed by an optional extension */
+ memcpy(cfg, &process->base_config, sizeof(struct sof_ipc4_base_module_cfg));
+ cfg += sizeof(struct sof_ipc4_base_module_cfg);
+
+ if (process->init_config == SOF_IPC4_MODULE_INIT_CONFIG_TYPE_BASE_CFG_WITH_EXT) {
+ struct sof_ipc4_base_module_cfg_ext *base_cfg_ext = process->base_config_ext;
+
+ ret = sof_ipc4_process_add_base_cfg_extn(swidget);
+ if (ret < 0)
+ return ret;
+
+ memcpy(cfg, base_cfg_ext, process->base_config_ext_size);
+ }
+
+ return 0;
+}
+
+static int sof_ipc4_control_load_volume(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol)
+{
+ struct sof_ipc4_control_data *control_data;
+ struct sof_ipc4_msg *msg;
+ int i;
+
+ scontrol->size = struct_size(control_data, chanv, scontrol->num_channels);
+
+ /* scontrol->ipc_control_data will be freed in sof_control_unload */
+ scontrol->ipc_control_data = kzalloc(scontrol->size, GFP_KERNEL);
+ if (!scontrol->ipc_control_data)
+ return -ENOMEM;
+
+ control_data = scontrol->ipc_control_data;
+ control_data->index = scontrol->index;
+
+ msg = &control_data->msg;
+ msg->primary = SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_LARGE_CONFIG_SET);
+ msg->primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ msg->primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG);
+
+ /* volume controls with range 0-1 (off/on) are switch controls */
+ if (scontrol->max == 1)
+ msg->extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(SOF_IPC4_SWITCH_CONTROL_PARAM_ID);
+ else
+ msg->extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(SOF_IPC4_GAIN_PARAM_ID);
+
+ for (i = 0; i < scontrol->num_channels; i++) {
+ control_data->chanv[i].channel = i;
+ /*
+ * Default, initial values:
+ * - 0dB for volume controls
+ * - off (0) for switch controls - value already zero after
+ * memory allocation
+ */
+ if (scontrol->max > 1)
+ control_data->chanv[i].value = SOF_IPC4_VOL_ZERO_DB;
+ }
+
+ return 0;
+}
+
+static int sof_ipc4_control_load_enum(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol)
+{
+ struct sof_ipc4_control_data *control_data;
+ struct sof_ipc4_msg *msg;
+ int i;
+
+ scontrol->size = struct_size(control_data, chanv, scontrol->num_channels);
+
+ /* scontrol->ipc_control_data will be freed in sof_control_unload */
+ scontrol->ipc_control_data = kzalloc(scontrol->size, GFP_KERNEL);
+ if (!scontrol->ipc_control_data)
+ return -ENOMEM;
+
+ control_data = scontrol->ipc_control_data;
+ control_data->index = scontrol->index;
+
+ msg = &control_data->msg;
+ msg->primary = SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_LARGE_CONFIG_SET);
+ msg->primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ msg->primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG);
+
+ msg->extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(SOF_IPC4_ENUM_CONTROL_PARAM_ID);
+
+ /* Default, initial value for enums: first enum entry is selected (0) */
+ for (i = 0; i < scontrol->num_channels; i++)
+ control_data->chanv[i].channel = i;
+
+ return 0;
+}
+
+static int sof_ipc4_control_load_bytes(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol)
+{
+ struct sof_ipc4_control_data *control_data;
+ struct sof_ipc4_msg *msg;
+ int ret;
+
+ if (scontrol->max_size < (sizeof(*control_data) + sizeof(struct sof_abi_hdr))) {
+ dev_err(sdev->dev, "insufficient size for a bytes control %s: %zu.\n",
+ scontrol->name, scontrol->max_size);
+ return -EINVAL;
+ }
+
+ if (scontrol->priv_size > scontrol->max_size - sizeof(*control_data)) {
+ dev_err(sdev->dev, "scontrol %s bytes data size %zu exceeds max %zu.\n",
+ scontrol->name, scontrol->priv_size,
+ scontrol->max_size - sizeof(*control_data));
+ return -EINVAL;
+ }
+
+ scontrol->size = sizeof(struct sof_ipc4_control_data) + scontrol->priv_size;
+
+ scontrol->ipc_control_data = kzalloc(scontrol->max_size, GFP_KERNEL);
+ if (!scontrol->ipc_control_data)
+ return -ENOMEM;
+
+ control_data = scontrol->ipc_control_data;
+ control_data->index = scontrol->index;
+ if (scontrol->priv_size > 0) {
+ memcpy(control_data->data, scontrol->priv, scontrol->priv_size);
+ kfree(scontrol->priv);
+ scontrol->priv = NULL;
+
+ if (control_data->data->magic != SOF_IPC4_ABI_MAGIC) {
+ dev_err(sdev->dev, "Wrong ABI magic (%#x) for control: %s\n",
+ control_data->data->magic, scontrol->name);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ /* TODO: check the ABI version */
+
+ if (control_data->data->size + sizeof(struct sof_abi_hdr) !=
+ scontrol->priv_size) {
+ dev_err(sdev->dev, "Control %s conflict in bytes %zu vs. priv size %zu.\n",
+ scontrol->name,
+ control_data->data->size + sizeof(struct sof_abi_hdr),
+ scontrol->priv_size);
+ ret = -EINVAL;
+ goto err;
+ }
+ }
+
+ msg = &control_data->msg;
+ msg->primary = SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_LARGE_CONFIG_SET);
+ msg->primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ msg->primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG);
+
+ return 0;
+
+err:
+ kfree(scontrol->ipc_control_data);
+ scontrol->ipc_control_data = NULL;
+ return ret;
+}
+
+static int sof_ipc4_control_setup(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol)
+{
+ switch (scontrol->info_type) {
+ case SND_SOC_TPLG_CTL_VOLSW:
+ case SND_SOC_TPLG_CTL_VOLSW_SX:
+ case SND_SOC_TPLG_CTL_VOLSW_XR_SX:
+ return sof_ipc4_control_load_volume(sdev, scontrol);
+ case SND_SOC_TPLG_CTL_BYTES:
+ return sof_ipc4_control_load_bytes(sdev, scontrol);
+ case SND_SOC_TPLG_CTL_ENUM:
+ case SND_SOC_TPLG_CTL_ENUM_VALUE:
+ return sof_ipc4_control_load_enum(sdev, scontrol);
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
+{
+ struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget;
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+ struct sof_ipc4_pipeline *pipeline;
+ struct sof_ipc4_msg *msg;
+ void *ipc_data = NULL;
+ u32 ipc_size = 0;
+ int ret;
+
+ switch (swidget->id) {
+ case snd_soc_dapm_scheduler:
+ pipeline = swidget->private;
+
+ if (pipeline->use_chain_dma) {
+ dev_warn(sdev->dev, "use_chain_dma set for scheduler %s",
+ swidget->widget->name);
+ return 0;
+ }
+
+ dev_dbg(sdev->dev, "pipeline: %d memory pages: %d\n", swidget->pipeline_id,
+ pipeline->mem_usage);
+
+ msg = &pipeline->msg;
+ msg->primary |= pipeline->mem_usage;
+
+ swidget->instance_id = ida_alloc_max(&pipeline_ida, ipc4_data->max_num_pipelines,
+ GFP_KERNEL);
+ if (swidget->instance_id < 0) {
+ dev_err(sdev->dev, "failed to assign pipeline id for %s: %d\n",
+ swidget->widget->name, swidget->instance_id);
+ return swidget->instance_id;
+ }
+ msg->primary &= ~SOF_IPC4_GLB_PIPE_INSTANCE_MASK;
+ msg->primary |= SOF_IPC4_GLB_PIPE_INSTANCE_ID(swidget->instance_id);
+ break;
+ case snd_soc_dapm_aif_in:
+ case snd_soc_dapm_aif_out:
+ case snd_soc_dapm_buffer:
+ {
+ struct sof_ipc4_copier *ipc4_copier = swidget->private;
+
+ pipeline = pipe_widget->private;
+ if (pipeline->use_chain_dma)
+ return 0;
+
+ ipc_size = ipc4_copier->ipc_config_size;
+ ipc_data = ipc4_copier->ipc_config_data;
+
+ msg = &ipc4_copier->msg;
+ break;
+ }
+ case snd_soc_dapm_dai_in:
+ case snd_soc_dapm_dai_out:
+ {
+ struct snd_sof_dai *dai = swidget->private;
+ struct sof_ipc4_copier *ipc4_copier = dai->private;
+
+ pipeline = pipe_widget->private;
+ if (pipeline->use_chain_dma)
+ return 0;
+
+ ipc_size = ipc4_copier->ipc_config_size;
+ ipc_data = ipc4_copier->ipc_config_data;
+
+ msg = &ipc4_copier->msg;
+ break;
+ }
+ case snd_soc_dapm_pga:
+ {
+ struct sof_ipc4_gain *gain = swidget->private;
+
+ ipc_size = sizeof(gain->data);
+ ipc_data = &gain->data;
+
+ msg = &gain->msg;
+ break;
+ }
+ case snd_soc_dapm_mixer:
+ {
+ struct sof_ipc4_mixer *mixer = swidget->private;
+
+ ipc_size = sizeof(mixer->base_config);
+ ipc_data = &mixer->base_config;
+
+ msg = &mixer->msg;
+ break;
+ }
+ case snd_soc_dapm_src:
+ {
+ struct sof_ipc4_src *src = swidget->private;
+
+ ipc_size = sizeof(src->data);
+ ipc_data = &src->data;
+
+ msg = &src->msg;
+ break;
+ }
+ case snd_soc_dapm_effect:
+ {
+ struct sof_ipc4_process *process = swidget->private;
+
+ if (!process->ipc_config_size) {
+ dev_err(sdev->dev, "module %s has no config data!\n",
+ swidget->widget->name);
+ return -EINVAL;
+ }
+
+ ipc_size = process->ipc_config_size;
+ ipc_data = process->ipc_config_data;
+
+ msg = &process->msg;
+ break;
+ }
+ default:
+ dev_err(sdev->dev, "widget type %d not supported", swidget->id);
+ return -EINVAL;
+ }
+
+ if (swidget->id != snd_soc_dapm_scheduler) {
+ int module_id = msg->primary & SOF_IPC4_MOD_ID_MASK;
+
+ ret = sof_ipc4_widget_assign_instance_id(sdev, swidget);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to assign instance id for %s\n",
+ swidget->widget->name);
+ return ret;
+ }
+
+ msg->primary &= ~SOF_IPC4_MOD_INSTANCE_MASK;
+ msg->primary |= SOF_IPC4_MOD_INSTANCE(swidget->instance_id);
+
+ msg->extension &= ~SOF_IPC4_MOD_EXT_PARAM_SIZE_MASK;
+ msg->extension |= ipc_size >> 2;
+
+ msg->extension &= ~SOF_IPC4_MOD_EXT_PPL_ID_MASK;
+ msg->extension |= SOF_IPC4_MOD_EXT_PPL_ID(pipe_widget->instance_id);
+
+ dev_dbg(sdev->dev, "Create widget %s (pipe %d) - ID %d, instance %d, core %d\n",
+ swidget->widget->name, swidget->pipeline_id, module_id,
+ swidget->instance_id, swidget->core);
+ } else {
+ dev_dbg(sdev->dev, "Create pipeline %s (pipe %d) - instance %d, core %d\n",
+ swidget->widget->name, swidget->pipeline_id,
+ swidget->instance_id, swidget->core);
+ }
+
+ msg->data_size = ipc_size;
+ msg->data_ptr = ipc_data;
+
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, msg, ipc_size);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to create module %s\n", swidget->widget->name);
+
+ if (swidget->id != snd_soc_dapm_scheduler) {
+ struct sof_ipc4_fw_module *fw_module = swidget->module_info;
+
+ ida_free(&fw_module->m_ida, swidget->instance_id);
+ } else {
+ ida_free(&pipeline_ida, swidget->instance_id);
+ }
+ }
+
+ return ret;
+}
+
+static int sof_ipc4_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
+{
+ struct sof_ipc4_fw_module *fw_module = swidget->module_info;
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+ int ret = 0;
+
+ mutex_lock(&ipc4_data->pipeline_state_mutex);
+
+ /* freeing a pipeline frees all the widgets associated with it */
+ if (swidget->id == snd_soc_dapm_scheduler) {
+ struct sof_ipc4_pipeline *pipeline = swidget->private;
+ struct sof_ipc4_msg msg = {{ 0 }};
+ u32 header;
+
+ if (pipeline->use_chain_dma) {
+ dev_warn(sdev->dev, "use_chain_dma set for scheduler %s",
+ swidget->widget->name);
+ mutex_unlock(&ipc4_data->pipeline_state_mutex);
+ return 0;
+ }
+
+ header = SOF_IPC4_GLB_PIPE_INSTANCE_ID(swidget->instance_id);
+ header |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_DELETE_PIPELINE);
+ header |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ header |= SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG);
+
+ msg.primary = header;
+
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0);
+ if (ret < 0)
+ dev_err(sdev->dev, "failed to free pipeline widget %s\n",
+ swidget->widget->name);
+
+ pipeline->mem_usage = 0;
+ pipeline->state = SOF_IPC4_PIPE_UNINITIALIZED;
+ ida_free(&pipeline_ida, swidget->instance_id);
+ swidget->instance_id = -EINVAL;
+ } else {
+ struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget;
+ struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
+
+ if (!pipeline->use_chain_dma)
+ ida_free(&fw_module->m_ida, swidget->instance_id);
+ }
+
+ mutex_unlock(&ipc4_data->pipeline_state_mutex);
+
+ return ret;
+}
+
+static int sof_ipc4_get_queue_id(struct snd_sof_widget *src_widget,
+ struct snd_sof_widget *sink_widget, bool pin_type)
+{
+ struct snd_sof_widget *current_swidget;
+ struct snd_soc_component *scomp;
+ struct ida *queue_ida;
+ const char *buddy_name;
+ char **pin_binding;
+ u32 num_pins;
+ int i;
+
+ if (pin_type == SOF_PIN_TYPE_OUTPUT) {
+ current_swidget = src_widget;
+ pin_binding = src_widget->output_pin_binding;
+ queue_ida = &src_widget->output_queue_ida;
+ num_pins = src_widget->num_output_pins;
+ buddy_name = sink_widget->widget->name;
+ } else {
+ current_swidget = sink_widget;
+ pin_binding = sink_widget->input_pin_binding;
+ queue_ida = &sink_widget->input_queue_ida;
+ num_pins = sink_widget->num_input_pins;
+ buddy_name = src_widget->widget->name;
+ }
+
+ scomp = current_swidget->scomp;
+
+ if (num_pins < 1) {
+ dev_err(scomp->dev, "invalid %s num_pins: %d for queue allocation for %s\n",
+ (pin_type == SOF_PIN_TYPE_OUTPUT ? "output" : "input"),
+ num_pins, current_swidget->widget->name);
+ return -EINVAL;
+ }
+
+ /* If there is only one input/output pin, queue id must be 0 */
+ if (num_pins == 1)
+ return 0;
+
+ /* Allocate queue ID from pin binding array if it is defined in topology. */
+ if (pin_binding) {
+ for (i = 0; i < num_pins; i++) {
+ if (!strcmp(pin_binding[i], buddy_name))
+ return i;
+ }
+ /*
+ * Fail if no queue ID found from pin binding array, so that we don't
+ * mixed use pin binding array and ida for queue ID allocation.
+ */
+ dev_err(scomp->dev, "no %s queue id found from pin binding array for %s\n",
+ (pin_type == SOF_PIN_TYPE_OUTPUT ? "output" : "input"),
+ current_swidget->widget->name);
+ return -EINVAL;
+ }
+
+ /* If no pin binding array specified in topology, use ida to allocate one */
+ return ida_alloc_max(queue_ida, num_pins, GFP_KERNEL);
+}
+
+static void sof_ipc4_put_queue_id(struct snd_sof_widget *swidget, int queue_id,
+ bool pin_type)
+{
+ struct ida *queue_ida;
+ char **pin_binding;
+ int num_pins;
+
+ if (pin_type == SOF_PIN_TYPE_OUTPUT) {
+ pin_binding = swidget->output_pin_binding;
+ queue_ida = &swidget->output_queue_ida;
+ num_pins = swidget->num_output_pins;
+ } else {
+ pin_binding = swidget->input_pin_binding;
+ queue_ida = &swidget->input_queue_ida;
+ num_pins = swidget->num_input_pins;
+ }
+
+ /* Nothing to free if queue ID is not allocated with ida. */
+ if (num_pins == 1 || pin_binding)
+ return;
+
+ ida_free(queue_ida, queue_id);
+}
+
+static int sof_ipc4_set_copier_sink_format(struct snd_sof_dev *sdev,
+ struct snd_sof_widget *src_widget,
+ struct snd_sof_widget *sink_widget,
+ int sink_id)
+{
+ struct sof_ipc4_copier_config_set_sink_format format;
+ const struct sof_ipc_ops *iops = sdev->ipc->ops;
+ struct sof_ipc4_base_module_cfg *src_config;
+ const struct sof_ipc4_audio_format *pin_fmt;
+ struct sof_ipc4_fw_module *fw_module;
+ struct sof_ipc4_msg msg = {{ 0 }};
+
+ dev_dbg(sdev->dev, "%s set copier sink %d format\n",
+ src_widget->widget->name, sink_id);
+
+ if (WIDGET_IS_DAI(src_widget->id)) {
+ struct snd_sof_dai *dai = src_widget->private;
+
+ src_config = dai->private;
+ } else {
+ src_config = src_widget->private;
+ }
+
+ fw_module = src_widget->module_info;
+
+ format.sink_id = sink_id;
+ memcpy(&format.source_fmt, &src_config->audio_fmt, sizeof(format.source_fmt));
+
+ pin_fmt = sof_ipc4_get_input_pin_audio_fmt(sink_widget, sink_id);
+ if (!pin_fmt) {
+ dev_err(sdev->dev, "Unable to get pin %d format for %s",
+ sink_id, sink_widget->widget->name);
+ return -EINVAL;
+ }
+
+ memcpy(&format.sink_fmt, pin_fmt, sizeof(format.sink_fmt));
+
+ msg.data_size = sizeof(format);
+ msg.data_ptr = &format;
+
+ msg.primary = fw_module->man4_module_entry.id;
+ msg.primary |= SOF_IPC4_MOD_INSTANCE(src_widget->instance_id);
+ msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ msg.primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG);
+
+ msg.extension =
+ SOF_IPC4_MOD_EXT_MSG_PARAM_ID(SOF_IPC4_COPIER_MODULE_CFG_PARAM_SET_SINK_FORMAT);
+
+ return iops->set_get_data(sdev, &msg, msg.data_size, true);
+}
+
+static int sof_ipc4_route_setup(struct snd_sof_dev *sdev, struct snd_sof_route *sroute)
+{
+ struct snd_sof_widget *src_widget = sroute->src_widget;
+ struct snd_sof_widget *sink_widget = sroute->sink_widget;
+ struct snd_sof_widget *src_pipe_widget = src_widget->spipe->pipe_widget;
+ struct snd_sof_widget *sink_pipe_widget = sink_widget->spipe->pipe_widget;
+ struct sof_ipc4_fw_module *src_fw_module = src_widget->module_info;
+ struct sof_ipc4_fw_module *sink_fw_module = sink_widget->module_info;
+ struct sof_ipc4_pipeline *src_pipeline = src_pipe_widget->private;
+ struct sof_ipc4_pipeline *sink_pipeline = sink_pipe_widget->private;
+ struct sof_ipc4_msg msg = {{ 0 }};
+ u32 header, extension;
+ int ret;
+
+ /* no route set up if chain DMA is used */
+ if (src_pipeline->use_chain_dma || sink_pipeline->use_chain_dma) {
+ if (!src_pipeline->use_chain_dma || !sink_pipeline->use_chain_dma) {
+ dev_err(sdev->dev,
+ "use_chain_dma must be set for both src %s and sink %s pipelines\n",
+ src_widget->widget->name, sink_widget->widget->name);
+ return -EINVAL;
+ }
+ return 0;
+ }
+
+ if (!src_fw_module || !sink_fw_module) {
+ dev_err(sdev->dev,
+ "cannot bind %s -> %s, no firmware module for: %s%s\n",
+ src_widget->widget->name, sink_widget->widget->name,
+ src_fw_module ? "" : " source",
+ sink_fw_module ? "" : " sink");
+
+ return -ENODEV;
+ }
+
+ sroute->src_queue_id = sof_ipc4_get_queue_id(src_widget, sink_widget,
+ SOF_PIN_TYPE_OUTPUT);
+ if (sroute->src_queue_id < 0) {
+ dev_err(sdev->dev, "failed to get queue ID for source widget: %s\n",
+ src_widget->widget->name);
+ return sroute->src_queue_id;
+ }
+
+ sroute->dst_queue_id = sof_ipc4_get_queue_id(src_widget, sink_widget,
+ SOF_PIN_TYPE_INPUT);
+ if (sroute->dst_queue_id < 0) {
+ dev_err(sdev->dev, "failed to get queue ID for sink widget: %s\n",
+ sink_widget->widget->name);
+ sof_ipc4_put_queue_id(src_widget, sroute->src_queue_id,
+ SOF_PIN_TYPE_OUTPUT);
+ return sroute->dst_queue_id;
+ }
+
+ /* Pin 0 format is already set during copier module init */
+ if (sroute->src_queue_id > 0 && WIDGET_IS_COPIER(src_widget->id)) {
+ ret = sof_ipc4_set_copier_sink_format(sdev, src_widget, sink_widget,
+ sroute->src_queue_id);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to set sink format for %s source queue ID %d\n",
+ src_widget->widget->name, sroute->src_queue_id);
+ goto out;
+ }
+ }
+
+ dev_dbg(sdev->dev, "bind %s:%d -> %s:%d\n",
+ src_widget->widget->name, sroute->src_queue_id,
+ sink_widget->widget->name, sroute->dst_queue_id);
+
+ header = src_fw_module->man4_module_entry.id;
+ header |= SOF_IPC4_MOD_INSTANCE(src_widget->instance_id);
+ header |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_BIND);
+ header |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ header |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG);
+
+ extension = sink_fw_module->man4_module_entry.id;
+ extension |= SOF_IPC4_MOD_EXT_DST_MOD_INSTANCE(sink_widget->instance_id);
+ extension |= SOF_IPC4_MOD_EXT_DST_MOD_QUEUE_ID(sroute->dst_queue_id);
+ extension |= SOF_IPC4_MOD_EXT_SRC_MOD_QUEUE_ID(sroute->src_queue_id);
+
+ msg.primary = header;
+ msg.extension = extension;
+
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to bind modules %s:%d -> %s:%d\n",
+ src_widget->widget->name, sroute->src_queue_id,
+ sink_widget->widget->name, sroute->dst_queue_id);
+ goto out;
+ }
+
+ return ret;
+
+out:
+ sof_ipc4_put_queue_id(src_widget, sroute->src_queue_id, SOF_PIN_TYPE_OUTPUT);
+ sof_ipc4_put_queue_id(sink_widget, sroute->dst_queue_id, SOF_PIN_TYPE_INPUT);
+ return ret;
+}
+
+static int sof_ipc4_route_free(struct snd_sof_dev *sdev, struct snd_sof_route *sroute)
+{
+ struct snd_sof_widget *src_widget = sroute->src_widget;
+ struct snd_sof_widget *sink_widget = sroute->sink_widget;
+ struct sof_ipc4_fw_module *src_fw_module = src_widget->module_info;
+ struct sof_ipc4_fw_module *sink_fw_module = sink_widget->module_info;
+ struct sof_ipc4_msg msg = {{ 0 }};
+ struct snd_sof_widget *src_pipe_widget = src_widget->spipe->pipe_widget;
+ struct snd_sof_widget *sink_pipe_widget = sink_widget->spipe->pipe_widget;
+ struct sof_ipc4_pipeline *src_pipeline = src_pipe_widget->private;
+ struct sof_ipc4_pipeline *sink_pipeline = sink_pipe_widget->private;
+ u32 header, extension;
+ int ret = 0;
+
+ /* no route is set up if chain DMA is used */
+ if (src_pipeline->use_chain_dma || sink_pipeline->use_chain_dma)
+ return 0;
+
+ dev_dbg(sdev->dev, "unbind modules %s:%d -> %s:%d\n",
+ src_widget->widget->name, sroute->src_queue_id,
+ sink_widget->widget->name, sroute->dst_queue_id);
+
+ /*
+ * routes belonging to the same pipeline will be disconnected by the FW when the pipeline
+ * is freed. So avoid sending this IPC which will be ignored by the FW anyway.
+ */
+ if (src_widget->spipe->pipe_widget == sink_widget->spipe->pipe_widget)
+ goto out;
+
+ header = src_fw_module->man4_module_entry.id;
+ header |= SOF_IPC4_MOD_INSTANCE(src_widget->instance_id);
+ header |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_UNBIND);
+ header |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ header |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG);
+
+ extension = sink_fw_module->man4_module_entry.id;
+ extension |= SOF_IPC4_MOD_EXT_DST_MOD_INSTANCE(sink_widget->instance_id);
+ extension |= SOF_IPC4_MOD_EXT_DST_MOD_QUEUE_ID(sroute->dst_queue_id);
+ extension |= SOF_IPC4_MOD_EXT_SRC_MOD_QUEUE_ID(sroute->src_queue_id);
+
+ msg.primary = header;
+ msg.extension = extension;
+
+ ret = sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0);
+ if (ret < 0)
+ dev_err(sdev->dev, "failed to unbind modules %s:%d -> %s:%d\n",
+ src_widget->widget->name, sroute->src_queue_id,
+ sink_widget->widget->name, sroute->dst_queue_id);
+out:
+ sof_ipc4_put_queue_id(sink_widget, sroute->dst_queue_id, SOF_PIN_TYPE_INPUT);
+ sof_ipc4_put_queue_id(src_widget, sroute->src_queue_id, SOF_PIN_TYPE_OUTPUT);
+
+ return ret;
+}
+
+static int sof_ipc4_dai_config(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget,
+ unsigned int flags, struct snd_sof_dai_config_data *data)
+{
+ struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget;
+ struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
+ struct snd_sof_dai *dai = swidget->private;
+ struct sof_ipc4_gtw_attributes *gtw_attr;
+ struct sof_ipc4_copier_data *copier_data;
+ struct sof_ipc4_copier *ipc4_copier;
+
+ if (!dai || !dai->private) {
+ dev_err(sdev->dev, "Invalid DAI or DAI private data for %s\n",
+ swidget->widget->name);
+ return -EINVAL;
+ }
+
+ ipc4_copier = (struct sof_ipc4_copier *)dai->private;
+ copier_data = &ipc4_copier->data;
+
+ if (!data)
+ return 0;
+
+ if (pipeline->use_chain_dma) {
+ pipeline->msg.primary &= ~SOF_IPC4_GLB_CHAIN_DMA_LINK_ID_MASK;
+ pipeline->msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_LINK_ID(data->dai_data);
+ return 0;
+ }
+
+ switch (ipc4_copier->dai_type) {
+ case SOF_DAI_INTEL_HDA:
+ gtw_attr = ipc4_copier->gtw_attr;
+ gtw_attr->lp_buffer_alloc = pipeline->lp_mode;
+ fallthrough;
+ case SOF_DAI_INTEL_ALH:
+ /*
+ * Do not clear the node ID when this op is invoked with
+ * SOF_DAI_CONFIG_FLAGS_HW_FREE. It is needed to free the group_ida during
+ * unprepare.
+ */
+ if (flags & SOF_DAI_CONFIG_FLAGS_HW_PARAMS) {
+ copier_data->gtw_cfg.node_id &= ~SOF_IPC4_NODE_INDEX_MASK;
+ copier_data->gtw_cfg.node_id |= SOF_IPC4_NODE_INDEX(data->dai_data);
+ }
+ break;
+ case SOF_DAI_INTEL_DMIC:
+ case SOF_DAI_INTEL_SSP:
+ /* nothing to do for SSP/DMIC */
+ break;
+ default:
+ dev_err(sdev->dev, "%s: unsupported dai type %d\n", __func__,
+ ipc4_copier->dai_type);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sof_ipc4_parse_manifest(struct snd_soc_component *scomp, int index,
+ struct snd_soc_tplg_manifest *man)
+{
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+ struct sof_manifest_tlv *manifest_tlv;
+ struct sof_manifest *manifest;
+ u32 size = le32_to_cpu(man->priv.size);
+ u8 *man_ptr = man->priv.data;
+ u32 len_check;
+ int i;
+
+ if (!size || size < SOF_IPC4_TPLG_ABI_SIZE) {
+ dev_err(scomp->dev, "%s: Invalid topology ABI size: %u\n",
+ __func__, size);
+ return -EINVAL;
+ }
+
+ manifest = (struct sof_manifest *)man_ptr;
+
+ dev_info(scomp->dev,
+ "Topology: ABI %d:%d:%d Kernel ABI %u:%u:%u\n",
+ le16_to_cpu(manifest->abi_major), le16_to_cpu(manifest->abi_minor),
+ le16_to_cpu(manifest->abi_patch),
+ SOF_ABI_MAJOR, SOF_ABI_MINOR, SOF_ABI_PATCH);
+
+ /* TODO: Add ABI compatibility check */
+
+ /* no more data after the ABI version */
+ if (size <= SOF_IPC4_TPLG_ABI_SIZE)
+ return 0;
+
+ manifest_tlv = manifest->items;
+ len_check = sizeof(struct sof_manifest);
+ for (i = 0; i < le16_to_cpu(manifest->count); i++) {
+ len_check += sizeof(struct sof_manifest_tlv) + le32_to_cpu(manifest_tlv->size);
+ if (len_check > size)
+ return -EINVAL;
+
+ switch (le32_to_cpu(manifest_tlv->type)) {
+ case SOF_MANIFEST_DATA_TYPE_NHLT:
+ /* no NHLT in BIOS, so use the one from topology manifest */
+ if (ipc4_data->nhlt)
+ break;
+ ipc4_data->nhlt = devm_kmemdup(sdev->dev, manifest_tlv->data,
+ le32_to_cpu(manifest_tlv->size), GFP_KERNEL);
+ if (!ipc4_data->nhlt)
+ return -ENOMEM;
+ break;
+ default:
+ dev_warn(scomp->dev, "Skipping unknown manifest data type %d\n",
+ manifest_tlv->type);
+ break;
+ }
+ man_ptr += sizeof(struct sof_manifest_tlv) + le32_to_cpu(manifest_tlv->size);
+ manifest_tlv = (struct sof_manifest_tlv *)man_ptr;
+ }
+
+ return 0;
+}
+
+static int sof_ipc4_dai_get_clk(struct snd_sof_dev *sdev, struct snd_sof_dai *dai, int clk_type)
+{
+ struct sof_ipc4_copier *ipc4_copier = dai->private;
+ struct snd_soc_tplg_hw_config *hw_config;
+ struct snd_sof_dai_link *slink;
+ bool dai_link_found = false;
+ bool hw_cfg_found = false;
+ int i;
+
+ if (!ipc4_copier)
+ return 0;
+
+ list_for_each_entry(slink, &sdev->dai_link_list, list) {
+ if (!strcmp(slink->link->name, dai->name)) {
+ dai_link_found = true;
+ break;
+ }
+ }
+
+ if (!dai_link_found) {
+ dev_err(sdev->dev, "no DAI link found for DAI %s\n", dai->name);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < slink->num_hw_configs; i++) {
+ hw_config = &slink->hw_configs[i];
+ if (dai->current_config == le32_to_cpu(hw_config->id)) {
+ hw_cfg_found = true;
+ break;
+ }
+ }
+
+ if (!hw_cfg_found) {
+ dev_err(sdev->dev, "no matching hw_config found for DAI %s\n", dai->name);
+ return -EINVAL;
+ }
+
+ switch (ipc4_copier->dai_type) {
+ case SOF_DAI_INTEL_SSP:
+ switch (clk_type) {
+ case SOF_DAI_CLK_INTEL_SSP_MCLK:
+ return le32_to_cpu(hw_config->mclk_rate);
+ case SOF_DAI_CLK_INTEL_SSP_BCLK:
+ return le32_to_cpu(hw_config->bclk_rate);
+ default:
+ dev_err(sdev->dev, "Invalid clk type for SSP %d\n", clk_type);
+ break;
+ }
+ break;
+ default:
+ dev_err(sdev->dev, "DAI type %d not supported yet!\n", ipc4_copier->dai_type);
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static int sof_ipc4_tear_down_all_pipelines(struct snd_sof_dev *sdev, bool verify)
+{
+ struct snd_sof_pcm *spcm;
+ int dir, ret;
+
+ /*
+ * This function is called during system suspend, we need to make sure
+ * that all streams have been freed up.
+ * Freeing might have been skipped when xrun happened just at the start
+ * of the suspend and it sent a SNDRV_PCM_TRIGGER_STOP to the active
+ * stream. This will call sof_pcm_stream_free() with
+ * free_widget_list = false which will leave the kernel and firmware out
+ * of sync during suspend/resume.
+ *
+ * This will also make sure that paused streams handled correctly.
+ */
+ list_for_each_entry(spcm, &sdev->pcm_list, list) {
+ for_each_pcm_streams(dir) {
+ struct snd_pcm_substream *substream = spcm->stream[dir].substream;
+
+ if (!substream || !substream->runtime || spcm->stream[dir].suspend_ignored)
+ continue;
+
+ if (spcm->stream[dir].list) {
+ ret = sof_pcm_stream_free(sdev, substream, spcm, dir, true);
+ if (ret < 0)
+ return ret;
+ }
+ }
+ }
+ return 0;
+}
+
+static int sof_ipc4_link_setup(struct snd_sof_dev *sdev, struct snd_soc_dai_link *link)
+{
+ if (link->no_pcm)
+ return 0;
+
+ /*
+ * set default trigger order for all links. Exceptions to
+ * the rule will be handled in sof_pcm_dai_link_fixup()
+ * For playback, the sequence is the following: start BE,
+ * start FE, stop FE, stop BE; for Capture the sequence is
+ * inverted start FE, start BE, stop BE, stop FE
+ */
+ link->trigger[SNDRV_PCM_STREAM_PLAYBACK] = SND_SOC_DPCM_TRIGGER_POST;
+ link->trigger[SNDRV_PCM_STREAM_CAPTURE] = SND_SOC_DPCM_TRIGGER_PRE;
+
+ return 0;
+}
+
+static enum sof_tokens common_copier_token_list[] = {
+ SOF_COMP_TOKENS,
+ SOF_AUDIO_FMT_NUM_TOKENS,
+ SOF_IN_AUDIO_FORMAT_TOKENS,
+ SOF_OUT_AUDIO_FORMAT_TOKENS,
+ SOF_COPIER_DEEP_BUFFER_TOKENS,
+ SOF_COPIER_TOKENS,
+ SOF_COMP_EXT_TOKENS,
+};
+
+static enum sof_tokens pipeline_token_list[] = {
+ SOF_SCHED_TOKENS,
+ SOF_PIPELINE_TOKENS,
+};
+
+static enum sof_tokens dai_token_list[] = {
+ SOF_COMP_TOKENS,
+ SOF_AUDIO_FMT_NUM_TOKENS,
+ SOF_IN_AUDIO_FORMAT_TOKENS,
+ SOF_OUT_AUDIO_FORMAT_TOKENS,
+ SOF_COPIER_TOKENS,
+ SOF_DAI_TOKENS,
+ SOF_COMP_EXT_TOKENS,
+};
+
+static enum sof_tokens pga_token_list[] = {
+ SOF_COMP_TOKENS,
+ SOF_GAIN_TOKENS,
+ SOF_AUDIO_FMT_NUM_TOKENS,
+ SOF_IN_AUDIO_FORMAT_TOKENS,
+ SOF_OUT_AUDIO_FORMAT_TOKENS,
+ SOF_COMP_EXT_TOKENS,
+};
+
+static enum sof_tokens mixer_token_list[] = {
+ SOF_COMP_TOKENS,
+ SOF_AUDIO_FMT_NUM_TOKENS,
+ SOF_IN_AUDIO_FORMAT_TOKENS,
+ SOF_OUT_AUDIO_FORMAT_TOKENS,
+ SOF_COMP_EXT_TOKENS,
+};
+
+static enum sof_tokens src_token_list[] = {
+ SOF_COMP_TOKENS,
+ SOF_SRC_TOKENS,
+ SOF_AUDIO_FMT_NUM_TOKENS,
+ SOF_IN_AUDIO_FORMAT_TOKENS,
+ SOF_OUT_AUDIO_FORMAT_TOKENS,
+ SOF_COMP_EXT_TOKENS,
+};
+
+static enum sof_tokens process_token_list[] = {
+ SOF_COMP_TOKENS,
+ SOF_AUDIO_FMT_NUM_TOKENS,
+ SOF_IN_AUDIO_FORMAT_TOKENS,
+ SOF_OUT_AUDIO_FORMAT_TOKENS,
+ SOF_COMP_EXT_TOKENS,
+};
+
+static const struct sof_ipc_tplg_widget_ops tplg_ipc4_widget_ops[SND_SOC_DAPM_TYPE_COUNT] = {
+ [snd_soc_dapm_aif_in] = {sof_ipc4_widget_setup_pcm, sof_ipc4_widget_free_comp_pcm,
+ common_copier_token_list, ARRAY_SIZE(common_copier_token_list),
+ NULL, sof_ipc4_prepare_copier_module,
+ sof_ipc4_unprepare_copier_module},
+ [snd_soc_dapm_aif_out] = {sof_ipc4_widget_setup_pcm, sof_ipc4_widget_free_comp_pcm,
+ common_copier_token_list, ARRAY_SIZE(common_copier_token_list),
+ NULL, sof_ipc4_prepare_copier_module,
+ sof_ipc4_unprepare_copier_module},
+ [snd_soc_dapm_dai_in] = {sof_ipc4_widget_setup_comp_dai, sof_ipc4_widget_free_comp_dai,
+ dai_token_list, ARRAY_SIZE(dai_token_list), NULL,
+ sof_ipc4_prepare_copier_module,
+ sof_ipc4_unprepare_copier_module},
+ [snd_soc_dapm_dai_out] = {sof_ipc4_widget_setup_comp_dai, sof_ipc4_widget_free_comp_dai,
+ dai_token_list, ARRAY_SIZE(dai_token_list), NULL,
+ sof_ipc4_prepare_copier_module,
+ sof_ipc4_unprepare_copier_module},
+ [snd_soc_dapm_buffer] = {sof_ipc4_widget_setup_pcm, sof_ipc4_widget_free_comp_pcm,
+ common_copier_token_list, ARRAY_SIZE(common_copier_token_list),
+ NULL, sof_ipc4_prepare_copier_module,
+ sof_ipc4_unprepare_copier_module},
+ [snd_soc_dapm_scheduler] = {sof_ipc4_widget_setup_comp_pipeline,
+ sof_ipc4_widget_free_comp_pipeline,
+ pipeline_token_list, ARRAY_SIZE(pipeline_token_list), NULL,
+ NULL, NULL},
+ [snd_soc_dapm_pga] = {sof_ipc4_widget_setup_comp_pga, sof_ipc4_widget_free_comp_pga,
+ pga_token_list, ARRAY_SIZE(pga_token_list), NULL,
+ sof_ipc4_prepare_gain_module,
+ NULL},
+ [snd_soc_dapm_mixer] = {sof_ipc4_widget_setup_comp_mixer, sof_ipc4_widget_free_comp_mixer,
+ mixer_token_list, ARRAY_SIZE(mixer_token_list),
+ NULL, sof_ipc4_prepare_mixer_module,
+ NULL},
+ [snd_soc_dapm_src] = {sof_ipc4_widget_setup_comp_src, sof_ipc4_widget_free_comp_src,
+ src_token_list, ARRAY_SIZE(src_token_list),
+ NULL, sof_ipc4_prepare_src_module,
+ NULL},
+ [snd_soc_dapm_effect] = {sof_ipc4_widget_setup_comp_process,
+ sof_ipc4_widget_free_comp_process,
+ process_token_list, ARRAY_SIZE(process_token_list),
+ NULL, sof_ipc4_prepare_process_module,
+ NULL},
+};
+
+const struct sof_ipc_tplg_ops ipc4_tplg_ops = {
+ .widget = tplg_ipc4_widget_ops,
+ .token_list = ipc4_token_list,
+ .control_setup = sof_ipc4_control_setup,
+ .control = &tplg_ipc4_control_ops,
+ .widget_setup = sof_ipc4_widget_setup,
+ .widget_free = sof_ipc4_widget_free,
+ .route_setup = sof_ipc4_route_setup,
+ .route_free = sof_ipc4_route_free,
+ .dai_config = sof_ipc4_dai_config,
+ .parse_manifest = sof_ipc4_parse_manifest,
+ .dai_get_clk = sof_ipc4_dai_get_clk,
+ .tear_down_all_pipelines = sof_ipc4_tear_down_all_pipelines,
+ .link_setup = sof_ipc4_link_setup,
+};
diff --git a/sound/soc/sof/ipc4-topology.h b/sound/soc/sof/ipc4-topology.h
new file mode 100644
index 000000000000..dce174a190dd
--- /dev/null
+++ b/sound/soc/sof/ipc4-topology.h
@@ -0,0 +1,481 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+/*
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * Copyright(c) 2022 Intel Corporation. All rights reserved.
+ */
+
+#ifndef __INCLUDE_SOUND_SOF_IPC4_TOPOLOGY_H__
+#define __INCLUDE_SOUND_SOF_IPC4_TOPOLOGY_H__
+
+#include <sound/sof/ipc4/header.h>
+
+#define SOF_IPC4_FW_PAGE_SIZE BIT(12)
+#define SOF_IPC4_FW_PAGE(x) ((((x) + BIT(12) - 1) & ~(BIT(12) - 1)) >> 12)
+#define SOF_IPC4_FW_ROUNDUP(x) (((x) + BIT(6) - 1) & (~(BIT(6) - 1)))
+
+#define SOF_IPC4_MODULE_LOAD_TYPE GENMASK(3, 0)
+#define SOF_IPC4_MODULE_AUTO_START BIT(4)
+/*
+ * Two module schedule domains in fw :
+ * LL domain - Low latency domain
+ * DP domain - Data processing domain
+ * The LL setting should be equal to !DP setting
+ */
+#define SOF_IPC4_MODULE_LL BIT(5)
+#define SOF_IPC4_MODULE_DP BIT(6)
+#define SOF_IPC4_MODULE_LIB_CODE BIT(7)
+#define SOF_IPC4_MODULE_INIT_CONFIG_MASK GENMASK(11, 8)
+
+#define SOF_IPC4_MODULE_INIT_CONFIG_TYPE_BASE_CFG 0
+#define SOF_IPC4_MODULE_INIT_CONFIG_TYPE_BASE_CFG_WITH_EXT 1
+
+#define SOF_IPC4_MODULE_INSTANCE_LIST_ITEM_SIZE 12
+#define SOF_IPC4_PIPELINE_OBJECT_SIZE 448
+#define SOF_IPC4_DATA_QUEUE_OBJECT_SIZE 128
+#define SOF_IPC4_LL_TASK_OBJECT_SIZE 72
+#define SOF_IPC4_DP_TASK_OBJECT_SIZE 104
+#define SOF_IPC4_DP_TASK_LIST_SIZE (12 + 8)
+#define SOF_IPC4_LL_TASK_LIST_ITEM_SIZE 12
+#define SOF_IPC4_FW_MAX_PAGE_COUNT 20
+#define SOF_IPC4_FW_MAX_QUEUE_COUNT 8
+
+/* Node index and mask applicable for host copier and ALH/HDA type DAI copiers */
+#define SOF_IPC4_NODE_INDEX_MASK 0xFF
+#define SOF_IPC4_NODE_INDEX(x) ((x) & SOF_IPC4_NODE_INDEX_MASK)
+#define SOF_IPC4_NODE_TYPE(x) ((x) << 8)
+
+/* Node ID for SSP type DAI copiers */
+#define SOF_IPC4_NODE_INDEX_INTEL_SSP(x) (((x) & 0xf) << 4)
+
+/* Node ID for DMIC type DAI copiers */
+#define SOF_IPC4_NODE_INDEX_INTEL_DMIC(x) ((x) & 0x7)
+
+#define SOF_IPC4_GAIN_ALL_CHANNELS_MASK 0xffffffff
+#define SOF_IPC4_VOL_ZERO_DB 0x7fffffff
+
+#define SOF_IPC4_DMA_DEVICE_MAX_COUNT 16
+
+#define SOF_IPC4_INVALID_NODE_ID 0xffffffff
+
+/* FW requires minimum 2ms DMA buffer size */
+#define SOF_IPC4_MIN_DMA_BUFFER_SIZE 2
+
+/*
+ * The base of multi-gateways. Multi-gateways addressing starts from
+ * ALH_MULTI_GTW_BASE and there are ALH_MULTI_GTW_COUNT multi-sources
+ * and ALH_MULTI_GTW_COUNT multi-sinks available.
+ * Addressing is continuous from ALH_MULTI_GTW_BASE to
+ * ALH_MULTI_GTW_BASE + ALH_MULTI_GTW_COUNT - 1.
+ */
+#define ALH_MULTI_GTW_BASE 0x50
+/* A magic number from FW */
+#define ALH_MULTI_GTW_COUNT 8
+
+enum sof_ipc4_copier_module_config_params {
+/*
+ * Use LARGE_CONFIG_SET to initialize timestamp event. Ipc mailbox must
+ * contain properly built CopierConfigTimestampInitData struct.
+ */
+ SOF_IPC4_COPIER_MODULE_CFG_PARAM_TIMESTAMP_INIT = 1,
+/*
+ * Use LARGE_CONFIG_SET to initialize copier sink. Ipc mailbox must contain
+ * properly built CopierConfigSetSinkFormat struct.
+ */
+ SOF_IPC4_COPIER_MODULE_CFG_PARAM_SET_SINK_FORMAT,
+/*
+ * Use LARGE_CONFIG_SET to initialize and enable on Copier data segment
+ * event. Ipc mailbox must contain properly built DataSegmentEnabled struct.
+ */
+ SOF_IPC4_COPIER_MODULE_CFG_PARAM_DATA_SEGMENT_ENABLED,
+/*
+ * Use LARGE_CONFIG_GET to retrieve Linear Link Position (LLP) value for non
+ * HD-A gateways.
+ */
+ SOF_IPC4_COPIER_MODULE_CFG_PARAM_LLP_READING,
+/*
+ * Use LARGE_CONFIG_GET to retrieve Linear Link Position (LLP) value for non
+ * HD-A gateways and corresponding total processed data
+ */
+ SOF_IPC4_COPIER_MODULE_CFG_PARAM_LLP_READING_EXTENDED,
+/*
+ * Use LARGE_CONFIG_SET to setup attenuation on output pins. Data is just uint32_t.
+ * note Config is only allowed when output pin is set up for 32bit and source
+ * is connected to Gateway
+ */
+ SOF_IPC4_COPIER_MODULE_CFG_ATTENUATION,
+};
+
+struct sof_ipc4_copier_config_set_sink_format {
+/* Id of sink */
+ u32 sink_id;
+/*
+ * Input format used by the source
+ * attention must be the same as present if already initialized.
+ */
+ struct sof_ipc4_audio_format source_fmt;
+/* Output format used by the sink */
+ struct sof_ipc4_audio_format sink_fmt;
+} __packed __aligned(4);
+
+/**
+ * struct sof_ipc4_pipeline - pipeline config data
+ * @priority: Priority of this pipeline
+ * @lp_mode: Low power mode
+ * @mem_usage: Memory usage
+ * @core_id: Target core for the pipeline
+ * @state: Pipeline state
+ * @use_chain_dma: flag to indicate if the firmware shall use chained DMA
+ * @msg: message structure for pipeline
+ * @skip_during_fe_trigger: skip triggering this pipeline during the FE DAI trigger
+ */
+struct sof_ipc4_pipeline {
+ uint32_t priority;
+ uint32_t lp_mode;
+ uint32_t mem_usage;
+ uint32_t core_id;
+ int state;
+ bool use_chain_dma;
+ struct sof_ipc4_msg msg;
+ bool skip_during_fe_trigger;
+};
+
+/**
+ * struct sof_ipc4_multi_pipeline_data - multi pipeline trigger IPC data
+ * @count: Number of pipelines to be triggered
+ * @pipeline_instance_ids: Flexible array of IDs of the pipelines to be triggered
+ */
+struct ipc4_pipeline_set_state_data {
+ u32 count;
+ DECLARE_FLEX_ARRAY(u32, pipeline_instance_ids);
+} __packed;
+
+/**
+ * struct sof_ipc4_pin_format - Module pin format
+ * @pin_index: pin index
+ * @buffer_size: buffer size in bytes
+ * @audio_fmt: audio format for the pin
+ *
+ * This structure can be used for both output or input pins and the pin_index is relative to the
+ * pin type i.e output/input pin
+ */
+struct sof_ipc4_pin_format {
+ u32 pin_index;
+ u32 buffer_size;
+ struct sof_ipc4_audio_format audio_fmt;
+};
+
+/**
+ * struct sof_ipc4_available_audio_format - Available audio formats
+ * @output_pin_fmts: Available output pin formats
+ * @input_pin_fmts: Available input pin formats
+ * @num_input_formats: Number of input pin formats
+ * @num_output_formats: Number of output pin formats
+ */
+struct sof_ipc4_available_audio_format {
+ struct sof_ipc4_pin_format *output_pin_fmts;
+ struct sof_ipc4_pin_format *input_pin_fmts;
+ u32 num_input_formats;
+ u32 num_output_formats;
+};
+
+/**
+ * struct sof_copier_gateway_cfg - IPC gateway configuration
+ * @node_id: ID of Gateway Node
+ * @dma_buffer_size: Preferred Gateway DMA buffer size (in bytes)
+ * @config_length: Length of gateway node configuration blob specified in #config_data
+ * config_data: Gateway node configuration blob
+ */
+struct sof_copier_gateway_cfg {
+ uint32_t node_id;
+ uint32_t dma_buffer_size;
+ uint32_t config_length;
+ uint32_t config_data[];
+};
+
+/**
+ * struct sof_ipc4_copier_data - IPC data for copier
+ * @base_config: Base configuration including input audio format
+ * @out_format: Output audio format
+ * @copier_feature_mask: Copier feature mask
+ * @gtw_cfg: Gateway configuration
+ */
+struct sof_ipc4_copier_data {
+ struct sof_ipc4_base_module_cfg base_config;
+ struct sof_ipc4_audio_format out_format;
+ uint32_t copier_feature_mask;
+ struct sof_copier_gateway_cfg gtw_cfg;
+};
+
+/**
+ * struct sof_ipc4_gtw_attributes: Gateway attributes
+ * @lp_buffer_alloc: Gateway data requested in low power memory
+ * @alloc_from_reg_file: Gateway data requested in register file memory
+ * @rsvd: reserved for future use
+ */
+struct sof_ipc4_gtw_attributes {
+ uint32_t lp_buffer_alloc : 1;
+ uint32_t alloc_from_reg_file : 1;
+ uint32_t rsvd : 30;
+};
+
+/**
+ * struct sof_ipc4_dma_device_stream_ch_map: abstract representation of
+ * channel mapping to DMAs
+ * @device: representation of hardware device address or FIFO
+ * @channel_mask: channels handled by @device. Channels are expected to be
+ * contiguous
+ */
+struct sof_ipc4_dma_device_stream_ch_map {
+ uint32_t device;
+ uint32_t channel_mask;
+};
+
+/**
+ * struct sof_ipc4_dma_stream_ch_map: DMA configuration data
+ * @device_count: Number valid items in mapping array
+ * @mapping: device address and channel mask
+ */
+struct sof_ipc4_dma_stream_ch_map {
+ uint32_t device_count;
+ struct sof_ipc4_dma_device_stream_ch_map mapping[SOF_IPC4_DMA_DEVICE_MAX_COUNT];
+} __packed;
+
+#define SOF_IPC4_DMA_METHOD_HDA 1
+#define SOF_IPC4_DMA_METHOD_GPDMA 2 /* defined for consistency but not used */
+
+/**
+ * struct sof_ipc4_dma_config: DMA configuration
+ * @dma_method: HDAudio or GPDMA
+ * @pre_allocated_by_host: 1 if host driver allocates DMA channels, 0 otherwise
+ * @dma_channel_id: for HDaudio defined as @stream_id - 1
+ * @stream_id: HDaudio stream tag
+ * @dma_stream_channel_map: array of device/channel mappings
+ * @dma_priv_config_size: currently not used
+ * @dma_priv_config: currently not used
+ */
+struct sof_ipc4_dma_config {
+ uint8_t dma_method;
+ uint8_t pre_allocated_by_host;
+ uint16_t rsvd;
+ uint32_t dma_channel_id;
+ uint32_t stream_id;
+ struct sof_ipc4_dma_stream_ch_map dma_stream_channel_map;
+ uint32_t dma_priv_config_size;
+ uint8_t dma_priv_config[];
+} __packed;
+
+#define SOF_IPC4_GTW_DMA_CONFIG_ID 0x1000
+
+/**
+ * struct sof_ipc4_dma_config: DMA configuration
+ * @type: set to SOF_IPC4_GTW_DMA_CONFIG_ID
+ * @length: sizeof(struct sof_ipc4_dma_config) + dma_config.dma_priv_config_size
+ * @dma_config: actual DMA configuration
+ */
+struct sof_ipc4_dma_config_tlv {
+ uint32_t type;
+ uint32_t length;
+ struct sof_ipc4_dma_config dma_config;
+} __packed;
+
+/** struct sof_ipc4_alh_configuration_blob: ALH blob
+ * @gw_attr: Gateway attributes
+ * @alh_cfg: ALH configuration data
+ */
+struct sof_ipc4_alh_configuration_blob {
+ struct sof_ipc4_gtw_attributes gw_attr;
+ struct sof_ipc4_dma_stream_ch_map alh_cfg;
+};
+
+/**
+ * struct sof_ipc4_copier - copier config data
+ * @data: IPC copier data
+ * @copier_config: Copier + blob
+ * @ipc_config_size: Size of copier_config
+ * @available_fmt: Available audio format
+ * @frame_fmt: frame format
+ * @msg: message structure for copier
+ * @gtw_attr: Gateway attributes for copier blob
+ * @dai_type: DAI type
+ * @dai_index: DAI index
+ * @dma_config_tlv: DMA configuration
+ */
+struct sof_ipc4_copier {
+ struct sof_ipc4_copier_data data;
+ u32 *copier_config;
+ uint32_t ipc_config_size;
+ void *ipc_config_data;
+ struct sof_ipc4_available_audio_format available_fmt;
+ u32 frame_fmt;
+ struct sof_ipc4_msg msg;
+ struct sof_ipc4_gtw_attributes *gtw_attr;
+ u32 dai_type;
+ int dai_index;
+ struct sof_ipc4_dma_config_tlv dma_config_tlv;
+};
+
+/**
+ * struct sof_ipc4_ctrl_value_chan: generic channel mapped value data
+ * @channel: Channel ID
+ * @value: Value associated with @channel
+ */
+struct sof_ipc4_ctrl_value_chan {
+ u32 channel;
+ u32 value;
+};
+
+/**
+ * struct sof_ipc4_control_data - IPC data for kcontrol IO
+ * @msg: message structure for kcontrol IO
+ * @index: pipeline ID
+ * @chanv: channel ID and value array used by volume type controls
+ * @data: data for binary kcontrols
+ */
+struct sof_ipc4_control_data {
+ struct sof_ipc4_msg msg;
+ int index;
+
+ union {
+ DECLARE_FLEX_ARRAY(struct sof_ipc4_ctrl_value_chan, chanv);
+ DECLARE_FLEX_ARRAY(struct sof_abi_hdr, data);
+ };
+};
+
+#define SOF_IPC4_SWITCH_CONTROL_PARAM_ID 200
+#define SOF_IPC4_ENUM_CONTROL_PARAM_ID 201
+
+/**
+ * struct sof_ipc4_control_msg_payload - IPC payload for kcontrol parameters
+ * @id: unique id of the control
+ * @num_elems: Number of elements in the chanv array
+ * @reserved: reserved for future use, must be set to 0
+ * @chanv: channel ID and value array
+ */
+struct sof_ipc4_control_msg_payload {
+ uint16_t id;
+ uint16_t num_elems;
+ uint32_t reserved[4];
+ DECLARE_FLEX_ARRAY(struct sof_ipc4_ctrl_value_chan, chanv);
+} __packed;
+
+/**
+ * struct sof_ipc4_gain_params - IPC gain parameters
+ * @channels: Channels
+ * @init_val: Initial value
+ * @curve_type: Curve type
+ * @reserved: reserved for future use
+ * @curve_duration_l: Curve duration low part
+ * @curve_duration_h: Curve duration high part
+ */
+struct sof_ipc4_gain_params {
+ uint32_t channels;
+ uint32_t init_val;
+ uint32_t curve_type;
+ uint32_t reserved;
+ uint32_t curve_duration_l;
+ uint32_t curve_duration_h;
+} __packed __aligned(4);
+
+/**
+ * struct sof_ipc4_gain_data - IPC gain init blob
+ * @base_config: IPC base config data
+ * @params: Initial parameters for the gain module
+ */
+struct sof_ipc4_gain_data {
+ struct sof_ipc4_base_module_cfg base_config;
+ struct sof_ipc4_gain_params params;
+} __packed __aligned(4);
+
+/**
+ * struct sof_ipc4_gain - gain config data
+ * @data: IPC gain blob
+ * @available_fmt: Available audio format
+ * @msg: message structure for gain
+ */
+struct sof_ipc4_gain {
+ struct sof_ipc4_gain_data data;
+ struct sof_ipc4_available_audio_format available_fmt;
+ struct sof_ipc4_msg msg;
+};
+
+/**
+ * struct sof_ipc4_mixer - mixer config data
+ * @base_config: IPC base config data
+ * @available_fmt: Available audio format
+ * @msg: IPC4 message struct containing header and data info
+ */
+struct sof_ipc4_mixer {
+ struct sof_ipc4_base_module_cfg base_config;
+ struct sof_ipc4_available_audio_format available_fmt;
+ struct sof_ipc4_msg msg;
+};
+
+/*
+ * struct sof_ipc4_src_data - IPC data for SRC
+ * @base_config: IPC base config data
+ * @sink_rate: Output rate for sink module
+ */
+struct sof_ipc4_src_data {
+ struct sof_ipc4_base_module_cfg base_config;
+ uint32_t sink_rate;
+} __packed __aligned(4);
+
+/**
+ * struct sof_ipc4_src - SRC config data
+ * @data: IPC base config data
+ * @available_fmt: Available audio format
+ * @msg: IPC4 message struct containing header and data info
+ */
+struct sof_ipc4_src {
+ struct sof_ipc4_src_data data;
+ struct sof_ipc4_available_audio_format available_fmt;
+ struct sof_ipc4_msg msg;
+};
+
+/**
+ * struct sof_ipc4_base_module_cfg_ext - base module config extension containing the pin format
+ * information for the module. Both @num_input_pin_fmts and @num_output_pin_fmts cannot be 0 for a
+ * module.
+ * @num_input_pin_fmts: number of input pin formats in the @pin_formats array
+ * @num_output_pin_fmts: number of output pin formats in the @pin_formats array
+ * @reserved: reserved for future use
+ * @pin_formats: flexible array consisting of @num_input_pin_fmts input pin format items followed
+ * by @num_output_pin_fmts output pin format items
+ */
+struct sof_ipc4_base_module_cfg_ext {
+ u16 num_input_pin_fmts;
+ u16 num_output_pin_fmts;
+ u8 reserved[12];
+ DECLARE_FLEX_ARRAY(struct sof_ipc4_pin_format, pin_formats);
+} __packed;
+
+/**
+ * struct sof_ipc4_process - process config data
+ * @base_config: IPC base config data
+ * @base_config_ext: Base config extension data for module init
+ * @output_format: Output audio format
+ * @available_fmt: Available audio format
+ * @ipc_config_data: Process module config data
+ * @ipc_config_size: Size of process module config data
+ * @msg: IPC4 message struct containing header and data info
+ * @base_config_ext_size: Size of the base config extension data in bytes
+ * @init_config: Module init config type (SOF_IPC4_MODULE_INIT_CONFIG_TYPE_*)
+ */
+struct sof_ipc4_process {
+ struct sof_ipc4_base_module_cfg base_config;
+ struct sof_ipc4_base_module_cfg_ext *base_config_ext;
+ struct sof_ipc4_audio_format output_format;
+ struct sof_ipc4_available_audio_format available_fmt;
+ void *ipc_config_data;
+ uint32_t ipc_config_size;
+ struct sof_ipc4_msg msg;
+ u32 base_config_ext_size;
+ u32 init_config;
+};
+
+bool sof_ipc4_copier_is_single_format(struct snd_sof_dev *sdev,
+ struct sof_ipc4_pin_format *pin_fmts,
+ u32 pin_fmts_size);
+#endif
diff --git a/sound/soc/sof/ipc4.c b/sound/soc/sof/ipc4.c
new file mode 100644
index 000000000000..ac5c6bc66d2a
--- /dev/null
+++ b/sound/soc/sof/ipc4.c
@@ -0,0 +1,847 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2022 Intel Corporation. All rights reserved.
+//
+// Authors: Rander Wang <rander.wang@linux.intel.com>
+// Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
+//
+#include <linux/firmware.h>
+#include <sound/sof/header.h>
+#include <sound/sof/ipc4/header.h>
+#include "sof-priv.h"
+#include "sof-audio.h"
+#include "ipc4-fw-reg.h"
+#include "ipc4-priv.h"
+#include "ipc4-telemetry.h"
+#include "ops.h"
+
+static const struct sof_ipc4_fw_status {
+ int status;
+ char *msg;
+} ipc4_status[] = {
+ {0, "The operation was successful"},
+ {1, "Invalid parameter specified"},
+ {2, "Unknown message type specified"},
+ {3, "Not enough space in the IPC reply buffer to complete the request"},
+ {4, "The system or resource is busy"},
+ {5, "Replaced ADSP IPC PENDING (unused)"},
+ {6, "Unknown error while processing the request"},
+ {7, "Unsupported operation requested"},
+ {8, "Reserved (ADSP_STAGE_UNINITIALIZED removed)"},
+ {9, "Specified resource not found"},
+ {10, "A resource's ID requested to be created is already assigned"},
+ {11, "Reserved (ADSP_IPC_OUT_OF_MIPS removed)"},
+ {12, "Required resource is in invalid state"},
+ {13, "Requested power transition failed to complete"},
+ {14, "Manifest of the library being loaded is invalid"},
+ {15, "Requested service or data is unavailable on the target platform"},
+ {42, "Library target address is out of storage memory range"},
+ {43, "Reserved"},
+ {44, "Image verification by CSE failed"},
+ {100, "General module management error"},
+ {101, "Module loading failed"},
+ {102, "Integrity check of the loaded module content failed"},
+ {103, "Attempt to unload code of the module in use"},
+ {104, "Other failure of module instance initialization request"},
+ {105, "Reserved (ADSP_IPC_OUT_OF_MIPS removed)"},
+ {106, "Reserved (ADSP_IPC_CONFIG_GET_ERROR removed)"},
+ {107, "Reserved (ADSP_IPC_CONFIG_SET_ERROR removed)"},
+ {108, "Reserved (ADSP_IPC_LARGE_CONFIG_GET_ERROR removed)"},
+ {109, "Reserved (ADSP_IPC_LARGE_CONFIG_SET_ERROR removed)"},
+ {110, "Invalid (out of range) module ID provided"},
+ {111, "Invalid module instance ID provided"},
+ {112, "Invalid queue (pin) ID provided"},
+ {113, "Invalid destination queue (pin) ID provided"},
+ {114, "Reserved (ADSP_IPC_BIND_UNBIND_DST_SINK_UNSUPPORTED removed)"},
+ {115, "Reserved (ADSP_IPC_UNLOAD_INST_EXISTS removed)"},
+ {116, "Invalid target code ID provided"},
+ {117, "Injection DMA buffer is too small for probing the input pin"},
+ {118, "Extraction DMA buffer is too small for probing the output pin"},
+ {120, "Invalid ID of configuration item provided in TLV list"},
+ {121, "Invalid length of configuration item provided in TLV list"},
+ {122, "Invalid structure of configuration item provided"},
+ {140, "Initialization of DMA Gateway failed"},
+ {141, "Invalid ID of gateway provided"},
+ {142, "Setting state of DMA Gateway failed"},
+ {143, "DMA_CONTROL message targeting gateway not allocated yet"},
+ {150, "Attempt to configure SCLK while I2S port is running"},
+ {151, "Attempt to configure MCLK while I2S port is running"},
+ {152, "Attempt to stop SCLK that is not running"},
+ {153, "Attempt to stop MCLK that is not running"},
+ {160, "Reserved (ADSP_IPC_PIPELINE_NOT_INITIALIZED removed)"},
+ {161, "Reserved (ADSP_IPC_PIPELINE_NOT_EXIST removed)"},
+ {162, "Reserved (ADSP_IPC_PIPELINE_SAVE_FAILED removed)"},
+ {163, "Reserved (ADSP_IPC_PIPELINE_RESTORE_FAILED removed)"},
+ {165, "Reserved (ADSP_IPC_PIPELINE_ALREADY_EXISTS removed)"},
+};
+
+typedef void (*ipc4_notification_handler)(struct snd_sof_dev *sdev,
+ struct sof_ipc4_msg *msg);
+
+static int sof_ipc4_check_reply_status(struct snd_sof_dev *sdev, u32 status)
+{
+ int i, ret;
+
+ status &= SOF_IPC4_REPLY_STATUS;
+
+ if (!status)
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE(ipc4_status); i++) {
+ if (ipc4_status[i].status == status) {
+ dev_err(sdev->dev, "FW reported error: %u - %s\n",
+ status, ipc4_status[i].msg);
+ goto to_errno;
+ }
+ }
+
+ if (i == ARRAY_SIZE(ipc4_status))
+ dev_err(sdev->dev, "FW reported error: %u - Unknown\n", status);
+
+to_errno:
+ switch (status) {
+ case 2:
+ case 15:
+ ret = -EOPNOTSUPP;
+ break;
+ case 8:
+ case 11:
+ case 105 ... 109:
+ case 114 ... 115:
+ case 160 ... 163:
+ case 165:
+ ret = -ENOENT;
+ break;
+ case 4:
+ case 150:
+ case 151:
+ ret = -EBUSY;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_VERBOSE_IPC)
+#define DBG_IPC4_MSG_TYPE_ENTRY(type) [SOF_IPC4_##type] = #type
+static const char * const ipc4_dbg_mod_msg_type[] = {
+ DBG_IPC4_MSG_TYPE_ENTRY(MOD_INIT_INSTANCE),
+ DBG_IPC4_MSG_TYPE_ENTRY(MOD_CONFIG_GET),
+ DBG_IPC4_MSG_TYPE_ENTRY(MOD_CONFIG_SET),
+ DBG_IPC4_MSG_TYPE_ENTRY(MOD_LARGE_CONFIG_GET),
+ DBG_IPC4_MSG_TYPE_ENTRY(MOD_LARGE_CONFIG_SET),
+ DBG_IPC4_MSG_TYPE_ENTRY(MOD_BIND),
+ DBG_IPC4_MSG_TYPE_ENTRY(MOD_UNBIND),
+ DBG_IPC4_MSG_TYPE_ENTRY(MOD_SET_DX),
+ DBG_IPC4_MSG_TYPE_ENTRY(MOD_SET_D0IX),
+ DBG_IPC4_MSG_TYPE_ENTRY(MOD_ENTER_MODULE_RESTORE),
+ DBG_IPC4_MSG_TYPE_ENTRY(MOD_EXIT_MODULE_RESTORE),
+ DBG_IPC4_MSG_TYPE_ENTRY(MOD_DELETE_INSTANCE),
+};
+
+static const char * const ipc4_dbg_glb_msg_type[] = {
+ DBG_IPC4_MSG_TYPE_ENTRY(GLB_BOOT_CONFIG),
+ DBG_IPC4_MSG_TYPE_ENTRY(GLB_ROM_CONTROL),
+ DBG_IPC4_MSG_TYPE_ENTRY(GLB_IPCGATEWAY_CMD),
+ DBG_IPC4_MSG_TYPE_ENTRY(GLB_PERF_MEASUREMENTS_CMD),
+ DBG_IPC4_MSG_TYPE_ENTRY(GLB_CHAIN_DMA),
+ DBG_IPC4_MSG_TYPE_ENTRY(GLB_LOAD_MULTIPLE_MODULES),
+ DBG_IPC4_MSG_TYPE_ENTRY(GLB_UNLOAD_MULTIPLE_MODULES),
+ DBG_IPC4_MSG_TYPE_ENTRY(GLB_CREATE_PIPELINE),
+ DBG_IPC4_MSG_TYPE_ENTRY(GLB_DELETE_PIPELINE),
+ DBG_IPC4_MSG_TYPE_ENTRY(GLB_SET_PIPELINE_STATE),
+ DBG_IPC4_MSG_TYPE_ENTRY(GLB_GET_PIPELINE_STATE),
+ DBG_IPC4_MSG_TYPE_ENTRY(GLB_GET_PIPELINE_CONTEXT_SIZE),
+ DBG_IPC4_MSG_TYPE_ENTRY(GLB_SAVE_PIPELINE),
+ DBG_IPC4_MSG_TYPE_ENTRY(GLB_RESTORE_PIPELINE),
+ DBG_IPC4_MSG_TYPE_ENTRY(GLB_LOAD_LIBRARY),
+ DBG_IPC4_MSG_TYPE_ENTRY(GLB_LOAD_LIBRARY_PREPARE),
+ DBG_IPC4_MSG_TYPE_ENTRY(GLB_INTERNAL_MESSAGE),
+ DBG_IPC4_MSG_TYPE_ENTRY(GLB_NOTIFICATION),
+};
+
+#define DBG_IPC4_NOTIFICATION_TYPE_ENTRY(type) [SOF_IPC4_NOTIFY_##type] = #type
+static const char * const ipc4_dbg_notification_type[] = {
+ DBG_IPC4_NOTIFICATION_TYPE_ENTRY(PHRASE_DETECTED),
+ DBG_IPC4_NOTIFICATION_TYPE_ENTRY(RESOURCE_EVENT),
+ DBG_IPC4_NOTIFICATION_TYPE_ENTRY(LOG_BUFFER_STATUS),
+ DBG_IPC4_NOTIFICATION_TYPE_ENTRY(TIMESTAMP_CAPTURED),
+ DBG_IPC4_NOTIFICATION_TYPE_ENTRY(FW_READY),
+ DBG_IPC4_NOTIFICATION_TYPE_ENTRY(FW_AUD_CLASS_RESULT),
+ DBG_IPC4_NOTIFICATION_TYPE_ENTRY(EXCEPTION_CAUGHT),
+ DBG_IPC4_NOTIFICATION_TYPE_ENTRY(MODULE_NOTIFICATION),
+ DBG_IPC4_NOTIFICATION_TYPE_ENTRY(PROBE_DATA_AVAILABLE),
+ DBG_IPC4_NOTIFICATION_TYPE_ENTRY(ASYNC_MSG_SRVC_MESSAGE),
+};
+
+static void sof_ipc4_log_header(struct device *dev, u8 *text, struct sof_ipc4_msg *msg,
+ bool data_size_valid)
+{
+ u32 val, type;
+ const u8 *str2 = NULL;
+ const u8 *str = NULL;
+
+ val = msg->primary & SOF_IPC4_MSG_TARGET_MASK;
+ type = SOF_IPC4_MSG_TYPE_GET(msg->primary);
+
+ if (val == SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG)) {
+ /* Module message */
+ if (type < SOF_IPC4_MOD_TYPE_LAST)
+ str = ipc4_dbg_mod_msg_type[type];
+ if (!str)
+ str = "Unknown Module message type";
+ } else {
+ /* Global FW message */
+ if (type < SOF_IPC4_GLB_TYPE_LAST)
+ str = ipc4_dbg_glb_msg_type[type];
+ if (!str)
+ str = "Unknown Global message type";
+
+ if (type == SOF_IPC4_GLB_NOTIFICATION) {
+ /* Notification message */
+ u32 notif = SOF_IPC4_NOTIFICATION_TYPE_GET(msg->primary);
+
+ /* Do not print log buffer notification if not desired */
+ if (notif == SOF_IPC4_NOTIFY_LOG_BUFFER_STATUS &&
+ !sof_debug_check_flag(SOF_DBG_PRINT_DMA_POSITION_UPDATE_LOGS))
+ return;
+
+ if (notif < SOF_IPC4_NOTIFY_TYPE_LAST)
+ str2 = ipc4_dbg_notification_type[notif];
+ if (!str2)
+ str2 = "Unknown Global notification";
+ }
+ }
+
+ if (str2) {
+ if (data_size_valid && msg->data_size)
+ dev_dbg(dev, "%s: %#x|%#x: %s|%s [data size: %zu]\n",
+ text, msg->primary, msg->extension, str, str2,
+ msg->data_size);
+ else
+ dev_dbg(dev, "%s: %#x|%#x: %s|%s\n", text, msg->primary,
+ msg->extension, str, str2);
+ } else {
+ if (data_size_valid && msg->data_size)
+ dev_dbg(dev, "%s: %#x|%#x: %s [data size: %zu]\n",
+ text, msg->primary, msg->extension, str,
+ msg->data_size);
+ else
+ dev_dbg(dev, "%s: %#x|%#x: %s\n", text, msg->primary,
+ msg->extension, str);
+ }
+}
+#else /* CONFIG_SND_SOC_SOF_DEBUG_VERBOSE_IPC */
+static void sof_ipc4_log_header(struct device *dev, u8 *text, struct sof_ipc4_msg *msg,
+ bool data_size_valid)
+{
+ /* Do not print log buffer notification if not desired */
+ if (!sof_debug_check_flag(SOF_DBG_PRINT_DMA_POSITION_UPDATE_LOGS) &&
+ !SOF_IPC4_MSG_IS_MODULE_MSG(msg->primary) &&
+ SOF_IPC4_MSG_TYPE_GET(msg->primary) == SOF_IPC4_GLB_NOTIFICATION &&
+ SOF_IPC4_NOTIFICATION_TYPE_GET(msg->primary) == SOF_IPC4_NOTIFY_LOG_BUFFER_STATUS)
+ return;
+
+ if (data_size_valid && msg->data_size)
+ dev_dbg(dev, "%s: %#x|%#x [data size: %zu]\n", text,
+ msg->primary, msg->extension, msg->data_size);
+ else
+ dev_dbg(dev, "%s: %#x|%#x\n", text, msg->primary, msg->extension);
+}
+#endif
+
+static void sof_ipc4_dump_payload(struct snd_sof_dev *sdev,
+ void *ipc_data, size_t size)
+{
+ print_hex_dump_debug("Message payload: ", DUMP_PREFIX_OFFSET,
+ 16, 4, ipc_data, size, false);
+}
+
+static int sof_ipc4_get_reply(struct snd_sof_dev *sdev)
+{
+ struct snd_sof_ipc_msg *msg = sdev->msg;
+ struct sof_ipc4_msg *ipc4_reply;
+ int ret;
+
+ /* get the generic reply */
+ ipc4_reply = msg->reply_data;
+
+ sof_ipc4_log_header(sdev->dev, "ipc tx reply", ipc4_reply, false);
+
+ ret = sof_ipc4_check_reply_status(sdev, ipc4_reply->primary);
+ if (ret)
+ return ret;
+
+ /* No other information is expected for non large config get replies */
+ if (!msg->reply_size || !SOF_IPC4_MSG_IS_MODULE_MSG(ipc4_reply->primary) ||
+ (SOF_IPC4_MSG_TYPE_GET(ipc4_reply->primary) != SOF_IPC4_MOD_LARGE_CONFIG_GET))
+ return 0;
+
+ /* Read the requested payload */
+ snd_sof_dsp_mailbox_read(sdev, sdev->dsp_box.offset, ipc4_reply->data_ptr,
+ msg->reply_size);
+
+ return 0;
+}
+
+/* wait for IPC message reply */
+static int ipc4_wait_tx_done(struct snd_sof_ipc *ipc, void *reply_data)
+{
+ struct snd_sof_ipc_msg *msg = &ipc->msg;
+ struct sof_ipc4_msg *ipc4_msg = msg->msg_data;
+ struct snd_sof_dev *sdev = ipc->sdev;
+ int ret;
+
+ /* wait for DSP IPC completion */
+ ret = wait_event_timeout(msg->waitq, msg->ipc_complete,
+ msecs_to_jiffies(sdev->ipc_timeout));
+ if (ret == 0) {
+ dev_err(sdev->dev, "ipc timed out for %#x|%#x\n",
+ ipc4_msg->primary, ipc4_msg->extension);
+ snd_sof_handle_fw_exception(ipc->sdev, "IPC timeout");
+ return -ETIMEDOUT;
+ }
+
+ if (msg->reply_error) {
+ dev_err(sdev->dev, "ipc error for msg %#x|%#x\n",
+ ipc4_msg->primary, ipc4_msg->extension);
+ ret = msg->reply_error;
+ } else {
+ if (reply_data) {
+ struct sof_ipc4_msg *ipc4_reply = msg->reply_data;
+ struct sof_ipc4_msg *ipc4_reply_data = reply_data;
+
+ /* Copy the header */
+ ipc4_reply_data->header_u64 = ipc4_reply->header_u64;
+ if (msg->reply_size && ipc4_reply_data->data_ptr) {
+ /* copy the payload returned from DSP */
+ memcpy(ipc4_reply_data->data_ptr, ipc4_reply->data_ptr,
+ msg->reply_size);
+ ipc4_reply_data->data_size = msg->reply_size;
+ }
+ }
+
+ ret = 0;
+ sof_ipc4_log_header(sdev->dev, "ipc tx done ", ipc4_msg, true);
+ }
+
+ /* re-enable dumps after successful IPC tx */
+ if (sdev->ipc_dump_printed) {
+ sdev->dbg_dump_printed = false;
+ sdev->ipc_dump_printed = false;
+ }
+
+ return ret;
+}
+
+static int ipc4_tx_msg_unlocked(struct snd_sof_ipc *ipc,
+ void *msg_data, size_t msg_bytes,
+ void *reply_data, size_t reply_bytes)
+{
+ struct sof_ipc4_msg *ipc4_msg = msg_data;
+ struct snd_sof_dev *sdev = ipc->sdev;
+ int ret;
+
+ if (msg_bytes > ipc->max_payload_size || reply_bytes > ipc->max_payload_size)
+ return -EINVAL;
+
+ sof_ipc4_log_header(sdev->dev, "ipc tx ", msg_data, true);
+
+ ret = sof_ipc_send_msg(sdev, msg_data, msg_bytes, reply_bytes);
+ if (ret) {
+ dev_err_ratelimited(sdev->dev,
+ "%s: ipc message send for %#x|%#x failed: %d\n",
+ __func__, ipc4_msg->primary, ipc4_msg->extension, ret);
+ return ret;
+ }
+
+ /* now wait for completion */
+ return ipc4_wait_tx_done(ipc, reply_data);
+}
+
+static int sof_ipc4_tx_msg(struct snd_sof_dev *sdev, void *msg_data, size_t msg_bytes,
+ void *reply_data, size_t reply_bytes, bool no_pm)
+{
+ struct snd_sof_ipc *ipc = sdev->ipc;
+ int ret;
+
+ if (!msg_data)
+ return -EINVAL;
+
+ if (!no_pm) {
+ const struct sof_dsp_power_state target_state = {
+ .state = SOF_DSP_PM_D0,
+ };
+
+ /* ensure the DSP is in D0i0 before sending a new IPC */
+ ret = snd_sof_dsp_set_power_state(sdev, &target_state);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Serialise IPC TX */
+ mutex_lock(&ipc->tx_mutex);
+
+ ret = ipc4_tx_msg_unlocked(ipc, msg_data, msg_bytes, reply_data, reply_bytes);
+
+ if (sof_debug_check_flag(SOF_DBG_DUMP_IPC_MESSAGE_PAYLOAD)) {
+ struct sof_ipc4_msg *msg = NULL;
+
+ /* payload is indicated by non zero msg/reply_bytes */
+ if (msg_bytes)
+ msg = msg_data;
+ else if (reply_bytes)
+ msg = reply_data;
+
+ if (msg)
+ sof_ipc4_dump_payload(sdev, msg->data_ptr, msg->data_size);
+ }
+
+ mutex_unlock(&ipc->tx_mutex);
+
+ return ret;
+}
+
+static int sof_ipc4_set_get_data(struct snd_sof_dev *sdev, void *data,
+ size_t payload_bytes, bool set)
+{
+ const struct sof_dsp_power_state target_state = {
+ .state = SOF_DSP_PM_D0,
+ };
+ size_t payload_limit = sdev->ipc->max_payload_size;
+ struct sof_ipc4_msg *ipc4_msg = data;
+ struct sof_ipc4_msg tx = {{ 0 }};
+ struct sof_ipc4_msg rx = {{ 0 }};
+ size_t remaining = payload_bytes;
+ size_t offset = 0;
+ size_t chunk_size;
+ int ret;
+
+ if (!data)
+ return -EINVAL;
+
+ if ((ipc4_msg->primary & SOF_IPC4_MSG_TARGET_MASK) !=
+ SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG))
+ return -EINVAL;
+
+ ipc4_msg->primary &= ~SOF_IPC4_MSG_TYPE_MASK;
+ tx.primary = ipc4_msg->primary;
+ tx.extension = ipc4_msg->extension;
+
+ if (set)
+ tx.primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_LARGE_CONFIG_SET);
+ else
+ tx.primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_LARGE_CONFIG_GET);
+
+ tx.extension &= ~SOF_IPC4_MOD_EXT_MSG_SIZE_MASK;
+ tx.extension |= SOF_IPC4_MOD_EXT_MSG_SIZE(payload_bytes);
+
+ tx.extension |= SOF_IPC4_MOD_EXT_MSG_FIRST_BLOCK(1);
+
+ /* ensure the DSP is in D0i0 before sending IPC */
+ ret = snd_sof_dsp_set_power_state(sdev, &target_state);
+ if (ret < 0)
+ return ret;
+
+ /* Serialise IPC TX */
+ mutex_lock(&sdev->ipc->tx_mutex);
+
+ do {
+ size_t tx_size, rx_size;
+
+ if (remaining > payload_limit) {
+ chunk_size = payload_limit;
+ } else {
+ chunk_size = remaining;
+ if (set)
+ tx.extension |= SOF_IPC4_MOD_EXT_MSG_LAST_BLOCK(1);
+ }
+
+ if (offset) {
+ tx.extension &= ~SOF_IPC4_MOD_EXT_MSG_FIRST_BLOCK_MASK;
+ tx.extension &= ~SOF_IPC4_MOD_EXT_MSG_SIZE_MASK;
+ tx.extension |= SOF_IPC4_MOD_EXT_MSG_SIZE(offset);
+ }
+
+ if (set) {
+ tx.data_size = chunk_size;
+ tx.data_ptr = ipc4_msg->data_ptr + offset;
+
+ tx_size = chunk_size;
+ rx_size = 0;
+ } else {
+ rx.primary = 0;
+ rx.extension = 0;
+ rx.data_size = chunk_size;
+ rx.data_ptr = ipc4_msg->data_ptr + offset;
+
+ tx_size = 0;
+ rx_size = chunk_size;
+ }
+
+ /* Send the message for the current chunk */
+ ret = ipc4_tx_msg_unlocked(sdev->ipc, &tx, tx_size, &rx, rx_size);
+ if (ret < 0) {
+ dev_err(sdev->dev,
+ "%s: large config %s failed at offset %zu: %d\n",
+ __func__, set ? "set" : "get", offset, ret);
+ goto out;
+ }
+
+ if (!set && rx.extension & SOF_IPC4_MOD_EXT_MSG_FIRST_BLOCK_MASK) {
+ /* Verify the firmware reported total payload size */
+ rx_size = rx.extension & SOF_IPC4_MOD_EXT_MSG_SIZE_MASK;
+
+ if (rx_size > payload_bytes) {
+ dev_err(sdev->dev,
+ "%s: Receive buffer (%zu) is too small for %zu\n",
+ __func__, payload_bytes, rx_size);
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ if (rx_size < chunk_size) {
+ chunk_size = rx_size;
+ remaining = rx_size;
+ } else if (rx_size < payload_bytes) {
+ remaining = rx_size;
+ }
+ }
+
+ offset += chunk_size;
+ remaining -= chunk_size;
+ } while (remaining);
+
+ /* Adjust the received data size if needed */
+ if (!set && payload_bytes != offset)
+ ipc4_msg->data_size = offset;
+
+out:
+ if (sof_debug_check_flag(SOF_DBG_DUMP_IPC_MESSAGE_PAYLOAD))
+ sof_ipc4_dump_payload(sdev, ipc4_msg->data_ptr, ipc4_msg->data_size);
+
+ mutex_unlock(&sdev->ipc->tx_mutex);
+
+ return ret;
+}
+
+static int sof_ipc4_init_msg_memory(struct snd_sof_dev *sdev)
+{
+ struct sof_ipc4_msg *ipc4_msg;
+ struct snd_sof_ipc_msg *msg = &sdev->ipc->msg;
+
+ /* TODO: get max_payload_size from firmware */
+ sdev->ipc->max_payload_size = SOF_IPC4_MSG_MAX_SIZE;
+
+ /* Allocate memory for the ipc4 container and the maximum payload */
+ msg->reply_data = devm_kzalloc(sdev->dev, sdev->ipc->max_payload_size +
+ sizeof(struct sof_ipc4_msg), GFP_KERNEL);
+ if (!msg->reply_data)
+ return -ENOMEM;
+
+ ipc4_msg = msg->reply_data;
+ ipc4_msg->data_ptr = msg->reply_data + sizeof(struct sof_ipc4_msg);
+
+ return 0;
+}
+
+size_t sof_ipc4_find_debug_slot_offset_by_type(struct snd_sof_dev *sdev,
+ u32 slot_type)
+{
+ size_t slot_desc_type_offset;
+ u32 type;
+ int i;
+
+ /* The type is the second u32 in the slot descriptor */
+ slot_desc_type_offset = sdev->debug_box.offset + sizeof(u32);
+ for (i = 0; i < SOF_IPC4_MAX_DEBUG_SLOTS; i++) {
+ sof_mailbox_read(sdev, slot_desc_type_offset, &type, sizeof(type));
+
+ if (type == slot_type)
+ return sdev->debug_box.offset + (i + 1) * SOF_IPC4_DEBUG_SLOT_SIZE;
+
+ slot_desc_type_offset += SOF_IPC4_DEBUG_DESCRIPTOR_SIZE;
+ }
+
+ dev_dbg(sdev->dev, "Slot type %#x is not available in debug window\n", slot_type);
+ return 0;
+}
+EXPORT_SYMBOL(sof_ipc4_find_debug_slot_offset_by_type);
+
+static int ipc4_fw_ready(struct snd_sof_dev *sdev, struct sof_ipc4_msg *ipc4_msg)
+{
+ /* no need to re-check version/ABI for subsequent boots */
+ if (!sdev->first_boot)
+ return 0;
+
+ sof_ipc4_create_exception_debugfs_node(sdev);
+
+ return sof_ipc4_init_msg_memory(sdev);
+}
+
+static void sof_ipc4_module_notification_handler(struct snd_sof_dev *sdev,
+ struct sof_ipc4_msg *ipc4_msg)
+{
+ struct sof_ipc4_notify_module_data *data = ipc4_msg->data_ptr;
+
+ /*
+ * If the notification includes additional, module specific data, then
+ * we need to re-allocate the buffer and re-read the whole payload,
+ * including the event_data
+ */
+ if (data->event_data_size) {
+ void *new;
+ int ret;
+
+ ipc4_msg->data_size += data->event_data_size;
+
+ new = krealloc(ipc4_msg->data_ptr, ipc4_msg->data_size, GFP_KERNEL);
+ if (!new) {
+ ipc4_msg->data_size -= data->event_data_size;
+ return;
+ }
+
+ /* re-read the whole payload */
+ ipc4_msg->data_ptr = new;
+ ret = snd_sof_ipc_msg_data(sdev, NULL, ipc4_msg->data_ptr,
+ ipc4_msg->data_size);
+ if (ret < 0) {
+ dev_err(sdev->dev,
+ "Failed to read the full module notification: %d\n",
+ ret);
+ return;
+ }
+ data = ipc4_msg->data_ptr;
+ }
+
+ /* Handle ALSA kcontrol notification */
+ if ((data->event_id & SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_MAGIC_MASK) ==
+ SOF_IPC4_NOTIFY_MODULE_EVENTID_ALSA_MAGIC_VAL) {
+ const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
+
+ if (tplg_ops->control->update)
+ tplg_ops->control->update(sdev, ipc4_msg);
+ }
+}
+
+static void sof_ipc4_rx_msg(struct snd_sof_dev *sdev)
+{
+ struct sof_ipc4_msg *ipc4_msg = sdev->ipc->msg.rx_data;
+ ipc4_notification_handler handler_func = NULL;
+ size_t data_size = 0;
+ int err;
+
+ if (!ipc4_msg || !SOF_IPC4_MSG_IS_NOTIFICATION(ipc4_msg->primary))
+ return;
+
+ ipc4_msg->data_ptr = NULL;
+ ipc4_msg->data_size = 0;
+
+ sof_ipc4_log_header(sdev->dev, "ipc rx ", ipc4_msg, false);
+
+ switch (SOF_IPC4_NOTIFICATION_TYPE_GET(ipc4_msg->primary)) {
+ case SOF_IPC4_NOTIFY_FW_READY:
+ /* check for FW boot completion */
+ if (sdev->fw_state == SOF_FW_BOOT_IN_PROGRESS) {
+ err = ipc4_fw_ready(sdev, ipc4_msg);
+ if (err < 0)
+ sof_set_fw_state(sdev, SOF_FW_BOOT_READY_FAILED);
+ else
+ sof_set_fw_state(sdev, SOF_FW_BOOT_READY_OK);
+
+ /* wake up firmware loader */
+ wake_up(&sdev->boot_wait);
+ }
+
+ break;
+ case SOF_IPC4_NOTIFY_RESOURCE_EVENT:
+ data_size = sizeof(struct sof_ipc4_notify_resource_data);
+ break;
+ case SOF_IPC4_NOTIFY_LOG_BUFFER_STATUS:
+ sof_ipc4_mtrace_update_pos(sdev, SOF_IPC4_LOG_CORE_GET(ipc4_msg->primary));
+ break;
+ case SOF_IPC4_NOTIFY_EXCEPTION_CAUGHT:
+ snd_sof_dsp_panic(sdev, 0, true);
+ break;
+ case SOF_IPC4_NOTIFY_MODULE_NOTIFICATION:
+ data_size = sizeof(struct sof_ipc4_notify_module_data);
+ handler_func = sof_ipc4_module_notification_handler;
+ break;
+ default:
+ dev_dbg(sdev->dev, "Unhandled DSP message: %#x|%#x\n",
+ ipc4_msg->primary, ipc4_msg->extension);
+ break;
+ }
+
+ if (data_size) {
+ ipc4_msg->data_ptr = kmalloc(data_size, GFP_KERNEL);
+ if (!ipc4_msg->data_ptr)
+ return;
+
+ ipc4_msg->data_size = data_size;
+ err = snd_sof_ipc_msg_data(sdev, NULL, ipc4_msg->data_ptr, ipc4_msg->data_size);
+ if (err < 0) {
+ dev_err(sdev->dev, "failed to read IPC notification data: %d\n", err);
+ kfree(ipc4_msg->data_ptr);
+ ipc4_msg->data_ptr = NULL;
+ ipc4_msg->data_size = 0;
+ return;
+ }
+ }
+
+ /* Handle notifications with payload */
+ if (handler_func)
+ handler_func(sdev, ipc4_msg);
+
+ sof_ipc4_log_header(sdev->dev, "ipc rx done ", ipc4_msg, true);
+
+ if (data_size) {
+ if (sof_debug_check_flag(SOF_DBG_DUMP_IPC_MESSAGE_PAYLOAD))
+ sof_ipc4_dump_payload(sdev, ipc4_msg->data_ptr,
+ ipc4_msg->data_size);
+
+ kfree(ipc4_msg->data_ptr);
+ ipc4_msg->data_ptr = NULL;
+ ipc4_msg->data_size = 0;
+ }
+}
+
+static int sof_ipc4_set_core_state(struct snd_sof_dev *sdev, int core_idx, bool on)
+{
+ struct sof_ipc4_dx_state_info dx_state;
+ struct sof_ipc4_msg msg;
+
+ dx_state.core_mask = BIT(core_idx);
+ if (on)
+ dx_state.dx_mask = BIT(core_idx);
+ else
+ dx_state.dx_mask = 0;
+
+ msg.primary = SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_SET_DX);
+ msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ msg.primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG);
+ msg.extension = 0;
+ msg.data_ptr = &dx_state;
+ msg.data_size = sizeof(dx_state);
+
+ return sof_ipc4_tx_msg(sdev, &msg, msg.data_size, NULL, 0, false);
+}
+
+/*
+ * The context save callback is used to send a message to the firmware notifying
+ * it that the primary core is going to be turned off, which is used as an
+ * indication to prepare for a full power down, thus preparing for IMR boot
+ * (when supported)
+ *
+ * Note: in IPC4 there is no message used to restore context, thus no context
+ * restore callback is implemented
+ */
+static int sof_ipc4_ctx_save(struct snd_sof_dev *sdev)
+{
+ return sof_ipc4_set_core_state(sdev, SOF_DSP_PRIMARY_CORE, false);
+}
+
+static int sof_ipc4_set_pm_gate(struct snd_sof_dev *sdev, u32 flags)
+{
+ struct sof_ipc4_msg msg = {{0}};
+
+ msg.primary = SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_SET_D0IX);
+ msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ msg.primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG);
+ msg.extension = flags;
+
+ return sof_ipc4_tx_msg(sdev, &msg, 0, NULL, 0, true);
+}
+
+static const struct sof_ipc_pm_ops ipc4_pm_ops = {
+ .ctx_save = sof_ipc4_ctx_save,
+ .set_core_state = sof_ipc4_set_core_state,
+ .set_pm_gate = sof_ipc4_set_pm_gate,
+};
+
+static int sof_ipc4_init(struct snd_sof_dev *sdev)
+{
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+ int inbox_offset;
+
+ mutex_init(&ipc4_data->pipeline_state_mutex);
+
+ xa_init_flags(&ipc4_data->fw_lib_xa, XA_FLAGS_ALLOC);
+
+ /* Set up the windows for IPC communication */
+ inbox_offset = snd_sof_dsp_get_mailbox_offset(sdev);
+ if (inbox_offset < 0) {
+ dev_err(sdev->dev, "%s: No mailbox offset\n", __func__);
+ return inbox_offset;
+ }
+
+ sdev->dsp_box.offset = inbox_offset;
+ sdev->dsp_box.size = SOF_IPC4_MSG_MAX_SIZE;
+ sdev->host_box.offset = snd_sof_dsp_get_window_offset(sdev,
+ SOF_IPC4_OUTBOX_WINDOW_IDX);
+ sdev->host_box.size = SOF_IPC4_MSG_MAX_SIZE;
+
+ sdev->debug_box.offset = snd_sof_dsp_get_window_offset(sdev,
+ SOF_IPC4_DEBUG_WINDOW_IDX);
+
+ sdev->fw_info_box.offset = snd_sof_dsp_get_window_offset(sdev,
+ SOF_IPC4_INBOX_WINDOW_IDX);
+ sdev->fw_info_box.size = sizeof(struct sof_ipc4_fw_registers);
+
+ dev_dbg(sdev->dev, "mailbox upstream %#x - size %#x\n",
+ sdev->dsp_box.offset, SOF_IPC4_MSG_MAX_SIZE);
+ dev_dbg(sdev->dev, "mailbox downstream %#x - size %#x\n",
+ sdev->host_box.offset, SOF_IPC4_MSG_MAX_SIZE);
+ dev_dbg(sdev->dev, "debug box %#x\n", sdev->debug_box.offset);
+
+ return 0;
+}
+
+static void sof_ipc4_exit(struct snd_sof_dev *sdev)
+{
+ struct sof_ipc4_fw_data *ipc4_data = sdev->private;
+ struct sof_ipc4_fw_library *fw_lib;
+ unsigned long lib_id;
+
+ xa_for_each(&ipc4_data->fw_lib_xa, lib_id, fw_lib) {
+ /*
+ * The basefw (ID == 0) is handled by generic code, it is not
+ * loaded by IPC4 code.
+ */
+ if (lib_id != 0)
+ release_firmware(fw_lib->sof_fw.fw);
+
+ fw_lib->sof_fw.fw = NULL;
+ }
+
+ xa_destroy(&ipc4_data->fw_lib_xa);
+}
+
+static int sof_ipc4_post_boot(struct snd_sof_dev *sdev)
+{
+ if (sdev->first_boot)
+ return sof_ipc4_query_fw_configuration(sdev);
+
+ return sof_ipc4_reload_fw_libraries(sdev);
+}
+
+const struct sof_ipc_ops ipc4_ops = {
+ .init = sof_ipc4_init,
+ .exit = sof_ipc4_exit,
+ .post_fw_boot = sof_ipc4_post_boot,
+ .tx_msg = sof_ipc4_tx_msg,
+ .rx_msg = sof_ipc4_rx_msg,
+ .set_get_data = sof_ipc4_set_get_data,
+ .get_reply = sof_ipc4_get_reply,
+ .pm = &ipc4_pm_ops,
+ .fw_loader = &ipc4_loader_ops,
+ .tplg = &ipc4_tplg_ops,
+ .pcm = &ipc4_pcm_ops,
+ .fw_tracing = &ipc4_mtrace_ops,
+};
diff --git a/sound/soc/sof/loader.c b/sound/soc/sof/loader.c
index b94fa5f5d480..2f8555f11c03 100644
--- a/sound/soc/sof/loader.c
+++ b/sound/soc/sof/loader.c
@@ -11,617 +11,9 @@
//
#include <linux/firmware.h>
-#include <sound/sof.h>
-#include <sound/sof/ext_manifest.h>
+#include "sof-priv.h"
#include "ops.h"
-static int get_ext_windows(struct snd_sof_dev *sdev,
- const struct sof_ipc_ext_data_hdr *ext_hdr)
-{
- const struct sof_ipc_window *w =
- container_of(ext_hdr, struct sof_ipc_window, ext_hdr);
- size_t w_size = struct_size(w, window, w->num_windows);
-
- if (w->num_windows == 0 || w->num_windows > SOF_IPC_MAX_ELEMS)
- return -EINVAL;
-
- if (sdev->info_window) {
- if (memcmp(sdev->info_window, w, w_size)) {
- dev_err(sdev->dev, "error: mismatch between window descriptor from extended manifest and mailbox");
- return -EINVAL;
- }
- return 0;
- }
-
- /* keep a local copy of the data */
- sdev->info_window = kmemdup(w, w_size, GFP_KERNEL);
- if (!sdev->info_window)
- return -ENOMEM;
-
- return 0;
-}
-
-static int get_cc_info(struct snd_sof_dev *sdev,
- const struct sof_ipc_ext_data_hdr *ext_hdr)
-{
- int ret;
-
- const struct sof_ipc_cc_version *cc =
- container_of(ext_hdr, struct sof_ipc_cc_version, ext_hdr);
-
- if (sdev->cc_version) {
- if (memcmp(sdev->cc_version, cc, cc->ext_hdr.hdr.size)) {
- dev_err(sdev->dev, "error: receive diverged cc_version descriptions");
- return -EINVAL;
- }
- return 0;
- }
-
- dev_dbg(sdev->dev, "Firmware info: used compiler %s %d:%d:%d%s used optimization flags %s\n",
- cc->name, cc->major, cc->minor, cc->micro, cc->desc,
- cc->optim);
-
- /* create read-only cc_version debugfs to store compiler version info */
- /* use local copy of the cc_version to prevent data corruption */
- if (sdev->first_boot) {
- sdev->cc_version = devm_kmalloc(sdev->dev, cc->ext_hdr.hdr.size,
- GFP_KERNEL);
-
- if (!sdev->cc_version)
- return -ENOMEM;
-
- memcpy(sdev->cc_version, cc, cc->ext_hdr.hdr.size);
- ret = snd_sof_debugfs_buf_item(sdev, sdev->cc_version,
- cc->ext_hdr.hdr.size,
- "cc_version", 0444);
-
- /* errors are only due to memory allocation, not debugfs */
- if (ret < 0) {
- dev_err(sdev->dev, "error: snd_sof_debugfs_buf_item failed\n");
- return ret;
- }
- }
-
- return 0;
-}
-
-/* parse the extended FW boot data structures from FW boot message */
-int snd_sof_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 bar, u32 offset)
-{
- struct sof_ipc_ext_data_hdr *ext_hdr;
- void *ext_data;
- int ret = 0;
-
- ext_data = kzalloc(PAGE_SIZE, GFP_KERNEL);
- if (!ext_data)
- return -ENOMEM;
-
- /* get first header */
- snd_sof_dsp_block_read(sdev, bar, offset, ext_data,
- sizeof(*ext_hdr));
- ext_hdr = ext_data;
-
- while (ext_hdr->hdr.cmd == SOF_IPC_FW_READY) {
- /* read in ext structure */
- snd_sof_dsp_block_read(sdev, bar, offset + sizeof(*ext_hdr),
- (void *)((u8 *)ext_data + sizeof(*ext_hdr)),
- ext_hdr->hdr.size - sizeof(*ext_hdr));
-
- dev_dbg(sdev->dev, "found ext header type %d size 0x%x\n",
- ext_hdr->type, ext_hdr->hdr.size);
-
- /* process structure data */
- switch (ext_hdr->type) {
- case SOF_IPC_EXT_WINDOW:
- ret = get_ext_windows(sdev, ext_hdr);
- break;
- case SOF_IPC_EXT_CC_INFO:
- ret = get_cc_info(sdev, ext_hdr);
- break;
- default:
- dev_warn(sdev->dev, "warning: unknown ext header type %d size 0x%x\n",
- ext_hdr->type, ext_hdr->hdr.size);
- ret = 0;
- break;
- }
-
- if (ret < 0) {
- dev_err(sdev->dev, "error: failed to parse ext data type %d\n",
- ext_hdr->type);
- break;
- }
-
- /* move to next header */
- offset += ext_hdr->hdr.size;
- snd_sof_dsp_block_read(sdev, bar, offset, ext_data,
- sizeof(*ext_hdr));
- ext_hdr = ext_data;
- }
-
- kfree(ext_data);
- return ret;
-}
-EXPORT_SYMBOL(snd_sof_fw_parse_ext_data);
-
-static int ext_man_get_fw_version(struct snd_sof_dev *sdev,
- const struct sof_ext_man_elem_header *hdr)
-{
- const struct sof_ext_man_fw_version *v =
- container_of(hdr, struct sof_ext_man_fw_version, hdr);
-
- memcpy(&sdev->fw_ready.version, &v->version, sizeof(v->version));
- sdev->fw_ready.flags = v->flags;
-
- /* log ABI versions and check FW compatibility */
- return snd_sof_ipc_valid(sdev);
-}
-
-static int ext_man_get_windows(struct snd_sof_dev *sdev,
- const struct sof_ext_man_elem_header *hdr)
-{
- const struct sof_ext_man_window *w;
-
- w = container_of(hdr, struct sof_ext_man_window, hdr);
-
- return get_ext_windows(sdev, &w->ipc_window.ext_hdr);
-}
-
-static int ext_man_get_cc_info(struct snd_sof_dev *sdev,
- const struct sof_ext_man_elem_header *hdr)
-{
- const struct sof_ext_man_cc_version *cc;
-
- cc = container_of(hdr, struct sof_ext_man_cc_version, hdr);
-
- return get_cc_info(sdev, &cc->cc_version.ext_hdr);
-}
-
-static ssize_t snd_sof_ext_man_size(const struct firmware *fw)
-{
- const struct sof_ext_man_header *head;
-
- head = (struct sof_ext_man_header *)fw->data;
-
- /*
- * assert fw size is big enough to contain extended manifest header,
- * it prevents from reading unallocated memory from `head` in following
- * step.
- */
- if (fw->size < sizeof(*head))
- return -EINVAL;
-
- /*
- * When fw points to extended manifest,
- * then first u32 must be equal SOF_EXT_MAN_MAGIC_NUMBER.
- */
- if (head->magic == SOF_EXT_MAN_MAGIC_NUMBER)
- return head->full_size;
-
- /* otherwise given fw don't have an extended manifest */
- return 0;
-}
-
-/* parse extended FW manifest data structures */
-static int snd_sof_fw_ext_man_parse(struct snd_sof_dev *sdev,
- const struct firmware *fw)
-{
- const struct sof_ext_man_elem_header *elem_hdr;
- const struct sof_ext_man_header *head;
- ssize_t ext_man_size;
- ssize_t remaining;
- uintptr_t iptr;
- int ret = 0;
-
- head = (struct sof_ext_man_header *)fw->data;
- remaining = head->full_size - head->header_size;
- ext_man_size = snd_sof_ext_man_size(fw);
-
- /* Assert firmware starts with extended manifest */
- if (ext_man_size <= 0)
- return ext_man_size;
-
- /* incompatible version */
- if (SOF_EXT_MAN_VERSION_INCOMPATIBLE(SOF_EXT_MAN_VERSION,
- head->header_version)) {
- dev_err(sdev->dev, "error: extended manifest version 0x%X differ from used 0x%X\n",
- head->header_version, SOF_EXT_MAN_VERSION);
- return -EINVAL;
- }
-
- /* get first extended manifest element header */
- iptr = (uintptr_t)fw->data + head->header_size;
-
- while (remaining > sizeof(*elem_hdr)) {
- elem_hdr = (struct sof_ext_man_elem_header *)iptr;
-
- dev_dbg(sdev->dev, "found sof_ext_man header type %d size 0x%X\n",
- elem_hdr->type, elem_hdr->size);
-
- if (elem_hdr->size < sizeof(*elem_hdr) ||
- elem_hdr->size > remaining) {
- dev_err(sdev->dev, "error: invalid sof_ext_man header size, type %d size 0x%X\n",
- elem_hdr->type, elem_hdr->size);
- return -EINVAL;
- }
-
- /* process structure data */
- switch (elem_hdr->type) {
- case SOF_EXT_MAN_ELEM_FW_VERSION:
- ret = ext_man_get_fw_version(sdev, elem_hdr);
- break;
- case SOF_EXT_MAN_ELEM_WINDOW:
- ret = ext_man_get_windows(sdev, elem_hdr);
- break;
- case SOF_EXT_MAN_ELEM_CC_VERSION:
- ret = ext_man_get_cc_info(sdev, elem_hdr);
- break;
- default:
- dev_warn(sdev->dev, "warning: unknown sof_ext_man header type %d size 0x%X\n",
- elem_hdr->type, elem_hdr->size);
- break;
- }
-
- if (ret < 0) {
- dev_err(sdev->dev, "error: failed to parse sof_ext_man header type %d size 0x%X\n",
- elem_hdr->type, elem_hdr->size);
- return ret;
- }
-
- remaining -= elem_hdr->size;
- iptr += elem_hdr->size;
- }
-
- if (remaining) {
- dev_err(sdev->dev, "error: sof_ext_man header is inconsistent\n");
- return -EINVAL;
- }
-
- return ext_man_size;
-}
-
-/*
- * IPC Firmware ready.
- */
-static void sof_get_windows(struct snd_sof_dev *sdev)
-{
- struct sof_ipc_window_elem *elem;
- u32 outbox_offset = 0;
- u32 stream_offset = 0;
- u32 inbox_offset = 0;
- u32 outbox_size = 0;
- u32 stream_size = 0;
- u32 inbox_size = 0;
- int window_offset;
- int bar;
- int i;
-
- if (!sdev->info_window) {
- dev_err(sdev->dev, "error: have no window info\n");
- return;
- }
-
- bar = snd_sof_dsp_get_bar_index(sdev, SOF_FW_BLK_TYPE_SRAM);
- if (bar < 0) {
- dev_err(sdev->dev, "error: have no bar mapping\n");
- return;
- }
-
- for (i = 0; i < sdev->info_window->num_windows; i++) {
- elem = &sdev->info_window->window[i];
-
- window_offset = snd_sof_dsp_get_window_offset(sdev, elem->id);
- if (window_offset < 0) {
- dev_warn(sdev->dev, "warn: no offset for window %d\n",
- elem->id);
- continue;
- }
-
- switch (elem->type) {
- case SOF_IPC_REGION_UPBOX:
- inbox_offset = window_offset + elem->offset;
- inbox_size = elem->size;
- snd_sof_debugfs_io_item(sdev,
- sdev->bar[bar] +
- inbox_offset,
- elem->size, "inbox",
- SOF_DEBUGFS_ACCESS_D0_ONLY);
- break;
- case SOF_IPC_REGION_DOWNBOX:
- outbox_offset = window_offset + elem->offset;
- outbox_size = elem->size;
- snd_sof_debugfs_io_item(sdev,
- sdev->bar[bar] +
- outbox_offset,
- elem->size, "outbox",
- SOF_DEBUGFS_ACCESS_D0_ONLY);
- break;
- case SOF_IPC_REGION_TRACE:
- snd_sof_debugfs_io_item(sdev,
- sdev->bar[bar] +
- window_offset +
- elem->offset,
- elem->size, "etrace",
- SOF_DEBUGFS_ACCESS_D0_ONLY);
- break;
- case SOF_IPC_REGION_DEBUG:
- snd_sof_debugfs_io_item(sdev,
- sdev->bar[bar] +
- window_offset +
- elem->offset,
- elem->size, "debug",
- SOF_DEBUGFS_ACCESS_D0_ONLY);
- break;
- case SOF_IPC_REGION_STREAM:
- stream_offset = window_offset + elem->offset;
- stream_size = elem->size;
- snd_sof_debugfs_io_item(sdev,
- sdev->bar[bar] +
- stream_offset,
- elem->size, "stream",
- SOF_DEBUGFS_ACCESS_D0_ONLY);
- break;
- case SOF_IPC_REGION_REGS:
- snd_sof_debugfs_io_item(sdev,
- sdev->bar[bar] +
- window_offset +
- elem->offset,
- elem->size, "regs",
- SOF_DEBUGFS_ACCESS_D0_ONLY);
- break;
- case SOF_IPC_REGION_EXCEPTION:
- sdev->dsp_oops_offset = window_offset + elem->offset;
- snd_sof_debugfs_io_item(sdev,
- sdev->bar[bar] +
- window_offset +
- elem->offset,
- elem->size, "exception",
- SOF_DEBUGFS_ACCESS_D0_ONLY);
- break;
- default:
- dev_err(sdev->dev, "error: get illegal window info\n");
- return;
- }
- }
-
- if (outbox_size == 0 || inbox_size == 0) {
- dev_err(sdev->dev, "error: get illegal mailbox window\n");
- return;
- }
-
- snd_sof_dsp_mailbox_init(sdev, inbox_offset, inbox_size,
- outbox_offset, outbox_size);
- sdev->stream_box.offset = stream_offset;
- sdev->stream_box.size = stream_size;
-
- dev_dbg(sdev->dev, " mailbox upstream 0x%x - size 0x%x\n",
- inbox_offset, inbox_size);
- dev_dbg(sdev->dev, " mailbox downstream 0x%x - size 0x%x\n",
- outbox_offset, outbox_size);
- dev_dbg(sdev->dev, " stream region 0x%x - size 0x%x\n",
- stream_offset, stream_size);
-}
-
-/* check for ABI compatibility and create memory windows on first boot */
-int sof_fw_ready(struct snd_sof_dev *sdev, u32 msg_id)
-{
- struct sof_ipc_fw_ready *fw_ready = &sdev->fw_ready;
- int offset;
- int bar;
- int ret;
-
- /* mailbox must be on 4k boundary */
- offset = snd_sof_dsp_get_mailbox_offset(sdev);
- if (offset < 0) {
- dev_err(sdev->dev, "error: have no mailbox offset\n");
- return offset;
- }
-
- bar = snd_sof_dsp_get_bar_index(sdev, SOF_FW_BLK_TYPE_SRAM);
- if (bar < 0) {
- dev_err(sdev->dev, "error: have no bar mapping\n");
- return -EINVAL;
- }
-
- dev_dbg(sdev->dev, "ipc: DSP is ready 0x%8.8x offset 0x%x\n",
- msg_id, offset);
-
- /* no need to re-check version/ABI for subsequent boots */
- if (!sdev->first_boot)
- return 0;
-
- /* copy data from the DSP FW ready offset */
- sof_block_read(sdev, bar, offset, fw_ready, sizeof(*fw_ready));
-
- /* make sure ABI version is compatible */
- ret = snd_sof_ipc_valid(sdev);
- if (ret < 0)
- return ret;
-
- /* now check for extended data */
- snd_sof_fw_parse_ext_data(sdev, bar, offset +
- sizeof(struct sof_ipc_fw_ready));
-
- sof_get_windows(sdev);
-
- return 0;
-}
-EXPORT_SYMBOL(sof_fw_ready);
-
-/* generic module parser for mmaped DSPs */
-int snd_sof_parse_module_memcpy(struct snd_sof_dev *sdev,
- struct snd_sof_mod_hdr *module)
-{
- struct snd_sof_blk_hdr *block;
- int count, bar;
- u32 offset;
- size_t remaining;
-
- dev_dbg(sdev->dev, "new module size 0x%x blocks 0x%x type 0x%x\n",
- module->size, module->num_blocks, module->type);
-
- block = (struct snd_sof_blk_hdr *)((u8 *)module + sizeof(*module));
-
- /* module->size doesn't include header size */
- remaining = module->size;
- for (count = 0; count < module->num_blocks; count++) {
- /* check for wrap */
- if (remaining < sizeof(*block)) {
- dev_err(sdev->dev, "error: not enough data remaining\n");
- return -EINVAL;
- }
-
- /* minus header size of block */
- remaining -= sizeof(*block);
-
- if (block->size == 0) {
- dev_warn(sdev->dev,
- "warning: block %d size zero\n", count);
- dev_warn(sdev->dev, " type 0x%x offset 0x%x\n",
- block->type, block->offset);
- continue;
- }
-
- switch (block->type) {
- case SOF_FW_BLK_TYPE_RSRVD0:
- case SOF_FW_BLK_TYPE_ROM...SOF_FW_BLK_TYPE_RSRVD14:
- continue; /* not handled atm */
- case SOF_FW_BLK_TYPE_IRAM:
- case SOF_FW_BLK_TYPE_DRAM:
- case SOF_FW_BLK_TYPE_SRAM:
- offset = block->offset;
- bar = snd_sof_dsp_get_bar_index(sdev, block->type);
- if (bar < 0) {
- dev_err(sdev->dev,
- "error: no BAR mapping for block type 0x%x\n",
- block->type);
- return bar;
- }
- break;
- default:
- dev_err(sdev->dev, "error: bad type 0x%x for block 0x%x\n",
- block->type, count);
- return -EINVAL;
- }
-
- dev_dbg(sdev->dev,
- "block %d type 0x%x size 0x%x ==> offset 0x%x\n",
- count, block->type, block->size, offset);
-
- /* checking block->size to avoid unaligned access */
- if (block->size % sizeof(u32)) {
- dev_err(sdev->dev, "error: invalid block size 0x%x\n",
- block->size);
- return -EINVAL;
- }
- snd_sof_dsp_block_write(sdev, bar, offset,
- block + 1, block->size);
-
- if (remaining < block->size) {
- dev_err(sdev->dev, "error: not enough data remaining\n");
- return -EINVAL;
- }
-
- /* minus body size of block */
- remaining -= block->size;
- /* next block */
- block = (struct snd_sof_blk_hdr *)((u8 *)block + sizeof(*block)
- + block->size);
- }
-
- return 0;
-}
-EXPORT_SYMBOL(snd_sof_parse_module_memcpy);
-
-static int check_header(struct snd_sof_dev *sdev, const struct firmware *fw,
- size_t fw_offset)
-{
- struct snd_sof_fw_header *header;
- size_t fw_size = fw->size - fw_offset;
-
- if (fw->size <= fw_offset) {
- dev_err(sdev->dev, "error: firmware size must be greater than firmware offset\n");
- return -EINVAL;
- }
-
- /* Read the header information from the data pointer */
- header = (struct snd_sof_fw_header *)(fw->data + fw_offset);
-
- /* verify FW sig */
- if (strncmp(header->sig, SND_SOF_FW_SIG, SND_SOF_FW_SIG_SIZE) != 0) {
- dev_err(sdev->dev, "error: invalid firmware signature\n");
- return -EINVAL;
- }
-
- /* check size is valid */
- if (fw_size != header->file_size + sizeof(*header)) {
- dev_err(sdev->dev, "error: invalid filesize mismatch got 0x%zx expected 0x%zx\n",
- fw_size, header->file_size + sizeof(*header));
- return -EINVAL;
- }
-
- dev_dbg(sdev->dev, "header size=0x%x modules=0x%x abi=0x%x size=%zu\n",
- header->file_size, header->num_modules,
- header->abi, sizeof(*header));
-
- return 0;
-}
-
-static int load_modules(struct snd_sof_dev *sdev, const struct firmware *fw,
- size_t fw_offset)
-{
- struct snd_sof_fw_header *header;
- struct snd_sof_mod_hdr *module;
- int (*load_module)(struct snd_sof_dev *sof_dev,
- struct snd_sof_mod_hdr *hdr);
- int ret, count;
- size_t remaining;
-
- header = (struct snd_sof_fw_header *)(fw->data + fw_offset);
- load_module = sof_ops(sdev)->load_module;
- if (!load_module)
- return -EINVAL;
-
- /* parse each module */
- module = (struct snd_sof_mod_hdr *)(fw->data + fw_offset +
- sizeof(*header));
- remaining = fw->size - sizeof(*header) - fw_offset;
- /* check for wrap */
- if (remaining > fw->size) {
- dev_err(sdev->dev, "error: fw size smaller than header size\n");
- return -EINVAL;
- }
-
- for (count = 0; count < header->num_modules; count++) {
- /* check for wrap */
- if (remaining < sizeof(*module)) {
- dev_err(sdev->dev, "error: not enough data remaining\n");
- return -EINVAL;
- }
-
- /* minus header size of module */
- remaining -= sizeof(*module);
-
- /* module */
- ret = load_module(sdev, module);
- if (ret < 0) {
- dev_err(sdev->dev, "error: invalid module %d\n", count);
- return ret;
- }
-
- if (remaining < module->size) {
- dev_err(sdev->dev, "error: not enough data remaining\n");
- return -EINVAL;
- }
-
- /* minus body size of module */
- remaining -= module->size;
- module = (struct snd_sof_mod_hdr *)((u8 *)module
- + sizeof(*module) + module->size);
- }
-
- return 0;
-}
-
int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev)
{
struct snd_sof_pdata *plat_data = sdev->pdata;
@@ -630,7 +22,7 @@ int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev)
int ret;
/* Don't request firmware again if firmware is already requested */
- if (plat_data->fw)
+ if (sdev->basefw.fw)
return 0;
fw_filename = kasprintf(GFP_KERNEL, "%s/%s",
@@ -639,11 +31,13 @@ int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev)
if (!fw_filename)
return -ENOMEM;
- ret = request_firmware(&plat_data->fw, fw_filename, sdev->dev);
+ ret = request_firmware(&sdev->basefw.fw, fw_filename, sdev->dev);
if (ret < 0) {
- dev_err(sdev->dev, "error: request firmware %s failed err: %d\n",
- fw_filename, ret);
+ dev_err(sdev->dev,
+ "error: sof firmware file is missing, you might need to\n");
+ dev_err(sdev->dev,
+ " download it from https://github.com/thesofproject/sof-bin/\n");
goto err;
} else {
dev_dbg(sdev->dev, "request_firmware %s successful\n",
@@ -651,10 +45,10 @@ int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev)
}
/* check for extended manifest */
- ext_man_size = snd_sof_fw_ext_man_parse(sdev, plat_data->fw);
+ ext_man_size = sdev->ipc->ops->fw_loader->parse_ext_manifest(sdev);
if (ext_man_size > 0) {
/* when no error occurred, drop extended manifest */
- plat_data->fw_offset = ext_man_size;
+ sdev->basefw.payload_offset = ext_man_size;
} else if (!ext_man_size) {
/* No extended manifest, so nothing to skip during FW load */
dev_dbg(sdev->dev, "firmware doesn't contain extended manifest\n");
@@ -673,7 +67,6 @@ EXPORT_SYMBOL(snd_sof_load_firmware_raw);
int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev)
{
- struct snd_sof_pdata *plat_data = sdev->pdata;
int ret;
ret = snd_sof_load_firmware_raw(sdev);
@@ -681,7 +74,7 @@ int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev)
return ret;
/* make sure the FW header and file is valid */
- ret = check_header(sdev, plat_data->fw, plat_data->fw_offset);
+ ret = sdev->ipc->ops->fw_loader->validate(sdev);
if (ret < 0) {
dev_err(sdev->dev, "error: invalid FW header\n");
goto error;
@@ -695,39 +88,34 @@ int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev)
}
/* parse and load firmware modules to DSP */
- ret = load_modules(sdev, plat_data->fw, plat_data->fw_offset);
- if (ret < 0) {
- dev_err(sdev->dev, "error: invalid FW modules\n");
- goto error;
+ if (sdev->ipc->ops->fw_loader->load_fw_to_dsp) {
+ ret = sdev->ipc->ops->fw_loader->load_fw_to_dsp(sdev);
+ if (ret < 0) {
+ dev_err(sdev->dev, "Firmware loading failed\n");
+ goto error;
+ }
}
return 0;
error:
- release_firmware(plat_data->fw);
- plat_data->fw = NULL;
+ release_firmware(sdev->basefw.fw);
+ sdev->basefw.fw = NULL;
return ret;
}
EXPORT_SYMBOL(snd_sof_load_firmware_memcpy);
-int snd_sof_load_firmware(struct snd_sof_dev *sdev)
-{
- dev_dbg(sdev->dev, "loading firmware\n");
-
- if (sof_ops(sdev)->load_firmware)
- return sof_ops(sdev)->load_firmware(sdev);
- return 0;
-}
-EXPORT_SYMBOL(snd_sof_load_firmware);
-
int snd_sof_run_firmware(struct snd_sof_dev *sdev)
{
int ret;
- int init_core_mask;
init_waitqueue_head(&sdev->boot_wait);
+ /* (re-)enable dsp dump */
+ sdev->dbg_dump_printed = false;
+ sdev->ipc_dump_printed = false;
+
/* create read-only fw_version debugfs to store boot version info */
if (sdev->first_boot) {
ret = snd_sof_debugfs_buf_item(sdev, &sdev->fw_version,
@@ -735,7 +123,7 @@ int snd_sof_run_firmware(struct snd_sof_dev *sdev)
"fw_version", 0444);
/* errors are only due to memory allocation, not debugfs */
if (ret < 0) {
- dev_err(sdev->dev, "error: snd_sof_debugfs_buf_item failed\n");
+ dev_err(sdev->dev, "snd_sof_debugfs_buf_item failed\n");
return ret;
}
}
@@ -743,7 +131,7 @@ int snd_sof_run_firmware(struct snd_sof_dev *sdev)
/* perform pre fw run operations */
ret = snd_sof_dsp_pre_fw_run(sdev);
if (ret < 0) {
- dev_err(sdev->dev, "error: failed pre fw run op\n");
+ dev_err(sdev->dev, "failed pre fw run op\n");
return ret;
}
@@ -752,12 +140,11 @@ int snd_sof_run_firmware(struct snd_sof_dev *sdev)
/* boot the firmware on the DSP */
ret = snd_sof_dsp_run(sdev);
if (ret < 0) {
- dev_err(sdev->dev, "error: failed to reset DSP\n");
+ snd_sof_dsp_dbg_dump(sdev, "Failed to start DSP",
+ SOF_DBG_DUMP_MBOX | SOF_DBG_DUMP_PCI);
return ret;
}
- init_core_mask = ret;
-
/*
* now wait for the DSP to boot. There are 3 possible outcomes:
* 1. Boot wait times out indicating FW boot failure.
@@ -768,18 +155,18 @@ int snd_sof_run_firmware(struct snd_sof_dev *sdev)
sdev->fw_state > SOF_FW_BOOT_IN_PROGRESS,
msecs_to_jiffies(sdev->boot_timeout));
if (ret == 0) {
- dev_err(sdev->dev, "error: firmware boot failure\n");
- snd_sof_dsp_dbg_dump(sdev, SOF_DBG_REGS | SOF_DBG_MBOX |
- SOF_DBG_TEXT | SOF_DBG_PCI);
- sdev->fw_state = SOF_FW_BOOT_FAILED;
+ snd_sof_dsp_dbg_dump(sdev, "Firmware boot failure due to timeout",
+ SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX |
+ SOF_DBG_DUMP_TEXT | SOF_DBG_DUMP_PCI);
return -EIO;
}
- if (sdev->fw_state == SOF_FW_BOOT_COMPLETE)
- dev_dbg(sdev->dev, "firmware boot complete\n");
- else
+ if (sdev->fw_state == SOF_FW_BOOT_READY_FAILED)
return -EIO; /* FW boots but fw_ready op failed */
+ dev_dbg(sdev->dev, "firmware boot complete\n");
+ sof_set_fw_state(sdev, SOF_FW_BOOT_COMPLETE);
+
/* perform post fw run operations */
ret = snd_sof_dsp_post_fw_run(sdev);
if (ret < 0) {
@@ -787,8 +174,8 @@ int snd_sof_run_firmware(struct snd_sof_dev *sdev)
return ret;
}
- /* fw boot is complete. Update the active cores mask */
- sdev->enabled_cores_mask = init_core_mask;
+ if (sdev->ipc->ops->post_fw_boot)
+ return sdev->ipc->ops->post_fw_boot(sdev);
return 0;
}
@@ -797,5 +184,7 @@ EXPORT_SYMBOL(snd_sof_run_firmware);
void snd_sof_fw_unload(struct snd_sof_dev *sdev)
{
/* TODO: support module unloading at runtime */
+ release_firmware(sdev->basefw.fw);
+ sdev->basefw.fw = NULL;
}
EXPORT_SYMBOL(snd_sof_fw_unload);
diff --git a/sound/soc/sof/mediatek/Kconfig b/sound/soc/sof/mediatek/Kconfig
new file mode 100644
index 000000000000..4a2eddf6009a
--- /dev/null
+++ b/sound/soc/sof/mediatek/Kconfig
@@ -0,0 +1,45 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+
+config SND_SOC_SOF_MTK_TOPLEVEL
+ bool "SOF support for MTK audio DSPs"
+ depends on ARM64 || COMPILE_TEST
+ depends on SND_SOC_SOF_OF
+ help
+ This adds support for Sound Open Firmware for Mediatek platforms.
+ It is top level for all mediatek platforms.
+ Say Y if you have such a device.
+ If unsure select "N".
+
+if SND_SOC_SOF_MTK_TOPLEVEL
+config SND_SOC_SOF_MTK_COMMON
+ tristate
+ select SND_SOC_SOF_OF_DEV
+ select SND_SOC_SOF
+ select SND_SOC_SOF_IPC3
+ select SND_SOC_SOF_XTENSA
+ select SND_SOC_SOF_COMPRESS
+ help
+ This option is not user-selectable but automagically handled by
+ 'select' statements at a higher level
+
+config SND_SOC_SOF_MT8186
+ tristate "SOF support for MT8186 audio DSP"
+ select SND_SOC_SOF_MTK_COMMON
+ depends on MTK_ADSP_IPC
+ help
+ This adds support for Sound Open Firmware for Mediatek platforms
+ using the mt8186 processors.
+ Say Y if you have such a device.
+ If unsure select "N".
+
+config SND_SOC_SOF_MT8195
+ tristate "SOF support for MT8195 audio DSP"
+ select SND_SOC_SOF_MTK_COMMON
+ depends on MTK_ADSP_IPC
+ help
+ This adds support for Sound Open Firmware for Mediatek platforms
+ using the mt8195 processors.
+ Say Y if you have such a device.
+ If unsure select "N".
+
+endif ## SND_SOC_SOF_MTK_TOPLEVEL
diff --git a/sound/soc/sof/mediatek/Makefile b/sound/soc/sof/mediatek/Makefile
new file mode 100644
index 000000000000..29c5afb2f3d6
--- /dev/null
+++ b/sound/soc/sof/mediatek/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+obj-$(CONFIG_SND_SOC_SOF_MTK_COMMON) += mtk-adsp-common.o
+obj-$(CONFIG_SND_SOC_SOF_MT8195) += mt8195/
+obj-$(CONFIG_SND_SOC_SOF_MT8186) += mt8186/
diff --git a/sound/soc/sof/mediatek/adsp_helper.h b/sound/soc/sof/mediatek/adsp_helper.h
new file mode 100644
index 000000000000..35527567962e
--- /dev/null
+++ b/sound/soc/sof/mediatek/adsp_helper.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Copyright (c) 2021 MediaTek Corporation. All rights reserved.
+ */
+
+#ifndef __MTK_ADSP_HELPER_H__
+#define __MTK_ADSP_HELPER_H__
+
+#include <linux/firmware/mediatek/mtk-adsp-ipc.h>
+
+/*
+ * Global important adsp data structure.
+ */
+struct mtk_adsp_chip_info {
+ phys_addr_t pa_sram;
+ phys_addr_t pa_dram; /* adsp dram physical base */
+ phys_addr_t pa_cfgreg;
+ u32 sramsize;
+ u32 dramsize;
+ u32 cfgregsize;
+ void __iomem *va_sram; /* corresponding to pa_sram */
+ void __iomem *va_dram; /* corresponding to pa_dram */
+ void __iomem *va_cfgreg;
+ phys_addr_t adsp_bootup_addr;
+ int dram_offset; /*dram offset between system and dsp view*/
+
+ phys_addr_t pa_secreg;
+ u32 secregsize;
+ void __iomem *va_secreg;
+
+ phys_addr_t pa_busreg;
+ u32 busregsize;
+ void __iomem *va_busreg;
+};
+
+struct adsp_priv {
+ struct device *dev;
+ struct snd_sof_dev *sdev;
+ struct mtk_adsp_ipc *dsp_ipc;
+ struct platform_device *ipc_dev;
+ struct mtk_adsp_chip_info *adsp;
+ struct clk **clk;
+ u32 (*ap2adsp_addr)(u32 addr, void *data);
+ u32 (*adsp2ap_addr)(u32 addr, void *data);
+
+ void *private_data;
+};
+
+#endif
diff --git a/sound/soc/sof/mediatek/mt8186/Makefile b/sound/soc/sof/mediatek/mt8186/Makefile
new file mode 100644
index 000000000000..c1f5fc4e2495
--- /dev/null
+++ b/sound/soc/sof/mediatek/mt8186/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+snd-sof-mt8186-objs := mt8186.o mt8186-clk.o mt8186-loader.o
+obj-$(CONFIG_SND_SOC_SOF_MT8186) += snd-sof-mt8186.o
+
diff --git a/sound/soc/sof/mediatek/mt8186/mt8186-clk.c b/sound/soc/sof/mediatek/mt8186/mt8186-clk.c
new file mode 100644
index 000000000000..cb2ab5884b8c
--- /dev/null
+++ b/sound/soc/sof/mediatek/mt8186/mt8186-clk.c
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// Copyright(c) 2022 Mediatek Corporation. All rights reserved.
+//
+// Author: Allen-KH Cheng <allen-kh.cheng@mediatek.com>
+// Tinghan Shen <tinghan.shen@mediatek.com>
+//
+// Hardware interface for mt8186 DSP clock
+
+#include <linux/clk.h>
+#include <linux/io.h>
+
+#include "../../sof-audio.h"
+#include "../../ops.h"
+#include "../adsp_helper.h"
+#include "mt8186.h"
+#include "mt8186-clk.h"
+
+static const char *adsp_clks[ADSP_CLK_MAX] = {
+ [CLK_TOP_AUDIODSP] = "audiodsp",
+ [CLK_TOP_ADSP_BUS] = "adsp_bus",
+};
+
+int mt8186_adsp_init_clock(struct snd_sof_dev *sdev)
+{
+ struct adsp_priv *priv = sdev->pdata->hw_pdata;
+ struct device *dev = sdev->dev;
+ int i;
+
+ priv->clk = devm_kcalloc(dev, ADSP_CLK_MAX, sizeof(*priv->clk), GFP_KERNEL);
+ if (!priv->clk)
+ return -ENOMEM;
+
+ for (i = 0; i < ADSP_CLK_MAX; i++) {
+ priv->clk[i] = devm_clk_get(dev, adsp_clks[i]);
+
+ if (IS_ERR(priv->clk[i]))
+ return PTR_ERR(priv->clk[i]);
+ }
+
+ return 0;
+}
+
+static int adsp_enable_all_clock(struct snd_sof_dev *sdev)
+{
+ struct adsp_priv *priv = sdev->pdata->hw_pdata;
+ struct device *dev = sdev->dev;
+ int ret;
+
+ ret = clk_prepare_enable(priv->clk[CLK_TOP_AUDIODSP]);
+ if (ret) {
+ dev_err(dev, "%s clk_prepare_enable(audiodsp) fail %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(priv->clk[CLK_TOP_ADSP_BUS]);
+ if (ret) {
+ dev_err(dev, "%s clk_prepare_enable(adsp_bus) fail %d\n",
+ __func__, ret);
+ clk_disable_unprepare(priv->clk[CLK_TOP_AUDIODSP]);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void adsp_disable_all_clock(struct snd_sof_dev *sdev)
+{
+ struct adsp_priv *priv = sdev->pdata->hw_pdata;
+
+ clk_disable_unprepare(priv->clk[CLK_TOP_ADSP_BUS]);
+ clk_disable_unprepare(priv->clk[CLK_TOP_AUDIODSP]);
+}
+
+int mt8186_adsp_clock_on(struct snd_sof_dev *sdev)
+{
+ struct device *dev = sdev->dev;
+ int ret;
+
+ ret = adsp_enable_all_clock(sdev);
+ if (ret) {
+ dev_err(dev, "failed to adsp_enable_clock: %d\n", ret);
+ return ret;
+ }
+ snd_sof_dsp_write(sdev, DSP_REG_BAR, ADSP_CK_EN,
+ UART_EN | DMA_EN | TIMER_EN | COREDBG_EN | CORE_CLK_EN);
+ snd_sof_dsp_write(sdev, DSP_REG_BAR, ADSP_UART_CTRL,
+ UART_BCLK_CG | UART_RSTN);
+
+ return 0;
+}
+
+void mt8186_adsp_clock_off(struct snd_sof_dev *sdev)
+{
+ snd_sof_dsp_write(sdev, DSP_REG_BAR, ADSP_CK_EN, 0);
+ snd_sof_dsp_write(sdev, DSP_REG_BAR, ADSP_UART_CTRL, 0);
+ adsp_disable_all_clock(sdev);
+}
+
diff --git a/sound/soc/sof/mediatek/mt8186/mt8186-clk.h b/sound/soc/sof/mediatek/mt8186/mt8186-clk.h
new file mode 100644
index 000000000000..89c23caf0fee
--- /dev/null
+++ b/sound/soc/sof/mediatek/mt8186/mt8186-clk.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+
+/*
+ * Copyright (c) 2022 MediaTek Corporation. All rights reserved.
+ *
+ * Header file for the mt8186 DSP clock definition
+ */
+
+#ifndef __MT8186_CLK_H
+#define __MT8186_CLK_H
+
+struct snd_sof_dev;
+
+/* DSP clock */
+enum adsp_clk_id {
+ CLK_TOP_AUDIODSP,
+ CLK_TOP_ADSP_BUS,
+ ADSP_CLK_MAX
+};
+
+int mt8186_adsp_init_clock(struct snd_sof_dev *sdev);
+int mt8186_adsp_clock_on(struct snd_sof_dev *sdev);
+void mt8186_adsp_clock_off(struct snd_sof_dev *sdev);
+#endif
diff --git a/sound/soc/sof/mediatek/mt8186/mt8186-loader.c b/sound/soc/sof/mediatek/mt8186/mt8186-loader.c
new file mode 100644
index 000000000000..946e6c43204f
--- /dev/null
+++ b/sound/soc/sof/mediatek/mt8186/mt8186-loader.c
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// Copyright (c) 2022 Mediatek Corporation. All rights reserved.
+//
+// Author: Allen-KH Cheng <allen-kh.cheng@mediatek.com>
+// Tinghan Shen <tinghan.shen@mediatek.com>
+//
+// Hardware interface for mt8186 DSP code loader
+
+#include <sound/sof.h>
+#include "mt8186.h"
+#include "../../ops.h"
+
+void mt8186_sof_hifixdsp_boot_sequence(struct snd_sof_dev *sdev, u32 boot_addr)
+{
+ /* set RUNSTALL to stop core */
+ snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, ADSP_HIFI_IO_CONFIG,
+ RUNSTALL, RUNSTALL);
+
+ /* enable mbox 0 & 1 IRQ */
+ snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, ADSP_MBOX_IRQ_EN,
+ DSP_MBOX0_IRQ_EN | DSP_MBOX1_IRQ_EN,
+ DSP_MBOX0_IRQ_EN | DSP_MBOX1_IRQ_EN);
+
+ /* set core boot address */
+ snd_sof_dsp_write(sdev, DSP_SECREG_BAR, ADSP_ALTVEC_C0, boot_addr);
+ snd_sof_dsp_write(sdev, DSP_SECREG_BAR, ADSP_ALTVECSEL, ADSP_ALTVECSEL_C0);
+
+ /* assert core reset */
+ snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, ADSP_CFGREG_SW_RSTN,
+ SW_RSTN_C0 | SW_DBG_RSTN_C0,
+ SW_RSTN_C0 | SW_DBG_RSTN_C0);
+
+ /* hardware requirement */
+ udelay(1);
+
+ /* release core reset */
+ snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, ADSP_CFGREG_SW_RSTN,
+ SW_RSTN_C0 | SW_DBG_RSTN_C0,
+ 0);
+
+ /* clear RUNSTALL (bit31) to start core */
+ snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, ADSP_HIFI_IO_CONFIG,
+ RUNSTALL, 0);
+}
+
+void mt8186_sof_hifixdsp_shutdown(struct snd_sof_dev *sdev)
+{
+ /* set RUNSTALL to stop core */
+ snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, ADSP_HIFI_IO_CONFIG,
+ RUNSTALL, RUNSTALL);
+
+ /* assert core reset */
+ snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, ADSP_CFGREG_SW_RSTN,
+ SW_RSTN_C0 | SW_DBG_RSTN_C0,
+ SW_RSTN_C0 | SW_DBG_RSTN_C0);
+}
+
diff --git a/sound/soc/sof/mediatek/mt8186/mt8186.c b/sound/soc/sof/mediatek/mt8186/mt8186.c
new file mode 100644
index 000000000000..0d2d7d697de0
--- /dev/null
+++ b/sound/soc/sof/mediatek/mt8186/mt8186.c
@@ -0,0 +1,671 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// Copyright(c) 2022 Mediatek Inc. All rights reserved.
+//
+// Author: Allen-KH Cheng <allen-kh.cheng@mediatek.com>
+// Tinghan Shen <tinghan.shen@mediatek.com>
+
+/*
+ * Hardware interface for audio DSP on mt8186
+ */
+
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/module.h>
+
+#include <sound/sof.h>
+#include <sound/sof/xtensa.h>
+#include "../../ops.h"
+#include "../../sof-of-dev.h"
+#include "../../sof-audio.h"
+#include "../adsp_helper.h"
+#include "../mtk-adsp-common.h"
+#include "mt8186.h"
+#include "mt8186-clk.h"
+
+static int mt8186_get_mailbox_offset(struct snd_sof_dev *sdev)
+{
+ return MBOX_OFFSET;
+}
+
+static int mt8186_get_window_offset(struct snd_sof_dev *sdev, u32 id)
+{
+ return MBOX_OFFSET;
+}
+
+static int mt8186_send_msg(struct snd_sof_dev *sdev,
+ struct snd_sof_ipc_msg *msg)
+{
+ struct adsp_priv *priv = sdev->pdata->hw_pdata;
+
+ sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data,
+ msg->msg_size);
+
+ return mtk_adsp_ipc_send(priv->dsp_ipc, MTK_ADSP_IPC_REQ, MTK_ADSP_IPC_OP_REQ);
+}
+
+static void mt8186_dsp_handle_reply(struct mtk_adsp_ipc *ipc)
+{
+ struct adsp_priv *priv = mtk_adsp_ipc_get_data(ipc);
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->sdev->ipc_lock, flags);
+ snd_sof_ipc_process_reply(priv->sdev, 0);
+ spin_unlock_irqrestore(&priv->sdev->ipc_lock, flags);
+}
+
+static void mt8186_dsp_handle_request(struct mtk_adsp_ipc *ipc)
+{
+ struct adsp_priv *priv = mtk_adsp_ipc_get_data(ipc);
+ u32 p; /* panic code */
+ int ret;
+
+ /* Read the message from the debug box. */
+ sof_mailbox_read(priv->sdev, priv->sdev->debug_box.offset + 4,
+ &p, sizeof(p));
+
+ /* Check to see if the message is a panic code 0x0dead*** */
+ if ((p & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) {
+ snd_sof_dsp_panic(priv->sdev, p, true);
+ } else {
+ snd_sof_ipc_msgs_rx(priv->sdev);
+
+ /* tell DSP cmd is done */
+ ret = mtk_adsp_ipc_send(priv->dsp_ipc, MTK_ADSP_IPC_RSP, MTK_ADSP_IPC_OP_RSP);
+ if (ret)
+ dev_err(priv->dev, "request send ipc failed");
+ }
+}
+
+static struct mtk_adsp_ipc_ops dsp_ops = {
+ .handle_reply = mt8186_dsp_handle_reply,
+ .handle_request = mt8186_dsp_handle_request,
+};
+
+static int platform_parse_resource(struct platform_device *pdev, void *data)
+{
+ struct resource *mmio;
+ struct resource res;
+ struct device_node *mem_region;
+ struct device *dev = &pdev->dev;
+ struct mtk_adsp_chip_info *adsp = data;
+ int ret;
+
+ ret = of_reserved_mem_device_init(dev);
+ if (ret) {
+ dev_err(dev, "of_reserved_mem_device_init failed\n");
+ return ret;
+ }
+
+ mem_region = of_parse_phandle(dev->of_node, "memory-region", 1);
+ if (!mem_region) {
+ dev_err(dev, "no memory-region sysmem phandle\n");
+ return -ENODEV;
+ }
+
+ ret = of_address_to_resource(mem_region, 0, &res);
+ of_node_put(mem_region);
+ if (ret) {
+ dev_err(dev, "of_address_to_resource sysmem failed\n");
+ return ret;
+ }
+
+ adsp->pa_dram = (phys_addr_t)res.start;
+ if (adsp->pa_dram & DRAM_REMAP_MASK) {
+ dev_err(dev, "adsp memory(%#x) is not 4K-aligned\n",
+ (u32)adsp->pa_dram);
+ return -EINVAL;
+ }
+
+ adsp->dramsize = resource_size(&res);
+ if (adsp->dramsize < TOTAL_SIZE_SHARED_DRAM_FROM_TAIL) {
+ dev_err(dev, "adsp memory(%#x) is not enough for share\n",
+ adsp->dramsize);
+ return -EINVAL;
+ }
+
+ dev_dbg(dev, "dram pbase=%pa size=%#x\n", &adsp->pa_dram, adsp->dramsize);
+
+ mmio = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cfg");
+ if (!mmio) {
+ dev_err(dev, "no ADSP-CFG register resource\n");
+ return -ENXIO;
+ }
+
+ adsp->va_cfgreg = devm_ioremap_resource(dev, mmio);
+ if (IS_ERR(adsp->va_cfgreg))
+ return PTR_ERR(adsp->va_cfgreg);
+
+ adsp->pa_cfgreg = (phys_addr_t)mmio->start;
+ adsp->cfgregsize = resource_size(mmio);
+
+ dev_dbg(dev, "cfgreg pbase=%pa size=%#x\n", &adsp->pa_cfgreg, adsp->cfgregsize);
+
+ mmio = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sram");
+ if (!mmio) {
+ dev_err(dev, "no SRAM resource\n");
+ return -ENXIO;
+ }
+
+ adsp->pa_sram = (phys_addr_t)mmio->start;
+ adsp->sramsize = resource_size(mmio);
+
+ dev_dbg(dev, "sram pbase=%pa size=%#x\n", &adsp->pa_sram, adsp->sramsize);
+
+ mmio = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sec");
+ if (!mmio) {
+ dev_err(dev, "no SEC register resource\n");
+ return -ENXIO;
+ }
+
+ adsp->va_secreg = devm_ioremap_resource(dev, mmio);
+ if (IS_ERR(adsp->va_secreg))
+ return PTR_ERR(adsp->va_secreg);
+
+ adsp->pa_secreg = (phys_addr_t)mmio->start;
+ adsp->secregsize = resource_size(mmio);
+
+ dev_dbg(dev, "secreg pbase=%pa size=%#x\n", &adsp->pa_secreg, adsp->secregsize);
+
+ mmio = platform_get_resource_byname(pdev, IORESOURCE_MEM, "bus");
+ if (!mmio) {
+ dev_err(dev, "no BUS register resource\n");
+ return -ENXIO;
+ }
+
+ adsp->va_busreg = devm_ioremap_resource(dev, mmio);
+ if (IS_ERR(adsp->va_busreg))
+ return PTR_ERR(adsp->va_busreg);
+
+ adsp->pa_busreg = (phys_addr_t)mmio->start;
+ adsp->busregsize = resource_size(mmio);
+
+ dev_dbg(dev, "busreg pbase=%pa size=%#x\n", &adsp->pa_busreg, adsp->busregsize);
+
+ return 0;
+}
+
+static void adsp_sram_power_on(struct snd_sof_dev *sdev)
+{
+ snd_sof_dsp_update_bits(sdev, DSP_BUSREG_BAR, ADSP_SRAM_POOL_CON,
+ DSP_SRAM_POOL_PD_MASK, 0);
+}
+
+static void adsp_sram_power_off(struct snd_sof_dev *sdev)
+{
+ snd_sof_dsp_update_bits(sdev, DSP_BUSREG_BAR, ADSP_SRAM_POOL_CON,
+ DSP_SRAM_POOL_PD_MASK, DSP_SRAM_POOL_PD_MASK);
+}
+
+/* Init the basic DSP DRAM address */
+static int adsp_memory_remap_init(struct snd_sof_dev *sdev, struct mtk_adsp_chip_info *adsp)
+{
+ u32 offset;
+
+ offset = adsp->pa_dram - DRAM_PHYS_BASE_FROM_DSP_VIEW;
+ adsp->dram_offset = offset;
+ offset >>= DRAM_REMAP_SHIFT;
+
+ dev_dbg(sdev->dev, "adsp->pa_dram %pa, offset %#x\n", &adsp->pa_dram, offset);
+
+ snd_sof_dsp_write(sdev, DSP_BUSREG_BAR, DSP_C0_EMI_MAP_ADDR, offset);
+ snd_sof_dsp_write(sdev, DSP_BUSREG_BAR, DSP_C0_DMAEMI_MAP_ADDR, offset);
+
+ if (offset != snd_sof_dsp_read(sdev, DSP_BUSREG_BAR, DSP_C0_EMI_MAP_ADDR) ||
+ offset != snd_sof_dsp_read(sdev, DSP_BUSREG_BAR, DSP_C0_DMAEMI_MAP_ADDR)) {
+ dev_err(sdev->dev, "emi remap fail\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int mt8186_run(struct snd_sof_dev *sdev)
+{
+ u32 adsp_bootup_addr;
+
+ adsp_bootup_addr = SRAM_PHYS_BASE_FROM_DSP_VIEW;
+ dev_dbg(sdev->dev, "HIFIxDSP boot from base : 0x%08X\n", adsp_bootup_addr);
+ mt8186_sof_hifixdsp_boot_sequence(sdev, adsp_bootup_addr);
+
+ return 0;
+}
+
+static int mt8186_dsp_probe(struct snd_sof_dev *sdev)
+{
+ struct platform_device *pdev = container_of(sdev->dev, struct platform_device, dev);
+ struct adsp_priv *priv;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ sdev->pdata->hw_pdata = priv;
+ priv->dev = sdev->dev;
+ priv->sdev = sdev;
+
+ priv->adsp = devm_kzalloc(&pdev->dev, sizeof(struct mtk_adsp_chip_info), GFP_KERNEL);
+ if (!priv->adsp)
+ return -ENOMEM;
+
+ ret = platform_parse_resource(pdev, priv->adsp);
+ if (ret)
+ return ret;
+
+ sdev->bar[SOF_FW_BLK_TYPE_IRAM] = devm_ioremap(sdev->dev,
+ priv->adsp->pa_sram,
+ priv->adsp->sramsize);
+ if (!sdev->bar[SOF_FW_BLK_TYPE_IRAM]) {
+ dev_err(sdev->dev, "failed to ioremap base %pa size %#x\n",
+ &priv->adsp->pa_sram, priv->adsp->sramsize);
+ return -ENOMEM;
+ }
+
+ priv->adsp->va_sram = sdev->bar[SOF_FW_BLK_TYPE_IRAM];
+
+ sdev->bar[SOF_FW_BLK_TYPE_SRAM] = devm_ioremap(sdev->dev,
+ priv->adsp->pa_dram,
+ priv->adsp->dramsize);
+
+ if (!sdev->bar[SOF_FW_BLK_TYPE_SRAM]) {
+ dev_err(sdev->dev, "failed to ioremap base %pa size %#x\n",
+ &priv->adsp->pa_dram, priv->adsp->dramsize);
+ return -ENOMEM;
+ }
+
+ priv->adsp->va_dram = sdev->bar[SOF_FW_BLK_TYPE_SRAM];
+
+ sdev->bar[DSP_REG_BAR] = priv->adsp->va_cfgreg;
+ sdev->bar[DSP_SECREG_BAR] = priv->adsp->va_secreg;
+ sdev->bar[DSP_BUSREG_BAR] = priv->adsp->va_busreg;
+
+ sdev->mmio_bar = SOF_FW_BLK_TYPE_SRAM;
+ sdev->mailbox_bar = SOF_FW_BLK_TYPE_SRAM;
+
+ /* set default mailbox offset for FW ready message */
+ sdev->dsp_box.offset = mt8186_get_mailbox_offset(sdev);
+
+ ret = adsp_memory_remap_init(sdev, priv->adsp);
+ if (ret) {
+ dev_err(sdev->dev, "adsp_memory_remap_init fail!\n");
+ return ret;
+ }
+
+ /* enable adsp clock before touching registers */
+ ret = mt8186_adsp_init_clock(sdev);
+ if (ret) {
+ dev_err(sdev->dev, "mt8186_adsp_init_clock failed\n");
+ return ret;
+ }
+
+ ret = mt8186_adsp_clock_on(sdev);
+ if (ret) {
+ dev_err(sdev->dev, "mt8186_adsp_clock_on fail!\n");
+ return ret;
+ }
+
+ adsp_sram_power_on(sdev);
+
+ priv->ipc_dev = platform_device_register_data(&pdev->dev, "mtk-adsp-ipc",
+ PLATFORM_DEVID_NONE,
+ pdev, sizeof(*pdev));
+ if (IS_ERR(priv->ipc_dev)) {
+ ret = PTR_ERR(priv->ipc_dev);
+ dev_err(sdev->dev, "failed to create mtk-adsp-ipc device\n");
+ goto err_adsp_off;
+ }
+
+ priv->dsp_ipc = dev_get_drvdata(&priv->ipc_dev->dev);
+ if (!priv->dsp_ipc) {
+ ret = -EPROBE_DEFER;
+ dev_err(sdev->dev, "failed to get drvdata\n");
+ goto exit_pdev_unregister;
+ }
+
+ mtk_adsp_ipc_set_data(priv->dsp_ipc, priv);
+ priv->dsp_ipc->ops = &dsp_ops;
+
+ return 0;
+
+exit_pdev_unregister:
+ platform_device_unregister(priv->ipc_dev);
+err_adsp_off:
+ adsp_sram_power_off(sdev);
+ mt8186_adsp_clock_off(sdev);
+
+ return ret;
+}
+
+static void mt8186_dsp_remove(struct snd_sof_dev *sdev)
+{
+ struct adsp_priv *priv = sdev->pdata->hw_pdata;
+
+ platform_device_unregister(priv->ipc_dev);
+ mt8186_sof_hifixdsp_shutdown(sdev);
+ adsp_sram_power_off(sdev);
+ mt8186_adsp_clock_off(sdev);
+}
+
+static int mt8186_dsp_shutdown(struct snd_sof_dev *sdev)
+{
+ return snd_sof_suspend(sdev->dev);
+}
+
+static int mt8186_dsp_suspend(struct snd_sof_dev *sdev, u32 target_state)
+{
+ mt8186_sof_hifixdsp_shutdown(sdev);
+ adsp_sram_power_off(sdev);
+ mt8186_adsp_clock_off(sdev);
+
+ return 0;
+}
+
+static int mt8186_dsp_resume(struct snd_sof_dev *sdev)
+{
+ int ret;
+
+ ret = mt8186_adsp_clock_on(sdev);
+ if (ret) {
+ dev_err(sdev->dev, "mt8186_adsp_clock_on fail!\n");
+ return ret;
+ }
+
+ adsp_sram_power_on(sdev);
+
+ return ret;
+}
+
+/* on mt8186 there is 1 to 1 match between type and BAR idx */
+static int mt8186_get_bar_index(struct snd_sof_dev *sdev, u32 type)
+{
+ return type;
+}
+
+static int mt8186_pcm_hw_params(struct snd_sof_dev *sdev,
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_sof_platform_stream_params *platform_params)
+{
+ platform_params->cont_update_posn = 1;
+
+ return 0;
+}
+
+static snd_pcm_uframes_t mt8186_pcm_pointer(struct snd_sof_dev *sdev,
+ struct snd_pcm_substream *substream)
+{
+ int ret;
+ snd_pcm_uframes_t pos;
+ struct snd_sof_pcm *spcm;
+ struct sof_ipc_stream_posn posn;
+ struct snd_sof_pcm_stream *stream;
+ struct snd_soc_component *scomp = sdev->component;
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+
+ spcm = snd_sof_find_spcm_dai(scomp, rtd);
+ if (!spcm) {
+ dev_warn_ratelimited(sdev->dev, "warn: can't find PCM with DAI ID %d\n",
+ rtd->dai_link->id);
+ return 0;
+ }
+
+ stream = &spcm->stream[substream->stream];
+ ret = snd_sof_ipc_msg_data(sdev, stream, &posn, sizeof(posn));
+ if (ret < 0) {
+ dev_warn(sdev->dev, "failed to read stream position: %d\n", ret);
+ return 0;
+ }
+
+ memcpy(&stream->posn, &posn, sizeof(posn));
+ pos = spcm->stream[substream->stream].posn.host_posn;
+ pos = bytes_to_frames(substream->runtime, pos);
+
+ return pos;
+}
+
+static void mt8186_adsp_dump(struct snd_sof_dev *sdev, u32 flags)
+{
+ u32 dbg_pc, dbg_data, dbg_inst, dbg_ls0stat, dbg_status, faultinfo;
+
+ /* dump debug registers */
+ dbg_pc = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGPC);
+ dbg_data = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGDATA);
+ dbg_inst = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGINST);
+ dbg_ls0stat = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGLS0STAT);
+ dbg_status = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGSTATUS);
+ faultinfo = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PFAULTINFO);
+
+ dev_info(sdev->dev, "adsp dump : pc %#x, data %#x, dbg_inst %#x,",
+ dbg_pc, dbg_data, dbg_inst);
+ dev_info(sdev->dev, "ls0stat %#x, status %#x, faultinfo %#x",
+ dbg_ls0stat, dbg_status, faultinfo);
+
+ mtk_adsp_dump(sdev, flags);
+}
+
+static struct snd_soc_dai_driver mt8186_dai[] = {
+{
+ .name = "SOF_DL1",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ },
+},
+{
+ .name = "SOF_DL2",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ },
+},
+{
+ .name = "SOF_UL1",
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ },
+},
+{
+ .name = "SOF_UL2",
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ },
+},
+};
+
+/* mt8186 ops */
+static struct snd_sof_dsp_ops sof_mt8186_ops = {
+ /* probe and remove */
+ .probe = mt8186_dsp_probe,
+ .remove = mt8186_dsp_remove,
+ .shutdown = mt8186_dsp_shutdown,
+
+ /* DSP core boot */
+ .run = mt8186_run,
+
+ /* Block IO */
+ .block_read = sof_block_read,
+ .block_write = sof_block_write,
+
+ /* Mailbox IO */
+ .mailbox_read = sof_mailbox_read,
+ .mailbox_write = sof_mailbox_write,
+
+ /* Register IO */
+ .write = sof_io_write,
+ .read = sof_io_read,
+ .write64 = sof_io_write64,
+ .read64 = sof_io_read64,
+
+ /* ipc */
+ .send_msg = mt8186_send_msg,
+ .get_mailbox_offset = mt8186_get_mailbox_offset,
+ .get_window_offset = mt8186_get_window_offset,
+ .ipc_msg_data = sof_ipc_msg_data,
+ .set_stream_data_offset = sof_set_stream_data_offset,
+
+ /* misc */
+ .get_bar_index = mt8186_get_bar_index,
+
+ /* stream callbacks */
+ .pcm_open = sof_stream_pcm_open,
+ .pcm_hw_params = mt8186_pcm_hw_params,
+ .pcm_pointer = mt8186_pcm_pointer,
+ .pcm_close = sof_stream_pcm_close,
+
+ /* firmware loading */
+ .load_firmware = snd_sof_load_firmware_memcpy,
+
+ /* Firmware ops */
+ .dsp_arch_ops = &sof_xtensa_arch_ops,
+
+ /* DAI drivers */
+ .drv = mt8186_dai,
+ .num_drv = ARRAY_SIZE(mt8186_dai),
+
+ /* Debug information */
+ .dbg_dump = mt8186_adsp_dump,
+ .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem,
+
+ /* PM */
+ .suspend = mt8186_dsp_suspend,
+ .resume = mt8186_dsp_resume,
+
+ /* ALSA HW info flags */
+ .hw_info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
+};
+
+static struct snd_sof_of_mach sof_mt8186_machs[] = {
+ {
+ .compatible = "mediatek,mt8186",
+ .sof_tplg_filename = "sof-mt8186.tplg",
+ },
+ {}
+};
+
+static const struct sof_dev_desc sof_of_mt8186_desc = {
+ .of_machines = sof_mt8186_machs,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "mediatek/sof",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "mediatek/sof-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-mt8186.ri",
+ },
+ .nocodec_tplg_filename = "sof-mt8186-nocodec.tplg",
+ .ops = &sof_mt8186_ops,
+};
+
+/*
+ * DL2, DL3, UL4, UL5 are registered as SOF FE, so creating the corresponding
+ * SOF BE to complete the pipeline.
+ */
+static struct snd_soc_dai_driver mt8188_dai[] = {
+{
+ .name = "SOF_DL2",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ },
+},
+{
+ .name = "SOF_DL3",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ },
+},
+{
+ .name = "SOF_UL4",
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ },
+},
+{
+ .name = "SOF_UL5",
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ },
+},
+};
+
+/* mt8188 ops */
+static struct snd_sof_dsp_ops sof_mt8188_ops;
+
+static int sof_mt8188_ops_init(struct snd_sof_dev *sdev)
+{
+ /* common defaults */
+ memcpy(&sof_mt8188_ops, &sof_mt8186_ops, sizeof(sof_mt8188_ops));
+
+ sof_mt8188_ops.drv = mt8188_dai;
+ sof_mt8188_ops.num_drv = ARRAY_SIZE(mt8188_dai);
+
+ return 0;
+}
+
+static struct snd_sof_of_mach sof_mt8188_machs[] = {
+ {
+ .compatible = "mediatek,mt8188",
+ .sof_tplg_filename = "sof-mt8188.tplg",
+ },
+ {}
+};
+
+static const struct sof_dev_desc sof_of_mt8188_desc = {
+ .of_machines = sof_mt8188_machs,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "mediatek/sof",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "mediatek/sof-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-mt8188.ri",
+ },
+ .nocodec_tplg_filename = "sof-mt8188-nocodec.tplg",
+ .ops = &sof_mt8188_ops,
+ .ops_init = sof_mt8188_ops_init,
+};
+
+static const struct of_device_id sof_of_mt8186_ids[] = {
+ { .compatible = "mediatek,mt8186-dsp", .data = &sof_of_mt8186_desc},
+ { .compatible = "mediatek,mt8188-dsp", .data = &sof_of_mt8188_desc},
+ { }
+};
+MODULE_DEVICE_TABLE(of, sof_of_mt8186_ids);
+
+/* DT driver definition */
+static struct platform_driver snd_sof_of_mt8186_driver = {
+ .probe = sof_of_probe,
+ .remove_new = sof_of_remove,
+ .shutdown = sof_of_shutdown,
+ .driver = {
+ .name = "sof-audio-of-mt8186",
+ .pm = &sof_of_pm,
+ .of_match_table = sof_of_mt8186_ids,
+ },
+};
+module_platform_driver(snd_sof_of_mt8186_driver);
+
+MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA);
+MODULE_IMPORT_NS(SND_SOC_SOF_MTK_COMMON);
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/sof/mediatek/mt8186/mt8186.h b/sound/soc/sof/mediatek/mt8186/mt8186.h
new file mode 100644
index 000000000000..91323f492a1e
--- /dev/null
+++ b/sound/soc/sof/mediatek/mt8186/mt8186.h
@@ -0,0 +1,93 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+
+/*
+ * Copyright (c) 2022 MediaTek Corporation. All rights reserved.
+ *
+ * Header file for the mt8186 DSP register definition
+ */
+
+#ifndef __MT8186_H
+#define __MT8186_H
+
+struct mtk_adsp_chip_info;
+struct snd_sof_dev;
+
+#define DSP_REG_BAR 4
+#define DSP_SECREG_BAR 5
+#define DSP_BUSREG_BAR 6
+
+/*****************************************************************************
+ * R E G I S T E R TABLE
+ *****************************************************************************/
+/* dsp cfg */
+#define ADSP_CFGREG_SW_RSTN 0x0000
+#define SW_DBG_RSTN_C0 BIT(0)
+#define SW_RSTN_C0 BIT(4)
+#define ADSP_HIFI_IO_CONFIG 0x000C
+#define TRACEMEMREADY BIT(15)
+#define RUNSTALL BIT(31)
+#define ADSP_IRQ_MASK 0x0030
+#define ADSP_DVFSRC_REQ 0x0040
+#define ADSP_DDREN_REQ_0 0x0044
+#define ADSP_SEMAPHORE 0x0064
+#define ADSP_WDT_CON_C0 0x007C
+#define ADSP_MBOX_IRQ_EN 0x009C
+#define DSP_MBOX0_IRQ_EN BIT(0)
+#define DSP_MBOX1_IRQ_EN BIT(1)
+#define DSP_MBOX2_IRQ_EN BIT(2)
+#define DSP_MBOX3_IRQ_EN BIT(3)
+#define DSP_MBOX4_IRQ_EN BIT(4)
+#define DSP_PDEBUGPC 0x013C
+#define DSP_PDEBUGDATA 0x0140
+#define DSP_PDEBUGINST 0x0144
+#define DSP_PDEBUGLS0STAT 0x0148
+#define DSP_PDEBUGSTATUS 0x014C
+#define DSP_PFAULTINFO 0x0150
+#define ADSP_CK_EN 0x1000
+#define CORE_CLK_EN BIT(0)
+#define COREDBG_EN BIT(1)
+#define TIMER_EN BIT(3)
+#define DMA_EN BIT(4)
+#define UART_EN BIT(5)
+#define ADSP_UART_CTRL 0x1010
+#define UART_BCLK_CG BIT(0)
+#define UART_RSTN BIT(3)
+
+/* dsp sec */
+#define ADSP_PRID 0x0
+#define ADSP_ALTVEC_C0 0x04
+#define ADSP_ALTVECSEL 0x0C
+#define MT8188_ADSP_ALTVECSEL_C0 BIT(0)
+#define MT8186_ADSP_ALTVECSEL_C0 BIT(1)
+
+/*
+ * On MT8188, BIT(1) is not evaluated and on MT8186 BIT(0) is not evaluated:
+ * We can simplify the driver by safely setting both bits regardless of the SoC.
+ */
+#define ADSP_ALTVECSEL_C0 (MT8188_ADSP_ALTVECSEL_C0 | \
+ MT8186_ADSP_ALTVECSEL_C0)
+
+/* dsp bus */
+#define ADSP_SRAM_POOL_CON 0x190
+#define DSP_SRAM_POOL_PD_MASK 0xF00F /* [0:3] and [12:15] */
+#define DSP_C0_EMI_MAP_ADDR 0xA00 /* ADSP Core0 To EMI Address Remap */
+#define DSP_C0_DMAEMI_MAP_ADDR 0xA08 /* DMA0 To EMI Address Remap */
+
+/* DSP memories */
+#define MBOX_OFFSET 0x500000 /* DRAM */
+#define MBOX_SIZE 0x1000 /* consistent with which in memory.h of sof fw */
+#define DSP_DRAM_SIZE 0xA00000 /* 16M */
+
+/*remap dram between AP and DSP view, 4KB aligned*/
+#define SRAM_PHYS_BASE_FROM_DSP_VIEW 0x4E100000 /* MT8186 DSP view */
+#define DRAM_PHYS_BASE_FROM_DSP_VIEW 0x60000000 /* MT8186 DSP view */
+#define DRAM_REMAP_SHIFT 12
+#define DRAM_REMAP_MASK 0xFFF
+
+#define SIZE_SHARED_DRAM_DL 0x40000 /*Shared buffer for Downlink*/
+#define SIZE_SHARED_DRAM_UL 0x40000 /*Shared buffer for Uplink*/
+#define TOTAL_SIZE_SHARED_DRAM_FROM_TAIL (SIZE_SHARED_DRAM_DL + SIZE_SHARED_DRAM_UL)
+
+void mt8186_sof_hifixdsp_boot_sequence(struct snd_sof_dev *sdev, u32 boot_addr);
+void mt8186_sof_hifixdsp_shutdown(struct snd_sof_dev *sdev);
+#endif
diff --git a/sound/soc/sof/mediatek/mt8195/Makefile b/sound/soc/sof/mediatek/mt8195/Makefile
new file mode 100644
index 000000000000..afc4f21fccc5
--- /dev/null
+++ b/sound/soc/sof/mediatek/mt8195/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+snd-sof-mt8195-objs := mt8195.o mt8195-clk.o mt8195-loader.o
+obj-$(CONFIG_SND_SOC_SOF_MT8195) += snd-sof-mt8195.o
diff --git a/sound/soc/sof/mediatek/mt8195/mt8195-clk.c b/sound/soc/sof/mediatek/mt8195/mt8195-clk.c
new file mode 100644
index 000000000000..7cffcad00f9b
--- /dev/null
+++ b/sound/soc/sof/mediatek/mt8195/mt8195-clk.c
@@ -0,0 +1,164 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// Copyright(c) 2021 Mediatek Corporation. All rights reserved.
+//
+// Author: YC Hung <yc.hung@mediatek.com>
+//
+// Hardware interface for mt8195 DSP clock
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include "mt8195.h"
+#include "mt8195-clk.h"
+#include "../adsp_helper.h"
+#include "../../sof-audio.h"
+
+static const char *adsp_clks[ADSP_CLK_MAX] = {
+ [CLK_TOP_ADSP] = "adsp_sel",
+ [CLK_TOP_CLK26M] = "clk26m_ck",
+ [CLK_TOP_AUDIO_LOCAL_BUS] = "audio_local_bus",
+ [CLK_TOP_MAINPLL_D7_D2] = "mainpll_d7_d2",
+ [CLK_SCP_ADSP_AUDIODSP] = "scp_adsp_audiodsp",
+ [CLK_TOP_AUDIO_H] = "audio_h",
+};
+
+int mt8195_adsp_init_clock(struct snd_sof_dev *sdev)
+{
+ struct device *dev = sdev->dev;
+ struct adsp_priv *priv = sdev->pdata->hw_pdata;
+ int i;
+
+ priv->clk = devm_kcalloc(dev, ADSP_CLK_MAX, sizeof(*priv->clk), GFP_KERNEL);
+
+ if (!priv->clk)
+ return -ENOMEM;
+
+ for (i = 0; i < ADSP_CLK_MAX; i++) {
+ priv->clk[i] = devm_clk_get(dev, adsp_clks[i]);
+ if (IS_ERR(priv->clk[i]))
+ return PTR_ERR(priv->clk[i]);
+ }
+
+ return 0;
+}
+
+static int adsp_enable_all_clock(struct snd_sof_dev *sdev)
+{
+ struct device *dev = sdev->dev;
+ struct adsp_priv *priv = sdev->pdata->hw_pdata;
+ int ret;
+
+ ret = clk_prepare_enable(priv->clk[CLK_TOP_MAINPLL_D7_D2]);
+ if (ret) {
+ dev_err(dev, "%s clk_prepare_enable(mainpll_d7_d2) fail %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(priv->clk[CLK_TOP_ADSP]);
+ if (ret) {
+ dev_err(dev, "%s clk_prepare_enable(adsp_sel) fail %d\n",
+ __func__, ret);
+ goto disable_mainpll_d7_d2_clk;
+ }
+
+ ret = clk_prepare_enable(priv->clk[CLK_TOP_AUDIO_LOCAL_BUS]);
+ if (ret) {
+ dev_err(dev, "%s clk_prepare_enable(audio_local_bus) fail %d\n",
+ __func__, ret);
+ goto disable_dsp_sel_clk;
+ }
+
+ ret = clk_prepare_enable(priv->clk[CLK_SCP_ADSP_AUDIODSP]);
+ if (ret) {
+ dev_err(dev, "%s clk_prepare_enable(scp_adsp_audiodsp) fail %d\n",
+ __func__, ret);
+ goto disable_audio_local_bus_clk;
+ }
+
+ ret = clk_prepare_enable(priv->clk[CLK_TOP_AUDIO_H]);
+ if (ret) {
+ dev_err(dev, "%s clk_prepare_enable(audio_h) fail %d\n",
+ __func__, ret);
+ goto disable_scp_adsp_audiodsp_clk;
+ }
+
+ return 0;
+
+disable_scp_adsp_audiodsp_clk:
+ clk_disable_unprepare(priv->clk[CLK_SCP_ADSP_AUDIODSP]);
+disable_audio_local_bus_clk:
+ clk_disable_unprepare(priv->clk[CLK_TOP_AUDIO_LOCAL_BUS]);
+disable_dsp_sel_clk:
+ clk_disable_unprepare(priv->clk[CLK_TOP_ADSP]);
+disable_mainpll_d7_d2_clk:
+ clk_disable_unprepare(priv->clk[CLK_TOP_MAINPLL_D7_D2]);
+
+ return ret;
+}
+
+static void adsp_disable_all_clock(struct snd_sof_dev *sdev)
+{
+ struct adsp_priv *priv = sdev->pdata->hw_pdata;
+
+ clk_disable_unprepare(priv->clk[CLK_TOP_AUDIO_H]);
+ clk_disable_unprepare(priv->clk[CLK_SCP_ADSP_AUDIODSP]);
+ clk_disable_unprepare(priv->clk[CLK_TOP_AUDIO_LOCAL_BUS]);
+ clk_disable_unprepare(priv->clk[CLK_TOP_ADSP]);
+ clk_disable_unprepare(priv->clk[CLK_TOP_MAINPLL_D7_D2]);
+}
+
+static int adsp_default_clk_init(struct snd_sof_dev *sdev, bool enable)
+{
+ struct device *dev = sdev->dev;
+ struct adsp_priv *priv = sdev->pdata->hw_pdata;
+ int ret;
+
+ dev_dbg(dev, "%s: %s\n", __func__, enable ? "on" : "off");
+
+ if (enable) {
+ ret = clk_set_parent(priv->clk[CLK_TOP_ADSP],
+ priv->clk[CLK_TOP_CLK26M]);
+ if (ret) {
+ dev_err(dev, "failed to set dsp_sel to clk26m: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_set_parent(priv->clk[CLK_TOP_AUDIO_LOCAL_BUS],
+ priv->clk[CLK_TOP_MAINPLL_D7_D2]);
+ if (ret) {
+ dev_err(dev, "set audio_local_bus failed %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_set_parent(priv->clk[CLK_TOP_AUDIO_H],
+ priv->clk[CLK_TOP_CLK26M]);
+ if (ret) {
+ dev_err(dev, "set audio_h_sel failed %d\n", ret);
+ return ret;
+ }
+
+ ret = adsp_enable_all_clock(sdev);
+ if (ret) {
+ dev_err(dev, "failed to adsp_enable_clock: %d\n", ret);
+ return ret;
+ }
+ } else {
+ adsp_disable_all_clock(sdev);
+ }
+
+ return 0;
+}
+
+int adsp_clock_on(struct snd_sof_dev *sdev)
+{
+ /* Open ADSP clock */
+ return adsp_default_clk_init(sdev, 1);
+}
+
+int adsp_clock_off(struct snd_sof_dev *sdev)
+{
+ /* Close ADSP clock */
+ return adsp_default_clk_init(sdev, 0);
+}
+
diff --git a/sound/soc/sof/mediatek/mt8195/mt8195-clk.h b/sound/soc/sof/mediatek/mt8195/mt8195-clk.h
new file mode 100644
index 000000000000..9cc0573d5cd2
--- /dev/null
+++ b/sound/soc/sof/mediatek/mt8195/mt8195-clk.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Copyright (c) 2021 MediaTek Corporation. All rights reserved.
+ *
+ * Header file for the mt8195 DSP clock definition
+ */
+
+#ifndef __MT8195_CLK_H
+#define __MT8195_CLK_H
+
+struct snd_sof_dev;
+
+/*DSP clock*/
+enum adsp_clk_id {
+ CLK_TOP_ADSP,
+ CLK_TOP_CLK26M,
+ CLK_TOP_AUDIO_LOCAL_BUS,
+ CLK_TOP_MAINPLL_D7_D2,
+ CLK_SCP_ADSP_AUDIODSP,
+ CLK_TOP_AUDIO_H,
+ ADSP_CLK_MAX
+};
+
+int mt8195_adsp_init_clock(struct snd_sof_dev *sdev);
+int adsp_clock_on(struct snd_sof_dev *sdev);
+int adsp_clock_off(struct snd_sof_dev *sdev);
+#endif
diff --git a/sound/soc/sof/mediatek/mt8195/mt8195-loader.c b/sound/soc/sof/mediatek/mt8195/mt8195-loader.c
new file mode 100644
index 000000000000..4be99ff4ebd3
--- /dev/null
+++ b/sound/soc/sof/mediatek/mt8195/mt8195-loader.c
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// Copyright (c) 2021 Mediatek Corporation. All rights reserved.
+//
+// Author: YC Hung <yc.hung@mediatek.com>
+//
+// Hardware interface for mt8195 DSP code loader
+
+#include <sound/sof.h>
+#include "mt8195.h"
+#include "../../ops.h"
+
+void sof_hifixdsp_boot_sequence(struct snd_sof_dev *sdev, u32 boot_addr)
+{
+ /* ADSP bootup base */
+ snd_sof_dsp_write(sdev, DSP_REG_BAR, DSP_ALTRESETVEC, boot_addr);
+
+ /* pull high RunStall (set bit3 to 1) */
+ snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, DSP_RESET_SW,
+ ADSP_RUNSTALL, ADSP_RUNSTALL);
+
+ /* pull high StatVectorSel to use AltResetVec (set bit4 to 1) */
+ snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, DSP_RESET_SW,
+ STATVECTOR_SEL, STATVECTOR_SEL);
+
+ /* toggle DReset & BReset */
+ /* pull high DReset & BReset */
+ snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, DSP_RESET_SW,
+ ADSP_BRESET_SW | ADSP_DRESET_SW,
+ ADSP_BRESET_SW | ADSP_DRESET_SW);
+
+ /* delay 10 DSP cycles at 26M about 1us by IP vendor's suggestion */
+ udelay(1);
+
+ /* pull low DReset & BReset */
+ snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, DSP_RESET_SW,
+ ADSP_BRESET_SW | ADSP_DRESET_SW,
+ 0);
+
+ /* Enable PDebug */
+ snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, DSP_PDEBUGBUS0,
+ PDEBUG_ENABLE,
+ PDEBUG_ENABLE);
+
+ /* release RunStall (set bit3 to 0) */
+ snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, DSP_RESET_SW,
+ ADSP_RUNSTALL, 0);
+}
+
+void sof_hifixdsp_shutdown(struct snd_sof_dev *sdev)
+{
+ /* RUN_STALL pull high again to reset */
+ snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, DSP_RESET_SW,
+ ADSP_RUNSTALL, ADSP_RUNSTALL);
+
+ /* pull high DReset & BReset */
+ snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, DSP_RESET_SW,
+ ADSP_BRESET_SW | ADSP_DRESET_SW,
+ ADSP_BRESET_SW | ADSP_DRESET_SW);
+}
+
diff --git a/sound/soc/sof/mediatek/mt8195/mt8195.c b/sound/soc/sof/mediatek/mt8195/mt8195.c
new file mode 100644
index 000000000000..8ee7ee246344
--- /dev/null
+++ b/sound/soc/sof/mediatek/mt8195/mt8195.c
@@ -0,0 +1,624 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// Copyright(c) 2021 Mediatek Inc. All rights reserved.
+//
+// Author: YC Hung <yc.hung@mediatek.com>
+//
+
+/*
+ * Hardware interface for audio DSP on mt8195
+ */
+
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/module.h>
+
+#include <sound/sof.h>
+#include <sound/sof/xtensa.h>
+#include "../../ops.h"
+#include "../../sof-of-dev.h"
+#include "../../sof-audio.h"
+#include "../adsp_helper.h"
+#include "../mtk-adsp-common.h"
+#include "mt8195.h"
+#include "mt8195-clk.h"
+
+static int mt8195_get_mailbox_offset(struct snd_sof_dev *sdev)
+{
+ return MBOX_OFFSET;
+}
+
+static int mt8195_get_window_offset(struct snd_sof_dev *sdev, u32 id)
+{
+ return MBOX_OFFSET;
+}
+
+static int mt8195_send_msg(struct snd_sof_dev *sdev,
+ struct snd_sof_ipc_msg *msg)
+{
+ struct adsp_priv *priv = sdev->pdata->hw_pdata;
+
+ sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data,
+ msg->msg_size);
+
+ return mtk_adsp_ipc_send(priv->dsp_ipc, MTK_ADSP_IPC_REQ, MTK_ADSP_IPC_OP_REQ);
+}
+
+static void mt8195_dsp_handle_reply(struct mtk_adsp_ipc *ipc)
+{
+ struct adsp_priv *priv = mtk_adsp_ipc_get_data(ipc);
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->sdev->ipc_lock, flags);
+ snd_sof_ipc_process_reply(priv->sdev, 0);
+ spin_unlock_irqrestore(&priv->sdev->ipc_lock, flags);
+}
+
+static void mt8195_dsp_handle_request(struct mtk_adsp_ipc *ipc)
+{
+ struct adsp_priv *priv = mtk_adsp_ipc_get_data(ipc);
+ u32 p; /* panic code */
+ int ret;
+
+ /* Read the message from the debug box. */
+ sof_mailbox_read(priv->sdev, priv->sdev->debug_box.offset + 4,
+ &p, sizeof(p));
+
+ /* Check to see if the message is a panic code 0x0dead*** */
+ if ((p & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) {
+ snd_sof_dsp_panic(priv->sdev, p, true);
+ } else {
+ snd_sof_ipc_msgs_rx(priv->sdev);
+
+ /* tell DSP cmd is done */
+ ret = mtk_adsp_ipc_send(priv->dsp_ipc, MTK_ADSP_IPC_RSP, MTK_ADSP_IPC_OP_RSP);
+ if (ret)
+ dev_err(priv->dev, "request send ipc failed");
+ }
+}
+
+static struct mtk_adsp_ipc_ops dsp_ops = {
+ .handle_reply = mt8195_dsp_handle_reply,
+ .handle_request = mt8195_dsp_handle_request,
+};
+
+static int platform_parse_resource(struct platform_device *pdev, void *data)
+{
+ struct resource *mmio;
+ struct resource res;
+ struct device_node *mem_region;
+ struct device *dev = &pdev->dev;
+ struct mtk_adsp_chip_info *adsp = data;
+ int ret;
+
+ ret = of_reserved_mem_device_init(dev);
+ if (ret) {
+ dev_err(dev, "of_reserved_mem_device_init failed\n");
+ return ret;
+ }
+
+ mem_region = of_parse_phandle(dev->of_node, "memory-region", 1);
+ if (!mem_region) {
+ dev_err(dev, "no memory-region sysmem phandle\n");
+ return -ENODEV;
+ }
+
+ ret = of_address_to_resource(mem_region, 0, &res);
+ of_node_put(mem_region);
+ if (ret) {
+ dev_err(dev, "of_address_to_resource sysmem failed\n");
+ return ret;
+ }
+
+ adsp->pa_dram = (phys_addr_t)res.start;
+ adsp->dramsize = resource_size(&res);
+ if (adsp->pa_dram & DRAM_REMAP_MASK) {
+ dev_err(dev, "adsp memory(%#x) is not 4K-aligned\n",
+ (u32)adsp->pa_dram);
+ return -EINVAL;
+ }
+
+ if (adsp->dramsize < TOTAL_SIZE_SHARED_DRAM_FROM_TAIL) {
+ dev_err(dev, "adsp memory(%#x) is not enough for share\n",
+ adsp->dramsize);
+ return -EINVAL;
+ }
+
+ dev_dbg(dev, "dram pbase=%pa, dramsize=%#x\n",
+ &adsp->pa_dram, adsp->dramsize);
+
+ /* Parse CFG base */
+ mmio = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cfg");
+ if (!mmio) {
+ dev_err(dev, "no ADSP-CFG register resource\n");
+ return -ENXIO;
+ }
+ /* remap for DSP register accessing */
+ adsp->va_cfgreg = devm_ioremap_resource(dev, mmio);
+ if (IS_ERR(adsp->va_cfgreg))
+ return PTR_ERR(adsp->va_cfgreg);
+
+ adsp->pa_cfgreg = (phys_addr_t)mmio->start;
+ adsp->cfgregsize = resource_size(mmio);
+
+ dev_dbg(dev, "cfgreg-vbase=%p, cfgregsize=%#x\n",
+ adsp->va_cfgreg, adsp->cfgregsize);
+
+ /* Parse SRAM */
+ mmio = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sram");
+ if (!mmio) {
+ dev_err(dev, "no SRAM resource\n");
+ return -ENXIO;
+ }
+
+ adsp->pa_sram = (phys_addr_t)mmio->start;
+ adsp->sramsize = resource_size(mmio);
+
+ dev_dbg(dev, "sram pbase=%pa,%#x\n", &adsp->pa_sram, adsp->sramsize);
+
+ return ret;
+}
+
+static int adsp_sram_power_on(struct device *dev, bool on)
+{
+ void __iomem *va_dspsysreg;
+ u32 srampool_con;
+
+ va_dspsysreg = ioremap(ADSP_SRAM_POOL_CON, 0x4);
+ if (!va_dspsysreg) {
+ dev_err(dev, "failed to ioremap sram pool base %#x\n",
+ ADSP_SRAM_POOL_CON);
+ return -ENOMEM;
+ }
+
+ srampool_con = readl(va_dspsysreg);
+ if (on)
+ writel(srampool_con & ~DSP_SRAM_POOL_PD_MASK, va_dspsysreg);
+ else
+ writel(srampool_con | DSP_SRAM_POOL_PD_MASK, va_dspsysreg);
+
+ iounmap(va_dspsysreg);
+ return 0;
+}
+
+/* Init the basic DSP DRAM address */
+static int adsp_memory_remap_init(struct device *dev, struct mtk_adsp_chip_info *adsp)
+{
+ void __iomem *vaddr_emi_map;
+ int offset;
+
+ if (!adsp)
+ return -ENXIO;
+
+ vaddr_emi_map = devm_ioremap(dev, DSP_EMI_MAP_ADDR, 0x4);
+ if (!vaddr_emi_map) {
+ dev_err(dev, "failed to ioremap emi map base %#x\n",
+ DSP_EMI_MAP_ADDR);
+ return -ENOMEM;
+ }
+
+ offset = adsp->pa_dram - DRAM_PHYS_BASE_FROM_DSP_VIEW;
+ adsp->dram_offset = offset;
+ offset >>= DRAM_REMAP_SHIFT;
+ dev_dbg(dev, "adsp->pa_dram %pa, offset %#x\n", &adsp->pa_dram, offset);
+ writel(offset, vaddr_emi_map);
+ if (offset != readl(vaddr_emi_map)) {
+ dev_err(dev, "write emi map fail : %#x\n", readl(vaddr_emi_map));
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int mt8195_run(struct snd_sof_dev *sdev)
+{
+ u32 adsp_bootup_addr;
+
+ adsp_bootup_addr = SRAM_PHYS_BASE_FROM_DSP_VIEW;
+ dev_dbg(sdev->dev, "HIFIxDSP boot from base : 0x%08X\n", adsp_bootup_addr);
+ sof_hifixdsp_boot_sequence(sdev, adsp_bootup_addr);
+
+ return 0;
+}
+
+static int mt8195_dsp_probe(struct snd_sof_dev *sdev)
+{
+ struct platform_device *pdev = container_of(sdev->dev, struct platform_device, dev);
+ struct adsp_priv *priv;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ sdev->pdata->hw_pdata = priv;
+ priv->dev = sdev->dev;
+ priv->sdev = sdev;
+
+ priv->adsp = devm_kzalloc(&pdev->dev, sizeof(struct mtk_adsp_chip_info), GFP_KERNEL);
+ if (!priv->adsp)
+ return -ENOMEM;
+
+ ret = platform_parse_resource(pdev, priv->adsp);
+ if (ret)
+ return ret;
+
+ ret = mt8195_adsp_init_clock(sdev);
+ if (ret) {
+ dev_err(sdev->dev, "mt8195_adsp_init_clock failed\n");
+ return -EINVAL;
+ }
+
+ ret = adsp_clock_on(sdev);
+ if (ret) {
+ dev_err(sdev->dev, "adsp_clock_on fail!\n");
+ return -EINVAL;
+ }
+
+ ret = adsp_sram_power_on(sdev->dev, true);
+ if (ret) {
+ dev_err(sdev->dev, "adsp_sram_power_on fail!\n");
+ goto exit_clk_disable;
+ }
+
+ ret = adsp_memory_remap_init(&pdev->dev, priv->adsp);
+ if (ret) {
+ dev_err(sdev->dev, "adsp_memory_remap_init fail!\n");
+ goto err_adsp_sram_power_off;
+ }
+
+ sdev->bar[SOF_FW_BLK_TYPE_IRAM] = devm_ioremap(sdev->dev,
+ priv->adsp->pa_sram,
+ priv->adsp->sramsize);
+ if (!sdev->bar[SOF_FW_BLK_TYPE_IRAM]) {
+ dev_err(sdev->dev, "failed to ioremap base %pa size %#x\n",
+ &priv->adsp->pa_sram, priv->adsp->sramsize);
+ ret = -EINVAL;
+ goto err_adsp_sram_power_off;
+ }
+
+ priv->adsp->va_sram = sdev->bar[SOF_FW_BLK_TYPE_IRAM];
+
+ sdev->bar[SOF_FW_BLK_TYPE_SRAM] = devm_ioremap(sdev->dev,
+ priv->adsp->pa_dram,
+ priv->adsp->dramsize);
+ if (!sdev->bar[SOF_FW_BLK_TYPE_SRAM]) {
+ dev_err(sdev->dev, "failed to ioremap base %pa size %#x\n",
+ &priv->adsp->pa_dram, priv->adsp->dramsize);
+ ret = -EINVAL;
+ goto err_adsp_sram_power_off;
+ }
+ priv->adsp->va_dram = sdev->bar[SOF_FW_BLK_TYPE_SRAM];
+
+ sdev->bar[DSP_REG_BAR] = priv->adsp->va_cfgreg;
+
+ sdev->mmio_bar = SOF_FW_BLK_TYPE_SRAM;
+ sdev->mailbox_bar = SOF_FW_BLK_TYPE_SRAM;
+
+ /* set default mailbox offset for FW ready message */
+ sdev->dsp_box.offset = mt8195_get_mailbox_offset(sdev);
+
+ priv->ipc_dev = platform_device_register_data(&pdev->dev, "mtk-adsp-ipc",
+ PLATFORM_DEVID_NONE,
+ pdev, sizeof(*pdev));
+ if (IS_ERR(priv->ipc_dev)) {
+ ret = PTR_ERR(priv->ipc_dev);
+ dev_err(sdev->dev, "failed to register mtk-adsp-ipc device\n");
+ goto err_adsp_sram_power_off;
+ }
+
+ priv->dsp_ipc = dev_get_drvdata(&priv->ipc_dev->dev);
+ if (!priv->dsp_ipc) {
+ ret = -EPROBE_DEFER;
+ dev_err(sdev->dev, "failed to get drvdata\n");
+ goto exit_pdev_unregister;
+ }
+
+ mtk_adsp_ipc_set_data(priv->dsp_ipc, priv);
+ priv->dsp_ipc->ops = &dsp_ops;
+
+ return 0;
+
+exit_pdev_unregister:
+ platform_device_unregister(priv->ipc_dev);
+err_adsp_sram_power_off:
+ adsp_sram_power_on(&pdev->dev, false);
+exit_clk_disable:
+ adsp_clock_off(sdev);
+
+ return ret;
+}
+
+static int mt8195_dsp_shutdown(struct snd_sof_dev *sdev)
+{
+ return snd_sof_suspend(sdev->dev);
+}
+
+static void mt8195_dsp_remove(struct snd_sof_dev *sdev)
+{
+ struct platform_device *pdev = container_of(sdev->dev, struct platform_device, dev);
+ struct adsp_priv *priv = sdev->pdata->hw_pdata;
+
+ platform_device_unregister(priv->ipc_dev);
+ adsp_sram_power_on(&pdev->dev, false);
+ adsp_clock_off(sdev);
+}
+
+static int mt8195_dsp_suspend(struct snd_sof_dev *sdev, u32 target_state)
+{
+ struct platform_device *pdev = container_of(sdev->dev, struct platform_device, dev);
+ int ret;
+ u32 reset_sw, dbg_pc;
+
+ /* wait dsp enter idle, timeout is 1 second */
+ ret = snd_sof_dsp_read_poll_timeout(sdev, DSP_REG_BAR,
+ DSP_RESET_SW, reset_sw,
+ ((reset_sw & ADSP_PWAIT) == ADSP_PWAIT),
+ SUSPEND_DSP_IDLE_POLL_INTERVAL_US,
+ SUSPEND_DSP_IDLE_TIMEOUT_US);
+ if (ret < 0) {
+ dbg_pc = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGPC);
+ dev_warn(sdev->dev, "dsp not idle, powering off anyway : swrest %#x, pc %#x, ret %d\n",
+ reset_sw, dbg_pc, ret);
+ }
+
+ /* stall and reset dsp */
+ sof_hifixdsp_shutdown(sdev);
+
+ /* power down adsp sram */
+ ret = adsp_sram_power_on(&pdev->dev, false);
+ if (ret) {
+ dev_err(sdev->dev, "adsp_sram_power_off fail!\n");
+ return ret;
+ }
+
+ /* turn off adsp clock */
+ return adsp_clock_off(sdev);
+}
+
+static int mt8195_dsp_resume(struct snd_sof_dev *sdev)
+{
+ int ret;
+
+ /* turn on adsp clock */
+ ret = adsp_clock_on(sdev);
+ if (ret) {
+ dev_err(sdev->dev, "adsp_clock_on fail!\n");
+ return ret;
+ }
+
+ /* power on adsp sram */
+ ret = adsp_sram_power_on(sdev->dev, true);
+ if (ret)
+ dev_err(sdev->dev, "adsp_sram_power_on fail!\n");
+
+ return ret;
+}
+
+/* on mt8195 there is 1 to 1 match between type and BAR idx */
+static int mt8195_get_bar_index(struct snd_sof_dev *sdev, u32 type)
+{
+ return type;
+}
+
+static int mt8195_pcm_hw_params(struct snd_sof_dev *sdev,
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_sof_platform_stream_params *platform_params)
+{
+ platform_params->cont_update_posn = 1;
+
+ return 0;
+}
+
+static snd_pcm_uframes_t mt8195_pcm_pointer(struct snd_sof_dev *sdev,
+ struct snd_pcm_substream *substream)
+{
+ int ret;
+ snd_pcm_uframes_t pos;
+ struct snd_sof_pcm *spcm;
+ struct sof_ipc_stream_posn posn;
+ struct snd_sof_pcm_stream *stream;
+ struct snd_soc_component *scomp = sdev->component;
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+
+ spcm = snd_sof_find_spcm_dai(scomp, rtd);
+ if (!spcm) {
+ dev_warn_ratelimited(sdev->dev, "warn: can't find PCM with DAI ID %d\n",
+ rtd->dai_link->id);
+ return 0;
+ }
+
+ stream = &spcm->stream[substream->stream];
+ ret = snd_sof_ipc_msg_data(sdev, stream, &posn, sizeof(posn));
+ if (ret < 0) {
+ dev_warn(sdev->dev, "failed to read stream position: %d\n", ret);
+ return 0;
+ }
+
+ memcpy(&stream->posn, &posn, sizeof(posn));
+ pos = spcm->stream[substream->stream].posn.host_posn;
+ pos = bytes_to_frames(substream->runtime, pos);
+
+ return pos;
+}
+
+static void mt8195_adsp_dump(struct snd_sof_dev *sdev, u32 flags)
+{
+ u32 dbg_pc, dbg_data, dbg_bus0, dbg_bus1, dbg_inst;
+ u32 dbg_ls0stat, dbg_ls1stat, faultbus, faultinfo, swrest;
+
+ /* dump debug registers */
+ dbg_pc = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGPC);
+ dbg_data = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGDATA);
+ dbg_bus0 = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGBUS0);
+ dbg_bus1 = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGBUS1);
+ dbg_inst = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGINST);
+ dbg_ls0stat = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGLS0STAT);
+ dbg_ls1stat = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGLS1STAT);
+ faultbus = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PFAULTBUS);
+ faultinfo = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PFAULTINFO);
+ swrest = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_RESET_SW);
+
+ dev_info(sdev->dev, "adsp dump : pc %#x, data %#x, bus0 %#x, bus1 %#x, swrest %#x",
+ dbg_pc, dbg_data, dbg_bus0, dbg_bus1, swrest);
+ dev_info(sdev->dev, "dbg_inst %#x, ls0stat %#x, ls1stat %#x, faultbus %#x, faultinfo %#x",
+ dbg_inst, dbg_ls0stat, dbg_ls1stat, faultbus, faultinfo);
+
+ mtk_adsp_dump(sdev, flags);
+}
+
+static struct snd_soc_dai_driver mt8195_dai[] = {
+{
+ .name = "SOF_DL2",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ },
+},
+{
+ .name = "SOF_DL3",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ },
+},
+{
+ .name = "SOF_UL4",
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ },
+},
+{
+ .name = "SOF_UL5",
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ },
+},
+};
+
+/* mt8195 ops */
+static struct snd_sof_dsp_ops sof_mt8195_ops = {
+ /* probe and remove */
+ .probe = mt8195_dsp_probe,
+ .remove = mt8195_dsp_remove,
+ .shutdown = mt8195_dsp_shutdown,
+
+ /* DSP core boot */
+ .run = mt8195_run,
+
+ /* Block IO */
+ .block_read = sof_block_read,
+ .block_write = sof_block_write,
+
+ /* Mailbox IO */
+ .mailbox_read = sof_mailbox_read,
+ .mailbox_write = sof_mailbox_write,
+
+ /* Register IO */
+ .write = sof_io_write,
+ .read = sof_io_read,
+ .write64 = sof_io_write64,
+ .read64 = sof_io_read64,
+
+ /* ipc */
+ .send_msg = mt8195_send_msg,
+ .get_mailbox_offset = mt8195_get_mailbox_offset,
+ .get_window_offset = mt8195_get_window_offset,
+ .ipc_msg_data = sof_ipc_msg_data,
+ .set_stream_data_offset = sof_set_stream_data_offset,
+
+ /* misc */
+ .get_bar_index = mt8195_get_bar_index,
+
+ /* stream callbacks */
+ .pcm_open = sof_stream_pcm_open,
+ .pcm_hw_params = mt8195_pcm_hw_params,
+ .pcm_pointer = mt8195_pcm_pointer,
+ .pcm_close = sof_stream_pcm_close,
+
+ /* firmware loading */
+ .load_firmware = snd_sof_load_firmware_memcpy,
+
+ /* Firmware ops */
+ .dsp_arch_ops = &sof_xtensa_arch_ops,
+
+ /* Debug information */
+ .dbg_dump = mt8195_adsp_dump,
+ .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem,
+
+ /* DAI drivers */
+ .drv = mt8195_dai,
+ .num_drv = ARRAY_SIZE(mt8195_dai),
+
+ /* PM */
+ .suspend = mt8195_dsp_suspend,
+ .resume = mt8195_dsp_resume,
+
+ /* ALSA HW info flags */
+ .hw_info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
+};
+
+static struct snd_sof_of_mach sof_mt8195_machs[] = {
+ {
+ .compatible = "google,tomato",
+ .sof_tplg_filename = "sof-mt8195-mt6359-rt1019-rt5682-dts.tplg"
+ }, {
+ .compatible = "mediatek,mt8195",
+ .sof_tplg_filename = "sof-mt8195.tplg"
+ }, {
+ /* sentinel */
+ }
+};
+
+static const struct sof_dev_desc sof_of_mt8195_desc = {
+ .of_machines = sof_mt8195_machs,
+ .ipc_supported_mask = BIT(SOF_IPC_TYPE_3),
+ .ipc_default = SOF_IPC_TYPE_3,
+ .default_fw_path = {
+ [SOF_IPC_TYPE_3] = "mediatek/sof",
+ },
+ .default_tplg_path = {
+ [SOF_IPC_TYPE_3] = "mediatek/sof-tplg",
+ },
+ .default_fw_filename = {
+ [SOF_IPC_TYPE_3] = "sof-mt8195.ri",
+ },
+ .nocodec_tplg_filename = "sof-mt8195-nocodec.tplg",
+ .ops = &sof_mt8195_ops,
+ .ipc_timeout = 1000,
+};
+
+static const struct of_device_id sof_of_mt8195_ids[] = {
+ { .compatible = "mediatek,mt8195-dsp", .data = &sof_of_mt8195_desc},
+ { }
+};
+MODULE_DEVICE_TABLE(of, sof_of_mt8195_ids);
+
+/* DT driver definition */
+static struct platform_driver snd_sof_of_mt8195_driver = {
+ .probe = sof_of_probe,
+ .remove_new = sof_of_remove,
+ .shutdown = sof_of_shutdown,
+ .driver = {
+ .name = "sof-audio-of-mt8195",
+ .pm = &sof_of_pm,
+ .of_match_table = sof_of_mt8195_ids,
+ },
+};
+module_platform_driver(snd_sof_of_mt8195_driver);
+
+MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA);
+MODULE_IMPORT_NS(SND_SOC_SOF_MTK_COMMON);
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/sof/mediatek/mt8195/mt8195.h b/sound/soc/sof/mediatek/mt8195/mt8195.h
new file mode 100644
index 000000000000..b4229170049f
--- /dev/null
+++ b/sound/soc/sof/mediatek/mt8195/mt8195.h
@@ -0,0 +1,161 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Copyright (c) 2021 MediaTek Corporation. All rights reserved.
+ *
+ * Header file for the mt8195 DSP register definition
+ */
+
+#ifndef __MT8195_H
+#define __MT8195_H
+
+struct mtk_adsp_chip_info;
+struct snd_sof_dev;
+
+#define DSP_REG_BASE 0x10803000
+#define SCP_CFGREG_BASE 0x10724000
+#define DSP_SYSAO_BASE 0x1080C000
+
+/*****************************************************************************
+ * R E G I S T E R TABLE
+ *****************************************************************************/
+#define DSP_JTAGMUX 0x0000
+#define DSP_ALTRESETVEC 0x0004
+#define DSP_PDEBUGDATA 0x0008
+#define DSP_PDEBUGBUS0 0x000c
+#define PDEBUG_ENABLE BIT(0)
+#define DSP_PDEBUGBUS1 0x0010
+#define DSP_PDEBUGINST 0x0014
+#define DSP_PDEBUGLS0STAT 0x0018
+#define DSP_PDEBUGLS1STAT 0x001c
+#define DSP_PDEBUGPC 0x0020
+#define DSP_RESET_SW 0x0024 /*reset sw*/
+#define ADSP_BRESET_SW BIT(0)
+#define ADSP_DRESET_SW BIT(1)
+#define ADSP_RUNSTALL BIT(3)
+#define STATVECTOR_SEL BIT(4)
+#define ADSP_PWAIT BIT(16)
+#define DSP_PFAULTBUS 0x0028
+#define DSP_PFAULTINFO 0x002c
+#define DSP_GPR00 0x0030
+#define DSP_GPR01 0x0034
+#define DSP_GPR02 0x0038
+#define DSP_GPR03 0x003c
+#define DSP_GPR04 0x0040
+#define DSP_GPR05 0x0044
+#define DSP_GPR06 0x0048
+#define DSP_GPR07 0x004c
+#define DSP_GPR08 0x0050
+#define DSP_GPR09 0x0054
+#define DSP_GPR0A 0x0058
+#define DSP_GPR0B 0x005c
+#define DSP_GPR0C 0x0060
+#define DSP_GPR0D 0x0064
+#define DSP_GPR0E 0x0068
+#define DSP_GPR0F 0x006c
+#define DSP_GPR10 0x0070
+#define DSP_GPR11 0x0074
+#define DSP_GPR12 0x0078
+#define DSP_GPR13 0x007c
+#define DSP_GPR14 0x0080
+#define DSP_GPR15 0x0084
+#define DSP_GPR16 0x0088
+#define DSP_GPR17 0x008c
+#define DSP_GPR18 0x0090
+#define DSP_GPR19 0x0094
+#define DSP_GPR1A 0x0098
+#define DSP_GPR1B 0x009c
+#define DSP_GPR1C 0x00a0
+#define DSP_GPR1D 0x00a4
+#define DSP_GPR1E 0x00a8
+#define DSP_GPR1F 0x00ac
+#define DSP_TCM_OFFSET 0x00b0 /* not used */
+#define DSP_DDR_OFFSET 0x00b4 /* not used */
+#define DSP_INTFDSP 0x00d0
+#define DSP_INTFDSP_CLR 0x00d4
+#define DSP_SRAM_PD_SW1 0x00d8
+#define DSP_SRAM_PD_SW2 0x00dc
+#define DSP_OCD 0x00e0
+#define DSP_RG_DSP_IRQ_POL 0x00f0 /* not used */
+#define DSP_DSP_IRQ_EN 0x00f4 /* not used */
+#define DSP_DSP_IRQ_LEVEL 0x00f8 /* not used */
+#define DSP_DSP_IRQ_STATUS 0x00fc /* not used */
+#define DSP_RG_INT2CIRQ 0x0114
+#define DSP_RG_INT_POL_CTL0 0x0120
+#define DSP_RG_INT_EN_CTL0 0x0130
+#define DSP_RG_INT_LV_CTL0 0x0140
+#define DSP_RG_INT_STATUS0 0x0150
+#define DSP_PDEBUGSTATUS0 0x0200
+#define DSP_PDEBUGSTATUS1 0x0204
+#define DSP_PDEBUGSTATUS2 0x0208
+#define DSP_PDEBUGSTATUS3 0x020c
+#define DSP_PDEBUGSTATUS4 0x0210
+#define DSP_PDEBUGSTATUS5 0x0214
+#define DSP_PDEBUGSTATUS6 0x0218
+#define DSP_PDEBUGSTATUS7 0x021c
+#define DSP_DSP2PSRAM_PRIORITY 0x0220 /* not used */
+#define DSP_AUDIO_DSP2SPM_INT 0x0224
+#define DSP_AUDIO_DSP2SPM_INT_ACK 0x0228
+#define DSP_AUDIO_DSP_DEBUG_SEL 0x022C
+#define DSP_AUDIO_DSP_EMI_BASE_ADDR 0x02E0 /* not used */
+#define DSP_AUDIO_DSP_SHARED_IRAM 0x02E4
+#define DSP_AUDIO_DSP_CKCTRL_P2P_CK_CON 0x02F0
+#define DSP_RG_SEMAPHORE00 0x0300
+#define DSP_RG_SEMAPHORE01 0x0304
+#define DSP_RG_SEMAPHORE02 0x0308
+#define DSP_RG_SEMAPHORE03 0x030C
+#define DSP_RG_SEMAPHORE04 0x0310
+#define DSP_RG_SEMAPHORE05 0x0314
+#define DSP_RG_SEMAPHORE06 0x0318
+#define DSP_RG_SEMAPHORE07 0x031C
+#define DSP_RESERVED_0 0x03F0
+#define DSP_RESERVED_1 0x03F4
+
+/* dsp wdt */
+#define DSP_WDT_MODE 0x0400
+
+/* dsp mbox */
+#define DSP_MBOX_IN_CMD 0x00
+#define DSP_MBOX_IN_CMD_CLR 0x04
+#define DSP_MBOX_OUT_CMD 0x1c
+#define DSP_MBOX_OUT_CMD_CLR 0x20
+#define DSP_MBOX_IN_MSG0 0x08
+#define DSP_MBOX_IN_MSG1 0x0C
+#define DSP_MBOX_OUT_MSG0 0x24
+#define DSP_MBOX_OUT_MSG1 0x28
+
+/*dsp sys ao*/
+#define ADSP_SRAM_POOL_CON (DSP_SYSAO_BASE + 0x30)
+#define DSP_SRAM_POOL_PD_MASK 0xf
+#define DSP_EMI_MAP_ADDR (DSP_SYSAO_BASE + 0x81c)
+
+/* DSP memories */
+#define MBOX_OFFSET 0x800000 /* DRAM */
+#define MBOX_SIZE 0x1000 /* consistent with which in memory.h of sof fw */
+#define DSP_DRAM_SIZE 0x1000000 /* 16M */
+
+#define DSP_REG_BAR 4
+#define DSP_MBOX0_BAR 5
+#define DSP_MBOX1_BAR 6
+#define DSP_MBOX2_BAR 7
+
+#define SIZE_SHARED_DRAM_DL 0x40000 /*Shared buffer for Downlink*/
+#define SIZE_SHARED_DRAM_UL 0x40000 /*Shared buffer for Uplink*/
+
+#define TOTAL_SIZE_SHARED_DRAM_FROM_TAIL \
+ (SIZE_SHARED_DRAM_DL + SIZE_SHARED_DRAM_UL)
+
+#define SRAM_PHYS_BASE_FROM_DSP_VIEW 0x40000000 /* MT8195 DSP view */
+#define DRAM_PHYS_BASE_FROM_DSP_VIEW 0x60000000 /* MT8195 DSP view */
+
+/*remap dram between AP and DSP view, 4KB aligned*/
+#define DRAM_REMAP_SHIFT 12
+#define DRAM_REMAP_MASK (BIT(DRAM_REMAP_SHIFT) - 1)
+
+/* suspend dsp idle check interval and timeout */
+#define SUSPEND_DSP_IDLE_TIMEOUT_US 1000000 /* timeout to wait dsp idle, 1 sec */
+#define SUSPEND_DSP_IDLE_POLL_INTERVAL_US 500 /* 0.5 msec */
+
+void sof_hifixdsp_boot_sequence(struct snd_sof_dev *sdev, u32 boot_addr);
+void sof_hifixdsp_shutdown(struct snd_sof_dev *sdev);
+#endif
diff --git a/sound/soc/sof/mediatek/mtk-adsp-common.c b/sound/soc/sof/mediatek/mtk-adsp-common.c
new file mode 100644
index 000000000000..de8dbe27cd0d
--- /dev/null
+++ b/sound/soc/sof/mediatek/mtk-adsp-common.c
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2022 MediaTek Inc. All rights reserved.
+//
+// Author: YC Hung <yc.hung@mediatek.com>
+
+/*
+ * Common helpers for the audio DSP on MediaTek platforms
+ */
+
+#include <linux/module.h>
+#include <sound/sof/xtensa.h>
+#include "../ops.h"
+#include "mtk-adsp-common.h"
+
+/**
+ * mtk_adsp_get_registers() - This function is called in case of DSP oops
+ * in order to gather information about the registers, filename and
+ * linenumber and stack.
+ * @sdev: SOF device
+ * @xoops: Stores information about registers.
+ * @panic_info: Stores information about filename and line number.
+ * @stack: Stores the stack dump.
+ * @stack_words: Size of the stack dump.
+ */
+static void mtk_adsp_get_registers(struct snd_sof_dev *sdev,
+ struct sof_ipc_dsp_oops_xtensa *xoops,
+ struct sof_ipc_panic_info *panic_info,
+ u32 *stack, size_t stack_words)
+{
+ u32 offset = sdev->dsp_oops_offset;
+
+ /* first read registers */
+ sof_mailbox_read(sdev, offset, xoops, sizeof(*xoops));
+
+ /* then get panic info */
+ if (xoops->arch_hdr.totalsize > EXCEPT_MAX_HDR_SIZE) {
+ dev_err(sdev->dev, "invalid header size 0x%x\n",
+ xoops->arch_hdr.totalsize);
+ return;
+ }
+ offset += xoops->arch_hdr.totalsize;
+ sof_mailbox_read(sdev, offset, panic_info, sizeof(*panic_info));
+
+ /* then get the stack */
+ offset += sizeof(*panic_info);
+ sof_mailbox_read(sdev, offset, stack, stack_words * sizeof(u32));
+}
+
+/**
+ * mtk_adsp_dump() - This function is called when a panic message is
+ * received from the firmware.
+ * @sdev: SOF device
+ * @flags: parameter not used but required by ops prototype
+ */
+void mtk_adsp_dump(struct snd_sof_dev *sdev, u32 flags)
+{
+ char *level = (flags & SOF_DBG_DUMP_OPTIONAL) ? KERN_DEBUG : KERN_ERR;
+ struct sof_ipc_dsp_oops_xtensa xoops;
+ struct sof_ipc_panic_info panic_info = {};
+ u32 stack[MTK_ADSP_STACK_DUMP_SIZE];
+ u32 status;
+
+ /* Get information about the panic status from the debug box area.
+ * Compute the trace point based on the status.
+ */
+ sof_mailbox_read(sdev, sdev->debug_box.offset + 0x4, &status, 4);
+
+ /* Get information about the registers, the filename and line
+ * number and the stack.
+ */
+ mtk_adsp_get_registers(sdev, &xoops, &panic_info, stack,
+ MTK_ADSP_STACK_DUMP_SIZE);
+
+ /* Print the information to the console */
+ sof_print_oops_and_stack(sdev, level, status, status, &xoops, &panic_info,
+ stack, MTK_ADSP_STACK_DUMP_SIZE);
+}
+EXPORT_SYMBOL(mtk_adsp_dump);
+
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/sof/mediatek/mtk-adsp-common.h b/sound/soc/sof/mediatek/mtk-adsp-common.h
new file mode 100644
index 000000000000..612cff1f38f7
--- /dev/null
+++ b/sound/soc/sof/mediatek/mtk-adsp-common.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+
+#ifndef __MEDIATEK_ADSP_COMMON_H__
+#define __MEDIATEK_ADSP_COMMON_H__
+
+#define EXCEPT_MAX_HDR_SIZE 0x400
+#define MTK_ADSP_STACK_DUMP_SIZE 32
+
+void mtk_adsp_dump(struct snd_sof_dev *sdev, u32 flags);
+#endif
diff --git a/sound/soc/sof/nocodec.c b/sound/soc/sof/nocodec.c
index 9e922df6a710..34aa8a7cfc7d 100644
--- a/sound/soc/sof/nocodec.c
+++ b/sound/soc/sof/nocodec.c
@@ -10,27 +10,29 @@
#include <linux/module.h>
#include <sound/sof.h>
+#include "sof-audio.h"
#include "sof-priv.h"
static struct snd_soc_card sof_nocodec_card = {
.name = "nocodec", /* the sof- prefix is added by the core */
+ .topology_shortname = "sof-nocodec",
.owner = THIS_MODULE
};
static int sof_nocodec_bes_setup(struct device *dev,
- const struct snd_sof_dsp_ops *ops,
+ struct snd_soc_dai_driver *drv,
struct snd_soc_dai_link *links,
int link_num, struct snd_soc_card *card)
{
struct snd_soc_dai_link_component *dlc;
int i;
- if (!ops || !links || !card)
+ if (!drv || !links || !card)
return -EINVAL;
/* set up BE dai_links */
for (i = 0; i < link_num; i++) {
- dlc = devm_kzalloc(dev, 3 * sizeof(*dlc), GFP_KERNEL);
+ dlc = devm_kcalloc(dev, 2, sizeof(*dlc), GFP_KERNEL);
if (!dlc)
return -ENOMEM;
@@ -39,9 +41,11 @@ static int sof_nocodec_bes_setup(struct device *dev,
if (!links[i].name)
return -ENOMEM;
+ links[i].stream_name = links[i].name;
+
links[i].cpus = &dlc[0];
- links[i].codecs = &dlc[1];
- links[i].platforms = &dlc[2];
+ links[i].codecs = &snd_soc_dummy_dlc;
+ links[i].platforms = &dlc[1];
links[i].num_cpus = 1;
links[i].num_codecs = 1;
@@ -49,14 +53,14 @@ static int sof_nocodec_bes_setup(struct device *dev,
links[i].id = i;
links[i].no_pcm = 1;
- links[i].cpus->dai_name = ops->drv[i].name;
- links[i].platforms->name = dev_name(dev);
- links[i].codecs->dai_name = "snd-soc-dummy-dai";
- links[i].codecs->name = "snd-soc-dummy";
- if (ops->drv[i].playback.channels_min)
+ links[i].cpus->dai_name = drv[i].name;
+ links[i].platforms->name = dev_name(dev->parent);
+ if (drv[i].playback.channels_min)
links[i].dpcm_playback = 1;
- if (ops->drv[i].capture.channels_min)
+ if (drv[i].capture.channels_min)
links[i].dpcm_capture = 1;
+
+ links[i].be_hw_params_fixup = sof_pcm_dai_link_fixup;
}
card->dai_link = links;
@@ -65,39 +69,40 @@ static int sof_nocodec_bes_setup(struct device *dev,
return 0;
}
-int sof_nocodec_setup(struct device *dev,
- const struct snd_sof_dsp_ops *ops)
+static int sof_nocodec_setup(struct device *dev,
+ u32 num_dai_drivers,
+ struct snd_soc_dai_driver *dai_drivers)
{
struct snd_soc_dai_link *links;
/* create dummy BE dai_links */
- links = devm_kzalloc(dev, sizeof(struct snd_soc_dai_link) *
- ops->num_drv, GFP_KERNEL);
+ links = devm_kcalloc(dev, num_dai_drivers, sizeof(struct snd_soc_dai_link), GFP_KERNEL);
if (!links)
return -ENOMEM;
- return sof_nocodec_bes_setup(dev, ops, links, ops->num_drv,
- &sof_nocodec_card);
+ return sof_nocodec_bes_setup(dev, dai_drivers, links, num_dai_drivers, &sof_nocodec_card);
}
-EXPORT_SYMBOL(sof_nocodec_setup);
static int sof_nocodec_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &sof_nocodec_card;
+ struct snd_soc_acpi_mach *mach;
+ int ret;
card->dev = &pdev->dev;
+ card->topology_shortname_created = true;
+ mach = pdev->dev.platform_data;
- return devm_snd_soc_register_card(&pdev->dev, card);
-}
+ ret = sof_nocodec_setup(card->dev, mach->mach_params.num_dai_drivers,
+ mach->mach_params.dai_drivers);
+ if (ret < 0)
+ return ret;
-static int sof_nocodec_remove(struct platform_device *pdev)
-{
- return 0;
+ return devm_snd_soc_register_card(&pdev->dev, card);
}
static struct platform_driver sof_nocodec_audio = {
.probe = sof_nocodec_probe,
- .remove = sof_nocodec_remove,
.driver = {
.name = "sof-nocodec",
.pm = &snd_soc_pm_ops,
diff --git a/sound/soc/sof/ops.c b/sound/soc/sof/ops.c
index 1a394b4c6a2f..ff066de4ceb9 100644
--- a/sound/soc/sof/ops.c
+++ b/sound/soc/sof/ops.c
@@ -142,22 +142,46 @@ void snd_sof_dsp_update_bits_forced(struct snd_sof_dev *sdev, u32 bar,
}
EXPORT_SYMBOL(snd_sof_dsp_update_bits_forced);
-void snd_sof_dsp_panic(struct snd_sof_dev *sdev, u32 offset)
+/**
+ * snd_sof_dsp_panic - handle a received DSP panic message
+ * @sdev: Pointer to the device's sdev
+ * @offset: offset of panic information
+ * @non_recoverable: the panic is fatal, no recovery will be done by the caller
+ */
+void snd_sof_dsp_panic(struct snd_sof_dev *sdev, u32 offset, bool non_recoverable)
{
- dev_err(sdev->dev, "error : DSP panic!\n");
-
/*
- * check if DSP is not ready and did not set the dsp_oops_offset.
- * if the dsp_oops_offset is not set, set it from the panic message.
- * Also add a check to memory window setting with panic message.
+ * if DSP is not ready and the dsp_oops_offset is not yet set, use the
+ * offset from the panic message.
*/
if (!sdev->dsp_oops_offset)
sdev->dsp_oops_offset = offset;
- else
- dev_dbg(sdev->dev, "panic: dsp_oops_offset %zu offset %d\n",
- sdev->dsp_oops_offset, offset);
- snd_sof_dsp_dbg_dump(sdev, SOF_DBG_REGS | SOF_DBG_MBOX);
- snd_sof_trace_notify_for_error(sdev);
+ /*
+ * Print warning if the offset from the panic message differs from
+ * dsp_oops_offset
+ */
+ if (sdev->dsp_oops_offset != offset)
+ dev_warn(sdev->dev,
+ "%s: dsp_oops_offset %zu differs from panic offset %u\n",
+ __func__, sdev->dsp_oops_offset, offset);
+
+ /*
+ * Set the fw_state to crashed only in case of non recoverable DSP panic
+ * event.
+ * Use different message within the snd_sof_dsp_dbg_dump() depending on
+ * the non_recoverable flag.
+ */
+ sdev->dbg_dump_printed = false;
+ if (non_recoverable) {
+ snd_sof_dsp_dbg_dump(sdev, "DSP panic!",
+ SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX);
+ sof_set_fw_state(sdev, SOF_FW_CRASHED);
+ sof_fw_trace_fw_crashed(sdev);
+ } else {
+ snd_sof_dsp_dbg_dump(sdev,
+ "DSP panic (recovery will be attempted)",
+ SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX);
+ }
}
EXPORT_SYMBOL(snd_sof_dsp_panic);
diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h
index b21632f5511a..6cf21e829e07 100644
--- a/sound/soc/sof/ops.h
+++ b/sound/soc/sof/ops.h
@@ -21,18 +21,52 @@
#define sof_ops(sdev) \
((sdev)->pdata->desc->ops)
+static inline int sof_ops_init(struct snd_sof_dev *sdev)
+{
+ if (sdev->pdata->desc->ops_init)
+ return sdev->pdata->desc->ops_init(sdev);
+
+ return 0;
+}
+
+static inline void sof_ops_free(struct snd_sof_dev *sdev)
+{
+ if (sdev->pdata->desc->ops_free)
+ sdev->pdata->desc->ops_free(sdev);
+}
+
/* Mandatory operations are verified during probing */
/* init */
+static inline int snd_sof_probe_early(struct snd_sof_dev *sdev)
+{
+ if (sof_ops(sdev)->probe_early)
+ return sof_ops(sdev)->probe_early(sdev);
+
+ return 0;
+}
+
static inline int snd_sof_probe(struct snd_sof_dev *sdev)
{
return sof_ops(sdev)->probe(sdev);
}
-static inline int snd_sof_remove(struct snd_sof_dev *sdev)
+static inline void snd_sof_remove(struct snd_sof_dev *sdev)
{
if (sof_ops(sdev)->remove)
- return sof_ops(sdev)->remove(sdev);
+ sof_ops(sdev)->remove(sdev);
+}
+
+static inline void snd_sof_remove_late(struct snd_sof_dev *sdev)
+{
+ if (sof_ops(sdev)->remove_late)
+ sof_ops(sdev)->remove_late(sdev);
+}
+
+static inline int snd_sof_shutdown(struct snd_sof_dev *sdev)
+{
+ if (sof_ops(sdev)->shutdown)
+ return sof_ops(sdev)->shutdown(sdev);
return 0;
}
@@ -48,10 +82,10 @@ static inline int snd_sof_dsp_run(struct snd_sof_dev *sdev)
return sof_ops(sdev)->run(sdev);
}
-static inline int snd_sof_dsp_stall(struct snd_sof_dev *sdev)
+static inline int snd_sof_dsp_stall(struct snd_sof_dev *sdev, unsigned int core_mask)
{
if (sof_ops(sdev)->stall)
- return sof_ops(sdev)->stall(sdev);
+ return sof_ops(sdev)->stall(sdev, core_mask);
return 0;
}
@@ -64,21 +98,66 @@ static inline int snd_sof_dsp_reset(struct snd_sof_dev *sdev)
return 0;
}
-/* dsp core power up/power down */
-static inline int snd_sof_dsp_core_power_up(struct snd_sof_dev *sdev,
- unsigned int core_mask)
+/* dsp core get/put */
+static inline int snd_sof_dsp_core_get(struct snd_sof_dev *sdev, int core)
{
- if (sof_ops(sdev)->core_power_up)
- return sof_ops(sdev)->core_power_up(sdev, core_mask);
+ if (core > sdev->num_cores - 1) {
+ dev_err(sdev->dev, "invalid core id: %d for num_cores: %d\n", core,
+ sdev->num_cores);
+ return -EINVAL;
+ }
+
+ if (sof_ops(sdev)->core_get) {
+ int ret;
+
+ /* if current ref_count is > 0, increment it and return */
+ if (sdev->dsp_core_ref_count[core] > 0) {
+ sdev->dsp_core_ref_count[core]++;
+ return 0;
+ }
+
+ /* power up the core */
+ ret = sof_ops(sdev)->core_get(sdev, core);
+ if (ret < 0)
+ return ret;
+
+ /* increment ref_count */
+ sdev->dsp_core_ref_count[core]++;
+
+ /* and update enabled_cores_mask */
+ sdev->enabled_cores_mask |= BIT(core);
+
+ dev_dbg(sdev->dev, "Core %d powered up\n", core);
+ }
return 0;
}
-static inline int snd_sof_dsp_core_power_down(struct snd_sof_dev *sdev,
- unsigned int core_mask)
+static inline int snd_sof_dsp_core_put(struct snd_sof_dev *sdev, int core)
{
- if (sof_ops(sdev)->core_power_down)
- return sof_ops(sdev)->core_power_down(sdev, core_mask);
+ if (core > sdev->num_cores - 1) {
+ dev_err(sdev->dev, "invalid core id: %d for num_cores: %d\n", core,
+ sdev->num_cores);
+ return -EINVAL;
+ }
+
+ if (sof_ops(sdev)->core_put) {
+ int ret;
+
+ /* decrement ref_count and return if it is > 0 */
+ if (--(sdev->dsp_core_ref_count[core]) > 0)
+ return 0;
+
+ /* power down the core */
+ ret = sof_ops(sdev)->core_put(sdev, core);
+ if (ret < 0)
+ return ret;
+
+ /* and update enabled_cores_mask */
+ sdev->enabled_cores_mask &= ~BIT(core);
+
+ dev_dbg(sdev->dev, "Core %d powered down\n", core);
+ }
return 0;
}
@@ -100,6 +179,16 @@ static inline int snd_sof_dsp_post_fw_run(struct snd_sof_dev *sdev)
return 0;
}
+/* parse platform specific extended manifest */
+static inline int snd_sof_dsp_parse_platform_ext_manifest(struct snd_sof_dev *sdev,
+ const struct sof_ext_man_elem_header *hdr)
+{
+ if (sof_ops(sdev)->parse_platform_ext_manifest)
+ return sof_ops(sdev)->parse_platform_ext_manifest(sdev, hdr);
+
+ return 0;
+}
+
/* misc */
/**
@@ -125,7 +214,7 @@ static inline int snd_sof_dsp_get_mailbox_offset(struct snd_sof_dev *sdev)
return sof_ops(sdev)->get_mailbox_offset(sdev);
dev_err(sdev->dev, "error: %s not defined\n", __func__);
- return -ENOTSUPP;
+ return -EOPNOTSUPP;
}
static inline int snd_sof_dsp_get_window_offset(struct snd_sof_dev *sdev,
@@ -135,7 +224,7 @@ static inline int snd_sof_dsp_get_window_offset(struct snd_sof_dev *sdev,
return sof_ops(sdev)->get_window_offset(sdev, id);
dev_err(sdev->dev, "error: %s not defined\n", __func__);
- return -ENOTSUPP;
+ return -EOPNOTSUPP;
}
/* power management */
static inline int snd_sof_dsp_resume(struct snd_sof_dev *sdev)
@@ -198,47 +287,67 @@ static inline int
snd_sof_dsp_set_power_state(struct snd_sof_dev *sdev,
const struct sof_dsp_power_state *target_state)
{
+ int ret = 0;
+
+ mutex_lock(&sdev->power_state_access);
+
if (sof_ops(sdev)->set_power_state)
- return sof_ops(sdev)->set_power_state(sdev, target_state);
+ ret = sof_ops(sdev)->set_power_state(sdev, target_state);
- /* D0 substate is not supported, do nothing here. */
- return 0;
+ mutex_unlock(&sdev->power_state_access);
+
+ return ret;
}
/* debug */
-static inline void snd_sof_dsp_dbg_dump(struct snd_sof_dev *sdev, u32 flags)
+void snd_sof_dsp_dbg_dump(struct snd_sof_dev *sdev, const char *msg, u32 flags);
+
+static inline int snd_sof_debugfs_add_region_item(struct snd_sof_dev *sdev,
+ enum snd_sof_fw_blk_type blk_type, u32 offset, size_t size,
+ const char *name, enum sof_debugfs_access_type access_type)
{
- if (sof_ops(sdev)->dbg_dump)
- return sof_ops(sdev)->dbg_dump(sdev, flags);
+ if (sof_ops(sdev) && sof_ops(sdev)->debugfs_add_region_item)
+ return sof_ops(sdev)->debugfs_add_region_item(sdev, blk_type, offset,
+ size, name, access_type);
+
+ return 0;
}
-static inline void snd_sof_ipc_dump(struct snd_sof_dev *sdev)
+/* register IO */
+static inline void snd_sof_dsp_write8(struct snd_sof_dev *sdev, u32 bar,
+ u32 offset, u8 value)
{
- if (sof_ops(sdev)->ipc_dump)
- return sof_ops(sdev)->ipc_dump(sdev);
+ if (sof_ops(sdev)->write8)
+ sof_ops(sdev)->write8(sdev, sdev->bar[bar] + offset, value);
+ else
+ writeb(value, sdev->bar[bar] + offset);
}
-/* register IO */
static inline void snd_sof_dsp_write(struct snd_sof_dev *sdev, u32 bar,
u32 offset, u32 value)
{
- if (sof_ops(sdev)->write) {
+ if (sof_ops(sdev)->write)
sof_ops(sdev)->write(sdev, sdev->bar[bar] + offset, value);
- return;
- }
-
- dev_err_ratelimited(sdev->dev, "error: %s not defined\n", __func__);
+ else
+ writel(value, sdev->bar[bar] + offset);
}
static inline void snd_sof_dsp_write64(struct snd_sof_dev *sdev, u32 bar,
u32 offset, u64 value)
{
- if (sof_ops(sdev)->write64) {
+ if (sof_ops(sdev)->write64)
sof_ops(sdev)->write64(sdev, sdev->bar[bar] + offset, value);
- return;
- }
+ else
+ writeq(value, sdev->bar[bar] + offset);
+}
- dev_err_ratelimited(sdev->dev, "error: %s not defined\n", __func__);
+static inline u8 snd_sof_dsp_read8(struct snd_sof_dev *sdev, u32 bar,
+ u32 offset)
+{
+ if (sof_ops(sdev)->read8)
+ return sof_ops(sdev)->read8(sdev, sdev->bar[bar] + offset);
+ else
+ return readb(sdev->bar[bar] + offset);
}
static inline u32 snd_sof_dsp_read(struct snd_sof_dev *sdev, u32 bar,
@@ -246,9 +355,8 @@ static inline u32 snd_sof_dsp_read(struct snd_sof_dev *sdev, u32 bar,
{
if (sof_ops(sdev)->read)
return sof_ops(sdev)->read(sdev, sdev->bar[bar] + offset);
-
- dev_err(sdev->dev, "error: %s not defined\n", __func__);
- return -ENOTSUPP;
+ else
+ return readl(sdev->bar[bar] + offset);
}
static inline u64 snd_sof_dsp_read64(struct snd_sof_dev *sdev, u32 bar,
@@ -256,55 +364,56 @@ static inline u64 snd_sof_dsp_read64(struct snd_sof_dev *sdev, u32 bar,
{
if (sof_ops(sdev)->read64)
return sof_ops(sdev)->read64(sdev, sdev->bar[bar] + offset);
-
- dev_err(sdev->dev, "error: %s not defined\n", __func__);
- return -ENOTSUPP;
+ else
+ return readq(sdev->bar[bar] + offset);
}
-/* block IO */
-static inline void snd_sof_dsp_block_read(struct snd_sof_dev *sdev, u32 bar,
- u32 offset, void *dest, size_t bytes)
+static inline void snd_sof_dsp_update8(struct snd_sof_dev *sdev, u32 bar,
+ u32 offset, u8 mask, u8 value)
{
- sof_ops(sdev)->block_read(sdev, bar, offset, dest, bytes);
+ u8 reg;
+
+ reg = snd_sof_dsp_read8(sdev, bar, offset);
+ reg &= ~mask;
+ reg |= value;
+ snd_sof_dsp_write8(sdev, bar, offset, reg);
}
-static inline void snd_sof_dsp_block_write(struct snd_sof_dev *sdev, u32 bar,
- u32 offset, void *src, size_t bytes)
+/* block IO */
+static inline int snd_sof_dsp_block_read(struct snd_sof_dev *sdev,
+ enum snd_sof_fw_blk_type blk_type,
+ u32 offset, void *dest, size_t bytes)
{
- sof_ops(sdev)->block_write(sdev, bar, offset, src, bytes);
+ return sof_ops(sdev)->block_read(sdev, blk_type, offset, dest, bytes);
}
-/* ipc */
-static inline int snd_sof_dsp_send_msg(struct snd_sof_dev *sdev,
- struct snd_sof_ipc_msg *msg)
+static inline int snd_sof_dsp_block_write(struct snd_sof_dev *sdev,
+ enum snd_sof_fw_blk_type blk_type,
+ u32 offset, void *src, size_t bytes)
{
- return sof_ops(sdev)->send_msg(sdev, msg);
+ return sof_ops(sdev)->block_write(sdev, blk_type, offset, src, bytes);
}
-/* host DMA trace */
-static inline int snd_sof_dma_trace_init(struct snd_sof_dev *sdev,
- u32 *stream_tag)
+/* mailbox IO */
+static inline void snd_sof_dsp_mailbox_read(struct snd_sof_dev *sdev,
+ u32 offset, void *dest, size_t bytes)
{
- if (sof_ops(sdev)->trace_init)
- return sof_ops(sdev)->trace_init(sdev, stream_tag);
-
- return 0;
+ if (sof_ops(sdev)->mailbox_read)
+ sof_ops(sdev)->mailbox_read(sdev, offset, dest, bytes);
}
-static inline int snd_sof_dma_trace_release(struct snd_sof_dev *sdev)
+static inline void snd_sof_dsp_mailbox_write(struct snd_sof_dev *sdev,
+ u32 offset, void *src, size_t bytes)
{
- if (sof_ops(sdev)->trace_release)
- return sof_ops(sdev)->trace_release(sdev);
-
- return 0;
+ if (sof_ops(sdev)->mailbox_write)
+ sof_ops(sdev)->mailbox_write(sdev, offset, src, bytes);
}
-static inline int snd_sof_dma_trace_trigger(struct snd_sof_dev *sdev, int cmd)
+/* ipc */
+static inline int snd_sof_dsp_send_msg(struct snd_sof_dev *sdev,
+ struct snd_sof_ipc_msg *msg)
{
- if (sof_ops(sdev)->trace_trigger)
- return sof_ops(sdev)->trace_trigger(sdev, cmd);
-
- return 0;
+ return sof_ops(sdev)->send_msg(sdev, msg);
}
/* host PCM ops */
@@ -334,11 +443,11 @@ static inline int
snd_sof_pcm_platform_hw_params(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
- struct sof_ipc_stream_params *ipc_params)
+ struct snd_sof_platform_stream_params *platform_params)
{
if (sof_ops(sdev) && sof_ops(sdev)->pcm_hw_params)
- return sof_ops(sdev)->pcm_hw_params(sdev, substream,
- params, ipc_params);
+ return sof_ops(sdev)->pcm_hw_params(sdev, substream, params,
+ platform_params);
return 0;
}
@@ -365,21 +474,32 @@ snd_sof_pcm_platform_trigger(struct snd_sof_dev *sdev,
return 0;
}
-/* host DSP message data */
-static inline void snd_sof_ipc_msg_data(struct snd_sof_dev *sdev,
- struct snd_pcm_substream *substream,
- void *p, size_t sz)
+/* Firmware loading */
+static inline int snd_sof_load_firmware(struct snd_sof_dev *sdev)
{
- sof_ops(sdev)->ipc_msg_data(sdev, substream, p, sz);
+ dev_dbg(sdev->dev, "loading firmware\n");
+
+ return sof_ops(sdev)->load_firmware(sdev);
}
-/* host configure DSP HW parameters */
+/* host DSP message data */
+static inline int snd_sof_ipc_msg_data(struct snd_sof_dev *sdev,
+ struct snd_sof_pcm_stream *sps,
+ void *p, size_t sz)
+{
+ return sof_ops(sdev)->ipc_msg_data(sdev, sps, p, sz);
+}
+/* host side configuration of the stream's data offset in stream mailbox area */
static inline int
-snd_sof_ipc_pcm_params(struct snd_sof_dev *sdev,
- struct snd_pcm_substream *substream,
- const struct sof_ipc_pcm_params_reply *reply)
+snd_sof_set_stream_data_offset(struct snd_sof_dev *sdev,
+ struct snd_sof_pcm_stream *sps,
+ size_t posn_offset)
{
- return sof_ops(sdev)->ipc_pcm_params(sdev, substream, reply);
+ if (sof_ops(sdev) && sof_ops(sdev)->set_stream_data_offset)
+ return sof_ops(sdev)->set_stream_data_offset(sdev, sps,
+ posn_offset);
+
+ return 0;
}
/* host stream pointer */
@@ -393,48 +513,25 @@ snd_sof_pcm_platform_pointer(struct snd_sof_dev *sdev,
return 0;
}
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES)
-static inline int
-snd_sof_probe_compr_assign(struct snd_sof_dev *sdev,
- struct snd_compr_stream *cstream, struct snd_soc_dai *dai)
-{
- return sof_ops(sdev)->probe_assign(sdev, cstream, dai);
-}
-
-static inline int
-snd_sof_probe_compr_free(struct snd_sof_dev *sdev,
- struct snd_compr_stream *cstream, struct snd_soc_dai *dai)
-{
- return sof_ops(sdev)->probe_free(sdev, cstream, dai);
-}
-
-static inline int
-snd_sof_probe_compr_set_params(struct snd_sof_dev *sdev,
- struct snd_compr_stream *cstream,
- struct snd_compr_params *params, struct snd_soc_dai *dai)
+/* pcm ack */
+static inline int snd_sof_pcm_platform_ack(struct snd_sof_dev *sdev,
+ struct snd_pcm_substream *substream)
{
- return sof_ops(sdev)->probe_set_params(sdev, cstream, params, dai);
-}
+ if (sof_ops(sdev) && sof_ops(sdev)->pcm_ack)
+ return sof_ops(sdev)->pcm_ack(sdev, substream);
-static inline int
-snd_sof_probe_compr_trigger(struct snd_sof_dev *sdev,
- struct snd_compr_stream *cstream, int cmd,
- struct snd_soc_dai *dai)
-{
- return sof_ops(sdev)->probe_trigger(sdev, cstream, cmd, dai);
+ return 0;
}
-static inline int
-snd_sof_probe_compr_pointer(struct snd_sof_dev *sdev,
- struct snd_compr_stream *cstream,
- struct snd_compr_tstamp *tstamp, struct snd_soc_dai *dai)
+static inline u64 snd_sof_pcm_get_stream_position(struct snd_sof_dev *sdev,
+ struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
{
- if (sof_ops(sdev) && sof_ops(sdev)->probe_pointer)
- return sof_ops(sdev)->probe_pointer(sdev, cstream, tstamp, dai);
+ if (sof_ops(sdev) && sof_ops(sdev)->get_stream_position)
+ return sof_ops(sdev)->get_stream_position(sdev, component, substream);
return 0;
}
-#endif
/* machine driver */
static inline int
@@ -453,36 +550,30 @@ snd_sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata)
sof_ops(sdev)->machine_unregister(sdev, pdata);
}
-static inline void
+static inline struct snd_soc_acpi_mach *
snd_sof_machine_select(struct snd_sof_dev *sdev)
{
if (sof_ops(sdev) && sof_ops(sdev)->machine_select)
- sof_ops(sdev)->machine_select(sdev);
+ return sof_ops(sdev)->machine_select(sdev);
+
+ return NULL;
}
static inline void
-snd_sof_set_mach_params(const struct snd_soc_acpi_mach *mach,
- struct device *dev)
+snd_sof_set_mach_params(struct snd_soc_acpi_mach *mach,
+ struct snd_sof_dev *sdev)
{
- struct snd_sof_dev *sdev = dev_get_drvdata(dev);
-
if (sof_ops(sdev) && sof_ops(sdev)->set_mach_params)
- sof_ops(sdev)->set_mach_params(mach, dev);
+ sof_ops(sdev)->set_mach_params(mach, sdev);
}
-static inline const struct snd_sof_dsp_ops
-*sof_get_ops(const struct sof_dev_desc *d,
- const struct sof_ops_table mach_ops[], int asize)
+static inline bool
+snd_sof_is_chain_dma_supported(struct snd_sof_dev *sdev, u32 dai_type)
{
- int i;
+ if (sof_ops(sdev) && sof_ops(sdev)->is_chain_dma_supported)
+ return sof_ops(sdev)->is_chain_dma_supported(sdev, dai_type);
- for (i = 0; i < asize; i++) {
- if (d == mach_ops[i].desc)
- return mach_ops[i].ops;
- }
-
- /* not found */
- return NULL;
+ return false;
}
/**
@@ -513,14 +604,16 @@ static inline const struct snd_sof_dsp_ops
(val) = snd_sof_dsp_read(sdev, bar, offset); \
if (cond) { \
dev_dbg(sdev->dev, \
- "FW Poll Status: reg=%#x successful\n", (val)); \
+ "FW Poll Status: reg[%#x]=%#x successful\n", \
+ (offset), (val)); \
break; \
} \
if (__timeout_us && \
ktime_compare(ktime_get(), __timeout) > 0) { \
(val) = snd_sof_dsp_read(sdev, bar, offset); \
dev_dbg(sdev->dev, \
- "FW Poll Status: reg=%#x timedout\n", (val)); \
+ "FW Poll Status: reg[%#x]=%#x timedout\n", \
+ (offset), (val)); \
break; \
} \
if (__sleep_us) \
@@ -552,5 +645,5 @@ int snd_sof_dsp_register_poll(struct snd_sof_dev *sdev, u32 bar, u32 offset,
u32 mask, u32 target, u32 timeout_ms,
u32 interval_us);
-void snd_sof_dsp_panic(struct snd_sof_dev *sdev, u32 offset);
+void snd_sof_dsp_panic(struct snd_sof_dev *sdev, u32 offset, bool non_recoverable);
#endif
diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c
index 71c3f29057a7..33d576b17647 100644
--- a/sound/soc/sof/pcm.c
+++ b/sound/soc/sof/pcm.c
@@ -13,19 +13,19 @@
#include <linux/pm_runtime.h>
#include <sound/pcm_params.h>
#include <sound/sof.h>
+#include <trace/events/sof.h>
+#include "sof-of-dev.h"
#include "sof-priv.h"
#include "sof-audio.h"
+#include "sof-utils.h"
#include "ops.h"
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES)
-#include "compress.h"
-#endif
/* Create DMA buffer page table for DSP */
static int create_page_table(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
unsigned char *dma_area, size_t size)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_sof_pcm *spcm;
struct snd_dma_buffer *dmab = snd_pcm_get_dma_buf(substream);
int stream = substream->stream;
@@ -38,26 +38,10 @@ static int create_page_table(struct snd_soc_component *component,
spcm->stream[stream].page_table.area, size);
}
-static int sof_pcm_dsp_params(struct snd_sof_pcm *spcm, struct snd_pcm_substream *substream,
- const struct sof_ipc_pcm_params_reply *reply)
-{
- struct snd_soc_component *scomp = spcm->scomp;
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
-
- /* validate offset */
- int ret = snd_sof_ipc_pcm_params(sdev, substream, reply);
-
- if (ret < 0)
- dev_err(scomp->dev, "error: got wrong reply for PCM %d\n",
- spcm->pcm.pcm_id);
-
- return ret;
-}
-
/*
* sof pcm period elapse work
*/
-void snd_sof_pcm_period_elapsed_work(struct work_struct *work)
+static void snd_sof_pcm_period_elapsed_work(struct work_struct *work)
{
struct snd_sof_pcm_stream *sps =
container_of(work, struct snd_sof_pcm_stream,
@@ -66,12 +50,17 @@ void snd_sof_pcm_period_elapsed_work(struct work_struct *work)
snd_pcm_period_elapsed(sps->substream);
}
+void snd_sof_pcm_init_elapsed_work(struct work_struct *work)
+{
+ INIT_WORK(work, snd_sof_pcm_period_elapsed_work);
+}
+
/*
* sof pcm period elapse, this could be called at irq thread context.
*/
void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_soc_component *component =
snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME);
struct snd_sof_pcm *spcm;
@@ -95,37 +84,51 @@ void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream)
}
EXPORT_SYMBOL(snd_sof_pcm_period_elapsed);
-static int sof_pcm_dsp_pcm_free(struct snd_pcm_substream *substream,
- struct snd_sof_dev *sdev,
- struct snd_sof_pcm *spcm)
+static int
+sof_pcm_setup_connected_widgets(struct snd_sof_dev *sdev, struct snd_soc_pcm_runtime *rtd,
+ struct snd_sof_pcm *spcm, struct snd_pcm_hw_params *params,
+ struct snd_sof_platform_stream_params *platform_params, int dir)
{
- struct sof_ipc_stream stream;
- struct sof_ipc_reply reply;
- int ret;
+ struct snd_soc_dai *dai;
+ int ret, j;
- stream.hdr.size = sizeof(stream);
- stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_FREE;
- stream.comp_id = spcm->stream[substream->stream].comp_id;
+ /* query DAPM for list of connected widgets and set them up */
+ for_each_rtd_cpu_dais(rtd, j, dai) {
+ struct snd_soc_dapm_widget_list *list;
- /* send IPC to the DSP */
- ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream,
- sizeof(stream), &reply, sizeof(reply));
- if (!ret)
- spcm->prepared[substream->stream] = false;
+ ret = snd_soc_dapm_dai_get_connected_widgets(dai, dir, &list,
+ dpcm_end_walk_at_be);
+ if (ret < 0) {
+ dev_err(sdev->dev, "error: dai %s has no valid %s path\n", dai->name,
+ dir == SNDRV_PCM_STREAM_PLAYBACK ? "playback" : "capture");
+ return ret;
+ }
- return ret;
+ spcm->stream[dir].list = list;
+
+ ret = sof_widget_list_setup(sdev, spcm, params, platform_params, dir);
+ if (ret < 0) {
+ dev_err(sdev->dev, "error: failed widget list set up for pcm %d dir %d\n",
+ spcm->pcm.pcm_id, dir);
+ spcm->stream[dir].list = NULL;
+ snd_soc_dapm_dai_free_widgets(&list);
+ return ret;
+ }
+ }
+
+ return 0;
}
static int sof_pcm_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
- struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ const struct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm);
+ struct snd_sof_platform_stream_params platform_params = { 0 };
+ struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_sof_pcm *spcm;
- struct sof_ipc_pcm_params pcm;
- struct sof_ipc_pcm_params_reply ipc_params_reply;
int ret;
/* nothing to do for BE */
@@ -140,105 +143,60 @@ static int sof_pcm_hw_params(struct snd_soc_component *component,
* Handle repeated calls to hw_params() without free_pcm() in
* between. At least ALSA OSS emulation depends on this.
*/
- if (spcm->prepared[substream->stream]) {
- ret = sof_pcm_dsp_pcm_free(substream, sdev, spcm);
+ if (pcm_ops && pcm_ops->hw_free && spcm->prepared[substream->stream]) {
+ ret = pcm_ops->hw_free(component, substream);
if (ret < 0)
return ret;
+
+ spcm->prepared[substream->stream] = false;
}
dev_dbg(component->dev, "pcm: hw params stream %d dir %d\n",
spcm->pcm.pcm_id, substream->stream);
- memset(&pcm, 0, sizeof(pcm));
+ ret = snd_sof_pcm_platform_hw_params(sdev, substream, params, &platform_params);
+ if (ret < 0) {
+ dev_err(component->dev, "platform hw params failed\n");
+ return ret;
+ }
+
+ /* if this is a repeated hw_params without hw_free, skip setting up widgets */
+ if (!spcm->stream[substream->stream].list) {
+ ret = sof_pcm_setup_connected_widgets(sdev, rtd, spcm, params, &platform_params,
+ substream->stream);
+ if (ret < 0)
+ return ret;
+ }
/* create compressed page table for audio firmware */
if (runtime->buffer_changed) {
ret = create_page_table(component, substream, runtime->dma_area,
runtime->dma_bytes);
+
if (ret < 0)
return ret;
}
- /* number of pages should be rounded up */
- pcm.params.buffer.pages = PFN_UP(runtime->dma_bytes);
-
- /* set IPC PCM parameters */
- pcm.hdr.size = sizeof(pcm);
- pcm.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS;
- pcm.comp_id = spcm->stream[substream->stream].comp_id;
- pcm.params.hdr.size = sizeof(pcm.params);
- pcm.params.buffer.phy_addr =
- spcm->stream[substream->stream].page_table.addr;
- pcm.params.buffer.size = runtime->dma_bytes;
- pcm.params.direction = substream->stream;
- pcm.params.sample_valid_bytes = params_width(params) >> 3;
- pcm.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED;
- pcm.params.rate = params_rate(params);
- pcm.params.channels = params_channels(params);
- pcm.params.host_period_bytes = params_period_bytes(params);
-
- /* container size */
- ret = snd_pcm_format_physical_width(params_format(params));
- if (ret < 0)
- return ret;
- pcm.params.sample_container_bytes = ret >> 3;
-
- /* format */
- switch (params_format(params)) {
- case SNDRV_PCM_FORMAT_S16:
- pcm.params.frame_fmt = SOF_IPC_FRAME_S16_LE;
- break;
- case SNDRV_PCM_FORMAT_S24:
- pcm.params.frame_fmt = SOF_IPC_FRAME_S24_4LE;
- break;
- case SNDRV_PCM_FORMAT_S32:
- pcm.params.frame_fmt = SOF_IPC_FRAME_S32_LE;
- break;
- case SNDRV_PCM_FORMAT_FLOAT:
- pcm.params.frame_fmt = SOF_IPC_FRAME_FLOAT;
- break;
- default:
- return -EINVAL;
- }
-
- /* firmware already configured host stream */
- ret = snd_sof_pcm_platform_hw_params(sdev,
- substream,
- params,
- &pcm.params);
- if (ret < 0) {
- dev_err(component->dev, "error: platform hw params failed\n");
- return ret;
- }
-
- dev_dbg(component->dev, "stream_tag %d", pcm.params.stream_tag);
-
- /* send IPC to the DSP */
- ret = sof_ipc_tx_message(sdev->ipc, pcm.hdr.cmd, &pcm, sizeof(pcm),
- &ipc_params_reply, sizeof(ipc_params_reply));
- if (ret < 0) {
- dev_err(component->dev, "error: hw params ipc failed for stream %d\n",
- pcm.params.stream_tag);
- return ret;
+ if (pcm_ops && pcm_ops->hw_params) {
+ ret = pcm_ops->hw_params(component, substream, params, &platform_params);
+ if (ret < 0)
+ return ret;
}
- ret = sof_pcm_dsp_params(spcm, substream, &ipc_params_reply);
- if (ret < 0)
- return ret;
-
spcm->prepared[substream->stream] = true;
/* save pcm hw_params */
memcpy(&spcm->params[substream->stream], params, sizeof(*params));
- return ret;
+ return 0;
}
static int sof_pcm_hw_free(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+ const struct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm);
struct snd_sof_pcm *spcm;
int ret, err = 0;
@@ -254,26 +212,41 @@ static int sof_pcm_hw_free(struct snd_soc_component *component,
spcm->pcm.pcm_id, substream->stream);
if (spcm->prepared[substream->stream]) {
- ret = sof_pcm_dsp_pcm_free(substream, sdev, spcm);
- if (ret < 0)
- err = ret;
- }
+ /* stop DMA first if needed */
+ if (pcm_ops && pcm_ops->platform_stop_during_hw_free)
+ snd_sof_pcm_platform_trigger(sdev, substream, SNDRV_PCM_TRIGGER_STOP);
+
+ /* free PCM in the DSP */
+ if (pcm_ops && pcm_ops->hw_free) {
+ ret = pcm_ops->hw_free(component, substream);
+ if (ret < 0)
+ err = ret;
+ }
- cancel_work_sync(&spcm->stream[substream->stream].period_elapsed_work);
+ spcm->prepared[substream->stream] = false;
+ }
+ /* reset DMA */
ret = snd_sof_pcm_platform_hw_free(sdev, substream);
if (ret < 0) {
dev_err(component->dev, "error: platform hw free failed\n");
err = ret;
}
+ /* free the DAPM widget list */
+ ret = sof_widget_list_free(sdev, spcm, substream->stream);
+ if (ret < 0)
+ err = ret;
+
+ cancel_work_sync(&spcm->stream[substream->stream].period_elapsed_work);
+
return err;
}
static int sof_pcm_prepare(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_sof_pcm *spcm;
int ret;
@@ -310,14 +283,13 @@ static int sof_pcm_prepare(struct snd_soc_component *component,
static int sof_pcm_trigger(struct snd_soc_component *component,
struct snd_pcm_substream *substream, int cmd)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+ const struct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm);
struct snd_sof_pcm *spcm;
- struct sof_ipc_stream stream;
- struct sof_ipc_reply reply;
bool reset_hw_params = false;
bool ipc_first = false;
- int ret;
+ int ret = 0;
/* nothing to do for BE */
if (rtd->dai_link->no_pcm)
@@ -330,38 +302,14 @@ static int sof_pcm_trigger(struct snd_soc_component *component,
dev_dbg(component->dev, "pcm: trigger stream %d dir %d cmd %d\n",
spcm->pcm.pcm_id, substream->stream, cmd);
- stream.hdr.size = sizeof(stream);
- stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG;
- stream.comp_id = spcm->stream[substream->stream].comp_id;
-
switch (cmd) {
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
- stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_PAUSE;
ipc_first = true;
break;
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_RELEASE;
+ if (pcm_ops && pcm_ops->ipc_first_on_start)
+ ipc_first = true;
break;
- case SNDRV_PCM_TRIGGER_RESUME:
- if (spcm->stream[substream->stream].suspend_ignored) {
- /*
- * this case will be triggered when INFO_RESUME is
- * supported, no need to resume streams that remained
- * enabled in D0ix.
- */
- spcm->stream[substream->stream].suspend_ignored = false;
- return 0;
- }
-
- /* set up hw_params */
- ret = sof_pcm_prepare(component, substream);
- if (ret < 0) {
- dev_err(component->dev,
- "error: failed to set up hw_params upon resume\n");
- return ret;
- }
-
- fallthrough;
case SNDRV_PCM_TRIGGER_START:
if (spcm->stream[substream->stream].suspend_ignored) {
/*
@@ -372,7 +320,9 @@ static int sof_pcm_trigger(struct snd_soc_component *component,
spcm->stream[substream->stream].suspend_ignored = false;
return 0;
}
- stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_START;
+
+ if (pcm_ops && pcm_ops->ipc_first_on_start)
+ ipc_first = true;
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
if (sdev->system_suspend_target == SOF_SUSPEND_S0IX &&
@@ -386,36 +336,49 @@ static int sof_pcm_trigger(struct snd_soc_component *component,
spcm->stream[substream->stream].suspend_ignored = true;
return 0;
}
+
+ /* On suspend the DMA must be stopped in DSPless mode */
+ if (sdev->dspless_mode_selected)
+ reset_hw_params = true;
+
fallthrough;
case SNDRV_PCM_TRIGGER_STOP:
- stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_STOP;
ipc_first = true;
- reset_hw_params = true;
+ if (pcm_ops && pcm_ops->reset_hw_params_during_stop)
+ reset_hw_params = true;
break;
default:
- dev_err(component->dev, "error: unhandled trigger cmd %d\n",
- cmd);
+ dev_err(component->dev, "Unhandled trigger cmd %d\n", cmd);
return -EINVAL;
}
- /*
- * DMA and IPC sequence is different for start and stop. Need to send
- * STOP IPC before stop DMA
- */
if (!ipc_first)
snd_sof_pcm_platform_trigger(sdev, substream, cmd);
- /* send IPC to the DSP */
- ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream,
- sizeof(stream), &reply, sizeof(reply));
+ if (pcm_ops && pcm_ops->trigger)
+ ret = pcm_ops->trigger(component, substream, cmd);
- /* need to STOP DMA even if STOP IPC failed */
- if (ipc_first)
- snd_sof_pcm_platform_trigger(sdev, substream, cmd);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_START:
+ /* invoke platform trigger to start DMA only if pcm_ops is successful */
+ if (ipc_first && !ret)
+ snd_sof_pcm_platform_trigger(sdev, substream, cmd);
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_STOP:
+ /* invoke platform trigger to stop DMA even if pcm_ops isn't set or if it failed */
+ if (!pcm_ops || !pcm_ops->platform_stop_during_hw_free)
+ snd_sof_pcm_platform_trigger(sdev, substream, cmd);
+ break;
+ default:
+ break;
+ }
/* free PCM if reset_hw_params is set and the STOP IPC is successful */
if (!ret && reset_hw_params)
- ret = sof_pcm_dsp_pcm_free(substream, sdev, spcm);
+ ret = sof_pcm_stream_free(sdev, substream, spcm, substream->stream, false);
return ret;
}
@@ -423,7 +386,7 @@ static int sof_pcm_trigger(struct snd_soc_component *component,
static snd_pcm_uframes_t sof_pcm_pointer(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
struct snd_sof_pcm *spcm;
snd_pcm_uframes_t host, dai;
@@ -446,9 +409,7 @@ static snd_pcm_uframes_t sof_pcm_pointer(struct snd_soc_component *component,
dai = bytes_to_frames(substream->runtime,
spcm->stream[substream->stream].posn.dai_posn);
- dev_dbg(component->dev,
- "PCM: stream %d dir %d DMA position %lu DAI position %lu\n",
- spcm->pcm.pcm_id, substream->stream, host, dai);
+ trace_sof_pcm_pointer_position(sdev, spcm, substream, host, dai);
return host;
}
@@ -456,10 +417,10 @@ static snd_pcm_uframes_t sof_pcm_pointer(struct snd_soc_component *component,
static int sof_pcm_open(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
- const struct snd_sof_dsp_ops *ops = sof_ops(sdev);
+ struct snd_sof_dsp_ops *ops = sof_ops(sdev);
struct snd_sof_pcm *spcm;
struct snd_soc_tplg_stream_caps *caps;
int ret;
@@ -478,17 +439,10 @@ static int sof_pcm_open(struct snd_soc_component *component,
caps = &spcm->pcm.caps[substream->stream];
- /* set any runtime constraints based on topology */
- snd_pcm_hw_constraint_step(substream->runtime, 0,
- SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
- le32_to_cpu(caps->period_size_min));
- snd_pcm_hw_constraint_step(substream->runtime, 0,
- SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
- le32_to_cpu(caps->period_size_min));
-
/* set runtime config */
runtime->hw.info = ops->hw_info; /* platform-specific */
+ /* set any runtime constraints based on topology */
runtime->hw.formats = le64_to_cpu(caps->formats);
runtime->hw.period_bytes_min = le32_to_cpu(caps->period_size_min);
runtime->hw.period_bytes_max = le32_to_cpu(caps->period_size_max);
@@ -528,7 +482,7 @@ static int sof_pcm_open(struct snd_soc_component *component,
static int sof_pcm_close(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
struct snd_sof_pcm *spcm;
int err;
@@ -627,8 +581,7 @@ capture:
}
/* fixup the BE DAI link to match any values from topology */
-static int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd,
- struct snd_pcm_hw_params *params)
+int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params)
{
struct snd_interval *rate = hw_param_interval(params,
SNDRV_PCM_HW_PARAM_RATE);
@@ -639,7 +592,8 @@ static int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd,
snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME);
struct snd_sof_dai *dai =
snd_sof_find_dai(component, (char *)rtd->dai_link->name);
- struct snd_soc_dpcm *dpcm;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+ const struct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm);
/* no topology exists for this BE, try a common configuration */
if (!dai) {
@@ -660,95 +614,12 @@ static int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd,
return 0;
}
- /* read format from topology */
- snd_mask_none(fmt);
-
- switch (dai->comp_dai.config.frame_fmt) {
- case SOF_IPC_FRAME_S16_LE:
- snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE);
- break;
- case SOF_IPC_FRAME_S24_4LE:
- snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE);
- break;
- case SOF_IPC_FRAME_S32_LE:
- snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE);
- break;
- default:
- dev_err(component->dev, "error: No available DAI format!\n");
- return -EINVAL;
- }
-
- /* read rate and channels from topology */
- switch (dai->dai_config->type) {
- case SOF_DAI_INTEL_SSP:
- rate->min = dai->dai_config->ssp.fsync_rate;
- rate->max = dai->dai_config->ssp.fsync_rate;
- channels->min = dai->dai_config->ssp.tdm_slots;
- channels->max = dai->dai_config->ssp.tdm_slots;
-
- dev_dbg(component->dev,
- "rate_min: %d rate_max: %d\n", rate->min, rate->max);
- dev_dbg(component->dev,
- "channels_min: %d channels_max: %d\n",
- channels->min, channels->max);
-
- break;
- case SOF_DAI_INTEL_DMIC:
- /* DMIC only supports 16 or 32 bit formats */
- if (dai->comp_dai.config.frame_fmt == SOF_IPC_FRAME_S24_4LE) {
- dev_err(component->dev,
- "error: invalid fmt %d for DAI type %d\n",
- dai->comp_dai.config.frame_fmt,
- dai->dai_config->type);
- }
- break;
- case SOF_DAI_INTEL_HDA:
- /*
- * HDaudio does not follow the default trigger
- * sequence due to firmware implementation
- */
- for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_PLAYBACK, dpcm) {
- struct snd_soc_pcm_runtime *fe = dpcm->fe;
-
- fe->dai_link->trigger[SNDRV_PCM_STREAM_PLAYBACK] =
- SND_SOC_DPCM_TRIGGER_POST;
- }
- break;
- case SOF_DAI_INTEL_ALH:
- /* do nothing for ALH dai_link */
- break;
- case SOF_DAI_IMX_ESAI:
- rate->min = dai->dai_config->esai.fsync_rate;
- rate->max = dai->dai_config->esai.fsync_rate;
- channels->min = dai->dai_config->esai.tdm_slots;
- channels->max = dai->dai_config->esai.tdm_slots;
-
- dev_dbg(component->dev,
- "rate_min: %d rate_max: %d\n", rate->min, rate->max);
- dev_dbg(component->dev,
- "channels_min: %d channels_max: %d\n",
- channels->min, channels->max);
- break;
- case SOF_DAI_IMX_SAI:
- rate->min = dai->dai_config->sai.fsync_rate;
- rate->max = dai->dai_config->sai.fsync_rate;
- channels->min = dai->dai_config->sai.tdm_slots;
- channels->max = dai->dai_config->sai.tdm_slots;
-
- dev_dbg(component->dev,
- "rate_min: %d rate_max: %d\n", rate->min, rate->max);
- dev_dbg(component->dev,
- "channels_min: %d channels_max: %d\n",
- channels->min, channels->max);
- break;
- default:
- dev_err(component->dev, "error: invalid DAI type %d\n",
- dai->dai_config->type);
- break;
- }
+ if (pcm_ops && pcm_ops->dai_link_fixup)
+ return pcm_ops->dai_link_fixup(rtd, params);
return 0;
}
+EXPORT_SYMBOL(sof_pcm_dai_link_fixup);
static int sof_pcm_probe(struct snd_soc_component *component)
{
@@ -757,6 +628,14 @@ static int sof_pcm_probe(struct snd_soc_component *component)
const char *tplg_filename;
int ret;
+ /*
+ * make sure the device is pm_runtime_active before loading the
+ * topology and initiating IPC or bus transactions
+ */
+ ret = pm_runtime_resume_and_get(component->dev);
+ if (ret < 0 && ret != -EACCES)
+ return ret;
+
/* load the default topology */
sdev->component = component;
@@ -764,15 +643,19 @@ static int sof_pcm_probe(struct snd_soc_component *component)
"%s/%s",
plat_data->tplg_filename_prefix,
plat_data->tplg_filename);
- if (!tplg_filename)
- return -ENOMEM;
+ if (!tplg_filename) {
+ ret = -ENOMEM;
+ goto pm_error;
+ }
ret = snd_sof_load_topology(component, tplg_filename);
- if (ret < 0) {
+ if (ret < 0)
dev_err(component->dev, "error: failed to load DSP topology %d\n",
ret);
- return ret;
- }
+
+pm_error:
+ pm_runtime_mark_last_busy(component->dev);
+ pm_runtime_put_autosuspend(component->dev);
return ret;
}
@@ -780,7 +663,27 @@ static int sof_pcm_probe(struct snd_soc_component *component)
static void sof_pcm_remove(struct snd_soc_component *component)
{
/* remove topology */
- snd_soc_tplg_component_remove(component, SND_SOC_TPLG_INDEX_ALL);
+ snd_soc_tplg_component_remove(component);
+}
+
+static int sof_pcm_ack(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+
+ return snd_sof_pcm_platform_ack(sdev, substream);
+}
+
+static snd_pcm_sframes_t sof_pcm_delay(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+ const struct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm);
+
+ if (pcm_ops && pcm_ops->delay)
+ return pcm_ops->delay(component, substream);
+
+ return 0;
}
void snd_sof_new_platform_drv(struct snd_sof_dev *sdev)
@@ -789,7 +692,12 @@ void snd_sof_new_platform_drv(struct snd_sof_dev *sdev)
struct snd_sof_pdata *plat_data = sdev->pdata;
const char *drv_name;
- drv_name = plat_data->machine->drv_name;
+ if (plat_data->machine)
+ drv_name = plat_data->machine->drv_name;
+ else if (plat_data->of_machine)
+ drv_name = plat_data->of_machine->drv_name;
+ else
+ drv_name = NULL;
pd->name = "sof-audio-component";
pd->probe = sof_pcm_probe;
@@ -801,21 +709,28 @@ void snd_sof_new_platform_drv(struct snd_sof_dev *sdev)
pd->hw_free = sof_pcm_hw_free;
pd->trigger = sof_pcm_trigger;
pd->pointer = sof_pcm_pointer;
+ pd->ack = sof_pcm_ack;
+ pd->delay = sof_pcm_delay;
#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS)
pd->compress_ops = &sof_compressed_ops;
#endif
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES)
- /* override cops when probe support is enabled */
- pd->compress_ops = &sof_probe_compressed_ops;
-#endif
+
pd->pcm_construct = sof_pcm_new;
pd->ignore_machine = drv_name;
- pd->be_hw_params_fixup = sof_pcm_dai_link_fixup;
pd->be_pcm_base = SOF_BE_PCM_BASE;
pd->use_dai_pcm_id = true;
pd->topology_name_prefix = "sof";
/* increment module refcount when a pcm is opened */
pd->module_get_upon_open = 1;
+
+ pd->legacy_dai_naming = 1;
+
+ /*
+ * The fixup is only needed when the DSP is in use as with the DSPless
+ * mode we are directly using the audio interface
+ */
+ if (!sdev->dspless_mode_selected)
+ pd->be_hw_params_fixup = sof_pcm_dai_link_fixup;
}
diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c
index 92e5f9b15f3a..704b21413c71 100644
--- a/sound/soc/sof/pm.c
+++ b/sound/soc/sof/pm.c
@@ -23,6 +23,9 @@ static u32 snd_sof_dsp_power_target(struct snd_sof_dev *sdev)
u32 target_dsp_state;
switch (sdev->system_suspend_target) {
+ case SOF_SUSPEND_S5:
+ case SOF_SUSPEND_S4:
+ /* DSP should be in D3 if the system is suspending to S3+ */
case SOF_SUSPEND_S3:
/* DSP should be in D3 if the system is suspending to S3 */
target_dsp_state = SOF_DSP_PM_D3;
@@ -48,22 +51,6 @@ static u32 snd_sof_dsp_power_target(struct snd_sof_dev *sdev)
return target_dsp_state;
}
-static int sof_send_pm_ctx_ipc(struct snd_sof_dev *sdev, int cmd)
-{
- struct sof_ipc_pm_ctx pm_ctx;
- struct sof_ipc_reply reply;
-
- memset(&pm_ctx, 0, sizeof(pm_ctx));
-
- /* configure ctx save ipc message */
- pm_ctx.hdr.size = sizeof(pm_ctx);
- pm_ctx.hdr.cmd = SOF_IPC_GLB_PM_MSG | cmd;
-
- /* send ctx save ipc to dsp */
- return sof_ipc_tx_message(sdev->ipc, pm_ctx.hdr.cmd, &pm_ctx,
- sizeof(pm_ctx), &reply, sizeof(reply));
-}
-
#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE)
static void sof_cache_debugfs(struct snd_sof_dev *sdev)
{
@@ -86,6 +73,8 @@ static void sof_cache_debugfs(struct snd_sof_dev *sdev)
static int sof_resume(struct device *dev, bool runtime_resume)
{
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
+ const struct sof_ipc_pm_ops *pm_ops = sof_ipc_get_ops(sdev, pm);
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
u32 old_state = sdev->dsp_power_state.state;
int ret;
@@ -114,15 +103,27 @@ static int sof_resume(struct device *dev, bool runtime_resume)
return ret;
}
+ if (sdev->dspless_mode_selected) {
+ sof_set_fw_state(sdev, SOF_DSPLESS_MODE);
+ return 0;
+ }
+
/*
* Nothing further to be done for platforms that support the low power
- * D0 substate.
+ * D0 substate. Resume trace and return when resuming from
+ * low-power D0 substate
*/
if (!runtime_resume && sof_ops(sdev)->set_power_state &&
- old_state == SOF_DSP_PM_D0)
+ old_state == SOF_DSP_PM_D0) {
+ ret = sof_fw_trace_resume(sdev);
+ if (ret < 0)
+ /* non fatal */
+ dev_warn(sdev->dev,
+ "failed to enable trace after resume %d\n", ret);
return 0;
+ }
- sdev->fw_state = SOF_FW_BOOT_PREPARE;
+ sof_set_fw_state(sdev, SOF_FW_BOOT_PREPARE);
/* load the firmware */
ret = snd_sof_load_firmware(sdev);
@@ -130,10 +131,11 @@ static int sof_resume(struct device *dev, bool runtime_resume)
dev_err(sdev->dev,
"error: failed to load DSP firmware after resume %d\n",
ret);
+ sof_set_fw_state(sdev, SOF_FW_BOOT_FAILED);
return ret;
}
- sdev->fw_state = SOF_FW_BOOT_IN_PROGRESS;
+ sof_set_fw_state(sdev, SOF_FW_BOOT_IN_PROGRESS);
/*
* Boot the firmware. The FW boot status will be modified
@@ -144,11 +146,12 @@ static int sof_resume(struct device *dev, bool runtime_resume)
dev_err(sdev->dev,
"error: failed to boot DSP firmware after resume %d\n",
ret);
+ sof_set_fw_state(sdev, SOF_FW_BOOT_FAILED);
return ret;
}
- /* resume DMA trace, only need send ipc */
- ret = snd_sof_init_trace_ipc(sdev);
+ /* resume DMA trace */
+ ret = sof_fw_trace_resume(sdev);
if (ret < 0) {
/* non fatal */
dev_warn(sdev->dev,
@@ -157,20 +160,35 @@ static int sof_resume(struct device *dev, bool runtime_resume)
}
/* restore pipelines */
- ret = sof_restore_pipelines(sdev->dev);
- if (ret < 0) {
- dev_err(sdev->dev,
- "error: failed to restore pipeline after resume %d\n",
- ret);
- return ret;
+ if (tplg_ops && tplg_ops->set_up_all_pipelines) {
+ ret = tplg_ops->set_up_all_pipelines(sdev, false);
+ if (ret < 0) {
+ dev_err(sdev->dev, "Failed to restore pipeline after resume %d\n", ret);
+ goto setup_fail;
+ }
}
+ /* Notify clients not managed by pm framework about core resume */
+ sof_resume_clients(sdev);
+
/* notify DSP of system resume */
- ret = sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_RESTORE);
- if (ret < 0)
- dev_err(sdev->dev,
- "error: ctx_restore ipc error during resume %d\n",
- ret);
+ if (pm_ops && pm_ops->ctx_restore) {
+ ret = pm_ops->ctx_restore(sdev);
+ if (ret < 0)
+ dev_err(sdev->dev, "ctx_restore IPC error during resume: %d\n", ret);
+ }
+
+setup_fail:
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE)
+ if (ret < 0) {
+ /*
+ * Debugfs cannot be read in runtime suspend, so cache
+ * the contents upon failure. This allows to capture
+ * possible DSP coredump information.
+ */
+ sof_cache_debugfs(sdev);
+ }
+#endif
return ret;
}
@@ -178,7 +196,11 @@ static int sof_resume(struct device *dev, bool runtime_resume)
static int sof_suspend(struct device *dev, bool runtime_suspend)
{
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
- u32 target_state = 0;
+ const struct sof_ipc_pm_ops *pm_ops = sof_ipc_get_ops(sdev, pm);
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
+ pm_message_t pm_state;
+ u32 target_state = snd_sof_dsp_power_target(sdev);
+ u32 old_state = sdev->dsp_power_state.state;
int ret;
/* do nothing if dsp suspend callback is not set */
@@ -188,12 +210,20 @@ static int sof_suspend(struct device *dev, bool runtime_suspend)
if (runtime_suspend && !sof_ops(sdev)->runtime_suspend)
return 0;
+ /* we need to tear down pipelines only if the DSP hardware is
+ * active, which happens for PCI devices. if the device is
+ * suspended, it is brought back to full power and then
+ * suspended again
+ */
+ if (tplg_ops && tplg_ops->tear_down_all_pipelines && (old_state == SOF_DSP_PM_D0))
+ tplg_ops->tear_down_all_pipelines(sdev, false);
+
if (sdev->fw_state != SOF_FW_BOOT_COMPLETE)
goto suspend;
- /* set restore_stream for all streams during system suspend */
+ /* prepare for streams to be resumed properly upon resume */
if (!runtime_suspend) {
- ret = sof_set_hw_params_upon_resume(sdev->dev);
+ ret = snd_sof_dsp_hw_params_upon_resume(sdev);
if (ret < 0) {
dev_err(sdev->dev,
"error: setting hw_params flag during suspend %d\n",
@@ -202,36 +232,38 @@ static int sof_suspend(struct device *dev, bool runtime_suspend)
}
}
- target_state = snd_sof_dsp_power_target(sdev);
+ pm_state.event = target_state;
+
+ /* suspend DMA trace */
+ sof_fw_trace_suspend(sdev, pm_state);
+
+ /* Notify clients not managed by pm framework about core suspend */
+ sof_suspend_clients(sdev, pm_state);
/* Skip to platform-specific suspend if DSP is entering D0 */
if (target_state == SOF_DSP_PM_D0)
goto suspend;
- /* release trace */
- snd_sof_release_trace(sdev);
-
#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE)
/* cache debugfs contents during runtime suspend */
if (runtime_suspend)
sof_cache_debugfs(sdev);
#endif
/* notify DSP of upcoming power down */
- ret = sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_SAVE);
- if (ret == -EBUSY || ret == -EAGAIN) {
- /*
- * runtime PM has logic to handle -EBUSY/-EAGAIN so
- * pass these errors up
- */
- dev_err(sdev->dev,
- "error: ctx_save ipc error during suspend %d\n",
- ret);
- return ret;
- } else if (ret < 0) {
- /* FW in unexpected state, continue to power down */
- dev_warn(sdev->dev,
- "ctx_save ipc error %d, proceeding with suspend\n",
- ret);
+ if (pm_ops && pm_ops->ctx_save) {
+ ret = pm_ops->ctx_save(sdev);
+ if (ret == -EBUSY || ret == -EAGAIN) {
+ /*
+ * runtime PM has logic to handle -EBUSY/-EAGAIN so
+ * pass these errors up
+ */
+ dev_err(sdev->dev, "ctx_save IPC error during suspend: %d\n", ret);
+ return ret;
+ } else if (ret < 0) {
+ /* FW in unexpected state, continue to power down */
+ dev_warn(sdev->dev, "ctx_save IPC error: %d, proceeding with suspend\n",
+ ret);
+ }
}
suspend:
@@ -255,16 +287,19 @@ suspend:
return ret;
/* reset FW state */
- sdev->fw_state = SOF_FW_BOOT_NOT_STARTED;
+ sof_set_fw_state(sdev, SOF_FW_BOOT_NOT_STARTED);
+ sdev->enabled_cores_mask = 0;
return ret;
}
int snd_sof_dsp_power_down_notify(struct snd_sof_dev *sdev)
{
+ const struct sof_ipc_pm_ops *pm_ops = sof_ipc_get_ops(sdev, pm);
+
/* Notify DSP of upcoming power down */
- if (sof_ops(sdev)->remove)
- return sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_SAVE);
+ if (sof_ops(sdev)->remove && pm_ops && pm_ops->ctx_save)
+ return pm_ops->ctx_save(sdev);
return 0;
}
@@ -304,15 +339,41 @@ EXPORT_SYMBOL(snd_sof_suspend);
int snd_sof_prepare(struct device *dev)
{
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
+ const struct sof_dev_desc *desc = sdev->pdata->desc;
+
+ /* will suspend to S3 by default */
+ sdev->system_suspend_target = SOF_SUSPEND_S3;
+
+ /*
+ * if the firmware is crashed or boot failed then we try to aim for S3
+ * to reboot the firmware
+ */
+ if (sdev->fw_state == SOF_FW_CRASHED ||
+ sdev->fw_state == SOF_FW_BOOT_FAILED)
+ return 0;
+
+ if (!desc->use_acpi_target_states)
+ return 0;
#if defined(CONFIG_ACPI)
- if (acpi_target_system_state() == ACPI_STATE_S0)
+ switch (acpi_target_system_state()) {
+ case ACPI_STATE_S0:
sdev->system_suspend_target = SOF_SUSPEND_S0IX;
- else
+ break;
+ case ACPI_STATE_S1:
+ case ACPI_STATE_S2:
+ case ACPI_STATE_S3:
sdev->system_suspend_target = SOF_SUSPEND_S3;
-#else
- /* will suspend to S3 by default */
- sdev->system_suspend_target = SOF_SUSPEND_S3;
+ break;
+ case ACPI_STATE_S4:
+ sdev->system_suspend_target = SOF_SUSPEND_S4;
+ break;
+ case ACPI_STATE_S5:
+ sdev->system_suspend_target = SOF_SUSPEND_S5;
+ break;
+ default:
+ break;
+ }
#endif
return 0;
diff --git a/sound/soc/sof/probe.c b/sound/soc/sof/probe.c
deleted file mode 100644
index 14509f4d3f86..000000000000
--- a/sound/soc/sof/probe.c
+++ /dev/null
@@ -1,290 +0,0 @@
-// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
-//
-// This file is provided under a dual BSD/GPLv2 license. When using or
-// redistributing this file, you may do so under either license.
-//
-// Copyright(c) 2019-2020 Intel Corporation. All rights reserved.
-//
-// Author: Cezary Rojewski <cezary.rojewski@intel.com>
-//
-
-#include "sof-priv.h"
-#include "probe.h"
-
-/**
- * sof_ipc_probe_init - initialize data probing
- * @sdev: SOF sound device
- * @stream_tag: Extractor stream tag
- * @buffer_size: DMA buffer size to set for extractor
- *
- * Host chooses whether extraction is supported or not by providing
- * valid stream tag to DSP. Once specified, stream described by that
- * tag will be tied to DSP for extraction for the entire lifetime of
- * probe.
- *
- * Probing is initialized only once and each INIT request must be
- * matched by DEINIT call.
- */
-int sof_ipc_probe_init(struct snd_sof_dev *sdev,
- u32 stream_tag, size_t buffer_size)
-{
- struct sof_ipc_probe_dma_add_params *msg;
- struct sof_ipc_reply reply;
- size_t size = struct_size(msg, dma, 1);
- int ret;
-
- msg = kmalloc(size, GFP_KERNEL);
- if (!msg)
- return -ENOMEM;
- msg->hdr.size = size;
- msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_INIT;
- msg->num_elems = 1;
- msg->dma[0].stream_tag = stream_tag;
- msg->dma[0].dma_buffer_size = buffer_size;
-
- ret = sof_ipc_tx_message(sdev->ipc, msg->hdr.cmd, msg, msg->hdr.size,
- &reply, sizeof(reply));
- kfree(msg);
- return ret;
-}
-EXPORT_SYMBOL(sof_ipc_probe_init);
-
-/**
- * sof_ipc_probe_deinit - cleanup after data probing
- * @sdev: SOF sound device
- *
- * Host sends DEINIT request to free previously initialized probe
- * on DSP side once it is no longer needed. DEINIT only when there
- * are no probes connected and with all injectors detached.
- */
-int sof_ipc_probe_deinit(struct snd_sof_dev *sdev)
-{
- struct sof_ipc_cmd_hdr msg;
- struct sof_ipc_reply reply;
-
- msg.size = sizeof(msg);
- msg.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_DEINIT;
-
- return sof_ipc_tx_message(sdev->ipc, msg.cmd, &msg, msg.size,
- &reply, sizeof(reply));
-}
-EXPORT_SYMBOL(sof_ipc_probe_deinit);
-
-static int sof_ipc_probe_info(struct snd_sof_dev *sdev, unsigned int cmd,
- void **params, size_t *num_params)
-{
- struct sof_ipc_probe_info_params msg = {{{0}}};
- struct sof_ipc_probe_info_params *reply;
- size_t bytes;
- int ret;
-
- *params = NULL;
- *num_params = 0;
-
- reply = kzalloc(SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL);
- if (!reply)
- return -ENOMEM;
- msg.rhdr.hdr.size = sizeof(msg);
- msg.rhdr.hdr.cmd = SOF_IPC_GLB_PROBE | cmd;
-
- ret = sof_ipc_tx_message(sdev->ipc, msg.rhdr.hdr.cmd, &msg,
- msg.rhdr.hdr.size, reply, SOF_IPC_MSG_MAX_SIZE);
- if (ret < 0 || reply->rhdr.error < 0)
- goto exit;
-
- if (!reply->num_elems)
- goto exit;
-
- if (cmd == SOF_IPC_PROBE_DMA_INFO)
- bytes = sizeof(reply->dma[0]);
- else
- bytes = sizeof(reply->desc[0]);
- bytes *= reply->num_elems;
- *params = kmemdup(&reply->dma[0], bytes, GFP_KERNEL);
- if (!*params) {
- ret = -ENOMEM;
- goto exit;
- }
- *num_params = reply->num_elems;
-
-exit:
- kfree(reply);
- return ret;
-}
-
-/**
- * sof_ipc_probe_dma_info - retrieve list of active injection dmas
- * @sdev: SOF sound device
- * @dma: Returned list of active dmas
- * @num_dma: Returned count of active dmas
- *
- * Host sends DMA_INFO request to obtain list of injection dmas it
- * can use to transfer data over with.
- *
- * Note that list contains only injection dmas as there is only one
- * extractor (dma) and it is always assigned on probing init.
- * DSP knows exactly where data from extraction probes is going to,
- * which is not the case for injection where multiple streams
- * could be engaged.
- */
-int sof_ipc_probe_dma_info(struct snd_sof_dev *sdev,
- struct sof_probe_dma **dma, size_t *num_dma)
-{
- return sof_ipc_probe_info(sdev, SOF_IPC_PROBE_DMA_INFO,
- (void **)dma, num_dma);
-}
-EXPORT_SYMBOL(sof_ipc_probe_dma_info);
-
-/**
- * sof_ipc_probe_dma_add - attach to specified dmas
- * @sdev: SOF sound device
- * @dma: List of streams (dmas) to attach to
- * @num_dma: Number of elements in @dma
- *
- * Contrary to extraction, injection streams are never assigned
- * on init. Before attempting any data injection, host is responsible
- * for specifying streams which will be later used to transfer data
- * to connected probe points.
- */
-int sof_ipc_probe_dma_add(struct snd_sof_dev *sdev,
- struct sof_probe_dma *dma, size_t num_dma)
-{
- struct sof_ipc_probe_dma_add_params *msg;
- struct sof_ipc_reply reply;
- size_t size = struct_size(msg, dma, num_dma);
- int ret;
-
- msg = kmalloc(size, GFP_KERNEL);
- if (!msg)
- return -ENOMEM;
- msg->hdr.size = size;
- msg->num_elems = num_dma;
- msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_DMA_ADD;
- memcpy(&msg->dma[0], dma, size - sizeof(*msg));
-
- ret = sof_ipc_tx_message(sdev->ipc, msg->hdr.cmd, msg, msg->hdr.size,
- &reply, sizeof(reply));
- kfree(msg);
- return ret;
-}
-EXPORT_SYMBOL(sof_ipc_probe_dma_add);
-
-/**
- * sof_ipc_probe_dma_remove - detach from specified dmas
- * @sdev: SOF sound device
- * @stream_tag: List of stream tags to detach from
- * @num_stream_tag: Number of elements in @stream_tag
- *
- * Host sends DMA_REMOVE request to free previously attached stream
- * from being occupied for injection. Each detach operation should
- * match equivalent DMA_ADD. Detach only when all probes tied to
- * given stream have been disconnected.
- */
-int sof_ipc_probe_dma_remove(struct snd_sof_dev *sdev,
- unsigned int *stream_tag, size_t num_stream_tag)
-{
- struct sof_ipc_probe_dma_remove_params *msg;
- struct sof_ipc_reply reply;
- size_t size = struct_size(msg, stream_tag, num_stream_tag);
- int ret;
-
- msg = kmalloc(size, GFP_KERNEL);
- if (!msg)
- return -ENOMEM;
- msg->hdr.size = size;
- msg->num_elems = num_stream_tag;
- msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_DMA_REMOVE;
- memcpy(&msg->stream_tag[0], stream_tag, size - sizeof(*msg));
-
- ret = sof_ipc_tx_message(sdev->ipc, msg->hdr.cmd, msg, msg->hdr.size,
- &reply, sizeof(reply));
- kfree(msg);
- return ret;
-}
-EXPORT_SYMBOL(sof_ipc_probe_dma_remove);
-
-/**
- * sof_ipc_probe_points_info - retrieve list of active probe points
- * @sdev: SOF sound device
- * @desc: Returned list of active probes
- * @num_desc: Returned count of active probes
- *
- * Host sends PROBE_POINT_INFO request to obtain list of active probe
- * points, valid for disconnection when given probe is no longer
- * required.
- */
-int sof_ipc_probe_points_info(struct snd_sof_dev *sdev,
- struct sof_probe_point_desc **desc, size_t *num_desc)
-{
- return sof_ipc_probe_info(sdev, SOF_IPC_PROBE_POINT_INFO,
- (void **)desc, num_desc);
-}
-EXPORT_SYMBOL(sof_ipc_probe_points_info);
-
-/**
- * sof_ipc_probe_points_add - connect specified probes
- * @sdev: SOF sound device
- * @desc: List of probe points to connect
- * @num_desc: Number of elements in @desc
- *
- * Dynamically connects to provided set of endpoints. Immediately
- * after connection is established, host must be prepared to
- * transfer data from or to target stream given the probing purpose.
- *
- * Each probe point should be removed using PROBE_POINT_REMOVE
- * request when no longer needed.
- */
-int sof_ipc_probe_points_add(struct snd_sof_dev *sdev,
- struct sof_probe_point_desc *desc, size_t num_desc)
-{
- struct sof_ipc_probe_point_add_params *msg;
- struct sof_ipc_reply reply;
- size_t size = struct_size(msg, desc, num_desc);
- int ret;
-
- msg = kmalloc(size, GFP_KERNEL);
- if (!msg)
- return -ENOMEM;
- msg->hdr.size = size;
- msg->num_elems = num_desc;
- msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_POINT_ADD;
- memcpy(&msg->desc[0], desc, size - sizeof(*msg));
-
- ret = sof_ipc_tx_message(sdev->ipc, msg->hdr.cmd, msg, msg->hdr.size,
- &reply, sizeof(reply));
- kfree(msg);
- return ret;
-}
-EXPORT_SYMBOL(sof_ipc_probe_points_add);
-
-/**
- * sof_ipc_probe_points_remove - disconnect specified probes
- * @sdev: SOF sound device
- * @buffer_id: List of probe points to disconnect
- * @num_buffer_id: Number of elements in @desc
- *
- * Removes previously connected probes from list of active probe
- * points and frees all resources on DSP side.
- */
-int sof_ipc_probe_points_remove(struct snd_sof_dev *sdev,
- unsigned int *buffer_id, size_t num_buffer_id)
-{
- struct sof_ipc_probe_point_remove_params *msg;
- struct sof_ipc_reply reply;
- size_t size = struct_size(msg, buffer_id, num_buffer_id);
- int ret;
-
- msg = kmalloc(size, GFP_KERNEL);
- if (!msg)
- return -ENOMEM;
- msg->hdr.size = size;
- msg->num_elems = num_buffer_id;
- msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_POINT_REMOVE;
- memcpy(&msg->buffer_id[0], buffer_id, size - sizeof(*msg));
-
- ret = sof_ipc_tx_message(sdev->ipc, msg->hdr.cmd, msg, msg->hdr.size,
- &reply, sizeof(reply));
- kfree(msg);
- return ret;
-}
-EXPORT_SYMBOL(sof_ipc_probe_points_remove);
diff --git a/sound/soc/sof/probe.h b/sound/soc/sof/probe.h
deleted file mode 100644
index 5e159ab239fa..000000000000
--- a/sound/soc/sof/probe.h
+++ /dev/null
@@ -1,85 +0,0 @@
-/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
-/*
- * This file is provided under a dual BSD/GPLv2 license. When using or
- * redistributing this file, you may do so under either license.
- *
- * Copyright(c) 2019-2020 Intel Corporation. All rights reserved.
- *
- * Author: Cezary Rojewski <cezary.rojewski@intel.com>
- */
-
-#ifndef __SOF_PROBE_H
-#define __SOF_PROBE_H
-
-#include <sound/sof/header.h>
-
-struct snd_sof_dev;
-
-#define SOF_PROBE_INVALID_NODE_ID UINT_MAX
-
-struct sof_probe_dma {
- unsigned int stream_tag;
- unsigned int dma_buffer_size;
-} __packed;
-
-enum sof_connection_purpose {
- SOF_CONNECTION_PURPOSE_EXTRACT = 1,
- SOF_CONNECTION_PURPOSE_INJECT,
-};
-
-struct sof_probe_point_desc {
- unsigned int buffer_id;
- unsigned int purpose;
- unsigned int stream_tag;
-} __packed;
-
-struct sof_ipc_probe_dma_add_params {
- struct sof_ipc_cmd_hdr hdr;
- unsigned int num_elems;
- struct sof_probe_dma dma[];
-} __packed;
-
-struct sof_ipc_probe_info_params {
- struct sof_ipc_reply rhdr;
- unsigned int num_elems;
- union {
- struct sof_probe_dma dma[0];
- struct sof_probe_point_desc desc[0];
- };
-} __packed;
-
-struct sof_ipc_probe_dma_remove_params {
- struct sof_ipc_cmd_hdr hdr;
- unsigned int num_elems;
- unsigned int stream_tag[];
-} __packed;
-
-struct sof_ipc_probe_point_add_params {
- struct sof_ipc_cmd_hdr hdr;
- unsigned int num_elems;
- struct sof_probe_point_desc desc[];
-} __packed;
-
-struct sof_ipc_probe_point_remove_params {
- struct sof_ipc_cmd_hdr hdr;
- unsigned int num_elems;
- unsigned int buffer_id[];
-} __packed;
-
-int sof_ipc_probe_init(struct snd_sof_dev *sdev,
- u32 stream_tag, size_t buffer_size);
-int sof_ipc_probe_deinit(struct snd_sof_dev *sdev);
-int sof_ipc_probe_dma_info(struct snd_sof_dev *sdev,
- struct sof_probe_dma **dma, size_t *num_dma);
-int sof_ipc_probe_dma_add(struct snd_sof_dev *sdev,
- struct sof_probe_dma *dma, size_t num_dma);
-int sof_ipc_probe_dma_remove(struct snd_sof_dev *sdev,
- unsigned int *stream_tag, size_t num_stream_tag);
-int sof_ipc_probe_points_info(struct snd_sof_dev *sdev,
- struct sof_probe_point_desc **desc, size_t *num_desc);
-int sof_ipc_probe_points_add(struct snd_sof_dev *sdev,
- struct sof_probe_point_desc *desc, size_t num_desc);
-int sof_ipc_probe_points_remove(struct snd_sof_dev *sdev,
- unsigned int *buffer_id, size_t num_buffer_id);
-
-#endif
diff --git a/sound/soc/sof/sof-acpi-dev.c b/sound/soc/sof/sof-acpi-dev.c
index 8aecc46b3647..2977f0a63fba 100644
--- a/sound/soc/sof/sof-acpi-dev.c
+++ b/sound/soc/sof/sof-acpi-dev.c
@@ -17,6 +17,7 @@
#include <sound/sof.h>
#include "../intel/common/soc-intel-quirks.h"
#include "ops.h"
+#include "sof-acpi-dev.h"
/* platform specific devices */
#include "intel/shim.h"
@@ -35,77 +36,17 @@ MODULE_PARM_DESC(sof_acpi_debug, "SOF ACPI debug options (0x0 all off)");
#define SOF_ACPI_DISABLE_PM_RUNTIME BIT(0)
-#if IS_ENABLED(CONFIG_ACPI) && IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL)
-static const struct sof_dev_desc sof_acpi_broadwell_desc = {
- .machines = snd_soc_acpi_intel_broadwell_machines,
- .resindex_lpe_base = 0,
- .resindex_pcicfg_base = 1,
- .resindex_imr_base = -1,
- .irqindex_host_ipc = 0,
- .chip_info = &bdw_chip_info,
- .default_fw_path = "intel/sof",
- .default_tplg_path = "intel/sof-tplg",
- .default_fw_filename = "sof-bdw.ri",
- .nocodec_tplg_filename = "sof-bdw-nocodec.tplg",
- .ops = &sof_bdw_ops,
-};
-#endif
-
-#if IS_ENABLED(CONFIG_ACPI) && IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
-
-/* BYTCR uses different IRQ index */
-static const struct sof_dev_desc sof_acpi_baytrailcr_desc = {
- .machines = snd_soc_acpi_intel_baytrail_machines,
- .resindex_lpe_base = 0,
- .resindex_pcicfg_base = 1,
- .resindex_imr_base = 2,
- .irqindex_host_ipc = 0,
- .chip_info = &byt_chip_info,
- .default_fw_path = "intel/sof",
- .default_tplg_path = "intel/sof-tplg",
- .default_fw_filename = "sof-byt.ri",
- .nocodec_tplg_filename = "sof-byt-nocodec.tplg",
- .ops = &sof_byt_ops,
-};
-
-static const struct sof_dev_desc sof_acpi_baytrail_desc = {
- .machines = snd_soc_acpi_intel_baytrail_machines,
- .resindex_lpe_base = 0,
- .resindex_pcicfg_base = 1,
- .resindex_imr_base = 2,
- .irqindex_host_ipc = 5,
- .chip_info = &byt_chip_info,
- .default_fw_path = "intel/sof",
- .default_tplg_path = "intel/sof-tplg",
- .default_fw_filename = "sof-byt.ri",
- .nocodec_tplg_filename = "sof-byt-nocodec.tplg",
- .ops = &sof_byt_ops,
-};
-
-static const struct sof_dev_desc sof_acpi_cherrytrail_desc = {
- .machines = snd_soc_acpi_intel_cherrytrail_machines,
- .resindex_lpe_base = 0,
- .resindex_pcicfg_base = 1,
- .resindex_imr_base = 2,
- .irqindex_host_ipc = 5,
- .chip_info = &cht_chip_info,
- .default_fw_path = "intel/sof",
- .default_tplg_path = "intel/sof-tplg",
- .default_fw_filename = "sof-cht.ri",
- .nocodec_tplg_filename = "sof-cht-nocodec.tplg",
- .ops = &sof_cht_ops,
-};
-
-#endif
-
-static const struct dev_pm_ops sof_acpi_pm = {
+const struct dev_pm_ops sof_acpi_pm = {
SET_SYSTEM_SLEEP_PM_OPS(snd_sof_suspend, snd_sof_resume)
SET_RUNTIME_PM_OPS(snd_sof_runtime_suspend, snd_sof_runtime_resume,
snd_sof_runtime_idle)
};
+EXPORT_SYMBOL_NS(sof_acpi_pm, SND_SOC_SOF_ACPI_DEV);
static void sof_acpi_probe_complete(struct device *dev)
{
+ dev_dbg(dev, "Completing SOF ACPI probe");
+
if (sof_acpi_debug & SOF_ACPI_DISABLE_PM_RUNTIME)
return;
@@ -115,108 +56,47 @@ static void sof_acpi_probe_complete(struct device *dev)
pm_runtime_enable(dev);
}
-static int sof_acpi_probe(struct platform_device *pdev)
+int sof_acpi_probe(struct platform_device *pdev, const struct sof_dev_desc *desc)
{
struct device *dev = &pdev->dev;
- const struct sof_dev_desc *desc;
struct snd_sof_pdata *sof_pdata;
- const struct snd_sof_dsp_ops *ops;
- int ret;
- dev_dbg(&pdev->dev, "ACPI DSP detected");
+ dev_dbg(dev, "ACPI DSP detected");
sof_pdata = devm_kzalloc(dev, sizeof(*sof_pdata), GFP_KERNEL);
if (!sof_pdata)
return -ENOMEM;
- desc = device_get_match_data(dev);
- if (!desc)
- return -ENODEV;
-
-#if IS_ENABLED(CONFIG_ACPI) && IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
- if (desc == &sof_acpi_baytrail_desc && soc_intel_is_byt_cr(pdev))
- desc = &sof_acpi_baytrailcr_desc;
-#endif
-
- /* get ops for platform */
- ops = desc->ops;
- if (!ops) {
+ if (!desc->ops) {
dev_err(dev, "error: no matching ACPI descriptor ops\n");
return -ENODEV;
}
sof_pdata->desc = desc;
sof_pdata->dev = &pdev->dev;
- sof_pdata->fw_filename = desc->default_fw_filename;
-
- /* alternate fw and tplg filenames ? */
- if (fw_path)
- sof_pdata->fw_filename_prefix = fw_path;
- else
- sof_pdata->fw_filename_prefix =
- sof_pdata->desc->default_fw_path;
-
- if (tplg_path)
- sof_pdata->tplg_filename_prefix = tplg_path;
- else
- sof_pdata->tplg_filename_prefix =
- sof_pdata->desc->default_tplg_path;
-
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)
- /* set callback to enable runtime_pm */
- sof_pdata->sof_probe_complete = sof_acpi_probe_complete;
-#endif
- /* call sof helper for DSP hardware probe */
- ret = snd_sof_device_probe(dev, sof_pdata);
- if (ret) {
- dev_err(dev, "error: failed to probe DSP hardware!\n");
- return ret;
- }
-#if !IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)
- sof_acpi_probe_complete(dev);
-#endif
+ sof_pdata->ipc_file_profile_base.ipc_type = desc->ipc_default;
+ sof_pdata->ipc_file_profile_base.fw_path = fw_path;
+ sof_pdata->ipc_file_profile_base.tplg_path = tplg_path;
- return ret;
+ /* set callback to be called on successful device probe to enable runtime_pm */
+ sof_pdata->sof_probe_complete = sof_acpi_probe_complete;
+
+ /* call sof helper for DSP hardware probe */
+ return snd_sof_device_probe(dev, sof_pdata);
}
+EXPORT_SYMBOL_NS(sof_acpi_probe, SND_SOC_SOF_ACPI_DEV);
-static int sof_acpi_remove(struct platform_device *pdev)
+void sof_acpi_remove(struct platform_device *pdev)
{
+ struct device *dev = &pdev->dev;
+
if (!(sof_acpi_debug & SOF_ACPI_DISABLE_PM_RUNTIME))
- pm_runtime_disable(&pdev->dev);
+ pm_runtime_disable(dev);
/* call sof helper for DSP hardware remove */
- snd_sof_device_remove(&pdev->dev);
-
- return 0;
+ snd_sof_device_remove(dev);
}
-
-#ifdef CONFIG_ACPI
-static const struct acpi_device_id sof_acpi_match[] = {
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL)
- { "INT3438", (unsigned long)&sof_acpi_broadwell_desc },
-#endif
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
- { "80860F28", (unsigned long)&sof_acpi_baytrail_desc },
- { "808622A8", (unsigned long)&sof_acpi_cherrytrail_desc },
-#endif
- { }
-};
-MODULE_DEVICE_TABLE(acpi, sof_acpi_match);
-#endif
-
-/* acpi_driver definition */
-static struct platform_driver snd_sof_acpi_driver = {
- .probe = sof_acpi_probe,
- .remove = sof_acpi_remove,
- .driver = {
- .name = "sof-audio-acpi",
- .pm = &sof_acpi_pm,
- .acpi_match_table = ACPI_PTR(sof_acpi_match),
- },
-};
-module_platform_driver(snd_sof_acpi_driver);
+EXPORT_SYMBOL_NS(sof_acpi_remove, SND_SOC_SOF_ACPI_DEV);
MODULE_LICENSE("Dual BSD/GPL");
-MODULE_IMPORT_NS(SND_SOC_SOF_BAYTRAIL);
-MODULE_IMPORT_NS(SND_SOC_SOF_BROADWELL);
diff --git a/sound/soc/sof/sof-acpi-dev.h b/sound/soc/sof/sof-acpi-dev.h
new file mode 100644
index 000000000000..9bf8f75ceaae
--- /dev/null
+++ b/sound/soc/sof/sof-acpi-dev.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+/*
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * Copyright(c) 2021 Intel Corporation. All rights reserved.
+ */
+
+#ifndef __SOUND_SOC_SOF_ACPI_H
+#define __SOUND_SOC_SOF_ACPI_H
+
+extern const struct dev_pm_ops sof_acpi_pm;
+int sof_acpi_probe(struct platform_device *pdev, const struct sof_dev_desc *desc);
+void sof_acpi_remove(struct platform_device *pdev);
+
+#endif
diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c
index 1c7698f8edd6..e693dcb475e4 100644
--- a/sound/soc/sof/sof-audio.c
+++ b/sound/soc/sof/sof-audio.c
@@ -8,275 +8,859 @@
// Author: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
//
+#include <linux/bitfield.h>
+#include <trace/events/sof.h>
#include "sof-audio.h"
#include "ops.h"
-/*
- * helper to determine if there are only D0i3 compatible
- * streams active
- */
-bool snd_sof_dsp_only_d0i3_compatible_stream_active(struct snd_sof_dev *sdev)
+static bool is_virtual_widget(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget,
+ const char *func)
{
- struct snd_pcm_substream *substream;
- struct snd_sof_pcm *spcm;
- bool d0i3_compatible_active = false;
- int dir;
+ switch (widget->id) {
+ case snd_soc_dapm_out_drv:
+ case snd_soc_dapm_output:
+ case snd_soc_dapm_input:
+ dev_dbg(sdev->dev, "%s: %s is a virtual widget\n", func, widget->name);
+ return true;
+ default:
+ return false;
+ }
+}
- list_for_each_entry(spcm, &sdev->pcm_list, list) {
- for_each_pcm_streams(dir) {
- substream = spcm->stream[dir].substream;
- if (!substream || !substream->runtime)
- continue;
+static void sof_reset_route_setup_status(struct snd_sof_dev *sdev, struct snd_sof_widget *widget)
+{
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
+ struct snd_sof_route *sroute;
- /*
- * substream->runtime being not NULL indicates that
- * that the stream is open. No need to check the
- * stream state.
- */
- if (!spcm->stream[dir].d0i3_compatible)
- return false;
+ list_for_each_entry(sroute, &sdev->route_list, list)
+ if (sroute->src_widget == widget || sroute->sink_widget == widget) {
+ if (sroute->setup && tplg_ops && tplg_ops->route_free)
+ tplg_ops->route_free(sdev, sroute);
- d0i3_compatible_active = true;
+ sroute->setup = false;
+ }
+}
+
+static int sof_widget_free_unlocked(struct snd_sof_dev *sdev,
+ struct snd_sof_widget *swidget)
+{
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
+ struct snd_sof_pipeline *spipe = swidget->spipe;
+ int err = 0;
+ int ret;
+
+ if (!swidget->private)
+ return 0;
+
+ trace_sof_widget_free(swidget);
+
+ /* only free when use_count is 0 */
+ if (--swidget->use_count)
+ return 0;
+
+ /* reset route setup status for all routes that contain this widget */
+ sof_reset_route_setup_status(sdev, swidget);
+
+ /* free DAI config and continue to free widget even if it fails */
+ if (WIDGET_IS_DAI(swidget->id)) {
+ struct snd_sof_dai_config_data data;
+ unsigned int flags = SOF_DAI_CONFIG_FLAGS_HW_FREE;
+
+ data.dai_data = DMA_CHAN_INVALID;
+
+ if (tplg_ops && tplg_ops->dai_config) {
+ err = tplg_ops->dai_config(sdev, swidget, flags, &data);
+ if (err < 0)
+ dev_err(sdev->dev, "failed to free config for widget %s\n",
+ swidget->widget->name);
}
}
- return d0i3_compatible_active;
+ /* continue to disable core even if IPC fails */
+ if (tplg_ops && tplg_ops->widget_free) {
+ ret = tplg_ops->widget_free(sdev, swidget);
+ if (ret < 0 && !err)
+ err = ret;
+ }
+
+ /*
+ * decrement ref count for cores associated with all modules in the pipeline and clear
+ * the complete flag
+ */
+ if (swidget->id == snd_soc_dapm_scheduler) {
+ int i;
+
+ for_each_set_bit(i, &spipe->core_mask, sdev->num_cores) {
+ ret = snd_sof_dsp_core_put(sdev, i);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to disable target core: %d for pipeline %s\n",
+ i, swidget->widget->name);
+ if (!err)
+ err = ret;
+ }
+ }
+ swidget->spipe->complete = 0;
+ }
+
+ /*
+ * free the scheduler widget (same as pipe_widget) associated with the current swidget.
+ * skip for static pipelines
+ */
+ if (swidget->spipe && swidget->dynamic_pipeline_widget &&
+ swidget->id != snd_soc_dapm_scheduler) {
+ ret = sof_widget_free_unlocked(sdev, swidget->spipe->pipe_widget);
+ if (ret < 0 && !err)
+ err = ret;
+ }
+
+ if (!err)
+ dev_dbg(sdev->dev, "widget %s freed\n", swidget->widget->name);
+
+ return err;
}
-EXPORT_SYMBOL(snd_sof_dsp_only_d0i3_compatible_stream_active);
-bool snd_sof_stream_suspend_ignored(struct snd_sof_dev *sdev)
+int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
{
- struct snd_sof_pcm *spcm;
+ int ret;
- list_for_each_entry(spcm, &sdev->pcm_list, list) {
- if (spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].suspend_ignored ||
- spcm->stream[SNDRV_PCM_STREAM_CAPTURE].suspend_ignored)
- return true;
+ mutex_lock(&swidget->setup_mutex);
+ ret = sof_widget_free_unlocked(sdev, swidget);
+ mutex_unlock(&swidget->setup_mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL(sof_widget_free);
+
+static int sof_widget_setup_unlocked(struct snd_sof_dev *sdev,
+ struct snd_sof_widget *swidget)
+{
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
+ struct snd_sof_pipeline *spipe = swidget->spipe;
+ bool use_count_decremented = false;
+ int ret;
+ int i;
+
+ /* skip if there is no private data */
+ if (!swidget->private)
+ return 0;
+
+ trace_sof_widget_setup(swidget);
+
+ /* widget already set up */
+ if (++swidget->use_count > 1)
+ return 0;
+
+ /*
+ * The scheduler widget for a pipeline is not part of the connected DAPM
+ * widget list and it needs to be set up before the widgets in the pipeline
+ * are set up. The use_count for the scheduler widget is incremented for every
+ * widget in a given pipeline to ensure that it is freed only after the last
+ * widget in the pipeline is freed. Skip setting up scheduler widget for static pipelines.
+ */
+ if (swidget->dynamic_pipeline_widget && swidget->id != snd_soc_dapm_scheduler) {
+ if (!swidget->spipe || !swidget->spipe->pipe_widget) {
+ dev_err(sdev->dev, "No pipeline set for %s\n", swidget->widget->name);
+ ret = -EINVAL;
+ goto use_count_dec;
+ }
+
+ ret = sof_widget_setup_unlocked(sdev, swidget->spipe->pipe_widget);
+ if (ret < 0)
+ goto use_count_dec;
}
- return false;
+ /* update ref count for cores associated with all modules in the pipeline */
+ if (swidget->id == snd_soc_dapm_scheduler) {
+ for_each_set_bit(i, &spipe->core_mask, sdev->num_cores) {
+ ret = snd_sof_dsp_core_get(sdev, i);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to enable target core %d for pipeline %s\n",
+ i, swidget->widget->name);
+ goto pipe_widget_free;
+ }
+ }
+ }
+
+ /* setup widget in the DSP */
+ if (tplg_ops && tplg_ops->widget_setup) {
+ ret = tplg_ops->widget_setup(sdev, swidget);
+ if (ret < 0)
+ goto pipe_widget_free;
+ }
+
+ /* send config for DAI components */
+ if (WIDGET_IS_DAI(swidget->id)) {
+ unsigned int flags = SOF_DAI_CONFIG_FLAGS_HW_PARAMS;
+
+ /*
+ * The config flags saved during BE DAI hw_params will be used for IPC3. IPC4 does
+ * not use the flags argument.
+ */
+ if (tplg_ops && tplg_ops->dai_config) {
+ ret = tplg_ops->dai_config(sdev, swidget, flags, NULL);
+ if (ret < 0)
+ goto widget_free;
+ }
+ }
+
+ /* restore kcontrols for widget */
+ if (tplg_ops && tplg_ops->control && tplg_ops->control->widget_kcontrol_setup) {
+ ret = tplg_ops->control->widget_kcontrol_setup(sdev, swidget);
+ if (ret < 0)
+ goto widget_free;
+ }
+
+ dev_dbg(sdev->dev, "widget %s setup complete\n", swidget->widget->name);
+
+ return 0;
+
+widget_free:
+ /* widget use_count will be decremented by sof_widget_free() */
+ sof_widget_free_unlocked(sdev, swidget);
+ use_count_decremented = true;
+pipe_widget_free:
+ if (swidget->id != snd_soc_dapm_scheduler) {
+ sof_widget_free_unlocked(sdev, swidget->spipe->pipe_widget);
+ } else {
+ int j;
+
+ /* decrement ref count for all cores that were updated previously */
+ for_each_set_bit(j, &spipe->core_mask, sdev->num_cores) {
+ if (j >= i)
+ break;
+ snd_sof_dsp_core_put(sdev, j);
+ }
+ }
+use_count_dec:
+ if (!use_count_decremented)
+ swidget->use_count--;
+
+ return ret;
}
-int sof_set_hw_params_upon_resume(struct device *dev)
+int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
{
- struct snd_sof_dev *sdev = dev_get_drvdata(dev);
- struct snd_pcm_substream *substream;
- struct snd_sof_pcm *spcm;
- snd_pcm_state_t state;
- int dir;
+ int ret;
+
+ mutex_lock(&swidget->setup_mutex);
+ ret = sof_widget_setup_unlocked(sdev, swidget);
+ mutex_unlock(&swidget->setup_mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL(sof_widget_setup);
+
+int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsource,
+ struct snd_soc_dapm_widget *wsink)
+{
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
+ struct snd_sof_widget *src_widget = wsource->dobj.private;
+ struct snd_sof_widget *sink_widget = wsink->dobj.private;
+ struct snd_sof_route *sroute;
+ bool route_found = false;
+
+ /* ignore routes involving virtual widgets in topology */
+ if (is_virtual_widget(sdev, src_widget->widget, __func__) ||
+ is_virtual_widget(sdev, sink_widget->widget, __func__))
+ return 0;
+
+ /* find route matching source and sink widgets */
+ list_for_each_entry(sroute, &sdev->route_list, list)
+ if (sroute->src_widget == src_widget && sroute->sink_widget == sink_widget) {
+ route_found = true;
+ break;
+ }
+
+ if (!route_found) {
+ dev_err(sdev->dev, "error: cannot find SOF route for source %s -> %s sink\n",
+ wsource->name, wsink->name);
+ return -EINVAL;
+ }
+
+ /* nothing to do if route is already set up */
+ if (sroute->setup)
+ return 0;
+
+ if (tplg_ops && tplg_ops->route_setup) {
+ int ret = tplg_ops->route_setup(sdev, sroute);
+
+ if (ret < 0)
+ return ret;
+ }
+
+ sroute->setup = true;
+ return 0;
+}
+
+static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev,
+ struct snd_soc_dapm_widget_list *list, int dir)
+{
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
+ struct snd_soc_dapm_widget *widget;
+ struct snd_sof_route *sroute;
+ struct snd_soc_dapm_path *p;
+ int ret = 0;
+ int i;
/*
- * SOF requires hw_params to be set-up internally upon resume.
- * So, set the flag to indicate this for those streams that
- * have been suspended.
+ * Set up connections between widgets in the sink/source paths based on direction.
+ * Some non-SOF widgets exist in topology either for compatibility or for the
+ * purpose of connecting a pipeline from a host to a DAI in order to receive the DAPM
+ * events. But they are not handled by the firmware. So ignore them.
*/
- list_for_each_entry(spcm, &sdev->pcm_list, list) {
- for_each_pcm_streams(dir) {
- /*
- * do not reset hw_params upon resume for streams that
- * were kept running during suspend
- */
- if (spcm->stream[dir].suspend_ignored)
+ if (dir == SNDRV_PCM_STREAM_PLAYBACK) {
+ for_each_dapm_widgets(list, i, widget) {
+ if (!widget->dobj.private)
continue;
- substream = spcm->stream[dir].substream;
- if (!substream || !substream->runtime)
+ snd_soc_dapm_widget_for_each_sink_path(widget, p) {
+ if (!widget_in_list(list, p->sink))
+ continue;
+
+ if (p->sink->dobj.private) {
+ ret = sof_route_setup(sdev, widget, p->sink);
+ if (ret < 0)
+ return ret;
+ }
+ }
+ }
+ } else {
+ for_each_dapm_widgets(list, i, widget) {
+ if (!widget->dobj.private)
continue;
- state = substream->runtime->status->state;
- if (state == SNDRV_PCM_STATE_SUSPENDED)
- spcm->prepared[dir] = false;
+ snd_soc_dapm_widget_for_each_source_path(widget, p) {
+ if (!widget_in_list(list, p->source))
+ continue;
+
+ if (p->source->dobj.private) {
+ ret = sof_route_setup(sdev, p->source, widget);
+ if (ret < 0)
+ return ret;
+ }
+ }
+ }
+ }
+
+ /*
+ * The above loop handles connections between widgets that belong to the DAPM widget list.
+ * This is not sufficient to handle loopback cases between pipelines configured with
+ * different directions, e.g. a sidetone or an amplifier feedback connected to a speaker
+ * protection module.
+ */
+ list_for_each_entry(sroute, &sdev->route_list, list) {
+ bool src_widget_in_dapm_list, sink_widget_in_dapm_list;
+ struct snd_sof_widget *swidget;
+
+ if (sroute->setup)
+ continue;
+
+ src_widget_in_dapm_list = widget_in_list(list, sroute->src_widget->widget);
+ sink_widget_in_dapm_list = widget_in_list(list, sroute->sink_widget->widget);
+
+ /*
+ * if both source and sink are in the DAPM list, the route must already have been
+ * set up above. And if neither are in the DAPM list, the route shouldn't be
+ * handled now.
+ */
+ if (src_widget_in_dapm_list == sink_widget_in_dapm_list)
+ continue;
+
+ /*
+ * At this point either the source widget or the sink widget is in the DAPM list
+ * with a route that might need to be set up. Check the use_count of the widget
+ * that is not in the DAPM list to confirm if it is in use currently before setting
+ * up the route.
+ */
+ if (src_widget_in_dapm_list)
+ swidget = sroute->sink_widget;
+ else
+ swidget = sroute->src_widget;
+
+ mutex_lock(&swidget->setup_mutex);
+ if (!swidget->use_count) {
+ mutex_unlock(&swidget->setup_mutex);
+ continue;
+ }
+
+ if (tplg_ops && tplg_ops->route_setup) {
+ /*
+ * this route will get freed when either the source widget or the sink
+ * widget is freed during hw_free
+ */
+ ret = tplg_ops->route_setup(sdev, sroute);
+ if (!ret)
+ sroute->setup = true;
+ }
+
+ mutex_unlock(&swidget->setup_mutex);
+
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static void
+sof_unprepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget,
+ struct snd_soc_dapm_widget_list *list)
+{
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
+ struct snd_sof_widget *swidget = widget->dobj.private;
+ const struct sof_ipc_tplg_widget_ops *widget_ops;
+ struct snd_soc_dapm_path *p;
+
+ if (is_virtual_widget(sdev, widget, __func__))
+ return;
+
+ /* skip if the widget is in use or if it is already unprepared */
+ if (!swidget || !swidget->prepared || swidget->use_count > 0)
+ goto sink_unprepare;
+
+ widget_ops = tplg_ops ? tplg_ops->widget : NULL;
+ if (widget_ops && widget_ops[widget->id].ipc_unprepare)
+ /* unprepare the source widget */
+ widget_ops[widget->id].ipc_unprepare(swidget);
+
+ swidget->prepared = false;
+
+sink_unprepare:
+ /* unprepare all widgets in the sink paths */
+ snd_soc_dapm_widget_for_each_sink_path(widget, p) {
+ if (!widget_in_list(list, p->sink))
+ continue;
+ if (!p->walking && p->sink->dobj.private) {
+ p->walking = true;
+ sof_unprepare_widgets_in_path(sdev, p->sink, list);
+ p->walking = false;
+ }
+ }
+}
+
+static int
+sof_prepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget,
+ struct snd_pcm_hw_params *fe_params,
+ struct snd_sof_platform_stream_params *platform_params,
+ struct snd_pcm_hw_params *pipeline_params, int dir,
+ struct snd_soc_dapm_widget_list *list)
+{
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
+ struct snd_sof_widget *swidget = widget->dobj.private;
+ const struct sof_ipc_tplg_widget_ops *widget_ops;
+ struct snd_soc_dapm_path *p;
+ int ret;
+
+ if (is_virtual_widget(sdev, widget, __func__))
+ return 0;
+
+ widget_ops = tplg_ops ? tplg_ops->widget : NULL;
+ if (!widget_ops)
+ return 0;
+
+ if (!swidget || !widget_ops[widget->id].ipc_prepare || swidget->prepared)
+ goto sink_prepare;
+
+ /* prepare the source widget */
+ ret = widget_ops[widget->id].ipc_prepare(swidget, fe_params, platform_params,
+ pipeline_params, dir);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to prepare widget %s\n", widget->name);
+ return ret;
+ }
+
+ swidget->prepared = true;
+
+sink_prepare:
+ /* prepare all widgets in the sink paths */
+ snd_soc_dapm_widget_for_each_sink_path(widget, p) {
+ if (!widget_in_list(list, p->sink))
+ continue;
+ if (!p->walking && p->sink->dobj.private) {
+ p->walking = true;
+ ret = sof_prepare_widgets_in_path(sdev, p->sink, fe_params,
+ platform_params, pipeline_params, dir,
+ list);
+ p->walking = false;
+ if (ret < 0) {
+ /* unprepare the source widget */
+ if (widget_ops[widget->id].ipc_unprepare &&
+ swidget && swidget->prepared) {
+ widget_ops[widget->id].ipc_unprepare(swidget);
+ swidget->prepared = false;
+ }
+ return ret;
+ }
}
}
- /* set internal flag for BE */
- return snd_sof_dsp_hw_params_upon_resume(sdev);
+ return 0;
}
-static int sof_restore_kcontrols(struct device *dev)
+/*
+ * free all widgets in the sink path starting from the source widget
+ * (DAI type for capture, AIF type for playback)
+ */
+static int sof_free_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget,
+ int dir, struct snd_sof_pcm *spcm)
{
- struct snd_sof_dev *sdev = dev_get_drvdata(dev);
- struct snd_sof_control *scontrol;
- int ipc_cmd, ctrl_type;
+ struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list;
+ struct snd_soc_dapm_path *p;
+ int err;
int ret = 0;
- /* restore kcontrol values */
- list_for_each_entry(scontrol, &sdev->kcontrol_list, list) {
- /* reset readback offset for scontrol after resuming */
- scontrol->readback_offset = 0;
-
- /* notify DSP of kcontrol values */
- switch (scontrol->cmd) {
- case SOF_CTRL_CMD_VOLUME:
- case SOF_CTRL_CMD_ENUM:
- case SOF_CTRL_CMD_SWITCH:
- ipc_cmd = SOF_IPC_COMP_SET_VALUE;
- ctrl_type = SOF_CTRL_TYPE_VALUE_CHAN_SET;
- ret = snd_sof_ipc_set_get_comp_data(scontrol,
- ipc_cmd, ctrl_type,
- scontrol->cmd,
- true);
- break;
- case SOF_CTRL_CMD_BINARY:
- ipc_cmd = SOF_IPC_COMP_SET_DATA;
- ctrl_type = SOF_CTRL_TYPE_DATA_SET;
- ret = snd_sof_ipc_set_get_comp_data(scontrol,
- ipc_cmd, ctrl_type,
- scontrol->cmd,
- true);
- break;
+ if (is_virtual_widget(sdev, widget, __func__))
+ return 0;
- default:
- break;
+ if (widget->dobj.private) {
+ err = sof_widget_free(sdev, widget->dobj.private);
+ if (err < 0)
+ ret = err;
+ }
+
+ /* free all widgets in the sink paths even in case of error to keep use counts balanced */
+ snd_soc_dapm_widget_for_each_sink_path(widget, p) {
+ if (!p->walking) {
+ if (!widget_in_list(list, p->sink))
+ continue;
+
+ p->walking = true;
+
+ err = sof_free_widgets_in_path(sdev, p->sink, dir, spcm);
+ if (err < 0)
+ ret = err;
+ p->walking = false;
}
+ }
- if (ret < 0) {
- dev_err(dev,
- "error: failed kcontrol value set for widget: %d\n",
- scontrol->comp_id);
+ return ret;
+}
+
+/*
+ * set up all widgets in the sink path starting from the source widget
+ * (DAI type for capture, AIF type for playback).
+ * The error path in this function ensures that all successfully set up widgets getting freed.
+ */
+static int sof_set_up_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget,
+ int dir, struct snd_sof_pcm *spcm)
+{
+ struct snd_sof_pcm_stream_pipeline_list *pipeline_list = &spcm->stream[dir].pipeline_list;
+ struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list;
+ struct snd_sof_widget *swidget = widget->dobj.private;
+ struct snd_sof_pipeline *spipe;
+ struct snd_soc_dapm_path *p;
+ int ret;
+
+ if (is_virtual_widget(sdev, widget, __func__))
+ return 0;
+
+ if (swidget) {
+ int i;
+ ret = sof_widget_setup(sdev, widget->dobj.private);
+ if (ret < 0)
return ret;
+
+ /* skip populating the pipe_widgets array if it is NULL */
+ if (!pipeline_list->pipelines)
+ goto sink_setup;
+
+ /*
+ * Add the widget's pipe_widget to the list of pipelines to be triggered if not
+ * already in the list. This will result in the pipelines getting added in the
+ * order source to sink.
+ */
+ for (i = 0; i < pipeline_list->count; i++) {
+ spipe = pipeline_list->pipelines[i];
+ if (spipe == swidget->spipe)
+ break;
+ }
+
+ if (i == pipeline_list->count) {
+ pipeline_list->count++;
+ pipeline_list->pipelines[i] = swidget->spipe;
+ }
+ }
+
+sink_setup:
+ snd_soc_dapm_widget_for_each_sink_path(widget, p) {
+ if (!p->walking) {
+ if (!widget_in_list(list, p->sink))
+ continue;
+
+ p->walking = true;
+
+ ret = sof_set_up_widgets_in_path(sdev, p->sink, dir, spcm);
+ p->walking = false;
+ if (ret < 0) {
+ if (swidget)
+ sof_widget_free(sdev, swidget);
+ return ret;
+ }
}
}
return 0;
}
-int sof_restore_pipelines(struct device *dev)
+static int
+sof_walk_widgets_in_order(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm,
+ struct snd_pcm_hw_params *fe_params,
+ struct snd_sof_platform_stream_params *platform_params, int dir,
+ enum sof_widget_op op)
{
- struct snd_sof_dev *sdev = dev_get_drvdata(dev);
- struct snd_sof_widget *swidget;
- struct snd_sof_route *sroute;
- struct sof_ipc_pipe_new *pipeline;
- struct snd_sof_dai *dai;
- struct sof_ipc_comp_dai *comp_dai;
- struct sof_ipc_cmd_hdr *hdr;
- int ret;
+ struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list;
+ struct snd_soc_dapm_widget *widget;
+ char *str;
+ int ret = 0;
+ int i;
- /* restore pipeline components */
- list_for_each_entry_reverse(swidget, &sdev->widget_list, list) {
- struct sof_ipc_comp_reply r;
+ if (!list)
+ return 0;
+
+ for_each_dapm_widgets(list, i, widget) {
+ if (is_virtual_widget(sdev, widget, __func__))
+ continue;
- /* skip if there is no private data */
- if (!swidget->private)
+ /* starting widget for playback is AIF type */
+ if (dir == SNDRV_PCM_STREAM_PLAYBACK && widget->id != snd_soc_dapm_aif_in)
continue;
- switch (swidget->id) {
- case snd_soc_dapm_dai_in:
- case snd_soc_dapm_dai_out:
- dai = swidget->private;
- comp_dai = &dai->comp_dai;
- ret = sof_ipc_tx_message(sdev->ipc,
- comp_dai->comp.hdr.cmd,
- comp_dai, sizeof(*comp_dai),
- &r, sizeof(r));
+ /* starting widget for capture is DAI type */
+ if (dir == SNDRV_PCM_STREAM_CAPTURE && widget->id != snd_soc_dapm_dai_out)
+ continue;
+
+ switch (op) {
+ case SOF_WIDGET_SETUP:
+ ret = sof_set_up_widgets_in_path(sdev, widget, dir, spcm);
+ str = "set up";
+ break;
+ case SOF_WIDGET_FREE:
+ ret = sof_free_widgets_in_path(sdev, widget, dir, spcm);
+ str = "free";
break;
- case snd_soc_dapm_scheduler:
+ case SOF_WIDGET_PREPARE:
+ {
+ struct snd_pcm_hw_params pipeline_params;
+ str = "prepare";
/*
- * During suspend, all DSP cores are powered off.
- * Therefore upon resume, create the pipeline comp
- * and power up the core that the pipeline is
- * scheduled on.
+ * When walking the list of connected widgets, the pipeline_params for each
+ * widget is modified by the source widget in the path. Use a local
+ * copy of the runtime params as the pipeline_params so that the runtime
+ * params does not get overwritten.
*/
- pipeline = swidget->private;
- ret = sof_load_pipeline_ipc(dev, pipeline, &r);
+ memcpy(&pipeline_params, fe_params, sizeof(*fe_params));
+
+ ret = sof_prepare_widgets_in_path(sdev, widget, fe_params, platform_params,
+ &pipeline_params, dir, list);
break;
- default:
- hdr = swidget->private;
- ret = sof_ipc_tx_message(sdev->ipc, hdr->cmd,
- swidget->private, hdr->size,
- &r, sizeof(r));
+ }
+ case SOF_WIDGET_UNPREPARE:
+ sof_unprepare_widgets_in_path(sdev, widget, list);
break;
+ default:
+ dev_err(sdev->dev, "Invalid widget op %d\n", op);
+ return -EINVAL;
}
if (ret < 0) {
- dev_err(dev,
- "error: failed to load widget type %d with ID: %d\n",
- swidget->widget->id, swidget->comp_id);
-
+ dev_err(sdev->dev, "Failed to %s connected widgets\n", str);
return ret;
}
}
- /* restore pipeline connections */
- list_for_each_entry_reverse(sroute, &sdev->route_list, list) {
- struct sof_ipc_pipe_comp_connect *connect;
- struct sof_ipc_reply reply;
+ return 0;
+}
- /* skip if there's no private data */
- if (!sroute->private)
- continue;
+int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm,
+ struct snd_pcm_hw_params *fe_params,
+ struct snd_sof_platform_stream_params *platform_params,
+ int dir)
+{
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
+ struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list;
+ struct snd_soc_dapm_widget *widget;
+ int i, ret;
- connect = sroute->private;
+ /* nothing to set up */
+ if (!list)
+ return 0;
- /* send ipc */
- ret = sof_ipc_tx_message(sdev->ipc,
- connect->hdr.cmd,
- connect, sizeof(*connect),
- &reply, sizeof(reply));
- if (ret < 0) {
- dev_err(dev,
- "error: failed to load route sink %s control %s source %s\n",
- sroute->route->sink,
- sroute->route->control ? sroute->route->control
- : "none",
- sroute->route->source);
+ /*
+ * Prepare widgets for set up. The prepare step is used to allocate memory, assign
+ * instance ID and pick the widget configuration based on the runtime PCM params.
+ */
+ ret = sof_walk_widgets_in_order(sdev, spcm, fe_params, platform_params,
+ dir, SOF_WIDGET_PREPARE);
+ if (ret < 0)
+ return ret;
- return ret;
- }
+ /* Set up is used to send the IPC to the DSP to create the widget */
+ ret = sof_walk_widgets_in_order(sdev, spcm, fe_params, platform_params,
+ dir, SOF_WIDGET_SETUP);
+ if (ret < 0) {
+ sof_walk_widgets_in_order(sdev, spcm, fe_params, platform_params,
+ dir, SOF_WIDGET_UNPREPARE);
+ return ret;
}
- /* restore dai links */
- list_for_each_entry_reverse(dai, &sdev->dai_list, list) {
- struct sof_ipc_reply reply;
- struct sof_ipc_dai_config *config = dai->dai_config;
+ /*
+ * error in setting pipeline connections will result in route status being reset for
+ * routes that were successfully set up when the widgets are freed.
+ */
+ ret = sof_setup_pipeline_connections(sdev, list, dir);
+ if (ret < 0)
+ goto widget_free;
- if (!config) {
- dev_err(dev, "error: no config for DAI %s\n",
- dai->name);
+ /* complete pipelines */
+ for_each_dapm_widgets(list, i, widget) {
+ struct snd_sof_widget *swidget = widget->dobj.private;
+ struct snd_sof_widget *pipe_widget;
+ struct snd_sof_pipeline *spipe;
+
+ if (!swidget || sdev->dspless_mode_selected)
continue;
+
+ spipe = swidget->spipe;
+ if (!spipe) {
+ dev_err(sdev->dev, "no pipeline found for %s\n",
+ swidget->widget->name);
+ ret = -EINVAL;
+ goto widget_free;
}
- /*
- * The link DMA channel would be invalidated for running
- * streams but not for streams that were in the PAUSED
- * state during suspend. So invalidate it here before setting
- * the dai config in the DSP.
- */
- if (config->type == SOF_DAI_INTEL_HDA)
- config->hda.link_dma_ch = DMA_CHAN_INVALID;
+ pipe_widget = spipe->pipe_widget;
+ if (!pipe_widget) {
+ dev_err(sdev->dev, "error: no pipeline widget found for %s\n",
+ swidget->widget->name);
+ ret = -EINVAL;
+ goto widget_free;
+ }
- ret = sof_ipc_tx_message(sdev->ipc,
- config->hdr.cmd, config,
- config->hdr.size,
- &reply, sizeof(reply));
+ if (spipe->complete)
+ continue;
- if (ret < 0) {
- dev_err(dev,
- "error: failed to set dai config for %s\n",
- dai->name);
+ if (tplg_ops && tplg_ops->pipeline_complete) {
+ spipe->complete = tplg_ops->pipeline_complete(sdev, pipe_widget);
+ if (spipe->complete < 0) {
+ ret = spipe->complete;
+ goto widget_free;
+ }
+ }
+ }
- return ret;
+ return 0;
+
+widget_free:
+ sof_walk_widgets_in_order(sdev, spcm, fe_params, platform_params, dir,
+ SOF_WIDGET_FREE);
+ sof_walk_widgets_in_order(sdev, spcm, NULL, NULL, dir, SOF_WIDGET_UNPREPARE);
+
+ return ret;
+}
+
+int sof_widget_list_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir)
+{
+ struct snd_sof_pcm_stream_pipeline_list *pipeline_list = &spcm->stream[dir].pipeline_list;
+ struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list;
+ int ret;
+
+ /* nothing to free */
+ if (!list)
+ return 0;
+
+ /* send IPC to free widget in the DSP */
+ ret = sof_walk_widgets_in_order(sdev, spcm, NULL, NULL, dir, SOF_WIDGET_FREE);
+
+ /* unprepare the widget */
+ sof_walk_widgets_in_order(sdev, spcm, NULL, NULL, dir, SOF_WIDGET_UNPREPARE);
+
+ snd_soc_dapm_dai_free_widgets(&list);
+ spcm->stream[dir].list = NULL;
+
+ pipeline_list->count = 0;
+
+ return ret;
+}
+
+/*
+ * helper to determine if there are only D0i3 compatible
+ * streams active
+ */
+bool snd_sof_dsp_only_d0i3_compatible_stream_active(struct snd_sof_dev *sdev)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_sof_pcm *spcm;
+ bool d0i3_compatible_active = false;
+ int dir;
+
+ list_for_each_entry(spcm, &sdev->pcm_list, list) {
+ for_each_pcm_streams(dir) {
+ substream = spcm->stream[dir].substream;
+ if (!substream || !substream->runtime)
+ continue;
+
+ /*
+ * substream->runtime being not NULL indicates
+ * that the stream is open. No need to check the
+ * stream state.
+ */
+ if (!spcm->stream[dir].d0i3_compatible)
+ return false;
+
+ d0i3_compatible_active = true;
}
}
- /* complete pipeline */
- list_for_each_entry(swidget, &sdev->widget_list, list) {
- switch (swidget->id) {
- case snd_soc_dapm_scheduler:
- swidget->complete =
- snd_sof_complete_pipeline(dev, swidget);
- break;
- default:
- break;
+ return d0i3_compatible_active;
+}
+EXPORT_SYMBOL(snd_sof_dsp_only_d0i3_compatible_stream_active);
+
+bool snd_sof_stream_suspend_ignored(struct snd_sof_dev *sdev)
+{
+ struct snd_sof_pcm *spcm;
+
+ list_for_each_entry(spcm, &sdev->pcm_list, list) {
+ if (spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].suspend_ignored ||
+ spcm->stream[SNDRV_PCM_STREAM_CAPTURE].suspend_ignored)
+ return true;
+ }
+
+ return false;
+}
+
+int sof_pcm_stream_free(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream,
+ struct snd_sof_pcm *spcm, int dir, bool free_widget_list)
+{
+ const struct sof_ipc_pcm_ops *pcm_ops = sof_ipc_get_ops(sdev, pcm);
+ int ret;
+
+ if (spcm->prepared[substream->stream]) {
+ /* stop DMA first if needed */
+ if (pcm_ops && pcm_ops->platform_stop_during_hw_free)
+ snd_sof_pcm_platform_trigger(sdev, substream, SNDRV_PCM_TRIGGER_STOP);
+
+ /* Send PCM_FREE IPC to reset pipeline */
+ if (pcm_ops && pcm_ops->hw_free) {
+ ret = pcm_ops->hw_free(sdev->component, substream);
+ if (ret < 0)
+ return ret;
}
+
+ spcm->prepared[substream->stream] = false;
}
- /* restore pipeline kcontrols */
- ret = sof_restore_kcontrols(dev);
+ /* reset the DMA */
+ ret = snd_sof_pcm_platform_hw_free(sdev, substream);
if (ret < 0)
- dev_err(dev,
- "error: restoring kcontrols after resume\n");
+ return ret;
+
+ /* free widget list */
+ if (free_widget_list) {
+ ret = sof_widget_list_free(sdev, spcm, dir);
+ if (ret < 0)
+ dev_err(sdev->dev, "failed to free widgets during suspend\n");
+ }
return ret;
}
@@ -330,20 +914,6 @@ struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_soc_component *scomp,
return NULL;
}
-struct snd_sof_pcm *snd_sof_find_spcm_pcm_id(struct snd_soc_component *scomp,
- unsigned int pcm_id)
-{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct snd_sof_pcm *spcm;
-
- list_for_each_entry(spcm, &sdev->pcm_list, list) {
- if (le32_to_cpu(spcm->pcm.pcm_id) == pcm_id)
- return spcm;
- }
-
- return NULL;
-}
-
struct snd_sof_widget *snd_sof_find_swidget(struct snd_soc_component *scomp,
const char *name)
{
@@ -395,86 +965,41 @@ struct snd_sof_dai *snd_sof_find_dai(struct snd_soc_component *scomp,
return NULL;
}
-/*
- * SOF Driver enumeration.
- */
-int sof_machine_check(struct snd_sof_dev *sdev)
+static int sof_dai_get_clk(struct snd_soc_pcm_runtime *rtd, int clk_type)
{
- struct snd_sof_pdata *sof_pdata = sdev->pdata;
- const struct sof_dev_desc *desc = sof_pdata->desc;
- struct snd_soc_acpi_mach *mach;
- int ret;
-
- /* force nocodec mode */
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_FORCE_NOCODEC_MODE)
- dev_warn(sdev->dev, "Force to use nocodec mode\n");
- goto nocodec;
-#endif
-
- /* find machine */
- snd_sof_machine_select(sdev);
- if (sof_pdata->machine) {
- snd_sof_set_mach_params(sof_pdata->machine, sdev->dev);
+ struct snd_soc_component *component =
+ snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME);
+ struct snd_sof_dai *dai =
+ snd_sof_find_dai(component, (char *)rtd->dai_link->name);
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
+
+ /* use the tplg configured mclk if existed */
+ if (!dai)
return 0;
- }
-
-#if !IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC)
- dev_err(sdev->dev, "error: no matching ASoC machine driver found - aborting probe\n");
- return -ENODEV;
-#endif
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_FORCE_NOCODEC_MODE)
-nocodec:
-#endif
- /* select nocodec mode */
- dev_warn(sdev->dev, "Using nocodec machine driver\n");
- mach = devm_kzalloc(sdev->dev, sizeof(*mach), GFP_KERNEL);
- if (!mach)
- return -ENOMEM;
- mach->drv_name = "sof-nocodec";
- sof_pdata->tplg_filename = desc->nocodec_tplg_filename;
-
- ret = sof_nocodec_setup(sdev->dev, desc->ops);
- if (ret < 0)
- return ret;
-
- sof_pdata->machine = mach;
- snd_sof_set_mach_params(sof_pdata->machine, sdev->dev);
+ if (tplg_ops && tplg_ops->dai_get_clk)
+ return tplg_ops->dai_get_clk(sdev, dai, clk_type);
return 0;
}
-EXPORT_SYMBOL(sof_machine_check);
-int sof_machine_register(struct snd_sof_dev *sdev, void *pdata)
+/*
+ * Helper to get SSP MCLK from a pcm_runtime.
+ * Return 0 if not exist.
+ */
+int sof_dai_get_mclk(struct snd_soc_pcm_runtime *rtd)
{
- struct snd_sof_pdata *plat_data = (struct snd_sof_pdata *)pdata;
- const char *drv_name;
- const void *mach;
- int size;
-
- drv_name = plat_data->machine->drv_name;
- mach = (const void *)plat_data->machine;
- size = sizeof(*plat_data->machine);
-
- /* register machine driver, pass machine info as pdata */
- plat_data->pdev_mach =
- platform_device_register_data(sdev->dev, drv_name,
- PLATFORM_DEVID_NONE, mach, size);
- if (IS_ERR(plat_data->pdev_mach))
- return PTR_ERR(plat_data->pdev_mach);
-
- dev_dbg(sdev->dev, "created machine %s\n",
- dev_name(&plat_data->pdev_mach->dev));
-
- return 0;
+ return sof_dai_get_clk(rtd, SOF_DAI_CLK_INTEL_SSP_MCLK);
}
-EXPORT_SYMBOL(sof_machine_register);
+EXPORT_SYMBOL(sof_dai_get_mclk);
-void sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata)
+/*
+ * Helper to get SSP BCLK from a pcm_runtime.
+ * Return 0 if not exist.
+ */
+int sof_dai_get_bclk(struct snd_soc_pcm_runtime *rtd)
{
- struct snd_sof_pdata *plat_data = (struct snd_sof_pdata *)pdata;
-
- if (!IS_ERR_OR_NULL(plat_data->pdev_mach))
- platform_device_unregister(plat_data->pdev_mach);
+ return sof_dai_get_clk(rtd, SOF_DAI_CLK_INTEL_SSP_BCLK);
}
-EXPORT_SYMBOL(sof_machine_unregister);
+EXPORT_SYMBOL(sof_dai_get_bclk);
diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h
index 9629994fe463..9ea2ac5adac7 100644
--- a/sound/soc/sof/sof-audio.h
+++ b/sound/soc/sof/sof-audio.h
@@ -23,24 +23,314 @@
#define SOF_AUDIO_PCM_DRV_NAME "sof-audio-component"
+/*
+ * The ipc4 firmware only supports up to 8 sink or source pins
+ * per widget, because only 3 bits are used for queue(pin) ID
+ * in ipc4 protocol.
+ */
+#define SOF_WIDGET_MAX_NUM_PINS 8
+
+/* Widget pin type */
+#define SOF_PIN_TYPE_INPUT 0
+#define SOF_PIN_TYPE_OUTPUT 1
+
/* max number of FE PCMs before BEs */
#define SOF_BE_PCM_BASE 16
#define DMA_CHAN_INVALID 0xFFFFFFFF
+#define WIDGET_IS_DAI(id) ((id) == snd_soc_dapm_dai_in || (id) == snd_soc_dapm_dai_out)
+#define WIDGET_IS_AIF(id) ((id) == snd_soc_dapm_aif_in || (id) == snd_soc_dapm_aif_out)
+#define WIDGET_IS_AIF_OR_DAI(id) (WIDGET_IS_DAI(id) || WIDGET_IS_AIF(id))
+#define WIDGET_IS_COPIER(id) (WIDGET_IS_AIF_OR_DAI(id) || (id) == snd_soc_dapm_buffer)
+
+#define SOF_DAI_CLK_INTEL_SSP_MCLK 0
+#define SOF_DAI_CLK_INTEL_SSP_BCLK 1
+
+enum sof_widget_op {
+ SOF_WIDGET_PREPARE,
+ SOF_WIDGET_SETUP,
+ SOF_WIDGET_FREE,
+ SOF_WIDGET_UNPREPARE,
+};
+
+/*
+ * Volume fractional word length define to 16 sets
+ * the volume linear gain value to use Qx.16 format
+ */
+#define VOLUME_FWL 16
+
+#define SOF_TLV_ITEMS 3
+
+static inline u32 mixer_to_ipc(unsigned int value, u32 *volume_map, int size)
+{
+ if (value >= size)
+ return volume_map[size - 1];
+
+ return volume_map[value];
+}
+
+static inline u32 ipc_to_mixer(u32 value, u32 *volume_map, int size)
+{
+ int i;
+
+ for (i = 0; i < size; i++) {
+ if (volume_map[i] >= value)
+ return i;
+ }
+
+ return i - 1;
+}
+
+struct snd_sof_widget;
+struct snd_sof_route;
+struct snd_sof_control;
+struct snd_sof_dai;
+struct snd_sof_pcm;
+
+struct snd_sof_dai_config_data {
+ int dai_index;
+ int dai_data; /* contains DAI-specific information */
+};
+
+/**
+ * struct sof_ipc_pcm_ops - IPC-specific PCM ops
+ * @hw_params: Function pointer for hw_params
+ * @hw_free: Function pointer for hw_free
+ * @trigger: Function pointer for trigger
+ * @dai_link_fixup: Function pointer for DAI link fixup
+ * @pcm_setup: Function pointer for IPC-specific PCM set up that can be used for allocating
+ * additional memory in the SOF PCM stream structure
+ * @pcm_free: Function pointer for PCM free that can be used for freeing any
+ * additional memory in the SOF PCM stream structure
+ * @delay: Function pointer for pcm delay calculation
+ * @reset_hw_params_during_stop: Flag indicating whether the hw_params should be reset during the
+ * STOP pcm trigger
+ * @ipc_first_on_start: Send IPC before invoking platform trigger during
+ * START/PAUSE_RELEASE triggers
+ * @platform_stop_during_hw_free: Invoke the platform trigger during hw_free. This is needed for
+ * IPC4 where a pipeline is only paused during stop/pause/suspend
+ * triggers. The FW keeps the host DMA running in this case and
+ * therefore the host must do the same and should stop the DMA during
+ * hw_free.
+ */
+struct sof_ipc_pcm_ops {
+ int (*hw_params)(struct snd_soc_component *component, struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_sof_platform_stream_params *platform_params);
+ int (*hw_free)(struct snd_soc_component *component, struct snd_pcm_substream *substream);
+ int (*trigger)(struct snd_soc_component *component, struct snd_pcm_substream *substream,
+ int cmd);
+ int (*dai_link_fixup)(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params);
+ int (*pcm_setup)(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm);
+ void (*pcm_free)(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm);
+ snd_pcm_sframes_t (*delay)(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream);
+ bool reset_hw_params_during_stop;
+ bool ipc_first_on_start;
+ bool platform_stop_during_hw_free;
+};
+
+/**
+ * struct sof_ipc_tplg_control_ops - IPC-specific ops for topology kcontrol IO
+ */
+struct sof_ipc_tplg_control_ops {
+ bool (*volume_put)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol);
+ int (*volume_get)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol);
+ bool (*switch_put)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol);
+ int (*switch_get)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol);
+ bool (*enum_put)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol);
+ int (*enum_get)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol);
+ int (*bytes_put)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol);
+ int (*bytes_get)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol);
+ int (*bytes_ext_get)(struct snd_sof_control *scontrol,
+ const unsigned int __user *binary_data, unsigned int size);
+ int (*bytes_ext_volatile_get)(struct snd_sof_control *scontrol,
+ const unsigned int __user *binary_data, unsigned int size);
+ int (*bytes_ext_put)(struct snd_sof_control *scontrol,
+ const unsigned int __user *binary_data, unsigned int size);
+ /* update control data based on notification from the DSP */
+ void (*update)(struct snd_sof_dev *sdev, void *ipc_control_message);
+ /* Optional callback to setup kcontrols associated with an swidget */
+ int (*widget_kcontrol_setup)(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget);
+ /* mandatory callback to set up volume table for volume kcontrols */
+ int (*set_up_volume_table)(struct snd_sof_control *scontrol, int tlv[SOF_TLV_ITEMS],
+ int size);
+};
+
+/**
+ * struct sof_ipc_tplg_widget_ops - IPC-specific ops for topology widgets
+ * @ipc_setup: Function pointer for setting up widget IPC params
+ * @ipc_free: Function pointer for freeing widget IPC params
+ * @token_list: List of token ID's that should be parsed for the widget
+ * @token_list_size: number of elements in token_list
+ * @bind_event: Function pointer for binding events to the widget
+ * @ipc_prepare: Optional op for preparing a widget for set up
+ * @ipc_unprepare: Optional op for unpreparing a widget
+ */
+struct sof_ipc_tplg_widget_ops {
+ int (*ipc_setup)(struct snd_sof_widget *swidget);
+ void (*ipc_free)(struct snd_sof_widget *swidget);
+ enum sof_tokens *token_list;
+ int token_list_size;
+ int (*bind_event)(struct snd_soc_component *scomp, struct snd_sof_widget *swidget,
+ u16 event_type);
+ int (*ipc_prepare)(struct snd_sof_widget *swidget,
+ struct snd_pcm_hw_params *fe_params,
+ struct snd_sof_platform_stream_params *platform_params,
+ struct snd_pcm_hw_params *source_params, int dir);
+ void (*ipc_unprepare)(struct snd_sof_widget *swidget);
+};
+
+/**
+ * struct sof_ipc_tplg_ops - IPC-specific topology ops
+ * @widget: Array of pointers to IPC-specific ops for widgets. This should always be of size
+ * SND_SOF_DAPM_TYPE_COUNT i.e one per widget type. Unsupported widget types will be
+ * initialized to 0.
+ * @control: Pointer to the IPC-specific ops for topology kcontrol IO
+ * @route_setup: Function pointer for setting up pipeline connections
+ * @route_free: Function pointer for freeing pipeline connections.
+ * @token_list: List of all tokens supported by the IPC version. The size of the token_list
+ * array should be SOF_TOKEN_COUNT. The unused elements in the array will be
+ * initialized to 0.
+ * @control_setup: Function pointer for setting up kcontrol IPC-specific data
+ * @control_free: Function pointer for freeing kcontrol IPC-specific data
+ * @pipeline_complete: Function pointer for pipeline complete IPC
+ * @widget_setup: Function pointer for setting up setup in the DSP
+ * @widget_free: Function pointer for freeing widget in the DSP
+ * @dai_config: Function pointer for sending DAI config IPC to the DSP
+ * @dai_get_clk: Function pointer for getting the DAI clock setting
+ * @set_up_all_pipelines: Function pointer for setting up all topology pipelines
+ * @tear_down_all_pipelines: Function pointer for tearing down all topology pipelines
+ * @parse_manifest: Function pointer for ipc4 specific parsing of topology manifest
+ * @link_setup: Function pointer for IPC-specific DAI link set up
+ *
+ * Note: function pointers (ops) are optional
+ */
+struct sof_ipc_tplg_ops {
+ const struct sof_ipc_tplg_widget_ops *widget;
+ const struct sof_ipc_tplg_control_ops *control;
+ int (*route_setup)(struct snd_sof_dev *sdev, struct snd_sof_route *sroute);
+ int (*route_free)(struct snd_sof_dev *sdev, struct snd_sof_route *sroute);
+ const struct sof_token_info *token_list;
+ int (*control_setup)(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol);
+ int (*control_free)(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol);
+ int (*pipeline_complete)(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget);
+ int (*widget_setup)(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget);
+ int (*widget_free)(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget);
+ int (*dai_config)(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget,
+ unsigned int flags, struct snd_sof_dai_config_data *data);
+ int (*dai_get_clk)(struct snd_sof_dev *sdev, struct snd_sof_dai *dai, int clk_type);
+ int (*set_up_all_pipelines)(struct snd_sof_dev *sdev, bool verify);
+ int (*tear_down_all_pipelines)(struct snd_sof_dev *sdev, bool verify);
+ int (*parse_manifest)(struct snd_soc_component *scomp, int index,
+ struct snd_soc_tplg_manifest *man);
+ int (*link_setup)(struct snd_sof_dev *sdev, struct snd_soc_dai_link *link);
+};
+
+/** struct snd_sof_tuple - Tuple info
+ * @token: Token ID
+ * @value: union of a string or a u32 values
+ */
+struct snd_sof_tuple {
+ u32 token;
+ union {
+ u32 v;
+ const char *s;
+ } value;
+};
+
+/*
+ * List of SOF token ID's. The order of ID's does not matter as token arrays are looked up based on
+ * the ID.
+ */
+enum sof_tokens {
+ SOF_PCM_TOKENS,
+ SOF_PIPELINE_TOKENS,
+ SOF_SCHED_TOKENS,
+ SOF_ASRC_TOKENS,
+ SOF_SRC_TOKENS,
+ SOF_COMP_TOKENS,
+ SOF_BUFFER_TOKENS,
+ SOF_VOLUME_TOKENS,
+ SOF_PROCESS_TOKENS,
+ SOF_DAI_TOKENS,
+ SOF_DAI_LINK_TOKENS,
+ SOF_HDA_TOKENS,
+ SOF_SSP_TOKENS,
+ SOF_ALH_TOKENS,
+ SOF_DMIC_TOKENS,
+ SOF_DMIC_PDM_TOKENS,
+ SOF_ESAI_TOKENS,
+ SOF_SAI_TOKENS,
+ SOF_AFE_TOKENS,
+ SOF_CORE_TOKENS,
+ SOF_COMP_EXT_TOKENS,
+ SOF_IN_AUDIO_FORMAT_TOKENS,
+ SOF_OUT_AUDIO_FORMAT_TOKENS,
+ SOF_COPIER_DEEP_BUFFER_TOKENS,
+ SOF_COPIER_TOKENS,
+ SOF_AUDIO_FMT_NUM_TOKENS,
+ SOF_COPIER_FORMAT_TOKENS,
+ SOF_GAIN_TOKENS,
+ SOF_ACPDMIC_TOKENS,
+ SOF_ACPI2S_TOKENS,
+ SOF_MICFIL_TOKENS,
+ SOF_ACP_SDW_TOKENS,
+
+ /* this should be the last */
+ SOF_TOKEN_COUNT,
+};
+
+/**
+ * struct sof_topology_token - SOF topology token definition
+ * @token: Token number
+ * @type: Token type
+ * @get_token: Function pointer to parse the token value and save it in a object
+ * @offset: Offset within an object to save the token value into
+ */
+struct sof_topology_token {
+ u32 token;
+ u32 type;
+ int (*get_token)(void *elem, void *object, u32 offset);
+ u32 offset;
+};
+
+struct sof_token_info {
+ const char *name;
+ const struct sof_topology_token *tokens;
+ int count;
+};
+
+/**
+ * struct snd_sof_pcm_stream_pipeline_list - List of pipelines associated with a PCM stream
+ * @count: number of pipeline widgets in the @pipe_widgets array
+ * @pipelines: array of pipelines
+ */
+struct snd_sof_pcm_stream_pipeline_list {
+ u32 count;
+ struct snd_sof_pipeline **pipelines;
+};
+
/* PCM stream, mapped to FW component */
struct snd_sof_pcm_stream {
u32 comp_id;
struct snd_dma_buffer page_table;
struct sof_ipc_stream_posn posn;
struct snd_pcm_substream *substream;
+ struct snd_compr_stream *cstream;
struct work_struct period_elapsed_work;
+ struct snd_soc_dapm_widget_list *list; /* list of connected DAPM widgets */
bool d0i3_compatible; /* DSP can be in D0I3 when this pcm is opened */
/*
* flag to indicate that the DSP pipelines should be kept
* active or not while suspending the stream
*/
bool suspend_ignored;
+ struct snd_sof_pcm_stream_pipeline_list pipeline_list;
+
+ /* used by IPC implementation and core does not touch it */
+ void *private;
};
/* ALSA SOF PCM device */
@@ -62,19 +352,50 @@ struct snd_sof_led_control {
/* ALSA SOF Kcontrol device */
struct snd_sof_control {
struct snd_soc_component *scomp;
+ const char *name;
int comp_id;
int min_volume_step; /* min volume step for volume_table */
int max_volume_step; /* max volume step for volume_table */
int num_channels;
- u32 readback_offset; /* offset to mmapped data if used */
- struct sof_ipc_ctrl_data *control_data;
+ unsigned int access;
+ int info_type;
+ int index; /* pipeline ID */
+ void *priv; /* private data copied from topology */
+ size_t priv_size; /* size of private data */
+ size_t max_size;
+ void *ipc_control_data;
+ void *old_ipc_control_data;
+ int max; /* applicable to volume controls */
u32 size; /* cdata size */
- enum sof_ipc_ctrl_cmd cmd;
u32 *volume_table; /* volume table computed from tlv data*/
struct list_head list; /* list in sdev control list */
struct snd_sof_led_control led_ctl;
+
+ /* if true, the control's data needs to be updated from Firmware */
+ bool comp_data_dirty;
+};
+
+/** struct snd_sof_dai_link - DAI link info
+ * @tuples: array of parsed tuples
+ * @num_tuples: number of tuples in the tuples array
+ * @link: Pointer to snd_soc_dai_link
+ * @hw_configs: Pointer to hw configs in topology
+ * @num_hw_configs: Number of hw configs in topology
+ * @default_hw_cfg_id: Default hw config ID
+ * @type: DAI type
+ * @list: item in snd_sof_dev dai_link list
+ */
+struct snd_sof_dai_link {
+ struct snd_sof_tuple *tuples;
+ int num_tuples;
+ struct snd_soc_dai_link *link;
+ struct snd_soc_tplg_hw_config *hw_configs;
+ int num_hw_configs;
+ int default_hw_cfg_id;
+ int type;
+ struct list_head list;
};
/* ASoC SOF DAPM widget */
@@ -82,21 +403,109 @@ struct snd_sof_widget {
struct snd_soc_component *scomp;
int comp_id;
int pipeline_id;
- int complete;
- int id;
+ /*
+ * the prepared flag is used to indicate that a widget has been prepared for getting set
+ * up in the DSP.
+ */
+ bool prepared;
+
+ struct mutex setup_mutex; /* to protect the swidget setup and free operations */
+
+ /*
+ * use_count is protected by the PCM mutex held by the core and the
+ * setup_mutex against non stream domain races (kcontrol access for
+ * example)
+ */
+ int use_count;
+
+ int core;
+ int id; /* id is the DAPM widget type */
+ /*
+ * Instance ID is set dynamically when the widget gets set up in the FW. It should be
+ * unique for each module type across all pipelines. This will not be used in SOF_IPC.
+ */
+ int instance_id;
+
+ /*
+ * Flag indicating if the widget should be set up dynamically when a PCM is opened.
+ * This flag is only set for the scheduler type widget in topology. During topology
+ * loading, this flag is propagated to all the widgets belonging to the same pipeline.
+ * When this flag is not set, a widget is set up at the time of topology loading
+ * and retained until the DSP enters D3. It will need to be set up again when resuming
+ * from D3.
+ */
+ bool dynamic_pipeline_widget;
struct snd_soc_dapm_widget *widget;
struct list_head list; /* list in sdev widget list */
+ struct snd_sof_pipeline *spipe;
+ void *module_info;
+
+ const guid_t uuid;
+
+ int num_tuples;
+ struct snd_sof_tuple *tuples;
+
+ /*
+ * The allowed range for num_input/output_pins is [0, SOF_WIDGET_MAX_NUM_PINS].
+ * Widgets may have zero input or output pins, for example the tone widget has
+ * zero input pins.
+ */
+ u32 num_input_pins;
+ u32 num_output_pins;
+
+ /*
+ * The input/output pin binding array, it takes the form of
+ * [widget_name_connected_to_pin0, widget_name_connected_to_pin1, ...],
+ * with the index as the queue ID.
+ *
+ * The array is used for special pin binding. Note that even if there
+ * is only one input/output pin requires special pin binding, pin binding
+ * should be defined for all input/output pins in topology, for pin(s) that
+ * are not used, give the value "NotConnected".
+ *
+ * If pin binding is not defined in topology, nothing to parse in the kernel,
+ * input_pin_binding and output_pin_binding shall be NULL.
+ */
+ char **input_pin_binding;
+ char **output_pin_binding;
+
+ struct ida output_queue_ida;
+ struct ida input_queue_ida;
void *private; /* core does not touch this */
};
+/** struct snd_sof_pipeline - ASoC SOF pipeline
+ * @pipe_widget: Pointer to the pipeline widget
+ * @started_count: Count of number of PCM's that have started this pipeline
+ * @paused_count: Count of number of PCM's that have started and have currently paused this
+ pipeline
+ * @complete: flag used to indicate that pipeline set up is complete.
+ * @core_mask: Mask containing target cores for all modules in the pipeline
+ * @list: List item in sdev pipeline_list
+ */
+struct snd_sof_pipeline {
+ struct snd_sof_widget *pipe_widget;
+ int started_count;
+ int paused_count;
+ int complete;
+ unsigned long core_mask;
+ struct list_head list;
+};
+
/* ASoC SOF DAPM route */
struct snd_sof_route {
struct snd_soc_component *scomp;
struct snd_soc_dapm_route *route;
struct list_head list; /* list in sdev route list */
+ struct snd_sof_widget *src_widget;
+ struct snd_sof_widget *sink_widget;
+ bool setup;
+
+ int src_queue_id;
+ int dst_queue_id;
void *private;
};
@@ -105,11 +514,14 @@ struct snd_sof_route {
struct snd_sof_dai {
struct snd_soc_component *scomp;
const char *name;
- const char *cpu_dai_name;
+ u32 type;
- struct sof_ipc_comp_dai comp_dai;
- struct sof_ipc_dai_config *dai_config;
+ int number_configs;
+ int current_config;
struct list_head list; /* list in sdev dai list */
+ /* core should not touch this */
+ const void *platform_private;
+ void *private;
};
/*
@@ -120,6 +532,8 @@ int snd_sof_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int snd_sof_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
+int snd_sof_volume_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo);
int snd_sof_switch_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
int snd_sof_switch_put(struct snd_kcontrol *kcontrol,
@@ -138,6 +552,10 @@ int snd_sof_bytes_ext_put(struct snd_kcontrol *kcontrol,
int snd_sof_bytes_ext_get(struct snd_kcontrol *kcontrol,
unsigned int __user *binary_data,
unsigned int size);
+int snd_sof_bytes_ext_volatile_get(struct snd_kcontrol *kcontrol, unsigned int __user *binary_data,
+ unsigned int size);
+void snd_sof_control_notify(struct snd_sof_dev *sdev,
+ struct sof_ipc_ctrl_data *cdata);
/*
* Topology.
@@ -145,12 +563,6 @@ int snd_sof_bytes_ext_get(struct snd_kcontrol *kcontrol,
* be freed by snd_soc_unregister_component,
*/
int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file);
-int snd_sof_complete_pipeline(struct device *dev,
- struct snd_sof_widget *swidget);
-
-int sof_load_pipeline_ipc(struct device *dev,
- struct sof_ipc_pipe_new *pipeline,
- struct sof_ipc_comp_reply *r);
/*
* Stream IPC
@@ -172,8 +584,7 @@ struct snd_sof_pcm *snd_sof_find_spcm_dai(struct snd_soc_component *scomp,
struct snd_soc_pcm_runtime *rtd)
{
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
-
- struct snd_sof_pcm *spcm = NULL;
+ struct snd_sof_pcm *spcm;
list_for_each_entry(spcm, &sdev->pcm_list, list) {
if (le32_to_cpu(spcm->pcm.dai_id) == rtd->dai_link->id)
@@ -188,23 +599,21 @@ struct snd_sof_pcm *snd_sof_find_spcm_name(struct snd_soc_component *scomp,
struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_soc_component *scomp,
unsigned int comp_id,
int *direction);
-struct snd_sof_pcm *snd_sof_find_spcm_pcm_id(struct snd_soc_component *scomp,
- unsigned int pcm_id);
void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream);
-void snd_sof_pcm_period_elapsed_work(struct work_struct *work);
+void snd_sof_pcm_init_elapsed_work(struct work_struct *work);
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS)
+void snd_sof_compr_fragment_elapsed(struct snd_compr_stream *cstream);
+void snd_sof_compr_init_elapsed_work(struct work_struct *work);
+#else
+static inline void snd_sof_compr_fragment_elapsed(struct snd_compr_stream *cstream) { }
+static inline void snd_sof_compr_init_elapsed_work(struct work_struct *work) { }
+#endif
-/*
- * Mixer IPC
- */
-int snd_sof_ipc_set_get_comp_data(struct snd_sof_control *scontrol,
- u32 ipc_cmd,
- enum sof_ipc_ctrl_type ctrl_type,
- enum sof_ipc_ctrl_cmd ctrl_cmd,
- bool send);
+/* DAI link fixup */
+int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params);
/* PM */
-int sof_restore_pipelines(struct device *dev);
-int sof_set_hw_params_upon_resume(struct device *dev);
bool snd_sof_stream_suspend_ignored(struct snd_sof_dev *sdev);
bool snd_sof_dsp_only_d0i3_compatible_stream_active(struct snd_sof_dev *sdev);
@@ -212,4 +621,29 @@ bool snd_sof_dsp_only_d0i3_compatible_stream_active(struct snd_sof_dev *sdev);
int sof_machine_register(struct snd_sof_dev *sdev, void *pdata);
void sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata);
+int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget);
+int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget);
+int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsource,
+ struct snd_soc_dapm_widget *wsink);
+
+/* PCM */
+int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm,
+ struct snd_pcm_hw_params *fe_params,
+ struct snd_sof_platform_stream_params *platform_params,
+ int dir);
+int sof_widget_list_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir);
+int sof_pcm_dsp_pcm_free(struct snd_pcm_substream *substream, struct snd_sof_dev *sdev,
+ struct snd_sof_pcm *spcm);
+int sof_pcm_stream_free(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream,
+ struct snd_sof_pcm *spcm, int dir, bool free_widget_list);
+int get_token_u32(void *elem, void *object, u32 offset);
+int get_token_u16(void *elem, void *object, u32 offset);
+int get_token_comp_format(void *elem, void *object, u32 offset);
+int get_token_dai_type(void *elem, void *object, u32 offset);
+int get_token_uuid(void *elem, void *object, u32 offset);
+int get_token_string(void *elem, void *object, u32 offset);
+int sof_update_ipc_object(struct snd_soc_component *scomp, void *object, enum sof_tokens token_id,
+ struct snd_sof_tuple *tuples, int num_tuples,
+ size_t object_size, int token_instance_num);
+u32 vol_compute_gain(u32 value, int *tlv);
#endif
diff --git a/sound/soc/sof/sof-client-ipc-flood-test.c b/sound/soc/sof/sof-client-ipc-flood-test.c
new file mode 100644
index 000000000000..c0d6723aed59
--- /dev/null
+++ b/sound/soc/sof/sof-client-ipc-flood-test.c
@@ -0,0 +1,394 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2022 Intel Corporation. All rights reserved.
+//
+// Authors: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
+// Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
+//
+
+#include <linux/auxiliary_bus.h>
+#include <linux/completion.h>
+#include <linux/debugfs.h>
+#include <linux/ktime.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <sound/sof/header.h>
+
+#include "sof-client.h"
+
+#define MAX_IPC_FLOOD_DURATION_MS 1000
+#define MAX_IPC_FLOOD_COUNT 10000
+#define IPC_FLOOD_TEST_RESULT_LEN 512
+#define SOF_IPC_CLIENT_SUSPEND_DELAY_MS 3000
+
+#define DEBUGFS_IPC_FLOOD_COUNT "ipc_flood_count"
+#define DEBUGFS_IPC_FLOOD_DURATION "ipc_flood_duration_ms"
+
+struct sof_ipc_flood_priv {
+ struct dentry *dfs_root;
+ struct dentry *dfs_link[2];
+ char *buf;
+};
+
+static int sof_ipc_flood_dfs_open(struct inode *inode, struct file *file)
+{
+ struct sof_client_dev *cdev = inode->i_private;
+ int ret;
+
+ if (sof_client_get_fw_state(cdev) == SOF_FW_CRASHED)
+ return -ENODEV;
+
+ ret = debugfs_file_get(file->f_path.dentry);
+ if (unlikely(ret))
+ return ret;
+
+ ret = simple_open(inode, file);
+ if (ret)
+ debugfs_file_put(file->f_path.dentry);
+
+ return ret;
+}
+
+/*
+ * helper function to perform the flood test. Only one of the two params, ipc_duration_ms
+ * or ipc_count, will be non-zero and will determine the type of test
+ */
+static int sof_debug_ipc_flood_test(struct sof_client_dev *cdev,
+ bool flood_duration_test,
+ unsigned long ipc_duration_ms,
+ unsigned long ipc_count)
+{
+ struct sof_ipc_flood_priv *priv = cdev->data;
+ struct device *dev = &cdev->auxdev.dev;
+ struct sof_ipc_cmd_hdr hdr;
+ u64 min_response_time = U64_MAX;
+ ktime_t start, end, test_end;
+ u64 avg_response_time = 0;
+ u64 max_response_time = 0;
+ u64 ipc_response_time;
+ int i = 0;
+ int ret;
+
+ /* configure test IPC */
+ hdr.cmd = SOF_IPC_GLB_TEST_MSG | SOF_IPC_TEST_IPC_FLOOD;
+ hdr.size = sizeof(hdr);
+
+ /* set test end time for duration flood test */
+ if (flood_duration_test)
+ test_end = ktime_get_ns() + ipc_duration_ms * NSEC_PER_MSEC;
+
+ /* send test IPC's */
+ while (1) {
+ start = ktime_get();
+ ret = sof_client_ipc_tx_message_no_reply(cdev, &hdr);
+ end = ktime_get();
+
+ if (ret < 0)
+ break;
+
+ /* compute min and max response times */
+ ipc_response_time = ktime_to_ns(ktime_sub(end, start));
+ min_response_time = min(min_response_time, ipc_response_time);
+ max_response_time = max(max_response_time, ipc_response_time);
+
+ /* sum up response times */
+ avg_response_time += ipc_response_time;
+ i++;
+
+ /* test complete? */
+ if (flood_duration_test) {
+ if (ktime_to_ns(end) >= test_end)
+ break;
+ } else {
+ if (i == ipc_count)
+ break;
+ }
+ }
+
+ if (ret < 0)
+ dev_err(dev, "ipc flood test failed at %d iterations\n", i);
+
+ /* return if the first IPC fails */
+ if (!i)
+ return ret;
+
+ /* compute average response time */
+ do_div(avg_response_time, i);
+
+ /* clear previous test output */
+ memset(priv->buf, 0, IPC_FLOOD_TEST_RESULT_LEN);
+
+ if (!ipc_count) {
+ dev_dbg(dev, "IPC Flood test duration: %lums\n", ipc_duration_ms);
+ snprintf(priv->buf, IPC_FLOOD_TEST_RESULT_LEN,
+ "IPC Flood test duration: %lums\n", ipc_duration_ms);
+ }
+
+ dev_dbg(dev, "IPC Flood count: %d, Avg response time: %lluns\n",
+ i, avg_response_time);
+ dev_dbg(dev, "Max response time: %lluns\n", max_response_time);
+ dev_dbg(dev, "Min response time: %lluns\n", min_response_time);
+
+ /* format output string and save test results */
+ snprintf(priv->buf + strlen(priv->buf),
+ IPC_FLOOD_TEST_RESULT_LEN - strlen(priv->buf),
+ "IPC Flood count: %d\nAvg response time: %lluns\n",
+ i, avg_response_time);
+
+ snprintf(priv->buf + strlen(priv->buf),
+ IPC_FLOOD_TEST_RESULT_LEN - strlen(priv->buf),
+ "Max response time: %lluns\nMin response time: %lluns\n",
+ max_response_time, min_response_time);
+
+ return ret;
+}
+
+/*
+ * Writing to the debugfs entry initiates the IPC flood test based on
+ * the IPC count or the duration specified by the user.
+ */
+static ssize_t sof_ipc_flood_dfs_write(struct file *file, const char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct sof_client_dev *cdev = file->private_data;
+ struct device *dev = &cdev->auxdev.dev;
+ unsigned long ipc_duration_ms = 0;
+ bool flood_duration_test = false;
+ unsigned long ipc_count = 0;
+ struct dentry *dentry;
+ int err;
+ size_t size;
+ char *string;
+ int ret;
+
+ string = kzalloc(count + 1, GFP_KERNEL);
+ if (!string)
+ return -ENOMEM;
+
+ size = simple_write_to_buffer(string, count, ppos, buffer, count);
+
+ /*
+ * write op is only supported for ipc_flood_count or
+ * ipc_flood_duration_ms debugfs entries atm.
+ * ipc_flood_count floods the DSP with the number of IPC's specified.
+ * ipc_duration_ms test floods the DSP for the time specified
+ * in the debugfs entry.
+ */
+ dentry = file->f_path.dentry;
+ if (strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_COUNT) &&
+ strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_DURATION)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (!strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_DURATION))
+ flood_duration_test = true;
+
+ /* test completion criterion */
+ if (flood_duration_test)
+ ret = kstrtoul(string, 0, &ipc_duration_ms);
+ else
+ ret = kstrtoul(string, 0, &ipc_count);
+ if (ret < 0)
+ goto out;
+
+ /* limit max duration/ipc count for flood test */
+ if (flood_duration_test) {
+ if (!ipc_duration_ms) {
+ ret = size;
+ goto out;
+ }
+
+ /* find the minimum. min() is not used to avoid warnings */
+ if (ipc_duration_ms > MAX_IPC_FLOOD_DURATION_MS)
+ ipc_duration_ms = MAX_IPC_FLOOD_DURATION_MS;
+ } else {
+ if (!ipc_count) {
+ ret = size;
+ goto out;
+ }
+
+ /* find the minimum. min() is not used to avoid warnings */
+ if (ipc_count > MAX_IPC_FLOOD_COUNT)
+ ipc_count = MAX_IPC_FLOOD_COUNT;
+ }
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0 && ret != -EACCES) {
+ dev_err_ratelimited(dev, "debugfs write failed to resume %d\n", ret);
+ goto out;
+ }
+
+ /* flood test */
+ ret = sof_debug_ipc_flood_test(cdev, flood_duration_test,
+ ipc_duration_ms, ipc_count);
+
+ pm_runtime_mark_last_busy(dev);
+ err = pm_runtime_put_autosuspend(dev);
+ if (err < 0)
+ dev_err_ratelimited(dev, "debugfs write failed to idle %d\n", err);
+
+ /* return size if test is successful */
+ if (ret >= 0)
+ ret = size;
+out:
+ kfree(string);
+ return ret;
+}
+
+/* return the result of the last IPC flood test */
+static ssize_t sof_ipc_flood_dfs_read(struct file *file, char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct sof_client_dev *cdev = file->private_data;
+ struct sof_ipc_flood_priv *priv = cdev->data;
+ size_t size_ret;
+
+ struct dentry *dentry;
+
+ dentry = file->f_path.dentry;
+ if (!strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_COUNT) ||
+ !strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_DURATION)) {
+ if (*ppos)
+ return 0;
+
+ count = min_t(size_t, count, strlen(priv->buf));
+ size_ret = copy_to_user(buffer, priv->buf, count);
+ if (size_ret)
+ return -EFAULT;
+
+ *ppos += count;
+ return count;
+ }
+ return count;
+}
+
+static int sof_ipc_flood_dfs_release(struct inode *inode, struct file *file)
+{
+ debugfs_file_put(file->f_path.dentry);
+
+ return 0;
+}
+
+static const struct file_operations sof_ipc_flood_fops = {
+ .open = sof_ipc_flood_dfs_open,
+ .read = sof_ipc_flood_dfs_read,
+ .llseek = default_llseek,
+ .write = sof_ipc_flood_dfs_write,
+ .release = sof_ipc_flood_dfs_release,
+
+ .owner = THIS_MODULE,
+};
+
+/*
+ * The IPC test client creates a couple of debugfs entries that will be used
+ * flood tests. Users can write to these entries to execute the IPC flood test
+ * by specifying either the number of IPCs to flood the DSP with or the duration
+ * (in ms) for which the DSP should be flooded with test IPCs. At the
+ * end of each test, the average, min and max response times are reported back.
+ * The results of the last flood test can be accessed by reading the debugfs
+ * entries.
+ */
+static int sof_ipc_flood_probe(struct auxiliary_device *auxdev,
+ const struct auxiliary_device_id *id)
+{
+ struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev);
+ struct dentry *debugfs_root = sof_client_get_debugfs_root(cdev);
+ struct device *dev = &auxdev->dev;
+ struct sof_ipc_flood_priv *priv;
+
+ /* allocate memory for client data */
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->buf = devm_kmalloc(dev, IPC_FLOOD_TEST_RESULT_LEN, GFP_KERNEL);
+ if (!priv->buf)
+ return -ENOMEM;
+
+ cdev->data = priv;
+
+ /* create debugfs root folder with device name under parent SOF dir */
+ priv->dfs_root = debugfs_create_dir(dev_name(dev), debugfs_root);
+ if (!IS_ERR_OR_NULL(priv->dfs_root)) {
+ /* create read-write ipc_flood_count debugfs entry */
+ debugfs_create_file(DEBUGFS_IPC_FLOOD_COUNT, 0644, priv->dfs_root,
+ cdev, &sof_ipc_flood_fops);
+
+ /* create read-write ipc_flood_duration_ms debugfs entry */
+ debugfs_create_file(DEBUGFS_IPC_FLOOD_DURATION, 0644,
+ priv->dfs_root, cdev, &sof_ipc_flood_fops);
+
+ if (auxdev->id == 0) {
+ /*
+ * Create symlinks for backwards compatibility to the
+ * first IPC flood test instance
+ */
+ char target[100];
+
+ snprintf(target, 100, "%s/" DEBUGFS_IPC_FLOOD_COUNT,
+ dev_name(dev));
+ priv->dfs_link[0] =
+ debugfs_create_symlink(DEBUGFS_IPC_FLOOD_COUNT,
+ debugfs_root, target);
+
+ snprintf(target, 100, "%s/" DEBUGFS_IPC_FLOOD_DURATION,
+ dev_name(dev));
+ priv->dfs_link[1] =
+ debugfs_create_symlink(DEBUGFS_IPC_FLOOD_DURATION,
+ debugfs_root, target);
+ }
+ }
+
+ /* enable runtime PM */
+ pm_runtime_set_autosuspend_delay(dev, SOF_IPC_CLIENT_SUSPEND_DELAY_MS);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_enable(dev);
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_idle(dev);
+
+ return 0;
+}
+
+static void sof_ipc_flood_remove(struct auxiliary_device *auxdev)
+{
+ struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev);
+ struct sof_ipc_flood_priv *priv = cdev->data;
+
+ pm_runtime_disable(&auxdev->dev);
+
+ if (auxdev->id == 0) {
+ debugfs_remove(priv->dfs_link[0]);
+ debugfs_remove(priv->dfs_link[1]);
+ }
+
+ debugfs_remove_recursive(priv->dfs_root);
+}
+
+static const struct auxiliary_device_id sof_ipc_flood_client_id_table[] = {
+ { .name = "snd_sof.ipc_flood" },
+ {},
+};
+MODULE_DEVICE_TABLE(auxiliary, sof_ipc_flood_client_id_table);
+
+/*
+ * No need for driver pm_ops as the generic pm callbacks in the auxiliary bus
+ * type are enough to ensure that the parent SOF device resumes to bring the DSP
+ * back to D0.
+ * Driver name will be set based on KBUILD_MODNAME.
+ */
+static struct auxiliary_driver sof_ipc_flood_client_drv = {
+ .probe = sof_ipc_flood_probe,
+ .remove = sof_ipc_flood_remove,
+
+ .id_table = sof_ipc_flood_client_id_table,
+};
+
+module_auxiliary_driver(sof_ipc_flood_client_drv);
+
+MODULE_DESCRIPTION("SOF IPC Flood Test Client Driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT);
diff --git a/sound/soc/sof/sof-client-ipc-kernel-injector.c b/sound/soc/sof/sof-client-ipc-kernel-injector.c
new file mode 100644
index 000000000000..ad0ed2d570a9
--- /dev/null
+++ b/sound/soc/sof/sof-client-ipc-kernel-injector.c
@@ -0,0 +1,162 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2023 Google Inc. All rights reserved.
+//
+// Author: Curtis Malainey <cujomalainey@chromium.org>
+//
+
+#include <linux/auxiliary_bus.h>
+#include <linux/debugfs.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <sound/sof/header.h>
+
+#include "sof-client.h"
+
+#define SOF_IPC_CLIENT_SUSPEND_DELAY_MS 3000
+
+struct sof_msg_inject_priv {
+ struct dentry *kernel_dfs_file;
+ size_t max_msg_size;
+
+ void *kernel_buffer;
+};
+
+static int sof_msg_inject_dfs_open(struct inode *inode, struct file *file)
+{
+ int ret = debugfs_file_get(file->f_path.dentry);
+
+ if (unlikely(ret))
+ return ret;
+
+ ret = simple_open(inode, file);
+ if (ret)
+ debugfs_file_put(file->f_path.dentry);
+
+ return ret;
+}
+
+static ssize_t sof_kernel_msg_inject_dfs_write(struct file *file, const char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct sof_client_dev *cdev = file->private_data;
+ struct sof_msg_inject_priv *priv = cdev->data;
+ struct sof_ipc_cmd_hdr *hdr = priv->kernel_buffer;
+ struct device *dev = &cdev->auxdev.dev;
+ ssize_t size;
+ int ret;
+
+ if (*ppos)
+ return 0;
+
+ size = simple_write_to_buffer(priv->kernel_buffer, priv->max_msg_size,
+ ppos, buffer, count);
+ if (size < 0)
+ return size;
+ if (size != count)
+ return -EFAULT;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0 && ret != -EACCES) {
+ dev_err_ratelimited(dev, "debugfs write failed to resume %d\n", ret);
+ return ret;
+ }
+
+ sof_client_ipc_rx_message(cdev, hdr, priv->kernel_buffer);
+
+ pm_runtime_mark_last_busy(dev);
+ ret = pm_runtime_put_autosuspend(dev);
+ if (ret < 0)
+ dev_err_ratelimited(dev, "debugfs write failed to idle %d\n", ret);
+
+ return count;
+};
+
+static int sof_msg_inject_dfs_release(struct inode *inode, struct file *file)
+{
+ debugfs_file_put(file->f_path.dentry);
+
+ return 0;
+}
+
+static const struct file_operations sof_kernel_msg_inject_fops = {
+ .open = sof_msg_inject_dfs_open,
+ .write = sof_kernel_msg_inject_dfs_write,
+ .release = sof_msg_inject_dfs_release,
+
+ .owner = THIS_MODULE,
+};
+
+static int sof_msg_inject_probe(struct auxiliary_device *auxdev,
+ const struct auxiliary_device_id *id)
+{
+ struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev);
+ struct dentry *debugfs_root = sof_client_get_debugfs_root(cdev);
+ struct device *dev = &auxdev->dev;
+ struct sof_msg_inject_priv *priv;
+ size_t alloc_size;
+
+ /* allocate memory for client data */
+ priv = devm_kzalloc(&auxdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->max_msg_size = sof_client_get_ipc_max_payload_size(cdev);
+ alloc_size = priv->max_msg_size;
+ priv->kernel_buffer = devm_kmalloc(dev, alloc_size, GFP_KERNEL);
+
+ if (!priv->kernel_buffer)
+ return -ENOMEM;
+
+ cdev->data = priv;
+
+ priv->kernel_dfs_file = debugfs_create_file("kernel_ipc_msg_inject", 0644,
+ debugfs_root, cdev,
+ &sof_kernel_msg_inject_fops);
+
+ /* enable runtime PM */
+ pm_runtime_set_autosuspend_delay(dev, SOF_IPC_CLIENT_SUSPEND_DELAY_MS);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_idle(dev);
+
+ return 0;
+}
+
+static void sof_msg_inject_remove(struct auxiliary_device *auxdev)
+{
+ struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev);
+ struct sof_msg_inject_priv *priv = cdev->data;
+
+ pm_runtime_disable(&auxdev->dev);
+
+ debugfs_remove(priv->kernel_dfs_file);
+}
+
+static const struct auxiliary_device_id sof_msg_inject_client_id_table[] = {
+ { .name = "snd_sof.kernel_injector" },
+ {},
+};
+MODULE_DEVICE_TABLE(auxiliary, sof_msg_inject_client_id_table);
+
+/*
+ * No need for driver pm_ops as the generic pm callbacks in the auxiliary bus
+ * type are enough to ensure that the parent SOF device resumes to bring the DSP
+ * back to D0.
+ * Driver name will be set based on KBUILD_MODNAME.
+ */
+static struct auxiliary_driver sof_msg_inject_client_drv = {
+ .probe = sof_msg_inject_probe,
+ .remove = sof_msg_inject_remove,
+
+ .id_table = sof_msg_inject_client_id_table,
+};
+
+module_auxiliary_driver(sof_msg_inject_client_drv);
+
+MODULE_DESCRIPTION("SOF IPC Kernel Injector Client Driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT);
diff --git a/sound/soc/sof/sof-client-ipc-msg-injector.c b/sound/soc/sof/sof-client-ipc-msg-injector.c
new file mode 100644
index 000000000000..e249d3a9afb5
--- /dev/null
+++ b/sound/soc/sof/sof-client-ipc-msg-injector.c
@@ -0,0 +1,340 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2022 Intel Corporation. All rights reserved.
+//
+// Author: Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
+//
+
+#include <linux/auxiliary_bus.h>
+#include <linux/completion.h>
+#include <linux/debugfs.h>
+#include <linux/ktime.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <sound/sof/header.h>
+#include <sound/sof/ipc4/header.h>
+
+#include "sof-client.h"
+
+#define SOF_IPC_CLIENT_SUSPEND_DELAY_MS 3000
+
+struct sof_msg_inject_priv {
+ struct dentry *dfs_file;
+ size_t max_msg_size;
+ enum sof_ipc_type ipc_type;
+
+ void *tx_buffer;
+ void *rx_buffer;
+};
+
+static int sof_msg_inject_dfs_open(struct inode *inode, struct file *file)
+{
+ struct sof_client_dev *cdev = inode->i_private;
+ int ret;
+
+ if (sof_client_get_fw_state(cdev) == SOF_FW_CRASHED)
+ return -ENODEV;
+
+ ret = debugfs_file_get(file->f_path.dentry);
+ if (unlikely(ret))
+ return ret;
+
+ ret = simple_open(inode, file);
+ if (ret)
+ debugfs_file_put(file->f_path.dentry);
+
+ return ret;
+}
+
+static ssize_t sof_msg_inject_dfs_read(struct file *file, char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct sof_client_dev *cdev = file->private_data;
+ struct sof_msg_inject_priv *priv = cdev->data;
+ struct sof_ipc_reply *rhdr = priv->rx_buffer;
+
+ if (!rhdr->hdr.size || !count || *ppos)
+ return 0;
+
+ if (count > rhdr->hdr.size)
+ count = rhdr->hdr.size;
+
+ if (copy_to_user(buffer, priv->rx_buffer, count))
+ return -EFAULT;
+
+ *ppos += count;
+ return count;
+}
+
+static ssize_t sof_msg_inject_ipc4_dfs_read(struct file *file,
+ char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct sof_client_dev *cdev = file->private_data;
+ struct sof_msg_inject_priv *priv = cdev->data;
+ struct sof_ipc4_msg *ipc4_msg = priv->rx_buffer;
+ size_t header_size = sizeof(ipc4_msg->header_u64);
+ size_t remaining;
+
+ if (!ipc4_msg->header_u64 || !count || *ppos)
+ return 0;
+
+ /* we need space for the header at minimum (u64) */
+ if (count < header_size)
+ return -ENOSPC;
+
+ remaining = header_size;
+
+ /* Only get large config have payload */
+ if (SOF_IPC4_MSG_IS_MODULE_MSG(ipc4_msg->primary) &&
+ (SOF_IPC4_MSG_TYPE_GET(ipc4_msg->primary) == SOF_IPC4_MOD_LARGE_CONFIG_GET))
+ remaining += ipc4_msg->data_size;
+
+ if (count > remaining)
+ count = remaining;
+ else if (count < remaining)
+ remaining = count;
+
+ /* copy the header first */
+ if (copy_to_user(buffer, &ipc4_msg->header_u64, header_size))
+ return -EFAULT;
+
+ *ppos += header_size;
+ remaining -= header_size;
+
+ if (!remaining)
+ return count;
+
+ if (remaining > ipc4_msg->data_size)
+ remaining = ipc4_msg->data_size;
+
+ /* Copy the payload */
+ if (copy_to_user(buffer + *ppos, ipc4_msg->data_ptr, remaining))
+ return -EFAULT;
+
+ *ppos += remaining;
+ return count;
+}
+
+static int sof_msg_inject_send_message(struct sof_client_dev *cdev)
+{
+ struct sof_msg_inject_priv *priv = cdev->data;
+ struct device *dev = &cdev->auxdev.dev;
+ int ret, err;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0 && ret != -EACCES) {
+ dev_err_ratelimited(dev, "debugfs write failed to resume %d\n", ret);
+ return ret;
+ }
+
+ /* send the message */
+ ret = sof_client_ipc_tx_message(cdev, priv->tx_buffer, priv->rx_buffer,
+ priv->max_msg_size);
+ if (ret)
+ dev_err(dev, "IPC message send failed: %d\n", ret);
+
+ pm_runtime_mark_last_busy(dev);
+ err = pm_runtime_put_autosuspend(dev);
+ if (err < 0)
+ dev_err_ratelimited(dev, "debugfs write failed to idle %d\n", err);
+
+ return ret;
+}
+
+static ssize_t sof_msg_inject_dfs_write(struct file *file, const char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct sof_client_dev *cdev = file->private_data;
+ struct sof_msg_inject_priv *priv = cdev->data;
+ ssize_t size;
+ int ret;
+
+ if (*ppos)
+ return 0;
+
+ size = simple_write_to_buffer(priv->tx_buffer, priv->max_msg_size,
+ ppos, buffer, count);
+ if (size < 0)
+ return size;
+ if (size != count)
+ return -EFAULT;
+
+ memset(priv->rx_buffer, 0, priv->max_msg_size);
+
+ ret = sof_msg_inject_send_message(cdev);
+
+ /* return the error code if test failed */
+ if (ret < 0)
+ size = ret;
+
+ return size;
+};
+
+static ssize_t sof_msg_inject_ipc4_dfs_write(struct file *file,
+ const char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct sof_client_dev *cdev = file->private_data;
+ struct sof_msg_inject_priv *priv = cdev->data;
+ struct sof_ipc4_msg *ipc4_msg = priv->tx_buffer;
+ size_t data_size;
+ int ret;
+
+ if (*ppos)
+ return 0;
+
+ if (count < sizeof(ipc4_msg->header_u64))
+ return -EINVAL;
+
+ /* copy the header first */
+ if (copy_from_user(&ipc4_msg->header_u64, buffer,
+ sizeof(ipc4_msg->header_u64)))
+ return -EFAULT;
+
+ data_size = count - sizeof(ipc4_msg->header_u64);
+ if (data_size > priv->max_msg_size)
+ return -EINVAL;
+
+ /* Copy the payload */
+ if (copy_from_user(ipc4_msg->data_ptr,
+ buffer + sizeof(ipc4_msg->header_u64), data_size))
+ return -EFAULT;
+
+ ipc4_msg->data_size = data_size;
+
+ /* Initialize the reply storage */
+ ipc4_msg = priv->rx_buffer;
+ ipc4_msg->header_u64 = 0;
+ ipc4_msg->data_size = priv->max_msg_size;
+ memset(ipc4_msg->data_ptr, 0, priv->max_msg_size);
+
+ ret = sof_msg_inject_send_message(cdev);
+
+ /* return the error code if test failed */
+ if (ret < 0)
+ return ret;
+
+ return count;
+};
+
+static int sof_msg_inject_dfs_release(struct inode *inode, struct file *file)
+{
+ debugfs_file_put(file->f_path.dentry);
+
+ return 0;
+}
+
+static const struct file_operations sof_msg_inject_fops = {
+ .open = sof_msg_inject_dfs_open,
+ .read = sof_msg_inject_dfs_read,
+ .write = sof_msg_inject_dfs_write,
+ .llseek = default_llseek,
+ .release = sof_msg_inject_dfs_release,
+
+ .owner = THIS_MODULE,
+};
+
+static const struct file_operations sof_msg_inject_ipc4_fops = {
+ .open = sof_msg_inject_dfs_open,
+ .read = sof_msg_inject_ipc4_dfs_read,
+ .write = sof_msg_inject_ipc4_dfs_write,
+ .llseek = default_llseek,
+ .release = sof_msg_inject_dfs_release,
+
+ .owner = THIS_MODULE,
+};
+
+static int sof_msg_inject_probe(struct auxiliary_device *auxdev,
+ const struct auxiliary_device_id *id)
+{
+ struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev);
+ struct dentry *debugfs_root = sof_client_get_debugfs_root(cdev);
+ static const struct file_operations *fops;
+ struct device *dev = &auxdev->dev;
+ struct sof_msg_inject_priv *priv;
+ size_t alloc_size;
+
+ /* allocate memory for client data */
+ priv = devm_kzalloc(&auxdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->ipc_type = sof_client_get_ipc_type(cdev);
+ priv->max_msg_size = sof_client_get_ipc_max_payload_size(cdev);
+ alloc_size = priv->max_msg_size;
+
+ if (priv->ipc_type == SOF_IPC_TYPE_4)
+ alloc_size += sizeof(struct sof_ipc4_msg);
+
+ priv->tx_buffer = devm_kmalloc(dev, alloc_size, GFP_KERNEL);
+ priv->rx_buffer = devm_kzalloc(dev, alloc_size, GFP_KERNEL);
+ if (!priv->tx_buffer || !priv->rx_buffer)
+ return -ENOMEM;
+
+ if (priv->ipc_type == SOF_IPC_TYPE_4) {
+ struct sof_ipc4_msg *ipc4_msg;
+
+ ipc4_msg = priv->tx_buffer;
+ ipc4_msg->data_ptr = priv->tx_buffer + sizeof(struct sof_ipc4_msg);
+
+ ipc4_msg = priv->rx_buffer;
+ ipc4_msg->data_ptr = priv->rx_buffer + sizeof(struct sof_ipc4_msg);
+
+ fops = &sof_msg_inject_ipc4_fops;
+ } else {
+ fops = &sof_msg_inject_fops;
+ }
+
+ cdev->data = priv;
+
+ priv->dfs_file = debugfs_create_file("ipc_msg_inject", 0644, debugfs_root,
+ cdev, fops);
+
+ /* enable runtime PM */
+ pm_runtime_set_autosuspend_delay(dev, SOF_IPC_CLIENT_SUSPEND_DELAY_MS);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_enable(dev);
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_idle(dev);
+
+ return 0;
+}
+
+static void sof_msg_inject_remove(struct auxiliary_device *auxdev)
+{
+ struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev);
+ struct sof_msg_inject_priv *priv = cdev->data;
+
+ pm_runtime_disable(&auxdev->dev);
+
+ debugfs_remove(priv->dfs_file);
+}
+
+static const struct auxiliary_device_id sof_msg_inject_client_id_table[] = {
+ { .name = "snd_sof.msg_injector" },
+ {},
+};
+MODULE_DEVICE_TABLE(auxiliary, sof_msg_inject_client_id_table);
+
+/*
+ * No need for driver pm_ops as the generic pm callbacks in the auxiliary bus
+ * type are enough to ensure that the parent SOF device resumes to bring the DSP
+ * back to D0.
+ * Driver name will be set based on KBUILD_MODNAME.
+ */
+static struct auxiliary_driver sof_msg_inject_client_drv = {
+ .probe = sof_msg_inject_probe,
+ .remove = sof_msg_inject_remove,
+
+ .id_table = sof_msg_inject_client_id_table,
+};
+
+module_auxiliary_driver(sof_msg_inject_client_drv);
+
+MODULE_DESCRIPTION("SOF IPC Message Injector Client Driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT);
diff --git a/sound/soc/sof/sof-client-probes-ipc3.c b/sound/soc/sof/sof-client-probes-ipc3.c
new file mode 100644
index 000000000000..5e8eb19582a8
--- /dev/null
+++ b/sound/soc/sof/sof-client-probes-ipc3.c
@@ -0,0 +1,232 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2019-2022 Intel Corporation. All rights reserved.
+//
+// Author: Cezary Rojewski <cezary.rojewski@intel.com>
+//
+// Code moved to this file by:
+// Jyri Sarha <jyri.sarha@intel.com>
+//
+
+#include <linux/stddef.h>
+#include <sound/soc.h>
+#include <sound/sof/header.h>
+#include "sof-client.h"
+#include "sof-client-probes.h"
+
+struct sof_probe_dma {
+ unsigned int stream_tag;
+ unsigned int dma_buffer_size;
+} __packed;
+
+struct sof_ipc_probe_dma_add_params {
+ struct sof_ipc_cmd_hdr hdr;
+ unsigned int num_elems;
+ struct sof_probe_dma dma[];
+} __packed;
+
+struct sof_ipc_probe_info_params {
+ struct sof_ipc_reply rhdr;
+ unsigned int num_elems;
+ union {
+ DECLARE_FLEX_ARRAY(struct sof_probe_dma, dma);
+ DECLARE_FLEX_ARRAY(struct sof_probe_point_desc, desc);
+ };
+} __packed;
+
+struct sof_ipc_probe_point_add_params {
+ struct sof_ipc_cmd_hdr hdr;
+ unsigned int num_elems;
+ struct sof_probe_point_desc desc[];
+} __packed;
+
+struct sof_ipc_probe_point_remove_params {
+ struct sof_ipc_cmd_hdr hdr;
+ unsigned int num_elems;
+ unsigned int buffer_id[];
+} __packed;
+
+/**
+ * ipc3_probes_init - initialize data probing
+ * @cdev: SOF client device
+ * @stream_tag: Extractor stream tag
+ * @buffer_size: DMA buffer size to set for extractor
+ *
+ * Host chooses whether extraction is supported or not by providing
+ * valid stream tag to DSP. Once specified, stream described by that
+ * tag will be tied to DSP for extraction for the entire lifetime of
+ * probe.
+ *
+ * Probing is initialized only once and each INIT request must be
+ * matched by DEINIT call.
+ */
+static int ipc3_probes_init(struct sof_client_dev *cdev, u32 stream_tag,
+ size_t buffer_size)
+{
+ struct sof_ipc_probe_dma_add_params *msg;
+ size_t size = struct_size(msg, dma, 1);
+ int ret;
+
+ msg = kmalloc(size, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+ msg->hdr.size = size;
+ msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_INIT;
+ msg->num_elems = 1;
+ msg->dma[0].stream_tag = stream_tag;
+ msg->dma[0].dma_buffer_size = buffer_size;
+
+ ret = sof_client_ipc_tx_message_no_reply(cdev, msg);
+ kfree(msg);
+ return ret;
+}
+
+/**
+ * ipc3_probes_deinit - cleanup after data probing
+ * @cdev: SOF client device
+ *
+ * Host sends DEINIT request to free previously initialized probe
+ * on DSP side once it is no longer needed. DEINIT only when there
+ * are no probes connected and with all injectors detached.
+ */
+static int ipc3_probes_deinit(struct sof_client_dev *cdev)
+{
+ struct sof_ipc_cmd_hdr msg;
+
+ msg.size = sizeof(msg);
+ msg.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_DEINIT;
+
+ return sof_client_ipc_tx_message_no_reply(cdev, &msg);
+}
+
+static int ipc3_probes_info(struct sof_client_dev *cdev, unsigned int cmd,
+ void **params, size_t *num_params)
+{
+ size_t max_msg_size = sof_client_get_ipc_max_payload_size(cdev);
+ struct sof_ipc_probe_info_params msg = {{{0}}};
+ struct sof_ipc_probe_info_params *reply;
+ size_t bytes;
+ int ret;
+
+ *params = NULL;
+ *num_params = 0;
+
+ reply = kzalloc(max_msg_size, GFP_KERNEL);
+ if (!reply)
+ return -ENOMEM;
+ msg.rhdr.hdr.size = sizeof(msg);
+ msg.rhdr.hdr.cmd = SOF_IPC_GLB_PROBE | cmd;
+
+ ret = sof_client_ipc_tx_message(cdev, &msg, reply, max_msg_size);
+ if (ret < 0 || reply->rhdr.error < 0)
+ goto exit;
+
+ if (!reply->num_elems)
+ goto exit;
+
+ if (cmd == SOF_IPC_PROBE_DMA_INFO)
+ bytes = sizeof(reply->dma[0]);
+ else
+ bytes = sizeof(reply->desc[0]);
+ bytes *= reply->num_elems;
+ *params = kmemdup(&reply->dma[0], bytes, GFP_KERNEL);
+ if (!*params) {
+ ret = -ENOMEM;
+ goto exit;
+ }
+ *num_params = reply->num_elems;
+
+exit:
+ kfree(reply);
+ return ret;
+}
+
+/**
+ * ipc3_probes_points_info - retrieve list of active probe points
+ * @cdev: SOF client device
+ * @desc: Returned list of active probes
+ * @num_desc: Returned count of active probes
+ *
+ * Host sends PROBE_POINT_INFO request to obtain list of active probe
+ * points, valid for disconnection when given probe is no longer
+ * required.
+ */
+static int ipc3_probes_points_info(struct sof_client_dev *cdev,
+ struct sof_probe_point_desc **desc,
+ size_t *num_desc)
+{
+ return ipc3_probes_info(cdev, SOF_IPC_PROBE_POINT_INFO,
+ (void **)desc, num_desc);
+}
+
+/**
+ * ipc3_probes_points_add - connect specified probes
+ * @cdev: SOF client device
+ * @desc: List of probe points to connect
+ * @num_desc: Number of elements in @desc
+ *
+ * Dynamically connects to provided set of endpoints. Immediately
+ * after connection is established, host must be prepared to
+ * transfer data from or to target stream given the probing purpose.
+ *
+ * Each probe point should be removed using PROBE_POINT_REMOVE
+ * request when no longer needed.
+ */
+static int ipc3_probes_points_add(struct sof_client_dev *cdev,
+ struct sof_probe_point_desc *desc,
+ size_t num_desc)
+{
+ struct sof_ipc_probe_point_add_params *msg;
+ size_t size = struct_size(msg, desc, num_desc);
+ int ret;
+
+ msg = kmalloc(size, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+ msg->hdr.size = size;
+ msg->num_elems = num_desc;
+ msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_POINT_ADD;
+ memcpy(&msg->desc[0], desc, size - sizeof(*msg));
+
+ ret = sof_client_ipc_tx_message_no_reply(cdev, msg);
+ kfree(msg);
+ return ret;
+}
+
+/**
+ * ipc3_probes_points_remove - disconnect specified probes
+ * @cdev: SOF client device
+ * @buffer_id: List of probe points to disconnect
+ * @num_buffer_id: Number of elements in @desc
+ *
+ * Removes previously connected probes from list of active probe
+ * points and frees all resources on DSP side.
+ */
+static int ipc3_probes_points_remove(struct sof_client_dev *cdev,
+ unsigned int *buffer_id,
+ size_t num_buffer_id)
+{
+ struct sof_ipc_probe_point_remove_params *msg;
+ size_t size = struct_size(msg, buffer_id, num_buffer_id);
+ int ret;
+
+ msg = kmalloc(size, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+ msg->hdr.size = size;
+ msg->num_elems = num_buffer_id;
+ msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_POINT_REMOVE;
+ memcpy(&msg->buffer_id[0], buffer_id, size - sizeof(*msg));
+
+ ret = sof_client_ipc_tx_message_no_reply(cdev, msg);
+ kfree(msg);
+ return ret;
+}
+
+const struct sof_probes_ipc_ops ipc3_probe_ops = {
+ .init = ipc3_probes_init,
+ .deinit = ipc3_probes_deinit,
+ .points_info = ipc3_probes_points_info,
+ .points_add = ipc3_probes_points_add,
+ .points_remove = ipc3_probes_points_remove,
+};
diff --git a/sound/soc/sof/sof-client-probes-ipc4.c b/sound/soc/sof/sof-client-probes-ipc4.c
new file mode 100644
index 000000000000..c56a85854d92
--- /dev/null
+++ b/sound/soc/sof/sof-client-probes-ipc4.c
@@ -0,0 +1,290 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2019-2022 Intel Corporation. All rights reserved.
+//
+// Author: Jyri Sarha <jyri.sarha@intel.com>
+//
+
+#include <sound/soc.h>
+#include <sound/sof/ipc4/header.h>
+#include <uapi/sound/sof/header.h>
+#include "sof-priv.h"
+#include "ipc4-priv.h"
+#include "sof-client.h"
+#include "sof-client-probes.h"
+
+enum sof_ipc4_dma_type {
+ SOF_IPC4_DMA_HDA_HOST_OUTPUT = 0,
+ SOF_IPC4_DMA_HDA_HOST_INPUT = 1,
+ SOF_IPC4_DMA_HDA_LINK_OUTPUT = 8,
+ SOF_IPC4_DMA_HDA_LINK_INPUT = 9,
+ SOF_IPC4_DMA_DMIC_LINK_INPUT = 11,
+ SOF_IPC4_DMA_I2S_LINK_OUTPUT = 12,
+ SOF_IPC4_DMA_I2S_LINK_INPUT = 13,
+};
+
+enum sof_ipc4_probe_runtime_param {
+ SOF_IPC4_PROBE_INJECTION_DMA = 1,
+ SOF_IPC4_PROBE_INJECTION_DMA_DETACH,
+ SOF_IPC4_PROBE_POINTS,
+ SOF_IPC4_PROBE_POINTS_DISCONNECT,
+};
+
+struct sof_ipc4_probe_gtw_cfg {
+ u32 node_id;
+ u32 dma_buffer_size;
+} __packed __aligned(4);
+
+#define SOF_IPC4_PROBE_NODE_ID_INDEX(x) ((x) & GENMASK(7, 0))
+#define SOF_IPC4_PROBE_NODE_ID_TYPE(x) (((x) << 8) & GENMASK(12, 8))
+
+struct sof_ipc4_probe_cfg {
+ struct sof_ipc4_base_module_cfg base;
+ struct sof_ipc4_probe_gtw_cfg gtw_cfg;
+} __packed __aligned(4);
+
+enum sof_ipc4_probe_type {
+ SOF_IPC4_PROBE_TYPE_INPUT = 0,
+ SOF_IPC4_PROBE_TYPE_OUTPUT,
+ SOF_IPC4_PROBE_TYPE_INTERNAL
+};
+
+struct sof_ipc4_probe_point {
+ u32 point_id;
+ u32 purpose;
+ u32 stream_tag;
+} __packed __aligned(4);
+
+#define INVALID_PIPELINE_ID 0xFF
+
+/**
+ * sof_ipc4_probe_get_module_info - Get IPC4 module info for probe module
+ * @cdev: SOF client device
+ * @return: Pointer to IPC4 probe module info
+ *
+ * Look up the IPC4 probe module info based on the hard coded uuid and
+ * store the value for the future calls.
+ */
+static struct sof_man4_module *sof_ipc4_probe_get_module_info(struct sof_client_dev *cdev)
+{
+ struct sof_probes_priv *priv = cdev->data;
+ struct device *dev = &cdev->auxdev.dev;
+ static const guid_t probe_uuid =
+ GUID_INIT(0x7CAD0808, 0xAB10, 0xCD23,
+ 0xEF, 0x45, 0x12, 0xAB, 0x34, 0xCD, 0x56, 0xEF);
+
+ if (!priv->ipc_priv) {
+ struct sof_ipc4_fw_module *fw_module =
+ sof_client_ipc4_find_module(cdev, &probe_uuid);
+
+ if (!fw_module) {
+ dev_err(dev, "%s: no matching uuid found", __func__);
+ return NULL;
+ }
+
+ priv->ipc_priv = &fw_module->man4_module_entry;
+ }
+
+ return (struct sof_man4_module *)priv->ipc_priv;
+}
+
+/**
+ * ipc4_probes_init - initialize data probing
+ * @cdev: SOF client device
+ * @stream_tag: Extractor stream tag
+ * @buffer_size: DMA buffer size to set for extractor
+ * @return: 0 on success, negative error code on error
+ *
+ * Host chooses whether extraction is supported or not by providing
+ * valid stream tag to DSP. Once specified, stream described by that
+ * tag will be tied to DSP for extraction for the entire lifetime of
+ * probe.
+ *
+ * Probing is initialized only once and each INIT request must be
+ * matched by DEINIT call.
+ */
+static int ipc4_probes_init(struct sof_client_dev *cdev, u32 stream_tag,
+ size_t buffer_size)
+{
+ struct sof_man4_module *mentry = sof_ipc4_probe_get_module_info(cdev);
+ struct sof_ipc4_msg msg;
+ struct sof_ipc4_probe_cfg cfg;
+
+ if (!mentry)
+ return -ENODEV;
+
+ memset(&cfg, '\0', sizeof(cfg));
+ cfg.gtw_cfg.node_id = SOF_IPC4_PROBE_NODE_ID_INDEX(stream_tag - 1) |
+ SOF_IPC4_PROBE_NODE_ID_TYPE(SOF_IPC4_DMA_HDA_HOST_INPUT);
+
+ cfg.gtw_cfg.dma_buffer_size = buffer_size;
+
+ msg.primary = mentry->id;
+ msg.primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_INIT_INSTANCE);
+ msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ msg.primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG);
+ msg.extension = SOF_IPC4_MOD_EXT_DST_MOD_INSTANCE(INVALID_PIPELINE_ID);
+ msg.extension |= SOF_IPC4_MOD_EXT_CORE_ID(0);
+
+ msg.data_size = sizeof(cfg);
+ msg.data_ptr = &cfg;
+
+ return sof_client_ipc_tx_message_no_reply(cdev, &msg);
+}
+
+/**
+ * ipc4_probes_deinit - cleanup after data probing
+ * @cdev: SOF client device
+ * @return: 0 on success, negative error code on error
+ *
+ * Host sends DEINIT request to free previously initialized probe
+ * on DSP side once it is no longer needed. DEINIT only when there
+ * are no probes connected and with all injectors detached.
+ */
+static int ipc4_probes_deinit(struct sof_client_dev *cdev)
+{
+ struct sof_man4_module *mentry = sof_ipc4_probe_get_module_info(cdev);
+ struct sof_ipc4_msg msg;
+
+ if (!mentry)
+ return -ENODEV;
+
+ msg.primary = mentry->id;
+ msg.primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_DELETE_INSTANCE);
+ msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ msg.primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG);
+ msg.extension = SOF_IPC4_MOD_EXT_DST_MOD_INSTANCE(INVALID_PIPELINE_ID);
+ msg.extension |= SOF_IPC4_MOD_EXT_CORE_ID(0);
+
+ msg.data_size = 0;
+ msg.data_ptr = NULL;
+
+ return sof_client_ipc_tx_message_no_reply(cdev, &msg);
+}
+
+/**
+ * ipc4_probes_points_info - retrieve list of active probe points
+ * @cdev: SOF client device
+ * @desc: Returned list of active probes
+ * @num_desc: Returned count of active probes
+ * @return: 0 on success, negative error code on error
+ *
+ * Dummy implementation returning empty list of probes.
+ */
+static int ipc4_probes_points_info(struct sof_client_dev *cdev,
+ struct sof_probe_point_desc **desc,
+ size_t *num_desc)
+{
+ /* TODO: Firmware side implementation needed first */
+ *desc = NULL;
+ *num_desc = 0;
+ return 0;
+}
+
+/**
+ * ipc4_probes_points_add - connect specified probes
+ * @cdev: SOF client device
+ * @desc: List of probe points to connect
+ * @num_desc: Number of elements in @desc
+ * @return: 0 on success, negative error code on error
+ *
+ * Translates the generic probe point presentation to an IPC4
+ * message to dynamically connect the provided set of endpoints.
+ */
+static int ipc4_probes_points_add(struct sof_client_dev *cdev,
+ struct sof_probe_point_desc *desc,
+ size_t num_desc)
+{
+ struct sof_man4_module *mentry = sof_ipc4_probe_get_module_info(cdev);
+ struct sof_ipc4_probe_point *points;
+ struct sof_ipc4_msg msg;
+ int i, ret;
+
+ if (!mentry)
+ return -ENODEV;
+
+ /* The sof_probe_point_desc and sof_ipc4_probe_point structs
+ * are of same size and even the integers are the same in the
+ * same order, and similar meaning, but since there is no
+ * performance issue I wrote the conversion explicitly open for
+ * future development.
+ */
+ points = kcalloc(num_desc, sizeof(*points), GFP_KERNEL);
+ if (!points)
+ return -ENOMEM;
+
+ for (i = 0; i < num_desc; i++) {
+ points[i].point_id = desc[i].buffer_id;
+ points[i].purpose = desc[i].purpose;
+ points[i].stream_tag = desc[i].stream_tag;
+ }
+
+ msg.primary = mentry->id;
+ msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ msg.primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG);
+
+ msg.extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(SOF_IPC4_PROBE_POINTS);
+
+ msg.data_size = sizeof(*points) * num_desc;
+ msg.data_ptr = points;
+
+ ret = sof_client_ipc_set_get_data(cdev, &msg, true);
+
+ kfree(points);
+
+ return ret;
+}
+
+/**
+ * ipc4_probes_points_remove - disconnect specified probes
+ * @cdev: SOF client device
+ * @buffer_id: List of probe points to disconnect
+ * @num_buffer_id: Number of elements in @desc
+ * @return: 0 on success, negative error code on error
+ *
+ * Converts the generic buffer_id to IPC4 probe_point_id and remove
+ * the probe points with an IPC4 for message.
+ */
+static int ipc4_probes_points_remove(struct sof_client_dev *cdev,
+ unsigned int *buffer_id, size_t num_buffer_id)
+{
+ struct sof_man4_module *mentry = sof_ipc4_probe_get_module_info(cdev);
+ struct sof_ipc4_msg msg;
+ u32 *probe_point_ids;
+ int i, ret;
+
+ if (!mentry)
+ return -ENODEV;
+
+ probe_point_ids = kcalloc(num_buffer_id, sizeof(*probe_point_ids),
+ GFP_KERNEL);
+ if (!probe_point_ids)
+ return -ENOMEM;
+
+ for (i = 0; i < num_buffer_id; i++)
+ probe_point_ids[i] = buffer_id[i];
+
+ msg.primary = mentry->id;
+ msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
+ msg.primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG);
+
+ msg.extension =
+ SOF_IPC4_MOD_EXT_MSG_PARAM_ID(SOF_IPC4_PROBE_POINTS_DISCONNECT);
+
+ msg.data_size = num_buffer_id * sizeof(*probe_point_ids);
+ msg.data_ptr = probe_point_ids;
+
+ ret = sof_client_ipc_set_get_data(cdev, &msg, true);
+
+ kfree(probe_point_ids);
+
+ return ret;
+}
+
+const struct sof_probes_ipc_ops ipc4_probe_ops = {
+ .init = ipc4_probes_init,
+ .deinit = ipc4_probes_deinit,
+ .points_info = ipc4_probes_points_info,
+ .points_add = ipc4_probes_points_add,
+ .points_remove = ipc4_probes_points_remove,
+};
diff --git a/sound/soc/sof/sof-client-probes.c b/sound/soc/sof/sof-client-probes.c
new file mode 100644
index 000000000000..30f771ac7bbf
--- /dev/null
+++ b/sound/soc/sof/sof-client-probes.c
@@ -0,0 +1,545 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2019-2022 Intel Corporation. All rights reserved.
+//
+// Author: Cezary Rojewski <cezary.rojewski@intel.com>
+//
+// SOF client support:
+// Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
+// Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
+//
+
+#include <linux/debugfs.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/string_helpers.h>
+#include <linux/stddef.h>
+
+#include <sound/soc.h>
+#include <sound/sof/header.h>
+#include "sof-client.h"
+#include "sof-client-probes.h"
+
+#define SOF_PROBES_SUSPEND_DELAY_MS 3000
+/* only extraction supported for now */
+#define SOF_PROBES_NUM_DAI_LINKS 1
+
+#define SOF_PROBES_INVALID_NODE_ID UINT_MAX
+
+static bool __read_mostly sof_probes_enabled;
+module_param_named(enable, sof_probes_enabled, bool, 0444);
+MODULE_PARM_DESC(enable, "Enable SOF probes support");
+
+static int sof_probes_compr_startup(struct snd_compr_stream *cstream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_card *card = snd_soc_component_get_drvdata(dai->component);
+ struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card);
+ struct sof_probes_priv *priv = cdev->data;
+ const struct sof_probes_host_ops *ops = priv->host_ops;
+ int ret;
+
+ if (sof_client_get_fw_state(cdev) == SOF_FW_CRASHED)
+ return -ENODEV;
+
+ ret = sof_client_core_module_get(cdev);
+ if (ret)
+ return ret;
+
+ ret = ops->startup(cdev, cstream, dai, &priv->extractor_stream_tag);
+ if (ret) {
+ dev_err(dai->dev, "Failed to startup probe stream: %d\n", ret);
+ priv->extractor_stream_tag = SOF_PROBES_INVALID_NODE_ID;
+ sof_client_core_module_put(cdev);
+ }
+
+ return ret;
+}
+
+static int sof_probes_compr_shutdown(struct snd_compr_stream *cstream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_card *card = snd_soc_component_get_drvdata(dai->component);
+ struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card);
+ struct sof_probes_priv *priv = cdev->data;
+ const struct sof_probes_host_ops *ops = priv->host_ops;
+ const struct sof_probes_ipc_ops *ipc = priv->ipc_ops;
+ struct sof_probe_point_desc *desc;
+ size_t num_desc;
+ int i, ret;
+
+ /* disconnect all probe points */
+ ret = ipc->points_info(cdev, &desc, &num_desc);
+ if (ret < 0) {
+ dev_err(dai->dev, "Failed to get probe points: %d\n", ret);
+ goto exit;
+ }
+
+ for (i = 0; i < num_desc; i++)
+ ipc->points_remove(cdev, &desc[i].buffer_id, 1);
+ kfree(desc);
+
+exit:
+ ret = ipc->deinit(cdev);
+ if (ret < 0)
+ dev_err(dai->dev, "Failed to deinit probe: %d\n", ret);
+
+ priv->extractor_stream_tag = SOF_PROBES_INVALID_NODE_ID;
+ snd_compr_free_pages(cstream);
+
+ ret = ops->shutdown(cdev, cstream, dai);
+
+ sof_client_core_module_put(cdev);
+
+ return ret;
+}
+
+static int sof_probes_compr_set_params(struct snd_compr_stream *cstream,
+ struct snd_compr_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_card *card = snd_soc_component_get_drvdata(dai->component);
+ struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card);
+ struct snd_compr_runtime *rtd = cstream->runtime;
+ struct sof_probes_priv *priv = cdev->data;
+ const struct sof_probes_host_ops *ops = priv->host_ops;
+ const struct sof_probes_ipc_ops *ipc = priv->ipc_ops;
+ int ret;
+
+ cstream->dma_buffer.dev.type = SNDRV_DMA_TYPE_DEV_SG;
+ cstream->dma_buffer.dev.dev = sof_client_get_dma_dev(cdev);
+ ret = snd_compr_malloc_pages(cstream, rtd->buffer_size);
+ if (ret < 0)
+ return ret;
+
+ ret = ops->set_params(cdev, cstream, params, dai);
+ if (ret)
+ return ret;
+
+ ret = ipc->init(cdev, priv->extractor_stream_tag, rtd->dma_bytes);
+ if (ret < 0) {
+ dev_err(dai->dev, "Failed to init probe: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sof_probes_compr_trigger(struct snd_compr_stream *cstream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_card *card = snd_soc_component_get_drvdata(dai->component);
+ struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card);
+ struct sof_probes_priv *priv = cdev->data;
+ const struct sof_probes_host_ops *ops = priv->host_ops;
+
+ return ops->trigger(cdev, cstream, cmd, dai);
+}
+
+static int sof_probes_compr_pointer(struct snd_compr_stream *cstream,
+ struct snd_compr_tstamp *tstamp,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_card *card = snd_soc_component_get_drvdata(dai->component);
+ struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card);
+ struct sof_probes_priv *priv = cdev->data;
+ const struct sof_probes_host_ops *ops = priv->host_ops;
+
+ return ops->pointer(cdev, cstream, tstamp, dai);
+}
+
+static const struct snd_soc_cdai_ops sof_probes_compr_ops = {
+ .startup = sof_probes_compr_startup,
+ .shutdown = sof_probes_compr_shutdown,
+ .set_params = sof_probes_compr_set_params,
+ .trigger = sof_probes_compr_trigger,
+ .pointer = sof_probes_compr_pointer,
+};
+
+static int sof_probes_compr_copy(struct snd_soc_component *component,
+ struct snd_compr_stream *cstream,
+ char __user *buf, size_t count)
+{
+ struct snd_compr_runtime *rtd = cstream->runtime;
+ unsigned int offset, n;
+ void *ptr;
+ int ret;
+
+ if (count > rtd->buffer_size)
+ count = rtd->buffer_size;
+
+ div_u64_rem(rtd->total_bytes_transferred, rtd->buffer_size, &offset);
+ ptr = rtd->dma_area + offset;
+ n = rtd->buffer_size - offset;
+
+ if (count < n) {
+ ret = copy_to_user(buf, ptr, count);
+ } else {
+ ret = copy_to_user(buf, ptr, n);
+ ret += copy_to_user(buf + n, rtd->dma_area, count - n);
+ }
+
+ if (ret)
+ return count - ret;
+ return count;
+}
+
+static const struct snd_compress_ops sof_probes_compressed_ops = {
+ .copy = sof_probes_compr_copy,
+};
+
+static ssize_t sof_probes_dfs_points_read(struct file *file, char __user *to,
+ size_t count, loff_t *ppos)
+{
+ struct sof_client_dev *cdev = file->private_data;
+ struct sof_probes_priv *priv = cdev->data;
+ struct device *dev = &cdev->auxdev.dev;
+ struct sof_probe_point_desc *desc;
+ const struct sof_probes_ipc_ops *ipc = priv->ipc_ops;
+ int remaining, offset;
+ size_t num_desc;
+ char *buf;
+ int i, ret, err;
+
+ if (priv->extractor_stream_tag == SOF_PROBES_INVALID_NODE_ID) {
+ dev_warn(dev, "no extractor stream running\n");
+ return -ENOENT;
+ }
+
+ buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0 && ret != -EACCES) {
+ dev_err_ratelimited(dev, "debugfs read failed to resume %d\n", ret);
+ goto exit;
+ }
+
+ ret = ipc->points_info(cdev, &desc, &num_desc);
+ if (ret < 0)
+ goto pm_error;
+
+ for (i = 0; i < num_desc; i++) {
+ offset = strlen(buf);
+ remaining = PAGE_SIZE - offset;
+ ret = snprintf(buf + offset, remaining,
+ "Id: %#010x Purpose: %u Node id: %#x\n",
+ desc[i].buffer_id, desc[i].purpose, desc[i].stream_tag);
+ if (ret < 0 || ret >= remaining) {
+ /* truncate the output buffer at the last full line */
+ buf[offset] = '\0';
+ break;
+ }
+ }
+
+ ret = simple_read_from_buffer(to, count, ppos, buf, strlen(buf));
+
+ kfree(desc);
+
+pm_error:
+ pm_runtime_mark_last_busy(dev);
+ err = pm_runtime_put_autosuspend(dev);
+ if (err < 0)
+ dev_err_ratelimited(dev, "debugfs read failed to idle %d\n", err);
+
+exit:
+ kfree(buf);
+ return ret;
+}
+
+static ssize_t
+sof_probes_dfs_points_write(struct file *file, const char __user *from,
+ size_t count, loff_t *ppos)
+{
+ struct sof_client_dev *cdev = file->private_data;
+ struct sof_probes_priv *priv = cdev->data;
+ const struct sof_probes_ipc_ops *ipc = priv->ipc_ops;
+ struct device *dev = &cdev->auxdev.dev;
+ struct sof_probe_point_desc *desc;
+ u32 num_elems, *array;
+ size_t bytes;
+ int ret, err;
+
+ if (priv->extractor_stream_tag == SOF_PROBES_INVALID_NODE_ID) {
+ dev_warn(dev, "no extractor stream running\n");
+ return -ENOENT;
+ }
+
+ ret = parse_int_array_user(from, count, (int **)&array);
+ if (ret < 0)
+ return ret;
+
+ num_elems = *array;
+ bytes = sizeof(*array) * num_elems;
+ if (bytes % sizeof(*desc)) {
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ desc = (struct sof_probe_point_desc *)&array[1];
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0 && ret != -EACCES) {
+ dev_err_ratelimited(dev, "debugfs write failed to resume %d\n", ret);
+ goto exit;
+ }
+
+ ret = ipc->points_add(cdev, desc, bytes / sizeof(*desc));
+ if (!ret)
+ ret = count;
+
+ pm_runtime_mark_last_busy(dev);
+ err = pm_runtime_put_autosuspend(dev);
+ if (err < 0)
+ dev_err_ratelimited(dev, "debugfs write failed to idle %d\n", err);
+exit:
+ kfree(array);
+ return ret;
+}
+
+static const struct file_operations sof_probes_points_fops = {
+ .open = simple_open,
+ .read = sof_probes_dfs_points_read,
+ .write = sof_probes_dfs_points_write,
+ .llseek = default_llseek,
+
+ .owner = THIS_MODULE,
+};
+
+static ssize_t
+sof_probes_dfs_points_remove_write(struct file *file, const char __user *from,
+ size_t count, loff_t *ppos)
+{
+ struct sof_client_dev *cdev = file->private_data;
+ struct sof_probes_priv *priv = cdev->data;
+ const struct sof_probes_ipc_ops *ipc = priv->ipc_ops;
+ struct device *dev = &cdev->auxdev.dev;
+ int ret, err;
+ u32 *array;
+
+ if (priv->extractor_stream_tag == SOF_PROBES_INVALID_NODE_ID) {
+ dev_warn(dev, "no extractor stream running\n");
+ return -ENOENT;
+ }
+
+ ret = parse_int_array_user(from, count, (int **)&array);
+ if (ret < 0)
+ return ret;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0) {
+ dev_err_ratelimited(dev, "debugfs write failed to resume %d\n", ret);
+ goto exit;
+ }
+
+ ret = ipc->points_remove(cdev, &array[1], array[0]);
+ if (!ret)
+ ret = count;
+
+ pm_runtime_mark_last_busy(dev);
+ err = pm_runtime_put_autosuspend(dev);
+ if (err < 0)
+ dev_err_ratelimited(dev, "debugfs write failed to idle %d\n", err);
+exit:
+ kfree(array);
+ return ret;
+}
+
+static const struct file_operations sof_probes_points_remove_fops = {
+ .open = simple_open,
+ .write = sof_probes_dfs_points_remove_write,
+ .llseek = default_llseek,
+
+ .owner = THIS_MODULE,
+};
+
+static const struct snd_soc_dai_ops sof_probes_dai_ops = {
+ .compress_new = snd_soc_new_compress,
+};
+
+static struct snd_soc_dai_driver sof_probes_dai_drv[] = {
+{
+ .name = "Probe Extraction CPU DAI",
+ .ops = &sof_probes_dai_ops,
+ .cops = &sof_probes_compr_ops,
+ .capture = {
+ .stream_name = "Probe Extraction",
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ },
+},
+};
+
+static const struct snd_soc_component_driver sof_probes_component = {
+ .name = "sof-probes-component",
+ .compress_ops = &sof_probes_compressed_ops,
+ .module_get_upon_open = 1,
+ .legacy_dai_naming = 1,
+};
+
+static int sof_probes_client_probe(struct auxiliary_device *auxdev,
+ const struct auxiliary_device_id *id)
+{
+ struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev);
+ struct dentry *dfsroot = sof_client_get_debugfs_root(cdev);
+ struct device *dev = &auxdev->dev;
+ struct snd_soc_dai_link_component platform_component[] = {
+ {
+ .name = dev_name(dev),
+ }
+ };
+ struct snd_soc_card *card;
+ struct sof_probes_priv *priv;
+ struct snd_soc_dai_link_component *cpus;
+ struct sof_probes_host_ops *ops;
+ struct snd_soc_dai_link *links;
+ int ret;
+
+ /* do not set up the probes support if it is not enabled */
+ if (!sof_probes_enabled)
+ return -ENXIO;
+
+ ops = dev_get_platdata(dev);
+ if (!ops) {
+ dev_err(dev, "missing platform data\n");
+ return -ENODEV;
+ }
+ if (!ops->startup || !ops->shutdown || !ops->set_params || !ops->trigger ||
+ !ops->pointer) {
+ dev_err(dev, "missing platform callback(s)\n");
+ return -ENODEV;
+ }
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->host_ops = ops;
+
+ switch (sof_client_get_ipc_type(cdev)) {
+#ifdef CONFIG_SND_SOC_SOF_IPC4
+ case SOF_IPC_TYPE_4:
+ priv->ipc_ops = &ipc4_probe_ops;
+ break;
+#endif
+#ifdef CONFIG_SND_SOC_SOF_IPC3
+ case SOF_IPC_TYPE_3:
+ priv->ipc_ops = &ipc3_probe_ops;
+ break;
+#endif
+ default:
+ dev_err(dev, "Matching IPC ops not found.");
+ return -ENODEV;
+ }
+
+ cdev->data = priv;
+
+ /* register probes component driver and dai */
+ ret = devm_snd_soc_register_component(dev, &sof_probes_component,
+ sof_probes_dai_drv,
+ ARRAY_SIZE(sof_probes_dai_drv));
+ if (ret < 0) {
+ dev_err(dev, "failed to register SOF probes DAI driver %d\n", ret);
+ return ret;
+ }
+
+ /* set client data */
+ priv->extractor_stream_tag = SOF_PROBES_INVALID_NODE_ID;
+
+ /* create read-write probes_points debugfs entry */
+ priv->dfs_points = debugfs_create_file("probe_points", 0644, dfsroot,
+ cdev, &sof_probes_points_fops);
+
+ /* create read-write probe_points_remove debugfs entry */
+ priv->dfs_points_remove = debugfs_create_file("probe_points_remove", 0644,
+ dfsroot, cdev,
+ &sof_probes_points_remove_fops);
+
+ links = devm_kcalloc(dev, SOF_PROBES_NUM_DAI_LINKS, sizeof(*links), GFP_KERNEL);
+ cpus = devm_kcalloc(dev, SOF_PROBES_NUM_DAI_LINKS, sizeof(*cpus), GFP_KERNEL);
+ if (!links || !cpus) {
+ debugfs_remove(priv->dfs_points);
+ debugfs_remove(priv->dfs_points_remove);
+ return -ENOMEM;
+ }
+
+ /* extraction DAI link */
+ links[0].name = "Compress Probe Capture";
+ links[0].id = 0;
+ links[0].cpus = &cpus[0];
+ links[0].num_cpus = 1;
+ links[0].cpus->dai_name = "Probe Extraction CPU DAI";
+ links[0].codecs = &snd_soc_dummy_dlc;
+ links[0].num_codecs = 1;
+ links[0].platforms = platform_component;
+ links[0].num_platforms = ARRAY_SIZE(platform_component);
+ links[0].nonatomic = 1;
+
+ card = &priv->card;
+
+ card->dev = dev;
+ card->name = "sof-probes";
+ card->owner = THIS_MODULE;
+ card->num_links = SOF_PROBES_NUM_DAI_LINKS;
+ card->dai_link = links;
+
+ /* set idle_bias_off to prevent the core from resuming the card->dev */
+ card->dapm.idle_bias_off = true;
+
+ snd_soc_card_set_drvdata(card, cdev);
+
+ ret = devm_snd_soc_register_card(dev, card);
+ if (ret < 0) {
+ debugfs_remove(priv->dfs_points);
+ debugfs_remove(priv->dfs_points_remove);
+ dev_err(dev, "Probes card register failed %d\n", ret);
+ return ret;
+ }
+
+ /* enable runtime PM */
+ pm_runtime_set_autosuspend_delay(dev, SOF_PROBES_SUSPEND_DELAY_MS);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_enable(dev);
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_idle(dev);
+
+ return 0;
+}
+
+static void sof_probes_client_remove(struct auxiliary_device *auxdev)
+{
+ struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev);
+ struct sof_probes_priv *priv = cdev->data;
+
+ if (!sof_probes_enabled)
+ return;
+
+ pm_runtime_disable(&auxdev->dev);
+ debugfs_remove(priv->dfs_points);
+ debugfs_remove(priv->dfs_points_remove);
+}
+
+static const struct auxiliary_device_id sof_probes_client_id_table[] = {
+ { .name = "snd_sof.hda-probes", },
+ { .name = "snd_sof.acp-probes", },
+ {},
+};
+MODULE_DEVICE_TABLE(auxiliary, sof_probes_client_id_table);
+
+/* driver name will be set based on KBUILD_MODNAME */
+static struct auxiliary_driver sof_probes_client_drv = {
+ .probe = sof_probes_client_probe,
+ .remove = sof_probes_client_remove,
+
+ .id_table = sof_probes_client_id_table,
+};
+
+module_auxiliary_driver(sof_probes_client_drv);
+
+MODULE_DESCRIPTION("SOF Probes Client Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT);
diff --git a/sound/soc/sof/sof-client-probes.h b/sound/soc/sof/sof-client-probes.h
new file mode 100644
index 000000000000..da04d65b8d99
--- /dev/null
+++ b/sound/soc/sof/sof-client-probes.h
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef __SOF_CLIENT_PROBES_H
+#define __SOF_CLIENT_PROBES_H
+
+struct snd_compr_stream;
+struct snd_compr_tstamp;
+struct snd_compr_params;
+struct sof_client_dev;
+struct snd_soc_dai;
+
+/*
+ * Callbacks used on platforms where the control for audio is split between
+ * DSP and host, like HDA.
+ */
+struct sof_probes_host_ops {
+ int (*startup)(struct sof_client_dev *cdev, struct snd_compr_stream *cstream,
+ struct snd_soc_dai *dai, u32 *stream_id);
+ int (*shutdown)(struct sof_client_dev *cdev, struct snd_compr_stream *cstream,
+ struct snd_soc_dai *dai);
+ int (*set_params)(struct sof_client_dev *cdev, struct snd_compr_stream *cstream,
+ struct snd_compr_params *params,
+ struct snd_soc_dai *dai);
+ int (*trigger)(struct sof_client_dev *cdev, struct snd_compr_stream *cstream,
+ int cmd, struct snd_soc_dai *dai);
+ int (*pointer)(struct sof_client_dev *cdev, struct snd_compr_stream *cstream,
+ struct snd_compr_tstamp *tstamp,
+ struct snd_soc_dai *dai);
+};
+
+struct sof_probe_point_desc {
+ unsigned int buffer_id;
+ unsigned int purpose;
+ unsigned int stream_tag;
+} __packed;
+
+struct sof_probes_ipc_ops {
+ int (*init)(struct sof_client_dev *cdev, u32 stream_tag,
+ size_t buffer_size);
+ int (*deinit)(struct sof_client_dev *cdev);
+ int (*points_info)(struct sof_client_dev *cdev,
+ struct sof_probe_point_desc **desc,
+ size_t *num_desc);
+ int (*points_add)(struct sof_client_dev *cdev,
+ struct sof_probe_point_desc *desc,
+ size_t num_desc);
+ int (*points_remove)(struct sof_client_dev *cdev,
+ unsigned int *buffer_id, size_t num_buffer_id);
+};
+
+extern const struct sof_probes_ipc_ops ipc3_probe_ops;
+extern const struct sof_probes_ipc_ops ipc4_probe_ops;
+
+struct sof_probes_priv {
+ struct dentry *dfs_points;
+ struct dentry *dfs_points_remove;
+ u32 extractor_stream_tag;
+ struct snd_soc_card card;
+ void *ipc_priv;
+
+ const struct sof_probes_host_ops *host_ops;
+ const struct sof_probes_ipc_ops *ipc_ops;
+};
+
+#endif
diff --git a/sound/soc/sof/sof-client.c b/sound/soc/sof/sof-client.c
new file mode 100644
index 000000000000..54dca91255a0
--- /dev/null
+++ b/sound/soc/sof/sof-client.c
@@ -0,0 +1,613 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2022 Intel Corporation. All rights reserved.
+//
+// Authors: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
+// Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
+//
+
+#include <linux/debugfs.h>
+#include <linux/errno.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <sound/sof/ipc4/header.h>
+#include "ops.h"
+#include "sof-client.h"
+#include "sof-priv.h"
+#include "ipc3-priv.h"
+#include "ipc4-priv.h"
+
+/**
+ * struct sof_ipc_event_entry - IPC client event description
+ * @ipc_msg_type: IPC msg type of the event the client is interested
+ * @cdev: sof_client_dev of the requesting client
+ * @callback: Callback function of the client
+ * @list: item in SOF core client event list
+ */
+struct sof_ipc_event_entry {
+ u32 ipc_msg_type;
+ struct sof_client_dev *cdev;
+ sof_client_event_callback callback;
+ struct list_head list;
+};
+
+/**
+ * struct sof_state_event_entry - DSP panic event subscription entry
+ * @cdev: sof_client_dev of the requesting client
+ * @callback: Callback function of the client
+ * @list: item in SOF core client event list
+ */
+struct sof_state_event_entry {
+ struct sof_client_dev *cdev;
+ sof_client_fw_state_callback callback;
+ struct list_head list;
+};
+
+static void sof_client_auxdev_release(struct device *dev)
+{
+ struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
+ struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev);
+
+ kfree(cdev->auxdev.dev.platform_data);
+ kfree(cdev);
+}
+
+static int sof_client_dev_add_data(struct sof_client_dev *cdev, const void *data,
+ size_t size)
+{
+ void *d = NULL;
+
+ if (data) {
+ d = kmemdup(data, size, GFP_KERNEL);
+ if (!d)
+ return -ENOMEM;
+ }
+
+ cdev->auxdev.dev.platform_data = d;
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)
+static int sof_register_ipc_flood_test(struct snd_sof_dev *sdev)
+{
+ int ret = 0;
+ int i;
+
+ if (sdev->pdata->ipc_type != SOF_IPC_TYPE_3)
+ return 0;
+
+ for (i = 0; i < CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST_NUM; i++) {
+ ret = sof_client_dev_register(sdev, "ipc_flood", i, NULL, 0);
+ if (ret < 0)
+ break;
+ }
+
+ if (ret) {
+ for (; i >= 0; --i)
+ sof_client_dev_unregister(sdev, "ipc_flood", i);
+ }
+
+ return ret;
+}
+
+static void sof_unregister_ipc_flood_test(struct snd_sof_dev *sdev)
+{
+ int i;
+
+ for (i = 0; i < CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST_NUM; i++)
+ sof_client_dev_unregister(sdev, "ipc_flood", i);
+}
+#else
+static inline int sof_register_ipc_flood_test(struct snd_sof_dev *sdev)
+{
+ return 0;
+}
+
+static inline void sof_unregister_ipc_flood_test(struct snd_sof_dev *sdev) {}
+#endif /* CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST */
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR)
+static int sof_register_ipc_msg_injector(struct snd_sof_dev *sdev)
+{
+ return sof_client_dev_register(sdev, "msg_injector", 0, NULL, 0);
+}
+
+static void sof_unregister_ipc_msg_injector(struct snd_sof_dev *sdev)
+{
+ sof_client_dev_unregister(sdev, "msg_injector", 0);
+}
+#else
+static inline int sof_register_ipc_msg_injector(struct snd_sof_dev *sdev)
+{
+ return 0;
+}
+
+static inline void sof_unregister_ipc_msg_injector(struct snd_sof_dev *sdev) {}
+#endif /* CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR */
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_KERNEL_INJECTOR)
+static int sof_register_ipc_kernel_injector(struct snd_sof_dev *sdev)
+{
+ /* Only IPC3 supported right now */
+ if (sdev->pdata->ipc_type != SOF_IPC_TYPE_3)
+ return 0;
+
+ return sof_client_dev_register(sdev, "kernel_injector", 0, NULL, 0);
+}
+
+static void sof_unregister_ipc_kernel_injector(struct snd_sof_dev *sdev)
+{
+ sof_client_dev_unregister(sdev, "kernel_injector", 0);
+}
+#else
+static inline int sof_register_ipc_kernel_injector(struct snd_sof_dev *sdev)
+{
+ return 0;
+}
+
+static inline void sof_unregister_ipc_kernel_injector(struct snd_sof_dev *sdev) {}
+#endif /* CONFIG_SND_SOC_SOF_DEBUG_IPC_KERNEL_INJECTOR */
+
+int sof_register_clients(struct snd_sof_dev *sdev)
+{
+ int ret;
+
+ if (sdev->dspless_mode_selected)
+ return 0;
+
+ /* Register platform independent client devices */
+ ret = sof_register_ipc_flood_test(sdev);
+ if (ret) {
+ dev_err(sdev->dev, "IPC flood test client registration failed\n");
+ return ret;
+ }
+
+ ret = sof_register_ipc_msg_injector(sdev);
+ if (ret) {
+ dev_err(sdev->dev, "IPC message injector client registration failed\n");
+ goto err_msg_injector;
+ }
+
+ ret = sof_register_ipc_kernel_injector(sdev);
+ if (ret) {
+ dev_err(sdev->dev, "IPC kernel injector client registration failed\n");
+ goto err_kernel_injector;
+ }
+
+ /* Platform dependent client device registration */
+
+ if (sof_ops(sdev) && sof_ops(sdev)->register_ipc_clients)
+ ret = sof_ops(sdev)->register_ipc_clients(sdev);
+
+ if (!ret)
+ return 0;
+
+ sof_unregister_ipc_kernel_injector(sdev);
+
+err_kernel_injector:
+ sof_unregister_ipc_msg_injector(sdev);
+
+err_msg_injector:
+ sof_unregister_ipc_flood_test(sdev);
+
+ return ret;
+}
+
+void sof_unregister_clients(struct snd_sof_dev *sdev)
+{
+ if (sof_ops(sdev) && sof_ops(sdev)->unregister_ipc_clients)
+ sof_ops(sdev)->unregister_ipc_clients(sdev);
+
+ sof_unregister_ipc_kernel_injector(sdev);
+ sof_unregister_ipc_msg_injector(sdev);
+ sof_unregister_ipc_flood_test(sdev);
+}
+
+int sof_client_dev_register(struct snd_sof_dev *sdev, const char *name, u32 id,
+ const void *data, size_t size)
+{
+ struct auxiliary_device *auxdev;
+ struct sof_client_dev *cdev;
+ int ret;
+
+ cdev = kzalloc(sizeof(*cdev), GFP_KERNEL);
+ if (!cdev)
+ return -ENOMEM;
+
+ cdev->sdev = sdev;
+ auxdev = &cdev->auxdev;
+ auxdev->name = name;
+ auxdev->dev.parent = sdev->dev;
+ auxdev->dev.release = sof_client_auxdev_release;
+ auxdev->id = id;
+
+ ret = sof_client_dev_add_data(cdev, data, size);
+ if (ret < 0)
+ goto err_dev_add_data;
+
+ ret = auxiliary_device_init(auxdev);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to initialize client dev %s.%d\n", name, id);
+ goto err_dev_init;
+ }
+
+ ret = auxiliary_device_add(&cdev->auxdev);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed to add client dev %s.%d\n", name, id);
+ /*
+ * sof_client_auxdev_release() will be invoked to free up memory
+ * allocations through put_device()
+ */
+ auxiliary_device_uninit(&cdev->auxdev);
+ return ret;
+ }
+
+ /* add to list of SOF client devices */
+ mutex_lock(&sdev->ipc_client_mutex);
+ list_add(&cdev->list, &sdev->ipc_client_list);
+ mutex_unlock(&sdev->ipc_client_mutex);
+
+ return 0;
+
+err_dev_init:
+ kfree(cdev->auxdev.dev.platform_data);
+
+err_dev_add_data:
+ kfree(cdev);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_dev_register, SND_SOC_SOF_CLIENT);
+
+void sof_client_dev_unregister(struct snd_sof_dev *sdev, const char *name, u32 id)
+{
+ struct sof_client_dev *cdev;
+
+ mutex_lock(&sdev->ipc_client_mutex);
+
+ /*
+ * sof_client_auxdev_release() will be invoked to free up memory
+ * allocations through put_device()
+ */
+ list_for_each_entry(cdev, &sdev->ipc_client_list, list) {
+ if (!strcmp(cdev->auxdev.name, name) && cdev->auxdev.id == id) {
+ list_del(&cdev->list);
+ auxiliary_device_delete(&cdev->auxdev);
+ auxiliary_device_uninit(&cdev->auxdev);
+ break;
+ }
+ }
+
+ mutex_unlock(&sdev->ipc_client_mutex);
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_dev_unregister, SND_SOC_SOF_CLIENT);
+
+int sof_client_ipc_tx_message(struct sof_client_dev *cdev, void *ipc_msg,
+ void *reply_data, size_t reply_bytes)
+{
+ if (cdev->sdev->pdata->ipc_type == SOF_IPC_TYPE_3) {
+ struct sof_ipc_cmd_hdr *hdr = ipc_msg;
+
+ return sof_ipc_tx_message(cdev->sdev->ipc, ipc_msg, hdr->size,
+ reply_data, reply_bytes);
+ } else if (cdev->sdev->pdata->ipc_type == SOF_IPC_TYPE_4) {
+ struct sof_ipc4_msg *msg = ipc_msg;
+
+ return sof_ipc_tx_message(cdev->sdev->ipc, ipc_msg, msg->data_size,
+ reply_data, reply_bytes);
+ }
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_ipc_tx_message, SND_SOC_SOF_CLIENT);
+
+int sof_client_ipc_rx_message(struct sof_client_dev *cdev, void *ipc_msg, void *msg_buf)
+{
+ if (IS_ENABLED(CONFIG_SND_SOC_SOF_IPC3) &&
+ cdev->sdev->pdata->ipc_type == SOF_IPC_TYPE_3) {
+ struct sof_ipc_cmd_hdr *hdr = ipc_msg;
+
+ if (hdr->size < sizeof(hdr)) {
+ dev_err(cdev->sdev->dev, "The received message size is invalid\n");
+ return -EINVAL;
+ }
+
+ sof_ipc3_do_rx_work(cdev->sdev, ipc_msg, msg_buf);
+ return 0;
+ }
+
+ return -EOPNOTSUPP;
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_ipc_rx_message, SND_SOC_SOF_CLIENT);
+
+int sof_client_ipc_set_get_data(struct sof_client_dev *cdev, void *ipc_msg,
+ bool set)
+{
+ if (cdev->sdev->pdata->ipc_type == SOF_IPC_TYPE_3) {
+ struct sof_ipc_cmd_hdr *hdr = ipc_msg;
+
+ return sof_ipc_set_get_data(cdev->sdev->ipc, ipc_msg, hdr->size,
+ set);
+ } else if (cdev->sdev->pdata->ipc_type == SOF_IPC_TYPE_4) {
+ struct sof_ipc4_msg *msg = ipc_msg;
+
+ return sof_ipc_set_get_data(cdev->sdev->ipc, ipc_msg,
+ msg->data_size, set);
+ }
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_ipc_set_get_data, SND_SOC_SOF_CLIENT);
+
+#ifdef CONFIG_SND_SOC_SOF_IPC4
+struct sof_ipc4_fw_module *sof_client_ipc4_find_module(struct sof_client_dev *c, const guid_t *uuid)
+{
+ struct snd_sof_dev *sdev = c->sdev;
+
+ if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4)
+ return sof_ipc4_find_module_by_uuid(sdev, uuid);
+ dev_err(sdev->dev, "Only supported with IPC4\n");
+
+ return NULL;
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_ipc4_find_module, SND_SOC_SOF_CLIENT);
+#endif
+
+int sof_suspend_clients(struct snd_sof_dev *sdev, pm_message_t state)
+{
+ struct auxiliary_driver *adrv;
+ struct sof_client_dev *cdev;
+
+ mutex_lock(&sdev->ipc_client_mutex);
+
+ list_for_each_entry(cdev, &sdev->ipc_client_list, list) {
+ /* Skip devices without loaded driver */
+ if (!cdev->auxdev.dev.driver)
+ continue;
+
+ adrv = to_auxiliary_drv(cdev->auxdev.dev.driver);
+ if (adrv->suspend)
+ adrv->suspend(&cdev->auxdev, state);
+ }
+
+ mutex_unlock(&sdev->ipc_client_mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(sof_suspend_clients, SND_SOC_SOF_CLIENT);
+
+int sof_resume_clients(struct snd_sof_dev *sdev)
+{
+ struct auxiliary_driver *adrv;
+ struct sof_client_dev *cdev;
+
+ mutex_lock(&sdev->ipc_client_mutex);
+
+ list_for_each_entry(cdev, &sdev->ipc_client_list, list) {
+ /* Skip devices without loaded driver */
+ if (!cdev->auxdev.dev.driver)
+ continue;
+
+ adrv = to_auxiliary_drv(cdev->auxdev.dev.driver);
+ if (adrv->resume)
+ adrv->resume(&cdev->auxdev);
+ }
+
+ mutex_unlock(&sdev->ipc_client_mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(sof_resume_clients, SND_SOC_SOF_CLIENT);
+
+struct dentry *sof_client_get_debugfs_root(struct sof_client_dev *cdev)
+{
+ return cdev->sdev->debugfs_root;
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_get_debugfs_root, SND_SOC_SOF_CLIENT);
+
+/* DMA buffer allocation in client drivers must use the core SOF device */
+struct device *sof_client_get_dma_dev(struct sof_client_dev *cdev)
+{
+ return cdev->sdev->dev;
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_get_dma_dev, SND_SOC_SOF_CLIENT);
+
+const struct sof_ipc_fw_version *sof_client_get_fw_version(struct sof_client_dev *cdev)
+{
+ struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
+
+ return &sdev->fw_ready.version;
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_get_fw_version, SND_SOC_SOF_CLIENT);
+
+size_t sof_client_get_ipc_max_payload_size(struct sof_client_dev *cdev)
+{
+ struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
+
+ return sdev->ipc->max_payload_size;
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_get_ipc_max_payload_size, SND_SOC_SOF_CLIENT);
+
+enum sof_ipc_type sof_client_get_ipc_type(struct sof_client_dev *cdev)
+{
+ struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
+
+ return sdev->pdata->ipc_type;
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_get_ipc_type, SND_SOC_SOF_CLIENT);
+
+/* module refcount management of SOF core */
+int sof_client_core_module_get(struct sof_client_dev *cdev)
+{
+ struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
+
+ if (!try_module_get(sdev->dev->driver->owner))
+ return -ENODEV;
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_core_module_get, SND_SOC_SOF_CLIENT);
+
+void sof_client_core_module_put(struct sof_client_dev *cdev)
+{
+ struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
+
+ module_put(sdev->dev->driver->owner);
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_core_module_put, SND_SOC_SOF_CLIENT);
+
+/* IPC event handling */
+void sof_client_ipc_rx_dispatcher(struct snd_sof_dev *sdev, void *msg_buf)
+{
+ struct sof_ipc_event_entry *event;
+ u32 msg_type;
+
+ if (sdev->pdata->ipc_type == SOF_IPC_TYPE_3) {
+ struct sof_ipc_cmd_hdr *hdr = msg_buf;
+
+ msg_type = hdr->cmd & SOF_GLB_TYPE_MASK;
+ } else if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4) {
+ struct sof_ipc4_msg *msg = msg_buf;
+
+ msg_type = SOF_IPC4_NOTIFICATION_TYPE_GET(msg->primary);
+ } else {
+ dev_dbg_once(sdev->dev, "Not supported IPC version: %d\n",
+ sdev->pdata->ipc_type);
+ return;
+ }
+
+ mutex_lock(&sdev->client_event_handler_mutex);
+
+ list_for_each_entry(event, &sdev->ipc_rx_handler_list, list) {
+ if (event->ipc_msg_type == msg_type)
+ event->callback(event->cdev, msg_buf);
+ }
+
+ mutex_unlock(&sdev->client_event_handler_mutex);
+}
+
+int sof_client_register_ipc_rx_handler(struct sof_client_dev *cdev,
+ u32 ipc_msg_type,
+ sof_client_event_callback callback)
+{
+ struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
+ struct sof_ipc_event_entry *event;
+
+ if (!callback)
+ return -EINVAL;
+
+ if (cdev->sdev->pdata->ipc_type == SOF_IPC_TYPE_3) {
+ if (!(ipc_msg_type & SOF_GLB_TYPE_MASK))
+ return -EINVAL;
+ } else if (cdev->sdev->pdata->ipc_type == SOF_IPC_TYPE_4) {
+ if (!(ipc_msg_type & SOF_IPC4_NOTIFICATION_TYPE_MASK))
+ return -EINVAL;
+ } else {
+ dev_warn(sdev->dev, "%s: Not supported IPC version: %d\n",
+ __func__, sdev->pdata->ipc_type);
+ return -EINVAL;
+ }
+
+ event = kmalloc(sizeof(*event), GFP_KERNEL);
+ if (!event)
+ return -ENOMEM;
+
+ event->ipc_msg_type = ipc_msg_type;
+ event->cdev = cdev;
+ event->callback = callback;
+
+ /* add to list of SOF client devices */
+ mutex_lock(&sdev->client_event_handler_mutex);
+ list_add(&event->list, &sdev->ipc_rx_handler_list);
+ mutex_unlock(&sdev->client_event_handler_mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_register_ipc_rx_handler, SND_SOC_SOF_CLIENT);
+
+void sof_client_unregister_ipc_rx_handler(struct sof_client_dev *cdev,
+ u32 ipc_msg_type)
+{
+ struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
+ struct sof_ipc_event_entry *event;
+
+ mutex_lock(&sdev->client_event_handler_mutex);
+
+ list_for_each_entry(event, &sdev->ipc_rx_handler_list, list) {
+ if (event->cdev == cdev && event->ipc_msg_type == ipc_msg_type) {
+ list_del(&event->list);
+ kfree(event);
+ break;
+ }
+ }
+
+ mutex_unlock(&sdev->client_event_handler_mutex);
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_unregister_ipc_rx_handler, SND_SOC_SOF_CLIENT);
+
+/*DSP state notification and query */
+void sof_client_fw_state_dispatcher(struct snd_sof_dev *sdev)
+{
+ struct sof_state_event_entry *event;
+
+ mutex_lock(&sdev->client_event_handler_mutex);
+
+ list_for_each_entry(event, &sdev->fw_state_handler_list, list)
+ event->callback(event->cdev, sdev->fw_state);
+
+ mutex_unlock(&sdev->client_event_handler_mutex);
+}
+
+int sof_client_register_fw_state_handler(struct sof_client_dev *cdev,
+ sof_client_fw_state_callback callback)
+{
+ struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
+ struct sof_state_event_entry *event;
+
+ if (!callback)
+ return -EINVAL;
+
+ event = kmalloc(sizeof(*event), GFP_KERNEL);
+ if (!event)
+ return -ENOMEM;
+
+ event->cdev = cdev;
+ event->callback = callback;
+
+ /* add to list of SOF client devices */
+ mutex_lock(&sdev->client_event_handler_mutex);
+ list_add(&event->list, &sdev->fw_state_handler_list);
+ mutex_unlock(&sdev->client_event_handler_mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_register_fw_state_handler, SND_SOC_SOF_CLIENT);
+
+void sof_client_unregister_fw_state_handler(struct sof_client_dev *cdev)
+{
+ struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
+ struct sof_state_event_entry *event;
+
+ mutex_lock(&sdev->client_event_handler_mutex);
+
+ list_for_each_entry(event, &sdev->fw_state_handler_list, list) {
+ if (event->cdev == cdev) {
+ list_del(&event->list);
+ kfree(event);
+ break;
+ }
+ }
+
+ mutex_unlock(&sdev->client_event_handler_mutex);
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_unregister_fw_state_handler, SND_SOC_SOF_CLIENT);
+
+enum sof_fw_state sof_client_get_fw_state(struct sof_client_dev *cdev)
+{
+ struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
+
+ return sdev->fw_state;
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_get_fw_state, SND_SOC_SOF_CLIENT);
diff --git a/sound/soc/sof/sof-client.h b/sound/soc/sof/sof-client.h
new file mode 100644
index 000000000000..b6ccc2cd69e5
--- /dev/null
+++ b/sound/soc/sof/sof-client.h
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef __SOC_SOF_CLIENT_H
+#define __SOC_SOF_CLIENT_H
+
+#include <linux/auxiliary_bus.h>
+#include <linux/device.h>
+#include <linux/list.h>
+#include <sound/sof.h>
+
+struct sof_ipc_fw_version;
+struct sof_ipc_cmd_hdr;
+struct snd_sof_dev;
+struct dentry;
+
+struct sof_ipc4_fw_module;
+
+/**
+ * struct sof_client_dev - SOF client device
+ * @auxdev: auxiliary device
+ * @sdev: pointer to SOF core device struct
+ * @list: item in SOF core client dev list
+ * @data: device specific data
+ */
+struct sof_client_dev {
+ struct auxiliary_device auxdev;
+ struct snd_sof_dev *sdev;
+ struct list_head list;
+ void *data;
+};
+
+#define sof_client_dev_to_sof_dev(cdev) ((cdev)->sdev)
+
+#define auxiliary_dev_to_sof_client_dev(auxiliary_dev) \
+ container_of(auxiliary_dev, struct sof_client_dev, auxdev)
+
+#define dev_to_sof_client_dev(dev) \
+ container_of(to_auxiliary_dev(dev), struct sof_client_dev, auxdev)
+
+int sof_client_ipc_tx_message(struct sof_client_dev *cdev, void *ipc_msg,
+ void *reply_data, size_t reply_bytes);
+static inline int sof_client_ipc_tx_message_no_reply(struct sof_client_dev *cdev, void *ipc_msg)
+{
+ return sof_client_ipc_tx_message(cdev, ipc_msg, NULL, 0);
+}
+int sof_client_ipc_set_get_data(struct sof_client_dev *cdev, void *ipc_msg,
+ bool set);
+
+struct sof_ipc4_fw_module *sof_client_ipc4_find_module(struct sof_client_dev *c, const guid_t *u);
+
+struct dentry *sof_client_get_debugfs_root(struct sof_client_dev *cdev);
+struct device *sof_client_get_dma_dev(struct sof_client_dev *cdev);
+const struct sof_ipc_fw_version *sof_client_get_fw_version(struct sof_client_dev *cdev);
+size_t sof_client_get_ipc_max_payload_size(struct sof_client_dev *cdev);
+enum sof_ipc_type sof_client_get_ipc_type(struct sof_client_dev *cdev);
+
+/* module refcount management of SOF core */
+int sof_client_core_module_get(struct sof_client_dev *cdev);
+void sof_client_core_module_put(struct sof_client_dev *cdev);
+
+/* IPC notification */
+typedef void (*sof_client_event_callback)(struct sof_client_dev *cdev, void *msg_buf);
+
+int sof_client_register_ipc_rx_handler(struct sof_client_dev *cdev,
+ u32 ipc_msg_type,
+ sof_client_event_callback callback);
+void sof_client_unregister_ipc_rx_handler(struct sof_client_dev *cdev,
+ u32 ipc_msg_type);
+
+/* DSP state notification and query */
+typedef void (*sof_client_fw_state_callback)(struct sof_client_dev *cdev,
+ enum sof_fw_state state);
+
+int sof_client_register_fw_state_handler(struct sof_client_dev *cdev,
+ sof_client_fw_state_callback callback);
+void sof_client_unregister_fw_state_handler(struct sof_client_dev *cdev);
+enum sof_fw_state sof_client_get_fw_state(struct sof_client_dev *cdev);
+int sof_client_ipc_rx_message(struct sof_client_dev *cdev, void *ipc_msg, void *msg_buf);
+
+#endif /* __SOC_SOF_CLIENT_H */
diff --git a/sound/soc/sof/sof-of-dev.c b/sound/soc/sof/sof-of-dev.c
index f492c5dfa659..b9a499e92b9a 100644
--- a/sound/soc/sof/sof-of-dev.c
+++ b/sound/soc/sof/sof-of-dev.c
@@ -7,65 +7,45 @@
#include <linux/firmware.h>
#include <linux/module.h>
+#include <linux/moduleparam.h>
#include <linux/pm_runtime.h>
#include <sound/sof.h>
+#include "sof-of-dev.h"
#include "ops.h"
-extern struct snd_sof_dsp_ops sof_imx8_ops;
-extern struct snd_sof_dsp_ops sof_imx8x_ops;
-extern struct snd_sof_dsp_ops sof_imx8m_ops;
-
-/* platform specific devices */
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_IMX8)
-static struct sof_dev_desc sof_of_imx8qxp_desc = {
- .default_fw_path = "imx/sof",
- .default_tplg_path = "imx/sof-tplg",
- .default_fw_filename = "sof-imx8x.ri",
- .nocodec_tplg_filename = "sof-imx8-nocodec.tplg",
- .ops = &sof_imx8x_ops,
-};
+static char *fw_path;
+module_param(fw_path, charp, 0444);
+MODULE_PARM_DESC(fw_path, "alternate path for SOF firmware.");
-static struct sof_dev_desc sof_of_imx8qm_desc = {
- .default_fw_path = "imx/sof",
- .default_tplg_path = "imx/sof-tplg",
- .default_fw_filename = "sof-imx8.ri",
- .nocodec_tplg_filename = "sof-imx8-nocodec.tplg",
- .ops = &sof_imx8_ops,
-};
-#endif
-
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_IMX8M)
-static struct sof_dev_desc sof_of_imx8mp_desc = {
- .default_fw_path = "imx/sof",
- .default_tplg_path = "imx/sof-tplg",
- .default_fw_filename = "sof-imx8m.ri",
- .nocodec_tplg_filename = "sof-imx8-nocodec.tplg",
- .ops = &sof_imx8m_ops,
-};
-#endif
+static char *tplg_path;
+module_param(tplg_path, charp, 0444);
+MODULE_PARM_DESC(tplg_path, "alternate path for SOF topology.");
-static const struct dev_pm_ops sof_of_pm = {
+const struct dev_pm_ops sof_of_pm = {
+ .prepare = snd_sof_prepare,
+ .complete = snd_sof_complete,
SET_SYSTEM_SLEEP_PM_OPS(snd_sof_suspend, snd_sof_resume)
SET_RUNTIME_PM_OPS(snd_sof_runtime_suspend, snd_sof_runtime_resume,
NULL)
};
+EXPORT_SYMBOL(sof_of_pm);
static void sof_of_probe_complete(struct device *dev)
{
/* allow runtime_pm */
pm_runtime_set_autosuspend_delay(dev, SND_SOF_SUSPEND_DELAY_MS);
pm_runtime_use_autosuspend(dev);
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_set_active(dev);
pm_runtime_enable(dev);
}
-static int sof_of_probe(struct platform_device *pdev)
+int sof_of_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct sof_dev_desc *desc;
struct snd_sof_pdata *sof_pdata;
- const struct snd_sof_dsp_ops *ops;
- int ret;
dev_info(&pdev->dev, "DT DSP detected");
@@ -77,71 +57,39 @@ static int sof_of_probe(struct platform_device *pdev)
if (!desc)
return -ENODEV;
- /* get ops for platform */
- ops = desc->ops;
- if (!ops) {
+ if (!desc->ops) {
dev_err(dev, "error: no matching DT descriptor ops\n");
return -ENODEV;
}
sof_pdata->desc = desc;
sof_pdata->dev = &pdev->dev;
- sof_pdata->fw_filename = desc->default_fw_filename;
- /* TODO: read alternate fw and tplg filenames from DT */
- sof_pdata->fw_filename_prefix = sof_pdata->desc->default_fw_path;
- sof_pdata->tplg_filename_prefix = sof_pdata->desc->default_tplg_path;
+ sof_pdata->ipc_file_profile_base.ipc_type = desc->ipc_default;
+ sof_pdata->ipc_file_profile_base.fw_path = fw_path;
+ sof_pdata->ipc_file_profile_base.tplg_path = tplg_path;
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)
- /* set callback to enable runtime_pm */
+ /* set callback to be called on successful device probe to enable runtime_pm */
sof_pdata->sof_probe_complete = sof_of_probe_complete;
-#endif
- /* call sof helper for DSP hardware probe */
- ret = snd_sof_device_probe(dev, sof_pdata);
- if (ret) {
- dev_err(dev, "error: failed to probe DSP hardware\n");
- return ret;
- }
-
-#if !IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)
- sof_of_probe_complete(dev);
-#endif
- return ret;
+ /* call sof helper for DSP hardware probe */
+ return snd_sof_device_probe(dev, sof_pdata);
}
+EXPORT_SYMBOL(sof_of_probe);
-static int sof_of_remove(struct platform_device *pdev)
+void sof_of_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
/* call sof helper for DSP hardware remove */
snd_sof_device_remove(&pdev->dev);
-
- return 0;
}
+EXPORT_SYMBOL(sof_of_remove);
-static const struct of_device_id sof_of_ids[] = {
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_IMX8)
- { .compatible = "fsl,imx8qxp-dsp", .data = &sof_of_imx8qxp_desc},
- { .compatible = "fsl,imx8qm-dsp", .data = &sof_of_imx8qm_desc},
-#endif
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_IMX8M)
- { .compatible = "fsl,imx8mp-dsp", .data = &sof_of_imx8mp_desc},
-#endif
- { }
-};
-MODULE_DEVICE_TABLE(of, sof_of_ids);
-
-/* DT driver definition */
-static struct platform_driver snd_sof_of_driver = {
- .probe = sof_of_probe,
- .remove = sof_of_remove,
- .driver = {
- .name = "sof-audio-of",
- .pm = &sof_of_pm,
- .of_match_table = sof_of_ids,
- },
-};
-module_platform_driver(snd_sof_of_driver);
+void sof_of_shutdown(struct platform_device *pdev)
+{
+ snd_sof_device_shutdown(&pdev->dev);
+}
+EXPORT_SYMBOL(sof_of_shutdown);
MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/sof/sof-of-dev.h b/sound/soc/sof/sof-of-dev.h
new file mode 100644
index 000000000000..b6cc70595f3b
--- /dev/null
+++ b/sound/soc/sof/sof-of-dev.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+/*
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * Copyright 2021 NXP
+ */
+
+#ifndef __SOUND_SOC_SOF_OF_H
+#define __SOUND_SOC_SOF_OF_H
+
+struct snd_sof_of_mach {
+ const char *compatible;
+ const char *drv_name;
+ const char *fw_filename;
+ const char *sof_tplg_filename;
+};
+
+extern const struct dev_pm_ops sof_of_pm;
+
+int sof_of_probe(struct platform_device *pdev);
+void sof_of_remove(struct platform_device *pdev);
+void sof_of_shutdown(struct platform_device *pdev);
+
+#endif
diff --git a/sound/soc/sof/sof-pci-dev.c b/sound/soc/sof/sof-pci-dev.c
index aa3532ba1434..aab5c900cecf 100644
--- a/sound/soc/sof/sof-pci-dev.c
+++ b/sound/soc/sof/sof-pci-dev.c
@@ -12,240 +12,157 @@
#include <linux/dmi.h>
#include <linux/module.h>
#include <linux/pci.h>
+#include <linux/platform_data/x86/soc.h>
#include <linux/pm_runtime.h>
-#include <sound/intel-dsp-config.h>
#include <sound/soc-acpi.h>
#include <sound/soc-acpi-intel-match.h>
#include <sound/sof.h>
#include "ops.h"
-
-/* platform specific devices */
-#include "intel/shim.h"
-#include "intel/hda.h"
+#include "sof-pci-dev.h"
static char *fw_path;
module_param(fw_path, charp, 0444);
MODULE_PARM_DESC(fw_path, "alternate path for SOF firmware.");
+static char *fw_filename;
+module_param(fw_filename, charp, 0444);
+MODULE_PARM_DESC(fw_filename, "alternate filename for SOF firmware.");
+
+static char *lib_path;
+module_param(lib_path, charp, 0444);
+MODULE_PARM_DESC(lib_path, "alternate path for SOF firmware libraries.");
+
static char *tplg_path;
module_param(tplg_path, charp, 0444);
MODULE_PARM_DESC(tplg_path, "alternate path for SOF topology.");
+static char *tplg_filename;
+module_param(tplg_filename, charp, 0444);
+MODULE_PARM_DESC(tplg_filename, "alternate filename for SOF topology.");
+
static int sof_pci_debug;
module_param_named(sof_pci_debug, sof_pci_debug, int, 0444);
MODULE_PARM_DESC(sof_pci_debug, "SOF PCI debug options (0x0 all off)");
+static int sof_pci_ipc_type = -1;
+module_param_named(ipc_type, sof_pci_ipc_type, int, 0444);
+MODULE_PARM_DESC(ipc_type, "Force SOF IPC type. 0 - IPC3, 1 - IPC4");
+
+static const char *sof_dmi_override_tplg_name;
+static bool sof_dmi_use_community_key;
+
#define SOF_PCI_DISABLE_PM_RUNTIME BIT(0)
+static int sof_tplg_cb(const struct dmi_system_id *id)
+{
+ sof_dmi_override_tplg_name = id->driver_data;
+ return 1;
+}
+
+static const struct dmi_system_id sof_tplg_table[] = {
+ {
+ .callback = sof_tplg_cb,
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_FAMILY, "Google_Volteer"),
+ DMI_MATCH(DMI_OEM_STRING, "AUDIO-MAX98373_ALC5682I_I2S_UP4"),
+ },
+ .driver_data = "sof-tgl-rt5682-ssp0-max98373-ssp2.tplg",
+ },
+ {
+ .callback = sof_tplg_cb,
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Alder Lake Client Platform"),
+ DMI_MATCH(DMI_OEM_STRING, "AUDIO-ADL_MAX98373_ALC5682I_I2S"),
+ },
+ .driver_data = "sof-adl-rt5682-ssp0-max98373-ssp2.tplg",
+ },
+ {
+ .callback = sof_tplg_cb,
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_FAMILY, "Google_Brya"),
+ DMI_MATCH(DMI_OEM_STRING, "AUDIO-MAX98390_ALC5682I_I2S"),
+ },
+ .driver_data = "sof-adl-max98390-ssp2-rt5682-ssp0.tplg",
+ },
+ {
+ .callback = sof_tplg_cb,
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_FAMILY, "Google_Brya"),
+ DMI_MATCH(DMI_OEM_STRING, "AUDIO_AMP-MAX98360_ALC5682VS_I2S_2WAY"),
+ },
+ .driver_data = "sof-adl-max98360a-rt5682-2way.tplg",
+ },
+ {
+ .callback = sof_tplg_cb,
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_FAMILY, "Google_Brya"),
+ DMI_MATCH(DMI_OEM_STRING, "AUDIO-AUDIO_MAX98357_ALC5682I_I2S_2WAY"),
+ },
+ .driver_data = "sof-adl-max98357a-rt5682-2way.tplg",
+ },
+ {
+ .callback = sof_tplg_cb,
+ .matches = {
+ DMI_MATCH(DMI_PRODUCT_FAMILY, "Google_Brya"),
+ DMI_MATCH(DMI_OEM_STRING, "AUDIO-MAX98360_ALC5682I_I2S_AMP_SSP2"),
+ },
+ .driver_data = "sof-adl-max98357a-rt5682.tplg",
+ },
+ {}
+};
+
+/* all Up boards use the community key */
+static int up_use_community_key(const struct dmi_system_id *id)
+{
+ sof_dmi_use_community_key = true;
+ return 1;
+}
+
+/*
+ * For ApolloLake Chromebooks we want to force the use of the Intel production key.
+ * All newer platforms use the community key
+ */
+static int chromebook_use_community_key(const struct dmi_system_id *id)
+{
+ if (!soc_intel_is_apl())
+ sof_dmi_use_community_key = true;
+ return 1;
+}
+
static const struct dmi_system_id community_key_platforms[] = {
{
- .ident = "Up Squared",
+ .ident = "Up boards",
+ .callback = up_use_community_key,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "AAEON"),
- DMI_MATCH(DMI_BOARD_NAME, "UP-APL01"),
}
},
{
.ident = "Google Chromebooks",
+ .callback = chromebook_use_community_key,
.matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "Google"),
+ DMI_MATCH(DMI_PRODUCT_FAMILY, "Google"),
}
},
- {},
-};
-
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_APOLLOLAKE)
-static const struct sof_dev_desc bxt_desc = {
- .machines = snd_soc_acpi_intel_bxt_machines,
- .resindex_lpe_base = 0,
- .resindex_pcicfg_base = -1,
- .resindex_imr_base = -1,
- .irqindex_host_ipc = -1,
- .resindex_dma_base = -1,
- .chip_info = &apl_chip_info,
- .default_fw_path = "intel/sof",
- .default_tplg_path = "intel/sof-tplg",
- .default_fw_filename = "sof-apl.ri",
- .nocodec_tplg_filename = "sof-apl-nocodec.tplg",
- .ops = &sof_apl_ops,
-};
-#endif
-
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_GEMINILAKE)
-static const struct sof_dev_desc glk_desc = {
- .machines = snd_soc_acpi_intel_glk_machines,
- .resindex_lpe_base = 0,
- .resindex_pcicfg_base = -1,
- .resindex_imr_base = -1,
- .irqindex_host_ipc = -1,
- .resindex_dma_base = -1,
- .chip_info = &apl_chip_info,
- .default_fw_path = "intel/sof",
- .default_tplg_path = "intel/sof-tplg",
- .default_fw_filename = "sof-glk.ri",
- .nocodec_tplg_filename = "sof-glk-nocodec.tplg",
- .ops = &sof_apl_ops,
-};
-#endif
-
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_MERRIFIELD)
-static struct snd_soc_acpi_mach sof_tng_machines[] = {
{
- .id = "INT343A",
- .drv_name = "edison",
- .sof_fw_filename = "sof-byt.ri",
- .sof_tplg_filename = "sof-byt.tplg",
+ .ident = "Google firmware",
+ .callback = chromebook_use_community_key,
+ .matches = {
+ DMI_MATCH(DMI_BIOS_VERSION, "Google"),
+ }
},
- {}
-};
-
-static const struct sof_dev_desc tng_desc = {
- .machines = sof_tng_machines,
- .resindex_lpe_base = 3, /* IRAM, but subtract IRAM offset */
- .resindex_pcicfg_base = -1,
- .resindex_imr_base = 0,
- .irqindex_host_ipc = -1,
- .resindex_dma_base = -1,
- .chip_info = &tng_chip_info,
- .default_fw_path = "intel/sof",
- .default_tplg_path = "intel/sof-tplg",
- .default_fw_filename = "sof-byt.ri",
- .nocodec_tplg_filename = "sof-byt.tplg",
- .ops = &sof_tng_ops,
-};
-#endif
-
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_CANNONLAKE)
-static const struct sof_dev_desc cnl_desc = {
- .machines = snd_soc_acpi_intel_cnl_machines,
- .alt_machines = snd_soc_acpi_intel_cnl_sdw_machines,
- .resindex_lpe_base = 0,
- .resindex_pcicfg_base = -1,
- .resindex_imr_base = -1,
- .irqindex_host_ipc = -1,
- .resindex_dma_base = -1,
- .chip_info = &cnl_chip_info,
- .default_fw_path = "intel/sof",
- .default_tplg_path = "intel/sof-tplg",
- .default_fw_filename = "sof-cnl.ri",
- .nocodec_tplg_filename = "sof-cnl-nocodec.tplg",
- .ops = &sof_cnl_ops,
-};
-#endif
-
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_COFFEELAKE)
-static const struct sof_dev_desc cfl_desc = {
- .machines = snd_soc_acpi_intel_cfl_machines,
- .alt_machines = snd_soc_acpi_intel_cfl_sdw_machines,
- .resindex_lpe_base = 0,
- .resindex_pcicfg_base = -1,
- .resindex_imr_base = -1,
- .irqindex_host_ipc = -1,
- .resindex_dma_base = -1,
- .chip_info = &cnl_chip_info,
- .default_fw_path = "intel/sof",
- .default_tplg_path = "intel/sof-tplg",
- .default_fw_filename = "sof-cfl.ri",
- .nocodec_tplg_filename = "sof-cnl-nocodec.tplg",
- .ops = &sof_cnl_ops,
-};
-#endif
-
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMETLAKE)
-static const struct sof_dev_desc cml_desc = {
- .machines = snd_soc_acpi_intel_cml_machines,
- .alt_machines = snd_soc_acpi_intel_cml_sdw_machines,
- .resindex_lpe_base = 0,
- .resindex_pcicfg_base = -1,
- .resindex_imr_base = -1,
- .irqindex_host_ipc = -1,
- .resindex_dma_base = -1,
- .chip_info = &cnl_chip_info,
- .default_fw_path = "intel/sof",
- .default_tplg_path = "intel/sof-tplg",
- .default_fw_filename = "sof-cml.ri",
- .nocodec_tplg_filename = "sof-cnl-nocodec.tplg",
- .ops = &sof_cnl_ops,
-};
-#endif
-
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_ICELAKE)
-static const struct sof_dev_desc icl_desc = {
- .machines = snd_soc_acpi_intel_icl_machines,
- .alt_machines = snd_soc_acpi_intel_icl_sdw_machines,
- .resindex_lpe_base = 0,
- .resindex_pcicfg_base = -1,
- .resindex_imr_base = -1,
- .irqindex_host_ipc = -1,
- .resindex_dma_base = -1,
- .chip_info = &icl_chip_info,
- .default_fw_path = "intel/sof",
- .default_tplg_path = "intel/sof-tplg",
- .default_fw_filename = "sof-icl.ri",
- .nocodec_tplg_filename = "sof-icl-nocodec.tplg",
- .ops = &sof_cnl_ops,
-};
-#endif
-
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_TIGERLAKE)
-static const struct sof_dev_desc tgl_desc = {
- .machines = snd_soc_acpi_intel_tgl_machines,
- .alt_machines = snd_soc_acpi_intel_tgl_sdw_machines,
- .resindex_lpe_base = 0,
- .resindex_pcicfg_base = -1,
- .resindex_imr_base = -1,
- .irqindex_host_ipc = -1,
- .resindex_dma_base = -1,
- .chip_info = &tgl_chip_info,
- .default_fw_path = "intel/sof",
- .default_tplg_path = "intel/sof-tplg",
- .default_fw_filename = "sof-tgl.ri",
- .nocodec_tplg_filename = "sof-tgl-nocodec.tplg",
- .ops = &sof_cnl_ops,
-};
-#endif
-
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_ELKHARTLAKE)
-static const struct sof_dev_desc ehl_desc = {
- .machines = snd_soc_acpi_intel_ehl_machines,
- .resindex_lpe_base = 0,
- .resindex_pcicfg_base = -1,
- .resindex_imr_base = -1,
- .irqindex_host_ipc = -1,
- .resindex_dma_base = -1,
- .chip_info = &ehl_chip_info,
- .default_fw_path = "intel/sof",
- .default_tplg_path = "intel/sof-tplg",
- .default_fw_filename = "sof-ehl.ri",
- .nocodec_tplg_filename = "sof-ehl-nocodec.tplg",
- .ops = &sof_cnl_ops,
-};
-#endif
-
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_JASPERLAKE)
-static const struct sof_dev_desc jsl_desc = {
- .machines = snd_soc_acpi_intel_jsl_machines,
- .resindex_lpe_base = 0,
- .resindex_pcicfg_base = -1,
- .resindex_imr_base = -1,
- .irqindex_host_ipc = -1,
- .resindex_dma_base = -1,
- .chip_info = &jsl_chip_info,
- .default_fw_path = "intel/sof",
- .default_tplg_path = "intel/sof-tplg",
- .default_fw_filename = "sof-jsl.ri",
- .nocodec_tplg_filename = "sof-jsl-nocodec.tplg",
- .ops = &sof_cnl_ops,
+ {},
};
-#endif
-static const struct dev_pm_ops sof_pci_pm = {
+const struct dev_pm_ops sof_pci_pm = {
.prepare = snd_sof_prepare,
.complete = snd_sof_complete,
SET_SYSTEM_SLEEP_PM_OPS(snd_sof_suspend, snd_sof_resume)
SET_RUNTIME_PM_OPS(snd_sof_runtime_suspend, snd_sof_runtime_resume,
snd_sof_runtime_idle)
};
+EXPORT_SYMBOL_NS(sof_pci_pm, SND_SOC_SOF_PCI_DEV);
static void sof_pci_probe_complete(struct device *dev)
{
@@ -271,26 +188,23 @@ static void sof_pci_probe_complete(struct device *dev)
pm_runtime_put_noidle(dev);
}
-static int sof_pci_probe(struct pci_dev *pci,
- const struct pci_device_id *pci_id)
+int sof_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
{
+ struct sof_loadable_file_profile *path_override;
struct device *dev = &pci->dev;
const struct sof_dev_desc *desc =
(const struct sof_dev_desc *)pci_id->driver_data;
struct snd_sof_pdata *sof_pdata;
- const struct snd_sof_dsp_ops *ops;
int ret;
- ret = snd_intel_dsp_driver_probe(pci);
- if (ret != SND_INTEL_DSP_DRIVER_ANY &&
- ret != SND_INTEL_DSP_DRIVER_SOF)
- return -ENODEV;
-
dev_dbg(&pci->dev, "PCI DSP detected");
- /* get ops for platform */
- ops = desc->ops;
- if (!ops) {
+ if (!desc) {
+ dev_err(dev, "error: no matching PCI descriptor\n");
+ return -ENODEV;
+ }
+
+ if (!desc->ops) {
dev_err(dev, "error: no matching PCI descriptor ops\n");
return -ENODEV;
}
@@ -308,156 +222,85 @@ static int sof_pci_probe(struct pci_dev *pci,
return ret;
sof_pdata->name = pci_name(pci);
- sof_pdata->desc = (struct sof_dev_desc *)pci_id->driver_data;
+
+ /* PCI defines a vendor ID of 0xFFFF as invalid. */
+ if (pci->subsystem_vendor != 0xFFFF) {
+ sof_pdata->subsystem_vendor = pci->subsystem_vendor;
+ sof_pdata->subsystem_device = pci->subsystem_device;
+ sof_pdata->subsystem_id_set = true;
+ }
+
+ sof_pdata->desc = desc;
sof_pdata->dev = dev;
- sof_pdata->fw_filename = desc->default_fw_filename;
- /*
- * for platforms using the SOF community key, change the
- * default path automatically to pick the right files from the
- * linux-firmware tree. This can be overridden with the
- * fw_path kernel parameter, e.g. for developers.
- */
+ path_override = &sof_pdata->ipc_file_profile_base;
- /* alternate fw and tplg filenames ? */
- if (fw_path) {
- sof_pdata->fw_filename_prefix = fw_path;
+ if (sof_pci_ipc_type < 0) {
+ path_override->ipc_type = desc->ipc_default;
+ } else if (sof_pci_ipc_type < SOF_IPC_TYPE_COUNT) {
+ path_override->ipc_type = sof_pci_ipc_type;
+ } else {
+ dev_err(dev, "Invalid IPC type requested: %d\n", sof_pci_ipc_type);
+ ret = -EINVAL;
+ goto out;
+ }
- dev_dbg(dev,
- "Module parameter used, changed fw path to %s\n",
- sof_pdata->fw_filename_prefix);
+ path_override->fw_path = fw_path;
+ path_override->fw_name = fw_filename;
+ path_override->fw_lib_path = lib_path;
+ path_override->tplg_path = tplg_path;
- } else if (dmi_check_system(community_key_platforms)) {
- sof_pdata->fw_filename_prefix =
- devm_kasprintf(dev, GFP_KERNEL, "%s/%s",
- sof_pdata->desc->default_fw_path,
- "community");
+ if (dmi_check_system(community_key_platforms) &&
+ sof_dmi_use_community_key) {
+ path_override->fw_path_postfix = "community";
+ path_override->fw_lib_path_postfix = "community";
+ }
- dev_dbg(dev,
- "Platform uses community key, changed fw path to %s\n",
- sof_pdata->fw_filename_prefix);
+ /*
+ * the topology filename will be provided in the machine descriptor, unless
+ * it is overridden by a module parameter or DMI quirk.
+ */
+ if (tplg_filename) {
+ path_override->tplg_name = tplg_filename;
} else {
- sof_pdata->fw_filename_prefix =
- sof_pdata->desc->default_fw_path;
+ dmi_check_system(sof_tplg_table);
+ if (sof_dmi_override_tplg_name)
+ path_override->tplg_name = sof_dmi_override_tplg_name;
}
- if (tplg_path)
- sof_pdata->tplg_filename_prefix = tplg_path;
- else
- sof_pdata->tplg_filename_prefix =
- sof_pdata->desc->default_tplg_path;
-
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)
- /* set callback to enable runtime_pm */
+ /* set callback to be called on successful device probe to enable runtime_pm */
sof_pdata->sof_probe_complete = sof_pci_probe_complete;
-#endif
+
/* call sof helper for DSP hardware probe */
ret = snd_sof_device_probe(dev, sof_pdata);
- if (ret) {
- dev_err(dev, "error: failed to probe DSP hardware!\n");
- goto release_regions;
- }
-
-#if !IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)
- sof_pci_probe_complete(dev);
-#endif
- return ret;
-
-release_regions:
- pci_release_regions(pci);
+out:
+ if (ret)
+ pci_release_regions(pci);
return ret;
}
+EXPORT_SYMBOL_NS(sof_pci_probe, SND_SOC_SOF_PCI_DEV);
-static void sof_pci_remove(struct pci_dev *pci)
+void sof_pci_remove(struct pci_dev *pci)
{
/* call sof helper for DSP hardware remove */
snd_sof_device_remove(&pci->dev);
/* follow recommendation in pci-driver.c to increment usage counter */
- if (!(sof_pci_debug & SOF_PCI_DISABLE_PM_RUNTIME))
+ if (snd_sof_device_probe_completed(&pci->dev) &&
+ !(sof_pci_debug & SOF_PCI_DISABLE_PM_RUNTIME))
pm_runtime_get_noresume(&pci->dev);
/* release pci regions and disable device */
pci_release_regions(pci);
}
+EXPORT_SYMBOL_NS(sof_pci_remove, SND_SOC_SOF_PCI_DEV);
-/* PCI IDs */
-static const struct pci_device_id sof_pci_ids[] = {
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_MERRIFIELD)
- { PCI_DEVICE(0x8086, 0x119a),
- .driver_data = (unsigned long)&tng_desc},
-#endif
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_APOLLOLAKE)
- /* BXT-P & Apollolake */
- { PCI_DEVICE(0x8086, 0x5a98),
- .driver_data = (unsigned long)&bxt_desc},
- { PCI_DEVICE(0x8086, 0x1a98),
- .driver_data = (unsigned long)&bxt_desc},
-#endif
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_GEMINILAKE)
- { PCI_DEVICE(0x8086, 0x3198),
- .driver_data = (unsigned long)&glk_desc},
-#endif
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_CANNONLAKE)
- { PCI_DEVICE(0x8086, 0x9dc8),
- .driver_data = (unsigned long)&cnl_desc},
-#endif
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_COFFEELAKE)
- { PCI_DEVICE(0x8086, 0xa348),
- .driver_data = (unsigned long)&cfl_desc},
-#endif
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_ICELAKE)
- { PCI_DEVICE(0x8086, 0x34C8), /* ICL-LP */
- .driver_data = (unsigned long)&icl_desc},
- { PCI_DEVICE(0x8086, 0x3dc8), /* ICL-H */
- .driver_data = (unsigned long)&icl_desc},
-
-#endif
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_JASPERLAKE)
- { PCI_DEVICE(0x8086, 0x38c8),
- .driver_data = (unsigned long)&jsl_desc},
- { PCI_DEVICE(0x8086, 0x4dc8),
- .driver_data = (unsigned long)&jsl_desc},
-#endif
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMETLAKE)
- { PCI_DEVICE(0x8086, 0x02c8), /* CML-LP */
- .driver_data = (unsigned long)&cml_desc},
- { PCI_DEVICE(0x8086, 0x06c8), /* CML-H */
- .driver_data = (unsigned long)&cml_desc},
- { PCI_DEVICE(0x8086, 0xa3f0), /* CML-S */
- .driver_data = (unsigned long)&cml_desc},
-#endif
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_TIGERLAKE)
- { PCI_DEVICE(0x8086, 0xa0c8), /* TGL-LP */
- .driver_data = (unsigned long)&tgl_desc},
- { PCI_DEVICE(0x8086, 0x43c8), /* TGL-H */
- .driver_data = (unsigned long)&tgl_desc},
-
-#endif
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_ELKHARTLAKE)
- { PCI_DEVICE(0x8086, 0x4b55),
- .driver_data = (unsigned long)&ehl_desc},
- { PCI_DEVICE(0x8086, 0x4b58),
- .driver_data = (unsigned long)&ehl_desc},
-#endif
- { 0, }
-};
-MODULE_DEVICE_TABLE(pci, sof_pci_ids);
-
-/* pci_driver definition */
-static struct pci_driver snd_sof_pci_driver = {
- .name = "sof-audio-pci",
- .id_table = sof_pci_ids,
- .probe = sof_pci_probe,
- .remove = sof_pci_remove,
- .driver = {
- .pm = &sof_pci_pm,
- },
-};
-module_pci_driver(snd_sof_pci_driver);
+void sof_pci_shutdown(struct pci_dev *pci)
+{
+ snd_sof_device_shutdown(&pci->dev);
+}
+EXPORT_SYMBOL_NS(sof_pci_shutdown, SND_SOC_SOF_PCI_DEV);
MODULE_LICENSE("Dual BSD/GPL");
-MODULE_IMPORT_NS(SND_SOC_SOF_MERRIFIELD);
-MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_HDA_COMMON);
diff --git a/sound/soc/sof/sof-pci-dev.h b/sound/soc/sof/sof-pci-dev.h
new file mode 100644
index 000000000000..81155a59e63a
--- /dev/null
+++ b/sound/soc/sof/sof-pci-dev.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+/*
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * Copyright(c) 2021 Intel Corporation. All rights reserved.
+ */
+
+#ifndef __SOUND_SOC_SOF_PCI_H
+#define __SOUND_SOC_SOF_PCI_H
+
+extern const struct dev_pm_ops sof_pci_pm;
+int sof_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id);
+void sof_pci_remove(struct pci_dev *pci);
+void sof_pci_shutdown(struct pci_dev *pci);
+
+#endif
diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h
index 64f28e082049..d453a4ce3b21 100644
--- a/sound/soc/sof/sof-priv.h
+++ b/sound/soc/sof/sof-priv.h
@@ -18,17 +18,51 @@
#include <sound/sof/pm.h>
#include <sound/sof/trace.h>
#include <uapi/sound/sof/fw.h>
+#include <sound/sof/ext_manifest.h>
-/* debug flags */
+struct snd_sof_pcm_stream;
+
+/* Flag definitions used in sof_core_debug (sof_debug module parameter) */
#define SOF_DBG_ENABLE_TRACE BIT(0)
-#define SOF_DBG_REGS BIT(1)
-#define SOF_DBG_MBOX BIT(2)
-#define SOF_DBG_TEXT BIT(3)
-#define SOF_DBG_PCI BIT(4)
-#define SOF_DBG_RETAIN_CTX BIT(5) /* prevent DSP D3 on FW exception */
+#define SOF_DBG_RETAIN_CTX BIT(1) /* prevent DSP D3 on FW exception */
+#define SOF_DBG_VERIFY_TPLG BIT(2) /* verify topology during load */
+#define SOF_DBG_DYNAMIC_PIPELINES_OVERRIDE BIT(3) /* 0: use topology token
+ * 1: override topology
+ */
+#define SOF_DBG_DYNAMIC_PIPELINES_ENABLE BIT(4) /* 0: use static pipelines
+ * 1: use dynamic pipelines
+ */
+#define SOF_DBG_DISABLE_MULTICORE BIT(5) /* schedule all pipelines/widgets
+ * on primary core
+ */
+#define SOF_DBG_PRINT_ALL_DUMPS BIT(6) /* Print all ipc and dsp dumps */
+#define SOF_DBG_IGNORE_D3_PERSISTENT BIT(7) /* ignore the DSP D3 persistent capability
+ * and always download firmware upon D3 exit
+ */
+#define SOF_DBG_PRINT_DMA_POSITION_UPDATE_LOGS BIT(8) /* print DMA position updates
+ * in dmesg logs
+ */
+#define SOF_DBG_PRINT_IPC_SUCCESS_LOGS BIT(9) /* print IPC success
+ * in dmesg logs
+ */
+#define SOF_DBG_FORCE_NOCODEC BIT(10) /* ignore all codec-related
+ * configurations
+ */
+#define SOF_DBG_DUMP_IPC_MESSAGE_PAYLOAD BIT(11) /* On top of the IPC message header
+ * dump the message payload also
+ */
+#define SOF_DBG_DSPLESS_MODE BIT(15) /* Do not initialize and use the DSP */
+
+/* Flag definitions used for controlling the DSP dump behavior */
+#define SOF_DBG_DUMP_REGS BIT(0)
+#define SOF_DBG_DUMP_MBOX BIT(1)
+#define SOF_DBG_DUMP_TEXT BIT(2)
+#define SOF_DBG_DUMP_PCI BIT(3)
+/* Output this dump (at the DEBUG level) only when SOF_DBG_PRINT_ALL_DUMPS is set */
+#define SOF_DBG_DUMP_OPTIONAL BIT(4)
/* global debug state set by SOF_DBG_ flags */
-extern int sof_core_debug;
+bool sof_debug_check_flag(int mask);
/* max BARs mmaped devices can use */
#define SND_SOF_BARS 8
@@ -50,19 +84,11 @@ extern int sof_core_debug;
#define SOF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_FLOAT)
-#define ENABLE_DEBUGFS_CACHEBUF \
- (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) || \
- IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST))
-
-/* DSP power state */
-enum sof_dsp_power_states {
- SOF_DSP_PM_D0,
- SOF_DSP_PM_D1,
- SOF_DSP_PM_D2,
- SOF_DSP_PM_D3_HOT,
- SOF_DSP_PM_D3,
- SOF_DSP_PM_D3_COLD,
-};
+/* So far the primary core on all DSPs has ID 0 */
+#define SOF_DSP_PRIMARY_CORE 0
+
+/* max number of DSP cores */
+#define SOF_MAX_DSP_NUM_CORES 8
struct sof_dsp_power_state {
u32 state;
@@ -74,6 +100,26 @@ enum sof_system_suspend_state {
SOF_SUSPEND_NONE = 0,
SOF_SUSPEND_S0IX,
SOF_SUSPEND_S3,
+ SOF_SUSPEND_S4,
+ SOF_SUSPEND_S5,
+};
+
+enum sof_dfsentry_type {
+ SOF_DFSENTRY_TYPE_IOMEM = 0,
+ SOF_DFSENTRY_TYPE_BUF,
+};
+
+enum sof_debugfs_access_type {
+ SOF_DEBUGFS_ACCESS_ALWAYS = 0,
+ SOF_DEBUGFS_ACCESS_D0_ONLY,
+};
+
+struct sof_compr_stream {
+ u64 copied_total;
+ u32 sampling_rate;
+ u16 channels;
+ u16 sample_container_bytes;
+ size_t posn_offset;
};
struct snd_sof_dev;
@@ -84,6 +130,40 @@ struct snd_soc_tplg_ops;
struct snd_soc_component;
struct snd_sof_pdata;
+/**
+ * struct snd_sof_platform_stream_params - platform dependent stream parameters
+ * @stream_tag: Stream tag to use
+ * @use_phy_addr: Use the provided @phy_addr for configuration
+ * @phy_addr: Platform dependent address to be used, if @use_phy_addr
+ * is true
+ * @no_ipc_position: Disable position update IPC from firmware
+ */
+struct snd_sof_platform_stream_params {
+ u16 stream_tag;
+ bool use_phy_address;
+ u32 phy_addr;
+ bool no_ipc_position;
+ bool cont_update_posn;
+};
+
+/**
+ * struct sof_firmware - Container struct for SOF firmware
+ * @fw: Pointer to the firmware
+ * @payload_offset: Offset of the data within the loaded firmware image to be
+ * loaded to the DSP (skipping for example ext_manifest section)
+ */
+struct sof_firmware {
+ const struct firmware *fw;
+ u32 payload_offset;
+};
+
+enum sof_dai_access {
+ SOF_DAI_DSP_ACCESS, /* access from DSP only */
+ SOF_DAI_HOST_ACCESS, /* access from host only */
+
+ SOF_DAI_ACCESS_NUM
+};
+
/*
* SOF DSP HW abstraction operations.
* Used to abstract DSP HW architecture and any IO busses between host CPU
@@ -91,24 +171,29 @@ struct snd_sof_pdata;
*/
struct snd_sof_dsp_ops {
- /* probe and remove */
+ /* probe/remove/shutdown */
+ int (*probe_early)(struct snd_sof_dev *sof_dev); /* optional */
int (*probe)(struct snd_sof_dev *sof_dev); /* mandatory */
- int (*remove)(struct snd_sof_dev *sof_dev); /* optional */
+ void (*remove)(struct snd_sof_dev *sof_dev); /* optional */
+ void (*remove_late)(struct snd_sof_dev *sof_dev); /* optional */
+ int (*shutdown)(struct snd_sof_dev *sof_dev); /* optional */
/* DSP core boot / reset */
int (*run)(struct snd_sof_dev *sof_dev); /* mandatory */
- int (*stall)(struct snd_sof_dev *sof_dev); /* optional */
+ int (*stall)(struct snd_sof_dev *sof_dev, unsigned int core_mask); /* optional */
int (*reset)(struct snd_sof_dev *sof_dev); /* optional */
- int (*core_power_up)(struct snd_sof_dev *sof_dev,
- unsigned int core_mask); /* optional */
- int (*core_power_down)(struct snd_sof_dev *sof_dev,
- unsigned int core_mask); /* optional */
+ int (*core_get)(struct snd_sof_dev *sof_dev, int core); /* optional */
+ int (*core_put)(struct snd_sof_dev *sof_dev, int core); /* optional */
/*
* Register IO: only used by respective drivers themselves,
* TODO: consider removing these operations and calling respective
* implementations directly
*/
+ void (*write8)(struct snd_sof_dev *sof_dev, void __iomem *addr,
+ u8 value); /* optional */
+ u8 (*read8)(struct snd_sof_dev *sof_dev,
+ void __iomem *addr); /* optional */
void (*write)(struct snd_sof_dev *sof_dev, void __iomem *addr,
u32 value); /* optional */
u32 (*read)(struct snd_sof_dev *sof_dev,
@@ -119,12 +204,20 @@ struct snd_sof_dsp_ops {
void __iomem *addr); /* optional */
/* memcpy IO */
- void (*block_read)(struct snd_sof_dev *sof_dev, u32 bar,
- u32 offset, void *dest,
- size_t size); /* mandatory */
- void (*block_write)(struct snd_sof_dev *sof_dev, u32 bar,
- u32 offset, void *src,
- size_t size); /* mandatory */
+ int (*block_read)(struct snd_sof_dev *sof_dev,
+ enum snd_sof_fw_blk_type type, u32 offset,
+ void *dest, size_t size); /* mandatory */
+ int (*block_write)(struct snd_sof_dev *sof_dev,
+ enum snd_sof_fw_blk_type type, u32 offset,
+ void *src, size_t size); /* mandatory */
+
+ /* Mailbox IO */
+ void (*mailbox_read)(struct snd_sof_dev *sof_dev,
+ u32 offset, void *dest,
+ size_t size); /* optional */
+ void (*mailbox_write)(struct snd_sof_dev *sof_dev,
+ u32 offset, void *src,
+ size_t size); /* optional */
/* doorbell */
irqreturn_t (*irq_handler)(int irq, void *context); /* optional */
@@ -138,11 +231,6 @@ struct snd_sof_dsp_ops {
int (*load_firmware)(struct snd_sof_dev *sof_dev); /* mandatory */
int (*load_module)(struct snd_sof_dev *sof_dev,
struct snd_sof_mod_hdr *hdr); /* optional */
- /*
- * FW ready checks for ABI compatibility and creates
- * memory windows at first boot
- */
- int (*fw_ready)(struct snd_sof_dev *sdev, u32 msg_id); /* mandatory */
/* connect pcm substream to a host stream */
int (*pcm_open)(struct snd_sof_dev *sdev,
@@ -155,7 +243,7 @@ struct snd_sof_dsp_ops {
int (*pcm_hw_params)(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
- struct sof_ipc_stream_params *ipc_params); /* optional */
+ struct snd_sof_platform_stream_params *platform_params); /* optional */
/* host stream hw_free */
int (*pcm_hw_free)(struct snd_sof_dev *sdev,
@@ -170,41 +258,36 @@ struct snd_sof_dsp_ops {
snd_pcm_uframes_t (*pcm_pointer)(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream); /* optional */
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES)
- /* Except for probe_pointer, all probe ops are mandatory */
- int (*probe_assign)(struct snd_sof_dev *sdev,
- struct snd_compr_stream *cstream,
- struct snd_soc_dai *dai); /* mandatory */
- int (*probe_free)(struct snd_sof_dev *sdev,
- struct snd_compr_stream *cstream,
- struct snd_soc_dai *dai); /* mandatory */
- int (*probe_set_params)(struct snd_sof_dev *sdev,
- struct snd_compr_stream *cstream,
- struct snd_compr_params *params,
- struct snd_soc_dai *dai); /* mandatory */
- int (*probe_trigger)(struct snd_sof_dev *sdev,
- struct snd_compr_stream *cstream, int cmd,
- struct snd_soc_dai *dai); /* mandatory */
- int (*probe_pointer)(struct snd_sof_dev *sdev,
- struct snd_compr_stream *cstream,
- struct snd_compr_tstamp *tstamp,
- struct snd_soc_dai *dai); /* optional */
-#endif
+ /* pcm ack */
+ int (*pcm_ack)(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream); /* optional */
+
+ /*
+ * optional callback to retrieve the link DMA position for the substream
+ * when the position is not reported in the shared SRAM windows but
+ * instead from a host-accessible hardware counter.
+ */
+ u64 (*get_stream_position)(struct snd_sof_dev *sdev,
+ struct snd_soc_component *component,
+ struct snd_pcm_substream *substream); /* optional */
/* host read DSP stream data */
- void (*ipc_msg_data)(struct snd_sof_dev *sdev,
- struct snd_pcm_substream *substream,
- void *p, size_t sz); /* mandatory */
+ int (*ipc_msg_data)(struct snd_sof_dev *sdev,
+ struct snd_sof_pcm_stream *sps,
+ void *p, size_t sz); /* mandatory */
- /* host configure DSP HW parameters */
- int (*ipc_pcm_params)(struct snd_sof_dev *sdev,
- struct snd_pcm_substream *substream,
- const struct sof_ipc_pcm_params_reply *reply); /* mandatory */
+ /* host side configuration of the stream's data offset in stream mailbox area */
+ int (*set_stream_data_offset)(struct snd_sof_dev *sdev,
+ struct snd_sof_pcm_stream *sps,
+ size_t posn_offset); /* optional */
/* pre/post firmware run */
int (*pre_fw_run)(struct snd_sof_dev *sof_dev); /* optional */
int (*post_fw_run)(struct snd_sof_dev *sof_dev); /* optional */
+ /* parse platform specific extended manifest, optional */
+ int (*parse_platform_ext_manifest)(struct snd_sof_dev *sof_dev,
+ const struct sof_ext_man_elem_header *hdr);
+
/* DSP PM */
int (*suspend)(struct snd_sof_dev *sof_dev,
u32 target_state); /* optional */
@@ -225,10 +308,15 @@ struct snd_sof_dsp_ops {
void (*dbg_dump)(struct snd_sof_dev *sof_dev,
u32 flags); /* optional */
void (*ipc_dump)(struct snd_sof_dev *sof_dev); /* optional */
+ int (*debugfs_add_region_item)(struct snd_sof_dev *sdev,
+ enum snd_sof_fw_blk_type blk_type, u32 offset,
+ size_t size, const char *name,
+ enum sof_debugfs_access_type access_type); /* optional */
- /* host DMA trace initialization */
+ /* host DMA trace (IPC3) */
int (*trace_init)(struct snd_sof_dev *sdev,
- u32 *stream_tag); /* optional */
+ struct snd_dma_buffer *dmatb,
+ struct sof_ipc_dma_trace_params_ext *dtrace_params); /* optional */
int (*trace_release)(struct snd_sof_dev *sdev); /* optional */
int (*trace_trigger)(struct snd_sof_dev *sdev,
int cmd); /* optional */
@@ -245,48 +333,39 @@ struct snd_sof_dsp_ops {
void *pdata); /* optional */
void (*machine_unregister)(struct snd_sof_dev *sdev,
void *pdata); /* optional */
- void (*machine_select)(struct snd_sof_dev *sdev); /* optional */
- void (*set_mach_params)(const struct snd_soc_acpi_mach *mach,
- struct device *dev); /* optional */
+ struct snd_soc_acpi_mach * (*machine_select)(struct snd_sof_dev *sdev); /* optional */
+ void (*set_mach_params)(struct snd_soc_acpi_mach *mach,
+ struct snd_sof_dev *sdev); /* optional */
+
+ /* IPC client ops */
+ int (*register_ipc_clients)(struct snd_sof_dev *sdev); /* optional */
+ void (*unregister_ipc_clients)(struct snd_sof_dev *sdev); /* optional */
/* DAI ops */
struct snd_soc_dai_driver *drv;
int num_drv;
+ bool (*is_chain_dma_supported)(struct snd_sof_dev *sdev, u32 dai_type); /* optional */
+
/* ALSA HW info flags, will be stored in snd_pcm_runtime.hw.info */
u32 hw_info;
- const struct sof_arch_ops *arch_ops;
+ const struct dsp_arch_ops *dsp_arch_ops;
};
/* DSP architecture specific callbacks for oops and stack dumps */
-struct sof_arch_ops {
- void (*dsp_oops)(struct snd_sof_dev *sdev, void *oops);
- void (*dsp_stack)(struct snd_sof_dev *sdev, void *oops,
+struct dsp_arch_ops {
+ void (*dsp_oops)(struct snd_sof_dev *sdev, const char *level, void *oops);
+ void (*dsp_stack)(struct snd_sof_dev *sdev, const char *level, void *oops,
u32 *stack, u32 stack_words);
};
-#define sof_arch_ops(sdev) ((sdev)->pdata->desc->ops->arch_ops)
-
-/* DSP device HW descriptor mapping between bus ID and ops */
-struct sof_ops_table {
- const struct sof_dev_desc *desc;
- const struct snd_sof_dsp_ops *ops;
-};
-
-enum sof_dfsentry_type {
- SOF_DFSENTRY_TYPE_IOMEM = 0,
- SOF_DFSENTRY_TYPE_BUF,
-};
-
-enum sof_debugfs_access_type {
- SOF_DEBUGFS_ACCESS_ALWAYS = 0,
- SOF_DEBUGFS_ACCESS_D0_ONLY,
-};
+#define sof_dsp_arch_ops(sdev) ((sdev)->pdata->desc->ops->dsp_arch_ops)
/* FS entry for debug files that can expose DSP memories, registers */
struct snd_sof_dfsentry {
size_t size;
+ size_t buf_data_size; /* length of buffered data for file read operation */
enum sof_dfsentry_type type;
/*
* access_type specifies if the
@@ -294,7 +373,7 @@ struct snd_sof_dfsentry {
* or if it is accessible only when the DSP is in D0.
*/
enum sof_debugfs_access_type access_type;
-#if ENABLE_DEBUGFS_CACHEBUF
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE)
char *cache_buf; /* buffer to cache the contents of debugfs memory */
#endif
struct snd_sof_dev *sdev;
@@ -327,26 +406,135 @@ struct snd_sof_mailbox {
/* IPC message descriptor for host <-> DSP IO */
struct snd_sof_ipc_msg {
/* message data */
- u32 header;
void *msg_data;
void *reply_data;
size_t msg_size;
size_t reply_size;
int reply_error;
+ /* notification, firmware initiated messages */
+ void *rx_data;
+
wait_queue_head_t waitq;
bool ipc_complete;
};
-enum snd_sof_fw_state {
- SOF_FW_BOOT_NOT_STARTED = 0,
- SOF_FW_BOOT_PREPARE,
- SOF_FW_BOOT_IN_PROGRESS,
- SOF_FW_BOOT_FAILED,
- SOF_FW_BOOT_READY_FAILED, /* firmware booted but fw_ready op failed */
- SOF_FW_BOOT_COMPLETE,
+/**
+ * struct sof_ipc_fw_tracing_ops - IPC-specific firmware tracing ops
+ * @init: Function pointer for initialization of the tracing
+ * @free: Optional function pointer for freeing of the tracing
+ * @fw_crashed: Optional function pointer to notify the tracing of a firmware crash
+ * @suspend: Function pointer for system/runtime suspend
+ * @resume: Function pointer for system/runtime resume
+ */
+struct sof_ipc_fw_tracing_ops {
+ int (*init)(struct snd_sof_dev *sdev);
+ void (*free)(struct snd_sof_dev *sdev);
+ void (*fw_crashed)(struct snd_sof_dev *sdev);
+ void (*suspend)(struct snd_sof_dev *sdev, pm_message_t pm_state);
+ int (*resume)(struct snd_sof_dev *sdev);
+};
+
+/**
+ * struct sof_ipc_pm_ops - IPC-specific PM ops
+ * @ctx_save: Optional function pointer for context save
+ * @ctx_restore: Optional function pointer for context restore
+ * @set_core_state: Optional function pointer for turning on/off a DSP core
+ * @set_pm_gate: Optional function pointer for pm gate settings
+ */
+struct sof_ipc_pm_ops {
+ int (*ctx_save)(struct snd_sof_dev *sdev);
+ int (*ctx_restore)(struct snd_sof_dev *sdev);
+ int (*set_core_state)(struct snd_sof_dev *sdev, int core_idx, bool on);
+ int (*set_pm_gate)(struct snd_sof_dev *sdev, u32 flags);
+};
+
+/**
+ * struct sof_ipc_fw_loader_ops - IPC/FW-specific loader ops
+ * @validate: Function pointer for validating the firmware image
+ * @parse_ext_manifest: Function pointer for parsing the manifest of the firmware
+ * @load_fw_to_dsp: Optional function pointer for loading the firmware to the
+ * DSP.
+ * The function implements generic, hardware independent way
+ * of loading the initial firmware and its modules (if any).
+ */
+struct sof_ipc_fw_loader_ops {
+ int (*validate)(struct snd_sof_dev *sdev);
+ size_t (*parse_ext_manifest)(struct snd_sof_dev *sdev);
+ int (*load_fw_to_dsp)(struct snd_sof_dev *sdev);
+};
+
+struct sof_ipc_tplg_ops;
+struct sof_ipc_pcm_ops;
+
+/**
+ * struct sof_ipc_ops - IPC-specific ops
+ * @tplg: Pointer to IPC-specific topology ops
+ * @pm: Pointer to PM ops
+ * @pcm: Pointer to PCM ops
+ * @fw_loader: Pointer to Firmware Loader ops
+ * @fw_tracing: Optional pointer to Firmware tracing ops
+ *
+ * @init: Optional pointer for IPC related initialization
+ * @exit: Optional pointer for IPC related cleanup
+ * @post_fw_boot: Optional pointer to execute IPC related tasks after firmware
+ * boot.
+ *
+ * @tx_msg: Function pointer for sending a 'short' IPC message
+ * @set_get_data: Function pointer for set/get data ('large' IPC message). This
+ * function may split up the 'large' message and use the @tx_msg
+ * path to transfer individual chunks, or use other means to transfer
+ * the message.
+ * @get_reply: Function pointer for fetching the reply to
+ * sdev->ipc->msg.reply_data
+ * @rx_msg: Function pointer for handling a received message
+ *
+ * Note: both @tx_msg and @set_get_data considered as TX functions and they are
+ * serialized for the duration of the instructed transfer. A large message sent
+ * via @set_get_data is a single transfer even if at the hardware level it is
+ * handled with multiple chunks.
+ */
+struct sof_ipc_ops {
+ const struct sof_ipc_tplg_ops *tplg;
+ const struct sof_ipc_pm_ops *pm;
+ const struct sof_ipc_pcm_ops *pcm;
+ const struct sof_ipc_fw_loader_ops *fw_loader;
+ const struct sof_ipc_fw_tracing_ops *fw_tracing;
+
+ int (*init)(struct snd_sof_dev *sdev);
+ void (*exit)(struct snd_sof_dev *sdev);
+ int (*post_fw_boot)(struct snd_sof_dev *sdev);
+
+ int (*tx_msg)(struct snd_sof_dev *sdev, void *msg_data, size_t msg_bytes,
+ void *reply_data, size_t reply_bytes, bool no_pm);
+ int (*set_get_data)(struct snd_sof_dev *sdev, void *data, size_t data_bytes,
+ bool set);
+ int (*get_reply)(struct snd_sof_dev *sdev);
+ void (*rx_msg)(struct snd_sof_dev *sdev);
+};
+
+/* SOF generic IPC data */
+struct snd_sof_ipc {
+ struct snd_sof_dev *sdev;
+
+ /* protects messages and the disable flag */
+ struct mutex tx_mutex;
+ /* disables further sending of ipc's */
+ bool disable_ipc_tx;
+
+ /* Maximum allowed size of a single IPC message/reply */
+ size_t max_payload_size;
+
+ struct snd_sof_ipc_msg msg;
+
+ /* IPC ops based on version */
+ const struct sof_ipc_ops *ops;
};
+/* Helper to retrieve the IPC ops */
+#define sof_ipc_get_ops(sdev, ops_name) \
+ (((sdev)->ipc && (sdev)->ipc->ops) ? (sdev)->ipc->ops->ops_name : NULL)
+
/*
* SOF Device Level.
*/
@@ -356,6 +544,19 @@ struct snd_sof_dev {
spinlock_t hw_lock; /* lock for HW IO access */
/*
+ * When true the DSP is not used.
+ * It is set under the following condition:
+ * User sets the SOF_DBG_DSPLESS_MODE flag in sof_debug module parameter
+ * and
+ * the platform advertises that it can support such mode
+ * pdata->desc->dspless_mode_supported is true.
+ */
+ bool dspless_mode_selected;
+
+ /* Main, Base firmware image */
+ struct sof_firmware basefw;
+
+ /*
* ASoC components. plat_drv fields are set dynamically so
* can't use const
*/
@@ -363,26 +564,31 @@ struct snd_sof_dev {
/* current DSP power state */
struct sof_dsp_power_state dsp_power_state;
+ /* mutex to protect the dsp_power_state access */
+ struct mutex power_state_access;
/* Intended power target of system suspend */
enum sof_system_suspend_state system_suspend_target;
/* DSP firmware boot */
wait_queue_head_t boot_wait;
- enum snd_sof_fw_state fw_state;
- u32 first_boot;
+ enum sof_fw_state fw_state;
+ bool first_boot;
/* work queue in case the probe is implemented in two steps */
struct work_struct probe_work;
+ bool probe_completed;
/* DSP HW differentiation */
struct snd_sof_pdata *pdata;
/* IPC */
struct snd_sof_ipc *ipc;
+ struct snd_sof_mailbox fw_info_box; /* FW shared memory */
struct snd_sof_mailbox dsp_box; /* DSP initiated IPC */
struct snd_sof_mailbox host_box; /* Host initiated IPC */
struct snd_sof_mailbox stream_box; /* Stream position update */
+ struct snd_sof_mailbox debug_box; /* Debug info updates */
struct snd_sof_ipc_msg *msg;
int ipc_irq;
u32 next_comp_id; /* monotonic - reset during S3 */
@@ -396,10 +602,11 @@ struct snd_sof_dev {
/* debug */
struct dentry *debugfs_root;
struct list_head dfsentry_list;
+ bool dbg_dump_printed;
+ bool ipc_dump_printed;
+ bool d3_prevented; /* runtime pm use count incremented to prevent context lost */
/* firmware loader */
- struct snd_dma_buffer dmab;
- struct snd_dma_buffer dmab_bdl;
struct sof_ipc_fw_ready fw_ready;
struct sof_ipc_fw_version fw_version;
struct sof_ipc_cc_version *cc_version;
@@ -409,10 +616,13 @@ struct snd_sof_dev {
struct list_head pcm_list;
struct list_head kcontrol_list;
struct list_head widget_list;
+ struct list_head pipeline_list;
struct list_head dai_list;
+ struct list_head dai_link_list;
struct list_head route_list;
struct snd_soc_component *component;
u32 enabled_cores_mask; /* keep track of enabled cores */
+ bool led_present;
/* FW configuration */
struct sof_ipc_window *info_window;
@@ -421,23 +631,52 @@ struct snd_sof_dev {
int ipc_timeout;
int boot_timeout;
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES)
- unsigned int extractor_stream_tag;
-#endif
-
- /* DMA for Trace */
- struct snd_dma_buffer dmatb;
- struct snd_dma_buffer dmatp;
- int dma_trace_pages;
- wait_queue_head_t trace_sleep;
- u32 host_offset;
- u32 dtrace_is_supported; /* set with Kconfig or module parameter */
- u32 dtrace_is_enabled;
- u32 dtrace_error;
- u32 dtrace_draining;
+ /* firmwre tracing */
+ bool fw_trace_is_supported; /* set with Kconfig or module parameter */
+ void *fw_trace_data; /* private data used by firmware tracing implementation */
bool msi_enabled;
+ /* DSP core context */
+ u32 num_cores;
+
+ /*
+ * ref count per core that will be modified during system suspend/resume and during pcm
+ * hw_params/hw_free. This doesn't need to be protected with a mutex because pcm
+ * hw_params/hw_free are already protected by the PCM mutex in the ALSA framework in
+ * sound/core/ when streams are active and during system suspend/resume, streams are
+ * already suspended.
+ */
+ int dsp_core_ref_count[SOF_MAX_DSP_NUM_CORES];
+
+ /*
+ * Used to keep track of registered IPC client devices so that they can
+ * be removed when the parent SOF module is removed.
+ */
+ struct list_head ipc_client_list;
+
+ /* mutex to protect client list */
+ struct mutex ipc_client_mutex;
+
+ /*
+ * Used for tracking the IPC client's RX registration for DSP initiated
+ * message handling.
+ */
+ struct list_head ipc_rx_handler_list;
+
+ /*
+ * Used for tracking the IPC client's registration for DSP state change
+ * notification
+ */
+ struct list_head fw_state_handler_list;
+
+ /* to protect the ipc_rx_handler_list and dsp_state_handler_list list */
+ struct mutex client_event_handler_mutex;
+
+ /* quirks to override topology values */
+ bool mclk_id_override;
+ u16 mclk_id_quirk; /* same size as in IPC3 definitions */
+
void *private; /* core does not touch this */
};
@@ -447,6 +686,8 @@ struct snd_sof_dev {
int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data);
int snd_sof_device_remove(struct device *dev);
+int snd_sof_device_shutdown(struct device *dev);
+bool snd_sof_device_probe_completed(struct device *dev);
int snd_sof_runtime_suspend(struct device *dev);
int snd_sof_runtime_resume(struct device *dev);
@@ -459,88 +700,108 @@ void snd_sof_complete(struct device *dev);
void snd_sof_new_platform_drv(struct snd_sof_dev *sdev);
-int snd_sof_create_page_table(struct device *dev,
- struct snd_dma_buffer *dmab,
- unsigned char *page_table, size_t size);
+/*
+ * Compress support
+ */
+extern struct snd_compress_ops sof_compressed_ops;
+
+/*
+ * Firmware (firmware, libraries, topologies) file location
+ */
+int sof_create_ipc_file_profile(struct snd_sof_dev *sdev,
+ struct sof_loadable_file_profile *base_profile,
+ struct sof_loadable_file_profile *out_profile);
/*
* Firmware loading.
*/
-int snd_sof_load_firmware(struct snd_sof_dev *sdev);
int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev);
int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev);
int snd_sof_run_firmware(struct snd_sof_dev *sdev);
-int snd_sof_parse_module_memcpy(struct snd_sof_dev *sdev,
- struct snd_sof_mod_hdr *module);
void snd_sof_fw_unload(struct snd_sof_dev *sdev);
-int snd_sof_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 bar, u32 offset);
/*
* IPC low level APIs.
*/
struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev);
void snd_sof_ipc_free(struct snd_sof_dev *sdev);
+void snd_sof_ipc_get_reply(struct snd_sof_dev *sdev);
void snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id);
-void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev);
-int snd_sof_ipc_stream_pcm_params(struct snd_sof_dev *sdev,
- struct sof_ipc_pcm_params *params);
-int snd_sof_dsp_mailbox_init(struct snd_sof_dev *sdev, u32 dspbox,
- size_t dspbox_size, u32 hostbox,
- size_t hostbox_size);
-int snd_sof_ipc_valid(struct snd_sof_dev *sdev);
-int sof_ipc_tx_message(struct snd_sof_ipc *ipc, u32 header,
- void *msg_data, size_t msg_bytes, void *reply_data,
- size_t reply_bytes);
-int sof_ipc_tx_message_no_pm(struct snd_sof_ipc *ipc, u32 header,
- void *msg_data, size_t msg_bytes,
+static inline void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev)
+{
+ sdev->ipc->ops->rx_msg(sdev);
+}
+int sof_ipc_tx_message(struct snd_sof_ipc *ipc, void *msg_data, size_t msg_bytes,
+ void *reply_data, size_t reply_bytes);
+static inline int sof_ipc_tx_message_no_reply(struct snd_sof_ipc *ipc, void *msg_data,
+ size_t msg_bytes)
+{
+ return sof_ipc_tx_message(ipc, msg_data, msg_bytes, NULL, 0);
+}
+int sof_ipc_set_get_data(struct snd_sof_ipc *ipc, void *msg_data,
+ size_t msg_bytes, bool set);
+int sof_ipc_tx_message_no_pm(struct snd_sof_ipc *ipc, void *msg_data, size_t msg_bytes,
void *reply_data, size_t reply_bytes);
+static inline int sof_ipc_tx_message_no_pm_no_reply(struct snd_sof_ipc *ipc, void *msg_data,
+ size_t msg_bytes)
+{
+ return sof_ipc_tx_message_no_pm(ipc, msg_data, msg_bytes, NULL, 0);
+}
+int sof_ipc_send_msg(struct snd_sof_dev *sdev, void *msg_data, size_t msg_bytes,
+ size_t reply_bytes);
+
+static inline void snd_sof_ipc_process_reply(struct snd_sof_dev *sdev, u32 msg_id)
+{
+ snd_sof_ipc_get_reply(sdev);
+ snd_sof_ipc_reply(sdev, msg_id);
+}
/*
* Trace/debug
*/
-int snd_sof_init_trace(struct snd_sof_dev *sdev);
-void snd_sof_release_trace(struct snd_sof_dev *sdev);
-void snd_sof_free_trace(struct snd_sof_dev *sdev);
int snd_sof_dbg_init(struct snd_sof_dev *sdev);
void snd_sof_free_debug(struct snd_sof_dev *sdev);
-int snd_sof_debugfs_io_item(struct snd_sof_dev *sdev,
- void __iomem *base, size_t size,
- const char *name,
- enum sof_debugfs_access_type access_type);
int snd_sof_debugfs_buf_item(struct snd_sof_dev *sdev,
void *base, size_t size,
const char *name, mode_t mode);
-int snd_sof_trace_update_pos(struct snd_sof_dev *sdev,
- struct sof_ipc_dma_trace_posn *posn);
-void snd_sof_trace_notify_for_error(struct snd_sof_dev *sdev);
-void snd_sof_get_status(struct snd_sof_dev *sdev, u32 panic_code,
- u32 tracep_code, void *oops,
- struct sof_ipc_panic_info *panic_info,
- void *stack, size_t stack_words);
-int snd_sof_init_trace_ipc(struct snd_sof_dev *sdev);
-void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev);
-
-/*
- * Platform specific ops.
- */
-extern struct snd_compress_ops sof_compressed_ops;
+void sof_print_oops_and_stack(struct snd_sof_dev *sdev, const char *level,
+ u32 panic_code, u32 tracep_code, void *oops,
+ struct sof_ipc_panic_info *panic_info,
+ void *stack, size_t stack_words);
+void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev, const char *msg);
+int snd_sof_dbg_memory_info_init(struct snd_sof_dev *sdev);
+int snd_sof_debugfs_add_region_item_iomem(struct snd_sof_dev *sdev,
+ enum snd_sof_fw_blk_type blk_type, u32 offset, size_t size,
+ const char *name, enum sof_debugfs_access_type access_type);
+/* Firmware tracing */
+int sof_fw_trace_init(struct snd_sof_dev *sdev);
+void sof_fw_trace_free(struct snd_sof_dev *sdev);
+void sof_fw_trace_fw_crashed(struct snd_sof_dev *sdev);
+void sof_fw_trace_suspend(struct snd_sof_dev *sdev, pm_message_t pm_state);
+int sof_fw_trace_resume(struct snd_sof_dev *sdev);
/*
* DSP Architectures.
*/
-static inline void sof_stack(struct snd_sof_dev *sdev, void *oops, u32 *stack,
- u32 stack_words)
+static inline void sof_stack(struct snd_sof_dev *sdev, const char *level,
+ void *oops, u32 *stack, u32 stack_words)
{
- sof_arch_ops(sdev)->dsp_stack(sdev, oops, stack, stack_words);
+ sof_dsp_arch_ops(sdev)->dsp_stack(sdev, level, oops, stack,
+ stack_words);
}
-static inline void sof_oops(struct snd_sof_dev *sdev, void *oops)
+static inline void sof_oops(struct snd_sof_dev *sdev, const char *level, void *oops)
{
- if (sof_arch_ops(sdev)->dsp_oops)
- sof_arch_ops(sdev)->dsp_oops(sdev, oops);
+ if (sof_dsp_arch_ops(sdev)->dsp_oops)
+ sof_dsp_arch_ops(sdev)->dsp_oops(sdev, level, oops);
}
-extern const struct sof_arch_ops sof_xtensa_arch_ops;
+extern const struct dsp_arch_ops sof_xtensa_arch_ops;
+
+/*
+ * Firmware state tracking
+ */
+void sof_set_fw_state(struct snd_sof_dev *sdev, enum sof_fw_state new_state);
/*
* Utilities
@@ -553,25 +814,76 @@ void sof_mailbox_write(struct snd_sof_dev *sdev, u32 offset,
void *message, size_t bytes);
void sof_mailbox_read(struct snd_sof_dev *sdev, u32 offset,
void *message, size_t bytes);
-void sof_block_write(struct snd_sof_dev *sdev, u32 bar, u32 offset, void *src,
- size_t size);
-void sof_block_read(struct snd_sof_dev *sdev, u32 bar, u32 offset, void *dest,
- size_t size);
-
-int sof_fw_ready(struct snd_sof_dev *sdev, u32 msg_id);
-
-void intel_ipc_msg_data(struct snd_sof_dev *sdev,
- struct snd_pcm_substream *substream,
- void *p, size_t sz);
-int intel_ipc_pcm_params(struct snd_sof_dev *sdev,
- struct snd_pcm_substream *substream,
- const struct sof_ipc_pcm_params_reply *reply);
-
-int intel_pcm_open(struct snd_sof_dev *sdev,
- struct snd_pcm_substream *substream);
-int intel_pcm_close(struct snd_sof_dev *sdev,
- struct snd_pcm_substream *substream);
-
-int sof_machine_check(struct snd_sof_dev *sdev);
+int sof_block_write(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type,
+ u32 offset, void *src, size_t size);
+int sof_block_read(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type,
+ u32 offset, void *dest, size_t size);
+
+int sof_ipc_msg_data(struct snd_sof_dev *sdev,
+ struct snd_sof_pcm_stream *sps,
+ void *p, size_t sz);
+int sof_set_stream_data_offset(struct snd_sof_dev *sdev,
+ struct snd_sof_pcm_stream *sps,
+ size_t posn_offset);
+
+int sof_stream_pcm_open(struct snd_sof_dev *sdev,
+ struct snd_pcm_substream *substream);
+int sof_stream_pcm_close(struct snd_sof_dev *sdev,
+ struct snd_pcm_substream *substream);
+
+/* SOF client support */
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_CLIENT)
+int sof_client_dev_register(struct snd_sof_dev *sdev, const char *name, u32 id,
+ const void *data, size_t size);
+void sof_client_dev_unregister(struct snd_sof_dev *sdev, const char *name, u32 id);
+int sof_register_clients(struct snd_sof_dev *sdev);
+void sof_unregister_clients(struct snd_sof_dev *sdev);
+void sof_client_ipc_rx_dispatcher(struct snd_sof_dev *sdev, void *msg_buf);
+void sof_client_fw_state_dispatcher(struct snd_sof_dev *sdev);
+int sof_suspend_clients(struct snd_sof_dev *sdev, pm_message_t state);
+int sof_resume_clients(struct snd_sof_dev *sdev);
+#else /* CONFIG_SND_SOC_SOF_CLIENT */
+static inline int sof_client_dev_register(struct snd_sof_dev *sdev, const char *name,
+ u32 id, const void *data, size_t size)
+{
+ return 0;
+}
+
+static inline void sof_client_dev_unregister(struct snd_sof_dev *sdev,
+ const char *name, u32 id)
+{
+}
+
+static inline int sof_register_clients(struct snd_sof_dev *sdev)
+{
+ return 0;
+}
+
+static inline void sof_unregister_clients(struct snd_sof_dev *sdev)
+{
+}
+
+static inline void sof_client_ipc_rx_dispatcher(struct snd_sof_dev *sdev, void *msg_buf)
+{
+}
+
+static inline void sof_client_fw_state_dispatcher(struct snd_sof_dev *sdev)
+{
+}
+
+static inline int sof_suspend_clients(struct snd_sof_dev *sdev, pm_message_t state)
+{
+ return 0;
+}
+
+static inline int sof_resume_clients(struct snd_sof_dev *sdev)
+{
+ return 0;
+}
+#endif /* CONFIG_SND_SOC_SOF_CLIENT */
+
+/* Main ops for IPC implementations */
+extern const struct sof_ipc_ops ipc3_ops;
+extern const struct sof_ipc_ops ipc4_ops;
#endif
diff --git a/sound/soc/sof/sof-utils.c b/sound/soc/sof/sof-utils.c
new file mode 100644
index 000000000000..b6345a7345af
--- /dev/null
+++ b/sound/soc/sof/sof-utils.c
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2018-2022 Intel Corporation. All rights reserved.
+//
+// Author: Keyon Jie <yang.jie@linux.intel.com>
+//
+
+#include <asm/unaligned.h>
+#include <linux/io-64-nonatomic-lo-hi.h>
+#include <linux/device.h>
+#include <sound/memalloc.h>
+#include <linux/module.h>
+#include "sof-utils.h"
+
+/*
+ * Generic buffer page table creation.
+ * Take the each physical page address and drop the least significant unused
+ * bits from each (based on PAGE_SIZE). Then pack valid page address bits
+ * into compressed page table.
+ */
+
+int snd_sof_create_page_table(struct device *dev,
+ struct snd_dma_buffer *dmab,
+ unsigned char *page_table, size_t size)
+{
+ int i, pages;
+
+ pages = snd_sgbuf_aligned_pages(size);
+
+ dev_dbg(dev, "generating page table for %p size 0x%zx pages %d\n",
+ dmab->area, size, pages);
+
+ for (i = 0; i < pages; i++) {
+ /*
+ * The number of valid address bits for each page is 20.
+ * idx determines the byte position within page_table
+ * where the current page's address is stored
+ * in the compressed page_table.
+ * This can be calculated by multiplying the page number by 2.5.
+ */
+ u32 idx = (5 * i) >> 1;
+ u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT;
+ u8 *pg_table;
+
+ pg_table = (u8 *)(page_table + idx);
+
+ /*
+ * pagetable compression:
+ * byte 0 byte 1 byte 2 byte 3 byte 4 byte 5
+ * ___________pfn 0__________ __________pfn 1___________ _pfn 2...
+ * .... .... .... .... .... .... .... .... .... .... ....
+ * It is created by:
+ * 1. set current location to 0, PFN index i to 0
+ * 2. put pfn[i] at current location in Little Endian byte order
+ * 3. calculate an intermediate value as
+ * x = (pfn[i+1] << 4) | (pfn[i] & 0xf)
+ * 4. put x at offset (current location + 2) in LE byte order
+ * 5. increment current location by 5 bytes, increment i by 2
+ * 6. continue to (2)
+ */
+ if (i & 1)
+ put_unaligned_le32((pg_table[0] & 0xf) | pfn << 4,
+ pg_table);
+ else
+ put_unaligned_le32(pfn, pg_table);
+ }
+
+ return pages;
+}
+EXPORT_SYMBOL(snd_sof_create_page_table);
+
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/sof/sof-utils.h b/sound/soc/sof/sof-utils.h
new file mode 100644
index 000000000000..6f902893807e
--- /dev/null
+++ b/sound/soc/sof/sof-utils.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+/*
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * Copyright(c) 2022 Intel Corporation. All rights reserved.
+ */
+
+#ifndef __SOC_SOF_UTILS_H
+#define __SOC_SOF_UTILS_H
+
+struct snd_dma_buffer;
+struct device;
+
+int snd_sof_create_page_table(struct device *dev,
+ struct snd_dma_buffer *dmab,
+ unsigned char *page_table, size_t size);
+
+#endif
diff --git a/sound/soc/sof/stream-ipc.c b/sound/soc/sof/stream-ipc.c
new file mode 100644
index 000000000000..216b454f6b94
--- /dev/null
+++ b/sound/soc/sof/stream-ipc.c
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// This file is provided under a dual BSD/GPLv2 license. When using or
+// redistributing this file, you may do so under either license.
+//
+// Copyright(c) 2019 Intel Corporation. All rights reserved.
+//
+// Authors: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
+
+/* Generic SOF IPC code */
+
+#include <linux/device.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/types.h>
+
+#include <sound/pcm.h>
+#include <sound/sof/stream.h>
+
+#include "ops.h"
+#include "sof-priv.h"
+#include "sof-audio.h"
+
+struct sof_stream {
+ size_t posn_offset;
+};
+
+/* Mailbox-based Generic IPC implementation */
+int sof_ipc_msg_data(struct snd_sof_dev *sdev,
+ struct snd_sof_pcm_stream *sps,
+ void *p, size_t sz)
+{
+ if (!sps || !sdev->stream_box.size) {
+ snd_sof_dsp_mailbox_read(sdev, sdev->dsp_box.offset, p, sz);
+ } else {
+ size_t posn_offset;
+
+ if (sps->substream) {
+ struct sof_stream *stream = sps->substream->runtime->private_data;
+
+ /* The stream might already be closed */
+ if (!stream)
+ return -ESTRPIPE;
+
+ posn_offset = stream->posn_offset;
+ } else {
+
+ struct sof_compr_stream *sstream = sps->cstream->runtime->private_data;
+
+ if (!sstream)
+ return -ESTRPIPE;
+
+ posn_offset = sstream->posn_offset;
+ }
+
+ snd_sof_dsp_mailbox_read(sdev, posn_offset, p, sz);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(sof_ipc_msg_data);
+
+int sof_set_stream_data_offset(struct snd_sof_dev *sdev,
+ struct snd_sof_pcm_stream *sps,
+ size_t posn_offset)
+{
+ /* check if offset is overflow or it is not aligned */
+ if (posn_offset > sdev->stream_box.size ||
+ posn_offset % sizeof(struct sof_ipc_stream_posn) != 0)
+ return -EINVAL;
+
+ posn_offset += sdev->stream_box.offset;
+
+ if (sps->substream) {
+ struct sof_stream *stream = sps->substream->runtime->private_data;
+
+ stream->posn_offset = posn_offset;
+ dev_dbg(sdev->dev, "pcm: stream dir %d, posn mailbox offset is %zu",
+ sps->substream->stream, posn_offset);
+ } else if (sps->cstream) {
+ struct sof_compr_stream *sstream = sps->cstream->runtime->private_data;
+
+ sstream->posn_offset = posn_offset;
+ dev_dbg(sdev->dev, "compr: stream dir %d, posn mailbox offset is %zu",
+ sps->cstream->direction, posn_offset);
+ } else {
+ dev_err(sdev->dev, "No stream opened");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(sof_set_stream_data_offset);
+
+int sof_stream_pcm_open(struct snd_sof_dev *sdev,
+ struct snd_pcm_substream *substream)
+{
+ struct sof_stream *stream = kmalloc(sizeof(*stream), GFP_KERNEL);
+
+ if (!stream)
+ return -ENOMEM;
+
+ /* binding pcm substream to hda stream */
+ substream->runtime->private_data = stream;
+
+ /* align to DMA minimum transfer size */
+ snd_pcm_hw_constraint_step(substream->runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 4);
+
+ /* avoid circular buffer wrap in middle of period */
+ snd_pcm_hw_constraint_integer(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+
+ return 0;
+}
+EXPORT_SYMBOL(sof_stream_pcm_open);
+
+int sof_stream_pcm_close(struct snd_sof_dev *sdev,
+ struct snd_pcm_substream *substream)
+{
+ struct sof_stream *stream = substream->runtime->private_data;
+
+ substream->runtime->private_data = NULL;
+ kfree(stream);
+
+ return 0;
+}
+EXPORT_SYMBOL(sof_stream_pcm_close);
+
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c
index 13e10a0c0b05..bcdb499c96a0 100644
--- a/sound/soc/sof/topology.c
+++ b/sound/soc/sof/topology.c
@@ -8,10 +8,12 @@
// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
//
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/errno.h>
#include <linux/firmware.h>
#include <linux/workqueue.h>
#include <sound/tlv.h>
-#include <sound/pcm_params.h>
#include <uapi/sound/sof/tokens.h>
#include "sof-priv.h"
#include "sof-audio.h"
@@ -25,198 +27,115 @@
#define VOL_TWENTIETH_ROOT_OF_TEN 73533
/* 40th root of 10 in Q1.16 fixed-point notation*/
#define VOL_FORTIETH_ROOT_OF_TEN 69419
-/*
- * Volume fractional word length define to 16 sets
- * the volume linear gain value to use Qx.16 format
- */
-#define VOLUME_FWL 16
+
/* 0.5 dB step value in topology TLV */
#define VOL_HALF_DB_STEP 50
-/* Full volume for default values */
-#define VOL_ZERO_DB BIT(VOLUME_FWL)
/* TLV data items */
-#define TLV_ITEMS 3
#define TLV_MIN 0
#define TLV_STEP 1
#define TLV_MUTE 2
-/* size of tplg abi in byte */
-#define SOF_TPLG_ABI_SIZE 3
-
-struct sof_widget_data {
- int ctrl_type;
- int ipc_cmd;
- struct sof_abi_hdr *pdata;
- struct snd_sof_control *control;
-};
-
-/* send pcm params ipc */
-static int ipc_pcm_params(struct snd_sof_widget *swidget, int dir)
+/**
+ * sof_update_ipc_object - Parse multiple sets of tokens within the token array associated with the
+ * token ID.
+ * @scomp: pointer to SOC component
+ * @object: target IPC struct to save the parsed values
+ * @token_id: token ID for the token array to be searched
+ * @tuples: pointer to the tuples array
+ * @num_tuples: number of tuples in the tuples array
+ * @object_size: size of the object
+ * @token_instance_num: number of times the same @token_id needs to be parsed i.e. the function
+ * looks for @token_instance_num of each token in the token array associated
+ * with the @token_id
+ */
+int sof_update_ipc_object(struct snd_soc_component *scomp, void *object, enum sof_tokens token_id,
+ struct snd_sof_tuple *tuples, int num_tuples,
+ size_t object_size, int token_instance_num)
{
- struct sof_ipc_pcm_params_reply ipc_params_reply;
- struct snd_soc_component *scomp = swidget->scomp;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct sof_ipc_pcm_params pcm;
- struct snd_pcm_hw_params *params;
- struct snd_sof_pcm *spcm;
- int ret = 0;
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
+ const struct sof_token_info *token_list;
+ const struct sof_topology_token *tokens;
+ int i, j;
- memset(&pcm, 0, sizeof(pcm));
+ token_list = tplg_ops ? tplg_ops->token_list : NULL;
+ /* nothing to do if token_list is NULL */
+ if (!token_list)
+ return 0;
- /* get runtime PCM params using widget's stream name */
- spcm = snd_sof_find_spcm_name(scomp, swidget->widget->sname);
- if (!spcm) {
- dev_err(scomp->dev, "error: cannot find PCM for %s\n",
- swidget->widget->name);
+ if (token_list[token_id].count < 0) {
+ dev_err(scomp->dev, "Invalid token count for token ID: %d\n", token_id);
return -EINVAL;
}
- params = &spcm->params[dir];
-
- /* set IPC PCM params */
- pcm.hdr.size = sizeof(pcm);
- pcm.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS;
- pcm.comp_id = swidget->comp_id;
- pcm.params.hdr.size = sizeof(pcm.params);
- pcm.params.direction = dir;
- pcm.params.sample_valid_bytes = params_width(params) >> 3;
- pcm.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED;
- pcm.params.rate = params_rate(params);
- pcm.params.channels = params_channels(params);
- pcm.params.host_period_bytes = params_period_bytes(params);
-
- /* set format */
- switch (params_format(params)) {
- case SNDRV_PCM_FORMAT_S16:
- pcm.params.frame_fmt = SOF_IPC_FRAME_S16_LE;
- break;
- case SNDRV_PCM_FORMAT_S24:
- pcm.params.frame_fmt = SOF_IPC_FRAME_S24_4LE;
- break;
- case SNDRV_PCM_FORMAT_S32:
- pcm.params.frame_fmt = SOF_IPC_FRAME_S32_LE;
- break;
- default:
+ /* No tokens to match */
+ if (!token_list[token_id].count)
+ return 0;
+
+ tokens = token_list[token_id].tokens;
+ if (!tokens) {
+ dev_err(scomp->dev, "Invalid tokens for token id: %d\n", token_id);
return -EINVAL;
}
- /* send IPC to the DSP */
- ret = sof_ipc_tx_message(sdev->ipc, pcm.hdr.cmd, &pcm, sizeof(pcm),
- &ipc_params_reply, sizeof(ipc_params_reply));
- if (ret < 0)
- dev_err(scomp->dev, "error: pcm params failed for %s\n",
- swidget->widget->name);
-
- return ret;
-}
-
- /* send stream trigger ipc */
-static int ipc_trigger(struct snd_sof_widget *swidget, int cmd)
-{
- struct snd_soc_component *scomp = swidget->scomp;
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct sof_ipc_stream stream;
- struct sof_ipc_reply reply;
- int ret = 0;
-
- /* set IPC stream params */
- stream.hdr.size = sizeof(stream);
- stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | cmd;
- stream.comp_id = swidget->comp_id;
-
- /* send IPC to the DSP */
- ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream,
- sizeof(stream), &reply, sizeof(reply));
- if (ret < 0)
- dev_err(scomp->dev, "error: failed to trigger %s\n",
- swidget->widget->name);
-
- return ret;
-}
-
-static int sof_keyword_dapm_event(struct snd_soc_dapm_widget *w,
- struct snd_kcontrol *k, int event)
-{
- struct snd_sof_widget *swidget = w->dobj.private;
- struct snd_soc_component *scomp;
- int stream = SNDRV_PCM_STREAM_CAPTURE;
- struct snd_sof_pcm *spcm;
- int ret = 0;
+ for (i = 0; i < token_list[token_id].count; i++) {
+ int offset = 0;
+ int num_tokens_matched = 0;
- if (!swidget)
- return 0;
+ for (j = 0; j < num_tuples; j++) {
+ if (tokens[i].token == tuples[j].token) {
+ switch (tokens[i].type) {
+ case SND_SOC_TPLG_TUPLE_TYPE_WORD:
+ {
+ u32 *val = (u32 *)((u8 *)object + tokens[i].offset +
+ offset);
- scomp = swidget->scomp;
-
- dev_dbg(scomp->dev, "received event %d for widget %s\n",
- event, w->name);
+ *val = tuples[j].value.v;
+ break;
+ }
+ case SND_SOC_TPLG_TUPLE_TYPE_SHORT:
+ case SND_SOC_TPLG_TUPLE_TYPE_BOOL:
+ {
+ u16 *val = (u16 *)((u8 *)object + tokens[i].offset +
+ offset);
- /* get runtime PCM params using widget's stream name */
- spcm = snd_sof_find_spcm_name(scomp, swidget->widget->sname);
- if (!spcm) {
- dev_err(scomp->dev, "error: cannot find PCM for %s\n",
- swidget->widget->name);
- return -EINVAL;
- }
+ *val = (u16)tuples[j].value.v;
+ break;
+ }
+ case SND_SOC_TPLG_TUPLE_TYPE_STRING:
+ {
+ if (!tokens[i].get_token) {
+ dev_err(scomp->dev,
+ "get_token not defined for token %d in %s\n",
+ tokens[i].token, token_list[token_id].name);
+ return -EINVAL;
+ }
+
+ tokens[i].get_token((void *)tuples[j].value.s, object,
+ tokens[i].offset + offset);
+ break;
+ }
+ default:
+ break;
+ }
- /* process events */
- switch (event) {
- case SND_SOC_DAPM_PRE_PMU:
- if (spcm->stream[stream].suspend_ignored) {
- dev_dbg(scomp->dev, "PRE_PMU event ignored, KWD pipeline is already RUNNING\n");
- return 0;
- }
+ num_tokens_matched++;
- /* set pcm params */
- ret = ipc_pcm_params(swidget, stream);
- if (ret < 0) {
- dev_err(scomp->dev,
- "error: failed to set pcm params for widget %s\n",
- swidget->widget->name);
- break;
- }
+ /* found all required sets of current token. Move to the next one */
+ if (!(num_tokens_matched % token_instance_num))
+ break;
- /* start trigger */
- ret = ipc_trigger(swidget, SOF_IPC_STREAM_TRIG_START);
- if (ret < 0)
- dev_err(scomp->dev,
- "error: failed to trigger widget %s\n",
- swidget->widget->name);
- break;
- case SND_SOC_DAPM_POST_PMD:
- if (spcm->stream[stream].suspend_ignored) {
- dev_dbg(scomp->dev, "POST_PMD even ignored, KWD pipeline will remain RUNNING\n");
- return 0;
+ /* move to the next object */
+ offset += object_size;
+ }
}
-
- /* stop trigger */
- ret = ipc_trigger(swidget, SOF_IPC_STREAM_TRIG_STOP);
- if (ret < 0)
- dev_err(scomp->dev,
- "error: failed to trigger widget %s\n",
- swidget->widget->name);
-
- /* pcm free */
- ret = ipc_trigger(swidget, SOF_IPC_STREAM_PCM_FREE);
- if (ret < 0)
- dev_err(scomp->dev,
- "error: failed to trigger widget %s\n",
- swidget->widget->name);
- break;
- default:
- break;
}
- return ret;
+ return 0;
}
-/* event handlers for keyword detect component */
-static const struct snd_soc_tplg_widget_events sof_kwd_events[] = {
- {SOF_KEYWORD_DETECT_DAPM_EVENT, sof_keyword_dapm_event},
-};
-
-static inline int get_tlv_data(const int *p, int tlv[TLV_ITEMS])
+static inline int get_tlv_data(const int *p, int tlv[SOF_TLV_ITEMS])
{
/* we only support dB scale TLV type at the moment */
if ((int)p[SNDRV_CTL_TLVO_TYPE] != SNDRV_CTL_TLVT_DB_SCALE)
@@ -306,7 +225,7 @@ static u32 vol_pow32(u32 a, int exp, u32 fwl)
* Function to calculate volume gain from TLV data.
* This function can only handle gain steps that are multiples of 0.5 dB
*/
-static u32 vol_compute_gain(u32 value, int *tlv)
+u32 vol_compute_gain(u32 value, int *tlv)
{
int dB_gain;
u32 linear_gain;
@@ -345,20 +264,17 @@ static u32 vol_compute_gain(u32 value, int *tlv)
* "size" specifies the number of entries in the table
*/
static int set_up_volume_table(struct snd_sof_control *scontrol,
- int tlv[TLV_ITEMS], int size)
+ int tlv[SOF_TLV_ITEMS], int size)
{
- int j;
-
- /* init the volume table */
- scontrol->volume_table = kcalloc(size, sizeof(u32), GFP_KERNEL);
- if (!scontrol->volume_table)
- return -ENOMEM;
+ struct snd_soc_component *scomp = scontrol->scomp;
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
- /* populate the volume table */
- for (j = 0; j < size ; j++)
- scontrol->volume_table[j] = vol_compute_gain(j, tlv);
+ if (tplg_ops && tplg_ops->control && tplg_ops->control->set_up_volume_table)
+ return tplg_ops->control->set_up_volume_table(scontrol, tlv, size);
- return 0;
+ dev_err(scomp->dev, "Mandatory op %s not set\n", __func__);
+ return -EINVAL;
}
struct sof_dai_types {
@@ -373,6 +289,16 @@ static const struct sof_dai_types sof_dais[] = {
{"ALH", SOF_DAI_INTEL_ALH},
{"SAI", SOF_DAI_IMX_SAI},
{"ESAI", SOF_DAI_IMX_ESAI},
+ {"ACPBT", SOF_DAI_AMD_BT},
+ {"ACPSP", SOF_DAI_AMD_SP},
+ {"ACPDMIC", SOF_DAI_AMD_DMIC},
+ {"ACPHS", SOF_DAI_AMD_HS},
+ {"AFE", SOF_DAI_MEDIATEK_AFE},
+ {"ACPSP_VIRTUAL", SOF_DAI_AMD_SP_VIRTUAL},
+ {"ACPHS_VIRTUAL", SOF_DAI_AMD_HS_VIRTUAL},
+ {"MICFIL", SOF_DAI_IMX_MICFIL},
+ {"ACP_SDW", SOF_DAI_AMD_SDW},
+
};
static enum sof_ipc_dai_type find_dai(const char *name)
@@ -416,62 +342,7 @@ static enum sof_ipc_frame find_format(const char *name)
return SOF_IPC_FRAME_S32_LE;
}
-struct sof_process_types {
- const char *name;
- enum sof_ipc_process_type type;
- enum sof_comp_type comp_type;
-};
-
-static const struct sof_process_types sof_process[] = {
- {"EQFIR", SOF_PROCESS_EQFIR, SOF_COMP_EQ_FIR},
- {"EQIIR", SOF_PROCESS_EQIIR, SOF_COMP_EQ_IIR},
- {"KEYWORD_DETECT", SOF_PROCESS_KEYWORD_DETECT, SOF_COMP_KEYWORD_DETECT},
- {"KPB", SOF_PROCESS_KPB, SOF_COMP_KPB},
- {"CHAN_SELECTOR", SOF_PROCESS_CHAN_SELECTOR, SOF_COMP_SELECTOR},
- {"MUX", SOF_PROCESS_MUX, SOF_COMP_MUX},
- {"DEMUX", SOF_PROCESS_DEMUX, SOF_COMP_DEMUX},
- {"DCBLOCK", SOF_PROCESS_DCBLOCK, SOF_COMP_DCBLOCK},
- {"SMART_AMP", SOF_PROCESS_SMART_AMP, SOF_COMP_SMART_AMP},
-};
-
-static enum sof_ipc_process_type find_process(const char *name)
-{
- int i;
-
- for (i = 0; i < ARRAY_SIZE(sof_process); i++) {
- if (strcmp(name, sof_process[i].name) == 0)
- return sof_process[i].type;
- }
-
- return SOF_PROCESS_NONE;
-}
-
-static enum sof_comp_type find_process_comp_type(enum sof_ipc_process_type type)
-{
- int i;
-
- for (i = 0; i < ARRAY_SIZE(sof_process); i++) {
- if (sof_process[i].type == type)
- return sof_process[i].comp_type;
- }
-
- return SOF_COMP_NONE;
-}
-
-/*
- * Topology Token Parsing.
- * New tokens should be added to headers and parsing tables below.
- */
-
-struct sof_topology_token {
- u32 token;
- u32 type;
- int (*get_token)(void *elem, void *object, u32 offset, u32 size);
- u32 offset;
- u32 size;
-};
-
-static int get_token_u32(void *elem, void *object, u32 offset, u32 size)
+int get_token_u32(void *elem, void *object, u32 offset)
{
struct snd_soc_tplg_vendor_value_elem *velem = elem;
u32 *val = (u32 *)((u8 *)object + offset);
@@ -480,7 +351,7 @@ static int get_token_u32(void *elem, void *object, u32 offset, u32 size)
return 0;
}
-static int get_token_u16(void *elem, void *object, u32 offset, u32 size)
+int get_token_u16(void *elem, void *object, u32 offset)
{
struct snd_soc_tplg_vendor_value_elem *velem = elem;
u16 *val = (u16 *)((u8 *)object + offset);
@@ -489,293 +360,95 @@ static int get_token_u16(void *elem, void *object, u32 offset, u32 size)
return 0;
}
-static int get_token_comp_format(void *elem, void *object, u32 offset, u32 size)
+int get_token_uuid(void *elem, void *object, u32 offset)
{
- struct snd_soc_tplg_vendor_string_elem *velem = elem;
- u32 *val = (u32 *)((u8 *)object + offset);
+ struct snd_soc_tplg_vendor_uuid_elem *velem = elem;
+ u8 *dst = (u8 *)object + offset;
+
+ memcpy(dst, velem->uuid, UUID_SIZE);
- *val = find_format(velem->string);
return 0;
}
-static int get_token_dai_type(void *elem, void *object, u32 offset, u32 size)
+/*
+ * The string gets from topology will be stored in heap, the owner only
+ * holds a char* member point to the heap.
+ */
+int get_token_string(void *elem, void *object, u32 offset)
{
- struct snd_soc_tplg_vendor_string_elem *velem = elem;
- u32 *val = (u32 *)((u8 *)object + offset);
+ /* "dst" here points to the char* member of the owner */
+ char **dst = (char **)((u8 *)object + offset);
- *val = find_dai(velem->string);
+ *dst = kstrdup(elem, GFP_KERNEL);
+ if (!*dst)
+ return -ENOMEM;
return 0;
-}
+};
-static int get_token_process_type(void *elem, void *object, u32 offset,
- u32 size)
+int get_token_comp_format(void *elem, void *object, u32 offset)
{
- struct snd_soc_tplg_vendor_string_elem *velem = elem;
u32 *val = (u32 *)((u8 *)object + offset);
- *val = find_process(velem->string);
+ *val = find_format((const char *)elem);
return 0;
}
-/* Buffers */
-static const struct sof_topology_token buffer_tokens[] = {
- {SOF_TKN_BUF_SIZE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_buffer, size), 0},
- {SOF_TKN_BUF_CAPS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_buffer, caps), 0},
-};
-
-/* DAI */
-static const struct sof_topology_token dai_tokens[] = {
- {SOF_TKN_DAI_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_dai_type,
- offsetof(struct sof_ipc_comp_dai, type), 0},
- {SOF_TKN_DAI_INDEX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_comp_dai, dai_index), 0},
- {SOF_TKN_DAI_DIRECTION, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_comp_dai, direction), 0},
-};
-
-/* BE DAI link */
-static const struct sof_topology_token dai_link_tokens[] = {
- {SOF_TKN_DAI_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_dai_type,
- offsetof(struct sof_ipc_dai_config, type), 0},
- {SOF_TKN_DAI_INDEX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_dai_config, dai_index), 0},
-};
-
-/* scheduling */
-static const struct sof_topology_token sched_tokens[] = {
- {SOF_TKN_SCHED_PERIOD, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_pipe_new, period), 0},
- {SOF_TKN_SCHED_PRIORITY, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_pipe_new, priority), 0},
- {SOF_TKN_SCHED_MIPS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_pipe_new, period_mips), 0},
- {SOF_TKN_SCHED_CORE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_pipe_new, core), 0},
- {SOF_TKN_SCHED_FRAMES, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_pipe_new, frames_per_sched), 0},
- {SOF_TKN_SCHED_TIME_DOMAIN, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_pipe_new, time_domain), 0},
-};
-
-/* volume */
-static const struct sof_topology_token volume_tokens[] = {
- {SOF_TKN_VOLUME_RAMP_STEP_TYPE, SND_SOC_TPLG_TUPLE_TYPE_WORD,
- get_token_u32, offsetof(struct sof_ipc_comp_volume, ramp), 0},
- {SOF_TKN_VOLUME_RAMP_STEP_MS,
- SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_comp_volume, initial_ramp), 0},
-};
-
-/* SRC */
-static const struct sof_topology_token src_tokens[] = {
- {SOF_TKN_SRC_RATE_IN, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_comp_src, source_rate), 0},
- {SOF_TKN_SRC_RATE_OUT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_comp_src, sink_rate), 0},
-};
-
-/* ASRC */
-static const struct sof_topology_token asrc_tokens[] = {
- {SOF_TKN_ASRC_RATE_IN, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_comp_asrc, source_rate), 0},
- {SOF_TKN_ASRC_RATE_OUT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_comp_asrc, sink_rate), 0},
- {SOF_TKN_ASRC_ASYNCHRONOUS_MODE, SND_SOC_TPLG_TUPLE_TYPE_WORD,
- get_token_u32,
- offsetof(struct sof_ipc_comp_asrc, asynchronous_mode), 0},
- {SOF_TKN_ASRC_OPERATION_MODE, SND_SOC_TPLG_TUPLE_TYPE_WORD,
- get_token_u32,
- offsetof(struct sof_ipc_comp_asrc, operation_mode), 0},
-};
-
-/* Tone */
-static const struct sof_topology_token tone_tokens[] = {
-};
-
-/* EFFECT */
-static const struct sof_topology_token process_tokens[] = {
- {SOF_TKN_PROCESS_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING,
- get_token_process_type,
- offsetof(struct sof_ipc_comp_process, type), 0},
-};
+int get_token_dai_type(void *elem, void *object, u32 offset)
+{
+ u32 *val = (u32 *)((u8 *)object + offset);
-/* PCM */
-static const struct sof_topology_token pcm_tokens[] = {
- {SOF_TKN_PCM_DMAC_CONFIG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_comp_host, dmac_config), 0},
-};
+ *val = find_dai((const char *)elem);
+ return 0;
+}
/* PCM */
static const struct sof_topology_token stream_tokens[] = {
- {SOF_TKN_STREAM_PLAYBACK_COMPATIBLE_D0I3,
- SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16,
- offsetof(struct snd_sof_pcm, stream[0].d0i3_compatible), 0},
- {SOF_TKN_STREAM_CAPTURE_COMPATIBLE_D0I3,
- SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16,
- offsetof(struct snd_sof_pcm, stream[1].d0i3_compatible), 0},
-};
-
-/* Generic components */
-static const struct sof_topology_token comp_tokens[] = {
- {SOF_TKN_COMP_PERIOD_SINK_COUNT,
- SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_comp_config, periods_sink), 0},
- {SOF_TKN_COMP_PERIOD_SOURCE_COUNT,
- SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_comp_config, periods_source), 0},
- {SOF_TKN_COMP_FORMAT,
- SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_comp_format,
- offsetof(struct sof_ipc_comp_config, frame_fmt), 0},
+ {SOF_TKN_STREAM_PLAYBACK_COMPATIBLE_D0I3, SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16,
+ offsetof(struct snd_sof_pcm, stream[0].d0i3_compatible)},
+ {SOF_TKN_STREAM_CAPTURE_COMPATIBLE_D0I3, SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16,
+ offsetof(struct snd_sof_pcm, stream[1].d0i3_compatible)},
};
-/* SSP */
-static const struct sof_topology_token ssp_tokens[] = {
- {SOF_TKN_INTEL_SSP_CLKS_CONTROL,
- SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_dai_ssp_params, clks_control), 0},
- {SOF_TKN_INTEL_SSP_MCLK_ID,
- SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
- offsetof(struct sof_ipc_dai_ssp_params, mclk_id), 0},
- {SOF_TKN_INTEL_SSP_SAMPLE_BITS, SND_SOC_TPLG_TUPLE_TYPE_WORD,
- get_token_u32,
- offsetof(struct sof_ipc_dai_ssp_params, sample_valid_bits), 0},
- {SOF_TKN_INTEL_SSP_FRAME_PULSE_WIDTH, SND_SOC_TPLG_TUPLE_TYPE_SHORT,
- get_token_u16,
- offsetof(struct sof_ipc_dai_ssp_params, frame_pulse_width), 0},
- {SOF_TKN_INTEL_SSP_QUIRKS, SND_SOC_TPLG_TUPLE_TYPE_WORD,
- get_token_u32,
- offsetof(struct sof_ipc_dai_ssp_params, quirks), 0},
- {SOF_TKN_INTEL_SSP_TDM_PADDING_PER_SLOT, SND_SOC_TPLG_TUPLE_TYPE_BOOL,
- get_token_u16,
- offsetof(struct sof_ipc_dai_ssp_params,
- tdm_per_slot_padding_flag), 0},
- {SOF_TKN_INTEL_SSP_BCLK_DELAY, SND_SOC_TPLG_TUPLE_TYPE_WORD,
- get_token_u32,
- offsetof(struct sof_ipc_dai_ssp_params, bclk_delay), 0},
-
-};
-
-/* ALH */
-static const struct sof_topology_token alh_tokens[] = {
- {SOF_TKN_INTEL_ALH_RATE,
- SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_dai_alh_params, rate), 0},
- {SOF_TKN_INTEL_ALH_CH,
- SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_dai_alh_params, channels), 0},
+/* Leds */
+static const struct sof_topology_token led_tokens[] = {
+ {SOF_TKN_MUTE_LED_USE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct snd_sof_led_control, use_led)},
+ {SOF_TKN_MUTE_LED_DIRECTION, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct snd_sof_led_control, direction)},
};
-/* DMIC */
-static const struct sof_topology_token dmic_tokens[] = {
- {SOF_TKN_INTEL_DMIC_DRIVER_VERSION,
- SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_dai_dmic_params, driver_ipc_version),
- 0},
- {SOF_TKN_INTEL_DMIC_CLK_MIN,
- SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_dai_dmic_params, pdmclk_min), 0},
- {SOF_TKN_INTEL_DMIC_CLK_MAX,
- SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_dai_dmic_params, pdmclk_max), 0},
- {SOF_TKN_INTEL_DMIC_SAMPLE_RATE,
- SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_dai_dmic_params, fifo_fs), 0},
- {SOF_TKN_INTEL_DMIC_DUTY_MIN,
- SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
- offsetof(struct sof_ipc_dai_dmic_params, duty_min), 0},
- {SOF_TKN_INTEL_DMIC_DUTY_MAX,
- SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
- offsetof(struct sof_ipc_dai_dmic_params, duty_max), 0},
- {SOF_TKN_INTEL_DMIC_NUM_PDM_ACTIVE,
- SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_dai_dmic_params,
- num_pdm_active), 0},
- {SOF_TKN_INTEL_DMIC_FIFO_WORD_LENGTH,
- SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
- offsetof(struct sof_ipc_dai_dmic_params, fifo_bits), 0},
- {SOF_TKN_INTEL_DMIC_UNMUTE_RAMP_TIME_MS,
- SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_dai_dmic_params, unmute_ramp_time), 0},
-
+static const struct sof_topology_token comp_pin_tokens[] = {
+ {SOF_TKN_COMP_NUM_INPUT_PINS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct snd_sof_widget, num_input_pins)},
+ {SOF_TKN_COMP_NUM_OUTPUT_PINS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
+ offsetof(struct snd_sof_widget, num_output_pins)},
};
-/* ESAI */
-static const struct sof_topology_token esai_tokens[] = {
- {SOF_TKN_IMX_ESAI_MCLK_ID,
- SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
- offsetof(struct sof_ipc_dai_esai_params, mclk_id), 0},
+static const struct sof_topology_token comp_input_pin_binding_tokens[] = {
+ {SOF_TKN_COMP_INPUT_PIN_BINDING_WNAME, SND_SOC_TPLG_TUPLE_TYPE_STRING,
+ get_token_string, 0},
};
-/* SAI */
-static const struct sof_topology_token sai_tokens[] = {
- {SOF_TKN_IMX_SAI_MCLK_ID,
- SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
- offsetof(struct sof_ipc_dai_sai_params, mclk_id), 0},
+static const struct sof_topology_token comp_output_pin_binding_tokens[] = {
+ {SOF_TKN_COMP_OUTPUT_PIN_BINDING_WNAME, SND_SOC_TPLG_TUPLE_TYPE_STRING,
+ get_token_string, 0},
};
-/*
- * DMIC PDM Tokens
- * SOF_TKN_INTEL_DMIC_PDM_CTRL_ID should be the first token
- * as it increments the index while parsing the array of pdm tokens
- * and determines the correct offset
+/**
+ * sof_parse_uuid_tokens - Parse multiple sets of UUID tokens
+ * @scomp: pointer to soc component
+ * @object: target ipc struct for parsed values
+ * @offset: offset within the object pointer
+ * @tokens: array of struct sof_topology_token containing the tokens to be matched
+ * @num_tokens: number of tokens in tokens array
+ * @array: source pointer to consecutive vendor arrays in topology
+ *
+ * This function parses multiple sets of string type tokens in vendor arrays
*/
-static const struct sof_topology_token dmic_pdm_tokens[] = {
- {SOF_TKN_INTEL_DMIC_PDM_CTRL_ID,
- SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
- offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, id),
- 0},
- {SOF_TKN_INTEL_DMIC_PDM_MIC_A_Enable,
- SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
- offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, enable_mic_a),
- 0},
- {SOF_TKN_INTEL_DMIC_PDM_MIC_B_Enable,
- SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
- offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, enable_mic_b),
- 0},
- {SOF_TKN_INTEL_DMIC_PDM_POLARITY_A,
- SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
- offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, polarity_mic_a),
- 0},
- {SOF_TKN_INTEL_DMIC_PDM_POLARITY_B,
- SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
- offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, polarity_mic_b),
- 0},
- {SOF_TKN_INTEL_DMIC_PDM_CLK_EDGE,
- SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
- offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, clk_edge),
- 0},
- {SOF_TKN_INTEL_DMIC_PDM_SKEW,
- SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
- offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, skew),
- 0},
-};
-
-/* HDA */
-static const struct sof_topology_token hda_tokens[] = {
- {SOF_TKN_INTEL_HDA_RATE,
- SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_dai_hda_params, rate), 0},
- {SOF_TKN_INTEL_HDA_CH,
- SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct sof_ipc_dai_hda_params, channels), 0},
-};
-
-/* Leds */
-static const struct sof_topology_token led_tokens[] = {
- {SOF_TKN_MUTE_LED_USE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
- offsetof(struct snd_sof_led_control, use_led), 0},
- {SOF_TKN_MUTE_LED_DIRECTION, SND_SOC_TPLG_TUPLE_TYPE_WORD,
- get_token_u32, offsetof(struct snd_sof_led_control, direction), 0},
-};
-
static int sof_parse_uuid_tokens(struct snd_soc_component *scomp,
- void *object,
- const struct sof_topology_token *tokens,
- int count,
- struct snd_soc_tplg_vendor_array *array,
- size_t offset)
+ void *object, size_t offset,
+ const struct sof_topology_token *tokens, int num_tokens,
+ struct snd_soc_tplg_vendor_array *array)
{
struct snd_soc_tplg_vendor_uuid_elem *elem;
int found = 0;
@@ -786,7 +459,7 @@ static int sof_parse_uuid_tokens(struct snd_soc_component *scomp,
elem = &array->uuid[i];
/* search for token */
- for (j = 0; j < count; j++) {
+ for (j = 0; j < num_tokens; j++) {
/* match token type */
if (tokens[j].type != SND_SOC_TPLG_TUPLE_TYPE_UUID)
continue;
@@ -797,8 +470,7 @@ static int sof_parse_uuid_tokens(struct snd_soc_component *scomp,
/* matched - now load token */
tokens[j].get_token(elem, object,
- offset + tokens[j].offset,
- tokens[j].size);
+ offset + tokens[j].offset);
found++;
}
@@ -807,23 +479,154 @@ static int sof_parse_uuid_tokens(struct snd_soc_component *scomp,
return found;
}
+/**
+ * sof_copy_tuples - Parse tokens and copy them to the @tuples array
+ * @sdev: pointer to struct snd_sof_dev
+ * @array: source pointer to consecutive vendor arrays in topology
+ * @array_size: size of @array
+ * @token_id: Token ID associated with a token array
+ * @token_instance_num: number of times the same @token_id needs to be parsed i.e. the function
+ * looks for @token_instance_num of each token in the token array associated
+ * with the @token_id
+ * @tuples: tuples array to copy the matched tuples to
+ * @tuples_size: size of @tuples
+ * @num_copied_tuples: pointer to the number of copied tuples in the tuples array
+ *
+ */
+static int sof_copy_tuples(struct snd_sof_dev *sdev, struct snd_soc_tplg_vendor_array *array,
+ int array_size, u32 token_id, int token_instance_num,
+ struct snd_sof_tuple *tuples, int tuples_size, int *num_copied_tuples)
+{
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
+ const struct sof_token_info *token_list;
+ const struct sof_topology_token *tokens;
+ int found = 0;
+ int num_tokens, asize;
+ int i, j;
+
+ token_list = tplg_ops ? tplg_ops->token_list : NULL;
+ /* nothing to do if token_list is NULL */
+ if (!token_list)
+ return 0;
+
+ if (!tuples || !num_copied_tuples) {
+ dev_err(sdev->dev, "Invalid tuples array\n");
+ return -EINVAL;
+ }
+
+ tokens = token_list[token_id].tokens;
+ num_tokens = token_list[token_id].count;
+
+ if (!tokens) {
+ dev_err(sdev->dev, "No token array defined for token ID: %d\n", token_id);
+ return -EINVAL;
+ }
+
+ /* check if there's space in the tuples array for new tokens */
+ if (*num_copied_tuples >= tuples_size) {
+ dev_err(sdev->dev, "No space in tuples array for new tokens from %s",
+ token_list[token_id].name);
+ return -EINVAL;
+ }
+
+ while (array_size > 0 && found < num_tokens * token_instance_num) {
+ asize = le32_to_cpu(array->size);
+
+ /* validate asize */
+ if (asize < 0) {
+ dev_err(sdev->dev, "Invalid array size 0x%x\n", asize);
+ return -EINVAL;
+ }
+
+ /* make sure there is enough data before parsing */
+ array_size -= asize;
+ if (array_size < 0) {
+ dev_err(sdev->dev, "Invalid array size 0x%x\n", asize);
+ return -EINVAL;
+ }
+
+ /* parse element by element */
+ for (i = 0; i < le32_to_cpu(array->num_elems); i++) {
+ /* search for token */
+ for (j = 0; j < num_tokens; j++) {
+ /* match token type */
+ if (!(tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_WORD ||
+ tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_SHORT ||
+ tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_BYTE ||
+ tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_BOOL ||
+ tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_STRING))
+ continue;
+
+ if (tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_STRING) {
+ struct snd_soc_tplg_vendor_string_elem *elem;
+
+ elem = &array->string[i];
+
+ /* match token id */
+ if (tokens[j].token != le32_to_cpu(elem->token))
+ continue;
+
+ tuples[*num_copied_tuples].token = tokens[j].token;
+ tuples[*num_copied_tuples].value.s = elem->string;
+ } else {
+ struct snd_soc_tplg_vendor_value_elem *elem;
+
+ elem = &array->value[i];
+
+ /* match token id */
+ if (tokens[j].token != le32_to_cpu(elem->token))
+ continue;
+
+ tuples[*num_copied_tuples].token = tokens[j].token;
+ tuples[*num_copied_tuples].value.v =
+ le32_to_cpu(elem->value);
+ }
+ found++;
+ (*num_copied_tuples)++;
+
+ /* stop if there's no space for any more new tuples */
+ if (*num_copied_tuples == tuples_size)
+ return 0;
+ }
+
+ /* stop when we've found the required token instances */
+ if (found == num_tokens * token_instance_num)
+ return 0;
+ }
+
+ /* next array */
+ array = (struct snd_soc_tplg_vendor_array *)((u8 *)array + asize);
+ }
+
+ return 0;
+}
+
+/**
+ * sof_parse_string_tokens - Parse multiple sets of tokens
+ * @scomp: pointer to soc component
+ * @object: target ipc struct for parsed values
+ * @offset: offset within the object pointer
+ * @tokens: array of struct sof_topology_token containing the tokens to be matched
+ * @num_tokens: number of tokens in tokens array
+ * @array: source pointer to consecutive vendor arrays in topology
+ *
+ * This function parses multiple sets of string type tokens in vendor arrays
+ */
static int sof_parse_string_tokens(struct snd_soc_component *scomp,
- void *object,
- const struct sof_topology_token *tokens,
- int count,
- struct snd_soc_tplg_vendor_array *array,
- size_t offset)
+ void *object, int offset,
+ const struct sof_topology_token *tokens, int num_tokens,
+ struct snd_soc_tplg_vendor_array *array)
{
struct snd_soc_tplg_vendor_string_elem *elem;
int found = 0;
- int i, j;
+ int i, j, ret;
/* parse element by element */
for (i = 0; i < le32_to_cpu(array->num_elems); i++) {
elem = &array->string[i];
/* search for token */
- for (j = 0; j < count; j++) {
+ for (j = 0; j < num_tokens; j++) {
/* match token type */
if (tokens[j].type != SND_SOC_TPLG_TUPLE_TYPE_STRING)
continue;
@@ -833,9 +636,9 @@ static int sof_parse_string_tokens(struct snd_soc_component *scomp,
continue;
/* matched - now load token */
- tokens[j].get_token(elem, object,
- offset + tokens[j].offset,
- tokens[j].size);
+ ret = tokens[j].get_token(elem->string, object, offset + tokens[j].offset);
+ if (ret < 0)
+ return ret;
found++;
}
@@ -844,12 +647,21 @@ static int sof_parse_string_tokens(struct snd_soc_component *scomp,
return found;
}
+/**
+ * sof_parse_word_tokens - Parse multiple sets of tokens
+ * @scomp: pointer to soc component
+ * @object: target ipc struct for parsed values
+ * @offset: offset within the object pointer
+ * @tokens: array of struct sof_topology_token containing the tokens to be matched
+ * @num_tokens: number of tokens in tokens array
+ * @array: source pointer to consecutive vendor arrays in topology
+ *
+ * This function parses multiple sets of word type tokens in vendor arrays
+ */
static int sof_parse_word_tokens(struct snd_soc_component *scomp,
- void *object,
- const struct sof_topology_token *tokens,
- int count,
- struct snd_soc_tplg_vendor_array *array,
- size_t offset)
+ void *object, int offset,
+ const struct sof_topology_token *tokens, int num_tokens,
+ struct snd_soc_tplg_vendor_array *array)
{
struct snd_soc_tplg_vendor_value_elem *elem;
int found = 0;
@@ -860,7 +672,7 @@ static int sof_parse_word_tokens(struct snd_soc_component *scomp,
elem = &array->value[i];
/* search for token */
- for (j = 0; j < count; j++) {
+ for (j = 0; j < num_tokens; j++) {
/* match token type */
if (!(tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_WORD ||
tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_SHORT ||
@@ -873,9 +685,7 @@ static int sof_parse_word_tokens(struct snd_soc_component *scomp,
continue;
/* load token */
- tokens[j].get_token(elem, object,
- offset + tokens[j].offset,
- tokens[j].size);
+ tokens[j].get_token(elem, object, offset + tokens[j].offset);
found++;
}
@@ -890,27 +700,27 @@ static int sof_parse_word_tokens(struct snd_soc_component *scomp,
* @object: target ipc struct for parsed values
* @tokens: token definition array describing what tokens to parse
* @count: number of tokens in definition array
- * @array: source pointer to consecutive vendor arrays to be parsed
- * @priv_size: total size of the consecutive source arrays
- * @sets: number of similar token sets to be parsed, 1 set has count elements
+ * @array: source pointer to consecutive vendor arrays in topology
+ * @array_size: total size of @array
+ * @token_instance_num: number of times the same tokens needs to be parsed i.e. the function
+ * looks for @token_instance_num of each token in the @tokens
* @object_size: offset to next target ipc struct with multiple sets
*
* This function parses multiple sets of tokens in vendor arrays into
* consecutive ipc structs.
*/
static int sof_parse_token_sets(struct snd_soc_component *scomp,
- void *object,
- const struct sof_topology_token *tokens,
- int count,
- struct snd_soc_tplg_vendor_array *array,
- int priv_size, int sets, size_t object_size)
+ void *object, const struct sof_topology_token *tokens,
+ int count, struct snd_soc_tplg_vendor_array *array,
+ int array_size, int token_instance_num, size_t object_size)
{
size_t offset = 0;
int found = 0;
int total = 0;
int asize;
+ int ret;
- while (priv_size > 0 && total < count * sets) {
+ while (array_size > 0 && total < count * token_instance_num) {
asize = le32_to_cpu(array->size);
/* validate asize */
@@ -921,8 +731,8 @@ static int sof_parse_token_sets(struct snd_soc_component *scomp,
}
/* make sure there is enough data before parsing */
- priv_size -= asize;
- if (priv_size < 0) {
+ array_size -= asize;
+ if (array_size < 0) {
dev_err(scomp->dev, "error: invalid array size 0x%x\n",
asize);
return -EINVAL;
@@ -931,19 +741,26 @@ static int sof_parse_token_sets(struct snd_soc_component *scomp,
/* call correct parser depending on type */
switch (le32_to_cpu(array->type)) {
case SND_SOC_TPLG_TUPLE_TYPE_UUID:
- found += sof_parse_uuid_tokens(scomp, object, tokens,
- count, array, offset);
+ found += sof_parse_uuid_tokens(scomp, object, offset, tokens, count,
+ array);
break;
case SND_SOC_TPLG_TUPLE_TYPE_STRING:
- found += sof_parse_string_tokens(scomp, object, tokens,
- count, array, offset);
+
+ ret = sof_parse_string_tokens(scomp, object, offset, tokens, count,
+ array);
+ if (ret < 0) {
+ dev_err(scomp->dev, "error: no memory to copy string token\n");
+ return ret;
+ }
+
+ found += ret;
break;
case SND_SOC_TPLG_TUPLE_TYPE_BOOL:
case SND_SOC_TPLG_TUPLE_TYPE_BYTE:
case SND_SOC_TPLG_TUPLE_TYPE_WORD:
case SND_SOC_TPLG_TUPLE_TYPE_SHORT:
- found += sof_parse_word_tokens(scomp, object, tokens,
- count, array, offset);
+ found += sof_parse_word_tokens(scomp, object, offset, tokens, count,
+ array);
break;
default:
dev_err(scomp->dev, "error: unknown token type %d\n",
@@ -966,12 +783,23 @@ static int sof_parse_token_sets(struct snd_soc_component *scomp,
return 0;
}
-static int sof_parse_tokens(struct snd_soc_component *scomp,
- void *object,
- const struct sof_topology_token *tokens,
- int count,
+/**
+ * sof_parse_tokens - Parse one set of tokens
+ * @scomp: pointer to soc component
+ * @object: target ipc struct for parsed values
+ * @tokens: token definition array describing what tokens to parse
+ * @num_tokens: number of tokens in definition array
+ * @array: source pointer to consecutive vendor arrays in topology
+ * @array_size: total size of @array
+ *
+ * This function parses a single set of tokens in vendor arrays into
+ * consecutive ipc structs.
+ */
+static int sof_parse_tokens(struct snd_soc_component *scomp, void *object,
+ const struct sof_topology_token *tokens, int num_tokens,
struct snd_soc_tplg_vendor_array *array,
- int priv_size)
+ int array_size)
+
{
/*
* sof_parse_tokens is used when topology contains only a single set of
@@ -979,16 +807,8 @@ static int sof_parse_tokens(struct snd_soc_component *scomp,
* sof_parse_token_sets are sets = 1 (only 1 set) and
* object_size = 0 (irrelevant).
*/
- return sof_parse_token_sets(scomp, object, tokens, count, array,
- priv_size, 1, 0);
-}
-
-static void sof_dbg_comp_config(struct snd_soc_component *scomp,
- struct sof_ipc_comp_config *config)
-{
- dev_dbg(scomp->dev, " config: periods snk %d src %d fmt %d\n",
- config->periods_sink, config->periods_source,
- config->frame_fmt);
+ return sof_parse_token_sets(scomp, object, tokens, num_tokens, array,
+ array_size, 1, 0);
}
/*
@@ -1003,58 +823,43 @@ static int sof_control_load_volume(struct snd_soc_component *scomp,
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct snd_soc_tplg_mixer_control *mc =
container_of(hdr, struct snd_soc_tplg_mixer_control, hdr);
- struct sof_ipc_ctrl_data *cdata;
- int tlv[TLV_ITEMS];
- unsigned int i;
- int ret = 0;
+ int tlv[SOF_TLV_ITEMS];
+ unsigned int mask;
+ int ret;
/* validate topology data */
- if (le32_to_cpu(mc->num_channels) > SND_SOC_TPLG_MAX_CHAN) {
- ret = -EINVAL;
- goto out;
- }
+ if (le32_to_cpu(mc->num_channels) > SND_SOC_TPLG_MAX_CHAN)
+ return -EINVAL;
- /* init the volume get/put data */
- scontrol->size = struct_size(scontrol->control_data, chanv,
- le32_to_cpu(mc->num_channels));
- scontrol->control_data = kzalloc(scontrol->size, GFP_KERNEL);
- if (!scontrol->control_data) {
- ret = -ENOMEM;
- goto out;
- }
+ /*
+ * If control has more than 2 channels we need to override the info. This is because even if
+ * ASoC layer has defined topology's max channel count to SND_SOC_TPLG_MAX_CHAN = 8, the
+ * pre-defined dapm control types (and related functions) creating the actual control
+ * restrict the channels only to mono or stereo.
+ */
+ if (le32_to_cpu(mc->num_channels) > 2)
+ kc->info = snd_sof_volume_info;
scontrol->comp_id = sdev->next_comp_id;
scontrol->min_volume_step = le32_to_cpu(mc->min);
scontrol->max_volume_step = le32_to_cpu(mc->max);
scontrol->num_channels = le32_to_cpu(mc->num_channels);
- /* set cmd for mixer control */
- if (le32_to_cpu(mc->max) == 1) {
- scontrol->cmd = SOF_CTRL_CMD_SWITCH;
+ scontrol->max = le32_to_cpu(mc->max);
+ if (le32_to_cpu(mc->max) == 1)
goto skip;
- }
-
- scontrol->cmd = SOF_CTRL_CMD_VOLUME;
/* extract tlv data */
- if (get_tlv_data(kc->tlv.p, tlv) < 0) {
+ if (!kc->tlv.p || get_tlv_data(kc->tlv.p, tlv) < 0) {
dev_err(scomp->dev, "error: invalid TLV data\n");
- ret = -EINVAL;
- goto out_free;
+ return -EINVAL;
}
/* set up volume table */
ret = set_up_volume_table(scontrol, tlv, le32_to_cpu(mc->max) + 1);
if (ret < 0) {
dev_err(scomp->dev, "error: setting up volume table\n");
- goto out_free;
- }
-
- /* set default volume values to 0dB in control */
- cdata = scontrol->control_data;
- for (i = 0; i < scontrol->num_channels; i++) {
- cdata->chanv[i].channel = i;
- cdata->chanv[i].value = VOL_ZERO_DB;
+ return ret;
}
skip:
@@ -1065,20 +870,28 @@ skip:
if (ret != 0) {
dev_err(scomp->dev, "error: parse led tokens failed %d\n",
le32_to_cpu(mc->priv.size));
- goto out_free_table;
+ goto err;
+ }
+
+ if (scontrol->led_ctl.use_led) {
+ mask = scontrol->led_ctl.direction ? SNDRV_CTL_ELEM_ACCESS_MIC_LED :
+ SNDRV_CTL_ELEM_ACCESS_SPK_LED;
+ scontrol->access &= ~SNDRV_CTL_ELEM_ACCESS_LED_MASK;
+ scontrol->access |= mask;
+ kc->access &= ~SNDRV_CTL_ELEM_ACCESS_LED_MASK;
+ kc->access |= mask;
+ sdev->led_present = true;
}
dev_dbg(scomp->dev, "tplg: load kcontrol index %d chans %d\n",
scontrol->comp_id, scontrol->num_channels);
- return ret;
+ return 0;
-out_free_table:
+err:
if (le32_to_cpu(mc->max) > 1)
kfree(scontrol->volume_table);
-out_free:
- kfree(scontrol->control_data);
-out:
+
return ret;
}
@@ -1095,18 +908,9 @@ static int sof_control_load_enum(struct snd_soc_component *scomp,
if (le32_to_cpu(ec->num_channels) > SND_SOC_TPLG_MAX_CHAN)
return -EINVAL;
- /* init the enum get/put data */
- scontrol->size = struct_size(scontrol->control_data, chanv,
- le32_to_cpu(ec->num_channels));
- scontrol->control_data = kzalloc(scontrol->size, GFP_KERNEL);
- if (!scontrol->control_data)
- return -ENOMEM;
-
scontrol->comp_id = sdev->next_comp_id;
scontrol->num_channels = le32_to_cpu(ec->num_channels);
- scontrol->cmd = SOF_CTRL_CMD_ENUM;
-
dev_dbg(scomp->dev, "tplg: load kcontrol index %d chans %d comp_id %d\n",
scontrol->comp_id, scontrol->num_channels, scontrol->comp_id);
@@ -1119,70 +923,26 @@ static int sof_control_load_bytes(struct snd_soc_component *scomp,
struct snd_soc_tplg_ctl_hdr *hdr)
{
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct sof_ipc_ctrl_data *cdata;
struct snd_soc_tplg_bytes_control *control =
container_of(hdr, struct snd_soc_tplg_bytes_control, hdr);
struct soc_bytes_ext *sbe = (struct soc_bytes_ext *)kc->private_value;
- int max_size = sbe->max;
- int ret = 0;
-
- /* init the get/put bytes data */
- scontrol->size = sizeof(struct sof_ipc_ctrl_data) +
- le32_to_cpu(control->priv.size);
-
- if (scontrol->size > max_size) {
- dev_err(scomp->dev, "err: bytes data size %d exceeds max %d.\n",
- scontrol->size, max_size);
- ret = -EINVAL;
- goto out;
- }
-
- scontrol->control_data = kzalloc(max_size, GFP_KERNEL);
- cdata = scontrol->control_data;
- if (!scontrol->control_data) {
- ret = -ENOMEM;
- goto out;
- }
+ size_t priv_size = le32_to_cpu(control->priv.size);
+ scontrol->max_size = sbe->max;
scontrol->comp_id = sdev->next_comp_id;
- scontrol->cmd = SOF_CTRL_CMD_BINARY;
- dev_dbg(scomp->dev, "tplg: load kcontrol index %d chans %d\n",
- scontrol->comp_id, scontrol->num_channels);
+ dev_dbg(scomp->dev, "tplg: load kcontrol index %d\n", scontrol->comp_id);
- if (le32_to_cpu(control->priv.size) > 0) {
- memcpy(cdata->data, control->priv.data,
- le32_to_cpu(control->priv.size));
+ /* copy the private data */
+ if (priv_size > 0) {
+ scontrol->priv = kmemdup(control->priv.data, priv_size, GFP_KERNEL);
+ if (!scontrol->priv)
+ return -ENOMEM;
- if (cdata->data->magic != SOF_ABI_MAGIC) {
- dev_err(scomp->dev, "error: Wrong ABI magic 0x%08x.\n",
- cdata->data->magic);
- ret = -EINVAL;
- goto out_free;
- }
- if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION,
- cdata->data->abi)) {
- dev_err(scomp->dev,
- "error: Incompatible ABI version 0x%08x.\n",
- cdata->data->abi);
- ret = -EINVAL;
- goto out_free;
- }
- if (cdata->data->size + sizeof(const struct sof_abi_hdr) !=
- le32_to_cpu(control->priv.size)) {
- dev_err(scomp->dev,
- "error: Conflict in bytes vs. priv size.\n");
- ret = -EINVAL;
- goto out_free;
- }
+ scontrol->priv_size = priv_size;
}
- return ret;
-
-out_free:
- kfree(scontrol->control_data);
-out:
- return ret;
+ return 0;
}
/* external kcontrol init - used for any driver specific init */
@@ -1196,7 +956,7 @@ static int sof_control_load(struct snd_soc_component *scomp, int index,
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct snd_soc_dobj *dobj;
struct snd_sof_control *scontrol;
- int ret = -EINVAL;
+ int ret;
dev_dbg(scomp->dev, "tplg: load control type %d name : %s\n",
hdr->type, hdr->name);
@@ -1205,7 +965,16 @@ static int sof_control_load(struct snd_soc_component *scomp, int index,
if (!scontrol)
return -ENOMEM;
+ scontrol->name = kstrdup(hdr->name, GFP_KERNEL);
+ if (!scontrol->name) {
+ kfree(scontrol);
+ return -ENOMEM;
+ }
+
scontrol->scomp = scomp;
+ scontrol->access = kc->access;
+ scontrol->info_type = le32_to_cpu(hdr->ops.info);
+ scontrol->index = kc->index;
switch (le32_to_cpu(hdr->ops.info)) {
case SND_SOC_TPLG_CTL_VOLSW:
@@ -1236,11 +1005,13 @@ static int sof_control_load(struct snd_soc_component *scomp, int index,
default:
dev_warn(scomp->dev, "control type not supported %d:%d:%d\n",
hdr->ops.get, hdr->ops.put, hdr->ops.info);
+ kfree(scontrol->name);
kfree(scontrol);
return 0;
}
if (ret < 0) {
+ kfree(scontrol->name);
kfree(scontrol);
return ret;
}
@@ -1249,29 +1020,33 @@ static int sof_control_load(struct snd_soc_component *scomp, int index,
dobj->private = scontrol;
list_add(&scontrol->list, &sdev->kcontrol_list);
- return ret;
+ return 0;
}
static int sof_control_unload(struct snd_soc_component *scomp,
struct snd_soc_dobj *dobj)
{
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct sof_ipc_free fcomp;
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
struct snd_sof_control *scontrol = dobj->private;
+ int ret = 0;
- dev_dbg(scomp->dev, "tplg: unload control name : %s\n", scomp->name);
+ dev_dbg(scomp->dev, "tplg: unload control name : %s\n", scontrol->name);
- fcomp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_FREE;
- fcomp.hdr.size = sizeof(fcomp);
- fcomp.id = scontrol->comp_id;
+ if (tplg_ops && tplg_ops->control_free) {
+ ret = tplg_ops->control_free(sdev, scontrol);
+ if (ret < 0)
+ dev_err(scomp->dev, "failed to free control: %s\n", scontrol->name);
+ }
- kfree(scontrol->control_data);
+ /* free all data before returning in case of error too */
+ kfree(scontrol->ipc_control_data);
+ kfree(scontrol->priv);
+ kfree(scontrol->name);
list_del(&scontrol->list);
kfree(scontrol);
- /* send IPC to the DSP */
- return sof_ipc_tx_message(sdev->ipc,
- fcomp.hdr.cmd, &fcomp, sizeof(fcomp),
- NULL, 0);
+
+ return ret;
}
/*
@@ -1286,69 +1061,49 @@ static int sof_connect_dai_widget(struct snd_soc_component *scomp,
struct snd_soc_card *card = scomp->card;
struct snd_soc_pcm_runtime *rtd;
struct snd_soc_dai *cpu_dai;
+ int stream;
int i;
- list_for_each_entry(rtd, &card->rtd_list, list) {
- dev_vdbg(scomp->dev, "tplg: check widget: %s stream: %s dai stream: %s\n",
- w->name, w->sname, rtd->dai_link->stream_name);
+ if (!w->sname) {
+ dev_err(scomp->dev, "Widget %s does not have stream\n", w->name);
+ return -EINVAL;
+ }
- if (!w->sname || !rtd->dai_link->stream_name)
- continue;
+ if (w->id == snd_soc_dapm_dai_out)
+ stream = SNDRV_PCM_STREAM_CAPTURE;
+ else if (w->id == snd_soc_dapm_dai_in)
+ stream = SNDRV_PCM_STREAM_PLAYBACK;
+ else
+ goto end;
+ list_for_each_entry(rtd, &card->rtd_list, list) {
/* does stream match DAI link ? */
- if (strcmp(w->sname, rtd->dai_link->stream_name))
+ if (!rtd->dai_link->stream_name ||
+ !strstr(rtd->dai_link->stream_name, w->sname))
continue;
- switch (w->id) {
- case snd_soc_dapm_dai_out:
- for_each_rtd_cpu_dais(rtd, i, cpu_dai) {
- /*
- * Please create DAI widget in the right order
- * to ensure BE will connect to the right DAI
- * widget.
- */
- if (!cpu_dai->capture_widget) {
- cpu_dai->capture_widget = w;
- break;
- }
- }
- if (i == rtd->num_cpus) {
- dev_err(scomp->dev, "error: can't find BE for DAI %s\n",
- w->name);
-
- return -EINVAL;
- }
- dai->name = rtd->dai_link->name;
- dev_dbg(scomp->dev, "tplg: connected widget %s -> DAI link %s\n",
- w->name, rtd->dai_link->name);
- break;
- case snd_soc_dapm_dai_in:
- for_each_rtd_cpu_dais(rtd, i, cpu_dai) {
- /*
- * Please create DAI widget in the right order
- * to ensure BE will connect to the right DAI
- * widget.
- */
- if (!cpu_dai->playback_widget) {
- cpu_dai->playback_widget = w;
- break;
- }
+ for_each_rtd_cpu_dais(rtd, i, cpu_dai) {
+ /*
+ * Please create DAI widget in the right order
+ * to ensure BE will connect to the right DAI
+ * widget.
+ */
+ if (!snd_soc_dai_get_widget(cpu_dai, stream)) {
+ snd_soc_dai_set_widget(cpu_dai, stream, w);
+ break;
}
- if (i == rtd->num_cpus) {
- dev_err(scomp->dev, "error: can't find BE for DAI %s\n",
- w->name);
+ }
+ if (i == rtd->dai_link->num_cpus) {
+ dev_err(scomp->dev, "error: can't find BE for DAI %s\n", w->name);
- return -EINVAL;
- }
- dai->name = rtd->dai_link->name;
- dev_dbg(scomp->dev, "tplg: connected widget %s -> DAI link %s\n",
- w->name, rtd->dai_link->name);
- break;
- default:
- break;
+ return -EINVAL;
}
- }
+ dai->name = rtd->dai_link->name;
+ dev_dbg(scomp->dev, "tplg: connected widget %s -> DAI link %s\n",
+ w->name, rtd->dai_link->name);
+ }
+end:
/* check we have a connection */
if (!dai->name) {
dev_err(scomp->dev, "error: can't connect DAI %s stream %s\n",
@@ -1359,116 +1114,49 @@ static int sof_connect_dai_widget(struct snd_soc_component *scomp,
return 0;
}
-static int sof_widget_load_dai(struct snd_soc_component *scomp, int index,
- struct snd_sof_widget *swidget,
- struct snd_soc_tplg_dapm_widget *tw,
- struct sof_ipc_comp_reply *r,
- struct snd_sof_dai *dai)
-{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct snd_soc_tplg_private *private = &tw->priv;
- struct sof_ipc_comp_dai comp_dai;
- int ret;
-
- /* configure dai IPC message */
- memset(&comp_dai, 0, sizeof(comp_dai));
- comp_dai.comp.hdr.size = sizeof(comp_dai);
- comp_dai.comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW;
- comp_dai.comp.id = swidget->comp_id;
- comp_dai.comp.type = SOF_COMP_DAI;
- comp_dai.comp.pipeline_id = index;
- comp_dai.config.hdr.size = sizeof(comp_dai.config);
-
- ret = sof_parse_tokens(scomp, &comp_dai, dai_tokens,
- ARRAY_SIZE(dai_tokens), private->array,
- le32_to_cpu(private->size));
- if (ret != 0) {
- dev_err(scomp->dev, "error: parse dai tokens failed %d\n",
- le32_to_cpu(private->size));
- return ret;
- }
-
- ret = sof_parse_tokens(scomp, &comp_dai.config, comp_tokens,
- ARRAY_SIZE(comp_tokens), private->array,
- le32_to_cpu(private->size));
- if (ret != 0) {
- dev_err(scomp->dev, "error: parse dai.cfg tokens failed %d\n",
- private->size);
- return ret;
- }
-
- dev_dbg(scomp->dev, "dai %s: type %d index %d\n",
- swidget->widget->name, comp_dai.type, comp_dai.dai_index);
- sof_dbg_comp_config(scomp, &comp_dai.config);
-
- ret = sof_ipc_tx_message(sdev->ipc, comp_dai.comp.hdr.cmd,
- &comp_dai, sizeof(comp_dai), r, sizeof(*r));
-
- if (ret == 0 && dai) {
- dai->scomp = scomp;
- memcpy(&dai->comp_dai, &comp_dai, sizeof(comp_dai));
- }
-
- return ret;
-}
-
-/*
- * Buffer topology
- */
-
-static int sof_widget_load_buffer(struct snd_soc_component *scomp, int index,
- struct snd_sof_widget *swidget,
- struct snd_soc_tplg_dapm_widget *tw,
- struct sof_ipc_comp_reply *r)
+static void sof_disconnect_dai_widget(struct snd_soc_component *scomp,
+ struct snd_soc_dapm_widget *w)
{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct snd_soc_tplg_private *private = &tw->priv;
- struct sof_ipc_buffer *buffer;
- int ret;
-
- buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
- if (!buffer)
- return -ENOMEM;
+ struct snd_soc_card *card = scomp->card;
+ struct snd_soc_pcm_runtime *rtd;
+ const char *sname = w->sname;
+ struct snd_soc_dai *cpu_dai;
+ int i, stream;
- /* configure dai IPC message */
- buffer->comp.hdr.size = sizeof(*buffer);
- buffer->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_BUFFER_NEW;
- buffer->comp.id = swidget->comp_id;
- buffer->comp.type = SOF_COMP_BUFFER;
- buffer->comp.pipeline_id = index;
+ if (!sname)
+ return;
- ret = sof_parse_tokens(scomp, buffer, buffer_tokens,
- ARRAY_SIZE(buffer_tokens), private->array,
- le32_to_cpu(private->size));
- if (ret != 0) {
- dev_err(scomp->dev, "error: parse buffer tokens failed %d\n",
- private->size);
- kfree(buffer);
- return ret;
- }
-
- dev_dbg(scomp->dev, "buffer %s: size %d caps 0x%x\n",
- swidget->widget->name, buffer->size, buffer->caps);
+ if (w->id == snd_soc_dapm_dai_out)
+ stream = SNDRV_PCM_STREAM_CAPTURE;
+ else if (w->id == snd_soc_dapm_dai_in)
+ stream = SNDRV_PCM_STREAM_PLAYBACK;
+ else
+ return;
- swidget->private = buffer;
+ list_for_each_entry(rtd, &card->rtd_list, list) {
+ /* does stream match DAI link ? */
+ if (!rtd->dai_link->stream_name ||
+ !strstr(rtd->dai_link->stream_name, sname))
+ continue;
- ret = sof_ipc_tx_message(sdev->ipc, buffer->comp.hdr.cmd, buffer,
- sizeof(*buffer), r, sizeof(*r));
- if (ret < 0) {
- dev_err(scomp->dev, "error: buffer %s load failed\n",
- swidget->widget->name);
- kfree(buffer);
+ for_each_rtd_cpu_dais(rtd, i, cpu_dai)
+ if (snd_soc_dai_get_widget(cpu_dai, stream) == w) {
+ snd_soc_dai_set_widget(cpu_dai, stream, NULL);
+ break;
+ }
}
-
- return ret;
}
/* bind PCM ID to host component ID */
static int spcm_bind(struct snd_soc_component *scomp, struct snd_sof_pcm *spcm,
int dir)
{
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct snd_sof_widget *host_widget;
+ if (sdev->dspless_mode_selected)
+ return 0;
+
host_widget = snd_sof_find_swidget_sname(scomp,
spcm->pcm.caps[dir].name,
dir);
@@ -1482,798 +1170,218 @@ static int spcm_bind(struct snd_soc_component *scomp, struct snd_sof_pcm *spcm,
return 0;
}
-/*
- * PCM Topology
- */
-
-static int sof_widget_load_pcm(struct snd_soc_component *scomp, int index,
- struct snd_sof_widget *swidget,
- enum sof_ipc_stream_direction dir,
- struct snd_soc_tplg_dapm_widget *tw,
- struct sof_ipc_comp_reply *r)
-{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct snd_soc_tplg_private *private = &tw->priv;
- struct sof_ipc_comp_host *host;
- int ret;
-
- host = kzalloc(sizeof(*host), GFP_KERNEL);
- if (!host)
- return -ENOMEM;
-
- /* configure host comp IPC message */
- host->comp.hdr.size = sizeof(*host);
- host->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW;
- host->comp.id = swidget->comp_id;
- host->comp.type = SOF_COMP_HOST;
- host->comp.pipeline_id = index;
- host->direction = dir;
- host->config.hdr.size = sizeof(host->config);
-
- ret = sof_parse_tokens(scomp, host, pcm_tokens,
- ARRAY_SIZE(pcm_tokens), private->array,
- le32_to_cpu(private->size));
- if (ret != 0) {
- dev_err(scomp->dev, "error: parse host tokens failed %d\n",
- private->size);
- goto err;
- }
-
- ret = sof_parse_tokens(scomp, &host->config, comp_tokens,
- ARRAY_SIZE(comp_tokens), private->array,
- le32_to_cpu(private->size));
- if (ret != 0) {
- dev_err(scomp->dev, "error: parse host.cfg tokens failed %d\n",
- le32_to_cpu(private->size));
- goto err;
- }
-
- dev_dbg(scomp->dev, "loaded host %s\n", swidget->widget->name);
- sof_dbg_comp_config(scomp, &host->config);
-
- swidget->private = host;
-
- ret = sof_ipc_tx_message(sdev->ipc, host->comp.hdr.cmd, host,
- sizeof(*host), r, sizeof(*r));
- if (ret >= 0)
- return ret;
-err:
- kfree(host);
- return ret;
-}
-
-/*
- * Pipeline Topology
- */
-int sof_load_pipeline_ipc(struct device *dev,
- struct sof_ipc_pipe_new *pipeline,
- struct sof_ipc_comp_reply *r)
-{
- struct snd_sof_dev *sdev = dev_get_drvdata(dev);
- struct sof_ipc_pm_core_config pm_core_config;
- int ret;
-
- ret = sof_ipc_tx_message(sdev->ipc, pipeline->hdr.cmd, pipeline,
- sizeof(*pipeline), r, sizeof(*r));
- if (ret < 0) {
- dev_err(dev, "error: load pipeline ipc failure\n");
- return ret;
- }
-
- /* power up the core that this pipeline is scheduled on */
- ret = snd_sof_dsp_core_power_up(sdev, 1 << pipeline->core);
- if (ret < 0) {
- dev_err(dev, "error: powering up pipeline schedule core %d\n",
- pipeline->core);
- return ret;
- }
-
- /* update enabled cores mask */
- sdev->enabled_cores_mask |= 1 << pipeline->core;
-
- /*
- * Now notify DSP that the core that this pipeline is scheduled on
- * has been powered up
- */
- memset(&pm_core_config, 0, sizeof(pm_core_config));
- pm_core_config.enable_mask = sdev->enabled_cores_mask;
-
- /* configure CORE_ENABLE ipc message */
- pm_core_config.hdr.size = sizeof(pm_core_config);
- pm_core_config.hdr.cmd = SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CORE_ENABLE;
-
- /* send ipc */
- ret = sof_ipc_tx_message(sdev->ipc, pm_core_config.hdr.cmd,
- &pm_core_config, sizeof(pm_core_config),
- &pm_core_config, sizeof(pm_core_config));
- if (ret < 0)
- dev_err(dev, "error: core enable ipc failure\n");
-
- return ret;
-}
-
-static int sof_widget_load_pipeline(struct snd_soc_component *scomp,
- int index, struct snd_sof_widget *swidget,
- struct snd_soc_tplg_dapm_widget *tw,
- struct sof_ipc_comp_reply *r)
+static int sof_get_token_value(u32 token_id, struct snd_sof_tuple *tuples, int num_tuples)
{
- struct snd_soc_tplg_private *private = &tw->priv;
- struct sof_ipc_pipe_new *pipeline;
- struct snd_sof_widget *comp_swidget;
- int ret;
-
- pipeline = kzalloc(sizeof(*pipeline), GFP_KERNEL);
- if (!pipeline)
- return -ENOMEM;
-
- /* configure dai IPC message */
- pipeline->hdr.size = sizeof(*pipeline);
- pipeline->hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_PIPE_NEW;
- pipeline->pipeline_id = index;
- pipeline->comp_id = swidget->comp_id;
-
- /* component at start of pipeline is our stream id */
- comp_swidget = snd_sof_find_swidget(scomp, tw->sname);
- if (!comp_swidget) {
- dev_err(scomp->dev, "error: widget %s refers to non existent widget %s\n",
- tw->name, tw->sname);
- ret = -EINVAL;
- goto err;
- }
-
- pipeline->sched_id = comp_swidget->comp_id;
-
- dev_dbg(scomp->dev, "tplg: pipeline id %d comp %d scheduling comp id %d\n",
- pipeline->pipeline_id, pipeline->comp_id, pipeline->sched_id);
-
- ret = sof_parse_tokens(scomp, pipeline, sched_tokens,
- ARRAY_SIZE(sched_tokens), private->array,
- le32_to_cpu(private->size));
- if (ret != 0) {
- dev_err(scomp->dev, "error: parse pipeline tokens failed %d\n",
- private->size);
- goto err;
- }
-
- dev_dbg(scomp->dev, "pipeline %s: period %d pri %d mips %d core %d frames %d\n",
- swidget->widget->name, pipeline->period, pipeline->priority,
- pipeline->period_mips, pipeline->core, pipeline->frames_per_sched);
-
- swidget->private = pipeline;
-
- /* send ipc's to create pipeline comp and power up schedule core */
- ret = sof_load_pipeline_ipc(scomp->dev, pipeline, r);
- if (ret >= 0)
- return ret;
-err:
- kfree(pipeline);
- return ret;
-}
-
-/*
- * Mixer topology
- */
-
-static int sof_widget_load_mixer(struct snd_soc_component *scomp, int index,
- struct snd_sof_widget *swidget,
- struct snd_soc_tplg_dapm_widget *tw,
- struct sof_ipc_comp_reply *r)
-{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct snd_soc_tplg_private *private = &tw->priv;
- struct sof_ipc_comp_mixer *mixer;
- int ret;
-
- mixer = kzalloc(sizeof(*mixer), GFP_KERNEL);
- if (!mixer)
- return -ENOMEM;
+ int i;
- /* configure mixer IPC message */
- mixer->comp.hdr.size = sizeof(*mixer);
- mixer->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW;
- mixer->comp.id = swidget->comp_id;
- mixer->comp.type = SOF_COMP_MIXER;
- mixer->comp.pipeline_id = index;
- mixer->config.hdr.size = sizeof(mixer->config);
+ if (!tuples)
+ return -EINVAL;
- ret = sof_parse_tokens(scomp, &mixer->config, comp_tokens,
- ARRAY_SIZE(comp_tokens), private->array,
- le32_to_cpu(private->size));
- if (ret != 0) {
- dev_err(scomp->dev, "error: parse mixer.cfg tokens failed %d\n",
- private->size);
- kfree(mixer);
- return ret;
+ for (i = 0; i < num_tuples; i++) {
+ if (tuples[i].token == token_id)
+ return tuples[i].value.v;
}
- sof_dbg_comp_config(scomp, &mixer->config);
-
- swidget->private = mixer;
-
- ret = sof_ipc_tx_message(sdev->ipc, mixer->comp.hdr.cmd, mixer,
- sizeof(*mixer), r, sizeof(*r));
- if (ret < 0)
- kfree(mixer);
-
- return ret;
+ return -EINVAL;
}
-/*
- * Mux topology
- */
-static int sof_widget_load_mux(struct snd_soc_component *scomp, int index,
- struct snd_sof_widget *swidget,
- struct snd_soc_tplg_dapm_widget *tw,
- struct sof_ipc_comp_reply *r)
+static int sof_widget_parse_tokens(struct snd_soc_component *scomp, struct snd_sof_widget *swidget,
+ struct snd_soc_tplg_dapm_widget *tw,
+ enum sof_tokens *object_token_list, int count)
{
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
struct snd_soc_tplg_private *private = &tw->priv;
- struct sof_ipc_comp_mux *mux;
- int ret;
-
- mux = kzalloc(sizeof(*mux), GFP_KERNEL);
- if (!mux)
- return -ENOMEM;
+ const struct sof_token_info *token_list;
+ int num_tuples = 0;
+ int ret, i;
- /* configure mux IPC message */
- mux->comp.hdr.size = sizeof(*mux);
- mux->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW;
- mux->comp.id = swidget->comp_id;
- mux->comp.type = SOF_COMP_MUX;
- mux->comp.pipeline_id = index;
- mux->config.hdr.size = sizeof(mux->config);
+ token_list = tplg_ops ? tplg_ops->token_list : NULL;
+ /* nothing to do if token_list is NULL */
+ if (!token_list)
+ return 0;
- ret = sof_parse_tokens(scomp, &mux->config, comp_tokens,
- ARRAY_SIZE(comp_tokens), private->array,
- le32_to_cpu(private->size));
- if (ret != 0) {
- dev_err(scomp->dev, "error: parse mux.cfg tokens failed %d\n",
- private->size);
- kfree(mux);
- return ret;
+ if (count > 0 && !object_token_list) {
+ dev_err(scomp->dev, "No token list for widget %s\n", swidget->widget->name);
+ return -EINVAL;
}
- sof_dbg_comp_config(scomp, &mux->config);
-
- swidget->private = mux;
-
- ret = sof_ipc_tx_message(sdev->ipc, mux->comp.hdr.cmd, mux,
- sizeof(*mux), r, sizeof(*r));
- if (ret < 0)
- kfree(mux);
-
- return ret;
-}
-
-/*
- * PGA Topology
- */
-
-static int sof_widget_load_pga(struct snd_soc_component *scomp, int index,
- struct snd_sof_widget *swidget,
- struct snd_soc_tplg_dapm_widget *tw,
- struct sof_ipc_comp_reply *r)
-{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct snd_soc_tplg_private *private = &tw->priv;
- struct sof_ipc_comp_volume *volume;
- struct snd_sof_control *scontrol;
- int min_step;
- int max_step;
- int ret;
+ /* calculate max size of tuples array */
+ for (i = 0; i < count; i++)
+ num_tuples += token_list[object_token_list[i]].count;
- volume = kzalloc(sizeof(*volume), GFP_KERNEL);
- if (!volume)
+ /* allocate memory for tuples array */
+ swidget->tuples = kcalloc(num_tuples, sizeof(*swidget->tuples), GFP_KERNEL);
+ if (!swidget->tuples)
return -ENOMEM;
- if (!le32_to_cpu(tw->num_kcontrols)) {
- dev_err(scomp->dev, "error: invalid kcontrol count %d for volume\n",
- tw->num_kcontrols);
- ret = -EINVAL;
- goto err;
- }
-
- /* configure volume IPC message */
- volume->comp.hdr.size = sizeof(*volume);
- volume->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW;
- volume->comp.id = swidget->comp_id;
- volume->comp.type = SOF_COMP_VOLUME;
- volume->comp.pipeline_id = index;
- volume->config.hdr.size = sizeof(volume->config);
+ /* parse token list for widget */
+ for (i = 0; i < count; i++) {
+ int num_sets = 1;
- ret = sof_parse_tokens(scomp, volume, volume_tokens,
- ARRAY_SIZE(volume_tokens), private->array,
- le32_to_cpu(private->size));
- if (ret != 0) {
- dev_err(scomp->dev, "error: parse volume tokens failed %d\n",
- private->size);
- goto err;
- }
- ret = sof_parse_tokens(scomp, &volume->config, comp_tokens,
- ARRAY_SIZE(comp_tokens), private->array,
- le32_to_cpu(private->size));
- if (ret != 0) {
- dev_err(scomp->dev, "error: parse volume.cfg tokens failed %d\n",
- le32_to_cpu(private->size));
- goto err;
- }
-
- sof_dbg_comp_config(scomp, &volume->config);
-
- swidget->private = volume;
-
- list_for_each_entry(scontrol, &sdev->kcontrol_list, list) {
- if (scontrol->comp_id == swidget->comp_id &&
- scontrol->volume_table) {
- min_step = scontrol->min_volume_step;
- max_step = scontrol->max_volume_step;
- volume->min_value = scontrol->volume_table[min_step];
- volume->max_value = scontrol->volume_table[max_step];
- volume->channels = scontrol->num_channels;
- break;
+ if (object_token_list[i] >= SOF_TOKEN_COUNT) {
+ dev_err(scomp->dev, "Invalid token id %d for widget %s\n",
+ object_token_list[i], swidget->widget->name);
+ ret = -EINVAL;
+ goto err;
}
- }
-
- ret = sof_ipc_tx_message(sdev->ipc, volume->comp.hdr.cmd, volume,
- sizeof(*volume), r, sizeof(*r));
- if (ret >= 0)
- return ret;
-err:
- kfree(volume);
- return ret;
-}
-
-/*
- * SRC Topology
- */
-
-static int sof_widget_load_src(struct snd_soc_component *scomp, int index,
- struct snd_sof_widget *swidget,
- struct snd_soc_tplg_dapm_widget *tw,
- struct sof_ipc_comp_reply *r)
-{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct snd_soc_tplg_private *private = &tw->priv;
- struct sof_ipc_comp_src *src;
- int ret;
-
- src = kzalloc(sizeof(*src), GFP_KERNEL);
- if (!src)
- return -ENOMEM;
-
- /* configure src IPC message */
- src->comp.hdr.size = sizeof(*src);
- src->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW;
- src->comp.id = swidget->comp_id;
- src->comp.type = SOF_COMP_SRC;
- src->comp.pipeline_id = index;
- src->config.hdr.size = sizeof(src->config);
-
- ret = sof_parse_tokens(scomp, src, src_tokens,
- ARRAY_SIZE(src_tokens), private->array,
- le32_to_cpu(private->size));
- if (ret != 0) {
- dev_err(scomp->dev, "error: parse src tokens failed %d\n",
- private->size);
- goto err;
- }
-
- ret = sof_parse_tokens(scomp, &src->config, comp_tokens,
- ARRAY_SIZE(comp_tokens), private->array,
- le32_to_cpu(private->size));
- if (ret != 0) {
- dev_err(scomp->dev, "error: parse src.cfg tokens failed %d\n",
- le32_to_cpu(private->size));
- goto err;
- }
-
- dev_dbg(scomp->dev, "src %s: source rate %d sink rate %d\n",
- swidget->widget->name, src->source_rate, src->sink_rate);
- sof_dbg_comp_config(scomp, &src->config);
-
- swidget->private = src;
-
- ret = sof_ipc_tx_message(sdev->ipc, src->comp.hdr.cmd, src,
- sizeof(*src), r, sizeof(*r));
- if (ret >= 0)
- return ret;
-err:
- kfree(src);
- return ret;
-}
-
-/*
- * ASRC Topology
- */
-
-static int sof_widget_load_asrc(struct snd_soc_component *scomp, int index,
- struct snd_sof_widget *swidget,
- struct snd_soc_tplg_dapm_widget *tw,
- struct sof_ipc_comp_reply *r)
-{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct snd_soc_tplg_private *private = &tw->priv;
- struct sof_ipc_comp_asrc *asrc;
- int ret;
-
- asrc = kzalloc(sizeof(*asrc), GFP_KERNEL);
- if (!asrc)
- return -ENOMEM;
-
- /* configure ASRC IPC message */
- asrc->comp.hdr.size = sizeof(*asrc);
- asrc->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW;
- asrc->comp.id = swidget->comp_id;
- asrc->comp.type = SOF_COMP_ASRC;
- asrc->comp.pipeline_id = index;
- asrc->config.hdr.size = sizeof(asrc->config);
-
- ret = sof_parse_tokens(scomp, asrc, asrc_tokens,
- ARRAY_SIZE(asrc_tokens), private->array,
- le32_to_cpu(private->size));
- if (ret != 0) {
- dev_err(scomp->dev, "error: parse asrc tokens failed %d\n",
- private->size);
- goto err;
- }
-
- ret = sof_parse_tokens(scomp, &asrc->config, comp_tokens,
- ARRAY_SIZE(comp_tokens), private->array,
- le32_to_cpu(private->size));
- if (ret != 0) {
- dev_err(scomp->dev, "error: parse asrc.cfg tokens failed %d\n",
- le32_to_cpu(private->size));
- goto err;
- }
-
- dev_dbg(scomp->dev, "asrc %s: source rate %d sink rate %d "
- "asynch %d operation %d\n",
- swidget->widget->name, asrc->source_rate, asrc->sink_rate,
- asrc->asynchronous_mode, asrc->operation_mode);
- sof_dbg_comp_config(scomp, &asrc->config);
-
- swidget->private = asrc;
-
- ret = sof_ipc_tx_message(sdev->ipc, asrc->comp.hdr.cmd, asrc,
- sizeof(*asrc), r, sizeof(*r));
- if (ret >= 0)
- return ret;
-err:
- kfree(asrc);
- return ret;
-}
-/*
- * Signal Generator Topology
- */
-
-static int sof_widget_load_siggen(struct snd_soc_component *scomp, int index,
- struct snd_sof_widget *swidget,
- struct snd_soc_tplg_dapm_widget *tw,
- struct sof_ipc_comp_reply *r)
-{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct snd_soc_tplg_private *private = &tw->priv;
- struct sof_ipc_comp_tone *tone;
- int ret;
-
- tone = kzalloc(sizeof(*tone), GFP_KERNEL);
- if (!tone)
- return -ENOMEM;
-
- /* configure siggen IPC message */
- tone->comp.hdr.size = sizeof(*tone);
- tone->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW;
- tone->comp.id = swidget->comp_id;
- tone->comp.type = SOF_COMP_TONE;
- tone->comp.pipeline_id = index;
- tone->config.hdr.size = sizeof(tone->config);
-
- ret = sof_parse_tokens(scomp, tone, tone_tokens,
- ARRAY_SIZE(tone_tokens), private->array,
- le32_to_cpu(private->size));
- if (ret != 0) {
- dev_err(scomp->dev, "error: parse tone tokens failed %d\n",
- le32_to_cpu(private->size));
- goto err;
- }
-
- ret = sof_parse_tokens(scomp, &tone->config, comp_tokens,
- ARRAY_SIZE(comp_tokens), private->array,
- le32_to_cpu(private->size));
- if (ret != 0) {
- dev_err(scomp->dev, "error: parse tone.cfg tokens failed %d\n",
- le32_to_cpu(private->size));
- goto err;
- }
-
- dev_dbg(scomp->dev, "tone %s: frequency %d amplitude %d\n",
- swidget->widget->name, tone->frequency, tone->amplitude);
- sof_dbg_comp_config(scomp, &tone->config);
-
- swidget->private = tone;
-
- ret = sof_ipc_tx_message(sdev->ipc, tone->comp.hdr.cmd, tone,
- sizeof(*tone), r, sizeof(*r));
- if (ret >= 0)
- return ret;
-err:
- kfree(tone);
- return ret;
-}
-
-static int sof_get_control_data(struct snd_soc_component *scomp,
- struct snd_soc_dapm_widget *widget,
- struct sof_widget_data *wdata,
- size_t *size)
-{
- const struct snd_kcontrol_new *kc;
- struct soc_mixer_control *sm;
- struct soc_bytes_ext *sbe;
- struct soc_enum *se;
- int i;
-
- *size = 0;
-
- for (i = 0; i < widget->num_kcontrols; i++) {
- kc = &widget->kcontrol_news[i];
+ switch (object_token_list[i]) {
+ case SOF_COMP_EXT_TOKENS:
+ /* parse and save UUID in swidget */
+ ret = sof_parse_tokens(scomp, swidget,
+ token_list[object_token_list[i]].tokens,
+ token_list[object_token_list[i]].count,
+ private->array, le32_to_cpu(private->size));
+ if (ret < 0) {
+ dev_err(scomp->dev, "Failed parsing %s for widget %s\n",
+ token_list[object_token_list[i]].name,
+ swidget->widget->name);
+ goto err;
+ }
- switch (widget->dobj.widget.kcontrol_type) {
- case SND_SOC_TPLG_TYPE_MIXER:
- sm = (struct soc_mixer_control *)kc->private_value;
- wdata[i].control = sm->dobj.private;
- break;
- case SND_SOC_TPLG_TYPE_BYTES:
- sbe = (struct soc_bytes_ext *)kc->private_value;
- wdata[i].control = sbe->dobj.private;
+ continue;
+ case SOF_IN_AUDIO_FORMAT_TOKENS:
+ num_sets = sof_get_token_value(SOF_TKN_COMP_NUM_INPUT_AUDIO_FORMATS,
+ swidget->tuples, swidget->num_tuples);
+ if (num_sets < 0) {
+ dev_err(sdev->dev, "Invalid input audio format count for %s\n",
+ swidget->widget->name);
+ ret = num_sets;
+ goto err;
+ }
break;
- case SND_SOC_TPLG_TYPE_ENUM:
- se = (struct soc_enum *)kc->private_value;
- wdata[i].control = se->dobj.private;
+ case SOF_OUT_AUDIO_FORMAT_TOKENS:
+ num_sets = sof_get_token_value(SOF_TKN_COMP_NUM_OUTPUT_AUDIO_FORMATS,
+ swidget->tuples, swidget->num_tuples);
+ if (num_sets < 0) {
+ dev_err(sdev->dev, "Invalid output audio format count for %s\n",
+ swidget->widget->name);
+ ret = num_sets;
+ goto err;
+ }
break;
default:
- dev_err(scomp->dev, "error: unknown kcontrol type %d in widget %s\n",
- widget->dobj.widget.kcontrol_type,
- widget->name);
- return -EINVAL;
- }
-
- if (!wdata[i].control) {
- dev_err(scomp->dev, "error: no scontrol for widget %s\n",
- widget->name);
- return -EINVAL;
+ break;
}
- wdata[i].pdata = wdata[i].control->control_data->data;
- if (!wdata[i].pdata)
- return -EINVAL;
+ if (num_sets > 1) {
+ struct snd_sof_tuple *new_tuples;
- /* make sure data is valid - data can be updated at runtime */
- if (wdata[i].pdata->magic != SOF_ABI_MAGIC)
- return -EINVAL;
+ num_tuples += token_list[object_token_list[i]].count * (num_sets - 1);
+ new_tuples = krealloc(swidget->tuples,
+ sizeof(*new_tuples) * num_tuples, GFP_KERNEL);
+ if (!new_tuples) {
+ ret = -ENOMEM;
+ goto err;
+ }
- *size += wdata[i].pdata->size;
+ swidget->tuples = new_tuples;
+ }
- /* get data type */
- switch (wdata[i].control->cmd) {
- case SOF_CTRL_CMD_VOLUME:
- case SOF_CTRL_CMD_ENUM:
- case SOF_CTRL_CMD_SWITCH:
- wdata[i].ipc_cmd = SOF_IPC_COMP_SET_VALUE;
- wdata[i].ctrl_type = SOF_CTRL_TYPE_VALUE_CHAN_SET;
- break;
- case SOF_CTRL_CMD_BINARY:
- wdata[i].ipc_cmd = SOF_IPC_COMP_SET_DATA;
- wdata[i].ctrl_type = SOF_CTRL_TYPE_DATA_SET;
- break;
- default:
- break;
+ /* copy one set of tuples per token ID into swidget->tuples */
+ ret = sof_copy_tuples(sdev, private->array, le32_to_cpu(private->size),
+ object_token_list[i], num_sets, swidget->tuples,
+ num_tuples, &swidget->num_tuples);
+ if (ret < 0) {
+ dev_err(scomp->dev, "Failed parsing %s for widget %s err: %d\n",
+ token_list[object_token_list[i]].name, swidget->widget->name, ret);
+ goto err;
}
}
return 0;
+err:
+ kfree(swidget->tuples);
+ return ret;
}
-static int sof_process_load(struct snd_soc_component *scomp, int index,
- struct snd_sof_widget *swidget,
- struct snd_soc_tplg_dapm_widget *tw,
- struct sof_ipc_comp_reply *r,
- int type)
+static void sof_free_pin_binding(struct snd_sof_widget *swidget,
+ bool pin_type)
{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct snd_soc_dapm_widget *widget = swidget->widget;
- struct snd_soc_tplg_private *private = &tw->priv;
- struct sof_ipc_comp_process *process = NULL;
- struct sof_widget_data *wdata = NULL;
- size_t ipc_data_size = 0;
- size_t ipc_size;
- int offset = 0;
- int ret = 0;
+ char **pin_binding;
+ u32 num_pins;
int i;
- if (type == SOF_COMP_NONE) {
- dev_err(scomp->dev, "error: invalid process comp type %d\n",
- type);
- return -EINVAL;
+ if (pin_type == SOF_PIN_TYPE_INPUT) {
+ pin_binding = swidget->input_pin_binding;
+ num_pins = swidget->num_input_pins;
+ } else {
+ pin_binding = swidget->output_pin_binding;
+ num_pins = swidget->num_output_pins;
}
- /* allocate struct for widget control data sizes and types */
- if (widget->num_kcontrols) {
- wdata = kcalloc(widget->num_kcontrols,
- sizeof(*wdata),
- GFP_KERNEL);
-
- if (!wdata)
- return -ENOMEM;
-
- /* get possible component controls and get size of all pdata */
- ret = sof_get_control_data(scomp, widget, wdata,
- &ipc_data_size);
-
- if (ret < 0)
- goto out;
+ if (pin_binding) {
+ for (i = 0; i < num_pins; i++)
+ kfree(pin_binding[i]);
}
- ipc_size = sizeof(struct sof_ipc_comp_process) +
- le32_to_cpu(private->size) +
- ipc_data_size;
+ kfree(pin_binding);
+}
- /* we are exceeding max ipc size, config needs to be sent separately */
- if (ipc_size > SOF_IPC_MSG_MAX_SIZE) {
- ipc_size -= ipc_data_size;
- ipc_data_size = 0;
- }
+static int sof_parse_pin_binding(struct snd_sof_widget *swidget,
+ struct snd_soc_tplg_private *priv, bool pin_type)
+{
+ const struct sof_topology_token *pin_binding_token;
+ char *pin_binding[SOF_WIDGET_MAX_NUM_PINS];
+ int token_count;
+ u32 num_pins;
+ char **pb;
+ int ret;
+ int i;
- process = kzalloc(ipc_size, GFP_KERNEL);
- if (!process) {
- ret = -ENOMEM;
- goto out;
+ if (pin_type == SOF_PIN_TYPE_INPUT) {
+ num_pins = swidget->num_input_pins;
+ pin_binding_token = comp_input_pin_binding_tokens;
+ token_count = ARRAY_SIZE(comp_input_pin_binding_tokens);
+ } else {
+ num_pins = swidget->num_output_pins;
+ pin_binding_token = comp_output_pin_binding_tokens;
+ token_count = ARRAY_SIZE(comp_output_pin_binding_tokens);
}
- /* configure iir IPC message */
- process->comp.hdr.size = ipc_size;
- process->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW;
- process->comp.id = swidget->comp_id;
- process->comp.type = type;
- process->comp.pipeline_id = index;
- process->config.hdr.size = sizeof(process->config);
-
- ret = sof_parse_tokens(scomp, &process->config, comp_tokens,
- ARRAY_SIZE(comp_tokens), private->array,
- le32_to_cpu(private->size));
- if (ret != 0) {
- dev_err(scomp->dev, "error: parse process.cfg tokens failed %d\n",
- le32_to_cpu(private->size));
+ memset(pin_binding, 0, SOF_WIDGET_MAX_NUM_PINS * sizeof(char *));
+ ret = sof_parse_token_sets(swidget->scomp, pin_binding, pin_binding_token,
+ token_count, priv->array, le32_to_cpu(priv->size),
+ num_pins, sizeof(char *));
+ if (ret < 0)
goto err;
- }
- sof_dbg_comp_config(scomp, &process->config);
-
- /*
- * found private data in control, so copy it.
- * get possible component controls - get size of all pdata,
- * then memcpy with headers
- */
- if (ipc_data_size) {
- for (i = 0; i < widget->num_kcontrols; i++) {
- memcpy(&process->data + offset,
- wdata[i].pdata->data,
- wdata[i].pdata->size);
- offset += wdata[i].pdata->size;
+ /* copy pin binding array to swidget only if it is defined in topology */
+ if (pin_binding[0]) {
+ pb = kmemdup(pin_binding, num_pins * sizeof(char *), GFP_KERNEL);
+ if (!pb) {
+ ret = -ENOMEM;
+ goto err;
}
+ if (pin_type == SOF_PIN_TYPE_INPUT)
+ swidget->input_pin_binding = pb;
+ else
+ swidget->output_pin_binding = pb;
}
- process->size = ipc_data_size;
- swidget->private = process;
-
- ret = sof_ipc_tx_message(sdev->ipc, process->comp.hdr.cmd, process,
- ipc_size, r, sizeof(*r));
-
- if (ret < 0) {
- dev_err(scomp->dev, "error: create process failed\n");
- goto err;
- }
-
- /* we sent the data in single message so return */
- if (ipc_data_size)
- goto out;
-
- /* send control data with large message supported method */
- for (i = 0; i < widget->num_kcontrols; i++) {
- wdata[i].control->readback_offset = 0;
- ret = snd_sof_ipc_set_get_comp_data(wdata[i].control,
- wdata[i].ipc_cmd,
- wdata[i].ctrl_type,
- wdata[i].control->cmd,
- true);
- if (ret != 0) {
- dev_err(scomp->dev, "error: send control failed\n");
- break;
- }
- }
+ return 0;
err:
- if (ret < 0)
- kfree(process);
-out:
- kfree(wdata);
+ for (i = 0; i < num_pins; i++)
+ kfree(pin_binding[i]);
+
return ret;
}
-/*
- * Processing Component Topology - can be "effect", "codec", or general
- * "processing".
- */
-
-static int sof_widget_load_process(struct snd_soc_component *scomp, int index,
- struct snd_sof_widget *swidget,
- struct snd_soc_tplg_dapm_widget *tw,
- struct sof_ipc_comp_reply *r)
+static int get_w_no_wname_in_long_name(void *elem, void *object, u32 offset)
{
- struct snd_soc_tplg_private *private = &tw->priv;
- struct sof_ipc_comp_process config;
- int ret;
-
- /* check we have some tokens - we need at least process type */
- if (le32_to_cpu(private->size) == 0) {
- dev_err(scomp->dev, "error: process tokens not found\n");
- return -EINVAL;
- }
-
- memset(&config, 0, sizeof(config));
-
- /* get the process token */
- ret = sof_parse_tokens(scomp, &config, process_tokens,
- ARRAY_SIZE(process_tokens), private->array,
- le32_to_cpu(private->size));
- if (ret != 0) {
- dev_err(scomp->dev, "error: parse process tokens failed %d\n",
- le32_to_cpu(private->size));
- return ret;
- }
-
- /* now load process specific data and send IPC */
- ret = sof_process_load(scomp, index, swidget, tw, r,
- find_process_comp_type(config.type));
- if (ret < 0) {
- dev_err(scomp->dev, "error: process loading failed\n");
- return ret;
- }
+ struct snd_soc_tplg_vendor_value_elem *velem = elem;
+ struct snd_soc_dapm_widget *w = object;
+ w->no_wname_in_kcontrol_name = !!le32_to_cpu(velem->value);
return 0;
}
-static int sof_widget_bind_event(struct snd_soc_component *scomp,
- struct snd_sof_widget *swidget,
- u16 event_type)
-{
- struct sof_ipc_comp *ipc_comp;
-
- /* validate widget event type */
- switch (event_type) {
- case SOF_KEYWORD_DETECT_DAPM_EVENT:
- /* only KEYWORD_DETECT comps should handle this */
- if (swidget->id != snd_soc_dapm_effect)
- break;
-
- ipc_comp = swidget->private;
- if (ipc_comp && ipc_comp->type != SOF_COMP_KEYWORD_DETECT)
- break;
-
- /* bind event to keyword detect comp */
- return snd_soc_tplg_widget_bind_event(swidget->widget,
- sof_kwd_events,
- ARRAY_SIZE(sof_kwd_events),
- event_type);
- default:
- break;
- }
-
- dev_err(scomp->dev,
- "error: invalid event type %d for widget %s\n",
- event_type, swidget->widget->name);
- return -EINVAL;
-}
+static const struct sof_topology_token dapm_widget_tokens[] = {
+ {SOF_TKN_COMP_NO_WNAME_IN_KCONTROL_NAME, SND_SOC_TPLG_TUPLE_TYPE_BOOL,
+ get_w_no_wname_in_long_name, 0}
+};
/* external widget init - used for any driver specific init */
static int sof_widget_ready(struct snd_soc_component *scomp, int index,
@@ -2281,10 +1389,13 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index,
struct snd_soc_tplg_dapm_widget *tw)
{
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
+ const struct sof_ipc_tplg_widget_ops *widget_ops;
+ struct snd_soc_tplg_private *priv = &tw->priv;
+ enum sof_tokens *token_list = NULL;
struct snd_sof_widget *swidget;
struct snd_sof_dai *dai;
- struct sof_ipc_comp_reply reply;
- struct snd_sof_control *scontrol;
+ int token_list_size = 0;
int ret = 0;
swidget = kzalloc(sizeof(*swidget), GFP_KERNEL);
@@ -2294,16 +1405,70 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index,
swidget->scomp = scomp;
swidget->widget = w;
swidget->comp_id = sdev->next_comp_id++;
- swidget->complete = 0;
swidget->id = w->id;
swidget->pipeline_id = index;
swidget->private = NULL;
- memset(&reply, 0, sizeof(reply));
+ mutex_init(&swidget->setup_mutex);
+
+ ida_init(&swidget->output_queue_ida);
+ ida_init(&swidget->input_queue_ida);
+
+ ret = sof_parse_tokens(scomp, w, dapm_widget_tokens, ARRAY_SIZE(dapm_widget_tokens),
+ priv->array, le32_to_cpu(priv->size));
+ if (ret < 0) {
+ dev_err(scomp->dev, "failed to parse dapm widget tokens for %s\n",
+ w->name);
+ goto widget_free;
+ }
+
+ ret = sof_parse_tokens(scomp, swidget, comp_pin_tokens,
+ ARRAY_SIZE(comp_pin_tokens), priv->array,
+ le32_to_cpu(priv->size));
+ if (ret < 0) {
+ dev_err(scomp->dev, "failed to parse component pin tokens for %s\n",
+ w->name);
+ goto widget_free;
+ }
- dev_dbg(scomp->dev, "tplg: ready widget id %d pipe %d type %d name : %s stream %s\n",
- swidget->comp_id, index, swidget->id, tw->name,
- strnlen(tw->sname, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) > 0
- ? tw->sname : "none");
+ if (swidget->num_input_pins > SOF_WIDGET_MAX_NUM_PINS ||
+ swidget->num_output_pins > SOF_WIDGET_MAX_NUM_PINS) {
+ dev_err(scomp->dev, "invalid pins for %s: [input: %d, output: %d]\n",
+ swidget->widget->name, swidget->num_input_pins, swidget->num_output_pins);
+ ret = -EINVAL;
+ goto widget_free;
+ }
+
+ if (swidget->num_input_pins > 1) {
+ ret = sof_parse_pin_binding(swidget, priv, SOF_PIN_TYPE_INPUT);
+ /* on parsing error, pin binding is not allocated, nothing to free. */
+ if (ret < 0) {
+ dev_err(scomp->dev, "failed to parse input pin binding for %s\n",
+ w->name);
+ goto widget_free;
+ }
+ }
+
+ if (swidget->num_output_pins > 1) {
+ ret = sof_parse_pin_binding(swidget, priv, SOF_PIN_TYPE_OUTPUT);
+ /* on parsing error, pin binding is not allocated, nothing to free. */
+ if (ret < 0) {
+ dev_err(scomp->dev, "failed to parse output pin binding for %s\n",
+ w->name);
+ goto widget_free;
+ }
+ }
+
+ dev_dbg(scomp->dev,
+ "tplg: widget %d (%s) is ready [type: %d, pipe: %d, pins: %d / %d, stream: %s]\n",
+ swidget->comp_id, w->name, swidget->id, index,
+ swidget->num_input_pins, swidget->num_output_pins,
+ strnlen(w->sname, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) > 0 ? w->sname : "none");
+
+ widget_ops = tplg_ops ? tplg_ops->widget : NULL;
+ if (widget_ops) {
+ token_list = widget_ops[w->id].token_list;
+ token_list_size = widget_ops[w->id].token_list_size;
+ }
/* handle any special case widgets */
switch (w->id) {
@@ -2311,100 +1476,115 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index,
case snd_soc_dapm_dai_out:
dai = kzalloc(sizeof(*dai), GFP_KERNEL);
if (!dai) {
- kfree(swidget);
- return -ENOMEM;
+ ret = -ENOMEM;
+ goto widget_free;
}
- ret = sof_widget_load_dai(scomp, index, swidget, tw, &reply,
- dai);
- if (ret == 0) {
- sof_connect_dai_widget(scomp, w, tw, dai);
- list_add(&dai->list, &sdev->dai_list);
- swidget->private = dai;
- } else {
+ ret = sof_widget_parse_tokens(scomp, swidget, tw, token_list, token_list_size);
+ if (!ret)
+ ret = sof_connect_dai_widget(scomp, w, tw, dai);
+ if (ret < 0) {
kfree(dai);
+ break;
}
+ list_add(&dai->list, &sdev->dai_list);
+ swidget->private = dai;
break;
- case snd_soc_dapm_mixer:
- ret = sof_widget_load_mixer(scomp, index, swidget, tw, &reply);
+ case snd_soc_dapm_effect:
+ /* check we have some tokens - we need at least process type */
+ if (le32_to_cpu(tw->priv.size) == 0) {
+ dev_err(scomp->dev, "error: process tokens not found\n");
+ ret = -EINVAL;
+ break;
+ }
+ ret = sof_widget_parse_tokens(scomp, swidget, tw, token_list, token_list_size);
break;
case snd_soc_dapm_pga:
- ret = sof_widget_load_pga(scomp, index, swidget, tw, &reply);
- /* Find scontrol for this pga and set readback offset*/
- list_for_each_entry(scontrol, &sdev->kcontrol_list, list) {
- if (scontrol->comp_id == swidget->comp_id) {
- scontrol->readback_offset = reply.offset;
- break;
- }
+ if (!le32_to_cpu(tw->num_kcontrols)) {
+ dev_err(scomp->dev, "invalid kcontrol count %d for volume\n",
+ tw->num_kcontrols);
+ ret = -EINVAL;
+ break;
}
- break;
+
+ fallthrough;
+ case snd_soc_dapm_mixer:
case snd_soc_dapm_buffer:
- ret = sof_widget_load_buffer(scomp, index, swidget, tw, &reply);
- break;
case snd_soc_dapm_scheduler:
- ret = sof_widget_load_pipeline(scomp, index, swidget, tw,
- &reply);
- break;
case snd_soc_dapm_aif_out:
- ret = sof_widget_load_pcm(scomp, index, swidget,
- SOF_IPC_STREAM_CAPTURE, tw, &reply);
- break;
case snd_soc_dapm_aif_in:
- ret = sof_widget_load_pcm(scomp, index, swidget,
- SOF_IPC_STREAM_PLAYBACK, tw, &reply);
- break;
case snd_soc_dapm_src:
- ret = sof_widget_load_src(scomp, index, swidget, tw, &reply);
- break;
case snd_soc_dapm_asrc:
- ret = sof_widget_load_asrc(scomp, index, swidget, tw, &reply);
- break;
case snd_soc_dapm_siggen:
- ret = sof_widget_load_siggen(scomp, index, swidget, tw, &reply);
- break;
- case snd_soc_dapm_effect:
- ret = sof_widget_load_process(scomp, index, swidget, tw,
- &reply);
- break;
case snd_soc_dapm_mux:
case snd_soc_dapm_demux:
- ret = sof_widget_load_mux(scomp, index, swidget, tw, &reply);
+ ret = sof_widget_parse_tokens(scomp, swidget, tw, token_list, token_list_size);
break;
case snd_soc_dapm_switch:
case snd_soc_dapm_dai_link:
case snd_soc_dapm_kcontrol:
default:
- dev_warn(scomp->dev, "warning: widget type %d name %s not handled\n",
- swidget->id, tw->name);
+ dev_dbg(scomp->dev, "widget type %d name %s not handled\n", swidget->id, tw->name);
break;
}
- /* check IPC reply */
- if (ret < 0 || reply.rhdr.error < 0) {
+ /* check token parsing reply */
+ if (ret < 0) {
dev_err(scomp->dev,
- "error: DSP failed to add widget id %d type %d name : %s stream %s reply %d\n",
+ "error: failed to add widget id %d type %d name : %s stream %s\n",
tw->shift, swidget->id, tw->name,
strnlen(tw->sname, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) > 0
- ? tw->sname : "none", reply.rhdr.error);
- kfree(swidget);
- return ret;
+ ? tw->sname : "none");
+ goto widget_free;
+ }
+
+ if (sof_debug_check_flag(SOF_DBG_DISABLE_MULTICORE)) {
+ swidget->core = SOF_DSP_PRIMARY_CORE;
+ } else {
+ int core = sof_get_token_value(SOF_TKN_COMP_CORE_ID, swidget->tuples,
+ swidget->num_tuples);
+
+ if (core >= 0)
+ swidget->core = core;
}
/* bind widget to external event */
if (tw->event_type) {
- ret = sof_widget_bind_event(scomp, swidget,
- le16_to_cpu(tw->event_type));
- if (ret) {
- dev_err(scomp->dev, "error: widget event binding failed\n");
- kfree(swidget->private);
- kfree(swidget);
- return ret;
+ if (widget_ops && widget_ops[w->id].bind_event) {
+ ret = widget_ops[w->id].bind_event(scomp, swidget,
+ le16_to_cpu(tw->event_type));
+ if (ret) {
+ dev_err(scomp->dev, "widget event binding failed for %s\n",
+ swidget->widget->name);
+ goto free;
+ }
}
}
+ /* create and add pipeline for scheduler type widgets */
+ if (w->id == snd_soc_dapm_scheduler) {
+ struct snd_sof_pipeline *spipe;
+
+ spipe = kzalloc(sizeof(*spipe), GFP_KERNEL);
+ if (!spipe) {
+ ret = -ENOMEM;
+ goto free;
+ }
+
+ spipe->pipe_widget = swidget;
+ swidget->spipe = spipe;
+ list_add(&spipe->list, &sdev->pipeline_list);
+ }
+
w->dobj.private = swidget;
list_add(&swidget->list, &sdev->widget_list);
return ret;
+free:
+ kfree(swidget->private);
+ kfree(swidget->tuples);
+widget_free:
+ kfree(swidget);
+ return ret;
}
static int sof_route_unload(struct snd_soc_component *scomp,
@@ -2428,16 +1608,16 @@ static int sof_widget_unload(struct snd_soc_component *scomp,
struct snd_soc_dobj *dobj)
{
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
+ const struct sof_ipc_tplg_widget_ops *widget_ops;
const struct snd_kcontrol_new *kc;
struct snd_soc_dapm_widget *widget;
- struct sof_ipc_pipe_new *pipeline;
struct snd_sof_control *scontrol;
struct snd_sof_widget *swidget;
struct soc_mixer_control *sm;
struct soc_bytes_ext *sbe;
struct snd_sof_dai *dai;
struct soc_enum *se;
- int ret = 0;
int i;
swidget = dobj->private;
@@ -2451,31 +1631,27 @@ static int sof_widget_unload(struct snd_soc_component *scomp,
case snd_soc_dapm_dai_out:
dai = swidget->private;
- if (dai) {
- /* free dai config */
- kfree(dai->dai_config);
+ if (dai)
list_del(&dai->list);
- }
- break;
- case snd_soc_dapm_scheduler:
- /* power down the pipeline schedule core */
- pipeline = swidget->private;
- ret = snd_sof_dsp_core_power_down(sdev, 1 << pipeline->core);
- if (ret < 0)
- dev_err(scomp->dev, "error: powering down pipeline schedule core %d\n",
- pipeline->core);
+ sof_disconnect_dai_widget(scomp, widget);
- /* update enabled cores mask */
- sdev->enabled_cores_mask &= ~(1 << pipeline->core);
+ break;
+ case snd_soc_dapm_scheduler:
+ {
+ struct snd_sof_pipeline *spipe = swidget->spipe;
+ list_del(&spipe->list);
+ kfree(spipe);
+ swidget->spipe = NULL;
break;
+ }
default:
break;
}
for (i = 0; i < widget->num_kcontrols; i++) {
kc = &widget->kcontrol_news[i];
- switch (dobj->widget.kcontrol_type) {
+ switch (widget->dobj.widget.kcontrol_type[i]) {
case SND_SOC_TPLG_TYPE_MIXER:
sm = (struct soc_mixer_control *)kc->private_value;
scontrol = sm->dobj.private;
@@ -2494,20 +1670,31 @@ static int sof_widget_unload(struct snd_soc_component *scomp,
dev_warn(scomp->dev, "unsupported kcontrol_type\n");
goto out;
}
- kfree(scontrol->control_data);
+ kfree(scontrol->ipc_control_data);
list_del(&scontrol->list);
+ kfree(scontrol->name);
kfree(scontrol);
}
out:
- /* free private value */
- kfree(swidget->private);
+ /* free IPC related data */
+ widget_ops = tplg_ops ? tplg_ops->widget : NULL;
+ if (widget_ops && widget_ops[swidget->id].ipc_free)
+ widget_ops[swidget->id].ipc_free(swidget);
+
+ ida_destroy(&swidget->output_queue_ida);
+ ida_destroy(&swidget->input_queue_ida);
+
+ sof_free_pin_binding(swidget, SOF_PIN_TYPE_INPUT);
+ sof_free_pin_binding(swidget, SOF_PIN_TYPE_OUTPUT);
+
+ kfree(swidget->tuples);
/* remove and free swidget object */
list_del(&swidget->list);
kfree(swidget);
- return ret;
+ return 0;
}
/*
@@ -2520,11 +1707,12 @@ static int sof_dai_load(struct snd_soc_component *scomp, int index,
struct snd_soc_tplg_pcm *pcm, struct snd_soc_dai *dai)
{
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ const struct sof_ipc_pcm_ops *ipc_pcm_ops = sof_ipc_get_ops(sdev, pcm);
struct snd_soc_tplg_stream_caps *caps;
struct snd_soc_tplg_private *private = &pcm->priv;
struct snd_sof_pcm *spcm;
int stream;
- int ret = 0;
+ int ret;
/* nothing to do for BEs atm */
if (!pcm)
@@ -2538,13 +1726,24 @@ static int sof_dai_load(struct snd_soc_component *scomp, int index,
for_each_pcm_streams(stream) {
spcm->stream[stream].comp_id = COMP_ID_UNASSIGNED;
- INIT_WORK(&spcm->stream[stream].period_elapsed_work,
- snd_sof_pcm_period_elapsed_work);
+ if (pcm->compress)
+ snd_sof_compr_init_elapsed_work(&spcm->stream[stream].period_elapsed_work);
+ else
+ snd_sof_pcm_init_elapsed_work(&spcm->stream[stream].period_elapsed_work);
}
spcm->pcm = *pcm;
dev_dbg(scomp->dev, "tplg: load pcm %s\n", pcm->dai_name);
+ /* perform pcm set op */
+ if (ipc_pcm_ops && ipc_pcm_ops->pcm_setup) {
+ ret = ipc_pcm_ops->pcm_setup(sdev, spcm);
+ if (ret < 0) {
+ kfree(spcm);
+ return ret;
+ }
+ }
+
dai_drv->dobj.private = spcm;
list_add(&spcm->list, &sdev->pcm_list);
@@ -2563,9 +1762,6 @@ static int sof_dai_load(struct snd_soc_component *scomp, int index,
stream = SNDRV_PCM_STREAM_PLAYBACK;
- dev_vdbg(scomp->dev, "tplg: pcm %s stream tokens: playback d0i3:%d\n",
- spcm->pcm.pcm_name, spcm->stream[stream].d0i3_compatible);
-
caps = &spcm->pcm.caps[stream];
/* allocate playback page table buffer */
@@ -2593,9 +1789,6 @@ capture:
if (!spcm->pcm.capture)
return ret;
- dev_vdbg(scomp->dev, "tplg: pcm %s stream tokens: capture d0i3:%d\n",
- spcm->pcm.pcm_name, spcm->stream[stream].d0i3_compatible);
-
caps = &spcm->pcm.caps[stream];
/* allocate capture page table buffer */
@@ -2628,6 +1821,8 @@ free_playback_tables:
static int sof_dai_unload(struct snd_soc_component *scomp,
struct snd_soc_dobj *dobj)
{
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ const struct sof_ipc_pcm_ops *ipc_pcm_ops = sof_ipc_get_ops(sdev, pcm);
struct snd_sof_pcm *spcm = dobj->private;
/* free PCM DMA pages */
@@ -2637,6 +1832,10 @@ static int sof_dai_unload(struct snd_soc_component *scomp,
if (spcm->pcm.capture)
snd_dma_free_pages(&spcm->stream[SNDRV_PCM_STREAM_CAPTURE].page_table);
+ /* perform pcm free op */
+ if (ipc_pcm_ops && ipc_pcm_ops->pcm_free)
+ ipc_pcm_ops->pcm_free(sdev, spcm);
+
/* remove from list and free spcm */
list_del(&spcm->list);
kfree(spcm);
@@ -2644,464 +1843,23 @@ static int sof_dai_unload(struct snd_soc_component *scomp,
return 0;
}
-static void sof_dai_set_format(struct snd_soc_tplg_hw_config *hw_config,
- struct sof_ipc_dai_config *config)
-{
- /* clock directions wrt codec */
- if (hw_config->bclk_master == SND_SOC_TPLG_BCLK_CM) {
- /* codec is bclk master */
- if (hw_config->fsync_master == SND_SOC_TPLG_FSYNC_CM)
- config->format |= SOF_DAI_FMT_CBM_CFM;
- else
- config->format |= SOF_DAI_FMT_CBM_CFS;
- } else {
- /* codec is bclk slave */
- if (hw_config->fsync_master == SND_SOC_TPLG_FSYNC_CM)
- config->format |= SOF_DAI_FMT_CBS_CFM;
- else
- config->format |= SOF_DAI_FMT_CBS_CFS;
- }
-
- /* inverted clocks ? */
- if (hw_config->invert_bclk) {
- if (hw_config->invert_fsync)
- config->format |= SOF_DAI_FMT_IB_IF;
- else
- config->format |= SOF_DAI_FMT_IB_NF;
- } else {
- if (hw_config->invert_fsync)
- config->format |= SOF_DAI_FMT_NB_IF;
- else
- config->format |= SOF_DAI_FMT_NB_NF;
- }
-}
-
-/*
- * Send IPC and set the same config for all DAIs with name matching the link
- * name. Note that the function can only be used for the case that all DAIs
- * have a common DAI config for now.
- */
-static int sof_set_dai_config(struct snd_sof_dev *sdev, u32 size,
- struct snd_soc_dai_link *link,
- struct sof_ipc_dai_config *config)
-{
- struct snd_sof_dai *dai;
- int found = 0;
-
- list_for_each_entry(dai, &sdev->dai_list, list) {
- if (!dai->name)
- continue;
-
- if (strcmp(link->name, dai->name) == 0) {
- struct sof_ipc_reply reply;
- int ret;
-
- /*
- * the same dai config will be applied to all DAIs in
- * the same dai link. We have to ensure that the ipc
- * dai config's dai_index match to the component's
- * dai_index.
- */
- config->dai_index = dai->comp_dai.dai_index;
-
- /* send message to DSP */
- ret = sof_ipc_tx_message(sdev->ipc,
- config->hdr.cmd, config, size,
- &reply, sizeof(reply));
-
- if (ret < 0) {
- dev_err(sdev->dev, "error: failed to set DAI config for %s index %d\n",
- dai->name, config->dai_index);
- return ret;
- }
- dai->dai_config = kmemdup(config, size, GFP_KERNEL);
- if (!dai->dai_config)
- return -ENOMEM;
-
- /* set cpu_dai_name */
- dai->cpu_dai_name = link->cpus->dai_name;
-
- found = 1;
- }
- }
-
- /*
- * machine driver may define a dai link with playback and capture
- * dai enabled, but the dai link in topology would support both, one
- * or none of them. Here print a warning message to notify user
- */
- if (!found) {
- dev_warn(sdev->dev, "warning: failed to find dai for dai link %s",
- link->name);
- }
-
- return 0;
-}
-
-static int sof_link_ssp_load(struct snd_soc_component *scomp, int index,
- struct snd_soc_dai_link *link,
- struct snd_soc_tplg_link_config *cfg,
- struct snd_soc_tplg_hw_config *hw_config,
- struct sof_ipc_dai_config *config)
-{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct snd_soc_tplg_private *private = &cfg->priv;
- u32 size = sizeof(*config);
- int ret;
-
- /* handle master/slave and inverted clocks */
- sof_dai_set_format(hw_config, config);
-
- /* init IPC */
- memset(&config->ssp, 0, sizeof(struct sof_ipc_dai_ssp_params));
- config->hdr.size = size;
-
- ret = sof_parse_tokens(scomp, &config->ssp, ssp_tokens,
- ARRAY_SIZE(ssp_tokens), private->array,
- le32_to_cpu(private->size));
- if (ret != 0) {
- dev_err(scomp->dev, "error: parse ssp tokens failed %d\n",
- le32_to_cpu(private->size));
- return ret;
- }
-
- config->ssp.mclk_rate = le32_to_cpu(hw_config->mclk_rate);
- config->ssp.bclk_rate = le32_to_cpu(hw_config->bclk_rate);
- config->ssp.fsync_rate = le32_to_cpu(hw_config->fsync_rate);
- config->ssp.tdm_slots = le32_to_cpu(hw_config->tdm_slots);
- config->ssp.tdm_slot_width = le32_to_cpu(hw_config->tdm_slot_width);
- config->ssp.mclk_direction = hw_config->mclk_direction;
- config->ssp.rx_slots = le32_to_cpu(hw_config->rx_slots);
- config->ssp.tx_slots = le32_to_cpu(hw_config->tx_slots);
-
- dev_dbg(scomp->dev, "tplg: config SSP%d fmt 0x%x mclk %d bclk %d fclk %d width (%d)%d slots %d mclk id %d quirks %d\n",
- config->dai_index, config->format,
- config->ssp.mclk_rate, config->ssp.bclk_rate,
- config->ssp.fsync_rate, config->ssp.sample_valid_bits,
- config->ssp.tdm_slot_width, config->ssp.tdm_slots,
- config->ssp.mclk_id, config->ssp.quirks);
-
- /* validate SSP fsync rate and channel count */
- if (config->ssp.fsync_rate < 8000 || config->ssp.fsync_rate > 192000) {
- dev_err(scomp->dev, "error: invalid fsync rate for SSP%d\n",
- config->dai_index);
- return -EINVAL;
- }
-
- if (config->ssp.tdm_slots < 1 || config->ssp.tdm_slots > 8) {
- dev_err(scomp->dev, "error: invalid channel count for SSP%d\n",
- config->dai_index);
- return -EINVAL;
- }
-
- /* set config for all DAI's with name matching the link name */
- ret = sof_set_dai_config(sdev, size, link, config);
- if (ret < 0)
- dev_err(scomp->dev, "error: failed to save DAI config for SSP%d\n",
- config->dai_index);
-
- return ret;
-}
-
-static int sof_link_sai_load(struct snd_soc_component *scomp, int index,
- struct snd_soc_dai_link *link,
- struct snd_soc_tplg_link_config *cfg,
- struct snd_soc_tplg_hw_config *hw_config,
- struct sof_ipc_dai_config *config)
-{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct snd_soc_tplg_private *private = &cfg->priv;
- u32 size = sizeof(*config);
- int ret;
-
- /* handle master/slave and inverted clocks */
- sof_dai_set_format(hw_config, config);
-
- /* init IPC */
- memset(&config->sai, 0, sizeof(struct sof_ipc_dai_sai_params));
- config->hdr.size = size;
-
- ret = sof_parse_tokens(scomp, &config->sai, sai_tokens,
- ARRAY_SIZE(sai_tokens), private->array,
- le32_to_cpu(private->size));
- if (ret != 0) {
- dev_err(scomp->dev, "error: parse sai tokens failed %d\n",
- le32_to_cpu(private->size));
- return ret;
- }
-
- config->sai.mclk_rate = le32_to_cpu(hw_config->mclk_rate);
- config->sai.bclk_rate = le32_to_cpu(hw_config->bclk_rate);
- config->sai.fsync_rate = le32_to_cpu(hw_config->fsync_rate);
- config->sai.mclk_direction = hw_config->mclk_direction;
-
- config->sai.tdm_slots = le32_to_cpu(hw_config->tdm_slots);
- config->sai.tdm_slot_width = le32_to_cpu(hw_config->tdm_slot_width);
- config->sai.rx_slots = le32_to_cpu(hw_config->rx_slots);
- config->sai.tx_slots = le32_to_cpu(hw_config->tx_slots);
-
- dev_info(scomp->dev,
- "tplg: config SAI%d fmt 0x%x mclk %d width %d slots %d mclk id %d\n",
- config->dai_index, config->format,
- config->sai.mclk_rate, config->sai.tdm_slot_width,
- config->sai.tdm_slots, config->sai.mclk_id);
-
- if (config->sai.tdm_slots < 1 || config->sai.tdm_slots > 8) {
- dev_err(scomp->dev, "error: invalid channel count for SAI%d\n",
- config->dai_index);
- return -EINVAL;
- }
-
- /* set config for all DAI's with name matching the link name */
- ret = sof_set_dai_config(sdev, size, link, config);
- if (ret < 0)
- dev_err(scomp->dev, "error: failed to save DAI config for SAI%d\n",
- config->dai_index);
-
- return ret;
-}
-
-static int sof_link_esai_load(struct snd_soc_component *scomp, int index,
- struct snd_soc_dai_link *link,
- struct snd_soc_tplg_link_config *cfg,
- struct snd_soc_tplg_hw_config *hw_config,
- struct sof_ipc_dai_config *config)
-{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct snd_soc_tplg_private *private = &cfg->priv;
- u32 size = sizeof(*config);
- int ret;
-
- /* handle master/slave and inverted clocks */
- sof_dai_set_format(hw_config, config);
-
- /* init IPC */
- memset(&config->esai, 0, sizeof(struct sof_ipc_dai_esai_params));
- config->hdr.size = size;
-
- ret = sof_parse_tokens(scomp, &config->esai, esai_tokens,
- ARRAY_SIZE(esai_tokens), private->array,
- le32_to_cpu(private->size));
- if (ret != 0) {
- dev_err(scomp->dev, "error: parse esai tokens failed %d\n",
- le32_to_cpu(private->size));
- return ret;
- }
-
- config->esai.mclk_rate = le32_to_cpu(hw_config->mclk_rate);
- config->esai.bclk_rate = le32_to_cpu(hw_config->bclk_rate);
- config->esai.fsync_rate = le32_to_cpu(hw_config->fsync_rate);
- config->esai.mclk_direction = hw_config->mclk_direction;
- config->esai.tdm_slots = le32_to_cpu(hw_config->tdm_slots);
- config->esai.tdm_slot_width = le32_to_cpu(hw_config->tdm_slot_width);
- config->esai.rx_slots = le32_to_cpu(hw_config->rx_slots);
- config->esai.tx_slots = le32_to_cpu(hw_config->tx_slots);
-
- dev_info(scomp->dev,
- "tplg: config ESAI%d fmt 0x%x mclk %d width %d slots %d mclk id %d\n",
- config->dai_index, config->format,
- config->esai.mclk_rate, config->esai.tdm_slot_width,
- config->esai.tdm_slots, config->esai.mclk_id);
-
- if (config->esai.tdm_slots < 1 || config->esai.tdm_slots > 8) {
- dev_err(scomp->dev, "error: invalid channel count for ESAI%d\n",
- config->dai_index);
- return -EINVAL;
- }
-
- /* set config for all DAI's with name matching the link name */
- ret = sof_set_dai_config(sdev, size, link, config);
- if (ret < 0)
- dev_err(scomp->dev, "error: failed to save DAI config for ESAI%d\n",
- config->dai_index);
-
- return ret;
-}
-
-static int sof_link_dmic_load(struct snd_soc_component *scomp, int index,
- struct snd_soc_dai_link *link,
- struct snd_soc_tplg_link_config *cfg,
- struct snd_soc_tplg_hw_config *hw_config,
- struct sof_ipc_dai_config *config)
-{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct snd_soc_tplg_private *private = &cfg->priv;
- struct sof_ipc_fw_ready *ready = &sdev->fw_ready;
- struct sof_ipc_fw_version *v = &ready->version;
- size_t size = sizeof(*config);
- int ret, j;
-
- /* Ensure the entire DMIC config struct is zeros */
- memset(&config->dmic, 0, sizeof(struct sof_ipc_dai_dmic_params));
-
- /* get DMIC tokens */
- ret = sof_parse_tokens(scomp, &config->dmic, dmic_tokens,
- ARRAY_SIZE(dmic_tokens), private->array,
- le32_to_cpu(private->size));
- if (ret != 0) {
- dev_err(scomp->dev, "error: parse dmic tokens failed %d\n",
- le32_to_cpu(private->size));
- return ret;
- }
-
- /*
- * alloc memory for private member
- * Used to track the pdm config array index currently being parsed
- */
- sdev->private = kzalloc(sizeof(u32), GFP_KERNEL);
- if (!sdev->private)
- return -ENOMEM;
-
- /* get DMIC PDM tokens */
- ret = sof_parse_token_sets(scomp, &config->dmic.pdm[0], dmic_pdm_tokens,
- ARRAY_SIZE(dmic_pdm_tokens), private->array,
- le32_to_cpu(private->size),
- config->dmic.num_pdm_active,
- sizeof(struct sof_ipc_dai_dmic_pdm_ctrl));
-
- if (ret != 0) {
- dev_err(scomp->dev, "error: parse dmic pdm tokens failed %d\n",
- le32_to_cpu(private->size));
- goto err;
- }
-
- /* set IPC header size */
- config->hdr.size = size;
-
- /* debug messages */
- dev_dbg(scomp->dev, "tplg: config DMIC%d driver version %d\n",
- config->dai_index, config->dmic.driver_ipc_version);
- dev_dbg(scomp->dev, "pdmclk_min %d pdm_clkmax %d duty_min %hd\n",
- config->dmic.pdmclk_min, config->dmic.pdmclk_max,
- config->dmic.duty_min);
- dev_dbg(scomp->dev, "duty_max %hd fifo_fs %d num_pdms active %d\n",
- config->dmic.duty_max, config->dmic.fifo_fs,
- config->dmic.num_pdm_active);
- dev_dbg(scomp->dev, "fifo word length %hd\n", config->dmic.fifo_bits);
-
- for (j = 0; j < config->dmic.num_pdm_active; j++) {
- dev_dbg(scomp->dev, "pdm %hd mic a %hd mic b %hd\n",
- config->dmic.pdm[j].id,
- config->dmic.pdm[j].enable_mic_a,
- config->dmic.pdm[j].enable_mic_b);
- dev_dbg(scomp->dev, "pdm %hd polarity a %hd polarity b %hd\n",
- config->dmic.pdm[j].id,
- config->dmic.pdm[j].polarity_mic_a,
- config->dmic.pdm[j].polarity_mic_b);
- dev_dbg(scomp->dev, "pdm %hd clk_edge %hd skew %hd\n",
- config->dmic.pdm[j].id,
- config->dmic.pdm[j].clk_edge,
- config->dmic.pdm[j].skew);
- }
-
- /*
- * this takes care of backwards compatible handling of fifo_bits_b.
- * It is deprecated since firmware ABI version 3.0.1.
- */
- if (SOF_ABI_VER(v->major, v->minor, v->micro) < SOF_ABI_VER(3, 0, 1))
- config->dmic.fifo_bits_b = config->dmic.fifo_bits;
-
- /* set config for all DAI's with name matching the link name */
- ret = sof_set_dai_config(sdev, size, link, config);
- if (ret < 0)
- dev_err(scomp->dev, "error: failed to save DAI config for DMIC%d\n",
- config->dai_index);
-
-err:
- kfree(sdev->private);
-
- return ret;
-}
-
-static int sof_link_hda_load(struct snd_soc_component *scomp, int index,
- struct snd_soc_dai_link *link,
- struct snd_soc_tplg_link_config *cfg,
- struct snd_soc_tplg_hw_config *hw_config,
- struct sof_ipc_dai_config *config)
-{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct snd_soc_tplg_private *private = &cfg->priv;
- struct snd_soc_dai *dai;
- u32 size = sizeof(*config);
- int ret;
-
- /* init IPC */
- memset(&config->hda, 0, sizeof(struct sof_ipc_dai_hda_params));
- config->hdr.size = size;
-
- /* get any bespoke DAI tokens */
- ret = sof_parse_tokens(scomp, &config->hda, hda_tokens,
- ARRAY_SIZE(hda_tokens), private->array,
- le32_to_cpu(private->size));
- if (ret != 0) {
- dev_err(scomp->dev, "error: parse hda tokens failed %d\n",
- le32_to_cpu(private->size));
- return ret;
- }
-
- dev_dbg(scomp->dev, "HDA config rate %d channels %d\n",
- config->hda.rate, config->hda.channels);
-
- dai = snd_soc_find_dai(link->cpus);
- if (!dai) {
- dev_err(scomp->dev, "error: failed to find dai %s in %s",
- link->cpus->dai_name, __func__);
- return -EINVAL;
- }
-
- config->hda.link_dma_ch = DMA_CHAN_INVALID;
-
- ret = sof_set_dai_config(sdev, size, link, config);
- if (ret < 0)
- dev_err(scomp->dev, "error: failed to process hda dai link %s",
- link->name);
-
- return ret;
-}
-
-static int sof_link_alh_load(struct snd_soc_component *scomp, int index,
- struct snd_soc_dai_link *link,
- struct snd_soc_tplg_link_config *cfg,
- struct snd_soc_tplg_hw_config *hw_config,
- struct sof_ipc_dai_config *config)
-{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct snd_soc_tplg_private *private = &cfg->priv;
- u32 size = sizeof(*config);
- int ret;
-
- ret = sof_parse_tokens(scomp, &config->alh, alh_tokens,
- ARRAY_SIZE(alh_tokens), private->array,
- le32_to_cpu(private->size));
- if (ret != 0) {
- dev_err(scomp->dev, "error: parse alh tokens failed %d\n",
- le32_to_cpu(private->size));
- return ret;
- }
-
- /* init IPC */
- config->hdr.size = size;
-
- /* set config for all DAI's with name matching the link name */
- ret = sof_set_dai_config(sdev, size, link, config);
- if (ret < 0)
- dev_err(scomp->dev, "error: failed to save DAI config for ALH %d\n",
- config->dai_index);
-
- return ret;
-}
+static const struct sof_topology_token common_dai_link_tokens[] = {
+ {SOF_TKN_DAI_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_dai_type,
+ offsetof(struct snd_sof_dai_link, type)},
+};
/* DAI link - used for any driver specific init */
-static int sof_link_load(struct snd_soc_component *scomp, int index,
- struct snd_soc_dai_link *link,
+static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_soc_dai_link *link,
struct snd_soc_tplg_link_config *cfg)
{
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
struct snd_soc_tplg_private *private = &cfg->priv;
- struct sof_ipc_dai_config config;
- struct snd_soc_tplg_hw_config *hw_config;
- int num_hw_configs;
- int ret;
- int i = 0;
+ const struct sof_token_info *token_list;
+ struct snd_sof_dai_link *slink;
+ u32 token_id = 0;
+ int num_tuples = 0;
+ int ret, num_sets;
if (!link->platforms) {
dev_err(scomp->dev, "error: no platforms\n");
@@ -3109,26 +1867,15 @@ static int sof_link_load(struct snd_soc_component *scomp, int index,
}
link->platforms->name = dev_name(scomp->dev);
- /*
- * Set nonatomic property for FE dai links as their trigger action
- * involves IPC's.
- */
+ if (tplg_ops && tplg_ops->link_setup) {
+ ret = tplg_ops->link_setup(sdev, link);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Set nonatomic property for FE dai links as their trigger action involves IPC's */
if (!link->no_pcm) {
link->nonatomic = true;
-
- /*
- * set default trigger order for all links. Exceptions to
- * the rule will be handled in sof_pcm_dai_link_fixup()
- * For playback, the sequence is the following: start FE,
- * start BE, stop BE, stop FE; for Capture the sequence is
- * inverted start BE, start FE, stop FE, stop BE
- */
- link->trigger[SNDRV_PCM_STREAM_PLAYBACK] =
- SND_SOC_DPCM_TRIGGER_PRE;
- link->trigger[SNDRV_PCM_STREAM_CAPTURE] =
- SND_SOC_DPCM_TRIGGER_POST;
-
- /* nothing more to do for FE dai links */
return 0;
}
@@ -3138,164 +1885,189 @@ static int sof_link_load(struct snd_soc_component *scomp, int index,
return -EINVAL;
}
- /* Send BE DAI link configurations to DSP */
- memset(&config, 0, sizeof(config));
+ slink = kzalloc(sizeof(*slink), GFP_KERNEL);
+ if (!slink)
+ return -ENOMEM;
- /* get any common DAI tokens */
- ret = sof_parse_tokens(scomp, &config, dai_link_tokens,
- ARRAY_SIZE(dai_link_tokens), private->array,
- le32_to_cpu(private->size));
- if (ret != 0) {
- dev_err(scomp->dev, "error: parse link tokens failed %d\n",
- le32_to_cpu(private->size));
- return ret;
+ slink->num_hw_configs = le32_to_cpu(cfg->num_hw_configs);
+ slink->hw_configs = kmemdup(cfg->hw_config,
+ sizeof(*slink->hw_configs) * slink->num_hw_configs,
+ GFP_KERNEL);
+ if (!slink->hw_configs) {
+ kfree(slink);
+ return -ENOMEM;
}
- /*
- * DAI links are expected to have at least 1 hw_config.
- * But some older topologies might have no hw_config for HDA dai links.
- */
- num_hw_configs = le32_to_cpu(cfg->num_hw_configs);
- if (!num_hw_configs) {
- if (config.type != SOF_DAI_INTEL_HDA) {
- dev_err(scomp->dev, "error: unexpected DAI config count %d!\n",
- le32_to_cpu(cfg->num_hw_configs));
- return -EINVAL;
- }
- } else {
- dev_dbg(scomp->dev, "tplg: %d hw_configs found, default id: %d!\n",
- cfg->num_hw_configs, le32_to_cpu(cfg->default_hw_config_id));
+ slink->default_hw_cfg_id = le32_to_cpu(cfg->default_hw_config_id);
+ slink->link = link;
- for (i = 0; i < num_hw_configs; i++) {
- if (cfg->hw_config[i].id == cfg->default_hw_config_id)
- break;
- }
+ dev_dbg(scomp->dev, "tplg: %d hw_configs found, default id: %d for dai link %s!\n",
+ slink->num_hw_configs, slink->default_hw_cfg_id, link->name);
- if (i == num_hw_configs) {
- dev_err(scomp->dev, "error: default hw_config id: %d not found!\n",
- le32_to_cpu(cfg->default_hw_config_id));
- return -EINVAL;
- }
+ ret = sof_parse_tokens(scomp, slink, common_dai_link_tokens,
+ ARRAY_SIZE(common_dai_link_tokens),
+ private->array, le32_to_cpu(private->size));
+ if (ret < 0) {
+ dev_err(scomp->dev, "Failed tp parse common DAI link tokens\n");
+ kfree(slink->hw_configs);
+ kfree(slink);
+ return ret;
}
- /* configure dai IPC message */
- hw_config = &cfg->hw_config[i];
-
- config.hdr.cmd = SOF_IPC_GLB_DAI_MSG | SOF_IPC_DAI_CONFIG;
- config.format = le32_to_cpu(hw_config->fmt);
+ token_list = tplg_ops ? tplg_ops->token_list : NULL;
+ if (!token_list)
+ goto out;
- /* now load DAI specific data and send IPC - type comes from token */
- switch (config.type) {
+ /* calculate size of tuples array */
+ num_tuples += token_list[SOF_DAI_LINK_TOKENS].count;
+ num_sets = slink->num_hw_configs;
+ switch (slink->type) {
case SOF_DAI_INTEL_SSP:
- ret = sof_link_ssp_load(scomp, index, link, cfg, hw_config,
- &config);
+ token_id = SOF_SSP_TOKENS;
+ num_tuples += token_list[SOF_SSP_TOKENS].count * slink->num_hw_configs;
break;
case SOF_DAI_INTEL_DMIC:
- ret = sof_link_dmic_load(scomp, index, link, cfg, hw_config,
- &config);
+ token_id = SOF_DMIC_TOKENS;
+ num_tuples += token_list[SOF_DMIC_TOKENS].count;
+
+ /* Allocate memory for max PDM controllers */
+ num_tuples += token_list[SOF_DMIC_PDM_TOKENS].count * SOF_DAI_INTEL_DMIC_NUM_CTRL;
break;
case SOF_DAI_INTEL_HDA:
- ret = sof_link_hda_load(scomp, index, link, cfg, hw_config,
- &config);
+ token_id = SOF_HDA_TOKENS;
+ num_tuples += token_list[SOF_HDA_TOKENS].count;
break;
case SOF_DAI_INTEL_ALH:
- ret = sof_link_alh_load(scomp, index, link, cfg, hw_config,
- &config);
+ token_id = SOF_ALH_TOKENS;
+ num_tuples += token_list[SOF_ALH_TOKENS].count;
break;
case SOF_DAI_IMX_SAI:
- ret = sof_link_sai_load(scomp, index, link, cfg, hw_config,
- &config);
+ token_id = SOF_SAI_TOKENS;
+ num_tuples += token_list[SOF_SAI_TOKENS].count;
break;
case SOF_DAI_IMX_ESAI:
- ret = sof_link_esai_load(scomp, index, link, cfg, hw_config,
- &config);
+ token_id = SOF_ESAI_TOKENS;
+ num_tuples += token_list[SOF_ESAI_TOKENS].count;
+ break;
+ case SOF_DAI_MEDIATEK_AFE:
+ token_id = SOF_AFE_TOKENS;
+ num_tuples += token_list[SOF_AFE_TOKENS].count;
+ break;
+ case SOF_DAI_AMD_DMIC:
+ token_id = SOF_ACPDMIC_TOKENS;
+ num_tuples += token_list[SOF_ACPDMIC_TOKENS].count;
+ break;
+ case SOF_DAI_AMD_BT:
+ case SOF_DAI_AMD_SP:
+ case SOF_DAI_AMD_HS:
+ case SOF_DAI_AMD_SP_VIRTUAL:
+ case SOF_DAI_AMD_HS_VIRTUAL:
+ token_id = SOF_ACPI2S_TOKENS;
+ num_tuples += token_list[SOF_ACPI2S_TOKENS].count;
+ break;
+ case SOF_DAI_IMX_MICFIL:
+ token_id = SOF_MICFIL_TOKENS;
+ num_tuples += token_list[SOF_MICFIL_TOKENS].count;
+ break;
+ case SOF_DAI_AMD_SDW:
+ token_id = SOF_ACP_SDW_TOKENS;
+ num_tuples += token_list[SOF_ACP_SDW_TOKENS].count;
break;
default:
- dev_err(scomp->dev, "error: invalid DAI type %d\n",
- config.type);
- ret = -EINVAL;
break;
}
- if (ret < 0)
- return ret;
- return 0;
-}
-
-static int sof_link_hda_unload(struct snd_sof_dev *sdev,
- struct snd_soc_dai_link *link)
-{
- struct snd_soc_dai *dai;
- int ret = 0;
-
- dai = snd_soc_find_dai(link->cpus);
- if (!dai) {
- dev_err(sdev->dev, "error: failed to find dai %s in %s",
- link->cpus->dai_name, __func__);
- return -EINVAL;
+ /* allocate memory for tuples array */
+ slink->tuples = kcalloc(num_tuples, sizeof(*slink->tuples), GFP_KERNEL);
+ if (!slink->tuples) {
+ kfree(slink->hw_configs);
+ kfree(slink);
+ return -ENOMEM;
}
- return ret;
-}
+ if (token_list[SOF_DAI_LINK_TOKENS].tokens) {
+ /* parse one set of DAI link tokens */
+ ret = sof_copy_tuples(sdev, private->array, le32_to_cpu(private->size),
+ SOF_DAI_LINK_TOKENS, 1, slink->tuples,
+ num_tuples, &slink->num_tuples);
+ if (ret < 0) {
+ dev_err(scomp->dev, "failed to parse %s for dai link %s\n",
+ token_list[SOF_DAI_LINK_TOKENS].name, link->name);
+ goto err;
+ }
+ }
-static int sof_link_unload(struct snd_soc_component *scomp,
- struct snd_soc_dobj *dobj)
-{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct snd_soc_dai_link *link =
- container_of(dobj, struct snd_soc_dai_link, dobj);
+ /* nothing more to do if there are no DAI type-specific tokens defined */
+ if (!token_id || !token_list[token_id].tokens)
+ goto out;
- struct snd_sof_dai *sof_dai;
- int ret = 0;
+ /* parse "num_sets" sets of DAI-specific tokens */
+ ret = sof_copy_tuples(sdev, private->array, le32_to_cpu(private->size),
+ token_id, num_sets, slink->tuples, num_tuples, &slink->num_tuples);
+ if (ret < 0) {
+ dev_err(scomp->dev, "failed to parse %s for dai link %s\n",
+ token_list[token_id].name, link->name);
+ goto err;
+ }
- /* only BE link is loaded by sof */
- if (!link->no_pcm)
- return 0;
+ /* for DMIC, also parse all sets of DMIC PDM tokens based on active PDM count */
+ if (token_id == SOF_DMIC_TOKENS) {
+ num_sets = sof_get_token_value(SOF_TKN_INTEL_DMIC_NUM_PDM_ACTIVE,
+ slink->tuples, slink->num_tuples);
- list_for_each_entry(sof_dai, &sdev->dai_list, list) {
- if (!sof_dai->name)
- continue;
+ if (num_sets < 0) {
+ dev_err(sdev->dev, "Invalid active PDM count for %s\n", link->name);
+ ret = num_sets;
+ goto err;
+ }
- if (strcmp(link->name, sof_dai->name) == 0)
- goto found;
+ ret = sof_copy_tuples(sdev, private->array, le32_to_cpu(private->size),
+ SOF_DMIC_PDM_TOKENS, num_sets, slink->tuples,
+ num_tuples, &slink->num_tuples);
+ if (ret < 0) {
+ dev_err(scomp->dev, "failed to parse %s for dai link %s\n",
+ token_list[SOF_DMIC_PDM_TOKENS].name, link->name);
+ goto err;
+ }
}
+out:
+ link->dobj.private = slink;
+ list_add(&slink->list, &sdev->dai_link_list);
- dev_err(scomp->dev, "error: failed to find dai %s in %s",
- link->name, __func__);
- return -EINVAL;
-found:
+ return 0;
- switch (sof_dai->dai_config->type) {
- case SOF_DAI_INTEL_SSP:
- case SOF_DAI_INTEL_DMIC:
- case SOF_DAI_INTEL_ALH:
- case SOF_DAI_IMX_SAI:
- case SOF_DAI_IMX_ESAI:
- /* no resource needs to be released for all cases above */
- break;
- case SOF_DAI_INTEL_HDA:
- ret = sof_link_hda_unload(sdev, link);
- break;
- default:
- dev_err(scomp->dev, "error: invalid DAI type %d\n",
- sof_dai->dai_config->type);
- ret = -EINVAL;
- break;
- }
+err:
+ kfree(slink->tuples);
+ kfree(slink->hw_configs);
+ kfree(slink);
return ret;
}
+static int sof_link_unload(struct snd_soc_component *scomp, struct snd_soc_dobj *dobj)
+{
+ struct snd_sof_dai_link *slink = dobj->private;
+
+ if (!slink)
+ return 0;
+
+ kfree(slink->tuples);
+ list_del(&slink->list);
+ kfree(slink->hw_configs);
+ kfree(slink);
+ dobj->private = NULL;
+
+ return 0;
+}
+
/* DAI link - used for any driver specific init */
static int sof_route_load(struct snd_soc_component *scomp, int index,
struct snd_soc_dapm_route *route)
{
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct sof_ipc_pipe_comp_connect *connect;
struct snd_sof_widget *source_swidget, *sink_swidget;
struct snd_soc_dobj *dobj = &route->dobj;
struct snd_sof_route *sroute;
- struct sof_ipc_reply reply;
int ret = 0;
/* allocate memory for sroute and connect */
@@ -3304,16 +2076,6 @@ static int sof_route_load(struct snd_soc_component *scomp, int index,
return -ENOMEM;
sroute->scomp = scomp;
-
- connect = kzalloc(sizeof(*connect), GFP_KERNEL);
- if (!connect) {
- kfree(sroute);
- return -ENOMEM;
- }
-
- connect->hdr.size = sizeof(*connect);
- connect->hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_CONNECT;
-
dev_dbg(scomp->dev, "sink %s control %s source %s\n",
route->sink, route->control ? route->control : "none",
route->source);
@@ -3337,8 +2099,6 @@ static int sof_route_load(struct snd_soc_component *scomp, int index,
source_swidget->id == snd_soc_dapm_output)
goto err;
- connect->source_id = source_swidget->comp_id;
-
/* sink component */
sink_swidget = snd_sof_find_swidget(scomp, (char *)route->sink);
if (!sink_swidget) {
@@ -3356,199 +2116,151 @@ static int sof_route_load(struct snd_soc_component *scomp, int index,
sink_swidget->id == snd_soc_dapm_output)
goto err;
- connect->sink_id = sink_swidget->comp_id;
-
- /*
- * For virtual routes, both sink and source are not
- * buffer. Since only buffer linked to component is supported by
- * FW, others are reported as error, add check in route function,
- * do not send it to FW when both source and sink are not buffer
- */
- if (source_swidget->id != snd_soc_dapm_buffer &&
- sink_swidget->id != snd_soc_dapm_buffer) {
- dev_dbg(scomp->dev, "warning: neither Linked source component %s nor sink component %s is of buffer type, ignoring link\n",
- route->source, route->sink);
- ret = 0;
- goto err;
- } else {
- ret = sof_ipc_tx_message(sdev->ipc,
- connect->hdr.cmd,
- connect, sizeof(*connect),
- &reply, sizeof(reply));
-
- /* check IPC return value */
- if (ret < 0) {
- dev_err(scomp->dev, "error: failed to add route sink %s control %s source %s\n",
- route->sink,
- route->control ? route->control : "none",
- route->source);
- goto err;
- }
-
- /* check IPC reply */
- if (reply.error < 0) {
- dev_err(scomp->dev, "error: DSP failed to add route sink %s control %s source %s result %d\n",
- route->sink,
- route->control ? route->control : "none",
- route->source, reply.error);
- ret = reply.error;
- goto err;
- }
-
- sroute->route = route;
- dobj->private = sroute;
- sroute->private = connect;
+ sroute->route = route;
+ dobj->private = sroute;
+ sroute->src_widget = source_swidget;
+ sroute->sink_widget = sink_swidget;
- /* add route to route list */
- list_add(&sroute->list, &sdev->route_list);
-
- return ret;
- }
+ /* add route to route list */
+ list_add(&sroute->list, &sdev->route_list);
+ return 0;
err:
- kfree(connect);
kfree(sroute);
return ret;
}
-/* Function to set the initial value of SOF kcontrols.
- * The value will be stored in scontrol->control_data
+/**
+ * sof_set_widget_pipeline - Set pipeline for a component
+ * @sdev: pointer to struct snd_sof_dev
+ * @spipe: pointer to struct snd_sof_pipeline
+ * @swidget: pointer to struct snd_sof_widget that has the same pipeline ID as @pipe_widget
+ *
+ * Return: 0 if successful, -EINVAL on error.
+ * The function checks if @swidget is associated with any volatile controls. If so, setting
+ * the dynamic_pipeline_widget is disallowed.
*/
-static int snd_sof_cache_kcontrol_val(struct snd_soc_component *scomp)
+static int sof_set_widget_pipeline(struct snd_sof_dev *sdev, struct snd_sof_pipeline *spipe,
+ struct snd_sof_widget *swidget)
{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct snd_sof_control *scontrol = NULL;
- int ipc_cmd, ctrl_type;
- int ret = 0;
-
- list_for_each_entry(scontrol, &sdev->kcontrol_list, list) {
+ struct snd_sof_widget *pipe_widget = spipe->pipe_widget;
+ struct snd_sof_control *scontrol;
- /* notify DSP of kcontrol values */
- switch (scontrol->cmd) {
- case SOF_CTRL_CMD_VOLUME:
- case SOF_CTRL_CMD_ENUM:
- case SOF_CTRL_CMD_SWITCH:
- ipc_cmd = SOF_IPC_COMP_GET_VALUE;
- ctrl_type = SOF_CTRL_TYPE_VALUE_CHAN_GET;
- break;
- case SOF_CTRL_CMD_BINARY:
- ipc_cmd = SOF_IPC_COMP_GET_DATA;
- ctrl_type = SOF_CTRL_TYPE_DATA_GET;
- break;
- default:
- dev_err(scomp->dev,
- "error: Invalid scontrol->cmd: %d\n",
- scontrol->cmd);
- return -EINVAL;
- }
- ret = snd_sof_ipc_set_get_comp_data(scontrol,
- ipc_cmd, ctrl_type,
- scontrol->cmd,
- false);
- if (ret < 0) {
- dev_warn(scomp->dev,
- "error: kcontrol value get for widget: %d\n",
- scontrol->comp_id);
- }
+ if (pipe_widget->dynamic_pipeline_widget) {
+ /* dynamic widgets cannot have volatile kcontrols */
+ list_for_each_entry(scontrol, &sdev->kcontrol_list, list)
+ if (scontrol->comp_id == swidget->comp_id &&
+ (scontrol->access & SNDRV_CTL_ELEM_ACCESS_VOLATILE)) {
+ dev_err(sdev->dev,
+ "error: volatile control found for dynamic widget %s\n",
+ swidget->widget->name);
+ return -EINVAL;
+ }
}
- return ret;
-}
-
-int snd_sof_complete_pipeline(struct device *dev,
- struct snd_sof_widget *swidget)
-{
- struct snd_sof_dev *sdev = dev_get_drvdata(dev);
- struct sof_ipc_pipe_ready ready;
- struct sof_ipc_reply reply;
- int ret;
-
- dev_dbg(dev, "tplg: complete pipeline %s id %d\n",
- swidget->widget->name, swidget->comp_id);
+ /* set the pipeline and apply the dynamic_pipeline_widget_flag */
+ swidget->spipe = spipe;
+ swidget->dynamic_pipeline_widget = pipe_widget->dynamic_pipeline_widget;
- memset(&ready, 0, sizeof(ready));
- ready.hdr.size = sizeof(ready);
- ready.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_PIPE_COMPLETE;
- ready.comp_id = swidget->comp_id;
-
- ret = sof_ipc_tx_message(sdev->ipc,
- ready.hdr.cmd, &ready, sizeof(ready), &reply,
- sizeof(reply));
- if (ret < 0)
- return ret;
- return 1;
+ return 0;
}
/* completion - called at completion of firmware loading */
-static void sof_complete(struct snd_soc_component *scomp)
+static int sof_complete(struct snd_soc_component *scomp)
{
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct snd_sof_widget *swidget;
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
+ const struct sof_ipc_tplg_widget_ops *widget_ops;
+ struct snd_sof_control *scontrol;
+ struct snd_sof_pipeline *spipe;
+ int ret;
- /* some widget types require completion notificattion */
- list_for_each_entry(swidget, &sdev->widget_list, list) {
- if (swidget->complete)
- continue;
+ widget_ops = tplg_ops ? tplg_ops->widget : NULL;
- switch (swidget->id) {
- case snd_soc_dapm_scheduler:
- swidget->complete =
- snd_sof_complete_pipeline(scomp->dev, swidget);
- break;
- default:
- break;
+ /* first update all control IPC structures based on the IPC version */
+ if (tplg_ops && tplg_ops->control_setup)
+ list_for_each_entry(scontrol, &sdev->kcontrol_list, list) {
+ ret = tplg_ops->control_setup(sdev, scontrol);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed updating IPC struct for control %s\n",
+ scontrol->name);
+ return ret;
+ }
}
- }
- /*
- * cache initial values of SOF kcontrols by reading DSP value over
- * IPC. It may be overwritten by alsa-mixer after booting up
- */
- snd_sof_cache_kcontrol_val(scomp);
-}
-/* manifest - optional to inform component of manifest */
-static int sof_manifest(struct snd_soc_component *scomp, int index,
- struct snd_soc_tplg_manifest *man)
-{
- u32 size;
- u32 abi_version;
+ /* set up the IPC structures for the pipeline widgets */
+ list_for_each_entry(spipe, &sdev->pipeline_list, list) {
+ struct snd_sof_widget *pipe_widget = spipe->pipe_widget;
+ struct snd_sof_widget *swidget;
- size = le32_to_cpu(man->priv.size);
+ pipe_widget->instance_id = -EINVAL;
- /* backward compatible with tplg without ABI info */
- if (!size) {
- dev_dbg(scomp->dev, "No topology ABI info\n");
- return 0;
+ /* Update the scheduler widget's IPC structure */
+ if (widget_ops && widget_ops[pipe_widget->id].ipc_setup) {
+ ret = widget_ops[pipe_widget->id].ipc_setup(pipe_widget);
+ if (ret < 0) {
+ dev_err(sdev->dev, "failed updating IPC struct for %s\n",
+ pipe_widget->widget->name);
+ return ret;
+ }
+ }
+
+ /* set the pipeline and update the IPC structure for the non scheduler widgets */
+ list_for_each_entry(swidget, &sdev->widget_list, list)
+ if (swidget->widget->id != snd_soc_dapm_scheduler &&
+ swidget->pipeline_id == pipe_widget->pipeline_id) {
+ ret = sof_set_widget_pipeline(sdev, spipe, swidget);
+ if (ret < 0)
+ return ret;
+
+ if (widget_ops && widget_ops[swidget->id].ipc_setup) {
+ ret = widget_ops[swidget->id].ipc_setup(swidget);
+ if (ret < 0) {
+ dev_err(sdev->dev,
+ "failed updating IPC struct for %s\n",
+ swidget->widget->name);
+ return ret;
+ }
+ }
+ }
}
- if (size != SOF_TPLG_ABI_SIZE) {
- dev_err(scomp->dev, "error: invalid topology ABI size\n");
- return -EINVAL;
+ /* verify topology components loading including dynamic pipelines */
+ if (sof_debug_check_flag(SOF_DBG_VERIFY_TPLG)) {
+ if (tplg_ops && tplg_ops->set_up_all_pipelines &&
+ tplg_ops->tear_down_all_pipelines) {
+ ret = tplg_ops->set_up_all_pipelines(sdev, true);
+ if (ret < 0) {
+ dev_err(sdev->dev, "Failed to set up all topology pipelines: %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = tplg_ops->tear_down_all_pipelines(sdev, true);
+ if (ret < 0) {
+ dev_err(sdev->dev, "Failed to tear down topology pipelines: %d\n",
+ ret);
+ return ret;
+ }
+ }
}
- dev_info(scomp->dev,
- "Topology: ABI %d:%d:%d Kernel ABI %d:%d:%d\n",
- man->priv.data[0], man->priv.data[1],
- man->priv.data[2], SOF_ABI_MAJOR, SOF_ABI_MINOR,
- SOF_ABI_PATCH);
+ /* set up static pipelines */
+ if (tplg_ops && tplg_ops->set_up_all_pipelines)
+ return tplg_ops->set_up_all_pipelines(sdev, false);
- abi_version = SOF_ABI_VER(man->priv.data[0],
- man->priv.data[1],
- man->priv.data[2]);
+ return 0;
+}
- if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, abi_version)) {
- dev_err(scomp->dev, "error: incompatible topology ABI version\n");
- return -EINVAL;
- }
+/* manifest - optional to inform component of manifest */
+static int sof_manifest(struct snd_soc_component *scomp, int index,
+ struct snd_soc_tplg_manifest *man)
+{
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
- if (abi_version > SOF_ABI_VERSION) {
- if (!IS_ENABLED(CONFIG_SND_SOC_SOF_STRICT_ABI_CHECKS)) {
- dev_warn(scomp->dev, "warn: topology ABI is more recent than kernel\n");
- } else {
- dev_err(scomp->dev, "error: topology ABI is more recent than kernel\n");
- return -EINVAL;
- }
- }
+ if (tplg_ops && tplg_ops->parse_manifest)
+ return tplg_ops->parse_manifest(scomp, index, man);
return 0;
}
@@ -3564,6 +2276,7 @@ static const struct snd_soc_tplg_kcontrol_ops sof_io_ops[] = {
/* vendor specific bytes ext handlers available for binding */
static const struct snd_soc_tplg_bytes_ext_ops sof_bytes_ext_ops[] = {
{SOF_TPLG_KCTL_BYTES_ID, snd_sof_bytes_ext_get, snd_sof_bytes_ext_put},
+ {SOF_TPLG_KCTL_BYTES_VOLATILE_RO, snd_sof_bytes_ext_volatile_get},
};
static struct snd_soc_tplg_ops sof_tplg_ops = {
@@ -3603,8 +2316,148 @@ static struct snd_soc_tplg_ops sof_tplg_ops = {
.bytes_ext_ops_count = ARRAY_SIZE(sof_bytes_ext_ops),
};
+static int snd_sof_dspless_kcontrol(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ return 0;
+}
+
+static const struct snd_soc_tplg_kcontrol_ops sof_dspless_io_ops[] = {
+ {SOF_TPLG_KCTL_VOL_ID, snd_sof_dspless_kcontrol, snd_sof_dspless_kcontrol},
+ {SOF_TPLG_KCTL_BYTES_ID, snd_sof_dspless_kcontrol, snd_sof_dspless_kcontrol},
+ {SOF_TPLG_KCTL_ENUM_ID, snd_sof_dspless_kcontrol, snd_sof_dspless_kcontrol},
+ {SOF_TPLG_KCTL_SWITCH_ID, snd_sof_dspless_kcontrol, snd_sof_dspless_kcontrol},
+};
+
+static int snd_sof_dspless_bytes_ext_get(struct snd_kcontrol *kcontrol,
+ unsigned int __user *binary_data,
+ unsigned int size)
+{
+ return 0;
+}
+
+static int snd_sof_dspless_bytes_ext_put(struct snd_kcontrol *kcontrol,
+ const unsigned int __user *binary_data,
+ unsigned int size)
+{
+ return 0;
+}
+
+static const struct snd_soc_tplg_bytes_ext_ops sof_dspless_bytes_ext_ops[] = {
+ {SOF_TPLG_KCTL_BYTES_ID, snd_sof_dspless_bytes_ext_get, snd_sof_dspless_bytes_ext_put},
+ {SOF_TPLG_KCTL_BYTES_VOLATILE_RO, snd_sof_dspless_bytes_ext_get},
+};
+
+/* external widget init - used for any driver specific init */
+static int sof_dspless_widget_ready(struct snd_soc_component *scomp, int index,
+ struct snd_soc_dapm_widget *w,
+ struct snd_soc_tplg_dapm_widget *tw)
+{
+ if (WIDGET_IS_DAI(w->id)) {
+ static const struct sof_topology_token dai_tokens[] = {
+ {SOF_TKN_DAI_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_dai_type, 0}};
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct snd_soc_tplg_private *priv = &tw->priv;
+ struct snd_sof_widget *swidget;
+ struct snd_sof_dai *sdai;
+ int ret;
+
+ swidget = kzalloc(sizeof(*swidget), GFP_KERNEL);
+ if (!swidget)
+ return -ENOMEM;
+
+ sdai = kzalloc(sizeof(*sdai), GFP_KERNEL);
+ if (!sdai) {
+ kfree(swidget);
+ return -ENOMEM;
+ }
+
+ ret = sof_parse_tokens(scomp, &sdai->type, dai_tokens, ARRAY_SIZE(dai_tokens),
+ priv->array, le32_to_cpu(priv->size));
+ if (ret < 0) {
+ dev_err(scomp->dev, "Failed to parse DAI tokens for %s\n", tw->name);
+ kfree(swidget);
+ kfree(sdai);
+ return ret;
+ }
+
+ ret = sof_connect_dai_widget(scomp, w, tw, sdai);
+ if (ret) {
+ kfree(swidget);
+ kfree(sdai);
+ return ret;
+ }
+
+ swidget->scomp = scomp;
+ swidget->widget = w;
+ swidget->private = sdai;
+ mutex_init(&swidget->setup_mutex);
+ w->dobj.private = swidget;
+ list_add(&swidget->list, &sdev->widget_list);
+ }
+
+ return 0;
+}
+
+static int sof_dspless_widget_unload(struct snd_soc_component *scomp,
+ struct snd_soc_dobj *dobj)
+{
+ struct snd_soc_dapm_widget *w = container_of(dobj, struct snd_soc_dapm_widget, dobj);
+
+ if (WIDGET_IS_DAI(w->id)) {
+ struct snd_sof_widget *swidget = dobj->private;
+
+ sof_disconnect_dai_widget(scomp, w);
+
+ if (!swidget)
+ return 0;
+
+ /* remove and free swidget object */
+ list_del(&swidget->list);
+ kfree(swidget->private);
+ kfree(swidget);
+ }
+
+ return 0;
+}
+
+static int sof_dspless_link_load(struct snd_soc_component *scomp, int index,
+ struct snd_soc_dai_link *link,
+ struct snd_soc_tplg_link_config *cfg)
+{
+ link->platforms->name = dev_name(scomp->dev);
+
+ /* Set nonatomic property for FE dai links for FE-BE compatibility */
+ if (!link->no_pcm)
+ link->nonatomic = true;
+
+ return 0;
+}
+
+static struct snd_soc_tplg_ops sof_dspless_tplg_ops = {
+ /* external widget init - used for any driver specific init */
+ .widget_ready = sof_dspless_widget_ready,
+ .widget_unload = sof_dspless_widget_unload,
+
+ /* FE DAI - used for any driver specific init */
+ .dai_load = sof_dai_load,
+ .dai_unload = sof_dai_unload,
+
+ /* DAI link - used for any driver specific init */
+ .link_load = sof_dspless_link_load,
+
+ /* vendor specific kcontrol handlers available for binding */
+ .io_ops = sof_dspless_io_ops,
+ .io_ops_count = ARRAY_SIZE(sof_dspless_io_ops),
+
+ /* vendor specific bytes ext handlers available for binding */
+ .bytes_ext_ops = sof_dspless_bytes_ext_ops,
+ .bytes_ext_ops_count = ARRAY_SIZE(sof_dspless_bytes_ext_ops),
+};
+
int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file)
{
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
const struct firmware *fw;
int ret;
@@ -3614,12 +2467,16 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file)
if (ret < 0) {
dev_err(scomp->dev, "error: tplg request firmware %s failed err: %d\n",
file, ret);
+ dev_err(scomp->dev,
+ "you may need to download the firmware from https://github.com/thesofproject/sof-bin/\n");
return ret;
}
- ret = snd_soc_tplg_component_load(scomp,
- &sof_tplg_ops, fw,
- SND_SOC_TPLG_INDEX_ALL);
+ if (sdev->dspless_mode_selected)
+ ret = snd_soc_tplg_component_load(scomp, &sof_dspless_tplg_ops, fw);
+ else
+ ret = snd_soc_tplg_component_load(scomp, &sof_tplg_ops, fw);
+
if (ret < 0) {
dev_err(scomp->dev, "error: tplg component load failed %d\n",
ret);
@@ -3627,6 +2484,10 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file)
}
release_firmware(fw);
+
+ if (ret >= 0 && sdev->led_present)
+ ret = snd_ctl_led_request();
+
return ret;
}
EXPORT_SYMBOL(snd_sof_load_topology);
diff --git a/sound/soc/sof/trace.c b/sound/soc/sof/trace.c
index 69889241a092..b2ab51e5214a 100644
--- a/sound/soc/sof/trace.c
+++ b/sound/soc/sof/trace.c
@@ -1,352 +1,53 @@
-// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
-//
-// This file is provided under a dual BSD/GPLv2 license. When using or
-// redistributing this file, you may do so under either license.
-//
-// Copyright(c) 2018 Intel Corporation. All rights reserved.
-//
-// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
+// SPDX-License-Identifier: GPL-2.0-only
//
+// Copyright(c) 2022 Intel Corporation. All rights reserved.
-#include <linux/debugfs.h>
-#include <linux/sched/signal.h>
#include "sof-priv.h"
-#include "ops.h"
-
-static size_t sof_trace_avail(struct snd_sof_dev *sdev,
- loff_t pos, size_t buffer_size)
-{
- loff_t host_offset = READ_ONCE(sdev->host_offset);
-
- /*
- * If host offset is less than local pos, it means write pointer of
- * host DMA buffer has been wrapped. We should output the trace data
- * at the end of host DMA buffer at first.
- */
- if (host_offset < pos)
- return buffer_size - pos;
-
- /* If there is available trace data now, it is unnecessary to wait. */
- if (host_offset > pos)
- return host_offset - pos;
-
- return 0;
-}
-
-static size_t sof_wait_trace_avail(struct snd_sof_dev *sdev,
- loff_t pos, size_t buffer_size)
-{
- wait_queue_entry_t wait;
- size_t ret = sof_trace_avail(sdev, pos, buffer_size);
-
- /* data immediately available */
- if (ret)
- return ret;
-
- if (!sdev->dtrace_is_enabled && sdev->dtrace_draining) {
- /*
- * tracing has ended and all traces have been
- * read by client, return EOF
- */
- sdev->dtrace_draining = false;
- return 0;
- }
-
- /* wait for available trace data from FW */
- init_waitqueue_entry(&wait, current);
- set_current_state(TASK_INTERRUPTIBLE);
- add_wait_queue(&sdev->trace_sleep, &wait);
-
- if (!signal_pending(current)) {
- /* set timeout to max value, no error code */
- schedule_timeout(MAX_SCHEDULE_TIMEOUT);
- }
- remove_wait_queue(&sdev->trace_sleep, &wait);
-
- return sof_trace_avail(sdev, pos, buffer_size);
-}
-
-static ssize_t sof_dfsentry_trace_read(struct file *file, char __user *buffer,
- size_t count, loff_t *ppos)
-{
- struct snd_sof_dfsentry *dfse = file->private_data;
- struct snd_sof_dev *sdev = dfse->sdev;
- unsigned long rem;
- loff_t lpos = *ppos;
- size_t avail, buffer_size = dfse->size;
- u64 lpos_64;
-
- /* make sure we know about any failures on the DSP side */
- sdev->dtrace_error = false;
-
- /* check pos and count */
- if (lpos < 0)
- return -EINVAL;
- if (!count)
- return 0;
-
- /* check for buffer wrap and count overflow */
- lpos_64 = lpos;
- lpos = do_div(lpos_64, buffer_size);
-
- if (count > buffer_size - lpos) /* min() not used to avoid sparse warnings */
- count = buffer_size - lpos;
-
- /* get available count based on current host offset */
- avail = sof_wait_trace_avail(sdev, lpos, buffer_size);
- if (sdev->dtrace_error) {
- dev_err(sdev->dev, "error: trace IO error\n");
- return -EIO;
- }
-
- /* make sure count is <= avail */
- count = avail > count ? count : avail;
-
- /* copy available trace data to debugfs */
- rem = copy_to_user(buffer, ((u8 *)(dfse->buf) + lpos), count);
- if (rem)
- return -EFAULT;
-
- *ppos += count;
-
- /* move debugfs reading position */
- return count;
-}
-
-static int sof_dfsentry_trace_release(struct inode *inode, struct file *file)
-{
- struct snd_sof_dfsentry *dfse = inode->i_private;
- struct snd_sof_dev *sdev = dfse->sdev;
-
- /* avoid duplicate traces at next open */
- if (!sdev->dtrace_is_enabled)
- sdev->host_offset = 0;
-
- return 0;
-}
-
-static const struct file_operations sof_dfs_trace_fops = {
- .open = simple_open,
- .read = sof_dfsentry_trace_read,
- .llseek = default_llseek,
- .release = sof_dfsentry_trace_release,
-};
-
-static int trace_debugfs_create(struct snd_sof_dev *sdev)
-{
- struct snd_sof_dfsentry *dfse;
-
- if (!sdev)
- return -EINVAL;
-
- dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL);
- if (!dfse)
- return -ENOMEM;
- dfse->type = SOF_DFSENTRY_TYPE_BUF;
- dfse->buf = sdev->dmatb.area;
- dfse->size = sdev->dmatb.bytes;
- dfse->sdev = sdev;
-
- debugfs_create_file("trace", 0444, sdev->debugfs_root, dfse,
- &sof_dfs_trace_fops);
-
- return 0;
-}
-
-int snd_sof_init_trace_ipc(struct snd_sof_dev *sdev)
+int sof_fw_trace_init(struct snd_sof_dev *sdev)
{
- struct sof_ipc_fw_ready *ready = &sdev->fw_ready;
- struct sof_ipc_fw_version *v = &ready->version;
- struct sof_ipc_dma_trace_params_ext params;
- struct sof_ipc_reply ipc_reply;
- int ret;
-
- if (!sdev->dtrace_is_supported)
- return 0;
-
- if (sdev->dtrace_is_enabled || !sdev->dma_trace_pages)
- return -EINVAL;
-
- /* set IPC parameters */
- params.hdr.cmd = SOF_IPC_GLB_TRACE_MSG;
- /* PARAMS_EXT is only supported from ABI 3.7.0 onwards */
- if (v->abi_version >= SOF_ABI_VER(3, 7, 0)) {
- params.hdr.size = sizeof(struct sof_ipc_dma_trace_params_ext);
- params.hdr.cmd |= SOF_IPC_TRACE_DMA_PARAMS_EXT;
- params.timestamp_ns = ktime_get(); /* in nanosecond */
- } else {
- params.hdr.size = sizeof(struct sof_ipc_dma_trace_params);
- params.hdr.cmd |= SOF_IPC_TRACE_DMA_PARAMS;
- }
- params.buffer.phy_addr = sdev->dmatp.addr;
- params.buffer.size = sdev->dmatb.bytes;
- params.buffer.pages = sdev->dma_trace_pages;
- params.stream_tag = 0;
-
- sdev->host_offset = 0;
- sdev->dtrace_draining = false;
-
- ret = snd_sof_dma_trace_init(sdev, &params.stream_tag);
- if (ret < 0) {
- dev_err(sdev->dev,
- "error: fail in snd_sof_dma_trace_init %d\n", ret);
- return ret;
- }
- dev_dbg(sdev->dev, "stream_tag: %d\n", params.stream_tag);
-
- /* send IPC to the DSP */
- ret = sof_ipc_tx_message(sdev->ipc,
- params.hdr.cmd, &params, sizeof(params),
- &ipc_reply, sizeof(ipc_reply));
- if (ret < 0) {
- dev_err(sdev->dev,
- "error: can't set params for DMA for trace %d\n", ret);
- goto trace_release;
- }
-
- ret = snd_sof_dma_trace_trigger(sdev, SNDRV_PCM_TRIGGER_START);
- if (ret < 0) {
- dev_err(sdev->dev,
- "error: snd_sof_dma_trace_trigger: start: %d\n", ret);
- goto trace_release;
- }
-
- sdev->dtrace_is_enabled = true;
+ const struct sof_ipc_fw_tracing_ops *fw_tracing = sof_ipc_get_ops(sdev, fw_tracing);
- return 0;
+ if (!fw_tracing) {
+ dev_info(sdev->dev, "Firmware tracing is not available\n");
+ sdev->fw_trace_is_supported = false;
-trace_release:
- snd_sof_dma_trace_release(sdev);
- return ret;
-}
-
-int snd_sof_init_trace(struct snd_sof_dev *sdev)
-{
- int ret;
-
- if (!sdev->dtrace_is_supported)
return 0;
-
- /* set false before start initialization */
- sdev->dtrace_is_enabled = false;
-
- /* allocate trace page table buffer */
- ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->dev,
- PAGE_SIZE, &sdev->dmatp);
- if (ret < 0) {
- dev_err(sdev->dev,
- "error: can't alloc page table for trace %d\n", ret);
- return ret;
- }
-
- /* allocate trace data buffer */
- ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV_SG, sdev->dev,
- DMA_BUF_SIZE_FOR_TRACE, &sdev->dmatb);
- if (ret < 0) {
- dev_err(sdev->dev,
- "error: can't alloc buffer for trace %d\n", ret);
- goto page_err;
}
- /* create compressed page table for audio firmware */
- ret = snd_sof_create_page_table(sdev->dev, &sdev->dmatb,
- sdev->dmatp.area, sdev->dmatb.bytes);
- if (ret < 0)
- goto table_err;
-
- sdev->dma_trace_pages = ret;
- dev_dbg(sdev->dev, "dma_trace_pages: %d\n", sdev->dma_trace_pages);
-
- if (sdev->first_boot) {
- ret = trace_debugfs_create(sdev);
- if (ret < 0)
- goto table_err;
- }
-
- init_waitqueue_head(&sdev->trace_sleep);
-
- ret = snd_sof_init_trace_ipc(sdev);
- if (ret < 0)
- goto table_err;
-
- return 0;
-table_err:
- sdev->dma_trace_pages = 0;
- snd_dma_free_pages(&sdev->dmatb);
-page_err:
- snd_dma_free_pages(&sdev->dmatp);
- return ret;
+ return fw_tracing->init(sdev);
}
-EXPORT_SYMBOL(snd_sof_init_trace);
-int snd_sof_trace_update_pos(struct snd_sof_dev *sdev,
- struct sof_ipc_dma_trace_posn *posn)
+void sof_fw_trace_free(struct snd_sof_dev *sdev)
{
- if (!sdev->dtrace_is_supported)
- return 0;
-
- if (sdev->dtrace_is_enabled && sdev->host_offset != posn->host_offset) {
- sdev->host_offset = posn->host_offset;
- wake_up(&sdev->trace_sleep);
- }
-
- if (posn->overflow != 0)
- dev_err(sdev->dev,
- "error: DSP trace buffer overflow %u bytes. Total messages %d\n",
- posn->overflow, posn->messages);
+ if (!sdev->fw_trace_is_supported)
+ return;
- return 0;
+ if (sdev->ipc->ops->fw_tracing->free)
+ sdev->ipc->ops->fw_tracing->free(sdev);
}
-/* an error has occurred within the DSP that prevents further trace */
-void snd_sof_trace_notify_for_error(struct snd_sof_dev *sdev)
+void sof_fw_trace_fw_crashed(struct snd_sof_dev *sdev)
{
- if (!sdev->dtrace_is_supported)
+ if (!sdev->fw_trace_is_supported)
return;
- if (sdev->dtrace_is_enabled) {
- dev_err(sdev->dev, "error: waking up any trace sleepers\n");
- sdev->dtrace_error = true;
- wake_up(&sdev->trace_sleep);
- }
+ if (sdev->ipc->ops->fw_tracing->fw_crashed)
+ sdev->ipc->ops->fw_tracing->fw_crashed(sdev);
}
-EXPORT_SYMBOL(snd_sof_trace_notify_for_error);
-void snd_sof_release_trace(struct snd_sof_dev *sdev)
+void sof_fw_trace_suspend(struct snd_sof_dev *sdev, pm_message_t pm_state)
{
- int ret;
-
- if (!sdev->dtrace_is_supported || !sdev->dtrace_is_enabled)
+ if (!sdev->fw_trace_is_supported)
return;
- ret = snd_sof_dma_trace_trigger(sdev, SNDRV_PCM_TRIGGER_STOP);
- if (ret < 0)
- dev_err(sdev->dev,
- "error: snd_sof_dma_trace_trigger: stop: %d\n", ret);
-
- ret = snd_sof_dma_trace_release(sdev);
- if (ret < 0)
- dev_err(sdev->dev,
- "error: fail in snd_sof_dma_trace_release %d\n", ret);
-
- sdev->dtrace_is_enabled = false;
- sdev->dtrace_draining = true;
- wake_up(&sdev->trace_sleep);
+ sdev->ipc->ops->fw_tracing->suspend(sdev, pm_state);
}
-EXPORT_SYMBOL(snd_sof_release_trace);
-void snd_sof_free_trace(struct snd_sof_dev *sdev)
+int sof_fw_trace_resume(struct snd_sof_dev *sdev)
{
- if (!sdev->dtrace_is_supported)
- return;
-
- snd_sof_release_trace(sdev);
+ if (!sdev->fw_trace_is_supported)
+ return 0;
- if (sdev->dma_trace_pages) {
- snd_dma_free_pages(&sdev->dmatb);
- snd_dma_free_pages(&sdev->dmatp);
- sdev->dma_trace_pages = 0;
- }
+ return sdev->ipc->ops->fw_tracing->resume(sdev);
}
-EXPORT_SYMBOL(snd_sof_free_trace);
diff --git a/sound/soc/sof/utils.c b/sound/soc/sof/utils.c
deleted file mode 100644
index 5539d3afbe8f..000000000000
--- a/sound/soc/sof/utils.c
+++ /dev/null
@@ -1,172 +0,0 @@
-// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
-//
-// This file is provided under a dual BSD/GPLv2 license. When using or
-// redistributing this file, you may do so under either license.
-//
-// Copyright(c) 2018 Intel Corporation. All rights reserved.
-//
-// Author: Keyon Jie <yang.jie@linux.intel.com>
-//
-
-#include <linux/io-64-nonatomic-lo-hi.h>
-#include <linux/platform_device.h>
-#include <asm/unaligned.h>
-#include <sound/soc.h>
-#include <sound/sof.h>
-#include "sof-priv.h"
-
-/*
- * Register IO
- *
- * The sof_io_xyz() wrappers are typically referenced in snd_sof_dsp_ops
- * structures and cannot be inlined.
- */
-
-void sof_io_write(struct snd_sof_dev *sdev, void __iomem *addr, u32 value)
-{
- writel(value, addr);
-}
-EXPORT_SYMBOL(sof_io_write);
-
-u32 sof_io_read(struct snd_sof_dev *sdev, void __iomem *addr)
-{
- return readl(addr);
-}
-EXPORT_SYMBOL(sof_io_read);
-
-void sof_io_write64(struct snd_sof_dev *sdev, void __iomem *addr, u64 value)
-{
- writeq(value, addr);
-}
-EXPORT_SYMBOL(sof_io_write64);
-
-u64 sof_io_read64(struct snd_sof_dev *sdev, void __iomem *addr)
-{
- return readq(addr);
-}
-EXPORT_SYMBOL(sof_io_read64);
-
-/*
- * IPC Mailbox IO
- */
-
-void sof_mailbox_write(struct snd_sof_dev *sdev, u32 offset,
- void *message, size_t bytes)
-{
- void __iomem *dest = sdev->bar[sdev->mailbox_bar] + offset;
-
- memcpy_toio(dest, message, bytes);
-}
-EXPORT_SYMBOL(sof_mailbox_write);
-
-void sof_mailbox_read(struct snd_sof_dev *sdev, u32 offset,
- void *message, size_t bytes)
-{
- void __iomem *src = sdev->bar[sdev->mailbox_bar] + offset;
-
- memcpy_fromio(message, src, bytes);
-}
-EXPORT_SYMBOL(sof_mailbox_read);
-
-/*
- * Memory copy.
- */
-
-void sof_block_write(struct snd_sof_dev *sdev, u32 bar, u32 offset, void *src,
- size_t size)
-{
- void __iomem *dest = sdev->bar[bar] + offset;
- const u8 *src_byte = src;
- u32 affected_mask;
- u32 tmp;
- int m, n;
-
- m = size / 4;
- n = size % 4;
-
- /* __iowrite32_copy use 32bit size values so divide by 4 */
- __iowrite32_copy(dest, src, m);
-
- if (n) {
- affected_mask = (1 << (8 * n)) - 1;
-
- /* first read the 32bit data of dest, then change affected
- * bytes, and write back to dest. For unaffected bytes, it
- * should not be changed
- */
- tmp = ioread32(dest + m * 4);
- tmp &= ~affected_mask;
-
- tmp |= *(u32 *)(src_byte + m * 4) & affected_mask;
- iowrite32(tmp, dest + m * 4);
- }
-}
-EXPORT_SYMBOL(sof_block_write);
-
-void sof_block_read(struct snd_sof_dev *sdev, u32 bar, u32 offset, void *dest,
- size_t size)
-{
- void __iomem *src = sdev->bar[bar] + offset;
-
- memcpy_fromio(dest, src, size);
-}
-EXPORT_SYMBOL(sof_block_read);
-
-/*
- * Generic buffer page table creation.
- * Take the each physical page address and drop the least significant unused
- * bits from each (based on PAGE_SIZE). Then pack valid page address bits
- * into compressed page table.
- */
-
-int snd_sof_create_page_table(struct device *dev,
- struct snd_dma_buffer *dmab,
- unsigned char *page_table, size_t size)
-{
- int i, pages;
-
- pages = snd_sgbuf_aligned_pages(size);
-
- dev_dbg(dev, "generating page table for %p size 0x%zx pages %d\n",
- dmab->area, size, pages);
-
- for (i = 0; i < pages; i++) {
- /*
- * The number of valid address bits for each page is 20.
- * idx determines the byte position within page_table
- * where the current page's address is stored
- * in the compressed page_table.
- * This can be calculated by multiplying the page number by 2.5.
- */
- u32 idx = (5 * i) >> 1;
- u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT;
- u8 *pg_table;
-
- dev_vdbg(dev, "pfn i %i idx %d pfn %x\n", i, idx, pfn);
-
- pg_table = (u8 *)(page_table + idx);
-
- /*
- * pagetable compression:
- * byte 0 byte 1 byte 2 byte 3 byte 4 byte 5
- * ___________pfn 0__________ __________pfn 1___________ _pfn 2...
- * .... .... .... .... .... .... .... .... .... .... ....
- * It is created by:
- * 1. set current location to 0, PFN index i to 0
- * 2. put pfn[i] at current location in Little Endian byte order
- * 3. calculate an intermediate value as
- * x = (pfn[i+1] << 4) | (pfn[i] & 0xf)
- * 4. put x at offset (current location + 2) in LE byte order
- * 5. increment current location by 5 bytes, increment i by 2
- * 6. continue to (2)
- */
- if (i & 1)
- put_unaligned_le32((pg_table[0] & 0xf) | pfn << 4,
- pg_table);
- else
- put_unaligned_le32(pfn, pg_table);
- }
-
- return pages;
-}
-EXPORT_SYMBOL(snd_sof_create_page_table);
diff --git a/sound/soc/sof/xtensa/core.c b/sound/soc/sof/xtensa/core.c
index bbb9a2282ed9..7c91a919eadc 100644
--- a/sound/soc/sof/xtensa/core.c
+++ b/sound/soc/sof/xtensa/core.c
@@ -81,33 +81,39 @@ static const struct xtensa_exception_cause xtensa_exception_causes[] = {
};
/* only need xtensa atm */
-static void xtensa_dsp_oops(struct snd_sof_dev *sdev, void *oops)
+static void xtensa_dsp_oops(struct snd_sof_dev *sdev, const char *level, void *oops)
{
struct sof_ipc_dsp_oops_xtensa *xoops = oops;
int i;
- dev_err(sdev->dev, "error: DSP Firmware Oops\n");
+ dev_printk(level, sdev->dev, "error: DSP Firmware Oops\n");
for (i = 0; i < ARRAY_SIZE(xtensa_exception_causes); i++) {
if (xtensa_exception_causes[i].id == xoops->exccause) {
- dev_err(sdev->dev, "error: Exception Cause: %s, %s\n",
- xtensa_exception_causes[i].msg,
- xtensa_exception_causes[i].description);
+ dev_printk(level, sdev->dev,
+ "error: Exception Cause: %s, %s\n",
+ xtensa_exception_causes[i].msg,
+ xtensa_exception_causes[i].description);
}
}
- dev_err(sdev->dev, "EXCCAUSE 0x%8.8x EXCVADDR 0x%8.8x PS 0x%8.8x SAR 0x%8.8x\n",
- xoops->exccause, xoops->excvaddr, xoops->ps, xoops->sar);
- dev_err(sdev->dev, "EPC1 0x%8.8x EPC2 0x%8.8x EPC3 0x%8.8x EPC4 0x%8.8x",
- xoops->epc1, xoops->epc2, xoops->epc3, xoops->epc4);
- dev_err(sdev->dev, "EPC5 0x%8.8x EPC6 0x%8.8x EPC7 0x%8.8x DEPC 0x%8.8x",
- xoops->epc5, xoops->epc6, xoops->epc7, xoops->depc);
- dev_err(sdev->dev, "EPS2 0x%8.8x EPS3 0x%8.8x EPS4 0x%8.8x EPS5 0x%8.8x",
- xoops->eps2, xoops->eps3, xoops->eps4, xoops->eps5);
- dev_err(sdev->dev, "EPS6 0x%8.8x EPS7 0x%8.8x INTENABL 0x%8.8x INTERRU 0x%8.8x",
- xoops->eps6, xoops->eps7, xoops->intenable, xoops->interrupt);
+ dev_printk(level, sdev->dev,
+ "EXCCAUSE 0x%8.8x EXCVADDR 0x%8.8x PS 0x%8.8x SAR 0x%8.8x\n",
+ xoops->exccause, xoops->excvaddr, xoops->ps, xoops->sar);
+ dev_printk(level, sdev->dev,
+ "EPC1 0x%8.8x EPC2 0x%8.8x EPC3 0x%8.8x EPC4 0x%8.8x",
+ xoops->epc1, xoops->epc2, xoops->epc3, xoops->epc4);
+ dev_printk(level, sdev->dev,
+ "EPC5 0x%8.8x EPC6 0x%8.8x EPC7 0x%8.8x DEPC 0x%8.8x",
+ xoops->epc5, xoops->epc6, xoops->epc7, xoops->depc);
+ dev_printk(level, sdev->dev,
+ "EPS2 0x%8.8x EPS3 0x%8.8x EPS4 0x%8.8x EPS5 0x%8.8x",
+ xoops->eps2, xoops->eps3, xoops->eps4, xoops->eps5);
+ dev_printk(level, sdev->dev,
+ "EPS6 0x%8.8x EPS7 0x%8.8x INTENABL 0x%8.8x INTERRU 0x%8.8x",
+ xoops->eps6, xoops->eps7, xoops->intenable, xoops->interrupt);
}
-static void xtensa_stack(struct snd_sof_dev *sdev, void *oops, u32 *stack,
- u32 stack_words)
+static void xtensa_stack(struct snd_sof_dev *sdev, const char *level, void *oops,
+ u32 *stack, u32 stack_words)
{
struct sof_ipc_dsp_oops_xtensa *xoops = oops;
u32 stack_ptr = xoops->plat_hdr.stackptr;
@@ -115,20 +121,31 @@ static void xtensa_stack(struct snd_sof_dev *sdev, void *oops, u32 *stack,
unsigned char buf[4 * 8 + 3 + 1];
int i;
- dev_err(sdev->dev, "stack dump from 0x%8.8x\n", stack_ptr);
+ dev_printk(level, sdev->dev, "stack dump from 0x%8.8x\n", stack_ptr);
/*
* example output:
* 0x0049fbb0: 8000f2d0 0049fc00 6f6c6c61 00632e63
*/
for (i = 0; i < stack_words; i += 4) {
- hex_dump_to_buffer(stack + i * 4, 16, 16, 4,
+ hex_dump_to_buffer(stack + i, 16, 16, 4,
buf, sizeof(buf), false);
- dev_err(sdev->dev, "0x%08x: %s\n", stack_ptr + i, buf);
+ dev_printk(level, sdev->dev, "0x%08x: %s\n", stack_ptr + i * 4, buf);
+ }
+
+ if (!xoops->plat_hdr.numaregs)
+ return;
+
+ dev_printk(level, sdev->dev, "AR registers:\n");
+ /* the number of ar registers is a multiple of 4 */
+ for (i = 0; i < xoops->plat_hdr.numaregs; i += 4) {
+ hex_dump_to_buffer(xoops->ar + i, 16, 16, 4,
+ buf, sizeof(buf), false);
+ dev_printk(level, sdev->dev, "%#x: %s\n", i * 4, buf);
}
}
-const struct sof_arch_ops sof_xtensa_arch_ops = {
+const struct dsp_arch_ops sof_xtensa_arch_ops = {
.dsp_oops = xtensa_dsp_oops,
.dsp_stack = xtensa_stack,
};