Update Linux to v5.10.109
Sourced from [1]
[1] https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.109.tar.xz
Change-Id: I19bca9fc6762d4e63bcf3e4cba88bbe560d9c76c
Signed-off-by: Olivier Deprez <olivier.deprez@arm.com>
diff --git a/sound/soc/sof/Kconfig b/sound/soc/sof/Kconfig
index bb8036a..8c1f082 100644
--- a/sound/soc/sof/Kconfig
+++ b/sound/soc/sof/Kconfig
@@ -14,8 +14,6 @@
depends on PCI
select SND_SOC_SOF
select SND_SOC_ACPI if ACPI
- select SND_SOC_SOF_OPTIONS
- select SND_SOC_SOF_INTEL_PCI if SND_SOC_SOF_INTEL_TOPLEVEL
help
This adds support for PCI enumeration. This option is
required to enable Intel Skylake+ devices
@@ -27,12 +25,10 @@
depends on ACPI || COMPILE_TEST
select SND_SOC_SOF
select SND_SOC_ACPI if ACPI
- select SND_SOC_SOF_OPTIONS
- select SND_SOC_SOF_INTEL_ACPI if SND_SOC_SOF_INTEL_TOPLEVEL
select IOSF_MBI if X86 && PCI
help
This adds support for ACPI enumeration. This option is required
- to enable Intel Haswell/Broadwell/Baytrail/Cherrytrail devices
+ to enable Intel Broadwell/Baytrail/Cherrytrail devices
Say Y if you need this option
If unsure select "N".
@@ -40,19 +36,32 @@
tristate "SOF OF enumeration support"
depends on OF || COMPILE_TEST
select SND_SOC_SOF
- select SND_SOC_SOF_OPTIONS
help
This adds support for Device Tree enumeration. This option is
required to enable i.MX8 devices.
Say Y if you need this option. If unsure select "N".
-config SND_SOC_SOF_OPTIONS
- tristate
+config SND_SOC_SOF_DEBUG_PROBES
+ bool "SOF enable data probing"
+ select SND_SOC_COMPRESS
help
- This option is not user-selectable but automagically handled by
- 'select' statements at a higher level
+ 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".
-if SND_SOC_SOF_OPTIONS
+config SND_SOC_SOF_DEVELOPER_SUPPORT
+ bool "SOF developer options support"
+ depends on EXPERT
+ help
+ This option unlock 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
+
+if SND_SOC_SOF_DEVELOPER_SUPPORT
config SND_SOC_SOF_NOCODEC
tristate
@@ -64,6 +73,11 @@
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
+ 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
If unsure select "N".
@@ -142,6 +156,14 @@
Say Y if you want to enable caching the memory windows.
If unsure, select "N".
+config SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE
+ bool "SOF 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)
+ If unsure, select "N".
+
config SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST
bool "SOF enable IPC flood test"
help
@@ -150,9 +172,17 @@
Say Y if you want to enable IPC flood test.
If unsure, select "N".
+config SND_SOC_SOF_DEBUG_RETAIN_DSP_CONTEXT
+ bool "SOF retain DSP context on any FW exceptions"
+ help
+ This option keeps the DSP in D0 state so that firmware debug
+ information can be retained and dumped to userspace.
+ Say Y if you want to retain DSP context for FW exceptions.
+ If unsure, select "N".
+
endif ## SND_SOC_SOF_DEBUG
-endif ## SND_SOC_SOF_OPTIONS
+endif ## SND_SOC_SOF_DEVELOPER_SUPPORT
config SND_SOC_SOF
tristate
diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile
index b0a6f01..05718df 100644
--- a/sound/soc/sof/Makefile
+++ b/sound/soc/sof/Makefile
@@ -1,7 +1,8 @@
-# SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+# 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
+ control.o trace.o utils.o sof-audio.o
+snd-sof-$(CONFIG_SND_SOC_SOF_DEBUG_PROBES) += probe.o compress.o
snd-sof-pci-objs := sof-pci-dev.o
snd-sof-acpi-objs := sof-acpi-dev.o
diff --git a/sound/soc/sof/compress.c b/sound/soc/sof/compress.c
new file mode 100644
index 0000000..2d4969c
--- /dev/null
+++ b/sound/soc/sof/compress.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) 2019-2020 Intel Corporation. All rights reserved.
+//
+// Author: Cezary Rojewski <cezary.rojewski@intel.com>
+//
+
+#include <sound/soc.h>
+#include "compress.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);
+
+int sof_probe_compr_open(struct snd_compr_stream *cstream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_sof_dev *sdev =
+ snd_soc_component_get_drvdata(dai->component);
+ int ret;
+
+ 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;
+ }
+
+ sdev->extractor_stream_tag = ret;
+ return 0;
+}
+EXPORT_SYMBOL(sof_probe_compr_open);
+
+int sof_probe_compr_free(struct snd_compr_stream *cstream,
+ struct snd_soc_dai *dai)
+{
+ 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;
+ }
+
+ for (i = 0; i < num_desc; i++)
+ sof_ipc_probe_points_remove(sdev, &desc[i].buffer_id, 1);
+ kfree(desc);
+
+exit:
+ ret = sof_ipc_probe_deinit(sdev);
+ if (ret < 0)
+ dev_err(dai->dev, "Failed to deinit probe: %d\n", ret);
+
+ sdev->extractor_stream_tag = SOF_PROBE_INVALID_NODE_ID;
+ snd_compr_free_pages(cstream);
+
+ return snd_sof_probe_compr_free(sdev, cstream, dai);
+}
+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)
+{
+ struct snd_compr_runtime *rtd = cstream->runtime;
+ struct snd_sof_dev *sdev =
+ snd_soc_component_get_drvdata(dai->component);
+ int ret;
+
+ 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);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_sof_probe_compr_set_params(sdev, cstream, params, dai);
+ if (ret < 0)
+ return ret;
+
+ ret = sof_ipc_probe_init(sdev, sdev->extractor_stream_tag,
+ rtd->dma_bytes);
+ if (ret < 0) {
+ dev_err(dai->dev, "Failed to init probe: %d\n", ret);
+ return ret;
+ }
+
+ 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)
+{
+ struct snd_sof_dev *sdev =
+ snd_soc_component_get_drvdata(dai->component);
+
+ return snd_sof_probe_compr_trigger(sdev, cstream, cmd, dai);
+}
+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)
+{
+ struct snd_sof_dev *sdev =
+ snd_soc_component_get_drvdata(dai->component);
+
+ return snd_sof_probe_compr_pointer(sdev, cstream, tstamp, dai);
+}
+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)
+{
+ 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;
+}
+EXPORT_SYMBOL(sof_probe_compr_copy);
diff --git a/sound/soc/sof/compress.h b/sound/soc/sof/compress.h
new file mode 100644
index 0000000..ca8790b
--- /dev/null
+++ b/sound/soc/sof/compress.h
@@ -0,0 +1,32 @@
+/* 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 2b8711e..0352d2b 100644
--- a/sound/soc/sof/control.c
+++ b/sound/soc/sof/control.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -11,7 +11,39 @@
/* Mixer Controls */
#include <linux/pm_runtime.h>
+#include <linux/leds.h>
#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)
{
@@ -57,7 +89,7 @@
struct soc_mixer_control *sm =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_sof_control *scontrol = sm->dobj.private;
- struct snd_sof_dev *sdev = scontrol->sdev;
+ 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;
@@ -73,8 +105,8 @@
}
/* notify DSP of mixer updates */
- if (pm_runtime_active(sdev->dev))
- snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol,
+ 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,
@@ -104,7 +136,7 @@
struct soc_mixer_control *sm =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_sof_control *scontrol = sm->dobj.private;
- struct snd_sof_dev *sdev = scontrol->sdev;
+ 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;
@@ -118,9 +150,12 @@
cdata->chanv[i].value = value;
}
+ if (scontrol->led_ctl.use_led)
+ update_mute_led(scontrol, kcontrol, ucontrol);
+
/* notify DSP of mixer updates */
- if (pm_runtime_active(sdev->dev))
- snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol,
+ 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,
@@ -151,7 +186,7 @@
struct soc_enum *se =
(struct soc_enum *)kcontrol->private_value;
struct snd_sof_control *scontrol = se->dobj.private;
- struct snd_sof_dev *sdev = scontrol->sdev;
+ 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;
@@ -166,8 +201,8 @@
}
/* notify DSP of enum updates */
- if (pm_runtime_active(sdev->dev))
- snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol,
+ 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,
@@ -182,33 +217,32 @@
struct soc_bytes_ext *be =
(struct soc_bytes_ext *)kcontrol->private_value;
struct snd_sof_control *scontrol = be->dobj.private;
- struct snd_sof_dev *sdev = scontrol->sdev;
+ 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(sdev->dev,
+ dev_err_ratelimited(scomp->dev,
"error: data max %d exceeds ucontrol data array size\n",
be->max);
return -EINVAL;
}
- size = data->size + sizeof(*data);
- if (size > be->max) {
- dev_err_ratelimited(sdev->dev,
- "error: DSP sent %zu bytes max is %d\n",
- size, be->max);
- ret = -EINVAL;
- goto out;
+ /* be->max has been verified to be >= sizeof(struct sof_abi_hdr) */
+ if (data->size > be->max - sizeof(*data)) {
+ dev_err_ratelimited(scomp->dev,
+ "error: %u bytes of control data is invalid, max is %zu\n",
+ data->size, be->max - sizeof(*data));
+ return -EINVAL;
}
+ size = data->size + sizeof(*data);
+
/* 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,
@@ -217,31 +251,34 @@
struct soc_bytes_ext *be =
(struct soc_bytes_ext *)kcontrol->private_value;
struct snd_sof_control *scontrol = be->dobj.private;
- struct snd_sof_dev *sdev = scontrol->sdev;
+ 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);
+ size_t size;
if (be->max > sizeof(ucontrol->value.bytes.data)) {
- dev_err_ratelimited(sdev->dev,
+ 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(sdev->dev,
- "error: size too big %zu bytes max is %d\n",
- size, be->max);
+ /* be->max has been verified to be >= sizeof(struct sof_abi_hdr) */
+ if (data->size > be->max - sizeof(*data)) {
+ dev_err_ratelimited(scomp->dev,
+ "error: data size too big %u bytes max is %zu\n",
+ data->size, be->max - 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(sdev->dev))
- snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol,
+ 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,
@@ -257,12 +294,16 @@
struct soc_bytes_ext *be =
(struct soc_bytes_ext *)kcontrol->private_value;
struct snd_sof_control *scontrol = be->dobj.private;
- struct snd_sof_dev *sdev = scontrol->sdev;
+ 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;
+ /* make sure we have at least a header */
+ if (size < sizeof(struct snd_ctl_tlv))
+ return -EINVAL;
+
/*
* The beginning of bytes data contains a header from where
* the length (as bytes) is needed to know the correct copy
@@ -271,16 +312,23 @@
if (copy_from_user(&header, tlvd, sizeof(const 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, "error: 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 > be->max) {
- dev_err_ratelimited(sdev->dev, "error: Bytes data size %d exceeds max %d.\n",
+ dev_err_ratelimited(scomp->dev, "error: Bytes data size %d exceeds max %d.\n",
header.length, be->max);
return -EINVAL;
}
/* Check that header id matches the command */
if (header.numid != scontrol->cmd) {
- dev_err_ratelimited(sdev->dev,
+ dev_err_ratelimited(scomp->dev,
"error: incorrect numid %d\n",
header.numid);
return -EINVAL;
@@ -290,26 +338,27 @@
return -EFAULT;
if (cdata->data->magic != SOF_ABI_MAGIC) {
- dev_err_ratelimited(sdev->dev,
+ dev_err_ratelimited(scomp->dev,
"error: Wrong ABI magic 0x%08x.\n",
cdata->data->magic);
return -EINVAL;
}
if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, cdata->data->abi)) {
- dev_err_ratelimited(sdev->dev, "error: Incompatible ABI version 0x%08x.\n",
+ dev_err_ratelimited(scomp->dev, "error: Incompatible ABI version 0x%08x.\n",
cdata->data->abi);
return -EINVAL;
}
- if (cdata->data->size + sizeof(const struct sof_abi_hdr) > be->max) {
- dev_err_ratelimited(sdev->dev, "error: Mismatch in ABI data size (truncated?).\n");
+ /* be->max has been verified to be >= sizeof(struct sof_abi_hdr) */
+ if (cdata->data->size > be->max - sizeof(const struct sof_abi_hdr)) {
+ dev_err_ratelimited(scomp->dev, "error: Mismatch in ABI data size (truncated?).\n");
return -EINVAL;
}
/* notify DSP of byte control updates */
- if (pm_runtime_active(sdev->dev))
- snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol,
+ 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,
@@ -318,42 +367,60 @@
return 0;
}
-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)
{
- 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_sof_dev *sdev = scontrol->sdev;
+ 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;
+ struct snd_ctl_tlv __user *tlvd = (struct snd_ctl_tlv __user *)binary_data;
+ size_t data_size;
+ int ret;
+ int err;
/*
* Decrement the limit by ext bytes header size to
* ensure the user space buffer is not exceeded.
*/
- size -= sizeof(const struct snd_ctl_tlv);
+ if (size < sizeof(struct snd_ctl_tlv))
+ return -ENOSPC;
+ size -= sizeof(struct snd_ctl_tlv);
+
+ ret = pm_runtime_get_sync(scomp->dev);
+ if (ret < 0 && ret != -EACCES) {
+ dev_err_ratelimited(scomp->dev, "error: bytes_ext get failed to resume %d\n", ret);
+ pm_runtime_put_noidle(scomp->dev);
+ return ret;
+ }
/* 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);
+ /* get all the component data from DSP */
+ ret = snd_sof_ipc_set_get_comp_data(scontrol, SOF_IPC_COMP_GET_DATA, SOF_CTRL_TYPE_DATA_GET,
+ scontrol->cmd, false);
+ if (ret < 0)
+ goto out;
/* check data size doesn't exceed max coming from topology */
- if (data_size > be->max) {
- dev_err_ratelimited(sdev->dev, "error: user data size %d exceeds max size %d.\n",
- data_size, be->max);
+ if (cdata->data->size > be->max - sizeof(const struct sof_abi_hdr)) {
+ dev_err_ratelimited(scomp->dev, "error: user data size %d exceeds max size %zu.\n",
+ cdata->data->size,
+ be->max - sizeof(const struct sof_abi_hdr));
ret = -EINVAL;
goto out;
}
+ data_size = cdata->data->size + sizeof(const struct sof_abi_hdr);
+
+ /* make sure we don't exceed size provided by user space for data */
+ if (data_size > size) {
+ ret = -ENOSPC;
+ goto out;
+ }
+
header.numid = scontrol->cmd;
header.length = data_size;
if (copy_to_user(tlvd, &header, sizeof(const struct snd_ctl_tlv))) {
@@ -363,7 +430,62 @@
if (copy_to_user(tlvd->tlv, cdata->data, data_size))
ret = -EFAULT;
-
out:
+ pm_runtime_mark_last_busy(scomp->dev);
+ err = pm_runtime_put_autosuspend(scomp->dev);
+ if (err < 0)
+ dev_err_ratelimited(scomp->dev, "error: bytes_ext get failed to idle %d\n", 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 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;
+ 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;
+
+ /* check data size doesn't exceed max coming from topology */
+ if (cdata->data->size > be->max - sizeof(const struct sof_abi_hdr)) {
+ dev_err_ratelimited(scomp->dev, "error: user data size %d exceeds max size %zu.\n",
+ cdata->data->size,
+ be->max - sizeof(const struct sof_abi_hdr));
+ return -EINVAL;
+ }
+
+ data_size = cdata->data->size + sizeof(const 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->cmd;
+ header.length = data_size;
+ if (copy_to_user(tlvd, &header, sizeof(const struct snd_ctl_tlv)))
+ return -EFAULT;
+
+ if (copy_to_user(tlvd->tlv, cdata->data, data_size))
+ return -EFAULT;
+
+ return 0;
+}
diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c
index 2a6b84d..feced90 100644
--- a/sound/soc/sof/core.c
+++ b/sound/soc/sof/core.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -10,124 +10,24 @@
#include <linux/firmware.h>
#include <linux/module.h>
-#include <asm/unaligned.h>
#include <sound/soc.h>
#include <sound/sof.h>
#include "sof-priv.h"
#include "ops.h"
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES)
+#include "probe.h"
+#endif
+
+/* see SOF_DBG_ flags */
+int sof_core_debug;
+module_param_named(sof_debug, sof_core_debug, int, 0444);
+MODULE_PARM_DESC(sof_debug, "SOF core debug options (0x0 all off)");
/* SOF defaults if not provided by the platform in ms */
#define TIMEOUT_DEFAULT_IPC_MS 500
#define TIMEOUT_DEFAULT_BOOT_MS 2000
/*
- * Generic object lookup APIs.
- */
-
-struct snd_sof_pcm *snd_sof_find_spcm_name(struct snd_sof_dev *sdev,
- const char *name)
-{
- struct snd_sof_pcm *spcm;
-
- list_for_each_entry(spcm, &sdev->pcm_list, list) {
- /* match with PCM dai name */
- if (strcmp(spcm->pcm.dai_name, name) == 0)
- return spcm;
-
- /* match with playback caps name if set */
- if (*spcm->pcm.caps[0].name &&
- !strcmp(spcm->pcm.caps[0].name, name))
- return spcm;
-
- /* match with capture caps name if set */
- if (*spcm->pcm.caps[1].name &&
- !strcmp(spcm->pcm.caps[1].name, name))
- return spcm;
- }
-
- return NULL;
-}
-
-struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_sof_dev *sdev,
- unsigned int comp_id,
- int *direction)
-{
- struct snd_sof_pcm *spcm;
-
- list_for_each_entry(spcm, &sdev->pcm_list, list) {
- if (spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].comp_id == comp_id) {
- *direction = SNDRV_PCM_STREAM_PLAYBACK;
- return spcm;
- }
- if (spcm->stream[SNDRV_PCM_STREAM_CAPTURE].comp_id == comp_id) {
- *direction = SNDRV_PCM_STREAM_CAPTURE;
- return spcm;
- }
- }
-
- return NULL;
-}
-
-struct snd_sof_pcm *snd_sof_find_spcm_pcm_id(struct snd_sof_dev *sdev,
- unsigned int pcm_id)
-{
- 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_sof_dev *sdev,
- const char *name)
-{
- struct snd_sof_widget *swidget;
-
- list_for_each_entry(swidget, &sdev->widget_list, list) {
- if (strcmp(name, swidget->widget->name) == 0)
- return swidget;
- }
-
- return NULL;
-}
-
-/* find widget by stream name and direction */
-struct snd_sof_widget *snd_sof_find_swidget_sname(struct snd_sof_dev *sdev,
- const char *pcm_name, int dir)
-{
- struct snd_sof_widget *swidget;
- enum snd_soc_dapm_type type;
-
- if (dir == SNDRV_PCM_STREAM_PLAYBACK)
- type = snd_soc_dapm_aif_in;
- else
- type = snd_soc_dapm_aif_out;
-
- list_for_each_entry(swidget, &sdev->widget_list, list) {
- if (!strcmp(pcm_name, swidget->widget->sname) && swidget->id == type)
- return swidget;
- }
-
- return NULL;
-}
-
-struct snd_sof_dai *snd_sof_find_dai(struct snd_sof_dev *sdev,
- const char *name)
-{
- struct snd_sof_dai *dai;
-
- list_for_each_entry(dai, &sdev->dai_list, list) {
- if (dai->name && (strcmp(name, dai->name) == 0))
- return dai;
- }
-
- return NULL;
-}
-
-/*
* FW Panic/fault handling.
*/
@@ -196,99 +96,6 @@
EXPORT_SYMBOL(snd_sof_get_status);
/*
- * 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 snd_sof_dev *sdev,
- struct snd_dma_buffer *dmab,
- unsigned char *page_table, size_t size)
-{
- int i, pages;
-
- pages = snd_sgbuf_aligned_pages(size);
-
- dev_dbg(sdev->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(sdev->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;
-}
-
-/*
- * SOF Driver enumeration.
- */
-static int sof_machine_check(struct snd_sof_dev *sdev)
-{
- struct snd_sof_pdata *plat_data = sdev->pdata;
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC)
- struct snd_soc_acpi_mach *machine;
- int ret;
-#endif
-
- if (plat_data->machine)
- 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
- /* fallback to nocodec mode */
- dev_warn(sdev->dev, "No ASoC machine driver found - using nocodec\n");
- machine = devm_kzalloc(sdev->dev, sizeof(*machine), GFP_KERNEL);
- if (!machine)
- return -ENOMEM;
-
- ret = sof_nocodec_setup(sdev->dev, plat_data, machine,
- plat_data->desc, plat_data->desc->ops);
- if (ret < 0)
- return ret;
-
- plat_data->machine = machine;
-
- return 0;
-#endif
-}
-
-/*
* FW Boot State Transition Diagram
*
* +-----------------------------------------------------------------------+
@@ -331,9 +138,6 @@
static int sof_probe_continue(struct snd_sof_dev *sdev)
{
struct snd_sof_pdata *plat_data = sdev->pdata;
- const char *drv_name;
- const void *mach;
- int size;
int ret;
/* probe the DSP hardware */
@@ -398,12 +202,20 @@
goto fw_run_err;
}
- /* init DMA trace */
- ret = snd_sof_init_trace(sdev);
- if (ret < 0) {
- /* non fatal */
- dev_warn(sdev->dev,
- "warning: failed to initialize trace %d\n", ret);
+ if (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE) ||
+ (sof_core_debug & SOF_DBG_ENABLE_TRACE)) {
+ sdev->dtrace_is_supported = true;
+
+ /* init DMA trace */
+ ret = snd_sof_init_trace(sdev);
+ if (ret < 0) {
+ /* non fatal */
+ dev_warn(sdev->dev,
+ "warning: failed to initialize trace %d\n",
+ ret);
+ }
+ } else {
+ dev_dbg(sdev->dev, "SOF firmware trace disabled\n");
}
/* hereafter all FW boot flows are for PM reasons */
@@ -419,22 +231,17 @@
goto fw_trace_err;
}
- 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)) {
- ret = PTR_ERR(plat_data->pdev_mach);
+ ret = snd_sof_machine_register(sdev, plat_data);
+ if (ret < 0)
goto fw_trace_err;
- }
- dev_dbg(sdev->dev, "created machine %s\n",
- dev_name(&plat_data->pdev_mach->dev));
+ /*
+ * Some platforms in SOF, ex: BYT, may not have their platform PM
+ * callbacks set. Increment the usage count so as to
+ * prevent the device from entering runtime suspend.
+ */
+ if (!sof_ops(sdev)->runtime_suspend || !sof_ops(sdev)->runtime_resume)
+ pm_runtime_get_noresume(sdev->dev);
if (plat_data->sof_probe_complete)
plat_data->sof_probe_complete(sdev->dev);
@@ -483,16 +290,23 @@
/* initialize sof device */
sdev->dev = dev;
+ /* initialize default DSP power state */
+ sdev->dsp_power_state.state = SOF_DSP_PM_D0;
+
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)->ipc_msg_data || !sof_ops(sdev)->ipc_pcm_params ||
+ !sof_ops(sdev)->fw_ready)
return -EINVAL;
INIT_LIST_HEAD(&sdev->pcm_list);
@@ -529,12 +343,17 @@
{
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
struct snd_sof_pdata *pdata = sdev->pdata;
+ int ret;
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) {
- snd_sof_fw_unload(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_free_trace(sdev);
@@ -545,8 +364,7 @@
* will remove the component driver and unload the topology
* before freeing the snd_card.
*/
- if (!IS_ERR_OR_NULL(pdata->pdev_mach))
- platform_device_unregister(pdata->pdev_mach);
+ snd_sof_machine_unregister(sdev, pdata);
/*
* Unregistering the machine driver results in unloading the topology.
@@ -558,8 +376,7 @@
snd_sof_remove(sdev);
/* release firmware */
- release_firmware(pdata->fw);
- pdata->fw = NULL;
+ snd_sof_fw_unload(sdev);
return 0;
}
diff --git a/sound/soc/sof/debug.c b/sound/soc/sof/debug.c
index 0872603..3ef51b2 100644
--- a/sound/soc/sof/debug.c
+++ b/sound/soc/sof/debug.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -17,6 +17,221 @@
#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
@@ -190,7 +405,7 @@
}
ret = pm_runtime_get_sync(sdev->dev);
- if (ret < 0) {
+ if (ret < 0 && ret != -EACCES) {
dev_err_ratelimited(sdev->dev,
"error: debugfs write failed to resume %d\n",
ret);
@@ -436,6 +651,17 @@
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
+
#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,
@@ -463,3 +689,19 @@
debugfs_remove_recursive(sdev->debugfs_root);
}
EXPORT_SYMBOL_GPL(snd_sof_free_debug);
+
+void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev)
+{
+ if (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_RETAIN_DSP_CONTEXT) ||
+ (sof_core_debug & SOF_DBG_RETAIN_CTX)) {
+ /* 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);
+ }
+
+ /* 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);
+}
+EXPORT_SYMBOL(snd_sof_handle_fw_exception);
diff --git a/sound/soc/sof/imx/Kconfig b/sound/soc/sof/imx/Kconfig
index b4f0426..48f998a 100644
--- a/sound/soc/sof/imx/Kconfig
+++ b/sound/soc/sof/imx/Kconfig
@@ -1,27 +1,61 @@
-# SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
config SND_SOC_SOF_IMX_TOPLEVEL
bool "SOF support for NXP i.MX audio DSPs"
depends on ARM64|| COMPILE_TEST
depends on SND_SOC_SOF_OF
help
- This adds support for Sound Open Firmware for NXP i.MX platforms.
- Say Y if you have such a device.
- If unsure select "N".
+ This adds support for Sound Open Firmware for NXP i.MX platforms.
+ Say Y if you have such a device.
+ If unsure select "N".
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
+ help
+ This option is not user-selectable but automagically handled by
+ 'select' statements at a higher level
+
+config SND_SOC_SOF_IMX_COMMON
+ tristate
+ help
+ This option is not user-selectable but automagically handled by
+ 'select' statements at a higher level.
+
config SND_SOC_SOF_IMX8_SUPPORT
bool "SOF support for i.MX8"
- depends on IMX_SCU
- select IMX_DSP
+ depends on IMX_SCU=y || IMX_SCU=SND_SOC_SOF_IMX_OF
+ depends on IMX_DSP=y || IMX_DSP=SND_SOC_SOF_IMX_OF
help
- This adds support for Sound Open Firmware for NXP i.MX8 platforms
- Say Y if you have such a device.
- If unsure select "N".
+ 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
- def_tristate SND_SOC_SOF_OF
- depends on SND_SOC_SOF_IMX8_SUPPORT
+ tristate
+ select SND_SOC_SOF_IMX_COMMON
+ select SND_SOC_SOF_XTENSA
+ 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
+ help
+ 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
+ select SND_SOC_SOF_IMX_COMMON
+ select SND_SOC_SOF_XTENSA
+ help
+ This option is not user-selectable but automagically handled by
+ 'select' statements at a higher level
endif ## SND_SOC_SOF_IMX_IMX_TOPLEVEL
diff --git a/sound/soc/sof/imx/Makefile b/sound/soc/sof/imx/Makefile
index 6ef908e..dba93c3 100644
--- a/sound/soc/sof/imx/Makefile
+++ b/sound/soc/sof/imx/Makefile
@@ -1,4 +1,9 @@
-# SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+# 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-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_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 0000000..5fee637
--- /dev/null
+++ b/sound/soc/sof/imx/imx-common.c
@@ -0,0 +1,75 @@
+// 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.
+ */
+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 */
+ snd_sof_get_status(sdev, status, status, &xoops, &panic_info, stack,
+ IMX8_STACK_DUMP_SIZE);
+}
+EXPORT_SYMBOL(imx8_dump);
+
+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 0000000..1cc7d67
--- /dev/null
+++ b/sound/soc/sof/imx/imx-common.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+
+#ifndef __IMX_COMMON_H__
+#define __IMX_COMMON_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);
+
+#endif
diff --git a/sound/soc/sof/imx/imx8.c b/sound/soc/sof/imx/imx8.c
index 69785f6..4e7dcca 100644
--- a/sound/soc/sof/imx/imx8.c
+++ b/sound/soc/sof/imx/imx8.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// Copyright 2019 NXP
//
@@ -21,6 +21,7 @@
#include <linux/firmware/imx/svc/misc.h>
#include <dt-bindings/firmware/imx/rsrc.h>
#include "../ops.h"
+#include "imx-common.h"
/* DSP memories */
#define IRAM_OFFSET 0x10000
@@ -115,18 +116,26 @@
static void imx8_dsp_handle_request(struct imx_dsp_ipc *ipc)
{
struct imx8_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);
+ else
+ snd_sof_ipc_msgs_rx(priv->sdev);
}
-struct imx_dsp_ops dsp_ops = {
+static struct imx_dsp_ops dsp_ops = {
.handle_reply = imx8_dsp_handle_reply,
.handle_request = imx8_dsp_handle_request,
};
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);
@@ -138,9 +147,9 @@
/*
* DSP control.
*/
-static int imx8_run(struct snd_sof_dev *sdev)
+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,
@@ -178,6 +187,24 @@
return 0;
}
+static int imx8_run(struct snd_sof_dev *sdev)
+{
+ 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,
+ IMX_SC_C_OFS_SEL, 0);
+ if (ret < 0) {
+ dev_err(sdev->dev, "Error system address offset source select\n");
+ return ret;
+ }
+
+ imx_sc_pm_cpu_start(dsp_priv->sc_ipc, IMX_SC_R_DSP, true,
+ RESET_VECTOR_VADDR);
+
+ return 0;
+}
+
static int imx8_probe(struct snd_sof_dev *sdev)
{
struct platform_device *pdev =
@@ -195,7 +222,7 @@
if (!priv)
return -ENOMEM;
- sdev->private = priv;
+ sdev->pdata->hw_pdata = priv;
priv->dev = sdev->dev;
priv->sdev = sdev;
@@ -294,8 +321,7 @@
}
sdev->bar[SOF_FW_BLK_TYPE_SRAM] = devm_ioremap_wc(sdev->dev, res.start,
- res.end - res.start +
- 1);
+ 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);
@@ -322,7 +348,7 @@
static int 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;
platform_device_unregister(priv->ipc_dev);
@@ -357,11 +383,30 @@
static struct snd_soc_dai_driver imx8_dai[] = {
{
- .name = "esai-port",
+ .name = "esai0",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+},
+{
+ .name = "sai1",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 32,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 32,
+ },
},
};
-/* i.MX8 ops */
+/* i.MX8 ops */
struct snd_sof_dsp_ops sof_imx8_ops = {
/* probe and remove */
.probe = imx8_probe,
@@ -373,6 +418,9 @@
.block_read = sof_block_read,
.block_write = sof_block_write,
+ /* Module IO */
+ .read64 = sof_io_read64,
+
/* ipc */
.send_msg = imx8_send_msg,
.fw_ready = sof_fw_ready,
@@ -388,10 +436,73 @@
/* firmware loading */
.load_firmware = snd_sof_load_firmware_memcpy,
+ /* Debug information */
+ .dbg_dump = imx8_dump,
+
+ /* Firmware ops */
+ .arch_ops = &sof_xtensa_arch_ops,
+
/* DAI drivers */
.drv = imx8_dai,
- .num_drv = 1, /* we have only 1 ESAI interface on i.MX8 */
+ .num_drv = ARRAY_SIZE(imx8_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_NO_PERIOD_WAKEUP,
};
EXPORT_SYMBOL(sof_imx8_ops);
+/* i.MX8X ops */
+struct snd_sof_dsp_ops sof_imx8x_ops = {
+ /* probe and remove */
+ .probe = imx8_probe,
+ .remove = imx8_remove,
+ /* DSP core boot */
+ .run = imx8x_run,
+
+ /* Block IO */
+ .block_read = sof_block_read,
+ .block_write = sof_block_write,
+
+ /* Module IO */
+ .read64 = sof_io_read64,
+
+ /* 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,
+
+ /* 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,
+
+ /* Firmware ops */
+ .arch_ops = &sof_xtensa_arch_ops,
+
+ /* DAI drivers */
+ .drv = imx8_dai,
+ .num_drv = ARRAY_SIZE(imx8_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_NO_PERIOD_WAKEUP
+};
+EXPORT_SYMBOL(sof_imx8x_ops);
+
+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
new file mode 100644
index 0000000..cb822d9
--- /dev/null
+++ b/sound/soc/sof/imx/imx8m.c
@@ -0,0 +1,311 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+//
+// Copyright 2020 NXP
+//
+// Author: Daniel Baluta <daniel.baluta@nxp.com>
+//
+// Hardware interface for audio DSP on i.MX8M
+
+#include <linux/firmware.h>
+#include <linux/of_platform.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+
+#include <linux/module.h>
+#include <sound/sof.h>
+#include <sound/sof/xtensa.h>
+#include <linux/firmware/imx/dsp.h>
+
+#include "../ops.h"
+#include "imx-common.h"
+
+#define MBOX_OFFSET 0x800000
+#define MBOX_SIZE 0x1000
+
+struct imx8m_priv {
+ struct device *dev;
+ struct snd_sof_dev *sdev;
+
+ /* 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);
+ }
+
+ msg->reply_error = ret;
+}
+
+static int imx8m_get_mailbox_offset(struct snd_sof_dev *sdev)
+{
+ return MBOX_OFFSET;
+}
+
+static int imx8m_get_window_offset(struct snd_sof_dev *sdev, u32 id)
+{
+ return MBOX_OFFSET;
+}
+
+static void imx8m_dsp_handle_reply(struct imx_dsp_ipc *ipc)
+{
+ struct imx8m_priv *priv = imx_dsp_get_data(ipc);
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->sdev->ipc_lock, flags);
+ imx8m_get_reply(priv->sdev);
+ snd_sof_ipc_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 */
+
+ /* 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);
+ else
+ snd_sof_ipc_msgs_rx(priv->sdev);
+}
+
+static struct imx_dsp_ops imx8m_dsp_ops = {
+ .handle_reply = imx8m_dsp_handle_reply,
+ .handle_request = imx8m_dsp_handle_request,
+};
+
+static int imx8m_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
+{
+ struct imx8m_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;
+}
+
+/*
+ * DSP control.
+ */
+static int imx8m_run(struct snd_sof_dev *sdev)
+{
+ /* TODO: start DSP using Audio MIX bits */
+ return 0;
+}
+
+static int imx8m_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 imx8m_priv *priv;
+ struct resource res;
+ u32 base, size;
+ int ret = 0;
+
+ 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->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 = &imx8m_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-region", 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);
+ 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;
+
+ return 0;
+
+exit_pdev_unregister:
+ platform_device_unregister(priv->ipc_dev);
+ return ret;
+}
+
+static int imx8m_remove(struct snd_sof_dev *sdev)
+{
+ struct imx8m_priv *priv = sdev->pdata->hw_pdata;
+
+ 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;
+}
+
+static struct snd_soc_dai_driver imx8m_dai[] = {
+{
+ .name = "sai3",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 32,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 32,
+ },
+},
+};
+
+/* i.MX8 ops */
+struct snd_sof_dsp_ops sof_imx8m_ops = {
+ /* probe and remove */
+ .probe = imx8m_probe,
+ .remove = imx8m_remove,
+ /* DSP core boot */
+ .run = imx8m_run,
+
+ /* Block IO */
+ .block_read = sof_block_read,
+ .block_write = sof_block_write,
+
+ /* Module IO */
+ .read64 = sof_io_read64,
+
+ /* 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,
+
+ /* 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,
+
+ /* Firmware ops */
+ .arch_ops = &sof_xtensa_arch_ops,
+
+ /* DAI drivers */
+ .drv = imx8m_dai,
+ .num_drv = ARRAY_SIZE(imx8m_dai),
+
+ .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,
+};
+EXPORT_SYMBOL(sof_imx8m_ops);
+
+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 8421b97..6708a2c 100644
--- a/sound/soc/sof/intel/Kconfig
+++ b/sound/soc/sof/intel/Kconfig
@@ -10,7 +10,7 @@
if SND_SOC_SOF_INTEL_TOPLEVEL
config SND_SOC_SOF_INTEL_ACPI
- tristate
+ 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
@@ -18,17 +18,17 @@
'select' statements at a higher level
config SND_SOC_SOF_INTEL_PCI
- tristate
+ 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_LP if SND_SOC_SOF_COMETLAKE_LP_SUPPORT
- select SND_SOC_SOF_COMETLAKE_H if SND_SOC_SOF_COMETLAKE_H_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
@@ -36,7 +36,7 @@
config SND_SOC_SOF_INTEL_HIFI_EP_IPC
tristate
help
- This option is not user-selectable but automagically handled by
+ This option is not user-selectable but automagically handled by
'select' statements at a higher level
config SND_SOC_SOF_INTEL_ATOM_HIFI_EP
@@ -61,10 +61,18 @@
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.
- Say Y if you have such a device.
+ 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".
config SND_SOC_SOF_BAYTRAIL
@@ -76,7 +84,7 @@
config SND_SOC_SOF_BROADWELL_SUPPORT
bool "SOF support for Broadwell"
- depends on SND_SOC_INTEL_HASWELL=n
+ depends on SND_SOC_INTEL_CATPT=n
help
This adds support for Sound Open Firmware for Intel(R) platforms
using the Broadwell processors.
@@ -158,6 +166,7 @@
config SND_SOC_SOF_CANNONLAKE
tristate
select SND_SOC_SOF_HDA_COMMON
+ select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
help
This option is not user-selectable but automagically handled by
'select' statements at a higher level
@@ -173,6 +182,7 @@
config SND_SOC_SOF_COFFEELAKE
tristate
select SND_SOC_SOF_HDA_COMMON
+ select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
help
This option is not user-selectable but automagically handled by
'select' statements at a higher level
@@ -188,72 +198,80 @@
config SND_SOC_SOF_ICELAKE
tristate
select SND_SOC_SOF_HDA_COMMON
+ select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
help
This option is not user-selectable but automagically handled by
'select' statements at a higher level
-config SND_SOC_SOF_COMETLAKE_LP
+config SND_SOC_SOF_COMETLAKE
tristate
select SND_SOC_SOF_HDA_COMMON
+ select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
help
This option is not user-selectable but automagically handled by
'select' statements at a higher level
+config SND_SOC_SOF_COMETLAKE_SUPPORT
+ bool
+
config SND_SOC_SOF_COMETLAKE_LP_SUPPORT
- bool "SOF support for CometLake-LP"
+ bool "SOF support for CometLake"
+ select SND_SOC_SOF_COMETLAKE_SUPPORT
help
This adds support for Sound Open Firmware for Intel(R) platforms
- using the Cometlake-LP processors.
- Say Y if you have such a device.
- If unsure select "N".
-
-config SND_SOC_SOF_COMETLAKE_H
- 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_COMETLAKE_H_SUPPORT
- bool "SOF support for CometLake-H"
- help
- This adds support for Sound Open Firmware for Intel(R) platforms
- using the Cometlake-H processors.
- Say Y if you have such a device.
+ using the Cometlake processors.
If unsure select "N".
config SND_SOC_SOF_TIGERLAKE_SUPPORT
bool "SOF support for Tigerlake"
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".
+ 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_TIGERLAKE
tristate
select SND_SOC_SOF_HDA_COMMON
+ select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
help
- This option is not user-selectable but automagically handled by
+ This option is not user-selectable but automagically handled by
'select' statements at a higher level
config SND_SOC_SOF_ELKHARTLAKE_SUPPORT
bool "SOF support for ElkhartLake"
help
- This adds support for Sound Open Firmware for Intel(R) platforms
- using the ElkhartLake processors.
- Say Y if you have such a device.
- If unsure select "N".
+ This adds support for Sound Open Firmware for Intel(R) platforms
+ using the ElkhartLake processors.
+ Say Y if you have such a device.
+ If unsure select "N".
config SND_SOC_SOF_ELKHARTLAKE
tristate
select SND_SOC_SOF_HDA_COMMON
+ select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
help
- This option is not user-selectable but automagically handled by
+ This option is not user-selectable but automagically handled by
+ 'select' statements at a higher level
+
+config SND_SOC_SOF_JASPERLAKE_SUPPORT
+ bool "SOF support for JasperLake"
+ help
+ 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_JASPERLAKE
+ 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_HDA_COMMON
tristate
+ select SND_INTEL_DSP_CONFIG
select SND_SOC_SOF_INTEL_COMMON
select SND_SOC_SOF_HDA_LINK_BASELINE
help
@@ -281,11 +299,20 @@
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.
+ 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
@@ -304,7 +331,30 @@
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_NHLT if ACPI
+ help
+ This option is not user-selectable but automagically handled by
+ 'select' statements at a higher level
+
+config SND_SOC_SOF_INTEL_SOUNDWIRE_LINK
+ bool "SOF support for SoundWire"
+ depends on ACPI
+ 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".
+
+config SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
+ tristate
+ select SND_SOC_SOF_INTEL_SOUNDWIRE if SND_SOC_SOF_INTEL_SOUNDWIRE_LINK
+ help
+ This option is not user-selectable but automagically handled by
+ 'select' statements at a higher level
+
+config SND_SOC_SOF_INTEL_SOUNDWIRE
+ tristate
+ select SOUNDWIRE
+ select SOUNDWIRE_INTEL
help
This option is not user-selectable but automagically handled by
'select' statements at a higher level
diff --git a/sound/soc/sof/intel/Makefile b/sound/soc/sof/intel/Makefile
index b8f58e0..72d85b2 100644
--- a/sound/soc/sof/intel/Makefile
+++ b/sound/soc/sof/intel/Makefile
@@ -1,4 +1,4 @@
-# SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+# 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
@@ -8,7 +8,8 @@
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
+ apl.o cnl.o tgl.o
+snd-sof-intel-hda-common-$(CONFIG_SND_SOC_SOF_HDA_PROBES) += hda-compress.o
snd-sof-intel-hda-objs := hda-codec.o
diff --git a/sound/soc/sof/intel/apl.c b/sound/soc/sof/intel/apl.c
index 8dc7a55..4eeade2 100644
--- a/sound/soc/sof/intel/apl.c
+++ b/sound/soc/sof/intel/apl.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -17,6 +17,7 @@
#include "../sof-priv.h"
#include "hda.h"
+#include "../sof-audio.h"
static const struct snd_sof_debugfs_map apl_dsp_debugfs[] = {
{"hda", HDA_DSP_HDA_BAR, 0, 0x4000, SOF_DEBUGFS_ACCESS_ALWAYS},
@@ -41,7 +42,6 @@
.block_write = sof_block_write,
/* doorbell */
- .irq_handler = hda_dsp_ipc_irq_handler,
.irq_thread = hda_dsp_ipc_irq_thread,
/* ipc */
@@ -53,6 +53,12 @@
.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,
+
/* debug */
.debug_map = apl_dsp_debugfs,
.debug_map_count = ARRAY_SIZE(apl_dsp_debugfs),
@@ -67,6 +73,15 @@
.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,
@@ -97,14 +112,24 @@
.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,
};
-EXPORT_SYMBOL(sof_apl_ops);
+EXPORT_SYMBOL_NS(sof_apl_ops, 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,
@@ -114,4 +139,4 @@
.ssp_count = APL_SSP_COUNT,
.ssp_base_offset = APL_SSP_BASE_OFFSET,
};
-EXPORT_SYMBOL(apl_chip_info);
+EXPORT_SYMBOL_NS(apl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
diff --git a/sound/soc/sof/intel/bdw.c b/sound/soc/sof/intel/bdw.c
index 80e2826..50a4a73 100644
--- a/sound/soc/sof/intel/bdw.c
+++ b/sound/soc/sof/intel/bdw.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -17,6 +17,7 @@
#include <sound/sof/xtensa.h>
#include "../ops.h"
#include "shim.h"
+#include "../sof-audio.h"
/* BARs */
#define BDW_DSP_BAR 0
@@ -247,7 +248,7 @@
struct sof_ipc_dsp_oops_xtensa xoops;
struct sof_ipc_panic_info panic_info;
u32 stack[BDW_STACK_DUMP_SIZE];
- u32 status, panic;
+ u32 status, panic, imrx, imrd;
/* now try generic SOF status messages */
status = snd_sof_dsp_read(sdev, BDW_DSP_BAR, SHIM_IPCD);
@@ -256,6 +257,26 @@
BDW_STACK_DUMP_SIZE);
snd_sof_get_status(sdev, 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);
+ imrd = snd_sof_dsp_read(sdev, BDW_DSP_BAR, SHIM_IMRD);
+ dev_err(sdev->dev,
+ "error: ipc host -> DSP: pending %s complete %s raw 0x%8.8x\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%8.8x\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%8.8x\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%8.8x\n",
+ (imrd & SHIM_IMRD_BUSY) ? "yes" : "no",
+ (imrd & SHIM_IMRD_DONE) ? "yes" : "no", imrd);
}
/*
@@ -516,13 +537,55 @@
return ret;
}
+static void 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;
+ struct snd_soc_acpi_mach *mach;
+
+ mach = snd_soc_acpi_find_machine(desc->machines);
+ if (!mach) {
+ dev_warn(sdev->dev, "warning: No matching ASoC machine driver found\n");
+ return;
+ }
+
+ sof_pdata->tplg_filename = mach->sof_tplg_filename;
+ mach->mach_params.acpi_ipc_irq_index = desc->irqindex_host_ipc;
+ sof_pdata->machine = mach;
+}
+
+static void bdw_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);
+}
+
/* Broadwell DAIs */
static struct snd_soc_dai_driver bdw_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,
+ },
},
};
@@ -554,6 +617,12 @@
.ipc_msg_data = intel_ipc_msg_data,
.ipc_pcm_params = intel_ipc_pcm_params,
+ /* machine driver */
+ .machine_select = bdw_machine_select,
+ .machine_register = sof_machine_register,
+ .machine_unregister = sof_machine_unregister,
+ .set_mach_params = bdw_set_mach_params,
+
/* debug */
.debug_map = bdw_debugfs,
.debug_map_count = ARRAY_SIZE(bdw_debugfs),
@@ -571,14 +640,25 @@
/* DAI drivers */
.drv = bdw_dai,
- .num_drv = ARRAY_SIZE(bdw_dai)
+ .num_drv = ARRAY_SIZE(bdw_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,
+
+ .arch_ops = &sof_xtensa_arch_ops,
};
-EXPORT_SYMBOL(sof_bdw_ops);
+EXPORT_SYMBOL_NS(sof_bdw_ops, SND_SOC_SOF_BROADWELL);
const struct sof_intel_dsp_desc bdw_chip_info = {
.cores_num = 1,
- .cores_mask = 1,
+ .host_managed_cores_mask = 1,
};
-EXPORT_SYMBOL(bdw_chip_info);
+EXPORT_SYMBOL_NS(bdw_chip_info, SND_SOC_SOF_BROADWELL);
MODULE_LICENSE("Dual BSD/GPL");
+MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_HIFI_EP_IPC);
+MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA);
diff --git a/sound/soc/sof/intel/byt.c b/sound/soc/sof/intel/byt.c
index 41008c9..186736e 100644
--- a/sound/soc/sof/intel/byt.c
+++ b/sound/soc/sof/intel/byt.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -17,6 +17,8 @@
#include <sound/sof/xtensa.h>
#include "../ops.h"
#include "shim.h"
+#include "../sof-audio.h"
+#include "../../intel/common/soc-intel-quirks.h"
/* DSP memories */
#define IRAM_OFFSET 0x0C0000
@@ -80,33 +82,6 @@
SOF_DEBUGFS_ACCESS_ALWAYS},
};
-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 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);
@@ -146,15 +121,36 @@
struct sof_ipc_dsp_oops_xtensa xoops;
struct sof_ipc_panic_info panic_info;
u32 stack[BYT_STACK_DUMP_SIZE];
- u32 status, panic;
+ u64 status, panic, imrd, imrx;
/* now try generic SOF status messages */
- status = snd_sof_dsp_read(sdev, BYT_DSP_BAR, SHIM_IPCD);
- panic = snd_sof_dsp_read(sdev, BYT_DSP_BAR, SHIM_IPCX);
+ 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);
+
}
/*
@@ -164,13 +160,31 @@
static irqreturn_t byt_irq_handler(int irq, void *context)
{
struct snd_sof_dev *sdev = context;
- u64 isr;
+ u64 ipcx, ipcd;
int ret = IRQ_NONE;
- /* Interrupt arrived, check src */
- isr = snd_sof_dsp_read64(sdev, BYT_DSP_BAR, SHIM_ISRX);
- if (isr & (SHIM_ISRX_DONE | SHIM_ISRX_BUSY))
+ 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;
}
@@ -179,19 +193,12 @@
{
struct snd_sof_dev *sdev = context;
u64 ipcx, ipcd;
- u64 imrx;
- imrx = snd_sof_dsp_read64(sdev, BYT_DSP_BAR, SHIM_IMRX);
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 &&
- !(imrx & SHIM_IMRX_DONE)) {
- /* Mask Done interrupt before first */
- snd_sof_dsp_update_bits64_unlocked(sdev, BYT_DSP_BAR,
- SHIM_IMRX,
- SHIM_IMRX_DONE,
- SHIM_IMRX_DONE);
+ if (ipcx & SHIM_BYT_IPCX_DONE) {
spin_lock_irq(&sdev->ipc_lock);
@@ -211,14 +218,7 @@
}
/* new message from DSP */
- ipcd = snd_sof_dsp_read64(sdev, BYT_DSP_BAR, SHIM_IPCD);
- if (ipcd & SHIM_BYT_IPCD_BUSY &&
- !(imrx & SHIM_IMRX_BUSY)) {
- /* Mask Busy interrupt before return */
- snd_sof_dsp_update_bits64_unlocked(sdev, BYT_DSP_BAR,
- SHIM_IMRX,
- SHIM_IMRX_BUSY,
- SHIM_IMRX_BUSY);
+ if (ipcd & SHIM_BYT_IPCD_BUSY) {
/* Handle messages from DSP Core */
if ((ipcd & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) {
@@ -236,6 +236,10 @@
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);
@@ -301,7 +305,7 @@
SHIM_BYT_IPCD_DONE,
SHIM_BYT_IPCD_DONE);
- /* unmask busy interrupt */
+ /* unmask and prepare to receive next new message */
snd_sof_dsp_update_bits64_unlocked(sdev, BYT_DSP_BAR, SHIM_IMRX,
SHIM_IMRX_BUSY, 0);
}
@@ -311,10 +315,6 @@
/* 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);
-
- /* unmask Done interrupt */
- snd_sof_dsp_update_bits64_unlocked(sdev, BYT_DSP_BAR, SHIM_IMRX,
- SHIM_IMRX_DONE, 0);
}
/*
@@ -362,25 +362,143 @@
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,
+ },
},
};
@@ -453,9 +571,10 @@
return ret;
}
- /* enable Interrupt from both sides */
- snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_IMRX, 0x3, 0x0);
- snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_IMRD, 0x3, 0x0);
+ /* 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;
@@ -494,6 +613,12 @@
.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),
@@ -512,19 +637,91 @@
/* 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(sof_tng_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,
+ .host_managed_cores_mask = 1,
};
-EXPORT_SYMBOL(tng_chip_info);
+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);
+
+ /* Put DSP into reset, set reset vector */
+ snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_CSR,
+ SHIM_BYT_CSR_RST | SHIM_BYT_CSR_VECTOR_SEL,
+ SHIM_BYT_CSR_RST | SHIM_BYT_CSR_VECTOR_SEL);
+}
+
+static int byt_suspend(struct snd_sof_dev *sdev, u32 target_state)
+{
+ byt_reset_dsp_disable_int(sdev);
+
+ return 0;
+}
+
+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,
+ SHIM_IMRX_BUSY | SHIM_IMRX_DONE,
+ SHIM_IMRX_DONE);
+
+ return 0;
+}
+
+static int 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;
@@ -613,9 +810,10 @@
return ret;
}
- /* enable Interrupt from both sides */
- snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_IMRX, 0x3, 0x0);
- snd_sof_dsp_update_bits64(sdev, BYT_DSP_BAR, SHIM_IMRD, 0x3, 0x0);
+ /* 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;
@@ -627,6 +825,7 @@
const struct snd_sof_dsp_ops sof_byt_ops = {
/* device init */
.probe = byt_acpi_probe,
+ .remove = byt_remove,
/* DSP core boot / reset */
.run = byt_run,
@@ -655,6 +854,12 @@
.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),
@@ -670,22 +875,36 @@
/*Firmware loading */
.load_firmware = snd_sof_load_firmware_memcpy,
+ /* PM */
+ .suspend = byt_suspend,
+ .resume = byt_resume,
+
/* 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(sof_byt_ops);
+EXPORT_SYMBOL_NS(sof_byt_ops, SND_SOC_SOF_BAYTRAIL);
const struct sof_intel_dsp_desc byt_chip_info = {
.cores_num = 1,
- .cores_mask = 1,
+ .host_managed_cores_mask = 1,
};
-EXPORT_SYMBOL(byt_chip_info);
+EXPORT_SYMBOL_NS(byt_chip_info, SND_SOC_SOF_BAYTRAIL);
/* cherrytrail and braswell ops */
const struct snd_sof_dsp_ops sof_cht_ops = {
/* device init */
.probe = byt_acpi_probe,
+ .remove = byt_remove,
/* DSP core boot / reset */
.run = byt_run,
@@ -714,6 +933,12 @@
.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 = cht_debugfs,
.debug_map_count = ARRAY_SIZE(cht_debugfs),
@@ -729,19 +954,34 @@
/*Firmware loading */
.load_firmware = snd_sof_load_firmware_memcpy,
+ /* PM */
+ .suspend = byt_suspend,
+ .resume = byt_resume,
+
/* DAI drivers */
.drv = byt_dai,
/* all 6 SSPs may be available for cherrytrail */
.num_drv = ARRAY_SIZE(byt_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,
+
+ .arch_ops = &sof_xtensa_arch_ops,
};
-EXPORT_SYMBOL(sof_cht_ops);
+EXPORT_SYMBOL_NS(sof_cht_ops, SND_SOC_SOF_BAYTRAIL);
const struct sof_intel_dsp_desc cht_chip_info = {
.cores_num = 1,
- .cores_mask = 1,
+ .host_managed_cores_mask = 1,
};
-EXPORT_SYMBOL(cht_chip_info);
+EXPORT_SYMBOL_NS(cht_chip_info, SND_SOC_SOF_BAYTRAIL);
#endif /* CONFIG_SND_SOC_SOF_BAYTRAIL */
MODULE_LICENSE("Dual BSD/GPL");
+MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_HIFI_EP_IPC);
+MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA);
diff --git a/sound/soc/sof/intel/cnl.c b/sound/soc/sof/intel/cnl.c
index 4ddd737..a5d3258 100644
--- a/sound/soc/sof/intel/cnl.c
+++ b/sound/soc/sof/intel/cnl.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -17,6 +17,8 @@
#include "../ops.h"
#include "hda.h"
+#include "hda-ipc.h"
+#include "../sof-audio.h"
static const struct snd_sof_debugfs_map cnl_dsp_debugfs[] = {
{"hda", HDA_DSP_HDA_BAR, 0, 0x4000, SOF_DEBUGFS_ACCESS_ALWAYS},
@@ -27,7 +29,7 @@
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_ipc_irq_thread(int irq, void *context)
{
struct snd_sof_dev *sdev = context;
u32 hipci;
@@ -63,11 +65,6 @@
hda_dsp_ipc_get_reply(sdev);
snd_sof_ipc_reply(sdev, msg);
- if (sdev->code_loading) {
- sdev->code_loading = 0;
- wake_up(&sdev->waitq);
- }
-
cnl_ipc_dsp_done(sdev);
spin_unlock_irq(&sdev->ipc_lock);
@@ -105,10 +102,6 @@
"nothing to do in IPC IRQ thread\n");
}
- /* re-enable IPC interrupt */
- snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIC,
- HDA_DSP_ADSPIC_IPC, HDA_DSP_ADSPIC_IPC);
-
return IRQ_HANDLED;
}
@@ -150,19 +143,74 @@
CNL_DSP_REG_HIPCCTL_DONE);
}
-static int cnl_ipc_send_msg(struct snd_sof_dev *sdev,
- struct snd_sof_ipc_msg *msg)
+static bool cnl_compact_ipc_compress(struct snd_sof_ipc_msg *msg,
+ u32 *dr, u32 *dd)
{
- /* send the message */
+ struct sof_ipc_pm_gate *pm_gate;
+
+ if (msg->header == (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_GATE)) {
+ pm_gate = msg->msg_data;
+
+ /* send the compact message via the primary register */
+ *dr = HDA_IPC_MSG_COMPACT | HDA_IPC_PM_GATE;
+
+ /* send payload via the extended data register */
+ *dd = pm_gate->flags;
+
+ return true;
+ }
+
+ return false;
+}
+
+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;
+ u32 dr = 0;
+ u32 dd = 0;
+
+ /*
+ * Currently the only compact IPC supported is the PM_GATE
+ * IPC which is used for transitioning the DSP between the
+ * D0I0 and D0I3 states. And these are sent only during the
+ * set_power_state() op. Therefore, there will never be a case
+ * that a compact IPC results in the DSP exiting D0I3 without
+ * the host and FW being in sync.
+ */
+ if (cnl_compact_ipc_compress(msg, &dr, &dd)) {
+ /* send the message via IPC registers */
+ snd_sof_dsp_write(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDD,
+ dd);
+ snd_sof_dsp_write(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDR,
+ CNL_DSP_REG_HIPCIDR_BUSY | dr);
+ return 0;
+ }
+
+ /* send the message via mailbox */
sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data,
msg->msg_size);
snd_sof_dsp_write(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDR,
CNL_DSP_REG_HIPCIDR_BUSY);
+ hdr = msg->msg_data;
+
+ /*
+ * Use mod_delayed_work() to schedule the delayed work
+ * to avoid scheduling multiple workqueue items when
+ * 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
+ * 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,
+ msecs_to_jiffies(SOF_HDA_D0I3_WORK_DELAY_MS));
+
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;
@@ -199,7 +247,6 @@
.block_write = sof_block_write,
/* doorbell */
- .irq_handler = hda_dsp_ipc_irq_handler,
.irq_thread = cnl_ipc_irq_thread,
/* ipc */
@@ -211,6 +258,12 @@
.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,
+
/* debug */
.debug_map = cnl_dsp_debugfs,
.debug_map_count = ARRAY_SIZE(cnl_dsp_debugfs),
@@ -225,6 +278,15 @@
.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,
@@ -255,17 +317,24 @@
.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,
};
-EXPORT_SYMBOL(sof_cnl_ops);
+EXPORT_SYMBOL_NS(sof_cnl_ops, 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,
@@ -275,16 +344,13 @@
.ssp_count = CNL_SSP_COUNT,
.ssp_base_offset = CNL_SSP_BASE_OFFSET,
};
-EXPORT_SYMBOL(cnl_chip_info);
+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),
+ .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,
@@ -294,29 +360,13 @@
.ssp_count = ICL_SSP_COUNT,
.ssp_base_offset = CNL_SSP_BASE_OFFSET,
};
-EXPORT_SYMBOL(icl_chip_info);
-
-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(tgl_chip_info);
+EXPORT_SYMBOL_NS(icl_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),
+ .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,
@@ -326,4 +376,20 @@
.ssp_count = ICL_SSP_COUNT,
.ssp_base_offset = CNL_SSP_BASE_OFFSET,
};
-EXPORT_SYMBOL(ehl_chip_info);
+EXPORT_SYMBOL_NS(ehl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
+
+const struct sof_intel_dsp_desc jsl_chip_info = {
+ /* Jasperlake */
+ .cores_num = 2,
+ .init_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_init_timeout = 300,
+ .ssp_count = ICL_SSP_COUNT,
+ .ssp_base_offset = CNL_SSP_BASE_OFFSET,
+};
+EXPORT_SYMBOL_NS(jsl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
diff --git a/sound/soc/sof/intel/hda-bus.c b/sound/soc/sof/intel/hda-bus.c
index 1d2babd..789148e 100644
--- a/sound/soc/sof/intel/hda-bus.c
+++ b/sound/soc/sof/intel/hda-bus.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
diff --git a/sound/soc/sof/intel/hda-codec.c b/sound/soc/sof/intel/hda-codec.c
index df38616..8d65004 100644
--- a/sound/soc/sof/intel/hda-codec.c
+++ b/sound/soc/sof/intel/hda-codec.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -24,19 +24,44 @@
#define IDISP_VID_INTEL 0x80860000
/* load the legacy HDA codec driver */
-static int hda_codec_load_module(struct hda_codec *codec)
+static int request_codec_module(struct hda_codec *codec)
{
#ifdef MODULE
char alias[MODULE_NAME_LEN];
- const char *module = alias;
+ const char *mod = NULL;
- snd_hdac_codec_modalias(&codec->core, alias, sizeof(alias));
- dev_dbg(&codec->core.dev, "loading codec module: %s\n", module);
- request_module(module);
+ switch (codec->probe_id) {
+ case HDA_CODEC_ID_GENERIC:
+#if IS_MODULE(CONFIG_SND_HDA_GENERIC)
+ mod = "snd-hda-codec-generic";
#endif
+ break;
+ default:
+ snd_hdac_codec_modalias(&codec->core, alias, sizeof(alias));
+ mod = alias;
+ break;
+ }
+
+ if (mod) {
+ dev_dbg(&codec->core.dev, "loading codec module: %s\n", mod);
+ request_module(mod);
+ }
+#endif /* MODULE */
return device_attach(hda_codec_dev(codec));
}
+static int hda_codec_load_module(struct hda_codec *codec)
+{
+ int ret = request_codec_module(codec);
+
+ if (ret <= 0) {
+ codec->probe_id = HDA_CODEC_ID_GENERIC;
+ ret = request_codec_module(codec);
+ }
+
+ return ret;
+}
+
/* enable controller wake up event for all codecs with jack connectors */
void hda_codec_jack_wake_enable(struct snd_sof_dev *sdev)
{
@@ -74,14 +99,24 @@
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(hda_codec_jack_wake_enable);
-EXPORT_SYMBOL(hda_codec_jack_check);
+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);
+
+#if IS_ENABLED(CONFIG_SND_HDA_GENERIC)
+#define is_generic_config(bus) \
+ ((bus)->modelname && !strcmp((bus)->modelname, "generic"))
+#else
+#define is_generic_config(x) 0
+#endif
/* probe individual codec */
-static int hda_codec_probe(struct snd_sof_dev *sdev, int address)
+static int hda_codec_probe(struct snd_sof_dev *sdev, int address,
+ bool hda_codec_use_common_hdmi)
{
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
struct hdac_hda_priv *hda_priv;
+ struct hda_codec *codec;
+ int type = HDA_DEV_LEGACY;
#endif
struct hda_bus *hbus = sof_to_hbus(sdev);
struct hdac_device *hdev;
@@ -106,15 +141,33 @@
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);
+ /* only probe ASoC codec drivers for HDAC-HDMI */
+ if (!hda_codec_use_common_hdmi && (resp & 0xFFFF0000) == IDISP_VID_INTEL)
+ type = HDA_DEV_ASOC;
+
+ ret = snd_hdac_ext_bus_device_init(&hbus->core, address, hdev, type);
if (ret < 0)
return ret;
- /* use legacy bus only for HDA codecs, idisp uses ext bus */
- if ((resp & 0xFFFF0000) != IDISP_VID_INTEL) {
- hdev->type = HDA_DEV_LEGACY;
- ret = hda_codec_load_module(&hda_priv->codec);
+ if ((resp & 0xFFFF0000) == IDISP_VID_INTEL) {
+ if (!hdev->bus->audio_component) {
+ dev_dbg(sdev->dev,
+ "iDisp hw present but no driver\n");
+ ret = -ENOENT;
+ goto out;
+ }
+ hda_priv->need_display_power = true;
+ }
+
+ if (is_generic_config(hbus))
+ codec->probe_id = HDA_CODEC_ID_GENERIC;
+ else
+ codec->probe_id = 0;
+
+ if (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
@@ -123,20 +176,25 @@
ret = -ENOENT;
}
- return ret;
+out:
+ if (ret < 0) {
+ snd_hdac_device_unregister(hdev);
+ put_device(&hdev->dev);
+ }
#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);
+ ret = snd_hdac_ext_bus_device_init(&hbus->core, address, hdev, HDA_DEV_ASOC);
+#endif
return ret;
-#endif
}
/* Codec initialization */
-int hda_codec_probe_bus(struct snd_sof_dev *sdev)
+void hda_codec_probe_bus(struct snd_sof_dev *sdev,
+ bool hda_codec_use_common_hdmi)
{
struct hdac_bus *bus = sof_to_bus(sdev);
int i, ret;
@@ -147,37 +205,29 @@
if (!(bus->codec_mask & (1 << i)))
continue;
- ret = hda_codec_probe(sdev, i);
+ ret = hda_codec_probe(sdev, i, hda_codec_use_common_hdmi);
if (ret < 0) {
- dev_err(bus->dev, "error: codec #%d probe error, ret: %d\n",
- i, ret);
- return ret;
+ dev_warn(bus->dev, "codec #%d probe error, ret: %d\n",
+ i, ret);
+ bus->codec_mask &= ~BIT(i);
}
}
-
- return 0;
}
-EXPORT_SYMBOL(hda_codec_probe_bus);
+EXPORT_SYMBOL_NS(hda_codec_probe_bus, SND_SOC_SOF_HDA_AUDIO_CODEC);
-#if IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI)
+#if IS_ENABLED(CONFIG_SND_HDA_CODEC_HDMI) || \
+ IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI)
-void hda_codec_i915_get(struct snd_sof_dev *sdev)
+void hda_codec_i915_display_power(struct snd_sof_dev *sdev, bool enable)
{
struct hdac_bus *bus = sof_to_bus(sdev);
- dev_dbg(bus->dev, "Turning i915 HDAC power on\n");
- snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, true);
+ 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(hda_codec_i915_get);
-
-void hda_codec_i915_put(struct snd_sof_dev *sdev)
-{
- struct hdac_bus *bus = sof_to_bus(sdev);
-
- dev_dbg(bus->dev, "Turning i915 HDAC power off\n");
- snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, false);
-}
-EXPORT_SYMBOL(hda_codec_i915_put);
+EXPORT_SYMBOL_NS(hda_codec_i915_display_power, SND_SOC_SOF_HDA_AUDIO_CODEC_I915);
int hda_codec_i915_init(struct snd_sof_dev *sdev)
{
@@ -189,25 +239,27 @@
if (ret < 0)
return ret;
- hda_codec_i915_get(sdev);
+ /* codec_mask not yet known, power up for probe */
+ snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, true);
return 0;
}
-EXPORT_SYMBOL(hda_codec_i915_init);
+EXPORT_SYMBOL_NS(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);
- int ret;
- hda_codec_i915_put(sdev);
+ if (!bus->audio_component)
+ return 0;
- ret = snd_hdac_i915_exit(bus);
+ /* power down unconditionally */
+ snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, false);
- return ret;
+ return snd_hdac_i915_exit(bus);
}
-EXPORT_SYMBOL(hda_codec_i915_exit);
+EXPORT_SYMBOL_NS(hda_codec_i915_exit, SND_SOC_SOF_HDA_AUDIO_CODEC_I915);
-#endif /* CONFIG_SND_SOC_HDAC_HDMI */
+#endif
MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/sof/intel/hda-compress.c b/sound/soc/sof/intel/hda-compress.c
new file mode 100644
index 0000000..53c0803
--- /dev/null
+++ b/sound/soc/sof/intel/hda-compress.c
@@ -0,0 +1,114 @@
+// 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 df1909e..fa5f0a7 100644
--- a/sound/soc/sof/intel/hda-ctrl.c
+++ b/sound/soc/sof/intel/hda-ctrl.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -15,11 +15,19 @@
* Hardware interface for generic Intel audio DSP HDA IP
*/
+#include <linux/module.h>
#include <sound/hdaudio_ext.h>
#include <sound/hda_register.h>
+#include <sound/hda_component.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.
*/
@@ -57,15 +65,32 @@
struct hdac_bus *bus = sof_to_bus(sdev);
u32 cap, offset, feature;
int count = 0;
+ int ret;
+
+ /*
+ * On some devices, one reset cycle is necessary before reading
+ * capabilities
+ */
+ ret = hda_dsp_ctrl_link_reset(sdev, true);
+ if (ret < 0)
+ return ret;
+ ret = hda_dsp_ctrl_link_reset(sdev, false);
+ if (ret < 0)
+ return ret;
offset = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_LLCH);
do {
- cap = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, offset);
-
dev_dbg(sdev->dev, "checking for capabilities at offset 0x%x\n",
offset & SOF_HDA_CAP_NEXT_MASK);
+ cap = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, offset);
+
+ if (cap == -1) {
+ dev_dbg(bus->dev, "Invalid capability reg read\n");
+ break;
+ }
+
feature = (cap & SOF_HDA_CAP_ID_MASK) >> SOF_HDA_CAP_ID_OFF;
switch (feature) {
@@ -98,8 +123,8 @@
bus->mlcap = bus->remap_addr + offset;
break;
default:
- dev_vdbg(sdev->dev, "found capability %d at 0x%x\n",
- feature, offset);
+ dev_dbg(sdev->dev, "found capability %d at 0x%x\n",
+ feature, offset);
break;
}
@@ -169,6 +194,9 @@
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);
if (full_reset) {
@@ -176,7 +204,7 @@
ret = hda_dsp_ctrl_link_reset(sdev, true);
if (ret < 0) {
dev_err(sdev->dev, "error: failed to reset HDA controller\n");
- return ret;
+ goto err;
}
usleep_range(500, 1000);
@@ -185,7 +213,7 @@
ret = hda_dsp_ctrl_link_reset(sdev, false);
if (ret < 0) {
dev_err(sdev->dev, "error: failed to exit HDA controller reset\n");
- return ret;
+ goto err;
}
usleep_range(1000, 1200);
@@ -195,7 +223,8 @@
/* check to see if controller is ready */
if (!snd_hdac_chip_readb(bus, GCTL)) {
dev_dbg(bus->dev, "controller not ready!\n");
- return -EBUSY;
+ ret = -EBUSY;
+ goto err;
}
/* Accept unsolicited responses */
@@ -206,6 +235,12 @@
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);
+ }
#endif
/* clear stream status */
@@ -255,7 +290,11 @@
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
return ret;
}
diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c
index 3f64520..ef31631 100644
--- a/sound/soc/sof/intel/hda-dai.c
+++ b/sound/soc/sof/intel/hda-dai.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -11,6 +11,7 @@
#include <sound/pcm_params.h>
#include <sound/hdaudio_ext.h>
#include "../sof-priv.h"
+#include "../sof-audio.h"
#include "hda.h"
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
@@ -55,7 +56,7 @@
hda_link_stream_assign(struct hdac_bus *bus,
struct snd_pcm_substream *substream)
{
- struct snd_soc_pcm_runtime *rtd = substream->private_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;
@@ -67,6 +68,7 @@
return NULL;
}
+ spin_lock_irq(&bus->reg_lock);
list_for_each_entry(stream, &bus->stream_list, list) {
struct hdac_ext_stream *hstream =
stream_to_hdac_ext_stream(stream);
@@ -106,12 +108,12 @@
* 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);
+ snd_hdac_ext_stream_decouple_locked(bus, res, true);
+
res->link_locked = 1;
res->link_substream = substream;
- spin_unlock_irq(&bus->reg_lock);
}
+ spin_unlock_irq(&bus->reg_lock);
return res;
}
@@ -202,8 +204,8 @@
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 = snd_pcm_substream_chip(substream);
- struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
struct sof_intel_hda_stream *hda_stream;
struct hda_pipe_params p_params = {0};
struct hdac_ext_link *link;
@@ -263,7 +265,7 @@
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 = snd_pcm_substream_chip(substream);
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
int stream = substream->stream;
if (link_dev->link_prepared)
@@ -290,9 +292,9 @@
hstream = substream->runtime->private_data;
bus = hstream->bus;
- rtd = snd_pcm_substream_chip(substream);
+ rtd = asoc_substream_to_rtd(substream);
- link = snd_hdac_ext_bus_get_link(bus, rtd->codec_dai->component->name);
+ link = snd_hdac_ext_bus_get_link(bus, asoc_rtd_to_codec(rtd, 0)->component->name);
if (!link)
return -EINVAL;
@@ -309,7 +311,7 @@
return ret;
}
- /* fallthrough */
+ fallthrough;
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
snd_hdac_ext_link_stream_start(link_dev);
@@ -332,7 +334,7 @@
link_dev->link_prepared = 0;
- /* fallthrough */
+ fallthrough;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
snd_hdac_ext_link_stream_clear(link_dev);
break;
@@ -356,7 +358,7 @@
hstream = substream->runtime->private_data;
bus = hstream->bus;
- rtd = snd_pcm_substream_chip(substream);
+ rtd = asoc_substream_to_rtd(substream);
link_dev = snd_soc_dai_get_dma_data(dai, substream);
if (!link_dev) {
@@ -373,7 +375,7 @@
if (ret < 0)
return ret;
- link = snd_hdac_ext_bus_get_link(bus, rtd->codec_dai->component->name);
+ link = snd_hdac_ext_bus_get_link(bus, asoc_rtd_to_codec(rtd, 0)->component->name);
if (!link)
return -EINVAL;
@@ -398,6 +400,19 @@
.trigger = hda_link_pcm_trigger,
.prepare = hda_link_pcm_prepare,
};
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES)
+#include "../compress.h"
+
+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,
+};
+
+#endif
#endif
/*
@@ -408,56 +423,167 @@
struct snd_soc_dai_driver skl_dai[] = {
{
.name = "SSP0 Pin",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
},
{
.name = "SSP1 Pin",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
},
{
.name = "SSP2 Pin",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
},
{
.name = "SSP3 Pin",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
},
{
.name = "SSP4 Pin",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
},
{
.name = "SSP5 Pin",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
},
{
.name = "DMIC01 Pin",
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 4,
+ },
},
{
.name = "DMIC16k Pin",
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 4,
+ },
},
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
{
.name = "iDisp1 Pin",
.ops = &hda_link_dai_ops,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
},
{
.name = "iDisp2 Pin",
.ops = &hda_link_dai_ops,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
},
{
.name = "iDisp3 Pin",
.ops = &hda_link_dai_ops,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
},
{
.name = "iDisp4 Pin",
.ops = &hda_link_dai_ops,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 8,
+ },
},
{
.name = "Analog CPU DAI",
.ops = &hda_link_dai_ops,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 16,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 16,
+ },
},
{
.name = "Digital CPU DAI",
.ops = &hda_link_dai_ops,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 16,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 16,
+ },
},
{
.name = "Alt Analog CPU DAI",
.ops = &hda_link_dai_ops,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 16,
+ },
+ .capture = {
+ .channels_min = 1,
+ .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
};
diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c
index 06715b3..85ec436 100644
--- a/sound/soc/sof/intel/hda-dsp.c
+++ b/sound/soc/sof/intel/hda-dsp.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -15,10 +15,20 @@
* Hardware interface for generic Intel audio DSP HDA IP
*/
+#include <linux/module.h>
#include <sound/hdaudio_ext.h>
#include <sound/hda_register.h>
+#include "../sof-audio.h"
#include "../ops.h"
#include "hda.h"
+#include "hda-ipc.h"
+
+static bool hda_enable_trace_D0I3_S0;
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG)
+module_param_named(enable_trace_D0I3_S0, hda_enable_trace_D0I3_S0, bool, 0444);
+MODULE_PARM_DESC(enable_trace_D0I3_S0,
+ "SOF HDA enable trace when the DSP is in D0I3 in S0");
+#endif
/*
* DSP Core control.
@@ -42,6 +52,12 @@
((adspcs & reset) == reset),
HDA_DSP_REG_POLL_INTERVAL_US,
HDA_DSP_RESET_TIMEOUT_US);
+ if (ret < 0) {
+ dev_err(sdev->dev,
+ "error: %s: timeout on HDA_DSP_REG_ADSPCS read\n",
+ __func__);
+ return ret;
+ }
/* has core entered reset ? */
adspcs = snd_sof_dsp_read(sdev, HDA_DSP_BAR,
@@ -77,6 +93,13 @@
HDA_DSP_REG_POLL_INTERVAL_US,
HDA_DSP_RESET_TIMEOUT_US);
+ if (ret < 0) {
+ dev_err(sdev->dev,
+ "error: %s: timeout on HDA_DSP_REG_ADSPCS read\n",
+ __func__);
+ return ret;
+ }
+
/* has core left reset ? */
adspcs = snd_sof_dsp_read(sdev, HDA_DSP_BAR,
HDA_DSP_REG_ADSPCS);
@@ -151,8 +174,12 @@
(adspcs & cpa) == cpa,
HDA_DSP_REG_POLL_INTERVAL_US,
HDA_DSP_RESET_TIMEOUT_US);
- if (ret < 0)
- dev_err(sdev->dev, "error: timeout on core powerup\n");
+ if (ret < 0) {
+ dev_err(sdev->dev,
+ "error: %s: timeout on HDA_DSP_REG_ADSPCS read\n",
+ __func__);
+ return ret;
+ }
/* did core power up ? */
adspcs = snd_sof_dsp_read(sdev, HDA_DSP_BAR,
@@ -171,17 +198,24 @@
int hda_dsp_core_power_down(struct snd_sof_dev *sdev, unsigned int core_mask)
{
u32 adspcs;
+ int ret;
/* update bits */
snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR,
HDA_DSP_REG_ADSPCS,
HDA_DSP_ADSPCS_SPA_MASK(core_mask), 0);
- return snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR,
+ ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR,
HDA_DSP_REG_ADSPCS, adspcs,
!(adspcs & HDA_DSP_ADSPCS_CPA_MASK(core_mask)),
HDA_DSP_REG_POLL_INTERVAL_US,
HDA_DSP_PD_TIMEOUT * USEC_PER_MSEC);
+ if (ret < 0)
+ dev_err(sdev->dev,
+ "error: %s: timeout on HDA_DSP_REG_ADSPCS read\n",
+ __func__);
+
+ return ret;
}
bool hda_dsp_core_is_enabled(struct snd_sof_dev *sdev,
@@ -212,10 +246,15 @@
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 */
@@ -232,8 +271,17 @@
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) {
@@ -289,6 +337,277 @@
HDA_DSP_REG_HIPCCTL_BUSY | HDA_DSP_REG_HIPCCTL_DONE, 0);
}
+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;
+
+ while (snd_hdac_chip_readb(bus, VS_D0I3C) & SOF_HDA_VS_D0I3C_CIP) {
+ if (!retry--)
+ return -ETIMEDOUT;
+ usleep_range(10, 15);
+ }
+
+ return 0;
+}
+
+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));
+
+ /* 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(sdev->ipc, pm_gate.hdr.cmd,
+ &pm_gate, sizeof(pm_gate), &reply,
+ sizeof(reply));
+}
+
+static int hda_dsp_update_d0i3c_register(struct snd_sof_dev *sdev, u8 value)
+{
+ struct hdac_bus *bus = sof_to_bus(sdev);
+ int ret;
+
+ /* 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");
+ return ret;
+ }
+
+ /* Update D0I3C register */
+ snd_hdac_chip_updateb(bus, VS_D0I3C, SOF_HDA_VS_D0I3C_I3, value);
+
+ /* 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");
+ return ret;
+ }
+
+ dev_vdbg(bus->dev, "D0I3C updated, register = 0x%x\n",
+ snd_hdac_chip_readb(bus, VS_D0I3C));
+
+ return 0;
+}
+
+static int hda_dsp_set_D0_state(struct snd_sof_dev *sdev,
+ const struct sof_dsp_power_state *target_state)
+{
+ u32 flags = 0;
+ int ret;
+ u8 value = 0;
+
+ /*
+ * Sanity check for illegal state transitions
+ * The only allowed transitions are:
+ * 1. D3 -> D0I0
+ * 2. D0I0 -> D0I3
+ * 3. D0I3 -> D0I0
+ */
+ switch (sdev->dsp_power_state.state) {
+ case SOF_DSP_PM_D0:
+ /* Follow the sequence below for D0 substate transitions */
+ break;
+ case SOF_DSP_PM_D3:
+ /* Follow regular flow for D3 -> D0 transition */
+ return 0;
+ default:
+ dev_err(sdev->dev, "error: transition from %d to %d not allowed\n",
+ sdev->dsp_power_state.state, target_state->state);
+ return -EINVAL;
+ }
+
+ /* Set flags and register value for D0 target substate */
+ if (target_state->substate == SOF_HDA_DSP_PM_D0I3) {
+ value = SOF_HDA_VS_D0I3C_I3;
+
+ /*
+ * Trace DMA need to be disabled when the DSP enters
+ * D0I3 for S0Ix suspend, but it can be kept enabled
+ * when the DSP enters D0I3 while the system is in S0
+ * for debug purpose.
+ */
+ if (!sdev->dtrace_is_supported ||
+ !hda_enable_trace_D0I3_S0 ||
+ sdev->system_suspend_target != SOF_SUSPEND_NONE)
+ flags = HDA_PM_NO_DMA_TRACE;
+ } else {
+ /* prevent power gating in D0I0 */
+ flags = HDA_PM_PPG;
+ }
+
+ /* update D0I3C register */
+ ret = hda_dsp_update_d0i3c_register(sdev, value);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Notify the DSP of the state change.
+ * If this IPC fails, revert the D0I3C register update in order
+ * to prevent partial state change.
+ */
+ ret = hda_dsp_send_pm_gate_ipc(sdev, flags);
+ if (ret < 0) {
+ dev_err(sdev->dev,
+ "error: PM_GATE ipc error %d\n", ret);
+ goto revert;
+ }
+
+ return ret;
+
+revert:
+ /* fallback to the previous register value */
+ value = value ? 0 : SOF_HDA_VS_D0I3C_I3;
+
+ /*
+ * This can fail but return the IPC error to signal that
+ * the state change failed.
+ */
+ hda_dsp_update_d0i3c_register(sdev, value);
+
+ return ret;
+}
+
+/* helper to log DSP state */
+static void hda_dsp_state_log(struct snd_sof_dev *sdev)
+{
+ switch (sdev->dsp_power_state.state) {
+ case SOF_DSP_PM_D0:
+ switch (sdev->dsp_power_state.substate) {
+ case SOF_HDA_DSP_PM_D0I0:
+ dev_dbg(sdev->dev, "Current DSP power state: D0I0\n");
+ break;
+ case SOF_HDA_DSP_PM_D0I3:
+ dev_dbg(sdev->dev, "Current DSP power state: D0I3\n");
+ break;
+ default:
+ dev_dbg(sdev->dev, "Unknown DSP D0 substate: %d\n",
+ sdev->dsp_power_state.substate);
+ break;
+ }
+ break;
+ case SOF_DSP_PM_D1:
+ dev_dbg(sdev->dev, "Current DSP power state: D1\n");
+ break;
+ 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);
+ break;
+ }
+}
+
+/*
+ * All DSP power state transitions are initiated by the driver.
+ * If the requested state change fails, the error is simply returned.
+ * Further state transitions are attempted only when the set_power_save() op
+ * 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)
+{
+ 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);
+ break;
+ case SOF_DSP_PM_D3:
+ /* The only allowed transition is: D0I0 -> D3 */
+ if (sdev->dsp_power_state.state == SOF_DSP_PM_D0 &&
+ sdev->dsp_power_state.substate == SOF_HDA_DSP_PM_D0I0)
+ break;
+
+ dev_err(sdev->dev,
+ "error: transition from %d to %d not allowed\n",
+ sdev->dsp_power_state.state, target_state->state);
+ return -EINVAL;
+ default:
+ dev_err(sdev->dev, "error: target state unsupported %d\n",
+ target_state->state);
+ return -EINVAL;
+ }
+ if (ret < 0) {
+ dev_err(sdev->dev,
+ "failed to set requested target DSP state %d substate %d\n",
+ target_state->state, target_state->substate);
+ return ret;
+ }
+
+ sdev->dsp_power_state = *target_state;
+ hda_dsp_state_log(sdev);
+ return ret;
+}
+
+/*
+ * Audio DSP states may transform as below:-
+ *
+ * Opportunistic D0I3 in S0
+ * Runtime +---------------------+ Delayed D0i3 work timeout
+ * suspend | +--------------------+
+ * +------------+ D0I0(active) | |
+ * | | <---------------+ |
+ * | +--------> | New IPC | |
+ * | |Runtime +--^--+---------^--+--+ (via mailbox) | |
+ * | |resume | | | | | |
+ * | | | | | | | |
+ * | | System| | | | | |
+ * | | resume| | S3/S0IX | | | |
+ * | | | | suspend | | S0IX | |
+ * | | | | | |suspend | |
+ * | | | | | | | |
+ * | | | | | | | |
+ * +-v---+-----------+--v-------+ | | +------+----v----+
+ * | | | +-----------> |
+ * | D3 (suspended) | | | D0I3 |
+ * | | +--------------+ |
+ * | | System resume | |
+ * +----------------------------+ +----------------+
+ *
+ * S0IX suspend: The DSP is in D0I3 if any D0I3-compatible streams
+ * ignored the suspend trigger. Otherwise the DSP
+ * is in D3.
+ */
+
static int hda_suspend(struct snd_sof_dev *sdev, bool runtime_suspend)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
@@ -298,6 +617,8 @@
#endif
int ret;
+ hda_sdw_int_enable(sdev, false);
+
/* disable IPC interrupts */
hda_dsp_ipc_int_disable(sdev);
@@ -310,7 +631,7 @@
#endif
/* power down DSP */
- ret = hda_dsp_core_reset_power_down(sdev, chip->cores_mask);
+ ret = hda_dsp_core_reset_power_down(sdev, chip->host_managed_cores_mask);
if (ret < 0) {
dev_err(sdev->dev,
"error: failed to power down core during suspend\n");
@@ -336,6 +657,9 @@
return ret;
}
+ /* display codec can powered off after link reset */
+ hda_codec_i915_display_power(sdev, false);
+
return 0;
}
@@ -347,6 +671,9 @@
#endif
int ret;
+ /* display codec must be powered before link reset */
+ hda_codec_i915_display_power(sdev, true);
+
/*
* clear TCSEL to clear playback on some HD Audio
* codecs. PCI TCSEL is defined in the Intel manuals.
@@ -363,8 +690,10 @@
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
/* check jack status */
- if (runtime_resume)
- hda_codec_jack_check(sdev);
+ if (runtime_resume) {
+ 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) {
@@ -386,14 +715,82 @@
int hda_dsp_resume(struct snd_sof_dev *sdev)
{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+ 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;
+ }
+ }
+ }
+
+ /* set up CORB/RIRB buffers if was on before suspend */
+ if (bus->cmd_dma_state)
+ snd_hdac_bus_init_cmd_io(bus);
+#endif
+
+ /* Set DSP power state */
+ ret = snd_sof_dsp_set_power_state(sdev, &target_state);
+ if (ret < 0) {
+ dev_err(sdev->dev, "error: setting dsp state %d substate %d\n",
+ target_state.state, target_state.substate);
+ return ret;
+ }
+
+ /* restore L1SEN bit */
+ if (hda->l1_support_changed)
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
+ HDA_VS_INTEL_EM2,
+ HDA_VS_INTEL_EM2_L1SEN, 0);
+
+ /* restore and disable the system wakeup */
+ pci_restore_state(pci);
+ disable_irq_wake(pci->irq);
+ return 0;
+ }
+
/* init hda controller. DSP cores will be powered up during fw boot */
- return hda_resume(sdev, false);
+ ret = hda_resume(sdev, false);
+ if (ret < 0)
+ return ret;
+
+ return snd_sof_dsp_set_power_state(sdev, &target_state);
}
int hda_dsp_runtime_resume(struct snd_sof_dev *sdev)
{
+ const struct sof_dsp_power_state target_state = {
+ .state = SOF_DSP_PM_D0,
+ };
+ int ret;
+
/* init hda controller. DSP cores will be powered up during fw boot */
- return hda_resume(sdev, true);
+ ret = hda_resume(sdev, true);
+ if (ret < 0)
+ return ret;
+
+ return snd_sof_dsp_set_power_state(sdev, &target_state);
}
int hda_dsp_runtime_idle(struct snd_sof_dev *sdev)
@@ -411,15 +808,79 @@
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;
+
+ /* cancel any attempt for DSP D0I3 */
+ cancel_delayed_work_sync(&hda->d0i3_work);
+
/* stop hda controller and power dsp off */
- return hda_suspend(sdev, true);
+ ret = hda_suspend(sdev, true);
+ if (ret < 0)
+ return ret;
+
+ return snd_sof_dsp_set_power_state(sdev, &target_state);
}
-int hda_dsp_suspend(struct snd_sof_dev *sdev)
+int hda_dsp_suspend(struct snd_sof_dev *sdev, u32 target_state)
{
+ 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_dsp_state = {
+ .state = target_state,
+ .substate = target_state == SOF_DSP_PM_D0 ?
+ SOF_HDA_DSP_PM_D0I3 : 0,
+ };
int ret;
+ /* 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) {
+ dev_err(sdev->dev, "error: setting dsp state %d substate %d\n",
+ target_dsp_state.state,
+ target_dsp_state.substate);
+ return ret;
+ }
+
+ /* 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 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);
+
+ /* no link can be powered in s0ix state */
+ ret = snd_hdac_ext_bus_link_power_down_all(bus);
+ if (ret < 0) {
+ dev_dbg(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);
+ pci_save_state(pci);
+ return 0;
+ }
+
/* stop hda controller and power dsp off */
ret = hda_suspend(sdev, false);
if (ret < 0) {
@@ -427,7 +888,7 @@
return ret;
}
- return 0;
+ return snd_sof_dsp_set_power_state(sdev, &target_dsp_state);
}
int hda_dsp_set_hw_params_upon_resume(struct snd_sof_dev *sdev)
@@ -452,8 +913,8 @@
* explicitly during suspend.
*/
if (stream->link_substream) {
- rtd = snd_pcm_substream_chip(stream->link_substream);
- name = rtd->codec_dai->component->name;
+ 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;
@@ -471,3 +932,33 @@
#endif
return 0;
}
+
+void hda_dsp_d0i3_work(struct work_struct *work)
+{
+ struct sof_intel_hda_dev *hdev = container_of(work,
+ struct sof_intel_hda_dev,
+ 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;
+ 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)
+ return;
+
+ /* This can fail but error cannot be propagated */
+ ret = snd_sof_dsp_set_power_state(sdev, &target_state);
+ if (ret < 0)
+ dev_err_ratelimited(sdev->dev,
+ "error: failed to set DSP state %d substate %d\n",
+ target_state.state, target_state.substate);
+}
diff --git a/sound/soc/sof/intel/hda-ipc.c b/sound/soc/sof/intel/hda-ipc.c
index 6aae6f1..acfeca4 100644
--- a/sound/soc/sof/intel/hda-ipc.c
+++ b/sound/soc/sof/intel/hda-ipc.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -83,10 +83,12 @@
}
hdr = msg->msg_data;
- if (hdr->cmd == (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CTX_SAVE)) {
+ 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 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;
@@ -104,7 +106,9 @@
ret = reply.error;
} else {
/* reply correct size ? */
- if (reply.hdr.size != msg->reply_size) {
+ if (reply.hdr.size != msg->reply_size &&
+ /* getter payload is never known upfront */
+ ((reply.hdr.cmd & SOF_GLB_TYPE_MASK) != SOF_IPC_GLB_PROBE)) {
dev_err(sdev->dev, "error: reply expected %zu got %u bytes\n",
msg->reply_size, reply.hdr.size);
ret = -EINVAL;
@@ -121,12 +125,6 @@
}
-static bool hda_dsp_ipc_is_sof(uint32_t msg)
-{
- return (msg & (HDA_DSP_IPC_PURGE_FW | 0xf << 9)) != msg ||
- (msg & HDA_DSP_IPC_PURGE_FW) != HDA_DSP_IPC_PURGE_FW;
-}
-
/* IPC handler thread */
irqreturn_t hda_dsp_ipc_irq_thread(int irq, void *context)
{
@@ -172,17 +170,9 @@
*/
spin_lock_irq(&sdev->ipc_lock);
- /* handle immediate reply from DSP core - ignore ROM messages */
- if (hda_dsp_ipc_is_sof(msg)) {
- hda_dsp_ipc_get_reply(sdev);
- snd_sof_ipc_reply(sdev, msg);
- }
-
- /* wake up sleeper if we are loading code */
- if (sdev->code_loading) {
- sdev->code_loading = 0;
- wake_up(&sdev->waitq);
- }
+ /* 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);
@@ -228,22 +218,15 @@
"nothing to do in IPC IRQ thread\n");
}
- /* re-enable IPC interrupt */
- snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIC,
- HDA_DSP_ADSPIC_IPC, HDA_DSP_ADSPIC_IPC);
-
return IRQ_HANDLED;
}
-/* is this IRQ for ADSP ? - we only care about IPC here */
-irqreturn_t hda_dsp_ipc_irq_handler(int irq, void *context)
+/* Check if an IPC IRQ occurred */
+bool hda_dsp_check_ipc_irq(struct snd_sof_dev *sdev)
{
- struct snd_sof_dev *sdev = context;
- int ret = IRQ_NONE;
+ bool ret = false;
u32 irq_status;
- spin_lock(&sdev->hw_lock);
-
/* 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);
@@ -253,16 +236,10 @@
goto out;
/* IPC message ? */
- if (irq_status & HDA_DSP_ADSPIS_IPC) {
- /* disable IPC interrupt */
- snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR,
- HDA_DSP_REG_ADSPIC,
- HDA_DSP_ADSPIC_IPC, 0);
- ret = IRQ_WAKE_THREAD;
- }
+ if (irq_status & HDA_DSP_ADSPIS_IPC)
+ ret = true;
out:
- spin_unlock(&sdev->hw_lock);
return ret;
}
diff --git a/sound/soc/sof/intel/hda-ipc.h b/sound/soc/sof/intel/hda-ipc.h
new file mode 100644
index 0000000..10fbca5
--- /dev/null
+++ b/sound/soc/sof/intel/hda-ipc.h
@@ -0,0 +1,55 @@
+/* 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.
+ *
+ * Author: Keyon Jie <yang.jie@linux.intel.com>
+ */
+
+#ifndef __SOF_INTEL_HDA_IPC_H
+#define __SOF_INTEL_HDA_IPC_H
+
+/*
+ * Primary register, mapped to
+ * - DIPCTDR (HIPCIDR) in sideband IPC (cAVS 1.8+)
+ * - DIPCT in cAVS 1.5 IPC
+ *
+ * Secondary register, mapped to:
+ * - DIPCTDD (HIPCIDD) in sideband IPC (cAVS 1.8+)
+ * - DIPCTE in cAVS 1.5 IPC
+ */
+
+/* Common bits in primary register */
+
+/* Reserved for doorbell */
+#define HDA_IPC_RSVD_31 BIT(31)
+/* Target, 0 - normal message, 1 - compact message(cAVS compatible) */
+#define HDA_IPC_MSG_COMPACT BIT(30)
+/* Direction, 0 - request, 1 - response */
+#define HDA_IPC_RSP BIT(29)
+
+#define HDA_IPC_TYPE_SHIFT 24
+#define HDA_IPC_TYPE_MASK GENMASK(28, 24)
+#define HDA_IPC_TYPE(x) ((x) << HDA_IPC_TYPE_SHIFT)
+
+#define HDA_IPC_PM_GATE HDA_IPC_TYPE(0x8U)
+
+/* Command specific payload bits in secondary register */
+
+/* Disable DMA tracing (0 - keep tracing, 1 - to disable DMA trace) */
+#define HDA_PM_NO_DMA_TRACE BIT(4)
+/* Prevent clock gating (0 - cg allowed, 1 - DSP clock always on) */
+#define HDA_PM_PCG BIT(3)
+/* Prevent power gating (0 - deep power state transitions allowed) */
+#define HDA_PM_PPG BIT(2)
+/* Indicates whether streaming is active */
+#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);
+
+#endif
diff --git a/sound/soc/sof/intel/hda-loader.c b/sound/soc/sof/intel/hda-loader.c
index 356bb13..2707a16 100644
--- a/sound/soc/sof/intel/hda-loader.c
+++ b/sound/soc/sof/intel/hda-loader.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -17,31 +17,28 @@
#include <linux/firmware.h>
#include <sound/hdaudio_ext.h>
+#include <sound/hda_register.h>
#include <sound/sof.h>
#include "../ops.h"
#include "hda.h"
#define HDA_FW_BOOT_ATTEMPTS 3
+#define HDA_CL_STREAM_FORMAT 0x40
-static int cl_stream_prepare(struct snd_sof_dev *sdev, unsigned int format,
- unsigned int size, struct snd_dma_buffer *dmab,
- int direction)
+static struct hdac_ext_stream *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_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;
- }
-
dsp_stream = hda_dsp_stream_get(sdev, direction);
if (!dsp_stream) {
dev_err(sdev->dev, "error: no stream available\n");
- return -ENODEV;
+ return ERR_PTR(-ENODEV);
}
hstream = &dsp_stream->hstream;
hstream->substream = NULL;
@@ -57,20 +54,27 @@
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, dsp_stream, dmab, NULL);
+ if (ret < 0) {
+ dev_err(sdev->dev, "error: iccmax stream prepare failed: %x\n", ret);
+ goto error;
+ }
+ } else {
+ 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;
+ }
+ hda_dsp_stream_spib_config(sdev, dsp_stream, HDA_DSP_SPIB_ENABLE, size);
}
- hda_dsp_stream_spib_config(sdev, dsp_stream, HDA_DSP_SPIB_ENABLE, size);
-
- return hstream->stream_tag;
+ return dsp_stream;
error:
hda_dsp_stream_put(sdev, direction, hstream->stream_tag);
snd_dma_free_pages(dmab);
- return ret;
+ return ERR_PTR(ret);
}
/*
@@ -78,8 +82,7 @@
* status on core 1, so power up core 1 also momentarily, keep it in
* reset/stall and then turn it off
*/
-static int cl_dsp_init(struct snd_sof_dev *sdev, const void *fwdata,
- u32 fwsize, int stream_tag)
+static int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
const struct sof_intel_dsp_desc *chip = hda->desc;
@@ -88,9 +91,10 @@
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;
}
@@ -110,9 +114,11 @@
((stream_tag - 1) << 9)));
/* 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, BIT(0));
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,15 +132,25 @@
HDA_DSP_INIT_TIMEOUT_US);
if (ret < 0) {
- dev_err(sdev->dev, "error: waiting for HIPCIE done\n");
+ if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS)
+ dev_err(sdev->dev,
+ "error: %s: timeout for HIPCIE done\n",
+ __func__);
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 5: power down corex */
- ret = hda_dsp_core_power_down(sdev,
- chip->cores_mask & ~(HDA_DSP_CORE_MASK(0)));
+ ret = hda_dsp_core_power_down(sdev, chip->host_managed_cores_mask & ~(BIT(0)));
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;
}
@@ -152,9 +168,14 @@
if (!ret)
return 0;
+ if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS)
+ dev_err(sdev->dev,
+ "error: %s: timeout HDA_DSP_SRAM_REG_ROM_STATUS read\n",
+ __func__);
+
err:
hda_dsp_dump(sdev, SOF_DBG_REGS | SOF_DBG_PCI | SOF_DBG_MBOX);
- hda_dsp_core_reset_power_down(sdev, chip->cores_mask);
+ hda_dsp_core_reset_power_down(sdev, chip->host_managed_cores_mask);
return ret;
}
@@ -168,9 +189,6 @@
/* code loader is special case that reuses stream ops */
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
- wait_event_timeout(sdev->waitq, !sdev->code_loading,
- HDA_DSP_CL_TRIGGER_TIMEOUT);
-
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL,
1 << hstream->index,
1 << hstream->index);
@@ -189,34 +207,20 @@
}
}
-static struct hdac_ext_stream *get_stream_with_tag(struct snd_sof_dev *sdev,
- int tag)
-{
- 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;
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, 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;
@@ -253,50 +257,104 @@
HDA_DSP_REG_POLL_INTERVAL_US,
HDA_DSP_BASEFW_TIMEOUT_US);
+ /*
+ * even in case of errors we still need to stop the DMAs,
+ * but we return the initial error should the DMA stop also fail
+ */
+
+ if (status < 0) {
+ dev_err(sdev->dev,
+ "error: %s: timeout HDA_DSP_SRAM_REG_ROM_STATUS read\n",
+ __func__);
+ }
+
ret = cl_trigger(sdev, stream, SNDRV_PCM_TRIGGER_STOP);
if (ret < 0) {
dev_err(sdev->dev, "error: DMA trigger stop failed\n");
- return ret;
+ if (!status)
+ status = ret;
}
return status;
}
+int hda_dsp_cl_boot_firmware_iccmax(struct snd_sof_dev *sdev)
+{
+ struct snd_sof_pdata *plat_data = sdev->pdata;
+ struct hdac_ext_stream *iccmax_stream;
+ struct hdac_bus *bus = sof_to_bus(sdev);
+ struct firmware stripped_firmware;
+ int ret, ret1;
+ u8 original_gb;
+
+ /* save the original LTRP guardband value */
+ original_gb = snd_hdac_chip_readb(bus, VS_LTRP) & HDA_VS_INTEL_LTRP_GB_MASK;
+
+ if (plat_data->fw->size <= plat_data->fw_offset) {
+ dev_err(sdev->dev, "error: firmware size must be greater than firmware offset\n");
+ return -EINVAL;
+ }
+
+ stripped_firmware.size = plat_data->fw->size - plat_data->fw_offset;
+
+ /* prepare capture stream for ICCMAX */
+ iccmax_stream = cl_stream_prepare(sdev, HDA_CL_STREAM_FORMAT, stripped_firmware.size,
+ &sdev->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 = cl_cleanup(sdev, &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_hdac_chip_updateb(bus, VS_LTRP, HDA_VS_INTEL_LTRP_GB_MASK, original_gb);
+
+ 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 firmware stripped_firmware;
- int ret, ret1, tag, i;
+ int ret, ret1, i;
chip_info = desc->chip_info;
- stripped_firmware.data = plat_data->fw->data;
- stripped_firmware.size = plat_data->fw->size;
+ if (plat_data->fw->size <= plat_data->fw_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;
/* 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;
- }
-
- /* 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;
+ stream = cl_stream_prepare(sdev, HDA_CL_STREAM_FORMAT, stripped_firmware.size,
+ &sdev->dmab, SNDRV_PCM_STREAM_PLAYBACK);
+ if (IS_ERR(stream)) {
+ dev_err(sdev->dev, "error: dma prepare for fw loading failed\n");
+ return PTR_ERR(stream);
}
memcpy(sdev->dmab.area, stripped_firmware.data,
@@ -304,29 +362,47 @@
/* 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;
+ ret = cl_dsp_init(sdev, stream->hstream.stream_tag);
/* don't retry anymore if successful */
if (!ret)
break;
-
- dev_err(sdev->dev, "error: 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));
- dev_err(sdev->dev, "error: iteration %d of Core En/ROM load failed: %d\n",
- i, ret);
}
if (i == HDA_FW_BOOT_ATTEMPTS) {
dev_err(sdev->dev, "error: dsp init failed after %d attempts with err: %d\n",
i, ret);
+ dev_err(sdev->dev, "ROM error=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));
goto cleanup;
}
/*
+ * When a SoundWire link is in clock stop state, a Slave
+ * device may trigger in-band wakes for events such as jack
+ * insertion or acoustic event detection. This event will lead
+ * to a WAKEEN interrupt, handled by the PCI device and routed
+ * to PME if the PCI device is in D3. The resume function in
+ * audio PCI driver will be invoked by ACPI for PME event and
+ * initialize the device and process WAKEEN interrupt.
+ *
+ * The WAKEEN interrupt should be processed ASAP to prevent an
+ * interrupt flood, otherwise other interrupts, such IPC,
+ * cannot work normally. The WAKEEN is handled after the ROM
+ * is initialized successfully, which ensures power rails are
+ * enabled before accessing the SoundWire SHIM registers
+ */
+ if (!sdev->first_boot)
+ hda_sdw_process_wakeen(sdev);
+
+ /*
* at this point DSP ROM has been initialized and
* should be ready for code loading and firmware boot
*/
@@ -340,24 +416,25 @@
/*
* Perform codeloader stream 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);
if (ret1 < 0) {
dev_err(sdev->dev, "error: Code loader DSP cleanup failed\n");
/* set return value to indicate cleanup failure */
- ret = ret1;
+ if (!ret)
+ ret = ret1;
}
/*
- * 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 */
@@ -377,6 +454,19 @@
/* post fw run operations */
int hda_dsp_post_fw_run(struct snd_sof_dev *sdev)
{
+ int ret;
+
+ if (sdev->first_boot) {
+ ret = hda_sdw_startup(sdev);
+ if (ret < 0) {
+ dev_err(sdev->dev,
+ "error: could not startup SoundWire links\n");
+ return ret;
+ }
+ }
+
+ hda_sdw_int_enable(sdev, true);
+
/* re-enable clock gating and power gating */
return hda_dsp_ctrl_clock_power_gating(sdev, true);
}
diff --git a/sound/soc/sof/intel/hda-pcm.c b/sound/soc/sof/intel/hda-pcm.c
index 9b730f1..b527d59 100644
--- a/sound/soc/sof/intel/hda-pcm.c
+++ b/sound/soc/sof/intel/hda-pcm.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -17,6 +17,7 @@
#include <sound/hda_register.h>
#include <sound/pcm_params.h>
+#include "../sof-audio.h"
#include "../ops.h"
#include "hda.h"
@@ -26,7 +27,7 @@
#define SDnFMT_BITS(x) ((x) << 4)
#define SDnFMT_CHAN(x) ((x) << 0)
-static inline u32 get_mult_div(struct snd_sof_dev *sdev, int rate)
+u32 hda_dsp_get_mult_div(struct snd_sof_dev *sdev, int rate)
{
switch (rate) {
case 8000:
@@ -60,7 +61,7 @@
}
};
-static inline u32 get_bits(struct snd_sof_dev *sdev, int sample_bits)
+u32 hda_dsp_get_bits(struct snd_sof_dev *sdev, int sample_bits)
{
switch (sample_bits) {
case 8:
@@ -89,12 +90,13 @@
struct hdac_ext_stream *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 = get_mult_div(sdev, params_rate(params));
- bits = get_bits(sdev, params_width(params));
+ rate = hda_dsp_get_mult_div(sdev, params_rate(params));
+ bits = hda_dsp_get_bits(sdev, params_width(params));
hstream->substream = substream;
@@ -116,9 +118,17 @@
/* disable SPIB, to enable buffer wrap for stream */
hda_dsp_stream_spib_config(sdev, stream, HDA_DSP_SPIB_DISABLE, 0);
- /* set host_period_bytes to 0 if no IPC position */
- if (hda && hda->no_ipc_position)
- ipc_params->host_period_bytes = 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;
+ }
ipc_params->stream_tag = hstream->stream_tag;
@@ -137,13 +147,14 @@
snd_pcm_uframes_t hda_dsp_pcm_pointer(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream)
{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_pcm_runtime *rtd = asoc_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;
struct snd_sof_pcm *spcm;
snd_pcm_uframes_t pos;
- spcm = snd_sof_find_spcm_dai(sdev, rtd);
+ 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);
diff --git a/sound/soc/sof/intel/hda-stream.c b/sound/soc/sof/intel/hda-stream.c
index 0c11fce..0e09ede 100644
--- a/sound/soc/sof/intel/hda-stream.c
+++ b/sound/soc/sof/intel/hda-stream.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -20,8 +20,11 @@
#include <sound/hda_register.h>
#include <sound/sof.h>
#include "../ops.h"
+#include "../sof-audio.h"
#include "hda.h"
+#define HDA_LTRP_GB_VALUE_US 95
+
/*
* set up one of BDL entries for a stream
*/
@@ -275,8 +278,12 @@
HDA_DSP_REG_POLL_INTERVAL_US,
HDA_DSP_STREAM_RUN_TIMEOUT);
- if (ret)
+ if (ret < 0) {
+ dev_err(sdev->dev,
+ "error: %s: cmd %d: timeout on STREAM_SD_OFFSET read\n",
+ __func__, cmd);
return ret;
+ }
hstream->running = true;
break;
@@ -294,8 +301,12 @@
HDA_DSP_REG_POLL_INTERVAL_US,
HDA_DSP_STREAM_RUN_TIMEOUT);
- if (ret)
+ 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,
@@ -313,6 +324,73 @@
return 0;
}
+/* minimal recommended programming for ICCMAX stream */
+int hda_dsp_iccmax_stream_hw_params(struct snd_sof_dev *sdev, struct hdac_ext_stream *stream,
+ struct snd_dma_buffer *dmab,
+ struct snd_pcm_hw_params *params)
+{
+ struct hdac_bus *bus = sof_to_bus(sdev);
+ struct hdac_stream *hstream = &stream->hstream;
+ int sd_offset = SOF_STREAM_SD_OFFSET(hstream);
+ int ret;
+ u32 mask = 0x1 << hstream->index;
+
+ if (!stream) {
+ dev_err(sdev->dev, "error: no stream available\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_CL_SD_BDLPL,
+ 0x0);
+ snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_CL_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_CL_SD_BDLPL,
+ (u32)hstream->bdl.addr);
+ snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_CL_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_CL_SD_CBL,
+ hstream->bufsize);
+
+ /* program last valid index */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
+ sd_offset + SOF_HDA_ADSP_REG_CL_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_hdac_chip_updateb(bus, VS_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;
+}
+
/*
* prepare for common hdac registers settings, for both code loader
* and normal stream.
@@ -356,8 +434,12 @@
HDA_DSP_REG_POLL_INTERVAL_US,
HDA_DSP_STREAM_RUN_TIMEOUT);
- if (ret)
+ if (ret < 0) {
+ dev_err(sdev->dev,
+ "error: %s: timeout on STREAM_SD_OFFSET read1\n",
+ __func__);
return ret;
+ }
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
sd_offset + SOF_HDA_ADSP_REG_CL_SD_STS,
@@ -418,8 +500,12 @@
HDA_DSP_REG_POLL_INTERVAL_US,
HDA_DSP_STREAM_RUN_TIMEOUT);
- if (ret)
+ if (ret < 0) {
+ dev_err(sdev->dev,
+ "error: %s: timeout on STREAM_SD_OFFSET read2\n",
+ __func__);
return ret;
+ }
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
sd_offset + SOF_HDA_ADSP_REG_CL_SD_STS,
@@ -530,29 +616,48 @@
SOF_HDA_REG_PP_PPCTL, mask, 0);
spin_unlock_irq(&bus->reg_lock);
+ stream->substream = NULL;
+
return 0;
}
-irqreturn_t hda_dsp_stream_interrupt(int irq, void *context)
+bool hda_dsp_check_stream_irq(struct snd_sof_dev *sdev)
{
- struct hdac_bus *bus = context;
- int ret = IRQ_WAKE_THREAD;
+ struct hdac_bus *bus = sof_to_bus(sdev);
+ bool ret = false;
u32 status;
- spin_lock(&bus->reg_lock);
+ /* 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);
- /* Register inaccessible, ignore it.*/
- if (status == 0xffffffff)
- ret = IRQ_NONE;
+ /* if Register inaccessible, ignore it.*/
+ if (status != 0xffffffff)
+ ret = true;
- spin_unlock(&bus->reg_lock);
+ spin_unlock_irq(&bus->reg_lock);
return ret;
}
+static void
+hda_dsp_set_bytes_transferred(struct hdac_stream *hstream, u64 buffer_size)
+{
+ u64 prev_pos, pos, num_bytes;
+
+ div64_u64_rem(hstream->curr_pos, buffer_size, &prev_pos);
+ pos = snd_hdac_stream_get_pos_posbuf(hstream);
+
+ if (pos < prev_pos)
+ num_bytes = (buffer_size - prev_pos) + pos;
+ else
+ num_bytes = pos - prev_pos;
+
+ hstream->curr_pos += num_bytes;
+}
+
static bool hda_dsp_stream_check(struct hdac_bus *bus, u32 status)
{
struct sof_intel_hda_dev *sof_hda = bus_to_sof_hda(bus);
@@ -570,14 +675,19 @@
snd_hdac_stream_writeb(s, SD_STS, sd_status);
active = true;
- if (!s->substream ||
+ if ((!s->substream && !s->cstream) ||
!s->running ||
(sd_status & SOF_HDA_CL_DMA_SD_INT_COMPLETE) == 0)
continue;
/* Inform ALSA only in case not do that with IPC */
- if (sof_hda->no_ipc_position)
+ 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);
+ snd_compr_fragment_elapsed(s->cstream);
+ }
}
}
@@ -586,7 +696,8 @@
irqreturn_t hda_dsp_stream_threaded_handler(int irq, void *context)
{
- struct hdac_bus *bus = 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
@@ -611,11 +722,16 @@
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);
- snd_hdac_chip_writeb(bus, RIRBSTS,
- RIRB_INT_MASK);
}
}
#endif
diff --git a/sound/soc/sof/intel/hda-trace.c b/sound/soc/sof/intel/hda-trace.c
index 33b23bd..1eb746d 100644
--- a/sound/soc/sof/intel/hda-trace.c
+++ b/sound/soc/sof/intel/hda-trace.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c
index a101667..b0faf05 100644
--- a/sound/soc/sof/intel/hda.c
+++ b/sound/soc/sof/intel/hda.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -18,10 +18,14 @@
#include <sound/hdaudio_ext.h>
#include <sound/hda_register.h>
+#include <linux/acpi.h>
#include <linux/module.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_intel.h>
#include <sound/intel-nhlt.h>
#include <sound/sof.h>
#include <sound/sof/xtensa.h>
+#include "../sof-audio.h"
#include "../ops.h"
#include "hda.h"
@@ -32,10 +36,235 @@
/* platform specific devices */
#include "shim.h"
-#define IS_CFL(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0xa348)
-#define IS_CNL(pci) ((pci)->vendor == 0x8086 && (pci)->device == 0x9dc8)
-
#define EXCEPT_MAX_HDR_SIZE 0x400
+#define HDA_EXT_ROM_STATUS_SIZE 8
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE)
+
+/*
+ * The default for SoundWire clock stop quirks is to power gate the IP
+ * and do a Bus Reset, this will need to be modified when the DSP
+ * needs to remain in D0i3 so that the Master does not lose context
+ * and enumeration is not required on clock restart
+ */
+static int sdw_clock_stop_quirks = SDW_INTEL_CLK_STOP_BUS_RESET;
+module_param(sdw_clock_stop_quirks, int, 0444);
+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);
+ }
+
+ return ret;
+}
+
+static int sdw_free_stream(struct device *dev,
+ struct sdw_intel_stream_free_data *free_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 ret;
+}
+
+static const struct sdw_intel_ops sdw_callback = {
+ .params_stream = sdw_params_stream,
+ .free_stream = sdw_free_stream,
+};
+
+void hda_sdw_int_enable(struct snd_sof_dev *sdev, bool enable)
+{
+ sdw_intel_enable_irq(sdev->bar[HDA_DSP_BAR], enable);
+}
+
+static int hda_sdw_acpi_scan(struct snd_sof_dev *sdev)
+{
+ struct sof_intel_hda_dev *hdev;
+ acpi_handle handle;
+ int ret;
+
+ handle = ACPI_HANDLE(sdev->dev);
+
+ /* save ACPI info for the probe step */
+ hdev = sdev->pdata->hw_pdata;
+
+ ret = sdw_intel_acpi_scan(handle, &hdev->info);
+ if (ret < 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int hda_sdw_probe(struct snd_sof_dev *sdev)
+{
+ struct sof_intel_hda_dev *hdev;
+ struct sdw_intel_res res;
+ void *sdw;
+
+ hdev = sdev->pdata->hw_pdata;
+
+ memset(&res, 0, sizeof(res));
+
+ res.mmio_base = sdev->bar[HDA_DSP_BAR];
+ 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;
+
+ /*
+ * ops and arg fields are not populated for now,
+ * they will be needed when the DAI callbacks are
+ * provided
+ */
+
+ /* we could filter links here if needed, e.g for quirks */
+ res.count = hdev->info.count;
+ res.link_mask = hdev->info.link_mask;
+
+ sdw = sdw_intel_probe(&res);
+ if (!sdw) {
+ dev_err(sdev->dev, "error: SoundWire probe failed\n");
+ return -EINVAL;
+ }
+
+ /* save context */
+ hdev->sdw = sdw;
+
+ return 0;
+}
+
+int hda_sdw_startup(struct snd_sof_dev *sdev)
+{
+ struct sof_intel_hda_dev *hdev;
+
+ hdev = sdev->pdata->hw_pdata;
+
+ if (!hdev->sdw)
+ return 0;
+
+ return sdw_intel_startup(hdev->sdw);
+}
+
+static int hda_sdw_exit(struct snd_sof_dev *sdev)
+{
+ struct sof_intel_hda_dev *hdev;
+
+ hdev = sdev->pdata->hw_pdata;
+
+ hda_sdw_int_enable(sdev, false);
+
+ if (hdev->sdw)
+ sdw_intel_exit(hdev->sdw);
+ hdev->sdw = NULL;
+
+ return 0;
+}
+
+static bool hda_dsp_check_sdw_irq(struct snd_sof_dev *sdev)
+{
+ struct sof_intel_hda_dev *hdev;
+ bool ret = false;
+ u32 irq_status;
+
+ hdev = sdev->pdata->hw_pdata;
+
+ if (!hdev->sdw)
+ return ret;
+
+ /* store status */
+ irq_status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIS2);
+
+ /* invalid message ? */
+ if (irq_status == 0xffffffff)
+ goto out;
+
+ /* SDW message ? */
+ if (irq_status & HDA_DSP_REG_ADSPIS2_SNDW)
+ ret = true;
+
+out:
+ return ret;
+}
+
+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)
+{
+ 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))
+ return true;
+
+ return false;
+}
+
+void hda_sdw_process_wakeen(struct snd_sof_dev *sdev)
+{
+ struct sof_intel_hda_dev *hdev;
+
+ hdev = sdev->pdata->hw_pdata;
+ if (!hdev->sdw)
+ return;
+
+ sdw_intel_process_wakeen_event(hdev->sdw);
+}
+
+#endif
/*
* Debug
@@ -52,10 +281,18 @@
MODULE_PARM_DESC(use_msi, "SOF HDA use PCI MSI mode");
#endif
+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);
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 const struct hda_dsp_msg_code hda_dsp_rom_msg[] = {
@@ -178,8 +415,28 @@
}
}
+/* dump the first 8 dwords representing the extended ROM status */
+static void hda_dsp_dump_ext_rom_status(struct snd_sof_dev *sdev)
+{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
+ char msg[128];
+ int len = 0;
+ u32 value;
+ int i;
+
+ for (i = 0; i < HDA_EXT_ROM_STATUS_SIZE; i++) {
+ value = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_SRAM_REG_ROM_STATUS + i * 0x4);
+ len += snprintf(msg + len, sizeof(msg) - len, " 0x%x", value);
+ }
+
+ sof_dev_dbg_or_err(sdev->dev, hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS,
+ "extended rom status: %s", msg);
+
+}
+
void hda_dsp_dump(struct snd_sof_dev *sdev, u32 flags)
{
+ struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
struct sof_ipc_dsp_oops_xtensa xoops;
struct sof_ipc_panic_info panic_info;
u32 stack[HDA_DSP_STACK_DUMP_SIZE];
@@ -199,8 +456,11 @@
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);
+ sof_dev_dbg_or_err(sdev->dev, hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS,
+ "status = 0x%8.8x panic = 0x%8.8x\n",
+ status, panic);
+
+ hda_dsp_dump_ext_rom_status(sdev);
hda_dsp_get_status(sdev);
}
}
@@ -262,10 +522,6 @@
/* HDA bus init */
sof_hda_bus_init(bus, &pci->dev);
- /* Workaround for a communication error on CFL (bko#199007) and CNL */
- if (IS_CFL(pci) || IS_CNL(pci))
- bus->polling_mode = 1;
-
bus->use_posbuf = 1;
bus->bdl_pos_adj = 0;
bus->sync_write = 1;
@@ -273,7 +529,7 @@
mutex_init(&hbus->prepare_mutex);
hbus->pci = pci;
hbus->mixer_assigned = -1;
- hbus->modelname = "sofbus";
+ hbus->modelname = hda_model;
/* initialise hdac bus */
bus->addr = pci_resource_start(pci, 0);
@@ -288,6 +544,11 @@
/* HDA base */
sdev->bar[HDA_DSP_HDA_BAR] = bus->remap_addr;
+ /* 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");
+
/* get controller capabilities */
ret = hda_dsp_ctrl_get_caps(sdev);
if (ret < 0)
@@ -307,7 +568,7 @@
if (nhlt) {
dmic_num = intel_nhlt_get_dmic_geo(sdev->dev, nhlt);
intel_nhlt_free(nhlt);
- if (dmic_num == 2 || dmic_num == 4)
+ if (dmic_num >= 1 && dmic_num <= 4)
return dmic_num;
}
@@ -344,19 +605,12 @@
static int hda_init_caps(struct snd_sof_dev *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;
- struct snd_soc_acpi_mach_params *mach_params;
- struct snd_soc_acpi_mach *hda_mach;
- struct snd_sof_pdata *pdata = sdev->pdata;
- struct snd_soc_acpi_mach *mach;
- const char *tplg_filename;
- const char *idisp_str;
- const char *dmic_str;
- int dmic_num;
- int codec_num = 0;
- int i;
#endif
+ struct sof_intel_hda_dev *hdev = pdata->hw_pdata;
+ u32 link_mask;
int ret = 0;
device_disable_async_suspend(bus->dev);
@@ -365,113 +619,51 @@
if (bus->ppcap)
dev_dbg(sdev->dev, "PP capability, will probe DSP later.\n");
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
- /* init i915 and HDMI codecs */
- ret = hda_codec_i915_init(sdev);
- if (ret < 0) {
- dev_err(sdev->dev, "error: init i915 and HDMI codec failed\n");
- return ret;
- }
-#endif
-
/* Init HDA controller after i915 init */
ret = hda_dsp_ctrl_init_chip(sdev, true);
if (ret < 0) {
dev_err(bus->dev, "error: init chip failed with ret: %d\n",
ret);
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
- hda_codec_i915_exit(sdev);
-#endif
return ret;
}
+ /* scan SoundWire capabilities exposed by DSDT */
+ ret = hda_sdw_acpi_scan(sdev);
+ if (ret < 0) {
+ dev_dbg(sdev->dev, "skipping SoundWire, not detected with ACPI scan\n");
+ goto skip_soundwire;
+ }
+
+ link_mask = hdev->info.link_mask;
+ if (!link_mask) {
+ dev_dbg(sdev->dev, "skipping SoundWire, no links enabled\n");
+ goto skip_soundwire;
+ }
+
+ /*
+ * probe/allocate SoundWire resources.
+ * The hardware configuration takes place in hda_sdw_startup
+ * after power rails are enabled.
+ * It's entirely possible to have a mix of I2S/DMIC/SoundWire
+ * devices, so we allocate the resources in all cases.
+ */
+ ret = hda_sdw_probe(sdev);
+ if (ret < 0) {
+ dev_err(sdev->dev, "error: SoundWire probe error\n");
+ return ret;
+ }
+
+skip_soundwire:
+
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
if (bus->mlcap)
snd_hdac_ext_bus_get_ml_capabilities(bus);
- /* codec detection */
- if (!bus->codec_mask) {
- dev_info(bus->dev, "no hda codecs found!\n");
- } else {
- dev_info(bus->dev, "hda codecs found, mask %lx\n",
- bus->codec_mask);
-
- for (i = 0; i < HDA_MAX_CODECS; i++) {
- if (bus->codec_mask & (1 << i))
- codec_num++;
- }
-
- /*
- * If no machine driver is found, then:
- *
- * hda machine driver is used if :
- * 1. there is one HDMI codec and one external HDAudio codec
- * 2. only HDMI codec
- */
- if (!pdata->machine && codec_num <= 2 &&
- HDA_IDISP_CODEC(bus->codec_mask)) {
- hda_mach = snd_soc_acpi_intel_hda_machines;
- pdata->machine = hda_mach;
-
- /* topology: use the info from hda_machines */
- pdata->tplg_filename =
- hda_mach->sof_tplg_filename;
-
- /* firmware: pick the first in machine list */
- mach = pdata->desc->machines;
- pdata->fw_filename = mach->sof_fw_filename;
-
- dev_info(bus->dev, "using HDA machine driver %s now\n",
- hda_mach->drv_name);
-
- if (codec_num == 1)
- idisp_str = "-idisp";
- 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;
- }
-
- tplg_filename = pdata->tplg_filename;
- tplg_filename = fixup_tplg_name(sdev, tplg_filename,
- idisp_str, dmic_str);
- if (!tplg_filename) {
- hda_codec_i915_exit(sdev);
- return ret;
- }
- pdata->tplg_filename = tplg_filename;
- }
- }
-
- /* used by hda machine driver to create dai links */
- if (pdata->machine) {
- mach_params = (struct snd_soc_acpi_mach_params *)
- &pdata->machine->mach_params;
- mach_params->codec_mask = bus->codec_mask;
- mach_params->platform = dev_name(sdev->dev);
- }
-
/* create codec instances */
- hda_codec_probe_bus(sdev);
+ hda_codec_probe_bus(sdev, hda_codec_use_common_hdmi);
- hda_codec_i915_put(sdev);
+ if (!HDA_IDISP_CODEC(bus->codec_mask))
+ hda_codec_i915_display_power(sdev, false);
/*
* we are done probing so decrement link counts
@@ -493,6 +685,56 @@
return chip_info;
}
+static irqreturn_t hda_dsp_interrupt_handler(int irq, void *context)
+{
+ struct snd_sof_dev *sdev = context;
+
+ /*
+ * Get global interrupt status. It includes all hardware interrupt
+ * sources in the Intel HD Audio controller.
+ */
+ if (snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTSTS) &
+ SOF_HDA_INTSTS_GIS) {
+
+ /* disable GIE interrupt */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
+ SOF_HDA_INTCTL,
+ SOF_HDA_INT_GLOBAL_EN,
+ 0);
+
+ return IRQ_WAKE_THREAD;
+ }
+
+ return IRQ_NONE;
+}
+
+static irqreturn_t hda_dsp_interrupt_thread(int irq, void *context)
+{
+ struct snd_sof_dev *sdev = context;
+ struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
+
+ /* deal with streams and controller first */
+ if (hda_dsp_check_stream_irq(sdev))
+ hda_dsp_stream_threaded_handler(irq, sdev);
+
+ if (hda_dsp_check_ipc_irq(sdev))
+ sof_ops(sdev)->irq_thread(irq, sdev);
+
+ if (hda_dsp_check_sdw_irq(sdev))
+ hda_dsp_sdw_thread(irq, hdev->sdw);
+
+ if (hda_sdw_check_wakeen_irq(sdev))
+ hda_sdw_process_wakeen(sdev);
+
+ /* enable GIE interrupt */
+ snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
+ SOF_HDA_INTCTL,
+ SOF_HDA_INT_GLOBAL_EN,
+ SOF_HDA_INT_GLOBAL_EN);
+
+ return IRQ_HANDLED;
+}
+
int hda_dsp_probe(struct snd_sof_dev *sdev)
{
struct pci_dev *pci = to_pci_dev(sdev->dev);
@@ -597,9 +839,7 @@
*/
if (hda_use_msi && pci_alloc_irq_vectors(pci, 1, 1, PCI_IRQ_MSI) > 0) {
dev_info(sdev->dev, "use msi interrupt mode\n");
- hdev->irq = pci_irq_vector(pci, 0);
- /* ipc irq number is the same of hda irq */
- sdev->ipc_irq = hdev->irq;
+ sdev->ipc_irq = pci_irq_vector(pci, 0);
/* initialised to "false" by kzalloc() */
sdev->msi_enabled = true;
}
@@ -610,28 +850,17 @@
* in IO-APIC mode, hda->irq and ipc_irq are using the same
* irq number of pci->irq
*/
- hdev->irq = pci->irq;
sdev->ipc_irq = pci->irq;
}
- dev_dbg(sdev->dev, "using HDA IRQ %d\n", hdev->irq);
- ret = request_threaded_irq(hdev->irq, hda_dsp_stream_interrupt,
- hda_dsp_stream_threaded_handler,
- IRQF_SHARED, "AudioHDA", bus);
- if (ret < 0) {
- dev_err(sdev->dev, "error: failed to register HDA IRQ %d\n",
- hdev->irq);
- goto free_irq_vector;
- }
-
dev_dbg(sdev->dev, "using IPC IRQ %d\n", sdev->ipc_irq);
- ret = request_threaded_irq(sdev->ipc_irq, hda_dsp_ipc_irq_handler,
- sof_ops(sdev)->irq_thread, IRQF_SHARED,
- "AudioDSP", sdev);
+ ret = request_threaded_irq(sdev->ipc_irq, hda_dsp_interrupt_handler,
+ hda_dsp_interrupt_thread,
+ IRQF_SHARED, "AudioDSP", sdev);
if (ret < 0) {
dev_err(sdev->dev, "error: failed to register IPC IRQ %d\n",
sdev->ipc_irq);
- goto free_hda_irq;
+ goto free_irq_vector;
}
pci_set_master(pci);
@@ -652,18 +881,15 @@
hda_dsp_ctrl_ppcap_enable(sdev, true);
hda_dsp_ctrl_ppcap_int_enable(sdev, true);
- /* initialize waitq for code loading */
- init_waitqueue_head(&sdev->waitq);
-
/* 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);
+
return 0;
free_ipc_irq:
free_irq(sdev->ipc_irq, sdev);
-free_hda_irq:
- free_irq(hdev->irq, bus);
free_irq_vector:
if (sdev->msi_enabled)
pci_free_irq_vectors(pci);
@@ -674,6 +900,7 @@
hdac_bus_unmap:
platform_device_unregister(hdev->dmic_dev);
iounmap(bus->remap_addr);
+ hda_codec_i915_exit(sdev);
err:
return ret;
}
@@ -685,11 +912,16 @@
struct pci_dev *pci = to_pci_dev(sdev->dev);
const struct sof_intel_dsp_desc *chip = hda->desc;
+ /* cancel any attempt for DSP D0I3 */
+ cancel_delayed_work_sync(&hda->d0i3_work);
+
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
/* codec removal, invoke bus_device_remove */
snd_hdac_ext_bus_device_remove(bus);
#endif
+ hda_sdw_exit(sdev);
+
if (!IS_ERR_OR_NULL(hda->dmic_dev))
platform_device_unregister(hda->dmic_dev);
@@ -703,14 +935,13 @@
/* disable cores */
if (chip)
- hda_dsp_core_reset_power_down(sdev, chip->cores_mask);
+ hda_dsp_core_reset_power_down(sdev, chip->host_managed_cores_mask);
/* disable DSP */
snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL,
SOF_HDA_PPCTL_GPROCEN, 0);
free_irq(sdev->ipc_irq, sdev);
- free_irq(hda->irq, bus);
if (sdev->msi_enabled)
pci_free_irq_vectors(pci);
@@ -730,4 +961,287 @@
return 0;
}
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
+static int hda_generic_machine_select(struct snd_sof_dev *sdev)
+{
+ struct hdac_bus *bus = sof_to_bus(sdev);
+ struct snd_soc_acpi_mach_params *mach_params;
+ struct snd_soc_acpi_mach *hda_mach;
+ 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 i;
+
+ /* codec detection */
+ if (!bus->codec_mask) {
+ dev_info(bus->dev, "no hda codecs found!\n");
+ } else {
+ dev_info(bus->dev, "hda codecs found, mask %lx\n",
+ bus->codec_mask);
+
+ for (i = 0; i < HDA_MAX_CODECS; i++) {
+ if (bus->codec_mask & (1 << i))
+ codec_num++;
+ }
+
+ /*
+ * If no machine driver is found, then:
+ *
+ * generic hda machine driver can handle:
+ * - one HDMI codec, and/or
+ * - one external HDAudio codec
+ */
+ if (!pdata->machine && codec_num <= 2) {
+ hda_mach = snd_soc_acpi_intel_hda_machines;
+
+ /* topology: use the info from hda_machines */
+ pdata->tplg_filename =
+ hda_mach->sof_tplg_filename;
+
+ dev_info(bus->dev, "using HDA machine driver %s now\n",
+ hda_mach->drv_name);
+
+ if (codec_num == 1 && HDA_IDISP_CODEC(bus->codec_mask))
+ idisp_str = "-idisp";
+ 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 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;
+ }
+
+ tplg_filename = pdata->tplg_filename;
+ tplg_filename = fixup_tplg_name(sdev, tplg_filename,
+ idisp_str, dmic_str);
+ if (!tplg_filename)
+ return -EINVAL;
+
+ dev_info(bus->dev,
+ "DMICs detected in NHLT tables: %d\n",
+ dmic_num);
+
+ pdata->machine = hda_mach;
+ pdata->tplg_filename = tplg_filename;
+ }
+ }
+
+ /* used by hda machine driver to create dai links */
+ if (pdata->machine) {
+ mach_params = (struct snd_soc_acpi_mach_params *)
+ &pdata->machine->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;
+ }
+
+ return 0;
+}
+#else
+static int hda_generic_machine_select(struct snd_sof_dev *sdev)
+{
+ 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)
+{
+ 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;
+ int i;
+
+ hdev = pdata->hw_pdata;
+ link_mask = hdev->info.link_mask;
+
+ /*
+ * Select SoundWire machine driver if needed using the
+ * alternate tables. This case deals with SoundWire-only
+ * machines, for mixed cases with I2C/I2S the detection relies
+ * on the HID list.
+ */
+ if (link_mask && !pdata->machine) {
+ for (mach = pdata->desc->alt_machines;
+ mach && mach->link_mask; mach++) {
+ /*
+ * On some platforms such as Up Extreme all links
+ * are enabled but only one link can be used by
+ * external codec. Instead of exact match of two masks,
+ * first check whether link_mask of mach is subset of
+ * link_mask supported by hw and then go on searching
+ * link_adr
+ */
+ if (~link_mask & mach->link_mask)
+ continue;
+
+ /* No need to match adr if there is no links defined */
+ if (!mach->links)
+ break;
+
+ link = mach->links;
+ for (i = 0; i < hdev->info.count && link->num_adr;
+ i++, link++) {
+ /*
+ * Try next machine if any expected Slaves
+ * are not found on this link.
+ */
+ if (!link_slaves_found(sdev, link, hdev->sdw))
+ break;
+ }
+ /* Found if all Slaves are checked */
+ if (i == hdev->info.count || !link->num_adr)
+ 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;
+ 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");
+ }
+ }
+
+ return 0;
+}
+#else
+static int hda_sdw_machine_select(struct snd_sof_dev *sdev)
+{
+ return 0;
+}
+#endif
+
+void hda_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);
+}
+
+void hda_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;
+
+ mach = snd_soc_acpi_find_machine(desc->machines);
+ if (mach) {
+ /*
+ * 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;
+
+ sof_pdata->machine = mach;
+
+ if (mach->link_mask) {
+ mach->mach_params.links = mach->links;
+ mach->mach_params.link_mask = mach->link_mask;
+ }
+ }
+
+ /*
+ * If I2S fails, try SoundWire
+ */
+ 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)
+ dev_warn(sdev->dev, "warning: No matching ASoC machine driver found\n");
+}
+
MODULE_LICENSE("Dual BSD/GPL");
+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(SOUNDWIRE_INTEL_INIT);
diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h
index 4be53ef..1bc4dab 100644
--- a/sound/soc/sof/intel/hda.h
+++ b/sound/soc/sof/intel/hda.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/* 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.
@@ -11,6 +11,9 @@
#ifndef __SOF_INTEL_HDA_H
#define __SOF_INTEL_HDA_H
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_intel.h>
+#include <sound/compress_driver.h>
#include <sound/hda_codec.h>
#include <sound/hdaudio_ext.h>
#include "shim.h"
@@ -43,11 +46,14 @@
/* SOF_HDA_GCTL register bist */
#define SOF_HDA_GCTL_RESET BIT(0)
-/* SOF_HDA_INCTL and SOF_HDA_INTSTS regs */
+/* SOF_HDA_INCTL regs */
#define SOF_HDA_INT_GLOBAL_EN BIT(31)
#define SOF_HDA_INT_CTRL_EN BIT(30)
#define SOF_HDA_INT_ALL_STREAM 0xff
+/* SOF_HDA_INTSTS regs */
+#define SOF_HDA_INTSTS_GIS BIT(31)
+
#define SOF_HDA_MAX_CAPS 10
#define SOF_HDA_CAP_ID_OFF 16
#define SOF_HDA_CAP_ID_MASK GENMASK(SOF_HDA_CAP_ID_OFF + 11,\
@@ -64,6 +70,13 @@
#define SOF_HDA_PPCTL_PIE BIT(31)
#define SOF_HDA_PPCTL_GPROCEN BIT(30)
+/*Vendor Specific Registers*/
+#define SOF_HDA_VS_D0I3C 0x104A
+
+/* D0I3C Register fields */
+#define SOF_HDA_VS_D0I3C_CIP BIT(0) /* Command-In-Progress */
+#define SOF_HDA_VS_D0I3C_I3 BIT(2) /* D0i3 enable bit */
+
/* DPIB entry size: 8 Bytes = 2 DWords */
#define SOF_HDA_DPIB_ENTRY_SIZE 0x8
@@ -164,7 +177,6 @@
* value cannot be read back within the specified time.
*/
#define HDA_DSP_STREAM_RUN_TIMEOUT 300
-#define HDA_DSP_CL_TRIGGER_TIMEOUT 300
#define HDA_DSP_SPIB_ENABLE 1
#define HDA_DSP_SPIB_DISABLE 0
@@ -207,6 +219,7 @@
#define HDA_DSP_CTRL_RESET_TIMEOUT 100
#define HDA_DSP_WAIT_TIMEOUT 500 /* 500 msec */
#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
@@ -219,6 +232,9 @@
#define HDA_DSP_REG_ADSPIC2 (HDA_DSP_GEN_BASE + 0x10)
#define HDA_DSP_REG_ADSPIS2 (HDA_DSP_GEN_BASE + 0x14)
+#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
#define HDA_DSP_REG_HIPCT (HDA_DSP_IPC_BASE + 0x00)
@@ -230,6 +246,7 @@
/* Intel Vendor Specific Registers */
#define HDA_VS_INTEL_EM2 0x1030
#define HDA_VS_INTEL_EM2_L1SEN BIT(13)
+#define HDA_VS_INTEL_LTRP_GB_MASK 0x3F
/* HIPCI */
#define HDA_DSP_REG_HIPCI_BUSY BIT(31)
@@ -257,6 +274,7 @@
#define BXT_D0I3_DELAY 5000
#define FW_CL_STREAM_NUMBER 0x1
+#define HDA_FW_BOOT_ATTEMPTS 3
/* ADSPCS - Audio DSP Control & Status */
@@ -288,9 +306,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
@@ -304,6 +319,7 @@
#define CNL_DSP_REG_HIPCTDD (CNL_DSP_IPC_BASE + 0x08)
#define CNL_DSP_REG_HIPCIDR (CNL_DSP_IPC_BASE + 0x10)
#define CNL_DSP_REG_HIPCIDA (CNL_DSP_IPC_BASE + 0x14)
+#define CNL_DSP_REG_HIPCIDD (CNL_DSP_IPC_BASE + 0x18)
#define CNL_DSP_REG_HIPCCTL (CNL_DSP_IPC_BASE + 0x28)
/* HIPCI */
@@ -336,7 +352,13 @@
/* Number of DAIs */
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
+
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES)
+#define SOF_SKL_NUM_DAIS 16
+#else
#define SOF_SKL_NUM_DAIS 15
+#endif
+
#else
#define SOF_SKL_NUM_DAIS 8
#endif
@@ -380,8 +402,22 @@
#define SOF_HDA_PLAYBACK 0
#define SOF_HDA_CAPTURE 1
+/*
+ * Time in ms for opportunistic D0I3 entry delay.
+ * This has been deliberately chosen to be long to avoid race conditions.
+ * Could be optimized in future.
+ */
+#define SOF_HDA_D0I3_WORK_DELAY_MS 5000
+
+/* HDA DSP D0 substate */
+enum sof_hda_D0_substate {
+ SOF_HDA_DSP_PM_D0I0, /* default D0 substate */
+ SOF_HDA_DSP_PM_D0I3, /* low power D0 substate */
+};
+
/* represents DSP HDA controller frontend - i.e. host facing control */
struct sof_intel_hda_dev {
+ int boot_iteration;
struct hda_bus hbus;
@@ -397,10 +433,20 @@
/* the maximum number of streams (playback + capture) supported */
u32 stream_max;
- int irq;
+ /* PM related */
+ bool l1_support_changed;/* during suspend, is L1SEN changed or not */
/* DMIC device */
struct platform_device *dmic_dev;
+
+ /* delayed work to enter D0I3 opportunistically */
+ struct delayed_work d0i3_work;
+
+ /* ACPI information stored between scan and probe steps */
+ struct sdw_intel_acpi_info info;
+
+ /* sdw context allocated by SoundWire driver */
+ struct sdw_intel_ctx *sdw;
};
static inline struct hdac_bus *sof_to_bus(struct snd_sof_dev *s)
@@ -455,7 +501,10 @@
void hda_dsp_ipc_int_enable(struct snd_sof_dev *sdev);
void hda_dsp_ipc_int_disable(struct snd_sof_dev *sdev);
-int hda_dsp_suspend(struct snd_sof_dev *sdev);
+int hda_dsp_set_power_state(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);
@@ -465,10 +514,13 @@
void hda_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);
/*
* DSP PCM Operations.
*/
+u32 hda_dsp_get_mult_div(struct snd_sof_dev *sdev, int rate);
+u32 hda_dsp_get_bits(struct snd_sof_dev *sdev, int sample_bits);
int hda_dsp_pcm_open(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream);
int hda_dsp_pcm_close(struct snd_sof_dev *sdev,
@@ -494,13 +546,17 @@
struct hdac_ext_stream *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 *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);
-irqreturn_t hda_dsp_stream_interrupt(int irq, void *context);
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);
+bool hda_dsp_check_ipc_irq(struct snd_sof_dev *sdev);
+bool hda_dsp_check_stream_irq(struct snd_sof_dev *sdev);
struct hdac_ext_stream *
hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction);
@@ -516,6 +572,29 @@
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
+
/*
* DSP IPC Operations.
*/
@@ -525,7 +604,6 @@
int hda_dsp_ipc_get_mailbox_offset(struct snd_sof_dev *sdev);
int hda_dsp_ipc_get_window_offset(struct snd_sof_dev *sdev, u32 id);
-irqreturn_t hda_dsp_ipc_irq_handler(int irq, void *context);
irqreturn_t hda_dsp_ipc_irq_thread(int irq, void *context);
int hda_dsp_ipc_cmd_done(struct snd_sof_dev *sdev, int dir);
@@ -533,6 +611,7 @@
* DSP Code loader.
*/
int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev);
+int hda_dsp_cl_boot_firmware_iccmax(struct snd_sof_dev *sdev);
int hda_dsp_cl_boot_firmware_skl(struct snd_sof_dev *sdev);
/* pre and post fw run ops */
@@ -559,27 +638,29 @@
/*
* HDA Codec operations.
*/
-int hda_codec_probe_bus(struct snd_sof_dev *sdev);
+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_jack_check(struct snd_sof_dev *sdev);
#endif /* CONFIG_SND_SOC_SOF_HDA */
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) && IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI)
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) && \
+ (IS_ENABLED(CONFIG_SND_HDA_CODEC_HDMI) || \
+ IS_ENABLED(CONFIG_SND_SOC_HDAC_HDMI))
-void hda_codec_i915_get(struct snd_sof_dev *sdev);
-void hda_codec_i915_put(struct snd_sof_dev *sdev);
+void hda_codec_i915_display_power(struct snd_sof_dev *sdev, bool enable);
int hda_codec_i915_init(struct snd_sof_dev *sdev);
int hda_codec_i915_exit(struct snd_sof_dev *sdev);
#else
-static inline void hda_codec_i915_get(struct snd_sof_dev *sdev) { }
-static inline void hda_codec_i915_put(struct snd_sof_dev *sdev) { }
+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; }
-#endif /* CONFIG_SND_SOC_SOF_HDA && CONFIG_SND_SOC_HDAC_HDMI */
+#endif
/*
* Trace Control.
@@ -588,6 +669,61 @@
int hda_dsp_trace_release(struct snd_sof_dev *sdev);
int hda_dsp_trace_trigger(struct snd_sof_dev *sdev, int cmd);
+/*
+ * SoundWire support
+ */
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE)
+
+int hda_sdw_startup(struct snd_sof_dev *sdev);
+void hda_sdw_int_enable(struct snd_sof_dev *sdev, bool enable);
+void hda_sdw_process_wakeen(struct snd_sof_dev *sdev);
+
+#else
+
+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_startup(struct snd_sof_dev *sdev)
+{
+ return 0;
+}
+
+static inline int hda_sdw_exit(struct snd_sof_dev *sdev)
+{
+ 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)
+{
+ 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;
+}
+
+static inline void hda_sdw_process_wakeen(struct snd_sof_dev *sdev)
+{
+}
+#endif
+
/* common dai driver */
extern struct snd_soc_dai_driver skl_dai[];
@@ -596,13 +732,20 @@
*/
extern const struct snd_sof_dsp_ops sof_apl_ops;
extern const struct snd_sof_dsp_ops sof_cnl_ops;
-extern const struct snd_sof_dsp_ops sof_skl_ops;
+extern const struct snd_sof_dsp_ops sof_tgl_ops;
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;
+
+/* 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);
#endif
diff --git a/sound/soc/sof/intel/intel-ipc.c b/sound/soc/sof/intel/intel-ipc.c
index 4edd921..310f916 100644
--- a/sound/soc/sof/intel/intel-ipc.c
+++ b/sound/soc/sof/intel/intel-ipc.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -39,7 +39,7 @@
sof_mailbox_read(sdev, stream->posn_offset, p, sz);
}
}
-EXPORT_SYMBOL(intel_ipc_msg_data);
+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,
@@ -60,7 +60,7 @@
return 0;
}
-EXPORT_SYMBOL(intel_ipc_pcm_params);
+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)
@@ -75,7 +75,7 @@
return 0;
}
-EXPORT_SYMBOL(intel_pcm_open);
+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)
@@ -87,6 +87,6 @@
return 0;
}
-EXPORT_SYMBOL(intel_pcm_close);
+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/shim.h b/sound/soc/sof/intel/shim.h
index f7a3f62..1e0afb5 100644
--- a/sound/soc/sof/intel/shim.h
+++ b/sound/soc/sof/intel/shim.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/* 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.
@@ -12,7 +12,7 @@
#define __SOF_INTEL_SHIM_H
/*
- * SHIM registers for BYT, BSW, CHT, HSW, BDW
+ * SHIM registers for BYT, BSW, CHT, BDW
*/
#define SHIM_CSR (SHIM_OFFSET + 0x00)
@@ -38,7 +38,7 @@
#define SHIM_PWMCTRL 0x1000
/*
- * SST SHIM register bits for BYT, BSW, CHT HSW, BDW
+ * SST SHIM register bits for BYT, BSW, CHT, BDW
* Register bit naming and functionaility can differ between devices.
*/
@@ -154,7 +154,7 @@
/* 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;
@@ -169,13 +169,11 @@
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_hsw_ops;
extern const struct snd_sof_dsp_ops sof_bdw_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 hsw_chip_info;
extern const struct sof_intel_dsp_desc tng_chip_info;
struct sof_intel_stream {
diff --git a/sound/soc/sof/intel/tgl.c b/sound/soc/sof/intel/tgl.c
new file mode 100644
index 0000000..0278b67
--- /dev/null
+++ b/sound/soc/sof/intel/tgl.c
@@ -0,0 +1,153 @@
+// 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 "../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},
+};
+
+/* Tigerlake ops */
+const struct snd_sof_dsp_ops sof_tgl_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 = cnl_ipc_irq_thread,
+
+ /* 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,
+
+ .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,
+
+ /* debug */
+ .debug_map = tgl_dsp_debugfs,
+ .debug_map_count = ARRAY_SIZE(tgl_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,
+
+ /* 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,
+
+ /* firmware run */
+ .run = hda_dsp_cl_boot_firmware_iccmax,
+
+ /* 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,
+};
+EXPORT_SYMBOL_NS(sof_tgl_ops, SND_SOC_SOF_INTEL_HDA_COMMON);
+
+const struct sof_intel_dsp_desc tgl_chip_info = {
+ /* Tigerlake */
+ .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_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 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_init_timeout = 300,
+ .ssp_count = ICL_SSP_COUNT,
+ .ssp_base_offset = CNL_SSP_BASE_OFFSET,
+};
+EXPORT_SYMBOL_NS(tglh_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON);
diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c
index f38f651..fd2b96a 100644
--- a/sound/soc/sof/ipc.c
+++ b/sound/soc/sof/ipc.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -15,6 +15,7 @@
#include <linux/types.h>
#include "sof-priv.h"
+#include "sof-audio.h"
#include "ops.h"
static void ipc_trace_message(struct snd_sof_dev *sdev, u32 msg_id);
@@ -53,6 +54,7 @@
u8 *str2 = NULL;
u32 glb;
u32 type;
+ bool vdbg = false;
glb = cmd & SOF_GLB_TYPE_MASK;
type = cmd & SOF_CMD_TYPE_MASK;
@@ -145,6 +147,7 @@
case SOF_IPC_STREAM_TRIG_XRUN:
str2 = "TRIG_XRUN"; break;
case SOF_IPC_STREAM_POSITION:
+ vdbg = true;
str2 = "POSITION"; break;
case SOF_IPC_STREAM_VORBIS_PARAMS:
str2 = "VORBIS_PARAMS"; break;
@@ -182,10 +185,14 @@
str = "unknown GLB command"; break;
}
- if (str2)
- dev_dbg(dev, "%s: 0x%x: %s: %s\n", text, cmd, str, str2);
- else
+ if (str2) {
+ if (vdbg)
+ dev_vdbg(dev, "%s: 0x%x: %s: %s\n", text, cmd, str, str2);
+ 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 ipc_log_header(struct device *dev, u8 *text, u32 cmd)
@@ -210,9 +217,7 @@
if (ret == 0) {
dev_err(sdev->dev, "error: ipc timed out for 0x%x size %d\n",
hdr->cmd, hdr->size);
- snd_sof_dsp_dbg_dump(ipc->sdev, SOF_DBG_REGS | SOF_DBG_MBOX);
- snd_sof_ipc_dump(ipc->sdev);
- snd_sof_trace_notify_for_error(ipc->sdev);
+ snd_sof_handle_fw_exception(ipc->sdev);
ret = -ETIMEDOUT;
} else {
ret = msg->reply_error;
@@ -271,7 +276,6 @@
spin_unlock_irq(&sdev->ipc_lock);
if (ret < 0) {
- /* So far IPC TX never fails, consider making the above void */
dev_err_ratelimited(sdev->dev,
"error: ipc tx failed with error %d\n",
ret);
@@ -292,6 +296,32 @@
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;
+ }
+
+ return sof_ipc_tx_message_no_pm(ipc, header, msg_data, msg_bytes,
+ reply_data, reply_bytes);
+}
+EXPORT_SYMBOL(sof_ipc_tx_message);
+
+/*
+ * 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,
+ void *reply_data, size_t reply_bytes)
+{
int ret;
if (msg_bytes > SOF_IPC_MSG_MAX_SIZE ||
@@ -308,24 +338,23 @@
return ret;
}
-EXPORT_SYMBOL(sof_ipc_tx_message);
+EXPORT_SYMBOL(sof_ipc_tx_message_no_pm);
/* handle reply message from DSP */
-int snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id)
+void snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id)
{
struct snd_sof_ipc_msg *msg = &sdev->ipc->msg;
if (msg->ipc_complete) {
- dev_err(sdev->dev, "error: no reply expected, received 0x%x",
+ dev_dbg(sdev->dev,
+ "no reply expected, received 0x%x, will be ignored",
msg_id);
- return -EINVAL;
+ return;
}
/* wake up and return the error if we have waiters on this message ? */
msg->ipc_complete = true;
wake_up(&msg->waitq);
-
- return 0;
}
EXPORT_SYMBOL(snd_sof_ipc_reply);
@@ -409,12 +438,13 @@
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(sdev, msg_id, &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",
@@ -425,8 +455,8 @@
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);
+ dev_vdbg(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));
@@ -438,12 +468,13 @@
/* 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(sdev, msg_id, &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);
@@ -485,10 +516,11 @@
}
/* get stream position IPC - use faster MMIO method if available on platform */
-int snd_sof_ipc_stream_posn(struct snd_sof_dev *sdev,
+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;
@@ -617,15 +649,15 @@
/*
* IPC get()/set() for kcontrols.
*/
-int snd_sof_ipc_set_get_comp_data(struct snd_sof_ipc *ipc,
- struct snd_sof_control *scontrol,
+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 = ipc->sdev;
+ 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;
@@ -791,12 +823,6 @@
struct snd_sof_ipc *ipc;
struct snd_sof_ipc_msg *msg;
- /* check if mandatory ops required for ipc are defined */
- if (!sof_ops(sdev)->fw_ready) {
- dev_err(sdev->dev, "error: ipc mandatory ops not defined\n");
- return NULL;
- }
-
ipc = devm_kzalloc(sdev->dev, sizeof(*ipc), GFP_KERNEL);
if (!ipc)
return NULL;
diff --git a/sound/soc/sof/loader.c b/sound/soc/sof/loader.c
index ce114df..2d5c3fc 100644
--- a/sound/soc/sof/loader.c
+++ b/sound/soc/sof/loader.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -12,26 +12,79 @@
#include <linux/firmware.h>
#include <sound/sof.h>
+#include <sound/sof/ext_manifest.h>
#include "ops.h"
static int get_ext_windows(struct snd_sof_dev *sdev,
- struct sof_ipc_ext_data_hdr *ext_hdr)
+ const struct sof_ipc_ext_data_hdr *ext_hdr)
{
- struct sof_ipc_window *w =
+ 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, "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, struct_size(w, window, w->num_windows),
- GFP_KERNEL);
+ sdev->info_window = devm_kmemdup(sdev->dev, w, ext_hdr->hdr.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)
{
@@ -50,8 +103,7 @@
while (ext_hdr->hdr.cmd == SOF_IPC_FW_READY) {
/* read in ext structure */
- offset += sizeof(*ext_hdr);
- snd_sof_dsp_block_read(sdev, bar, offset,
+ 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));
@@ -60,14 +112,21 @@
/* process structure data */
switch (ext_hdr->type) {
- case SOF_IPC_EXT_DMA_BUFFER:
- break;
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;
+ 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_warn(sdev->dev, "warning: unknown ext header type %d size 0x%x\n",
ext_hdr->type, ext_hdr->hdr.size);
+ ret = 0;
break;
}
@@ -89,6 +148,161 @@
}
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 int 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 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;
+ case SOF_EXT_MAN_ELEM_DBG_ABI:
+ ret = ext_man_get_dbg_abi_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.
*/
@@ -101,6 +315,8 @@
u32 outbox_size = 0;
u32 stream_size = 0;
u32 inbox_size = 0;
+ u32 debug_size = 0;
+ u32 debug_offset = 0;
int window_offset;
int bar;
int i;
@@ -154,6 +370,8 @@
SOF_DEBUGFS_ACCESS_D0_ONLY);
break;
case SOF_IPC_REGION_DEBUG:
+ debug_offset = window_offset + elem->offset;
+ debug_size = elem->size;
snd_sof_debugfs_io_item(sdev,
sdev->bar[bar] +
window_offset +
@@ -203,12 +421,17 @@
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);
}
/* check for ABI compatibility and create memory windows on first boot */
@@ -342,12 +565,19 @@
}
EXPORT_SYMBOL(snd_sof_parse_module_memcpy);
-static int check_header(struct snd_sof_dev *sdev, const struct firmware *fw)
+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;
+ 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) {
@@ -356,9 +586,9 @@
}
/* check size is valid */
- if (fw->size != header->file_size + sizeof(*header)) {
+ 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));
+ fw_size, header->file_size + sizeof(*header));
return -EINVAL;
}
@@ -369,7 +599,8 @@
return 0;
}
-static int load_modules(struct snd_sof_dev *sdev, const struct firmware *fw)
+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;
@@ -378,14 +609,15 @@
int ret, count;
size_t remaining;
- header = (struct snd_sof_fw_header *)fw->data;
+ 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 *)((u8 *)(fw->data) + sizeof(*header));
- remaining = fw->size - sizeof(*header);
+ 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");
@@ -427,11 +659,9 @@
{
struct snd_sof_pdata *plat_data = sdev->pdata;
const char *fw_filename;
+ ssize_t ext_man_size;
int ret;
- /* set code loading condition to true */
- sdev->code_loading = 1;
-
/* Don't request firmware again if firmware is already requested */
if (plat_data->fw)
return 0;
@@ -447,8 +677,27 @@
if (ret < 0) {
dev_err(sdev->dev, "error: request firmware %s failed err: %d\n",
fw_filename, ret);
+ goto err;
+ } else {
+ dev_dbg(sdev->dev, "request_firmware %s successful\n",
+ fw_filename);
}
+ /* check for extended manifest */
+ ext_man_size = snd_sof_fw_ext_man_parse(sdev, plat_data->fw);
+ if (ext_man_size > 0) {
+ /* when no error occurred, drop extended manifest */
+ plat_data->fw_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");
+ } else {
+ ret = ext_man_size;
+ dev_err(sdev->dev, "error: firmware %s contains unsupported or invalid extended manifest: %d\n",
+ fw_filename, ret);
+ }
+
+err:
kfree(fw_filename);
return ret;
@@ -465,7 +714,7 @@
return ret;
/* make sure the FW header and file is valid */
- ret = check_header(sdev, plat_data->fw);
+ ret = check_header(sdev, plat_data->fw, plat_data->fw_offset);
if (ret < 0) {
dev_err(sdev->dev, "error: invalid FW header\n");
goto error;
@@ -479,7 +728,7 @@
}
/* parse and load firmware modules to DSP */
- ret = load_modules(sdev, plat_data->fw);
+ 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;
@@ -560,7 +809,7 @@
}
if (sdev->fw_state == SOF_FW_BOOT_COMPLETE)
- dev_info(sdev->dev, "firmware boot complete\n");
+ dev_dbg(sdev->dev, "firmware boot complete\n");
else
return -EIO; /* FW boots but fw_ready op failed */
@@ -581,5 +830,7 @@
void snd_sof_fw_unload(struct snd_sof_dev *sdev)
{
/* TODO: support module unloading at runtime */
+ release_firmware(sdev->pdata->fw);
+ sdev->pdata->fw = NULL;
}
EXPORT_SYMBOL(snd_sof_fw_unload);
diff --git a/sound/soc/sof/nocodec.c b/sound/soc/sof/nocodec.c
index 7141011..9e922df 100644
--- a/sound/soc/sof/nocodec.c
+++ b/sound/soc/sof/nocodec.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -66,22 +66,9 @@
}
int sof_nocodec_setup(struct device *dev,
- struct snd_sof_pdata *sof_pdata,
- struct snd_soc_acpi_mach *mach,
- const struct sof_dev_desc *desc,
const struct snd_sof_dsp_ops *ops)
{
struct snd_soc_dai_link *links;
- int ret;
-
- if (!mach)
- return -EINVAL;
-
- sof_pdata->drv_name = "sof-nocodec";
-
- mach->drv_name = "sof-nocodec";
- sof_pdata->fw_filename = desc->nocodec_fw_filename;
- sof_pdata->tplg_filename = desc->nocodec_tplg_filename;
/* create dummy BE dai_links */
links = devm_kzalloc(dev, sizeof(struct snd_soc_dai_link) *
@@ -89,9 +76,8 @@
if (!links)
return -ENOMEM;
- ret = sof_nocodec_bes_setup(dev, ops, links, ops->num_drv,
- &sof_nocodec_card);
- return ret;
+ return sof_nocodec_bes_setup(dev, ops, links, ops->num_drv,
+ &sof_nocodec_card);
}
EXPORT_SYMBOL(sof_nocodec_setup);
diff --git a/sound/soc/sof/ops.c b/sound/soc/sof/ops.c
index 7a27c3b..1a394b4 100644
--- a/sound/soc/sof/ops.c
+++ b/sound/soc/sof/ops.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h
index 824d36f..b21632f 100644
--- a/sound/soc/sof/ops.h
+++ b/sound/soc/sof/ops.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/* 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.
@@ -146,10 +146,11 @@
return 0;
}
-static inline int snd_sof_dsp_suspend(struct snd_sof_dev *sdev)
+static inline int snd_sof_dsp_suspend(struct snd_sof_dev *sdev,
+ u32 target_state)
{
if (sof_ops(sdev)->suspend)
- return sof_ops(sdev)->suspend(sdev);
+ return sof_ops(sdev)->suspend(sdev, target_state);
return 0;
}
@@ -193,6 +194,17 @@
return 0;
}
+static inline int
+snd_sof_dsp_set_power_state(struct snd_sof_dev *sdev,
+ const struct sof_dsp_power_state *target_state)
+{
+ if (sof_ops(sdev)->set_power_state)
+ return sof_ops(sdev)->set_power_state(sdev, target_state);
+
+ /* D0 substate is not supported, do nothing here. */
+ return 0;
+}
+
/* debug */
static inline void snd_sof_dsp_dbg_dump(struct snd_sof_dev *sdev, u32 flags)
{
@@ -381,6 +393,83 @@
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)
+{
+ return sof_ops(sdev)->probe_set_params(sdev, cstream, params, dai);
+}
+
+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);
+}
+
+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)
+{
+ if (sof_ops(sdev) && sof_ops(sdev)->probe_pointer)
+ return sof_ops(sdev)->probe_pointer(sdev, cstream, tstamp, dai);
+
+ return 0;
+}
+#endif
+
+/* machine driver */
+static inline int
+snd_sof_machine_register(struct snd_sof_dev *sdev, void *pdata)
+{
+ if (sof_ops(sdev) && sof_ops(sdev)->machine_register)
+ return sof_ops(sdev)->machine_register(sdev, pdata);
+
+ return 0;
+}
+
+static inline void
+snd_sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata)
+{
+ if (sof_ops(sdev) && sof_ops(sdev)->machine_unregister)
+ sof_ops(sdev)->machine_unregister(sdev, pdata);
+}
+
+static inline void
+snd_sof_machine_select(struct snd_sof_dev *sdev)
+{
+ if (sof_ops(sdev) && sof_ops(sdev)->machine_select)
+ sof_ops(sdev)->machine_select(sdev);
+}
+
+static inline void
+snd_sof_set_mach_params(const struct snd_soc_acpi_mach *mach,
+ struct device *dev)
+{
+ 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);
+}
+
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)
diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c
index 2b876d4..cbac6f1 100644
--- a/sound/soc/sof/pcm.c
+++ b/sound/soc/sof/pcm.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -14,39 +14,41 @@
#include <sound/pcm_params.h>
#include <sound/sof.h>
#include "sof-priv.h"
+#include "sof-audio.h"
#include "ops.h"
-
-#define DRV_NAME "sof-audio-component"
+#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_pcm_substream *substream,
+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 = substream->private_data;
- struct snd_soc_component *component =
- snd_soc_rtdcom_lookup(rtd, DRV_NAME);
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_sof_pcm *spcm;
struct snd_dma_buffer *dmab = snd_pcm_get_dma_buf(substream);
int stream = substream->stream;
- spcm = snd_sof_find_spcm_dai(sdev, rtd);
+ spcm = snd_sof_find_spcm_dai(component, rtd);
if (!spcm)
return -EINVAL;
- return snd_sof_create_page_table(sdev, dmab,
+ return snd_sof_create_page_table(component->dev, dmab,
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_sof_dev *sdev = spcm->sdev;
+ 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(sdev->dev, "error: got wrong reply for PCM %d\n",
+ dev_err(scomp->dev, "error: got wrong reply for PCM %d\n",
spcm->pcm.pcm_id);
return ret;
@@ -55,7 +57,7 @@
/*
* sof pcm period elapse work
*/
-static void sof_pcm_period_elapsed_work(struct work_struct *work)
+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,
@@ -69,15 +71,14 @@
*/
void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream)
{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_soc_component *component =
- snd_soc_rtdcom_lookup(rtd, DRV_NAME);
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+ snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME);
struct snd_sof_pcm *spcm;
- spcm = snd_sof_find_spcm_dai(sdev, rtd);
+ spcm = snd_sof_find_spcm_dai(component, rtd);
if (!spcm) {
- dev_err(sdev->dev,
+ dev_err(component->dev,
"error: period elapsed for unknown stream!\n");
return;
}
@@ -94,14 +95,33 @@
}
EXPORT_SYMBOL(snd_sof_pcm_period_elapsed);
-/* this may get called several times by oss emulation */
-static int sof_pcm_hw_params(struct snd_pcm_substream *substream,
+static int sof_pcm_dsp_pcm_free(struct snd_pcm_substream *substream,
+ struct snd_sof_dev *sdev,
+ struct snd_sof_pcm *spcm)
+{
+ struct sof_ipc_stream stream;
+ struct sof_ipc_reply reply;
+ int ret;
+
+ 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 */
+ ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream,
+ sizeof(stream), &reply, sizeof(reply));
+ if (!ret)
+ spcm->prepared[substream->stream] = false;
+
+ return ret;
+}
+
+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 = substream->private_data;
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
- struct snd_soc_component *component =
- snd_soc_rtdcom_lookup(rtd, DRV_NAME);
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
struct snd_sof_pcm *spcm;
struct sof_ipc_pcm_params pcm;
@@ -112,30 +132,28 @@
if (rtd->dai_link->no_pcm)
return 0;
- spcm = snd_sof_find_spcm_dai(sdev, rtd);
+ spcm = snd_sof_find_spcm_dai(component, rtd);
if (!spcm)
return -EINVAL;
- dev_dbg(sdev->dev, "pcm: hw params stream %d dir %d\n",
+ /*
+ * 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 (ret < 0)
+ return ret;
+ }
+
+ dev_dbg(component->dev, "pcm: hw params stream %d dir %d\n",
spcm->pcm.pcm_id, substream->stream);
memset(&pcm, 0, sizeof(pcm));
- /* allocate audio buffer pages */
- ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
- if (ret < 0) {
- dev_err(sdev->dev, "error: could not allocate %d bytes for PCM %d\n",
- params_buffer_bytes(params), spcm->pcm.pcm_id);
- return ret;
- }
- if (ret) {
- /*
- * ret == 1 means the buffer is changed
- * create compressed page table for audio firmware
- * ret == 0 means the buffer is not changed
- * so no need to regenerate the page table
- */
- ret = create_page_table(substream, runtime->dma_area,
+ /* 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;
@@ -189,17 +207,17 @@
params,
&pcm.params);
if (ret < 0) {
- dev_err(sdev->dev, "error: platform hw params failed\n");
+ dev_err(component->dev, "error: platform hw params failed\n");
return ret;
}
- dev_dbg(sdev->dev, "stream_tag %d", pcm.params.stream_tag);
+ 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(sdev->dev, "error: hw params ipc failed for stream %d\n",
+ dev_err(component->dev, "error: hw params ipc failed for stream %d\n",
pcm.params.stream_tag);
return ret;
}
@@ -216,32 +234,10 @@
return ret;
}
-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_hw_free(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
{
- struct sof_ipc_stream stream;
- struct sof_ipc_reply reply;
- int ret;
-
- 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 */
- ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream,
- sizeof(stream), &reply, sizeof(reply));
- if (!ret)
- spcm->prepared[substream->stream] = false;
-
- return ret;
-}
-
-static int sof_pcm_hw_free(struct snd_pcm_substream *substream)
-{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_component *component =
- snd_soc_rtdcom_lookup(rtd, DRV_NAME);
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
struct snd_sof_pcm *spcm;
int ret, err = 0;
@@ -250,12 +246,12 @@
if (rtd->dai_link->no_pcm)
return 0;
- spcm = snd_sof_find_spcm_dai(sdev, rtd);
+ spcm = snd_sof_find_spcm_dai(component, rtd);
if (!spcm)
return -EINVAL;
- dev_dbg(sdev->dev, "pcm: free stream %d dir %d\n", spcm->pcm.pcm_id,
- substream->stream);
+ dev_dbg(component->dev, "pcm: free stream %d dir %d\n",
+ spcm->pcm.pcm_id, substream->stream);
if (spcm->prepared[substream->stream]) {
ret = sof_pcm_dsp_pcm_free(substream, sdev, spcm);
@@ -263,25 +259,21 @@
err = ret;
}
- snd_pcm_lib_free_pages(substream);
-
cancel_work_sync(&spcm->stream[substream->stream].period_elapsed_work);
ret = snd_sof_pcm_platform_hw_free(sdev, substream);
if (ret < 0) {
- dev_err(sdev->dev, "error: platform hw free failed\n");
+ dev_err(component->dev, "error: platform hw free failed\n");
err = ret;
}
return err;
}
-static int sof_pcm_prepare(struct snd_pcm_substream *substream)
+static int sof_pcm_prepare(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_component *component =
- snd_soc_rtdcom_lookup(rtd, DRV_NAME);
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_sof_pcm *spcm;
int ret;
@@ -289,20 +281,22 @@
if (rtd->dai_link->no_pcm)
return 0;
- spcm = snd_sof_find_spcm_dai(sdev, rtd);
+ spcm = snd_sof_find_spcm_dai(component, rtd);
if (!spcm)
return -EINVAL;
if (spcm->prepared[substream->stream])
return 0;
- dev_dbg(sdev->dev, "pcm: prepare stream %d dir %d\n", spcm->pcm.pcm_id,
- substream->stream);
+ dev_dbg(component->dev, "pcm: prepare stream %d dir %d\n",
+ spcm->pcm.pcm_id, substream->stream);
/* set hw_params */
- ret = sof_pcm_hw_params(substream, &spcm->params[substream->stream]);
+ ret = sof_pcm_hw_params(component,
+ substream, &spcm->params[substream->stream]);
if (ret < 0) {
- dev_err(sdev->dev, "error: set pcm hw_params after resume\n");
+ dev_err(component->dev,
+ "error: set pcm hw_params after resume\n");
return ret;
}
@@ -313,11 +307,10 @@
* FE dai link trigger actions are always executed in non-atomic context because
* they involve IPC's.
*/
-static int sof_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+static int sof_pcm_trigger(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream, int cmd)
{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_component *component =
- snd_soc_rtdcom_lookup(rtd, DRV_NAME);
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
struct snd_sof_pcm *spcm;
struct sof_ipc_stream stream;
@@ -330,11 +323,11 @@
if (rtd->dai_link->no_pcm)
return 0;
- spcm = snd_sof_find_spcm_dai(sdev, rtd);
+ spcm = snd_sof_find_spcm_dai(component, rtd);
if (!spcm)
return -EINVAL;
- dev_dbg(sdev->dev, "pcm: trigger stream %d dir %d cmd %d\n",
+ 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);
@@ -350,26 +343,58 @@
stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_RELEASE;
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(substream);
+ ret = sof_pcm_prepare(component, substream);
if (ret < 0) {
- dev_err(sdev->dev,
+ dev_err(component->dev,
"error: failed to set up hw_params upon resume\n");
return ret;
}
- /* fallthrough */
+ fallthrough;
case SNDRV_PCM_TRIGGER_START:
+ if (spcm->stream[substream->stream].suspend_ignored) {
+ /*
+ * This case will be triggered when INFO_RESUME is
+ * not supported, no need to re-start streams that
+ * remained enabled in D0ix.
+ */
+ spcm->stream[substream->stream].suspend_ignored = false;
+ return 0;
+ }
stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_START;
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
+ if (sdev->system_suspend_target == SOF_SUSPEND_S0IX &&
+ spcm->stream[substream->stream].d0i3_compatible) {
+ /*
+ * trap the event, not sending trigger stop to
+ * prevent the FW pipelines from being stopped,
+ * and mark the flag to ignore the upcoming DAPM
+ * PM events.
+ */
+ spcm->stream[substream->stream].suspend_ignored = true;
+ return 0;
+ }
+ fallthrough;
case SNDRV_PCM_TRIGGER_STOP:
stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_STOP;
ipc_first = true;
reset_hw_params = true;
break;
default:
- dev_err(sdev->dev, "error: unhandled trigger cmd %d\n", cmd);
+ dev_err(component->dev, "error: unhandled trigger cmd %d\n",
+ cmd);
return -EINVAL;
}
@@ -395,11 +420,10 @@
return ret;
}
-static snd_pcm_uframes_t sof_pcm_pointer(struct snd_pcm_substream *substream)
+static snd_pcm_uframes_t sof_pcm_pointer(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_component *component =
- snd_soc_rtdcom_lookup(rtd, DRV_NAME);
+ struct snd_soc_pcm_runtime *rtd = asoc_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;
@@ -412,7 +436,7 @@
if (sof_ops(sdev)->pcm_pointer)
return sof_ops(sdev)->pcm_pointer(sdev, substream);
- spcm = snd_sof_find_spcm_dai(sdev, rtd);
+ spcm = snd_sof_find_spcm_dai(component, rtd);
if (!spcm)
return -EINVAL;
@@ -422,19 +446,20 @@
dai = bytes_to_frames(substream->runtime,
spcm->stream[substream->stream].posn.dai_posn);
- dev_dbg(sdev->dev, "PCM: stream %d dir %d DMA position %lu DAI position %lu\n",
- spcm->pcm.pcm_id, substream->stream, host, dai);
+ dev_vdbg(component->dev,
+ "PCM: stream %d dir %d DMA position %lu DAI position %lu\n",
+ spcm->pcm.pcm_id, substream->stream, host, dai);
return host;
}
-static int sof_pcm_open(struct snd_pcm_substream *substream)
+static int sof_pcm_open(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
- struct snd_soc_component *component =
- snd_soc_rtdcom_lookup(rtd, DRV_NAME);
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+ const struct snd_sof_dsp_ops *ops = sof_ops(sdev);
struct snd_sof_pcm *spcm;
struct snd_soc_tplg_stream_caps *caps;
int ret;
@@ -443,15 +468,13 @@
if (rtd->dai_link->no_pcm)
return 0;
- spcm = snd_sof_find_spcm_dai(sdev, rtd);
+ spcm = snd_sof_find_spcm_dai(component, rtd);
if (!spcm)
return -EINVAL;
- dev_dbg(sdev->dev, "pcm: open stream %d dir %d\n", spcm->pcm.pcm_id,
- substream->stream);
+ dev_dbg(component->dev, "pcm: open stream %d dir %d\n",
+ spcm->pcm.pcm_id, substream->stream);
- INIT_WORK(&spcm->stream[substream->stream].period_elapsed_work,
- sof_pcm_period_elapsed_work);
caps = &spcm->pcm.caps[substream->stream];
@@ -464,11 +487,8 @@
le32_to_cpu(caps->period_size_min));
/* set runtime config */
- runtime->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;
+ runtime->hw.info = ops->hw_info; /* platform-specific */
+
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);
@@ -481,13 +501,13 @@
*/
runtime->hw.buffer_bytes_max = le32_to_cpu(caps->buffer_size_max);
- dev_dbg(sdev->dev, "period min %zd max %zd bytes\n",
+ dev_dbg(component->dev, "period min %zd max %zd bytes\n",
runtime->hw.period_bytes_min,
runtime->hw.period_bytes_max);
- dev_dbg(sdev->dev, "period count %d max %d\n",
+ dev_dbg(component->dev, "period count %d max %d\n",
runtime->hw.periods_min,
runtime->hw.periods_max);
- dev_dbg(sdev->dev, "buffer max %zd bytes\n",
+ dev_dbg(component->dev, "buffer max %zd bytes\n",
runtime->hw.buffer_bytes_max);
/* set wait time - TODO: come from topology */
@@ -500,16 +520,15 @@
ret = snd_sof_pcm_platform_open(sdev, substream);
if (ret < 0)
- dev_err(sdev->dev, "error: pcm open failed %d\n", ret);
+ dev_err(component->dev, "error: pcm open failed %d\n", ret);
return ret;
}
-static int sof_pcm_close(struct snd_pcm_substream *substream)
+static int sof_pcm_close(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_component *component =
- snd_soc_rtdcom_lookup(rtd, DRV_NAME);
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
struct snd_sof_pcm *spcm;
int err;
@@ -518,16 +537,16 @@
if (rtd->dai_link->no_pcm)
return 0;
- spcm = snd_sof_find_spcm_dai(sdev, rtd);
+ spcm = snd_sof_find_spcm_dai(component, rtd);
if (!spcm)
return -EINVAL;
- dev_dbg(sdev->dev, "pcm: close stream %d dir %d\n", spcm->pcm.pcm_id,
- substream->stream);
+ dev_dbg(component->dev, "pcm: close stream %d dir %d\n",
+ spcm->pcm.pcm_id, substream->stream);
err = snd_sof_pcm_platform_close(sdev, substream);
if (err < 0) {
- dev_err(sdev->dev, "error: pcm close failed %d\n",
+ dev_err(component->dev, "error: pcm close failed %d\n",
err);
/*
* keep going, no point in preventing the close
@@ -538,27 +557,14 @@
return 0;
}
-static struct snd_pcm_ops sof_pcm_ops = {
- .open = sof_pcm_open,
- .close = sof_pcm_close,
- .ioctl = snd_pcm_lib_ioctl,
- .hw_params = sof_pcm_hw_params,
- .prepare = sof_pcm_prepare,
- .hw_free = sof_pcm_hw_free,
- .trigger = sof_pcm_trigger,
- .pointer = sof_pcm_pointer,
- .page = snd_pcm_sgbuf_ops_page,
-};
-
/*
* Pre-allocate playback/capture audio buffer pages.
* no need to explicitly release memory preallocated by sof_pcm_new in pcm_free
* snd_pcm_lib_preallocate_free_for_all() is called by the core.
*/
-static int sof_pcm_new(struct snd_soc_pcm_runtime *rtd)
+static int sof_pcm_new(struct snd_soc_component *component,
+ struct snd_soc_pcm_runtime *rtd)
{
- struct snd_soc_component *component =
- snd_soc_rtdcom_lookup(rtd, DRV_NAME);
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
struct snd_sof_pcm *spcm;
struct snd_pcm *pcm = rtd->pcm;
@@ -566,14 +572,14 @@
int stream = SNDRV_PCM_STREAM_PLAYBACK;
/* find SOF PCM for this RTD */
- spcm = snd_sof_find_spcm_dai(sdev, rtd);
+ spcm = snd_sof_find_spcm_dai(component, rtd);
if (!spcm) {
- dev_warn(sdev->dev, "warn: can't find PCM with DAI ID %d\n",
+ dev_warn(component->dev, "warn: can't find PCM with DAI ID %d\n",
rtd->dai_link->id);
return 0;
}
- dev_dbg(sdev->dev, "creating new PCM %s\n", spcm->pcm.pcm_name);
+ dev_dbg(component->dev, "creating new PCM %s\n", spcm->pcm.pcm_name);
/* do we need to pre-allocate playback audio buffer pages */
if (!spcm->pcm.playback)
@@ -582,13 +588,18 @@
caps = &spcm->pcm.caps[stream];
/* pre-allocate playback audio buffer pages */
- dev_dbg(sdev->dev, "spcm: allocate %s playback DMA buffer size 0x%x max 0x%x\n",
+ dev_dbg(component->dev,
+ "spcm: allocate %s playback DMA buffer size 0x%x max 0x%x\n",
caps->name, caps->buffer_size_min, caps->buffer_size_max);
- snd_pcm_lib_preallocate_pages(pcm->streams[stream].substream,
- SNDRV_DMA_TYPE_DEV_SG, sdev->dev,
- le32_to_cpu(caps->buffer_size_min),
- le32_to_cpu(caps->buffer_size_max));
+ if (!pcm->streams[stream].substream) {
+ dev_err(component->dev, "error: NULL playback substream!\n");
+ return -EINVAL;
+ }
+
+ snd_pcm_set_managed_buffer(pcm->streams[stream].substream,
+ SNDRV_DMA_TYPE_DEV_SG, sdev->dev,
+ 0, le32_to_cpu(caps->buffer_size_max));
capture:
stream = SNDRV_PCM_STREAM_CAPTURE;
@@ -599,13 +610,18 @@
caps = &spcm->pcm.caps[stream];
/* pre-allocate capture audio buffer pages */
- dev_dbg(sdev->dev, "spcm: allocate %s capture DMA buffer size 0x%x max 0x%x\n",
+ dev_dbg(component->dev,
+ "spcm: allocate %s capture DMA buffer size 0x%x max 0x%x\n",
caps->name, caps->buffer_size_min, caps->buffer_size_max);
- snd_pcm_lib_preallocate_pages(pcm->streams[stream].substream,
- SNDRV_DMA_TYPE_DEV_SG, sdev->dev,
- le32_to_cpu(caps->buffer_size_min),
- le32_to_cpu(caps->buffer_size_max));
+ if (!pcm->streams[stream].substream) {
+ dev_err(component->dev, "error: NULL capture substream!\n");
+ return -EINVAL;
+ }
+
+ snd_pcm_set_managed_buffer(pcm->streams[stream].substream,
+ SNDRV_DMA_TYPE_DEV_SG, sdev->dev,
+ 0, le32_to_cpu(caps->buffer_size_max));
return 0;
}
@@ -620,14 +636,15 @@
SNDRV_PCM_HW_PARAM_CHANNELS);
struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
struct snd_soc_component *component =
- snd_soc_rtdcom_lookup(rtd, DRV_NAME);
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
+ snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME);
struct snd_sof_dai *dai =
- snd_sof_find_dai(sdev, (char *)rtd->dai_link->name);
+ snd_sof_find_dai(component, (char *)rtd->dai_link->name);
+ struct snd_soc_dpcm *dpcm;
/* no topology exists for this BE, try a common configuration */
if (!dai) {
- dev_warn(sdev->dev, "warning: no topology found for BE DAI %s config\n",
+ dev_warn(component->dev,
+ "warning: no topology found for BE DAI %s config\n",
rtd->dai_link->name);
/* set 48k, stereo, 16bits by default */
@@ -657,7 +674,7 @@
snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE);
break;
default:
- dev_err(sdev->dev, "error: No available DAI format!\n");
+ dev_err(component->dev, "error: No available DAI format!\n");
return -EINVAL;
}
@@ -669,9 +686,9 @@
channels->min = dai->dai_config->ssp.tdm_slots;
channels->max = dai->dai_config->ssp.tdm_slots;
- dev_dbg(sdev->dev,
+ dev_dbg(component->dev,
"rate_min: %d rate_max: %d\n", rate->min, rate->max);
- dev_dbg(sdev->dev,
+ dev_dbg(component->dev,
"channels_min: %d channels_max: %d\n",
channels->min, channels->max);
@@ -679,20 +696,53 @@
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(sdev->dev,
+ 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:
- /* do nothing for HDA dai_link */
+ /*
+ * 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(sdev->dev, "error: invalid DAI type %d\n",
+ dev_err(component->dev, "error: invalid DAI type %d\n",
dai->dai_config->type);
break;
}
@@ -717,21 +767,13 @@
if (!tplg_filename)
return -ENOMEM;
- ret = snd_sof_load_topology(sdev, tplg_filename);
+ ret = snd_sof_load_topology(component, tplg_filename);
if (ret < 0) {
- dev_err(sdev->dev, "error: failed to load DSP topology %d\n",
+ dev_err(component->dev, "error: failed to load DSP topology %d\n",
ret);
return ret;
}
- /*
- * Some platforms in SOF, ex: BYT, may not have their platform PM
- * callbacks set. Increment the usage count so as to
- * prevent the device from entering runtime suspend.
- */
- if (!sof_ops(sdev)->runtime_suspend || !sof_ops(sdev)->runtime_resume)
- pm_runtime_get_noresume(sdev->dev);
-
return ret;
}
@@ -752,11 +794,22 @@
pd->name = "sof-audio-component";
pd->probe = sof_pcm_probe;
pd->remove = sof_pcm_remove;
- pd->ops = &sof_pcm_ops;
+ pd->open = sof_pcm_open;
+ pd->close = sof_pcm_close;
+ pd->hw_params = sof_pcm_hw_params;
+ pd->prepare = sof_pcm_prepare;
+ pd->hw_free = sof_pcm_hw_free;
+ pd->trigger = sof_pcm_trigger;
+ pd->pointer = sof_pcm_pointer;
+
#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS)
- pd->compr_ops = &sof_compressed_ops;
+ pd->compress_ops = &sof_compressed_ops;
#endif
- pd->pcm_new = sof_pcm_new;
+#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;
diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c
index 128680b..c83fb62 100644
--- a/sound/soc/sof/pm.c
+++ b/sound/soc/sof/pm.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -10,194 +10,45 @@
#include "ops.h"
#include "sof-priv.h"
+#include "sof-audio.h"
-static int sof_restore_kcontrols(struct snd_sof_dev *sdev)
+/*
+ * Helper function to determine the target DSP state during
+ * system suspend. This function only cares about the device
+ * D-states. Platform-specific substates, if any, should be
+ * handled by the platform-specific parts.
+ */
+static u32 snd_sof_dsp_power_target(struct snd_sof_dev *sdev)
{
- struct snd_sof_control *scontrol;
- int ipc_cmd, ctrl_type;
- int ret = 0;
+ u32 target_dsp_state;
- /* 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(sdev->ipc, 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(sdev->ipc, scontrol,
- ipc_cmd, ctrl_type,
- scontrol->cmd,
- true);
- break;
-
- default:
- break;
- }
-
- if (ret < 0) {
- dev_err(sdev->dev,
- "error: failed kcontrol value set for widget: %d\n",
- scontrol->comp_id);
-
- return ret;
- }
- }
-
- return 0;
-}
-
-static int sof_restore_pipelines(struct snd_sof_dev *sdev)
-{
- 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;
-
- /* restore pipeline components */
- list_for_each_entry_reverse(swidget, &sdev->widget_list, list) {
- struct sof_ipc_comp_reply r;
-
- /* skip if there is no private data */
- if (!swidget->private)
- 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));
- break;
- case snd_soc_dapm_scheduler:
-
- /*
- * 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.
- */
- pipeline = swidget->private;
- ret = sof_load_pipeline_ipc(sdev, pipeline, &r);
- break;
- default:
- hdr = swidget->private;
- ret = sof_ipc_tx_message(sdev->ipc, hdr->cmd,
- swidget->private, hdr->size,
- &r, sizeof(r));
- break;
- }
- if (ret < 0) {
- dev_err(sdev->dev,
- "error: failed to load widget type %d with ID: %d\n",
- swidget->widget->id, swidget->comp_id);
-
- 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;
-
- /* skip if there's no private data */
- if (!sroute->private)
- continue;
-
- connect = sroute->private;
-
- /* send ipc */
- ret = sof_ipc_tx_message(sdev->ipc,
- connect->hdr.cmd,
- connect, sizeof(*connect),
- &reply, sizeof(reply));
- if (ret < 0) {
- dev_err(sdev->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);
-
- 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;
-
- if (!config) {
- dev_err(sdev->dev, "error: no config for DAI %s\n",
- dai->name);
- continue;
- }
-
+ switch (sdev->system_suspend_target) {
+ case SOF_SUSPEND_S3:
+ /* DSP should be in D3 if the system is suspending to S3 */
+ target_dsp_state = SOF_DSP_PM_D3;
+ break;
+ case SOF_SUSPEND_S0IX:
/*
- * 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.
+ * Currently, the only criterion for retaining the DSP in D0
+ * is that there are streams that ignored the suspend trigger.
+ * Additional criteria such Soundwire clock-stop mode and
+ * device suspend latency considerations will be added later.
*/
- if (config->type == SOF_DAI_INTEL_HDA)
- config->hda.link_dma_ch = DMA_CHAN_INVALID;
-
- ret = sof_ipc_tx_message(sdev->ipc,
- config->hdr.cmd, config,
- config->hdr.size,
- &reply, sizeof(reply));
-
- if (ret < 0) {
- dev_err(sdev->dev,
- "error: failed to set dai config for %s\n",
- dai->name);
-
- return ret;
- }
+ if (snd_sof_stream_suspend_ignored(sdev))
+ target_dsp_state = SOF_DSP_PM_D0;
+ else
+ target_dsp_state = SOF_DSP_PM_D3;
+ break;
+ default:
+ /* This case would be during runtime suspend */
+ target_dsp_state = SOF_DSP_PM_D3;
+ break;
}
- /* 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(sdev, swidget);
- break;
- default:
- break;
- }
- }
-
- /* restore pipeline kcontrols */
- ret = sof_restore_kcontrols(sdev);
- if (ret < 0)
- dev_err(sdev->dev,
- "error: restoring kcontrols after resume\n");
-
- return ret;
+ return target_dsp_state;
}
-static int sof_send_pm_ipc(struct snd_sof_dev *sdev, int cmd)
+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;
@@ -213,34 +64,6 @@
sizeof(pm_ctx), &reply, sizeof(reply));
}
-static int sof_set_hw_params_upon_resume(struct snd_sof_dev *sdev)
-{
- struct snd_pcm_substream *substream;
- struct snd_sof_pcm *spcm;
- snd_pcm_state_t state;
- int dir;
-
- /*
- * 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.
- */
- list_for_each_entry(spcm, &sdev->pcm_list, list) {
- for (dir = 0; dir <= SNDRV_PCM_STREAM_CAPTURE; dir++) {
- substream = spcm->stream[dir].substream;
- if (!substream || !substream->runtime)
- continue;
-
- state = substream->runtime->status->state;
- if (state == SNDRV_PCM_STATE_SUSPENDED)
- spcm->prepared[dir] = false;
- }
- }
-
- /* set internal flag for BE */
- return snd_sof_dsp_hw_params_upon_resume(sdev);
-}
-
#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE)
static void sof_cache_debugfs(struct snd_sof_dev *sdev)
{
@@ -263,6 +86,7 @@
static int sof_resume(struct device *dev, bool runtime_resume)
{
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
+ u32 old_state = sdev->dsp_power_state.state;
int ret;
/* do nothing if dsp resume callbacks are not set */
@@ -290,6 +114,14 @@
return ret;
}
+ /*
+ * Nothing further to be done for platforms that support the low power
+ * D0 substate.
+ */
+ if (!runtime_resume && sof_ops(sdev)->set_power_state &&
+ old_state == SOF_DSP_PM_D0)
+ return 0;
+
sdev->fw_state = SOF_FW_BOOT_PREPARE;
/* load the firmware */
@@ -325,7 +157,7 @@
}
/* restore pipelines */
- ret = sof_restore_pipelines(sdev);
+ ret = sof_restore_pipelines(sdev->dev);
if (ret < 0) {
dev_err(sdev->dev,
"error: failed to restore pipeline after resume %d\n",
@@ -334,7 +166,7 @@
}
/* notify DSP of system resume */
- ret = sof_send_pm_ipc(sdev, SOF_IPC_PM_CTX_RESTORE);
+ 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",
@@ -346,6 +178,7 @@
static int sof_suspend(struct device *dev, bool runtime_suspend)
{
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
+ u32 target_state = 0;
int ret;
/* do nothing if dsp suspend callback is not set */
@@ -356,14 +189,11 @@
return 0;
if (sdev->fw_state != SOF_FW_BOOT_COMPLETE)
- goto power_down;
-
- /* release trace */
- snd_sof_release_trace(sdev);
+ goto suspend;
/* set restore_stream for all streams during system suspend */
if (!runtime_suspend) {
- ret = sof_set_hw_params_upon_resume(sdev);
+ ret = sof_set_hw_params_upon_resume(sdev->dev);
if (ret < 0) {
dev_err(sdev->dev,
"error: setting hw_params flag during suspend %d\n",
@@ -372,13 +202,22 @@
}
}
+ target_state = snd_sof_dsp_power_target(sdev);
+
+ /* 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_ipc(sdev, SOF_IPC_PM_CTX_SAVE);
+ 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
@@ -395,28 +234,42 @@
ret);
}
-power_down:
+suspend:
/* return if the DSP was not probed successfully */
if (sdev->fw_state == SOF_FW_BOOT_NOT_STARTED)
return 0;
- /* power down all DSP cores */
+ /* platform-specific suspend */
if (runtime_suspend)
ret = snd_sof_dsp_runtime_suspend(sdev);
else
- ret = snd_sof_dsp_suspend(sdev);
+ ret = snd_sof_dsp_suspend(sdev, target_state);
if (ret < 0)
dev_err(sdev->dev,
"error: failed to power down DSP during suspend %d\n",
ret);
+ /* Do not reset FW state if DSP is in D0 */
+ if (target_state == SOF_DSP_PM_D0)
+ return ret;
+
/* reset FW state */
sdev->fw_state = SOF_FW_BOOT_NOT_STARTED;
+ sdev->enabled_cores_mask = 0;
return ret;
}
+int snd_sof_dsp_power_down_notify(struct snd_sof_dev *sdev)
+{
+ /* Notify DSP of upcoming power down */
+ if (sof_ops(sdev)->remove)
+ return sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_SAVE);
+
+ return 0;
+}
+
int snd_sof_runtime_suspend(struct device *dev)
{
return sof_suspend(dev, true);
@@ -448,3 +301,31 @@
return sof_suspend(dev, false);
}
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 (!desc->use_acpi_target_states)
+ return 0;
+
+#if defined(CONFIG_ACPI)
+ if (acpi_target_system_state() == ACPI_STATE_S0)
+ sdev->system_suspend_target = SOF_SUSPEND_S0IX;
+#endif
+
+ return 0;
+}
+EXPORT_SYMBOL(snd_sof_prepare);
+
+void snd_sof_complete(struct device *dev)
+{
+ struct snd_sof_dev *sdev = dev_get_drvdata(dev);
+
+ sdev->system_suspend_target = SOF_SUSPEND_NONE;
+}
+EXPORT_SYMBOL(snd_sof_complete);
diff --git a/sound/soc/sof/probe.c b/sound/soc/sof/probe.c
new file mode 100644
index 0000000..14509f4
--- /dev/null
+++ b/sound/soc/sof/probe.c
@@ -0,0 +1,290 @@
+// 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
new file mode 100644
index 0000000..5e159ab
--- /dev/null
+++ b/sound/soc/sof/probe.h
@@ -0,0 +1,85 @@
+/* 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 ea7b8b8..a78b76e 100644
--- a/sound/soc/sof/sof-acpi-dev.c
+++ b/sound/soc/sof/sof-acpi-dev.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -29,24 +29,13 @@
module_param(tplg_path, charp, 0444);
MODULE_PARM_DESC(tplg_path, "alternate path for SOF topology.");
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HASWELL)
-static const struct sof_dev_desc sof_acpi_haswell_desc = {
- .machines = snd_soc_acpi_intel_haswell_machines,
- .resindex_lpe_base = 0,
- .resindex_pcicfg_base = 1,
- .resindex_imr_base = -1,
- .irqindex_host_ipc = 0,
- .chip_info = &hsw_chip_info,
- .default_fw_path = "intel/sof",
- .default_tplg_path = "intel/sof-tplg",
- .nocodec_fw_filename = "sof-hsw.ri",
- .nocodec_tplg_filename = "sof-hsw-nocodec.tplg",
- .ops = &sof_hsw_ops,
- .arch_ops = &sof_xtensa_arch_ops
-};
-#endif
+static int sof_acpi_debug;
+module_param_named(sof_acpi_debug, sof_acpi_debug, int, 0444);
+MODULE_PARM_DESC(sof_acpi_debug, "SOF ACPI debug options (0x0 all off)");
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL)
+#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,
@@ -56,14 +45,13 @@
.chip_info = &bdw_chip_info,
.default_fw_path = "intel/sof",
.default_tplg_path = "intel/sof-tplg",
- .nocodec_fw_filename = "sof-bdw.ri",
+ .default_fw_filename = "sof-bdw.ri",
.nocodec_tplg_filename = "sof-bdw-nocodec.tplg",
.ops = &sof_bdw_ops,
- .arch_ops = &sof_xtensa_arch_ops
};
#endif
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
+#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 = {
@@ -75,10 +63,9 @@
.chip_info = &byt_chip_info,
.default_fw_path = "intel/sof",
.default_tplg_path = "intel/sof-tplg",
- .nocodec_fw_filename = "sof-byt.ri",
+ .default_fw_filename = "sof-byt.ri",
.nocodec_tplg_filename = "sof-byt-nocodec.tplg",
.ops = &sof_byt_ops,
- .arch_ops = &sof_xtensa_arch_ops
};
static const struct sof_dev_desc sof_acpi_baytrail_desc = {
@@ -90,10 +77,9 @@
.chip_info = &byt_chip_info,
.default_fw_path = "intel/sof",
.default_tplg_path = "intel/sof-tplg",
- .nocodec_fw_filename = "sof-byt.ri",
+ .default_fw_filename = "sof-byt.ri",
.nocodec_tplg_filename = "sof-byt-nocodec.tplg",
.ops = &sof_byt_ops,
- .arch_ops = &sof_xtensa_arch_ops
};
static const struct sof_dev_desc sof_acpi_cherrytrail_desc = {
@@ -105,10 +91,9 @@
.chip_info = &cht_chip_info,
.default_fw_path = "intel/sof",
.default_tplg_path = "intel/sof-tplg",
- .nocodec_fw_filename = "sof-cht.ri",
+ .default_fw_filename = "sof-cht.ri",
.nocodec_tplg_filename = "sof-cht-nocodec.tplg",
.ops = &sof_cht_ops,
- .arch_ops = &sof_xtensa_arch_ops
};
#endif
@@ -121,6 +106,11 @@
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;
+
/* allow runtime_pm */
pm_runtime_set_autosuspend_delay(dev, SND_SOF_SUSPEND_DELAY_MS);
pm_runtime_use_autosuspend(dev);
@@ -131,7 +121,6 @@
{
struct device *dev = &pdev->dev;
const struct sof_dev_desc *desc;
- struct snd_soc_acpi_mach *mach;
struct snd_sof_pdata *sof_pdata;
const struct snd_sof_dsp_ops *ops;
int ret;
@@ -146,7 +135,7 @@
if (!desc)
return -ENODEV;
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_BAYTRAIL)
+#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
@@ -158,35 +147,9 @@
return -ENODEV;
}
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_FORCE_NOCODEC_MODE)
- /* force nocodec mode */
- dev_warn(dev, "Force to use nocodec mode\n");
- mach = devm_kzalloc(dev, sizeof(*mach), GFP_KERNEL);
- if (!mach)
- return -ENOMEM;
- ret = sof_nocodec_setup(dev, sof_pdata, mach, desc, ops);
- if (ret < 0)
- return ret;
-#else
- /* find machine */
- mach = snd_soc_acpi_find_machine(desc->machines);
- if (!mach) {
- dev_warn(dev, "warning: No matching ASoC machine driver found\n");
- } else {
- sof_pdata->fw_filename = mach->sof_fw_filename;
- sof_pdata->tplg_filename = mach->sof_tplg_filename;
- }
-#endif
-
- if (mach) {
- mach->mach_params.platform = dev_name(dev);
- mach->mach_params.acpi_ipc_irq_index = desc->irqindex_host_ipc;
- }
-
- sof_pdata->machine = mach;
sof_pdata->desc = desc;
sof_pdata->dev = &pdev->dev;
- sof_pdata->platform = dev_name(dev);
+ sof_pdata->fw_filename = desc->default_fw_filename;
/* alternate fw and tplg filenames ? */
if (fw_path)
@@ -221,7 +184,8 @@
static int sof_acpi_remove(struct platform_device *pdev)
{
- pm_runtime_disable(&pdev->dev);
+ if (!(sof_acpi_debug & SOF_ACPI_DISABLE_PM_RUNTIME))
+ pm_runtime_disable(&pdev->dev);
/* call sof helper for DSP hardware remove */
snd_sof_device_remove(&pdev->dev);
@@ -229,10 +193,8 @@
return 0;
}
+#ifdef CONFIG_ACPI
static const struct acpi_device_id sof_acpi_match[] = {
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_HASWELL)
- { "INT33C8", (unsigned long)&sof_acpi_haswell_desc },
-#endif
#if IS_ENABLED(CONFIG_SND_SOC_SOF_BROADWELL)
{ "INT3438", (unsigned long)&sof_acpi_broadwell_desc },
#endif
@@ -243,6 +205,7 @@
{ }
};
MODULE_DEVICE_TABLE(acpi, sof_acpi_match);
+#endif
/* acpi_driver definition */
static struct platform_driver snd_sof_acpi_driver = {
@@ -257,3 +220,5 @@
module_platform_driver(snd_sof_acpi_driver);
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-audio.c b/sound/soc/sof/sof-audio.c
new file mode 100644
index 0000000..afe7e50
--- /dev/null
+++ b/sound/soc/sof/sof-audio.c
@@ -0,0 +1,518 @@
+// 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.
+//
+// Author: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
+//
+
+#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)
+{
+ 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;
+ }
+ }
+
+ 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_set_hw_params_upon_resume(struct device *dev)
+{
+ 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;
+
+ /*
+ * 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.
+ */
+ 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)
+ continue;
+
+ substream = spcm->stream[dir].substream;
+ if (!substream || !substream->runtime)
+ continue;
+
+ state = substream->runtime->status->state;
+ if (state == SNDRV_PCM_STATE_SUSPENDED)
+ spcm->prepared[dir] = false;
+ }
+ }
+
+ /* set internal flag for BE */
+ return snd_sof_dsp_hw_params_upon_resume(sdev);
+}
+
+static int sof_restore_kcontrols(struct device *dev)
+{
+ struct snd_sof_dev *sdev = dev_get_drvdata(dev);
+ struct snd_sof_control *scontrol;
+ int ipc_cmd, ctrl_type;
+ 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;
+
+ default:
+ break;
+ }
+
+ if (ret < 0) {
+ dev_err(dev,
+ "error: failed kcontrol value set for widget: %d\n",
+ scontrol->comp_id);
+
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+const struct sof_ipc_pipe_new *snd_sof_pipeline_find(struct snd_sof_dev *sdev,
+ int pipeline_id)
+{
+ const struct snd_sof_widget *swidget;
+
+ list_for_each_entry(swidget, &sdev->widget_list, list)
+ if (swidget->id == snd_soc_dapm_scheduler) {
+ const struct sof_ipc_pipe_new *pipeline =
+ swidget->private;
+ if (pipeline->pipeline_id == pipeline_id)
+ return pipeline;
+ }
+
+ return NULL;
+}
+
+int sof_restore_pipelines(struct device *dev)
+{
+ 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_cmd_hdr *hdr;
+ struct sof_ipc_comp *comp;
+ size_t ipc_size;
+ int ret;
+
+ /* restore pipeline components */
+ list_for_each_entry_reverse(swidget, &sdev->widget_list, list) {
+ struct sof_ipc_comp_reply r;
+
+ /* skip if there is no private data */
+ if (!swidget->private)
+ continue;
+
+ ret = sof_pipeline_core_enable(sdev, swidget);
+ if (ret < 0) {
+ dev_err(dev,
+ "error: failed to enable target core: %d\n",
+ ret);
+
+ return ret;
+ }
+
+ switch (swidget->id) {
+ case snd_soc_dapm_dai_in:
+ case snd_soc_dapm_dai_out:
+ ipc_size = sizeof(struct sof_ipc_comp_dai) +
+ sizeof(struct sof_ipc_comp_ext);
+ comp = kzalloc(ipc_size, GFP_KERNEL);
+ if (!comp)
+ return -ENOMEM;
+
+ dai = swidget->private;
+ memcpy(comp, &dai->comp_dai,
+ sizeof(struct sof_ipc_comp_dai));
+
+ /* append extended data to the end of the component */
+ memcpy((u8 *)comp + sizeof(struct sof_ipc_comp_dai),
+ &swidget->comp_ext, sizeof(swidget->comp_ext));
+
+ ret = sof_ipc_tx_message(sdev->ipc, comp->hdr.cmd,
+ comp, ipc_size,
+ &r, sizeof(r));
+ kfree(comp);
+ break;
+ case snd_soc_dapm_scheduler:
+
+ /*
+ * 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.
+ */
+ pipeline = swidget->private;
+ ret = sof_load_pipeline_ipc(dev, pipeline, &r);
+ break;
+ default:
+ hdr = swidget->private;
+ ret = sof_ipc_tx_message(sdev->ipc, hdr->cmd,
+ swidget->private, hdr->size,
+ &r, sizeof(r));
+ break;
+ }
+ if (ret < 0) {
+ dev_err(dev,
+ "error: failed to load widget type %d with ID: %d\n",
+ swidget->widget->id, swidget->comp_id);
+
+ 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;
+
+ /* skip if there's no private data */
+ if (!sroute->private)
+ continue;
+
+ connect = sroute->private;
+
+ /* 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);
+
+ 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;
+
+ if (!config) {
+ dev_err(dev, "error: no config for DAI %s\n",
+ dai->name);
+ continue;
+ }
+
+ /*
+ * 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_ipc_tx_message(sdev->ipc,
+ config->hdr.cmd, config,
+ config->hdr.size,
+ &reply, sizeof(reply));
+
+ if (ret < 0) {
+ dev_err(dev,
+ "error: failed to set dai config for %s\n",
+ dai->name);
+
+ return ret;
+ }
+ }
+
+ /* 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;
+ }
+ }
+
+ /* restore pipeline kcontrols */
+ ret = sof_restore_kcontrols(dev);
+ if (ret < 0)
+ dev_err(dev,
+ "error: restoring kcontrols after resume\n");
+
+ return ret;
+}
+
+/*
+ * Generic object lookup APIs.
+ */
+
+struct snd_sof_pcm *snd_sof_find_spcm_name(struct snd_soc_component *scomp,
+ const char *name)
+{
+ 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) {
+ /* match with PCM dai name */
+ if (strcmp(spcm->pcm.dai_name, name) == 0)
+ return spcm;
+
+ /* match with playback caps name if set */
+ if (*spcm->pcm.caps[0].name &&
+ !strcmp(spcm->pcm.caps[0].name, name))
+ return spcm;
+
+ /* match with capture caps name if set */
+ if (*spcm->pcm.caps[1].name &&
+ !strcmp(spcm->pcm.caps[1].name, name))
+ return spcm;
+ }
+
+ return NULL;
+}
+
+struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_soc_component *scomp,
+ unsigned int comp_id,
+ int *direction)
+{
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct snd_sof_pcm *spcm;
+ int dir;
+
+ list_for_each_entry(spcm, &sdev->pcm_list, list) {
+ for_each_pcm_streams(dir) {
+ if (spcm->stream[dir].comp_id == comp_id) {
+ *direction = dir;
+ return spcm;
+ }
+ }
+ }
+
+ 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)
+{
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct snd_sof_widget *swidget;
+
+ list_for_each_entry(swidget, &sdev->widget_list, list) {
+ if (strcmp(name, swidget->widget->name) == 0)
+ return swidget;
+ }
+
+ return NULL;
+}
+
+/* find widget by stream name and direction */
+struct snd_sof_widget *
+snd_sof_find_swidget_sname(struct snd_soc_component *scomp,
+ const char *pcm_name, int dir)
+{
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct snd_sof_widget *swidget;
+ enum snd_soc_dapm_type type;
+
+ if (dir == SNDRV_PCM_STREAM_PLAYBACK)
+ type = snd_soc_dapm_aif_in;
+ else
+ type = snd_soc_dapm_aif_out;
+
+ list_for_each_entry(swidget, &sdev->widget_list, list) {
+ if (!strcmp(pcm_name, swidget->widget->sname) &&
+ swidget->id == type)
+ return swidget;
+ }
+
+ return NULL;
+}
+
+struct snd_sof_dai *snd_sof_find_dai(struct snd_soc_component *scomp,
+ const char *name)
+{
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct snd_sof_dai *dai;
+
+ list_for_each_entry(dai, &sdev->dai_list, list) {
+ if (dai->name && (strcmp(name, dai->name) == 0))
+ return dai;
+ }
+
+ return NULL;
+}
+
+/*
+ * SOF Driver enumeration.
+ */
+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;
+ 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);
+ 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);
+
+ return 0;
+}
+EXPORT_SYMBOL(sof_machine_check);
+
+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;
+
+ if (!IS_ERR_OR_NULL(plat_data->pdev_mach))
+ platform_device_unregister(plat_data->pdev_mach);
+}
+EXPORT_SYMBOL(sof_machine_unregister);
diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h
new file mode 100644
index 0000000..9f645a2
--- /dev/null
+++ b/sound/soc/sof/sof-audio.h
@@ -0,0 +1,225 @@
+/* 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.
+ *
+ * Author: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
+ */
+
+#ifndef __SOUND_SOC_SOF_AUDIO_H
+#define __SOUND_SOC_SOF_AUDIO_H
+
+#include <linux/workqueue.h>
+
+#include <sound/soc.h>
+#include <sound/control.h>
+#include <sound/sof/stream.h> /* needs to be included before control.h */
+#include <sound/sof/control.h>
+#include <sound/sof/dai.h>
+#include <sound/sof/topology.h>
+#include "sof-priv.h"
+
+#define SOF_AUDIO_PCM_DRV_NAME "sof-audio-component"
+
+/* max number of FE PCMs before BEs */
+#define SOF_BE_PCM_BASE 16
+
+#define DMA_CHAN_INVALID 0xFFFFFFFF
+
+/* 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 work_struct period_elapsed_work;
+ 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;
+};
+
+/* ALSA SOF PCM device */
+struct snd_sof_pcm {
+ struct snd_soc_component *scomp;
+ struct snd_soc_tplg_pcm pcm;
+ struct snd_sof_pcm_stream stream[2];
+ struct list_head list; /* list in sdev pcm list */
+ struct snd_pcm_hw_params params[2];
+ bool prepared[2]; /* PCM_PARAMS set successfully */
+};
+
+struct snd_sof_led_control {
+ unsigned int use_led;
+ unsigned int direction;
+ int led_value;
+};
+
+/* ALSA SOF Kcontrol device */
+struct snd_sof_control {
+ struct snd_soc_component *scomp;
+ 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;
+ 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;
+};
+
+/* ASoC SOF DAPM widget */
+struct snd_sof_widget {
+ struct snd_soc_component *scomp;
+ int comp_id;
+ int pipeline_id;
+ int complete;
+ int core;
+ int id;
+
+ struct snd_soc_dapm_widget *widget;
+ struct list_head list; /* list in sdev widget list */
+
+ /* extended data for UUID components */
+ struct sof_ipc_comp_ext comp_ext;
+
+ void *private; /* core does not touch this */
+};
+
+/* 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 */
+
+ void *private;
+};
+
+/* ASoC DAI device */
+struct snd_sof_dai {
+ struct snd_soc_component *scomp;
+ const char *name;
+ const char *cpu_dai_name;
+
+ struct sof_ipc_comp_dai comp_dai;
+ struct sof_ipc_dai_config *dai_config;
+ struct list_head list; /* list in sdev dai list */
+};
+
+/*
+ * Kcontrols.
+ */
+
+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_switch_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+int snd_sof_switch_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+int snd_sof_enum_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+int snd_sof_enum_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+int snd_sof_bytes_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+int snd_sof_bytes_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol);
+int snd_sof_bytes_ext_put(struct snd_kcontrol *kcontrol,
+ const unsigned int __user *binary_data,
+ unsigned int size);
+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);
+
+/*
+ * Topology.
+ * There is no snd_sof_free_topology since topology components will
+ * 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);
+int sof_pipeline_core_enable(struct snd_sof_dev *sdev,
+ const struct snd_sof_widget *swidget);
+
+/*
+ * Stream IPC
+ */
+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_widget *snd_sof_find_swidget(struct snd_soc_component *scomp,
+ const char *name);
+struct snd_sof_widget *
+snd_sof_find_swidget_sname(struct snd_soc_component *scomp,
+ const char *pcm_name, int dir);
+struct snd_sof_dai *snd_sof_find_dai(struct snd_soc_component *scomp,
+ const char *name);
+
+static inline
+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;
+
+ list_for_each_entry(spcm, &sdev->pcm_list, list) {
+ if (le32_to_cpu(spcm->pcm.dai_id) == rtd->dai_link->id)
+ return spcm;
+ }
+
+ return NULL;
+}
+
+struct snd_sof_pcm *snd_sof_find_spcm_name(struct snd_soc_component *scomp,
+ const char *name);
+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);
+const struct sof_ipc_pipe_new *snd_sof_pipeline_find(struct snd_sof_dev *sdev,
+ int pipeline_id);
+void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream);
+void snd_sof_pcm_period_elapsed_work(struct work_struct *work);
+
+/*
+ * 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);
+
+/* 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);
+
+/* Machine driver enumeration */
+int sof_machine_register(struct snd_sof_dev *sdev, void *pdata);
+void sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata);
+
+#endif
diff --git a/sound/soc/sof/sof-of-dev.c b/sound/soc/sof/sof-of-dev.c
index 28a9692..85ff0db 100644
--- a/sound/soc/sof/sof-of-dev.c
+++ b/sound/soc/sof/sof-of-dev.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// Copyright 2019 NXP
//
@@ -13,19 +13,41 @@
#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",
- .nocodec_fw_filename = "sof-imx8.ri",
+ .default_fw_filename = "sof-imx8x.ri",
+ .nocodec_tplg_filename = "sof-imx8-nocodec.tplg",
+ .ops = &sof_imx8x_ops,
+};
+
+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 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)
@@ -36,15 +58,17 @@
/* allow runtime_pm */
pm_runtime_set_autosuspend_delay(dev, SND_SOF_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_put_autosuspend(dev);
}
static int sof_of_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct sof_dev_desc *desc;
- /*TODO: create a generic snd_soc_xxx_mach */
- struct snd_soc_acpi_mach *mach;
struct snd_sof_pdata *sof_pdata;
const struct snd_sof_dsp_ops *ops;
int ret;
@@ -66,27 +90,9 @@
return -ENODEV;
}
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_FORCE_NOCODEC_MODE)
- /* force nocodec mode */
- dev_warn(dev, "Force to use nocodec mode\n");
- mach = devm_kzalloc(dev, sizeof(*mach), GFP_KERNEL);
- if (!mach)
- return -ENOMEM;
- ret = sof_nocodec_setup(dev, sof_pdata, mach, desc, ops);
- if (ret < 0)
- return ret;
-#else
- /* TODO: implement case where we actually have a codec */
- return -ENODEV;
-#endif
-
- if (mach)
- mach->mach_params.platform = dev_name(dev);
-
- sof_pdata->machine = mach;
sof_pdata->desc = desc;
sof_pdata->dev = &pdev->dev;
- sof_pdata->platform = dev_name(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;
@@ -123,6 +129,10 @@
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
{ }
};
diff --git a/sound/soc/sof/sof-pci-dev.c b/sound/soc/sof/sof-pci-dev.c
index 3f79cd0..75657a2 100644
--- a/sound/soc/sof/sof-pci-dev.c
+++ b/sound/soc/sof/sof-pci-dev.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -9,9 +9,11 @@
//
#include <linux/firmware.h>
+#include <linux/dmi.h>
#include <linux/module.h>
#include <linux/pci.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>
@@ -29,9 +31,60 @@
module_param(tplg_path, charp, 0444);
MODULE_PARM_DESC(tplg_path, "alternate path 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 const char *sof_override_tplg_name;
+
+#define SOF_PCI_DISABLE_PM_RUNTIME BIT(0)
+
+static int sof_tplg_cb(const struct dmi_system_id *id)
+{
+ sof_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",
+ },
+ {}
+};
+
+static const struct dmi_system_id community_key_platforms[] = {
+ {
+ .ident = "Up Squared",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "AAEON"),
+ DMI_MATCH(DMI_BOARD_NAME, "UP-APL01"),
+ }
+ },
+ {
+ .ident = "Up Extreme",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "AAEON"),
+ DMI_MATCH(DMI_BOARD_NAME, "UP-WHL01"),
+ }
+ },
+ {
+ .ident = "Google Chromebooks",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Google"),
+ }
+ },
+ {},
+};
+
#if IS_ENABLED(CONFIG_SND_SOC_SOF_APOLLOLAKE)
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,
@@ -40,16 +93,16 @@
.chip_info = &apl_chip_info,
.default_fw_path = "intel/sof",
.default_tplg_path = "intel/sof-tplg",
- .nocodec_fw_filename = "sof-apl.ri",
+ .default_fw_filename = "sof-apl.ri",
.nocodec_tplg_filename = "sof-apl-nocodec.tplg",
.ops = &sof_apl_ops,
- .arch_ops = &sof_xtensa_arch_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,
+ .use_acpi_target_states = true,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
@@ -58,10 +111,9 @@
.chip_info = &apl_chip_info,
.default_fw_path = "intel/sof",
.default_tplg_path = "intel/sof-tplg",
- .nocodec_fw_filename = "sof-glk.ri",
+ .default_fw_filename = "sof-glk.ri",
.nocodec_tplg_filename = "sof-glk-nocodec.tplg",
.ops = &sof_apl_ops,
- .arch_ops = &sof_xtensa_arch_ops
};
#endif
@@ -86,16 +138,17 @@
.chip_info = &tng_chip_info,
.default_fw_path = "intel/sof",
.default_tplg_path = "intel/sof-tplg",
- .nocodec_fw_filename = "sof-byt.ri",
+ .default_fw_filename = "sof-byt.ri",
.nocodec_tplg_filename = "sof-byt.tplg",
.ops = &sof_tng_ops,
- .arch_ops = &sof_xtensa_arch_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,
+ .use_acpi_target_states = true,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
@@ -104,16 +157,17 @@
.chip_info = &cnl_chip_info,
.default_fw_path = "intel/sof",
.default_tplg_path = "intel/sof-tplg",
- .nocodec_fw_filename = "sof-cnl.ri",
+ .default_fw_filename = "sof-cnl.ri",
.nocodec_tplg_filename = "sof-cnl-nocodec.tplg",
.ops = &sof_cnl_ops,
- .arch_ops = &sof_xtensa_arch_ops
};
#endif
#if IS_ENABLED(CONFIG_SND_SOC_SOF_COFFEELAKE)
static const struct sof_dev_desc cfl_desc = {
- .machines = snd_soc_acpi_intel_cnl_machines,
+ .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,
@@ -122,18 +176,17 @@
.chip_info = &cnl_chip_info,
.default_fw_path = "intel/sof",
.default_tplg_path = "intel/sof-tplg",
- .nocodec_fw_filename = "sof-cnl.ri",
+ .default_fw_filename = "sof-cfl.ri",
.nocodec_tplg_filename = "sof-cnl-nocodec.tplg",
.ops = &sof_cnl_ops,
- .arch_ops = &sof_xtensa_arch_ops
};
#endif
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMETLAKE_LP) || \
- IS_ENABLED(CONFIG_SND_SOC_SOF_COMETLAKE_H)
-
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMETLAKE)
static const struct sof_dev_desc cml_desc = {
- .machines = snd_soc_acpi_intel_cnl_machines,
+ .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,
@@ -142,16 +195,17 @@
.chip_info = &cnl_chip_info,
.default_fw_path = "intel/sof",
.default_tplg_path = "intel/sof-tplg",
- .nocodec_fw_filename = "sof-cnl.ri",
+ .default_fw_filename = "sof-cml.ri",
.nocodec_tplg_filename = "sof-cnl-nocodec.tplg",
.ops = &sof_cnl_ops,
- .arch_ops = &sof_xtensa_arch_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,
+ .use_acpi_target_states = true,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
@@ -160,52 +214,17 @@
.chip_info = &icl_chip_info,
.default_fw_path = "intel/sof",
.default_tplg_path = "intel/sof-tplg",
- .nocodec_fw_filename = "sof-icl.ri",
+ .default_fw_filename = "sof-icl.ri",
.nocodec_tplg_filename = "sof-icl-nocodec.tplg",
.ops = &sof_cnl_ops,
- .arch_ops = &sof_xtensa_arch_ops
-};
-#endif
-
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_SKYLAKE)
-static const 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,
- .irqindex_host_ipc = -1,
- .resindex_dma_base = -1,
- .chip_info = &skl_chip_info,
- .default_fw_path = "intel/sof",
- .default_tplg_path = "intel/sof-tplg",
- .nocodec_fw_filename = "sof-skl.ri",
- .nocodec_tplg_filename = "sof-skl-nocodec.tplg",
- .ops = &sof_skl_ops,
- .arch_ops = &sof_xtensa_arch_ops
-};
-#endif
-
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_KABYLAKE)
-static const 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,
- .irqindex_host_ipc = -1,
- .resindex_dma_base = -1,
- .chip_info = &skl_chip_info,
- .default_fw_path = "intel/sof",
- .default_tplg_path = "intel/sof-tplg",
- .nocodec_fw_filename = "sof-kbl.ri",
- .nocodec_tplg_filename = "sof-kbl-nocodec.tplg",
- .ops = &sof_skl_ops,
- .arch_ops = &sof_xtensa_arch_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,
+ .use_acpi_target_states = true,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
@@ -214,16 +233,32 @@
.chip_info = &tgl_chip_info,
.default_fw_path = "intel/sof",
.default_tplg_path = "intel/sof-tplg",
- .nocodec_fw_filename = "sof-tgl.ri",
+ .default_fw_filename = "sof-tgl.ri",
.nocodec_tplg_filename = "sof-tgl-nocodec.tplg",
- .ops = &sof_cnl_ops,
- .arch_ops = &sof_xtensa_arch_ops
+ .ops = &sof_tgl_ops,
+};
+
+static const struct sof_dev_desc tglh_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 = &tglh_chip_info,
+ .default_fw_path = "intel/sof",
+ .default_tplg_path = "intel/sof-tplg",
+ .default_fw_filename = "sof-tgl-h.ri",
+ .nocodec_tplg_filename = "sof-tgl-nocodec.tplg",
+ .ops = &sof_tgl_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,
+ .use_acpi_target_states = true,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
@@ -232,14 +267,33 @@
.chip_info = &ehl_chip_info,
.default_fw_path = "intel/sof",
.default_tplg_path = "intel/sof-tplg",
- .nocodec_fw_filename = "sof-ehl.ri",
+ .default_fw_filename = "sof-ehl.ri",
.nocodec_tplg_filename = "sof-ehl-nocodec.tplg",
.ops = &sof_cnl_ops,
- .arch_ops = &sof_xtensa_arch_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,
+ .use_acpi_target_states = true,
+ .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 = {
+ .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)
@@ -249,6 +303,9 @@
{
dev_dbg(dev, "Completing SOF PCI probe");
+ if (sof_pci_debug & SOF_PCI_DISABLE_PM_RUNTIME)
+ return;
+
/* allow runtime_pm */
pm_runtime_set_autosuspend_delay(dev, SND_SOF_SUSPEND_DELAY_MS);
pm_runtime_use_autosuspend(dev);
@@ -272,11 +329,15 @@
struct device *dev = &pci->dev;
const struct sof_dev_desc *desc =
(const struct sof_dev_desc *)pci_id->driver_data;
- struct snd_soc_acpi_mach *mach;
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) {
+ dev_dbg(&pci->dev, "SOF PCI driver not selected, aborting probe\n");
+ return -ENODEV;
+ }
dev_dbg(&pci->dev, "PCI DSP detected");
/* get ops for platform */
@@ -298,42 +359,39 @@
if (ret < 0)
return ret;
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_FORCE_NOCODEC_MODE)
- /* force nocodec mode */
- dev_warn(dev, "Force to use nocodec mode\n");
- mach = devm_kzalloc(dev, sizeof(*mach), GFP_KERNEL);
- if (!mach) {
- ret = -ENOMEM;
- goto release_regions;
- }
- ret = sof_nocodec_setup(dev, sof_pdata, mach, desc, ops);
- if (ret < 0)
- goto release_regions;
-
-#else
- /* find machine */
- mach = snd_soc_acpi_find_machine(desc->machines);
- if (!mach) {
- dev_warn(dev, "warning: No matching ASoC machine driver found\n");
- } else {
- mach->mach_params.platform = dev_name(dev);
- sof_pdata->fw_filename = mach->sof_fw_filename;
- sof_pdata->tplg_filename = mach->sof_tplg_filename;
- }
-#endif /* CONFIG_SND_SOC_SOF_FORCE_NOCODEC_MODE */
-
sof_pdata->name = pci_name(pci);
- sof_pdata->machine = mach;
sof_pdata->desc = (struct sof_dev_desc *)pci_id->driver_data;
sof_pdata->dev = dev;
- sof_pdata->platform = dev_name(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.
+ */
/* alternate fw and tplg filenames ? */
- if (fw_path)
+ if (fw_path) {
sof_pdata->fw_filename_prefix = fw_path;
- else
+
+ dev_dbg(dev,
+ "Module parameter used, changed fw path to %s\n",
+ sof_pdata->fw_filename_prefix);
+
+ } 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");
+
+ dev_dbg(dev,
+ "Platform uses community key, changed fw path to %s\n",
+ sof_pdata->fw_filename_prefix);
+ } else {
sof_pdata->fw_filename_prefix =
sof_pdata->desc->default_fw_path;
+ }
if (tplg_path)
sof_pdata->tplg_filename_prefix = tplg_path;
@@ -341,6 +399,10 @@
sof_pdata->tplg_filename_prefix =
sof_pdata->desc->default_tplg_path;
+ dmi_check_system(sof_tplg_table);
+ if (sof_override_tplg_name)
+ sof_pdata->tplg_filename = sof_override_tplg_name;
+
#if IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)
/* set callback to enable runtime_pm */
sof_pdata->sof_probe_complete = sof_pci_probe_complete;
@@ -370,7 +432,8 @@
snd_sof_device_remove(&pci->dev);
/* follow recommendation in pci-driver.c to increment usage counter */
- pm_runtime_get_noresume(&pci->dev);
+ if (!(sof_pci_debug & SOF_PCI_DISABLE_PM_RUNTIME))
+ pm_runtime_get_noresume(&pci->dev);
/* release pci regions and disable device */
pci_release_regions(pci);
@@ -401,35 +464,39 @@
{ PCI_DEVICE(0x8086, 0xa348),
.driver_data = (unsigned long)&cfl_desc},
#endif
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_KABYLAKE)
- { PCI_DEVICE(0x8086, 0x9d71),
- .driver_data = (unsigned long)&kbl_desc},
-#endif
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_SKYLAKE)
- { PCI_DEVICE(0x8086, 0x9d70),
- .driver_data = (unsigned long)&skl_desc},
-#endif
#if IS_ENABLED(CONFIG_SND_SOC_SOF_ICELAKE)
- { PCI_DEVICE(0x8086, 0x34C8),
+ { 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_COMETLAKE_LP)
- { PCI_DEVICE(0x8086, 0x02c8),
+#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},
-#endif
-#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMETLAKE_H)
- { PCI_DEVICE(0x8086, 0x06c8),
+ { 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),
+ { PCI_DEVICE(0x8086, 0xa0c8), /* TGL-LP */
.driver_data = (unsigned long)&tgl_desc},
+ { PCI_DEVICE(0x8086, 0x43c8), /* TGL-H */
+ .driver_data = (unsigned long)&tglh_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, }
};
@@ -448,3 +515,5 @@
module_pci_driver(snd_sof_pci_driver);
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-priv.h b/sound/soc/sof/sof-priv.h
index 7b329bd..0aed2a7 100644
--- a/sound/soc/sof/sof-priv.h
+++ b/sound/soc/sof/sof-priv.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/* 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.
@@ -12,26 +12,23 @@
#define __SOUND_SOC_SOF_PRIV_H
#include <linux/device.h>
-
#include <sound/hdaudio.h>
-#include <sound/soc.h>
-
#include <sound/sof.h>
-#include <sound/sof/stream.h> /* needs to be included before control.h */
-#include <sound/sof/control.h>
-#include <sound/sof/dai.h>
#include <sound/sof/info.h>
#include <sound/sof/pm.h>
-#include <sound/sof/topology.h>
#include <sound/sof/trace.h>
-
#include <uapi/sound/sof/fw.h>
/* debug flags */
-#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_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 */
+
+/* global debug state set by SOF_DBG_ flags */
+extern int sof_core_debug;
/* max BARs mmaped devices can use */
#define SND_SOF_BARS 8
@@ -42,9 +39,6 @@
/* DMA buffer size for trace */
#define DMA_BUF_SIZE_FOR_TRACE (PAGE_SIZE * 16)
-/* max number of FE PCMs before BEs */
-#define SOF_BE_PCM_BASE 16
-
#define SOF_IPC_DSP_REPLY 0
#define SOF_IPC_HOST_REPLY 1
@@ -60,7 +54,30 @@
(IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) || \
IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST))
-#define DMA_CHAN_INVALID 0xFFFFFFFF
+/* So far the primary core on all DSPs has ID 0 */
+#define SOF_DSP_PRIMARY_CORE 0
+
+/* 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,
+};
+
+struct sof_dsp_power_state {
+ u32 state;
+ u32 substate; /* platform-specific */
+};
+
+/* System suspend target state */
+enum sof_system_suspend_state {
+ SOF_SUSPEND_NONE = 0,
+ SOF_SUSPEND_S0IX,
+ SOF_SUSPEND_S3,
+};
struct snd_sof_dev;
struct snd_sof_ipc_msg;
@@ -128,7 +145,7 @@
* FW ready checks for ABI compatibility and creates
* memory windows at first boot
*/
- int (*fw_ready)(struct snd_sof_dev *sdev, u32 msg_id); /* optional */
+ 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,
@@ -156,6 +173,27 @@
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
+
/* host read DSP stream data */
void (*ipc_msg_data)(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream,
@@ -171,12 +209,15 @@
int (*post_fw_run)(struct snd_sof_dev *sof_dev); /* optional */
/* DSP PM */
- int (*suspend)(struct snd_sof_dev *sof_dev); /* optional */
+ int (*suspend)(struct snd_sof_dev *sof_dev,
+ u32 target_state); /* optional */
int (*resume)(struct snd_sof_dev *sof_dev); /* optional */
int (*runtime_suspend)(struct snd_sof_dev *sof_dev); /* optional */
int (*runtime_resume)(struct snd_sof_dev *sof_dev); /* optional */
int (*runtime_idle)(struct snd_sof_dev *sof_dev); /* optional */
int (*set_hw_params_upon_resume)(struct snd_sof_dev *sdev); /* optional */
+ int (*set_power_state)(struct snd_sof_dev *sdev,
+ const struct sof_dsp_power_state *target_state); /* optional */
/* DSP clocking */
int (*set_clk)(struct snd_sof_dev *sof_dev, u32 freq); /* optional */
@@ -202,9 +243,23 @@
int (*get_window_offset)(struct snd_sof_dev *sdev,
u32 id);/* mandatory for common loader code */
+ /* machine driver ops */
+ int (*machine_register)(struct snd_sof_dev *sdev,
+ 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 */
+
/* DAI ops */
struct snd_soc_dai_driver *drv;
int num_drv;
+
+ /* ALSA HW info flags, will be stored in snd_pcm_runtime.hw.info */
+ u32 hw_info;
+
+ const struct sof_arch_ops *arch_ops;
};
/* DSP architecture specific callbacks for oops and stack dumps */
@@ -214,7 +269,7 @@
u32 *stack, u32 stack_words);
};
-#define sof_arch_ops(sdev) ((sdev)->pdata->desc->arch_ops)
+#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 {
@@ -286,76 +341,6 @@
bool ipc_complete;
};
-/* 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 work_struct period_elapsed_work;
-};
-
-/* ALSA SOF PCM device */
-struct snd_sof_pcm {
- struct snd_sof_dev *sdev;
- struct snd_soc_tplg_pcm pcm;
- struct snd_sof_pcm_stream stream[2];
- struct list_head list; /* list in sdev pcm list */
- struct snd_pcm_hw_params params[2];
- bool prepared[2]; /* PCM_PARAMS set successfully */
-};
-
-/* ALSA SOF Kcontrol device */
-struct snd_sof_control {
- struct snd_sof_dev *sdev;
- 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 mmaped data if used */
- struct sof_ipc_ctrl_data *control_data;
- 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 */
-};
-
-/* ASoC SOF DAPM widget */
-struct snd_sof_widget {
- struct snd_sof_dev *sdev;
- int comp_id;
- int pipeline_id;
- int complete;
- int id;
-
- struct snd_soc_dapm_widget *widget;
- struct list_head list; /* list in sdev widget list */
-
- void *private; /* core does not touch this */
-};
-
-/* ASoC SOF DAPM route */
-struct snd_sof_route {
- struct snd_sof_dev *sdev;
-
- struct snd_soc_dapm_route *route;
- struct list_head list; /* list in sdev route list */
-
- void *private;
-};
-
-/* ASoC DAI device */
-struct snd_sof_dai {
- struct snd_sof_dev *sdev;
- const char *name;
- const char *cpu_dai_name;
-
- struct sof_ipc_comp_dai comp_dai;
- struct sof_ipc_dai_config *dai_config;
- struct list_head list; /* list in sdev dai list */
-};
-
enum snd_sof_fw_state {
SOF_FW_BOOT_NOT_STARTED = 0,
SOF_FW_BOOT_PREPARE,
@@ -379,10 +364,16 @@
*/
struct snd_soc_component_driver plat_drv;
+ /* current DSP power state */
+ struct sof_dsp_power_state dsp_power_state;
+
+ /* 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;
+ bool first_boot;
/* work queue in case the probe is implemented in two steps */
struct work_struct probe_work;
@@ -395,6 +386,7 @@
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 */
@@ -414,6 +406,7 @@
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;
/* topology */
struct snd_soc_tplg_ops *tplg_ops;
@@ -426,16 +419,15 @@
u32 enabled_cores_mask; /* keep track of enabled cores */
/* FW configuration */
- struct sof_ipc_dma_buffer_data *info_buffer;
struct sof_ipc_window *info_window;
/* IPC timeouts in ms */
int ipc_timeout;
int boot_timeout;
- /* Wait queue for code loading */
- wait_queue_head_t waitq;
- int code_loading;
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES)
+ unsigned int extractor_stream_tag;
+#endif
/* DMA for Trace */
struct snd_dma_buffer dmatb;
@@ -443,9 +435,10 @@
int dma_trace_pages;
wait_queue_head_t trace_sleep;
u32 host_offset;
- u32 dtrace_is_enabled;
- u32 dtrace_error;
- u32 dtrace_draining;
+ bool dtrace_is_supported; /* set with Kconfig or module parameter */
+ bool dtrace_is_enabled;
+ bool dtrace_error;
+ bool dtrace_draining;
bool msi_enabled;
@@ -464,10 +457,13 @@
int snd_sof_runtime_idle(struct device *dev);
int snd_sof_resume(struct device *dev);
int snd_sof_suspend(struct device *dev);
+int snd_sof_dsp_power_down_notify(struct snd_sof_dev *sdev);
+int snd_sof_prepare(struct device *dev);
+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 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);
@@ -488,7 +484,7 @@
*/
struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev);
void snd_sof_ipc_free(struct snd_sof_dev *sdev);
-int snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id);
+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);
@@ -499,67 +495,9 @@
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);
-struct snd_sof_widget *snd_sof_find_swidget(struct snd_sof_dev *sdev,
- const char *name);
-struct snd_sof_widget *snd_sof_find_swidget_sname(struct snd_sof_dev *sdev,
- const char *pcm_name,
- int dir);
-struct snd_sof_dai *snd_sof_find_dai(struct snd_sof_dev *sdev,
- const char *name);
-
-static inline
-struct snd_sof_pcm *snd_sof_find_spcm_dai(struct snd_sof_dev *sdev,
- struct snd_soc_pcm_runtime *rtd)
-{
- struct snd_sof_pcm *spcm = NULL;
-
- list_for_each_entry(spcm, &sdev->pcm_list, list) {
- if (le32_to_cpu(spcm->pcm.dai_id) == rtd->dai_link->id)
- return spcm;
- }
-
- return NULL;
-}
-
-struct snd_sof_pcm *snd_sof_find_spcm_name(struct snd_sof_dev *sdev,
- const char *name);
-struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_sof_dev *sdev,
- unsigned int comp_id,
- int *direction);
-struct snd_sof_pcm *snd_sof_find_spcm_pcm_id(struct snd_sof_dev *sdev,
- unsigned int pcm_id);
-void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream);
-
-/*
- * Stream IPC
- */
-int snd_sof_ipc_stream_posn(struct snd_sof_dev *sdev,
- struct snd_sof_pcm *spcm, int direction,
- struct sof_ipc_stream_posn *posn);
-
-/*
- * Mixer IPC
- */
-int snd_sof_ipc_set_get_comp_data(struct snd_sof_ipc *ipc,
- struct snd_sof_control *scontrol, u32 ipc_cmd,
- enum sof_ipc_ctrl_type ctrl_type,
- enum sof_ipc_ctrl_cmd ctrl_cmd,
- bool send);
-
-/*
- * Topology.
- * There is no snd_sof_free_topology since topology components will
- * be freed by snd_soc_unregister_component,
- */
-int snd_sof_init_topology(struct snd_sof_dev *sdev,
- struct snd_soc_tplg_ops *ops);
-int snd_sof_load_topology(struct snd_sof_dev *sdev, const char *file);
-int snd_sof_complete_pipeline(struct snd_sof_dev *sdev,
- struct snd_sof_widget *swidget);
-
-int sof_load_pipeline_ipc(struct snd_sof_dev *sdev,
- struct sof_ipc_pipe_new *pipeline,
- struct sof_ipc_comp_reply *r);
+int sof_ipc_tx_message_no_pm(struct snd_sof_ipc *ipc, u32 header,
+ void *msg_data, size_t msg_bytes,
+ void *reply_data, size_t reply_bytes);
/*
* Trace/debug
@@ -584,38 +522,12 @@
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_compr_ops sof_compressed_ops;
-
-/*
- * Kcontrols.
- */
-
-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_switch_get(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol);
-int snd_sof_switch_put(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol);
-int snd_sof_enum_get(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol);
-int snd_sof_enum_put(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol);
-int snd_sof_bytes_get(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol);
-int snd_sof_bytes_put(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol);
-int snd_sof_bytes_ext_put(struct snd_kcontrol *kcontrol,
- const unsigned int __user *binary_data,
- unsigned int size);
-int snd_sof_bytes_ext_get(struct snd_kcontrol *kcontrol,
- unsigned int __user *binary_data,
- unsigned int size);
+extern struct snd_compress_ops sof_compressed_ops;
/*
* DSP Architectures.
@@ -623,7 +535,6 @@
static inline void sof_stack(struct snd_sof_dev *sdev, void *oops, u32 *stack,
u32 stack_words)
{
- if (sof_arch_ops(sdev)->dsp_stack)
sof_arch_ops(sdev)->dsp_stack(sdev, oops, stack, stack_words);
}
@@ -665,4 +576,14 @@
int intel_pcm_close(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream);
+int sof_machine_check(struct snd_sof_dev *sdev);
+
+#define sof_dev_dbg_or_err(dev, is_err, fmt, ...) \
+ do { \
+ if (is_err) \
+ dev_err(dev, "error: " fmt, __VA_ARGS__); \
+ else \
+ dev_dbg(dev, fmt, __VA_ARGS__); \
+ } while (0)
+
#endif
diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c
index fa299e0..b6327c3 100644
--- a/sound/soc/sof/topology.c
+++ b/sound/soc/sof/topology.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -8,11 +8,16 @@
// 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"
#include "ops.h"
#define COMP_ID_UNASSIGNED 0xffffffff
@@ -53,18 +58,19 @@
static int ipc_pcm_params(struct snd_sof_widget *swidget, int dir)
{
struct sof_ipc_pcm_params_reply ipc_params_reply;
- struct snd_sof_dev *sdev = swidget->sdev;
+ 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;
+ int ret;
memset(&pcm, 0, sizeof(pcm));
/* get runtime PCM params using widget's stream name */
- spcm = snd_sof_find_spcm_name(sdev, swidget->widget->sname);
+ spcm = snd_sof_find_spcm_name(scomp, swidget->widget->sname);
if (!spcm) {
- dev_err(sdev->dev, "error: cannot find PCM for %s\n",
+ dev_err(scomp->dev, "error: cannot find PCM for %s\n",
swidget->widget->name);
return -EINVAL;
}
@@ -102,7 +108,7 @@
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(sdev->dev, "error: pcm params failed for %s\n",
+ dev_err(scomp->dev, "error: pcm params failed for %s\n",
swidget->widget->name);
return ret;
@@ -111,10 +117,11 @@
/* send stream trigger ipc */
static int ipc_trigger(struct snd_sof_widget *swidget, int cmd)
{
- struct snd_sof_dev *sdev = swidget->sdev;
+ 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;
+ int ret;
/* set IPC stream params */
stream.hdr.size = sizeof(stream);
@@ -125,7 +132,7 @@
ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream,
sizeof(stream), &reply, sizeof(reply));
if (ret < 0)
- dev_err(sdev->dev, "error: failed to trigger %s\n",
+ dev_err(scomp->dev, "error: failed to trigger %s\n",
swidget->widget->name);
return ret;
@@ -135,24 +142,39 @@
struct snd_kcontrol *k, int event)
{
struct snd_sof_widget *swidget = w->dobj.private;
- struct snd_sof_dev *sdev;
+ struct snd_soc_component *scomp;
+ int stream = SNDRV_PCM_STREAM_CAPTURE;
+ struct snd_sof_pcm *spcm;
int ret = 0;
if (!swidget)
return 0;
- sdev = swidget->sdev;
+ scomp = swidget->scomp;
- dev_dbg(sdev->dev, "received event %d for widget %s\n",
+ 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, "error: cannot find PCM for %s\n",
+ 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 = ipc_pcm_params(swidget, SOF_IPC_STREAM_CAPTURE);
+ ret = ipc_pcm_params(swidget, stream);
if (ret < 0) {
- dev_err(sdev->dev,
+ dev_err(scomp->dev,
"error: failed to set pcm params for widget %s\n",
swidget->widget->name);
break;
@@ -161,22 +183,27 @@
/* start trigger */
ret = ipc_trigger(swidget, SOF_IPC_STREAM_TRIG_START);
if (ret < 0)
- dev_err(sdev->dev,
+ 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;
+ }
+
/* stop trigger */
ret = ipc_trigger(swidget, SOF_IPC_STREAM_TRIG_STOP);
if (ret < 0)
- dev_err(sdev->dev,
+ 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(sdev->dev,
+ dev_err(scomp->dev,
"error: failed to trigger widget %s\n",
swidget->widget->name);
break;
@@ -406,6 +433,8 @@
{"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)
@@ -433,164 +462,6 @@
}
/*
- * Standard Kcontrols.
- */
-
-static int sof_control_load_volume(struct snd_soc_component *scomp,
- struct snd_sof_control *scontrol,
- struct snd_kcontrol_new *kc,
- struct snd_soc_tplg_ctl_hdr *hdr)
-{
- 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;
-
- /* validate topology data */
- 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)
- return -ENOMEM;
-
- 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;
- goto out;
- }
-
- scontrol->cmd = SOF_CTRL_CMD_VOLUME;
-
- /* extract tlv data */
- if (get_tlv_data(kc->tlv.p, tlv) < 0) {
- dev_err(sdev->dev, "error: invalid TLV data\n");
- return -EINVAL;
- }
-
- /* set up volume table */
- ret = set_up_volume_table(scontrol, tlv, le32_to_cpu(mc->max) + 1);
- if (ret < 0) {
- dev_err(sdev->dev, "error: setting up volume table\n");
- return ret;
- }
-
- /* 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;
- }
-
-out:
- dev_dbg(sdev->dev, "tplg: load kcontrol index %d chans %d\n",
- scontrol->comp_id, scontrol->num_channels);
-
- return 0;
-}
-
-static int sof_control_load_enum(struct snd_soc_component *scomp,
- struct snd_sof_control *scontrol,
- struct snd_kcontrol_new *kc,
- struct snd_soc_tplg_ctl_hdr *hdr)
-{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct snd_soc_tplg_enum_control *ec =
- container_of(hdr, struct snd_soc_tplg_enum_control, hdr);
-
- /* validate topology data */
- 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(sdev->dev, "tplg: load kcontrol index %d chans %d comp_id %d\n",
- scontrol->comp_id, scontrol->num_channels, scontrol->comp_id);
-
- return 0;
-}
-
-static int sof_control_load_bytes(struct snd_soc_component *scomp,
- struct snd_sof_control *scontrol,
- struct snd_kcontrol_new *kc,
- 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;
-
- /* 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(sdev->dev, "err: bytes data size %d exceeds max %d.\n",
- scontrol->size, max_size);
- return -EINVAL;
- }
-
- scontrol->control_data = kzalloc(max_size, GFP_KERNEL);
- cdata = scontrol->control_data;
- if (!scontrol->control_data)
- return -ENOMEM;
-
- scontrol->comp_id = sdev->next_comp_id;
- scontrol->cmd = SOF_CTRL_CMD_BINARY;
-
- dev_dbg(sdev->dev, "tplg: load kcontrol index %d chans %d\n",
- scontrol->comp_id, scontrol->num_channels);
-
- if (le32_to_cpu(control->priv.size) > 0) {
- memcpy(cdata->data, control->priv.data,
- le32_to_cpu(control->priv.size));
-
- if (cdata->data->magic != SOF_ABI_MAGIC) {
- dev_err(sdev->dev, "error: Wrong ABI magic 0x%08x.\n",
- cdata->data->magic);
- return -EINVAL;
- }
- if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION,
- cdata->data->abi)) {
- dev_err(sdev->dev,
- "error: Incompatible ABI version 0x%08x.\n",
- cdata->data->abi);
- return -EINVAL;
- }
- if (cdata->data->size + sizeof(const struct sof_abi_hdr) !=
- le32_to_cpu(control->priv.size)) {
- dev_err(sdev->dev,
- "error: Conflict in bytes vs. priv size.\n");
- return -EINVAL;
- }
- }
- return 0;
-}
-
-/*
* Topology Token Parsing.
* New tokens should be added to headers and parsing tables below.
*/
@@ -621,6 +492,16 @@
return 0;
}
+static int get_token_uuid(void *elem, void *object, u32 offset, u32 size)
+{
+ struct snd_soc_tplg_vendor_uuid_elem *velem = elem;
+ u8 *dst = (u8 *)object + offset;
+
+ memcpy(dst, velem->uuid, UUID_SIZE);
+
+ return 0;
+}
+
static int get_token_comp_format(void *elem, void *object, u32 offset, u32 size)
{
struct snd_soc_tplg_vendor_string_elem *velem = elem;
@@ -708,6 +589,20 @@
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[] = {
};
@@ -725,6 +620,16 @@
offsetof(struct sof_ipc_comp_host, dmac_config), 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,
@@ -765,6 +670,16 @@
};
+/* 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},
+};
+
/* DMIC */
static const struct sof_topology_token dmic_tokens[] = {
{SOF_TKN_INTEL_DMIC_DRIVER_VERSION,
@@ -799,6 +714,34 @@
};
+/* 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},
+};
+
+/* 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},
+};
+
+/* 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), 0},
+};
+
+/* 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 sof_ipc_comp_ext, uuid), 0},
+};
+
/*
* DMIC PDM Tokens
* SOF_TKN_INTEL_DMIC_PDM_CTRL_ID should be the first token
@@ -838,15 +781,31 @@
/* 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},
};
-static void 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)
+/* 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)
{
struct snd_soc_tplg_vendor_uuid_elem *elem;
+ int found = 0;
int i, j;
/* parse element by element */
@@ -864,19 +823,26 @@
continue;
/* matched - now load token */
- tokens[j].get_token(elem, object, tokens[j].offset,
+ tokens[j].get_token(elem, object,
+ offset + tokens[j].offset,
tokens[j].size);
+
+ found++;
}
}
+
+ return found;
}
-static void 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)
+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)
{
struct snd_soc_tplg_vendor_string_elem *elem;
+ int found = 0;
int i, j;
/* parse element by element */
@@ -894,24 +860,27 @@
continue;
/* matched - now load token */
- tokens[j].get_token(elem, object, tokens[j].offset,
+ tokens[j].get_token(elem, object,
+ offset + tokens[j].offset,
tokens[j].size);
+
+ found++;
}
}
+
+ return found;
}
-static void 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)
+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)
{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct snd_soc_tplg_vendor_value_elem *elem;
- size_t size = sizeof(struct sof_ipc_dai_dmic_pdm_ctrl);
+ int found = 0;
int i, j;
- u32 offset;
- u32 *index = NULL;
/* parse element by element */
for (i = 0; i < le32_to_cpu(array->num_elems); i++) {
@@ -930,46 +899,98 @@
if (tokens[j].token != le32_to_cpu(elem->token))
continue;
- /* pdm config array index */
- if (sdev->private)
- index = sdev->private;
-
- /* matched - determine offset */
- switch (tokens[j].token) {
- case SOF_TKN_INTEL_DMIC_PDM_CTRL_ID:
-
- /* inc number of pdm array index */
- if (index)
- (*index)++;
- /* fallthrough */
- case SOF_TKN_INTEL_DMIC_PDM_MIC_A_Enable:
- case SOF_TKN_INTEL_DMIC_PDM_MIC_B_Enable:
- case SOF_TKN_INTEL_DMIC_PDM_POLARITY_A:
- case SOF_TKN_INTEL_DMIC_PDM_POLARITY_B:
- case SOF_TKN_INTEL_DMIC_PDM_CLK_EDGE:
- case SOF_TKN_INTEL_DMIC_PDM_SKEW:
-
- /* check if array index is valid */
- if (!index || *index == 0) {
- dev_err(sdev->dev,
- "error: invalid array offset\n");
- continue;
- } else {
- /* offset within the pdm config array */
- offset = size * (*index - 1);
- }
- break;
- default:
- offset = 0;
- break;
- }
-
/* load token */
tokens[j].get_token(elem, object,
offset + tokens[j].offset,
tokens[j].size);
+
+ found++;
}
}
+
+ return found;
+}
+
+/**
+ * sof_parse_token_sets - Parse multiple sets of tokens
+ * @scomp: pointer to soc component
+ * @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
+ * @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)
+{
+ size_t offset = 0;
+ int found = 0;
+ int total = 0;
+ int asize;
+
+ while (priv_size > 0 && total < count * sets) {
+ asize = le32_to_cpu(array->size);
+
+ /* validate asize */
+ if (asize < 0) { /* FIXME: A zero-size array makes no sense */
+ dev_err(scomp->dev, "error: invalid array size 0x%x\n",
+ asize);
+ return -EINVAL;
+ }
+
+ /* make sure there is enough data before parsing */
+ priv_size -= asize;
+ if (priv_size < 0) {
+ dev_err(scomp->dev, "error: invalid array size 0x%x\n",
+ asize);
+ return -EINVAL;
+ }
+
+ /* 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);
+ break;
+ case SND_SOC_TPLG_TUPLE_TYPE_STRING:
+ found += sof_parse_string_tokens(scomp, object, tokens,
+ count, array, offset);
+ 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);
+ break;
+ default:
+ dev_err(scomp->dev, "error: unknown token type %d\n",
+ array->type);
+ return -EINVAL;
+ }
+
+ /* next array */
+ array = (struct snd_soc_tplg_vendor_array *)((u8 *)array
+ + asize);
+
+ /* move to next target struct */
+ if (found >= count) {
+ offset += object_size;
+ total += found;
+ found = 0;
+ }
+ }
+
+ return 0;
}
static int sof_parse_tokens(struct snd_soc_component *scomp,
@@ -979,67 +1000,224 @@
struct snd_soc_tplg_vendor_array *array,
int priv_size)
{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- int asize;
-
- while (priv_size > 0) {
- asize = le32_to_cpu(array->size);
-
- /* validate asize */
- if (asize < 0) { /* FIXME: A zero-size array makes no sense */
- dev_err(sdev->dev, "error: invalid array size 0x%x\n",
- asize);
- return -EINVAL;
- }
-
- /* make sure there is enough data before parsing */
- priv_size -= asize;
- if (priv_size < 0) {
- dev_err(sdev->dev, "error: invalid array size 0x%x\n",
- asize);
- return -EINVAL;
- }
-
- /* call correct parser depending on type */
- switch (le32_to_cpu(array->type)) {
- case SND_SOC_TPLG_TUPLE_TYPE_UUID:
- sof_parse_uuid_tokens(scomp, object, tokens, count,
- array);
- break;
- case SND_SOC_TPLG_TUPLE_TYPE_STRING:
- sof_parse_string_tokens(scomp, object, tokens, count,
- array);
- 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:
- sof_parse_word_tokens(scomp, object, tokens, count,
- array);
- break;
- default:
- dev_err(sdev->dev, "error: unknown token type %d\n",
- array->type);
- return -EINVAL;
- }
-
- /* next array */
- array = (struct snd_soc_tplg_vendor_array *)((u8 *)array
- + asize);
- }
- return 0;
+ /*
+ * sof_parse_tokens is used when topology contains only a single set of
+ * identical tuples arrays. So additional parameters to
+ * 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)
{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
-
- dev_dbg(sdev->dev, " config: periods snk %d src %d fmt %d\n",
+ dev_dbg(scomp->dev, " config: periods snk %d src %d fmt %d\n",
config->periods_sink, config->periods_source,
config->frame_fmt);
}
+/*
+ * Standard Kcontrols.
+ */
+
+static int sof_control_load_volume(struct snd_soc_component *scomp,
+ struct snd_sof_control *scontrol,
+ struct snd_kcontrol_new *kc,
+ struct snd_soc_tplg_ctl_hdr *hdr)
+{
+ 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;
+
+ /* validate topology data */
+ if (le32_to_cpu(mc->num_channels) > SND_SOC_TPLG_MAX_CHAN) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* 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;
+ }
+
+ 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;
+ goto skip;
+ }
+
+ scontrol->cmd = SOF_CTRL_CMD_VOLUME;
+
+ /* extract tlv data */
+ if (get_tlv_data(kc->tlv.p, tlv) < 0) {
+ dev_err(scomp->dev, "error: invalid TLV data\n");
+ ret = -EINVAL;
+ goto out_free;
+ }
+
+ /* 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;
+ }
+
+skip:
+ /* set up possible led control from mixer private data */
+ ret = sof_parse_tokens(scomp, &scontrol->led_ctl, led_tokens,
+ ARRAY_SIZE(led_tokens), mc->priv.array,
+ le32_to_cpu(mc->priv.size));
+ if (ret != 0) {
+ dev_err(scomp->dev, "error: parse led tokens failed %d\n",
+ le32_to_cpu(mc->priv.size));
+ goto out_free_table;
+ }
+
+ dev_dbg(scomp->dev, "tplg: load kcontrol index %d chans %d\n",
+ scontrol->comp_id, scontrol->num_channels);
+
+ return 0;
+
+out_free_table:
+ if (le32_to_cpu(mc->max) > 1)
+ kfree(scontrol->volume_table);
+out_free:
+ kfree(scontrol->control_data);
+out:
+ return ret;
+}
+
+static int sof_control_load_enum(struct snd_soc_component *scomp,
+ struct snd_sof_control *scontrol,
+ struct snd_kcontrol_new *kc,
+ struct snd_soc_tplg_ctl_hdr *hdr)
+{
+ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
+ struct snd_soc_tplg_enum_control *ec =
+ container_of(hdr, struct snd_soc_tplg_enum_control, hdr);
+
+ /* validate topology data */
+ 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);
+
+ return 0;
+}
+
+static int sof_control_load_bytes(struct snd_soc_component *scomp,
+ struct snd_sof_control *scontrol,
+ struct snd_kcontrol_new *kc,
+ 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;
+ size_t max_size = sbe->max;
+ size_t priv_size = le32_to_cpu(control->priv.size);
+ int ret;
+
+ if (max_size < sizeof(struct sof_ipc_ctrl_data) ||
+ max_size < sizeof(struct sof_abi_hdr)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* init the get/put bytes data */
+ if (priv_size > max_size - sizeof(struct sof_ipc_ctrl_data)) {
+ dev_err(scomp->dev, "err: bytes data size %zu exceeds max %zu.\n",
+ priv_size, max_size - sizeof(struct sof_ipc_ctrl_data));
+ ret = -EINVAL;
+ goto out;
+ }
+
+ scontrol->size = sizeof(struct sof_ipc_ctrl_data) + priv_size;
+
+ scontrol->control_data = kzalloc(max_size, GFP_KERNEL);
+ cdata = scontrol->control_data;
+ if (!scontrol->control_data) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ 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);
+
+ if (le32_to_cpu(control->priv.size) > 0) {
+ memcpy(cdata->data, control->priv.data,
+ le32_to_cpu(control->priv.size));
+
+ 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;
+ }
+ }
+
+ return 0;
+
+out_free:
+ kfree(scontrol->control_data);
+out:
+ return ret;
+}
+
/* external kcontrol init - used for any driver specific init */
static int sof_control_load(struct snd_soc_component *scomp, int index,
struct snd_kcontrol_new *kc,
@@ -1051,16 +1229,16 @@
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(sdev->dev, "tplg: load control type %d name : %s\n",
+ dev_dbg(scomp->dev, "tplg: load control type %d name : %s\n",
hdr->type, hdr->name);
scontrol = kzalloc(sizeof(*scontrol), GFP_KERNEL);
if (!scontrol)
return -ENOMEM;
- scontrol->sdev = sdev;
+ scontrol->scomp = scomp;
switch (le32_to_cpu(hdr->ops.info)) {
case SND_SOC_TPLG_CTL_VOLSW:
@@ -1089,15 +1267,22 @@
case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE:
case SND_SOC_TPLG_DAPM_CTL_PIN:
default:
- dev_warn(sdev->dev, "control type not supported %d:%d:%d\n",
+ dev_warn(scomp->dev, "control type not supported %d:%d:%d\n",
hdr->ops.get, hdr->ops.put, hdr->ops.info);
kfree(scontrol);
return 0;
}
+ if (ret < 0) {
+ kfree(scontrol);
+ return ret;
+ }
+
+ scontrol->led_ctl.led_value = -1;
+
dobj->private = scontrol;
list_add(&scontrol->list, &sdev->kcontrol_list);
- return ret;
+ return 0;
}
static int sof_control_unload(struct snd_soc_component *scomp,
@@ -1107,7 +1292,7 @@
struct sof_ipc_free fcomp;
struct snd_sof_control *scontrol = dobj->private;
- dev_dbg(sdev->dev, "tplg: unload control name : %s\n", scomp->name);
+ dev_dbg(scomp->dev, "tplg: unload control name : %s\n", scomp->name);
fcomp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_FREE;
fcomp.hdr.size = sizeof(fcomp);
@@ -1126,17 +1311,85 @@
* DAI Topology
*/
+/* Static DSP core power management so far, should be extended in the future */
+static int sof_core_enable(struct snd_sof_dev *sdev, int core)
+{
+ struct sof_ipc_pm_core_config pm_core_config = {
+ .hdr = {
+ .cmd = SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CORE_ENABLE,
+ .size = sizeof(pm_core_config),
+ },
+ .enable_mask = sdev->enabled_cores_mask | BIT(core),
+ };
+ int ret;
+
+ if (sdev->enabled_cores_mask & BIT(core))
+ return 0;
+
+ /* power up the core if it is host managed */
+ ret = snd_sof_dsp_core_power_up(sdev, BIT(core));
+ if (ret < 0) {
+ dev_err(sdev->dev, "error: %d powering up core %d\n",
+ ret, core);
+ return ret;
+ }
+
+ /* Now notify DSP */
+ 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(sdev->dev, "error: core %d enable ipc failure %d\n",
+ core, ret);
+ goto err;
+ }
+
+ /* update enabled cores mask */
+ sdev->enabled_cores_mask |= BIT(core);
+
+ return ret;
+err:
+ /* power down core if it is host managed and return the original error if this fails too */
+ if (snd_sof_dsp_core_power_down(sdev, BIT(core)) < 0)
+ dev_err(sdev->dev, "error: powering down core %d\n", core);
+
+ return ret;
+}
+
+int sof_pipeline_core_enable(struct snd_sof_dev *sdev,
+ const struct snd_sof_widget *swidget)
+{
+ const struct sof_ipc_pipe_new *pipeline;
+ int ret;
+
+ if (swidget->id == snd_soc_dapm_scheduler) {
+ pipeline = swidget->private;
+ } else {
+ pipeline = snd_sof_pipeline_find(sdev, swidget->pipeline_id);
+ if (!pipeline)
+ return -ENOENT;
+ }
+
+ /* First enable the pipeline core */
+ ret = sof_core_enable(sdev, pipeline->core);
+ if (ret < 0)
+ return ret;
+
+ return sof_core_enable(sdev, swidget->core);
+}
+
static int sof_connect_dai_widget(struct snd_soc_component *scomp,
struct snd_soc_dapm_widget *w,
struct snd_soc_tplg_dapm_widget *tw,
struct snd_sof_dai *dai)
{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct snd_soc_card *card = scomp->card;
struct snd_soc_pcm_runtime *rtd;
+ struct snd_soc_dai *cpu_dai;
+ int i;
list_for_each_entry(rtd, &card->rtd_list, list) {
- dev_vdbg(sdev->dev, "tplg: check widget: %s stream: %s dai stream: %s\n",
+ 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 || !rtd->dai_link->stream_name)
@@ -1148,15 +1401,47 @@
switch (w->id) {
case snd_soc_dapm_dai_out:
- rtd->cpu_dai->capture_widget = w;
+ 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(sdev->dev, "tplg: connected widget %s -> DAI link %s\n",
+ 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:
- rtd->cpu_dai->playback_widget = w;
+ 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;
+ }
+ }
+ 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(sdev->dev, "tplg: connected widget %s -> DAI link %s\n",
+ dev_dbg(scomp->dev, "tplg: connected widget %s -> DAI link %s\n",
w->name, rtd->dai_link->name);
break;
default:
@@ -1166,7 +1451,7 @@
/* check we have a connection */
if (!dai->name) {
- dev_err(sdev->dev, "error: can't connect DAI %s stream %s\n",
+ dev_err(scomp->dev, "error: can't connect DAI %s stream %s\n",
w->name, w->sname);
return -EINVAL;
}
@@ -1174,6 +1459,49 @@
return 0;
}
+/**
+ * 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 struct sof_ipc_comp *sof_comp_alloc(struct snd_sof_widget *swidget,
+ size_t *ipc_size, int index)
+{
+ u8 nil_uuid[SOF_UUID_SIZE] = {0};
+ struct sof_ipc_comp *comp;
+ size_t total_size = *ipc_size;
+
+ /* only non-zero UUID is valid */
+ if (memcmp(&swidget->comp_ext, nil_uuid, SOF_UUID_SIZE))
+ total_size += sizeof(swidget->comp_ext);
+
+ 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->comp_ext, sizeof(swidget->comp_ext));
+ comp->ext_data_length = sizeof(swidget->comp_ext);
+ }
+
+ /* update ipc_size and return */
+ *ipc_size = total_size;
+ return comp;
+}
+
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,
@@ -1182,48 +1510,57 @@
{
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;
+ struct sof_ipc_comp_dai *comp_dai;
+ size_t ipc_size = sizeof(*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);
+ comp_dai = (struct sof_ipc_comp_dai *)
+ sof_comp_alloc(swidget, &ipc_size, index);
+ if (!comp_dai)
+ return -ENOMEM;
- ret = sof_parse_tokens(scomp, &comp_dai, dai_tokens,
+ /* configure dai IPC message */
+ comp_dai->comp.type = SOF_COMP_DAI;
+ 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(sdev->dev, "error: parse dai tokens failed %d\n",
+ dev_err(scomp->dev, "error: parse dai tokens failed %d\n",
le32_to_cpu(private->size));
- return ret;
+ goto finish;
}
- ret = sof_parse_tokens(scomp, &comp_dai.config, comp_tokens,
+ 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(sdev->dev, "error: parse dai.cfg tokens failed %d\n",
+ dev_err(scomp->dev, "error: parse dai.cfg tokens failed %d\n",
private->size);
- return ret;
+ goto finish;
}
- dev_dbg(sdev->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);
+ 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));
+ ret = sof_ipc_tx_message(sdev->ipc, comp_dai->comp.hdr.cmd,
+ comp_dai, ipc_size, r, sizeof(*r));
if (ret == 0 && dai) {
- dai->sdev = sdev;
- memcpy(&dai->comp_dai, &comp_dai, sizeof(comp_dai));
+ dai->scomp = scomp;
+
+ /*
+ * copy only the sof_ipc_comp_dai to avoid collapsing
+ * the snd_sof_dai, the extended data is kept in the
+ * snd_sof_widget.
+ */
+ memcpy(&dai->comp_dai, comp_dai, sizeof(*comp_dai));
}
+finish:
+ kfree(comp_dai);
return ret;
}
@@ -1251,18 +1588,19 @@
buffer->comp.id = swidget->comp_id;
buffer->comp.type = SOF_COMP_BUFFER;
buffer->comp.pipeline_id = index;
+ buffer->comp.core = swidget->core;
ret = sof_parse_tokens(scomp, buffer, buffer_tokens,
ARRAY_SIZE(buffer_tokens), private->array,
le32_to_cpu(private->size));
if (ret != 0) {
- dev_err(sdev->dev, "error: parse buffer tokens failed %d\n",
+ dev_err(scomp->dev, "error: parse buffer tokens failed %d\n",
private->size);
kfree(buffer);
return ret;
}
- dev_dbg(sdev->dev, "buffer %s: size %d caps 0x%x\n",
+ dev_dbg(scomp->dev, "buffer %s: size %d caps 0x%x\n",
swidget->widget->name, buffer->size, buffer->caps);
swidget->private = buffer;
@@ -1270,7 +1608,7 @@
ret = sof_ipc_tx_message(sdev->ipc, buffer->comp.hdr.cmd, buffer,
sizeof(*buffer), r, sizeof(*r));
if (ret < 0) {
- dev_err(sdev->dev, "error: buffer %s load failed\n",
+ dev_err(scomp->dev, "error: buffer %s load failed\n",
swidget->widget->name);
kfree(buffer);
}
@@ -1279,16 +1617,16 @@
}
/* bind PCM ID to host component ID */
-static int spcm_bind(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm,
+static int spcm_bind(struct snd_soc_component *scomp, struct snd_sof_pcm *spcm,
int dir)
{
struct snd_sof_widget *host_widget;
- host_widget = snd_sof_find_swidget_sname(sdev,
+ host_widget = snd_sof_find_swidget_sname(scomp,
spcm->pcm.caps[dir].name,
dir);
if (!host_widget) {
- dev_err(sdev->dev, "can't find host comp to bind pcm\n");
+ dev_err(scomp->dev, "can't find host comp to bind pcm\n");
return -EINVAL;
}
@@ -1310,18 +1648,16 @@
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;
+ size_t ipc_size = sizeof(*host);
int ret;
- host = kzalloc(sizeof(*host), GFP_KERNEL);
+ host = (struct sof_ipc_comp_host *)
+ sof_comp_alloc(swidget, &ipc_size, index);
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);
@@ -1329,7 +1665,7 @@
ARRAY_SIZE(pcm_tokens), private->array,
le32_to_cpu(private->size));
if (ret != 0) {
- dev_err(sdev->dev, "error: parse host tokens failed %d\n",
+ dev_err(scomp->dev, "error: parse host tokens failed %d\n",
private->size);
goto err;
}
@@ -1338,18 +1674,18 @@
ARRAY_SIZE(comp_tokens), private->array,
le32_to_cpu(private->size));
if (ret != 0) {
- dev_err(sdev->dev, "error: parse host.cfg tokens failed %d\n",
+ dev_err(scomp->dev, "error: parse host.cfg tokens failed %d\n",
le32_to_cpu(private->size));
goto err;
}
- dev_dbg(sdev->dev, "loaded host %s\n", swidget->widget->name);
+ 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));
+ ipc_size, r, sizeof(*r));
if (ret >= 0)
return ret;
err:
@@ -1360,58 +1696,29 @@
/*
* Pipeline Topology
*/
-int sof_load_pipeline_ipc(struct snd_sof_dev *sdev,
+int sof_load_pipeline_ipc(struct device *dev,
struct sof_ipc_pipe_new *pipeline,
struct sof_ipc_comp_reply *r)
{
- struct sof_ipc_pm_core_config pm_core_config;
- int ret;
+ struct snd_sof_dev *sdev = dev_get_drvdata(dev);
+ int ret = sof_core_enable(sdev, pipeline->core);
+
+ if (ret < 0)
+ return ret;
ret = sof_ipc_tx_message(sdev->ipc, pipeline->hdr.cmd, pipeline,
sizeof(*pipeline), r, sizeof(*r));
- if (ret < 0) {
- dev_err(sdev->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(sdev->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(sdev->dev, "error: core enable ipc failure\n");
+ dev_err(dev, "error: load pipeline ipc failure\n");
return ret;
}
-static int sof_widget_load_pipeline(struct snd_soc_component *scomp,
- int index, struct snd_sof_widget *swidget,
+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)
{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct snd_soc_tplg_private *private = &tw->priv;
struct sof_ipc_pipe_new *pipeline;
struct snd_sof_widget *comp_swidget;
@@ -1428,9 +1735,9 @@
pipeline->comp_id = swidget->comp_id;
/* component at start of pipeline is our stream id */
- comp_swidget = snd_sof_find_swidget(sdev, tw->sname);
+ comp_swidget = snd_sof_find_swidget(scomp, tw->sname);
if (!comp_swidget) {
- dev_err(sdev->dev, "error: widget %s refers to non existent widget %s\n",
+ dev_err(scomp->dev, "error: widget %s refers to non existent widget %s\n",
tw->name, tw->sname);
ret = -EINVAL;
goto err;
@@ -1438,26 +1745,26 @@
pipeline->sched_id = comp_swidget->comp_id;
- dev_dbg(sdev->dev, "tplg: pipeline id %d comp %d scheduling comp id %d\n",
+ 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(sdev->dev, "error: parse pipeline tokens failed %d\n",
+ dev_err(scomp->dev, "error: parse pipeline tokens failed %d\n",
private->size);
goto err;
}
- dev_dbg(sdev->dev, "pipeline %s: period %d pri %d mips %d core %d frames %d\n",
+ 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(sdev, pipeline, r);
+ ret = sof_load_pipeline_ipc(scomp->dev, pipeline, r);
if (ret >= 0)
return ret;
err:
@@ -1477,25 +1784,23 @@
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;
+ size_t ipc_size = sizeof(*mixer);
int ret;
- mixer = kzalloc(sizeof(*mixer), GFP_KERNEL);
+ mixer = (struct sof_ipc_comp_mixer *)
+ sof_comp_alloc(swidget, &ipc_size, index);
if (!mixer)
return -ENOMEM;
/* 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);
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(sdev->dev, "error: parse mixer.cfg tokens failed %d\n",
+ dev_err(scomp->dev, "error: parse mixer.cfg tokens failed %d\n",
private->size);
kfree(mixer);
return ret;
@@ -1506,7 +1811,7 @@
swidget->private = mixer;
ret = sof_ipc_tx_message(sdev->ipc, mixer->comp.hdr.cmd, mixer,
- sizeof(*mixer), r, sizeof(*r));
+ ipc_size, r, sizeof(*r));
if (ret < 0)
kfree(mixer);
@@ -1524,25 +1829,23 @@
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct snd_soc_tplg_private *private = &tw->priv;
struct sof_ipc_comp_mux *mux;
+ size_t ipc_size = sizeof(*mux);
int ret;
- mux = kzalloc(sizeof(*mux), GFP_KERNEL);
+ mux = (struct sof_ipc_comp_mux *)
+ sof_comp_alloc(swidget, &ipc_size, index);
if (!mux)
return -ENOMEM;
/* 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);
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(sdev->dev, "error: parse mux.cfg tokens failed %d\n",
+ dev_err(scomp->dev, "error: parse mux.cfg tokens failed %d\n",
private->size);
kfree(mux);
return ret;
@@ -1553,7 +1856,7 @@
swidget->private = mux;
ret = sof_ipc_tx_message(sdev->ipc, mux->comp.hdr.cmd, mux,
- sizeof(*mux), r, sizeof(*r));
+ ipc_size, r, sizeof(*r));
if (ret < 0)
kfree(mux);
@@ -1573,34 +1876,32 @@
struct snd_soc_tplg_private *private = &tw->priv;
struct sof_ipc_comp_volume *volume;
struct snd_sof_control *scontrol;
+ size_t ipc_size = sizeof(*volume);
int min_step;
int max_step;
int ret;
- volume = kzalloc(sizeof(*volume), GFP_KERNEL);
+ volume = (struct sof_ipc_comp_volume *)
+ sof_comp_alloc(swidget, &ipc_size, index);
if (!volume)
return -ENOMEM;
- if (le32_to_cpu(tw->num_kcontrols) != 1) {
- dev_err(sdev->dev, "error: invalid kcontrol count %d for volume\n",
+ 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);
ret = sof_parse_tokens(scomp, volume, volume_tokens,
ARRAY_SIZE(volume_tokens), private->array,
le32_to_cpu(private->size));
if (ret != 0) {
- dev_err(sdev->dev, "error: parse volume tokens failed %d\n",
+ dev_err(scomp->dev, "error: parse volume tokens failed %d\n",
private->size);
goto err;
}
@@ -1608,7 +1909,7 @@
ARRAY_SIZE(comp_tokens), private->array,
le32_to_cpu(private->size));
if (ret != 0) {
- dev_err(sdev->dev, "error: parse volume.cfg tokens failed %d\n",
+ dev_err(scomp->dev, "error: parse volume.cfg tokens failed %d\n",
le32_to_cpu(private->size));
goto err;
}
@@ -1618,7 +1919,8 @@
swidget->private = volume;
list_for_each_entry(scontrol, &sdev->kcontrol_list, list) {
- if (scontrol->comp_id == swidget->comp_id) {
+ 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];
@@ -1629,7 +1931,7 @@
}
ret = sof_ipc_tx_message(sdev->ipc, volume->comp.hdr.cmd, volume,
- sizeof(*volume), r, sizeof(*r));
+ ipc_size, r, sizeof(*r));
if (ret >= 0)
return ret;
err:
@@ -1649,25 +1951,23 @@
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;
+ size_t ipc_size = sizeof(*src);
int ret;
- src = kzalloc(sizeof(*src), GFP_KERNEL);
+ src = (struct sof_ipc_comp_src *)
+ sof_comp_alloc(swidget, &ipc_size, index);
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(sdev->dev, "error: parse src tokens failed %d\n",
+ dev_err(scomp->dev, "error: parse src tokens failed %d\n",
private->size);
goto err;
}
@@ -1676,19 +1976,19 @@
ARRAY_SIZE(comp_tokens), private->array,
le32_to_cpu(private->size));
if (ret != 0) {
- dev_err(sdev->dev, "error: parse src.cfg tokens failed %d\n",
+ dev_err(scomp->dev, "error: parse src.cfg tokens failed %d\n",
le32_to_cpu(private->size));
goto err;
}
- dev_dbg(sdev->dev, "src %s: source rate %d sink rate %d\n",
+ 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));
+ ipc_size, r, sizeof(*r));
if (ret >= 0)
return ret;
err:
@@ -1697,6 +1997,65 @@
}
/*
+ * 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;
+ size_t ipc_size = sizeof(*asrc);
+ int ret;
+
+ asrc = (struct sof_ipc_comp_asrc *)
+ sof_comp_alloc(swidget, &ipc_size, index);
+ if (!asrc)
+ return -ENOMEM;
+
+ /* configure ASRC IPC message */
+ asrc->comp.type = SOF_COMP_ASRC;
+ 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,
+ ipc_size, r, sizeof(*r));
+ if (ret >= 0)
+ return ret;
+err:
+ kfree(asrc);
+ return ret;
+}
+
+/*
* Signal Generator Topology
*/
@@ -1708,25 +2067,23 @@
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;
+ size_t ipc_size = sizeof(*tone);
int ret;
- tone = kzalloc(sizeof(*tone), GFP_KERNEL);
+ tone = (struct sof_ipc_comp_tone *)
+ sof_comp_alloc(swidget, &ipc_size, index);
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(sdev->dev, "error: parse tone tokens failed %d\n",
+ dev_err(scomp->dev, "error: parse tone tokens failed %d\n",
le32_to_cpu(private->size));
goto err;
}
@@ -1735,19 +2092,19 @@
ARRAY_SIZE(comp_tokens), private->array,
le32_to_cpu(private->size));
if (ret != 0) {
- dev_err(sdev->dev, "error: parse tone.cfg tokens failed %d\n",
+ dev_err(scomp->dev, "error: parse tone.cfg tokens failed %d\n",
le32_to_cpu(private->size));
goto err;
}
- dev_dbg(sdev->dev, "tone %s: frequency %d amplitude %d\n",
+ 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));
+ ipc_size, r, sizeof(*r));
if (ret >= 0)
return ret;
err:
@@ -1755,7 +2112,7 @@
return ret;
}
-static int sof_get_control_data(struct snd_sof_dev *sdev,
+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)
@@ -1785,14 +2142,14 @@
wdata[i].control = se->dobj.private;
break;
default:
- dev_err(sdev->dev, "error: unknown kcontrol type %d in widget %s\n",
+ 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(sdev->dev, "error: no scontrol for widget %s\n",
+ dev_err(scomp->dev, "error: no scontrol for widget %s\n",
widget->name);
return -EINVAL;
}
@@ -1836,20 +2193,14 @@
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_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 = 0;
+ int ret;
int i;
- if (type == SOF_COMP_NONE) {
- dev_err(sdev->dev, "error: invalid process comp type %d\n",
- type);
- return -EINVAL;
- }
-
/* allocate struct for widget control data sizes and types */
if (widget->num_kcontrols) {
wdata = kcalloc(widget->num_kcontrols,
@@ -1860,16 +2211,14 @@
return -ENOMEM;
/* get possible component controls and get size of all pdata */
- ret = sof_get_control_data(sdev, widget, wdata,
+ ret = sof_get_control_data(scomp, widget, wdata,
&ipc_data_size);
if (ret < 0)
goto out;
}
- ipc_size = sizeof(struct sof_ipc_comp_process) +
- le32_to_cpu(private->size) +
- ipc_data_size;
+ 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) {
@@ -1877,25 +2226,22 @@
ipc_data_size = 0;
}
- process = kzalloc(ipc_size, GFP_KERNEL);
+ process = (struct sof_ipc_comp_process *)
+ sof_comp_alloc(swidget, &ipc_size, index);
if (!process) {
ret = -ENOMEM;
goto out;
}
/* 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(sdev->dev, "error: parse process.cfg tokens failed %d\n",
+ dev_err(scomp->dev, "error: parse process.cfg tokens failed %d\n",
le32_to_cpu(private->size));
goto err;
}
@@ -1923,7 +2269,7 @@
ipc_size, r, sizeof(*r));
if (ret < 0) {
- dev_err(sdev->dev, "error: create process failed\n");
+ dev_err(scomp->dev, "error: create process failed\n");
goto err;
}
@@ -1934,13 +2280,13 @@
/* 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(sdev->ipc, wdata[i].control,
+ 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(sdev->dev, "error: send control failed\n");
+ dev_err(scomp->dev, "error: send control failed\n");
break;
}
}
@@ -1963,25 +2309,25 @@
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_process config;
int ret;
/* check we have some tokens - we need at least process type */
if (le32_to_cpu(private->size) == 0) {
- dev_err(sdev->dev, "error: process tokens not found\n");
+ dev_err(scomp->dev, "error: process tokens not found\n");
return -EINVAL;
}
memset(&config, 0, sizeof(config));
+ config.comp.core = swidget->core;
/* 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(sdev->dev, "error: parse process tokens failed %d\n",
+ dev_err(scomp->dev, "error: parse process tokens failed %d\n",
le32_to_cpu(private->size));
return ret;
}
@@ -1990,14 +2336,14 @@
ret = sof_process_load(scomp, index, swidget, tw, r,
find_process_comp_type(config.type));
if (ret < 0) {
- dev_err(sdev->dev, "error: process loading failed\n");
+ dev_err(scomp->dev, "error: process loading failed\n");
return ret;
}
return 0;
}
-static int sof_widget_bind_event(struct snd_sof_dev *sdev,
+static int sof_widget_bind_event(struct snd_soc_component *scomp,
struct snd_sof_widget *swidget,
u16 event_type)
{
@@ -2023,7 +2369,7 @@
break;
}
- dev_err(sdev->dev,
+ dev_err(scomp->dev,
"error: invalid event type %d for widget %s\n",
event_type, swidget->widget->name);
return -EINVAL;
@@ -2039,13 +2385,16 @@
struct snd_sof_dai *dai;
struct sof_ipc_comp_reply reply;
struct snd_sof_control *scontrol;
+ struct sof_ipc_comp comp = {
+ .core = SOF_DSP_PRIMARY_CORE,
+ };
int ret = 0;
swidget = kzalloc(sizeof(*swidget), GFP_KERNEL);
if (!swidget)
return -ENOMEM;
- swidget->sdev = sdev;
+ swidget->scomp = scomp;
swidget->widget = w;
swidget->comp_id = sdev->next_comp_id++;
swidget->complete = 0;
@@ -2054,11 +2403,41 @@
swidget->private = NULL;
memset(&reply, 0, sizeof(reply));
- dev_dbg(sdev->dev, "tplg: ready widget id %d pipe %d type %d name : %s stream %s\n",
+ 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");
+ ret = sof_parse_tokens(scomp, &comp, core_tokens,
+ ARRAY_SIZE(core_tokens), tw->priv.array,
+ le32_to_cpu(tw->priv.size));
+ if (ret != 0) {
+ dev_err(scomp->dev, "error: parsing core tokens failed %d\n",
+ ret);
+ kfree(swidget);
+ return ret;
+ }
+
+ swidget->core = comp.core;
+
+ /* default is primary core, safe to call for already enabled cores */
+ ret = sof_core_enable(sdev, comp.core);
+ if (ret < 0) {
+ dev_err(scomp->dev, "error: enable core: %d\n", ret);
+ kfree(swidget);
+ return ret;
+ }
+
+ ret = sof_parse_tokens(scomp, &swidget->comp_ext, comp_ext_tokens,
+ ARRAY_SIZE(comp_ext_tokens), tw->priv.array,
+ le32_to_cpu(tw->priv.size));
+ if (ret != 0) {
+ dev_err(scomp->dev, "error: parsing comp_ext_tokens failed %d\n",
+ ret);
+ kfree(swidget);
+ return ret;
+ }
+
/* handle any special case widgets */
switch (w->id) {
case snd_soc_dapm_dai_in:
@@ -2069,8 +2448,7 @@
return -ENOMEM;
}
- ret = sof_widget_load_dai(scomp, index, swidget, tw, &reply,
- dai);
+ 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);
@@ -2096,8 +2474,7 @@
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);
+ 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,
@@ -2110,12 +2487,14 @@
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);
+ ret = sof_widget_load_process(scomp, index, swidget, tw, &reply);
break;
case snd_soc_dapm_mux:
case snd_soc_dapm_demux:
@@ -2125,14 +2504,13 @@
case snd_soc_dapm_dai_link:
case snd_soc_dapm_kcontrol:
default:
- dev_warn(sdev->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) {
- dev_err(sdev->dev,
+ dev_err(scomp->dev,
"error: DSP failed to add widget id %d type %d name : %s stream %s reply %d\n",
tw->shift, swidget->id, tw->name,
strnlen(tw->sname, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) > 0
@@ -2143,10 +2521,10 @@
/* bind widget to external event */
if (tw->event_type) {
- ret = sof_widget_bind_event(sdev, swidget,
+ ret = sof_widget_bind_event(scomp, swidget,
le16_to_cpu(tw->event_type));
if (ret) {
- dev_err(sdev->dev, "error: widget event binding failed\n");
+ dev_err(scomp->dev, "error: widget event binding failed\n");
kfree(swidget->private);
kfree(swidget);
return ret;
@@ -2212,9 +2590,18 @@
/* power down the pipeline schedule core */
pipeline = swidget->private;
+
+ /*
+ * Runtime PM should still function normally if topology loading fails and
+ * it's components are unloaded. Do not power down the primary core so that the
+ * CTX_SAVE IPC can succeed during runtime suspend.
+ */
+ if (pipeline->core == SOF_DSP_PRIMARY_CORE)
+ break;
+
ret = snd_sof_dsp_core_power_down(sdev, 1 << pipeline->core);
if (ret < 0)
- dev_err(sdev->dev, "error: powering down pipeline schedule core %d\n",
+ dev_err(scomp->dev, "error: powering down pipeline schedule core %d\n",
pipeline->core);
/* update enabled cores mask */
@@ -2242,7 +2629,7 @@
scontrol = sbe->dobj.private;
break;
default:
- dev_warn(sdev->dev, "unsupported kcontrol_type\n");
+ dev_warn(scomp->dev, "unsupported kcontrol_type\n");
goto out;
}
kfree(scontrol->control_data);
@@ -2272,9 +2659,10 @@
{
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct snd_soc_tplg_stream_caps *caps;
+ struct snd_soc_tplg_private *private = &pcm->priv;
struct snd_sof_pcm *spcm;
- int stream = SNDRV_PCM_STREAM_PLAYBACK;
- int ret = 0;
+ int stream;
+ int ret;
/* nothing to do for BEs atm */
if (!pcm)
@@ -2284,37 +2672,54 @@
if (!spcm)
return -ENOMEM;
- spcm->sdev = sdev;
- spcm->stream[SNDRV_PCM_STREAM_PLAYBACK].comp_id = COMP_ID_UNASSIGNED;
- spcm->stream[SNDRV_PCM_STREAM_CAPTURE].comp_id = COMP_ID_UNASSIGNED;
+ spcm->scomp = scomp;
- if (pcm) {
- spcm->pcm = *pcm;
- dev_dbg(sdev->dev, "tplg: load pcm %s\n", pcm->dai_name);
+ 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);
}
+
+ spcm->pcm = *pcm;
+ dev_dbg(scomp->dev, "tplg: load pcm %s\n", pcm->dai_name);
+
dai_drv->dobj.private = spcm;
list_add(&spcm->list, &sdev->pcm_list);
+ ret = sof_parse_tokens(scomp, spcm, stream_tokens,
+ ARRAY_SIZE(stream_tokens), private->array,
+ le32_to_cpu(private->size));
+ if (ret) {
+ dev_err(scomp->dev, "error: parse stream tokens failed %d\n",
+ le32_to_cpu(private->size));
+ return ret;
+ }
+
/* do we need to allocate playback PCM DMA pages */
if (!spcm->pcm.playback)
goto capture;
+ 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 */
ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->dev,
PAGE_SIZE, &spcm->stream[stream].page_table);
if (ret < 0) {
- dev_err(sdev->dev, "error: can't alloc page table for %s %d\n",
+ dev_err(scomp->dev, "error: can't alloc page table for %s %d\n",
caps->name, ret);
return ret;
}
/* bind pcm to host comp */
- ret = spcm_bind(sdev, spcm, stream);
+ ret = spcm_bind(scomp, spcm, stream);
if (ret) {
- dev_err(sdev->dev,
+ dev_err(scomp->dev,
"error: can't bind pcm to host\n");
goto free_playback_tables;
}
@@ -2326,21 +2731,24 @@
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 */
ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->dev,
PAGE_SIZE, &spcm->stream[stream].page_table);
if (ret < 0) {
- dev_err(sdev->dev, "error: can't alloc page table for %s %d\n",
+ dev_err(scomp->dev, "error: can't alloc page table for %s %d\n",
caps->name, ret);
goto free_playback_tables;
}
/* bind pcm to host comp */
- ret = spcm_bind(sdev, spcm, stream);
+ ret = spcm_bind(scomp, spcm, stream);
if (ret) {
- dev_err(sdev->dev,
+ dev_err(scomp->dev,
"error: can't bind pcm to host\n");
snd_dma_free_pages(&spcm->stream[stream].page_table);
goto free_playback_tables;
@@ -2406,7 +2814,11 @@
}
}
-/* set config for all DAI's with name matching the link name */
+/*
+ * 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)
@@ -2419,6 +2831,27 @@
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;
@@ -2451,7 +2884,6 @@
{
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct snd_soc_tplg_private *private = &cfg->priv;
- struct sof_ipc_reply reply;
u32 size = sizeof(*config);
int ret;
@@ -2466,7 +2898,7 @@
ARRAY_SIZE(ssp_tokens), private->array,
le32_to_cpu(private->size));
if (ret != 0) {
- dev_err(sdev->dev, "error: parse ssp tokens failed %d\n",
+ dev_err(scomp->dev, "error: parse ssp tokens failed %d\n",
le32_to_cpu(private->size));
return ret;
}
@@ -2480,7 +2912,7 @@
config->ssp.rx_slots = le32_to_cpu(hw_config->rx_slots);
config->ssp.tx_slots = le32_to_cpu(hw_config->tx_slots);
- dev_dbg(sdev->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",
+ 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,
@@ -2489,32 +2921,21 @@
/* validate SSP fsync rate and channel count */
if (config->ssp.fsync_rate < 8000 || config->ssp.fsync_rate > 192000) {
- dev_err(sdev->dev, "error: invalid fsync rate for SSP%d\n",
+ 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(sdev->dev, "error: invalid channel count for SSP%d\n",
+ dev_err(scomp->dev, "error: invalid channel count for SSP%d\n",
config->dai_index);
return -EINVAL;
}
- /* 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 SSP%d\n",
- config->dai_index);
- return ret;
- }
-
/* 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(sdev->dev, "error: failed to save DAI config for SSP%d\n",
+ dev_err(scomp->dev, "error: failed to save DAI config for SSP%d\n",
config->dai_index);
return ret;
@@ -2526,8 +2947,56 @@
struct snd_soc_tplg_hw_config *hw_config,
struct sof_ipc_dai_config *config)
{
- /*TODO: Add implementation */
- return 0;
+ 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,
@@ -2536,8 +3005,55 @@
struct snd_soc_tplg_hw_config *hw_config,
struct sof_ipc_dai_config *config)
{
- /*TODO: Add implementation */
- return 0;
+ 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,
@@ -2548,18 +3064,12 @@
{
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct snd_soc_tplg_private *private = &cfg->priv;
- struct sof_ipc_dai_config *ipc_config;
- struct sof_ipc_reply reply;
struct sof_ipc_fw_ready *ready = &sdev->fw_ready;
struct sof_ipc_fw_version *v = &ready->version;
- u32 size;
+ size_t size = sizeof(*config);
int ret, j;
- /*
- * config is only used for the common params in dmic_params structure
- * that does not include the PDM controller config array
- * Set the common params to 0.
- */
+ /* Ensure the entire DMIC config struct is zeros */
memset(&config->dmic, 0, sizeof(struct sof_ipc_dai_dmic_params));
/* get DMIC tokens */
@@ -2567,164 +3077,67 @@
ARRAY_SIZE(dmic_tokens), private->array,
le32_to_cpu(private->size));
if (ret != 0) {
- dev_err(sdev->dev, "error: parse dmic tokens failed %d\n",
+ dev_err(scomp->dev, "error: parse dmic tokens failed %d\n",
le32_to_cpu(private->size));
return ret;
}
- /*
- * allocate memory for dmic dai config accounting for the
- * variable number of active pdm controllers
- * This will be the ipc payload for setting dai config
- */
- size = sizeof(*config) + sizeof(struct sof_ipc_dai_dmic_pdm_ctrl) *
- config->dmic.num_pdm_active;
-
- ipc_config = kzalloc(size, GFP_KERNEL);
- if (!ipc_config)
- return -ENOMEM;
-
- /* copy the common dai config and dmic params */
- memcpy(ipc_config, config, sizeof(*config));
-
- /*
- * 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) {
- kfree(ipc_config);
- return -ENOMEM;
- }
-
/* get DMIC PDM tokens */
- ret = sof_parse_tokens(scomp, &ipc_config->dmic.pdm[0], 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));
+ le32_to_cpu(private->size),
+ config->dmic.num_pdm_active,
+ sizeof(struct sof_ipc_dai_dmic_pdm_ctrl));
+
if (ret != 0) {
- dev_err(sdev->dev, "error: parse dmic pdm tokens failed %d\n",
+ dev_err(scomp->dev, "error: parse dmic pdm tokens failed %d\n",
le32_to_cpu(private->size));
- goto err;
+ return ret;
}
/* set IPC header size */
- ipc_config->hdr.size = size;
+ config->hdr.size = size;
/* debug messages */
- dev_dbg(sdev->dev, "tplg: config DMIC%d driver version %d\n",
- ipc_config->dai_index, ipc_config->dmic.driver_ipc_version);
- dev_dbg(sdev->dev, "pdmclk_min %d pdm_clkmax %d duty_min %hd\n",
- ipc_config->dmic.pdmclk_min, ipc_config->dmic.pdmclk_max,
- ipc_config->dmic.duty_min);
- dev_dbg(sdev->dev, "duty_max %hd fifo_fs %d num_pdms active %d\n",
- ipc_config->dmic.duty_max, ipc_config->dmic.fifo_fs,
- ipc_config->dmic.num_pdm_active);
- dev_dbg(sdev->dev, "fifo word length %hd\n",
- ipc_config->dmic.fifo_bits);
+ 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 < ipc_config->dmic.num_pdm_active; j++) {
- dev_dbg(sdev->dev, "pdm %hd mic a %hd mic b %hd\n",
- ipc_config->dmic.pdm[j].id,
- ipc_config->dmic.pdm[j].enable_mic_a,
- ipc_config->dmic.pdm[j].enable_mic_b);
- dev_dbg(sdev->dev, "pdm %hd polarity a %hd polarity b %hd\n",
- ipc_config->dmic.pdm[j].id,
- ipc_config->dmic.pdm[j].polarity_mic_a,
- ipc_config->dmic.pdm[j].polarity_mic_b);
- dev_dbg(sdev->dev, "pdm %hd clk_edge %hd skew %hd\n",
- ipc_config->dmic.pdm[j].id,
- ipc_config->dmic.pdm[j].clk_edge,
- ipc_config->dmic.pdm[j].skew);
- }
-
- if (SOF_ABI_VER(v->major, v->minor, v->micro) < SOF_ABI_VER(3, 0, 1)) {
- /* this takes care of backwards compatible handling of fifo_bits_b */
- ipc_config->dmic.reserved_2 = ipc_config->dmic.fifo_bits;
- }
-
- /* send message to DSP */
- ret = sof_ipc_tx_message(sdev->ipc,
- ipc_config->hdr.cmd, ipc_config, size, &reply,
- sizeof(reply));
-
- if (ret < 0) {
- dev_err(sdev->dev,
- "error: failed to set DAI config for DMIC%d\n",
- config->dai_index);
- goto err;
- }
-
- /* set config for all DAI's with name matching the link name */
- ret = sof_set_dai_config(sdev, size, link, ipc_config);
- if (ret < 0)
- dev_err(sdev->dev, "error: failed to save DAI config for DMIC%d\n",
- config->dai_index);
-
-err:
- kfree(sdev->private);
- kfree(ipc_config);
-
- return ret;
-}
-
-/*
- * for hda link, playback and capture are supported by different dai
- * in FW. Here get the dai_index, set dma channel of each dai
- * and send config to FW. In FW, each dai sets config by dai_index
- */
-static int sof_link_hda_process(struct snd_sof_dev *sdev,
- struct snd_soc_dai_link *link,
- struct sof_ipc_dai_config *config)
-{
- struct sof_ipc_reply reply;
- u32 size = sizeof(*config);
- struct snd_sof_dai *sof_dai;
- int found = 0;
- int ret;
-
- list_for_each_entry(sof_dai, &sdev->dai_list, list) {
- if (!sof_dai->name)
- continue;
-
- if (strcmp(link->name, sof_dai->name) == 0) {
- config->dai_index = sof_dai->comp_dai.dai_index;
- found = 1;
-
- config->hda.link_dma_ch = DMA_CHAN_INVALID;
-
- /* save config in dai component */
- sof_dai->dai_config = kmemdup(config, size, GFP_KERNEL);
- if (!sof_dai->dai_config)
- return -ENOMEM;
-
- sof_dai->cpu_dai_name = link->cpus->dai_name;
-
- /* 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 direction:%d of HDA dai %d\n",
- sof_dai->comp_dai.direction,
- config->dai_index);
-
- return ret;
- }
- }
+ 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);
}
/*
- * 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
+ * this takes care of backwards compatible handling of fifo_bits_b.
+ * It is deprecated since firmware ABI version 3.0.1.
*/
- if (!found) {
- dev_warn(sdev->dev, "warning: failed to find dai for dai link %s",
- link->name);
- }
+ 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;
- return 0;
+ /* 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);
+
+ return ret;
}
static int sof_link_hda_load(struct snd_soc_component *scomp, int index,
@@ -2744,25 +3157,30 @@
config->hdr.size = size;
/* get any bespoke DAI tokens */
- ret = sof_parse_tokens(scomp, config, hda_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(sdev->dev, "error: parse hda tokens failed %d\n",
+ 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(sdev->dev, "error: failed to find dai %s in %s",
+ dev_err(scomp->dev, "error: failed to find dai %s in %s",
link->cpus->dai_name, __func__);
return -EINVAL;
}
- ret = sof_link_hda_process(sdev, link, config);
+ config->hda.link_dma_ch = DMA_CHAN_INVALID;
+
+ ret = sof_set_dai_config(sdev, size, link, config);
if (ret < 0)
- dev_err(sdev->dev, "error: failed to process hda dai link %s",
+ dev_err(scomp->dev, "error: failed to process hda dai link %s",
link->name);
return ret;
@@ -2775,28 +3193,26 @@
struct sof_ipc_dai_config *config)
{
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
- struct sof_ipc_reply reply;
+ 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;
- /* 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 ALH %d\n",
- config->dai_index);
- return ret;
- }
-
/* 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(sdev->dev, "error: failed to save DAI config for ALH %d\n",
+ dev_err(scomp->dev, "error: failed to save DAI config for ALH %d\n",
config->dai_index);
return ret;
@@ -2807,7 +3223,6 @@
struct snd_soc_dai_link *link,
struct snd_soc_tplg_link_config *cfg)
{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct snd_soc_tplg_private *private = &cfg->priv;
struct sof_ipc_dai_config config;
struct snd_soc_tplg_hw_config *hw_config;
@@ -2816,10 +3231,10 @@
int i = 0;
if (!link->platforms) {
- dev_err(sdev->dev, "error: no platforms\n");
+ dev_err(scomp->dev, "error: no platforms\n");
return -EINVAL;
}
- link->platforms->name = dev_name(sdev->dev);
+ link->platforms->name = dev_name(scomp->dev);
/*
* Set nonatomic property for FE dai links as their trigger action
@@ -2828,9 +3243,17 @@
if (!link->no_pcm) {
link->nonatomic = true;
- /* set trigger order */
- link->trigger[0] = SND_SOC_DPCM_TRIGGER_POST;
- link->trigger[1] = SND_SOC_DPCM_TRIGGER_POST;
+ /*
+ * 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;
@@ -2838,7 +3261,7 @@
/* check we have some tokens - we need at least DAI type */
if (le32_to_cpu(private->size) == 0) {
- dev_err(sdev->dev, "error: expected tokens for DAI, none found\n");
+ dev_err(scomp->dev, "error: expected tokens for DAI, none found\n");
return -EINVAL;
}
@@ -2850,7 +3273,7 @@
ARRAY_SIZE(dai_link_tokens), private->array,
le32_to_cpu(private->size));
if (ret != 0) {
- dev_err(sdev->dev, "error: parse link tokens failed %d\n",
+ dev_err(scomp->dev, "error: parse link tokens failed %d\n",
le32_to_cpu(private->size));
return ret;
}
@@ -2862,12 +3285,12 @@
num_hw_configs = le32_to_cpu(cfg->num_hw_configs);
if (!num_hw_configs) {
if (config.type != SOF_DAI_INTEL_HDA) {
- dev_err(sdev->dev, "error: unexpected DAI config count %d!\n",
+ dev_err(scomp->dev, "error: unexpected DAI config count %d!\n",
le32_to_cpu(cfg->num_hw_configs));
return -EINVAL;
}
} else {
- dev_dbg(sdev->dev, "tplg: %d hw_configs found, default id: %d!\n",
+ 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));
for (i = 0; i < num_hw_configs; i++) {
@@ -2876,7 +3299,7 @@
}
if (i == num_hw_configs) {
- dev_err(sdev->dev, "error: default hw_config id: %d not found!\n",
+ dev_err(scomp->dev, "error: default hw_config id: %d not found!\n",
le32_to_cpu(cfg->default_hw_config_id));
return -EINVAL;
}
@@ -2915,7 +3338,8 @@
&config);
break;
default:
- dev_err(sdev->dev, "error: invalid DAI type %d\n", config.type);
+ dev_err(scomp->dev, "error: invalid DAI type %d\n",
+ config.type);
ret = -EINVAL;
break;
}
@@ -2929,7 +3353,6 @@
struct snd_soc_dai_link *link)
{
struct snd_soc_dai *dai;
- int ret = 0;
dai = snd_soc_find_dai(link->cpus);
if (!dai) {
@@ -2938,7 +3361,7 @@
return -EINVAL;
}
- return ret;
+ return 0;
}
static int sof_link_unload(struct snd_soc_component *scomp,
@@ -2963,7 +3386,7 @@
goto found;
}
- dev_err(sdev->dev, "error: failed to find dai %s in %s",
+ dev_err(scomp->dev, "error: failed to find dai %s in %s",
link->name, __func__);
return -EINVAL;
found:
@@ -2972,13 +3395,15 @@
case SOF_DAI_INTEL_SSP:
case SOF_DAI_INTEL_DMIC:
case SOF_DAI_INTEL_ALH:
- /* no resource needs to be released for SSP, DMIC and 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(sdev->dev, "error: invalid DAI type %d\n",
+ dev_err(scomp->dev, "error: invalid DAI type %d\n",
sof_dai->dai_config->type);
ret = -EINVAL;
break;
@@ -3004,7 +3429,7 @@
if (!sroute)
return -ENOMEM;
- sroute->sdev = sdev;
+ sroute->scomp = scomp;
connect = kzalloc(sizeof(*connect), GFP_KERNEL);
if (!connect) {
@@ -3015,14 +3440,14 @@
connect->hdr.size = sizeof(*connect);
connect->hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_CONNECT;
- dev_dbg(sdev->dev, "sink %s control %s source %s\n",
+ dev_dbg(scomp->dev, "sink %s control %s source %s\n",
route->sink, route->control ? route->control : "none",
route->source);
/* source component */
- source_swidget = snd_sof_find_swidget(sdev, (char *)route->source);
+ source_swidget = snd_sof_find_swidget(scomp, (char *)route->source);
if (!source_swidget) {
- dev_err(sdev->dev, "error: source %s not found\n",
+ dev_err(scomp->dev, "error: source %s not found\n",
route->source);
ret = -EINVAL;
goto err;
@@ -3041,9 +3466,9 @@
connect->source_id = source_swidget->comp_id;
/* sink component */
- sink_swidget = snd_sof_find_swidget(sdev, (char *)route->sink);
+ sink_swidget = snd_sof_find_swidget(scomp, (char *)route->sink);
if (!sink_swidget) {
- dev_err(sdev->dev, "error: sink %s not found\n",
+ dev_err(scomp->dev, "error: sink %s not found\n",
route->sink);
ret = -EINVAL;
goto err;
@@ -3067,9 +3492,8 @@
*/
if (source_swidget->id != snd_soc_dapm_buffer &&
sink_swidget->id != snd_soc_dapm_buffer) {
- dev_dbg(sdev->dev, "warning: neither Linked source component %s nor sink component %s is of buffer type, ignoring link\n",
+ 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,
@@ -3079,7 +3503,7 @@
/* check IPC return value */
if (ret < 0) {
- dev_err(sdev->dev, "error: failed to add route sink %s control %s source %s\n",
+ 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);
@@ -3088,7 +3512,7 @@
/* check IPC reply */
if (reply.error < 0) {
- dev_err(sdev->dev, "error: DSP failed to add route sink %s control %s source %s result %d\n",
+ 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);
@@ -3103,7 +3527,7 @@
/* add route to route list */
list_add(&sroute->list, &sdev->route_list);
- return ret;
+ return 0;
}
err:
@@ -3115,8 +3539,9 @@
/* Function to set the initial value of SOF kcontrols.
* The value will be stored in scontrol->control_data
*/
-static int snd_sof_cache_kcontrol_val(struct snd_sof_dev *sdev)
+static int snd_sof_cache_kcontrol_val(struct snd_soc_component *scomp)
{
+ 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;
@@ -3136,33 +3561,34 @@
ctrl_type = SOF_CTRL_TYPE_DATA_GET;
break;
default:
- dev_err(sdev->dev,
+ dev_err(scomp->dev,
"error: Invalid scontrol->cmd: %d\n",
scontrol->cmd);
return -EINVAL;
}
- ret = snd_sof_ipc_set_get_comp_data(sdev->ipc, scontrol,
+ ret = snd_sof_ipc_set_get_comp_data(scontrol,
ipc_cmd, ctrl_type,
scontrol->cmd,
false);
if (ret < 0) {
- dev_warn(sdev->dev,
- "error: kcontrol value get for widget: %d\n",
- scontrol->comp_id);
+ dev_warn(scomp->dev,
+ "error: kcontrol value get for widget: %d\n",
+ scontrol->comp_id);
}
}
return ret;
}
-int snd_sof_complete_pipeline(struct snd_sof_dev *sdev,
+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(sdev->dev, "tplg: complete pipeline %s id %d\n",
+ dev_dbg(dev, "tplg: complete pipeline %s id %d\n",
swidget->widget->name, swidget->comp_id);
memset(&ready, 0, sizeof(ready));
@@ -3192,7 +3618,7 @@
switch (swidget->id) {
case snd_soc_dapm_scheduler:
swidget->complete =
- snd_sof_complete_pipeline(sdev, swidget);
+ snd_sof_complete_pipeline(scomp->dev, swidget);
break;
default:
break;
@@ -3202,14 +3628,13 @@
* 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(sdev);
+ 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)
{
- struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
u32 size;
u32 abi_version;
@@ -3217,16 +3642,16 @@
/* backward compatible with tplg without ABI info */
if (!size) {
- dev_dbg(sdev->dev, "No topology ABI info\n");
+ dev_dbg(scomp->dev, "No topology ABI info\n");
return 0;
}
if (size != SOF_TPLG_ABI_SIZE) {
- dev_err(sdev->dev, "error: invalid topology ABI size\n");
+ dev_err(scomp->dev, "error: invalid topology ABI size\n");
return -EINVAL;
}
- dev_info(sdev->dev,
+ 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,
@@ -3237,15 +3662,15 @@
man->priv.data[2]);
if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, abi_version)) {
- dev_err(sdev->dev, "error: incompatible topology ABI version\n");
+ dev_err(scomp->dev, "error: incompatible topology ABI version\n");
return -EINVAL;
}
if (abi_version > SOF_ABI_VERSION) {
if (!IS_ENABLED(CONFIG_SND_SOC_SOF_STRICT_ABI_CHECKS)) {
- dev_warn(sdev->dev, "warn: topology ABI is more recent than kernel\n");
+ dev_warn(scomp->dev, "warn: topology ABI is more recent than kernel\n");
} else {
- dev_err(sdev->dev, "error: topology ABI is more recent than kernel\n");
+ dev_err(scomp->dev, "error: topology ABI is more recent than kernel\n");
return -EINVAL;
}
}
@@ -3264,6 +3689,7 @@
/* 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 = {
@@ -3303,34 +3729,25 @@
.bytes_ext_ops_count = ARRAY_SIZE(sof_bytes_ext_ops),
};
-int snd_sof_init_topology(struct snd_sof_dev *sdev,
- struct snd_soc_tplg_ops *ops)
-{
- /* TODO: support linked list of topologies */
- sdev->tplg_ops = ops;
- return 0;
-}
-EXPORT_SYMBOL(snd_sof_init_topology);
-
-int snd_sof_load_topology(struct snd_sof_dev *sdev, const char *file)
+int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file)
{
const struct firmware *fw;
int ret;
- dev_dbg(sdev->dev, "loading topology:%s\n", file);
+ dev_dbg(scomp->dev, "loading topology:%s\n", file);
- ret = request_firmware(&fw, file, sdev->dev);
+ ret = request_firmware(&fw, file, scomp->dev);
if (ret < 0) {
- dev_err(sdev->dev, "error: tplg request firmware %s failed err: %d\n",
+ dev_err(scomp->dev, "error: tplg request firmware %s failed err: %d\n",
file, ret);
return ret;
}
- ret = snd_soc_tplg_component_load(sdev->component,
+ ret = snd_soc_tplg_component_load(scomp,
&sof_tplg_ops, fw,
SND_SOC_TPLG_INDEX_ALL);
if (ret < 0) {
- dev_err(sdev->dev, "error: tplg component load failed %d\n",
+ dev_err(scomp->dev, "error: tplg component load failed %d\n",
ret);
ret = -EINVAL;
}
diff --git a/sound/soc/sof/trace.c b/sound/soc/sof/trace.c
index fd6f591..6988924 100644
--- a/sound/soc/sof/trace.c
+++ b/sound/soc/sof/trace.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -162,6 +162,9 @@
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;
@@ -222,6 +225,9 @@
{
int ret;
+ if (!sdev->dtrace_is_supported)
+ return 0;
+
/* set false before start initialization */
sdev->dtrace_is_enabled = false;
@@ -244,8 +250,8 @@
}
/* create compressed page table for audio firmware */
- ret = snd_sof_create_page_table(sdev, &sdev->dmatb, sdev->dmatp.area,
- sdev->dmatb.bytes);
+ ret = snd_sof_create_page_table(sdev->dev, &sdev->dmatb,
+ sdev->dmatp.area, sdev->dmatb.bytes);
if (ret < 0)
goto table_err;
@@ -277,6 +283,9 @@
int snd_sof_trace_update_pos(struct snd_sof_dev *sdev,
struct sof_ipc_dma_trace_posn *posn)
{
+ 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);
@@ -293,6 +302,9 @@
/* an error has occurred within the DSP that prevents further trace */
void snd_sof_trace_notify_for_error(struct snd_sof_dev *sdev)
{
+ if (!sdev->dtrace_is_supported)
+ return;
+
if (sdev->dtrace_is_enabled) {
dev_err(sdev->dev, "error: waking up any trace sleepers\n");
sdev->dtrace_error = true;
@@ -305,7 +317,7 @@
{
int ret;
- if (!sdev->dtrace_is_enabled)
+ if (!sdev->dtrace_is_supported || !sdev->dtrace_is_enabled)
return;
ret = snd_sof_dma_trace_trigger(sdev, SNDRV_PCM_TRIGGER_STOP);
@@ -326,6 +338,9 @@
void snd_sof_free_trace(struct snd_sof_dev *sdev)
{
+ if (!sdev->dtrace_is_supported)
+ return;
+
snd_sof_release_trace(sdev);
if (sdev->dma_trace_pages) {
diff --git a/sound/soc/sof/utils.c b/sound/soc/sof/utils.c
index 2ac4c3d..5539d3a 100644
--- a/sound/soc/sof/utils.c
+++ b/sound/soc/sof/utils.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -10,6 +10,7 @@
#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"
@@ -110,3 +111,62 @@
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/Makefile b/sound/soc/sof/xtensa/Makefile
index cc89c74..b8376ea 100644
--- a/sound/soc/sof/xtensa/Makefile
+++ b/sound/soc/sof/xtensa/Makefile
@@ -1,4 +1,4 @@
-# SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
snd-sof-xtensa-dsp-objs := core.o
diff --git a/sound/soc/sof/xtensa/core.c b/sound/soc/sof/xtensa/core.c
index 46a4905..bbb9a22 100644
--- a/sound/soc/sof/xtensa/core.c
+++ b/sound/soc/sof/xtensa/core.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// 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.
@@ -132,7 +132,7 @@
.dsp_oops = xtensa_dsp_oops,
.dsp_stack = xtensa_stack,
};
-EXPORT_SYMBOL(sof_xtensa_arch_ops);
+EXPORT_SYMBOL_NS(sof_xtensa_arch_ops, SND_SOC_SOF_XTENSA);
MODULE_DESCRIPTION("SOF Xtensa DSP support");
MODULE_LICENSE("Dual BSD/GPL");