diff options
Diffstat (limited to 'sound/soc/sof')
126 files changed, 25859 insertions, 8959 deletions
diff --git a/sound/soc/sof/Kconfig b/sound/soc/sof/Kconfig index 041c54639c4d..37f7df5fde17 100644 --- a/sound/soc/sof/Kconfig +++ b/sound/soc/sof/Kconfig @@ -40,28 +40,38 @@ config SND_SOC_SOF_ACPI_DEV config SND_SOC_SOF_OF tristate "SOF OF enumeration support" depends on OF || COMPILE_TEST - select SND_SOC_SOF help This adds support for Device Tree enumeration. This option is - required to enable i.MX8 devices. + required to enable i.MX8 or Mediatek devices. Say Y if you need this option. If unsure select "N". +config SND_SOC_SOF_OF_DEV + tristate + config SND_SOC_SOF_COMPRESS bool select SND_SOC_COMPRESS config SND_SOC_SOF_DEBUG_PROBES - bool "SOF enable data probing" + tristate + select SND_SOC_SOF_CLIENT select SND_SOC_COMPRESS help This option enables the data probing feature that can be used to gather data directly from specific points of the audio pipeline. - Say Y if you want to enable probes. - If unsure, select "N". + This option is not user-selectable but automagically handled by + 'select' statements at a higher level. + +config SND_SOC_SOF_CLIENT + tristate + select AUXILIARY_BUS + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level. config SND_SOC_SOF_DEVELOPER_SUPPORT bool "SOF developer options support" - depends on EXPERT + depends on EXPERT && SND_SOC_SOF help This option unlocks SOF developer options for debug/performance/ code hardening. @@ -185,13 +195,34 @@ config SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE If unsure, select "N". config SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST - bool "SOF enable IPC flood test" + tristate "SOF enable IPC flood test" + depends on SND_SOC_SOF + select SND_SOC_SOF_CLIENT help - This option enables the IPC flood test which can be used to flood - the DSP with test IPCs and gather stats about response times. + This option enables a separate client device for IPC flood test + which can be used to flood the DSP with test IPCs and gather stats + about response times. Say Y if you want to enable IPC flood test. If unsure, select "N". +config SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST_NUM + int "Number of IPC flood test clients" + range 1 32 + default 2 + depends on SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST + help + Select the number of IPC flood test clients to be created. + +config SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR + tristate "SOF enable IPC message injector" + depends on SND_SOC_SOF + select SND_SOC_SOF_CLIENT + help + This option enables the IPC message injector which can be used to send + crafted IPC messages to the DSP to test its robustness. + Say Y if you want to enable the IPC message injector. + If unsure, select "N". + config SND_SOC_SOF_DEBUG_RETAIN_DSP_CONTEXT bool "SOF retain DSP context on any FW exceptions" help @@ -223,8 +254,17 @@ config SND_SOC_SOF_PROBE_WORK_QUEUE When selected, the probe is handled in two steps, for example to avoid lockdeps if request_module is used in the probe. +# Supported IPC versions +config SND_SOC_SOF_IPC3 + bool + +config SND_SOC_SOF_INTEL_IPC4 + bool + +source "sound/soc/sof/amd/Kconfig" source "sound/soc/sof/imx/Kconfig" source "sound/soc/sof/intel/Kconfig" +source "sound/soc/sof/mediatek/Kconfig" source "sound/soc/sof/xtensa/Kconfig" endif diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile index 06e5f49f7ee8..eab7cc53f71a 100644 --- a/sound/soc/sof/Makefile +++ b/sound/soc/sof/Makefile @@ -1,25 +1,52 @@ # SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) snd-sof-objs := core.o ops.o loader.o ipc.o pcm.o pm.o debug.o topology.o\ - control.o trace.o utils.o sof-audio.o stream-ipc.o + control.o trace.o iomem-utils.o sof-audio.o stream-ipc.o + +# IPC implementations +ifneq ($(CONFIG_SND_SOC_SOF_IPC3),) +snd-sof-objs += ipc3.o ipc3-loader.o ipc3-topology.o ipc3-control.o ipc3-pcm.o\ + ipc3-dtrace.o +endif +ifneq ($(CONFIG_SND_SOC_SOF_INTEL_IPC4),) +snd-sof-objs += ipc4.o ipc4-loader.o ipc4-topology.o ipc4-control.o ipc4-pcm.o\ + ipc4-mtrace.o +endif + +# SOF client support +ifneq ($(CONFIG_SND_SOC_SOF_CLIENT),) +snd-sof-objs += sof-client.o +endif -snd-sof-$(CONFIG_SND_SOC_SOF_DEBUG_PROBES) += sof-probes.o snd-sof-$(CONFIG_SND_SOC_SOF_COMPRESS) += compress.o snd-sof-pci-objs := sof-pci-dev.o snd-sof-acpi-objs := sof-acpi-dev.o snd-sof-of-objs := sof-of-dev.o +snd-sof-ipc-flood-test-objs := sof-client-ipc-flood-test.o +snd-sof-ipc-msg-injector-objs := sof-client-ipc-msg-injector.o +snd-sof-probes-objs := sof-client-probes.o + snd-sof-nocodec-objs := nocodec.o +snd-sof-utils-objs := sof-utils.o + obj-$(CONFIG_SND_SOC_SOF) += snd-sof.o obj-$(CONFIG_SND_SOC_SOF_NOCODEC) += snd-sof-nocodec.o +obj-$(CONFIG_SND_SOC_SOF) += snd-sof-utils.o obj-$(CONFIG_SND_SOC_SOF_ACPI_DEV) += snd-sof-acpi.o -obj-$(CONFIG_SND_SOC_SOF_OF) += snd-sof-of.o +obj-$(CONFIG_SND_SOC_SOF_OF_DEV) += snd-sof-of.o obj-$(CONFIG_SND_SOC_SOF_PCI_DEV) += snd-sof-pci.o +obj-$(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) += snd-sof-ipc-flood-test.o +obj-$(CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR) += snd-sof-ipc-msg-injector.o +obj-$(CONFIG_SND_SOC_SOF_DEBUG_PROBES) += snd-sof-probes.o + obj-$(CONFIG_SND_SOC_SOF_INTEL_TOPLEVEL) += intel/ obj-$(CONFIG_SND_SOC_SOF_IMX_TOPLEVEL) += imx/ +obj-$(CONFIG_SND_SOC_SOF_AMD_TOPLEVEL) += amd/ obj-$(CONFIG_SND_SOC_SOF_XTENSA) += xtensa/ +obj-$(CONFIG_SND_SOC_SOF_MTK_TOPLEVEL) += mediatek/ diff --git a/sound/soc/sof/amd/Kconfig b/sound/soc/sof/amd/Kconfig new file mode 100644 index 000000000000..a305ea6efea9 --- /dev/null +++ b/sound/soc/sof/amd/Kconfig @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +# This file is provided under a dual BSD/GPLv2 license. When using or +# redistributing this file, you may do so under either license. +# +# Copyright(c) 2021 Advanced Micro Devices, Inc. All rights reserved. + +config SND_SOC_SOF_AMD_TOPLEVEL + tristate "SOF support for AMD audio DSPs" + depends on X86 || COMPILE_TEST + help + This adds support for Sound Open Firmware for AMD platforms. + Say Y if you have such a device. + If unsure select "N". + +if SND_SOC_SOF_AMD_TOPLEVEL + +config SND_SOC_SOF_AMD_COMMON + tristate + select SND_SOC_SOF + select SND_SOC_SOF_IPC3 + select SND_SOC_SOF_PCI_DEV + select SND_AMD_ACP_CONFIG + select SND_SOC_ACPI if ACPI + help + This option is not user-selectable but automatically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_AMD_RENOIR + tristate "SOF support for RENOIR" + depends on SND_SOC_SOF_PCI + select SND_SOC_SOF_AMD_COMMON + help + Select this option for SOF support on AMD Renoir platform + +config SND_SOC_SOF_AMD_REMBRANDT + tristate "SOF support for REMBRANDT" + depends on SND_SOC_SOF_PCI + select SND_SOC_SOF_AMD_COMMON + help + Select this option for SOF support on AMD Rembrandt platform + Say Y if you want to enable SOF on Rembrandt. + If unsure select "N". + +endif diff --git a/sound/soc/sof/amd/Makefile b/sound/soc/sof/amd/Makefile new file mode 100644 index 000000000000..5626d13b3e69 --- /dev/null +++ b/sound/soc/sof/amd/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +# This file is provided under a dual BSD/GPLv2 license. When using or +# redistributing this file, you may do so under either license. +# +# Copyright(c) 2021 Advanced Micro Devices, Inc. All rights reserved. + +snd-sof-amd-acp-objs := acp.o acp-loader.o acp-ipc.o acp-pcm.o acp-stream.o acp-trace.o acp-common.o +snd-sof-amd-renoir-objs := pci-rn.o renoir.o +snd-sof-amd-rembrandt-objs := pci-rmb.o rembrandt.o + +obj-$(CONFIG_SND_SOC_SOF_AMD_COMMON) += snd-sof-amd-acp.o +obj-$(CONFIG_SND_SOC_SOF_AMD_RENOIR) +=snd-sof-amd-renoir.o +obj-$(CONFIG_SND_SOC_SOF_AMD_REMBRANDT) +=snd-sof-amd-rembrandt.o diff --git a/sound/soc/sof/amd/acp-common.c b/sound/soc/sof/amd/acp-common.c new file mode 100644 index 000000000000..27b95187356e --- /dev/null +++ b/sound/soc/sof/amd/acp-common.c @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2022 Advanced Micro Devices, Inc. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> +// V sujith kumar Reddy <Vsujithkumar.Reddy@amd.com> + +/* ACP-specific Common code */ + +#include "../sof-priv.h" +#include "../sof-audio.h" +#include "../ops.h" +#include "../sof-audio.h" +#include "acp.h" +#include "acp-dsp-offset.h" + +int acp_dai_probe(struct snd_soc_dai *dai) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + unsigned int val; + + val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->i2s_pin_config_offset); + if (val != desc->i2s_mode) { + dev_err(sdev->dev, "I2S Mode is not supported (I2S_PIN_CONFIG: %#x)\n", val); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_NS(acp_dai_probe, SND_SOC_SOF_AMD_COMMON); + +struct snd_soc_acpi_mach *amd_sof_machine_select(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *sof_pdata = sdev->pdata; + const struct sof_dev_desc *desc = sof_pdata->desc; + struct snd_soc_acpi_mach *mach; + + mach = snd_soc_acpi_find_machine(desc->machines); + if (!mach) { + dev_warn(sdev->dev, "No matching ASoC machine driver found\n"); + return NULL; + } + + sof_pdata->tplg_filename = mach->sof_tplg_filename; + sof_pdata->fw_filename = mach->fw_filename; + + return mach; +} + +/* AMD Common DSP ops */ +struct snd_sof_dsp_ops sof_acp_common_ops = { + /* probe and remove */ + .probe = amd_sof_acp_probe, + .remove = amd_sof_acp_remove, + + /* Register IO */ + .write = sof_io_write, + .read = sof_io_read, + + /* Block IO */ + .block_read = acp_dsp_block_read, + .block_write = acp_dsp_block_write, + + /*Firmware loading */ + .load_firmware = snd_sof_load_firmware_memcpy, + .pre_fw_run = acp_dsp_pre_fw_run, + .get_bar_index = acp_get_bar_index, + + /* DSP core boot */ + .run = acp_sof_dsp_run, + + /*IPC */ + .send_msg = acp_sof_ipc_send_msg, + .ipc_msg_data = acp_sof_ipc_msg_data, + .get_mailbox_offset = acp_sof_ipc_get_mailbox_offset, + .get_window_offset = acp_sof_ipc_get_window_offset, + .irq_thread = acp_sof_ipc_irq_thread, + + /* stream callbacks */ + .pcm_open = acp_pcm_open, + .pcm_close = acp_pcm_close, + .pcm_hw_params = acp_pcm_hw_params, + + .hw_info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, + + /* Machine driver callbacks */ + .machine_select = amd_sof_machine_select, + .machine_register = sof_machine_register, + .machine_unregister = sof_machine_unregister, + + /* Trace Logger */ + .trace_init = acp_sof_trace_init, + .trace_release = acp_sof_trace_release, + + /* PM */ + .suspend = amd_sof_acp_suspend, + .resume = amd_sof_acp_resume, +}; +EXPORT_SYMBOL_NS(sof_acp_common_ops, SND_SOC_SOF_AMD_COMMON); + +MODULE_IMPORT_NS(SND_SOC_SOF_AMD_COMMON); +MODULE_DESCRIPTION("ACP SOF COMMON Driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/amd/acp-dsp-offset.h b/sound/soc/sof/amd/acp-dsp-offset.h new file mode 100644 index 000000000000..de5726251dc6 --- /dev/null +++ b/sound/soc/sof/amd/acp-dsp-offset.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright(c) 2021 Advanced Micro Devices, Inc. All rights reserved. + * + * Author: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> + */ + +#ifndef _ACP_DSP_IP_OFFSET_H +#define _ACP_DSP_IP_OFFSET_H + +/* Registers from ACP_DMA_0 block */ +#define ACP_DMA_CNTL_0 0x00 +#define ACP_DMA_DSCR_STRT_IDX_0 0x20 +#define ACP_DMA_DSCR_CNT_0 0x40 +#define ACP_DMA_PRIO_0 0x60 +#define ACP_DMA_CUR_DSCR_0 0x80 +#define ACP_DMA_ERR_STS_0 0xC0 +#define ACP_DMA_DESC_BASE_ADDR 0xE0 +#define ACP_DMA_DESC_MAX_NUM_DSCR 0xE4 +#define ACP_DMA_CH_STS 0xE8 +#define ACP_DMA_CH_GROUP 0xEC +#define ACP_DMA_CH_RST_STS 0xF0 + +/* Registers from ACP_DSP_0 block */ +#define ACP_DSP0_RUNSTALL 0x414 + +/* Registers from ACP_AXI2AXIATU block */ +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1 0xC00 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_1 0xC04 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_2 0xC08 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_2 0xC0C +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_3 0xC10 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_3 0xC14 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_4 0xC18 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_4 0xC1C +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_5 0xC20 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_5 0xC24 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_6 0xC28 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_6 0xC2C +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_7 0xC30 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_7 0xC34 +#define ACPAXI2AXI_ATU_PAGE_SIZE_GRP_8 0xC38 +#define ACPAXI2AXI_ATU_BASE_ADDR_GRP_8 0xC3C +#define ACPAXI2AXI_ATU_CTRL 0xC40 +#define ACP_SOFT_RESET 0x1000 +#define ACP_CONTROL 0x1004 + +#define ACP3X_I2S_PIN_CONFIG 0x1400 +#define ACP6X_I2S_PIN_CONFIG 0x1440 + +/* Registers offsets from ACP_PGFSM block */ +#define ACP3X_PGFSM_BASE 0x141C +#define ACP6X_PGFSM_BASE 0x1024 +#define PGFSM_CONTROL_OFFSET 0x0 +#define PGFSM_STATUS_OFFSET 0x4 +#define ACP3X_CLKMUX_SEL 0x1424 +#define ACP6X_CLKMUX_SEL 0x102C + +/* Registers from ACP_INTR block */ +#define ACP3X_EXT_INTR_STAT 0x1808 +#define ACP6X_EXT_INTR_STAT 0x1A0C + +#define ACP3X_DSP_SW_INTR_BASE 0x1814 +#define ACP6X_DSP_SW_INTR_BASE 0x1808 +#define DSP_SW_INTR_CNTL_OFFSET 0x0 +#define DSP_SW_INTR_STAT_OFFSET 0x4 +#define DSP_SW_INTR_TRIG_OFFSET 0x8 +#define ACP_ERROR_STATUS 0x18C4 +#define ACP3X_AXI2DAGB_SEM_0 0x1880 +#define ACP6X_AXI2DAGB_SEM_0 0x1874 + +/* Registers from ACP_SHA block */ +#define ACP_SHA_DSP_FW_QUALIFIER 0x1C70 +#define ACP_SHA_DMA_CMD 0x1CB0 +#define ACP_SHA_MSG_LENGTH 0x1CB4 +#define ACP_SHA_DMA_STRT_ADDR 0x1CB8 +#define ACP_SHA_DMA_DESTINATION_ADDR 0x1CBC +#define ACP_SHA_DMA_CMD_STS 0x1CC0 +#define ACP_SHA_DMA_ERR_STATUS 0x1CC4 +#define ACP_SHA_TRANSFER_BYTE_CNT 0x1CC8 +#define ACP_SHA_PSP_ACK 0x1C74 + +#define ACP_SCRATCH_REG_0 0x10000 +#define ACP6X_DSP_FUSION_RUNSTALL 0x0644 +#endif diff --git a/sound/soc/sof/amd/acp-ipc.c b/sound/soc/sof/amd/acp-ipc.c new file mode 100644 index 000000000000..dd030566e372 --- /dev/null +++ b/sound/soc/sof/amd/acp-ipc.c @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Advanced Micro Devices, Inc. +// +// Authors: Balakishore Pati <Balakishore.pati@amd.com> +// Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> + +/* ACP-specific SOF IPC code */ + +#include <linux/module.h> +#include "../ops.h" +#include "acp.h" +#include "acp-dsp-offset.h" + +void acp_mailbox_write(struct snd_sof_dev *sdev, u32 offset, void *message, size_t bytes) +{ + memcpy_to_scratch(sdev, offset, message, bytes); +} +EXPORT_SYMBOL_NS(acp_mailbox_write, SND_SOC_SOF_AMD_COMMON); + +void acp_mailbox_read(struct snd_sof_dev *sdev, u32 offset, void *message, size_t bytes) +{ + memcpy_from_scratch(sdev, offset, message, bytes); +} +EXPORT_SYMBOL_NS(acp_mailbox_read, SND_SOC_SOF_AMD_COMMON); + +static void acpbus_trigger_host_to_dsp_swintr(struct acp_dev_data *adata) +{ + struct snd_sof_dev *sdev = adata->dev; + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + u32 swintr_trigger; + + swintr_trigger = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->dsp_intr_base + + DSP_SW_INTR_TRIG_OFFSET); + swintr_trigger |= 0x01; + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->dsp_intr_base + DSP_SW_INTR_TRIG_OFFSET, + swintr_trigger); +} + +static void acp_ipc_host_msg_set(struct snd_sof_dev *sdev) +{ + unsigned int host_msg = sdev->debug_box.offset + + offsetof(struct scratch_ipc_conf, sof_host_msg_write); + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + host_msg, 1); +} + +static void acp_dsp_ipc_host_done(struct snd_sof_dev *sdev) +{ + unsigned int dsp_msg = sdev->debug_box.offset + + offsetof(struct scratch_ipc_conf, sof_dsp_msg_write); + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + dsp_msg, 0); +} + +static void acp_dsp_ipc_dsp_done(struct snd_sof_dev *sdev) +{ + unsigned int dsp_ack = sdev->debug_box.offset + + offsetof(struct scratch_ipc_conf, sof_dsp_ack_write); + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + dsp_ack, 0); +} + +int acp_sof_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) +{ + struct acp_dev_data *adata = sdev->pdata->hw_pdata; + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + unsigned int offset = sdev->host_box.offset; + unsigned int count = ACP_HW_SEM_RETRY_COUNT; + + while (snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->hw_semaphore_offset)) { + /* Wait until acquired HW Semaphore Lock or timeout*/ + count--; + if (!count) { + dev_err(sdev->dev, "%s: Failed to acquire HW lock\n", __func__); + return -EINVAL; + } + } + + acp_mailbox_write(sdev, offset, msg->msg_data, msg->msg_size); + acp_ipc_host_msg_set(sdev); + + /* Trigger host to dsp interrupt for the msg */ + acpbus_trigger_host_to_dsp_swintr(adata); + + /* Unlock or Release HW Semaphore */ + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->hw_semaphore_offset, 0x0); + + return 0; +} +EXPORT_SYMBOL_NS(acp_sof_ipc_send_msg, SND_SOC_SOF_AMD_COMMON); + +static void acp_dsp_ipc_get_reply(struct snd_sof_dev *sdev) +{ + struct snd_sof_ipc_msg *msg = sdev->msg; + struct sof_ipc_reply reply; + struct sof_ipc_cmd_hdr *hdr; + unsigned int offset = sdev->host_box.offset; + int ret = 0; + + /* + * Sometimes, there is unexpected reply ipc arriving. The reply + * ipc belongs to none of the ipcs sent from driver. + * In this case, the driver must ignore the ipc. + */ + if (!msg) { + dev_warn(sdev->dev, "unexpected ipc interrupt raised!\n"); + return; + } + hdr = msg->msg_data; + if (hdr->cmd == (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CTX_SAVE) || + hdr->cmd == (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_GATE)) { + /* + * memory windows are powered off before sending IPC reply, + * so we can't read the mailbox for CTX_SAVE and PM_GATE + * replies. + */ + reply.error = 0; + reply.hdr.cmd = SOF_IPC_GLB_REPLY; + reply.hdr.size = sizeof(reply); + memcpy(msg->reply_data, &reply, sizeof(reply)); + goto out; + } + /* get IPC reply from DSP in the mailbox */ + acp_mailbox_read(sdev, offset, &reply, sizeof(reply)); + if (reply.error < 0) { + memcpy(msg->reply_data, &reply, sizeof(reply)); + ret = reply.error; + } else { + /* reply correct size ? */ + if (reply.hdr.size != msg->reply_size && + !(reply.hdr.cmd & SOF_IPC_GLB_PROBE)) { + dev_err(sdev->dev, "reply expected %zu got %u bytes\n", + msg->reply_size, reply.hdr.size); + ret = -EINVAL; + } + /* read the message */ + if (msg->reply_size > 0) + acp_mailbox_read(sdev, offset, msg->reply_data, msg->reply_size); + } +out: + msg->reply_error = ret; +} + +irqreturn_t acp_sof_ipc_irq_thread(int irq, void *context) +{ + struct snd_sof_dev *sdev = context; + unsigned int dsp_msg_write = sdev->debug_box.offset + + offsetof(struct scratch_ipc_conf, sof_dsp_msg_write); + unsigned int dsp_ack_write = sdev->debug_box.offset + + offsetof(struct scratch_ipc_conf, sof_dsp_ack_write); + bool ipc_irq = false; + int dsp_msg, dsp_ack; + + if (sdev->first_boot && sdev->fw_state != SOF_FW_BOOT_COMPLETE) { + snd_sof_ipc_msgs_rx(sdev); + acp_dsp_ipc_host_done(sdev); + return IRQ_HANDLED; + } + + dsp_msg = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + dsp_msg_write); + if (dsp_msg) { + snd_sof_ipc_msgs_rx(sdev); + acp_dsp_ipc_host_done(sdev); + ipc_irq = true; + } + + dsp_ack = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + dsp_ack_write); + if (dsp_ack) { + spin_lock_irq(&sdev->ipc_lock); + /* handle immediate reply from DSP core */ + acp_dsp_ipc_get_reply(sdev); + snd_sof_ipc_reply(sdev, 0); + /* set the done bit */ + acp_dsp_ipc_dsp_done(sdev); + spin_unlock_irq(&sdev->ipc_lock); + ipc_irq = true; + } + + if (!ipc_irq) + dev_dbg_ratelimited(sdev->dev, "nothing to do in IPC IRQ thread\n"); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL_NS(acp_sof_ipc_irq_thread, SND_SOC_SOF_AMD_COMMON); + +int acp_sof_ipc_msg_data(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, + void *p, size_t sz) +{ + unsigned int offset = sdev->dsp_box.offset; + + if (!substream || !sdev->stream_box.size) + acp_mailbox_read(sdev, offset, p, sz); + + return 0; +} +EXPORT_SYMBOL_NS(acp_sof_ipc_msg_data, SND_SOC_SOF_AMD_COMMON); + +int acp_sof_ipc_get_mailbox_offset(struct snd_sof_dev *sdev) +{ + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + + return desc->sram_pte_offset; +} +EXPORT_SYMBOL_NS(acp_sof_ipc_get_mailbox_offset, SND_SOC_SOF_AMD_COMMON); + +int acp_sof_ipc_get_window_offset(struct snd_sof_dev *sdev, u32 id) +{ + return 0; +} +EXPORT_SYMBOL_NS(acp_sof_ipc_get_window_offset, SND_SOC_SOF_AMD_COMMON); + +MODULE_DESCRIPTION("AMD ACP sof-ipc driver"); diff --git a/sound/soc/sof/amd/acp-loader.c b/sound/soc/sof/amd/acp-loader.c new file mode 100644 index 000000000000..d1e74baf5d8b --- /dev/null +++ b/sound/soc/sof/amd/acp-loader.c @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Advanced Micro Devices, Inc. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> + +/* + * Hardware interface for ACP DSP Firmware binaries loader + */ + +#include <linux/firmware.h> +#include <linux/module.h> +#include <linux/pci.h> + +#include "../ops.h" +#include "acp-dsp-offset.h" +#include "acp.h" + +#define FW_BIN 0 +#define FW_DATA_BIN 1 + +#define FW_BIN_PTE_OFFSET 0x00 +#define FW_DATA_BIN_PTE_OFFSET 0x08 + +#define ACP_DSP_RUN 0x00 + +int acp_dsp_block_read(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type, + u32 offset, void *dest, size_t size) +{ + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + switch (blk_type) { + case SOF_FW_BLK_TYPE_SRAM: + offset = offset - desc->sram_pte_offset; + memcpy_from_scratch(sdev, offset, dest, size); + break; + default: + dev_err(sdev->dev, "bad blk type 0x%x\n", blk_type); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_NS(acp_dsp_block_read, SND_SOC_SOF_AMD_COMMON); + +int acp_dsp_block_write(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type, + u32 offset, void *src, size_t size) +{ + struct snd_sof_pdata *plat_data = sdev->pdata; + struct pci_dev *pci = to_pci_dev(sdev->dev); + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + struct acp_dev_data *adata; + void *dest; + u32 dma_size, page_count; + unsigned int size_fw; + + adata = sdev->pdata->hw_pdata; + + switch (blk_type) { + case SOF_FW_BLK_TYPE_IRAM: + if (!adata->bin_buf) { + size_fw = plat_data->fw->size; + page_count = PAGE_ALIGN(size_fw) >> PAGE_SHIFT; + dma_size = page_count * ACP_PAGE_SIZE; + adata->bin_buf = dma_alloc_coherent(&pci->dev, dma_size, + &adata->sha_dma_addr, + GFP_ATOMIC); + if (!adata->bin_buf) + return -ENOMEM; + } + adata->fw_bin_size = size + offset; + dest = adata->bin_buf + offset; + break; + case SOF_FW_BLK_TYPE_DRAM: + if (!adata->data_buf) { + adata->data_buf = dma_alloc_coherent(&pci->dev, + ACP_DEFAULT_DRAM_LENGTH, + &adata->dma_addr, + GFP_ATOMIC); + if (!adata->data_buf) + return -ENOMEM; + } + dest = adata->data_buf + offset; + adata->fw_data_bin_size = size + offset; + break; + case SOF_FW_BLK_TYPE_SRAM: + offset = offset - desc->sram_pte_offset; + memcpy_to_scratch(sdev, offset, src, size); + return 0; + default: + dev_err(sdev->dev, "bad blk type 0x%x\n", blk_type); + return -EINVAL; + } + + memcpy(dest, src, size); + return 0; +} +EXPORT_SYMBOL_NS(acp_dsp_block_write, SND_SOC_SOF_AMD_COMMON); + +int acp_get_bar_index(struct snd_sof_dev *sdev, u32 type) +{ + return type; +} +EXPORT_SYMBOL_NS(acp_get_bar_index, SND_SOC_SOF_AMD_COMMON); + +static void configure_pte_for_fw_loading(int type, int num_pages, struct acp_dev_data *adata) +{ + struct snd_sof_dev *sdev = adata->dev; + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + unsigned int low, high; + dma_addr_t addr; + u16 page_idx; + u32 offset; + + switch (type) { + case FW_BIN: + offset = FW_BIN_PTE_OFFSET; + addr = adata->sha_dma_addr; + break; + case FW_DATA_BIN: + offset = adata->fw_bin_page_count * 8; + addr = adata->dma_addr; + break; + default: + dev_err(sdev->dev, "Invalid data type %x\n", type); + return; + } + + /* Group Enable */ + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACPAXI2AXI_ATU_BASE_ADDR_GRP_1, + desc->sram_pte_offset | BIT(31)); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1, + PAGE_SIZE_4K_ENABLE); + + for (page_idx = 0; page_idx < num_pages; page_idx++) { + low = lower_32_bits(addr); + high = upper_32_bits(addr); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + offset, low); + high |= BIT(31); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + offset + 4, high); + offset += 8; + addr += PAGE_SIZE; + } + + /* Flush ATU Cache after PTE Update */ + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACPAXI2AXI_ATU_CTRL, ACP_ATU_CACHE_INVALID); +} + +/* pre fw run operations */ +int acp_dsp_pre_fw_run(struct snd_sof_dev *sdev) +{ + struct pci_dev *pci = to_pci_dev(sdev->dev); + struct snd_sof_pdata *plat_data = sdev->pdata; + struct acp_dev_data *adata; + unsigned int src_addr, size_fw; + u32 page_count, dma_size; + int ret; + + adata = sdev->pdata->hw_pdata; + size_fw = adata->fw_bin_size; + + page_count = PAGE_ALIGN(size_fw) >> PAGE_SHIFT; + adata->fw_bin_page_count = page_count; + + configure_pte_for_fw_loading(FW_BIN, page_count, adata); + ret = configure_and_run_sha_dma(adata, adata->bin_buf, ACP_SYSTEM_MEMORY_WINDOW, + ACP_IRAM_BASE_ADDRESS, size_fw); + if (ret < 0) { + dev_err(sdev->dev, "SHA DMA transfer failed status: %d\n", ret); + return ret; + } + configure_pte_for_fw_loading(FW_DATA_BIN, ACP_DRAM_PAGE_COUNT, adata); + + src_addr = ACP_SYSTEM_MEMORY_WINDOW + page_count * ACP_PAGE_SIZE; + ret = configure_and_run_dma(adata, src_addr, ACP_DATA_RAM_BASE_ADDRESS, + adata->fw_data_bin_size); + if (ret < 0) { + dev_err(sdev->dev, "acp dma configuration failed: %d\n", ret); + return ret; + } + + ret = acp_dma_status(adata, 0); + if (ret < 0) + dev_err(sdev->dev, "acp dma transfer status: %d\n", ret); + + /* Free memory once DMA is complete */ + dma_size = (PAGE_ALIGN(plat_data->fw->size) >> PAGE_SHIFT) * ACP_PAGE_SIZE; + dma_free_coherent(&pci->dev, dma_size, adata->bin_buf, adata->sha_dma_addr); + dma_free_coherent(&pci->dev, ACP_DEFAULT_DRAM_LENGTH, adata->data_buf, adata->dma_addr); + adata->bin_buf = NULL; + adata->data_buf = NULL; + + return ret; +} +EXPORT_SYMBOL_NS(acp_dsp_pre_fw_run, SND_SOC_SOF_AMD_COMMON); + +int acp_sof_dsp_run(struct snd_sof_dev *sdev) +{ + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + int val; + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DSP0_RUNSTALL, ACP_DSP_RUN); + val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_DSP0_RUNSTALL); + dev_dbg(sdev->dev, "ACP_DSP0_RUNSTALL : 0x%0x\n", val); + + /* Some platforms won't support fusion DSP,keep offset zero for no support */ + if (desc->fusion_dsp_offset) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->fusion_dsp_offset, ACP_DSP_RUN); + val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->fusion_dsp_offset); + dev_dbg(sdev->dev, "ACP_DSP0_FUSION_RUNSTALL : 0x%0x\n", val); + } + return 0; +} +EXPORT_SYMBOL_NS(acp_sof_dsp_run, SND_SOC_SOF_AMD_COMMON); diff --git a/sound/soc/sof/amd/acp-pcm.c b/sound/soc/sof/amd/acp-pcm.c new file mode 100644 index 000000000000..727c3a784a20 --- /dev/null +++ b/sound/soc/sof/amd/acp-pcm.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Advanced Micro Devices, Inc. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> + +/* + * PCM interface for generic AMD audio ACP DSP block + */ +#include <sound/pcm_params.h> + +#include "../ops.h" +#include "acp.h" +#include "acp-dsp-offset.h" + +int acp_pcm_hw_params(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_sof_platform_stream_params *platform_params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct acp_dsp_stream *stream = runtime->private_data; + unsigned int buf_offset, index; + u32 size; + int ret; + + size = runtime->dma_bytes; + stream->num_pages = PFN_UP(runtime->dma_bytes); + stream->dmab = substream->runtime->dma_buffer_p; + + ret = acp_dsp_stream_config(sdev, stream); + if (ret < 0) { + dev_err(sdev->dev, "stream configuration failed\n"); + return ret; + } + + platform_params->use_phy_address = true; + platform_params->phy_addr = stream->reg_offset; + platform_params->stream_tag = stream->stream_tag; + + /* write buffer size of stream in scratch memory */ + + buf_offset = sdev->debug_box.offset + + offsetof(struct scratch_reg_conf, buf_size); + index = stream->stream_tag - 1; + buf_offset = buf_offset + index * 4; + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + buf_offset, size); + + return 0; +} +EXPORT_SYMBOL_NS(acp_pcm_hw_params, SND_SOC_SOF_AMD_COMMON); + +int acp_pcm_open(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream) +{ + struct acp_dsp_stream *stream; + + stream = acp_dsp_stream_get(sdev, 0); + if (!stream) + return -ENODEV; + + substream->runtime->private_data = stream; + stream->substream = substream; + + return 0; +} +EXPORT_SYMBOL_NS(acp_pcm_open, SND_SOC_SOF_AMD_COMMON); + +int acp_pcm_close(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream) +{ + struct acp_dsp_stream *stream; + + stream = substream->runtime->private_data; + if (!stream) { + dev_err(sdev->dev, "No open stream\n"); + return -EINVAL; + } + + stream->substream = NULL; + substream->runtime->private_data = NULL; + + return acp_dsp_stream_put(sdev, stream); +} +EXPORT_SYMBOL_NS(acp_pcm_close, SND_SOC_SOF_AMD_COMMON); diff --git a/sound/soc/sof/amd/acp-stream.c b/sound/soc/sof/amd/acp-stream.c new file mode 100644 index 000000000000..6f40ef7ba85e --- /dev/null +++ b/sound/soc/sof/amd/acp-stream.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Advanced Micro Devices, Inc. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> + +/* + * Hardware interface for generic AMD audio DSP ACP IP + */ + +#include "../ops.h" +#include "acp-dsp-offset.h" +#include "acp.h" + +#define PTE_GRP1_OFFSET 0x00000000 +#define PTE_GRP2_OFFSET 0x00800000 +#define PTE_GRP3_OFFSET 0x01000000 +#define PTE_GRP4_OFFSET 0x01800000 +#define PTE_GRP5_OFFSET 0x02000000 +#define PTE_GRP6_OFFSET 0x02800000 +#define PTE_GRP7_OFFSET 0x03000000 +#define PTE_GRP8_OFFSET 0x03800000 + +int acp_dsp_stream_config(struct snd_sof_dev *sdev, struct acp_dsp_stream *stream) +{ + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + unsigned int pte_reg, pte_size, phy_addr_offset, index; + int stream_tag = stream->stream_tag; + u32 low, high, offset, reg_val; + dma_addr_t addr; + int page_idx; + + switch (stream_tag) { + case 1: + pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_1; + pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1; + offset = offsetof(struct scratch_reg_conf, grp1_pte); + stream->reg_offset = PTE_GRP1_OFFSET; + break; + case 2: + pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_2; + pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_2; + offset = offsetof(struct scratch_reg_conf, grp2_pte); + stream->reg_offset = PTE_GRP2_OFFSET; + break; + case 3: + pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_3; + pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_3; + offset = offsetof(struct scratch_reg_conf, grp3_pte); + stream->reg_offset = PTE_GRP3_OFFSET; + break; + case 4: + pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_4; + pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_4; + offset = offsetof(struct scratch_reg_conf, grp4_pte); + stream->reg_offset = PTE_GRP4_OFFSET; + break; + case 5: + pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_5; + pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_5; + offset = offsetof(struct scratch_reg_conf, grp5_pte); + stream->reg_offset = PTE_GRP5_OFFSET; + break; + case 6: + pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_6; + pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_6; + offset = offsetof(struct scratch_reg_conf, grp6_pte); + stream->reg_offset = PTE_GRP6_OFFSET; + break; + case 7: + pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_7; + pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_7; + offset = offsetof(struct scratch_reg_conf, grp7_pte); + stream->reg_offset = PTE_GRP7_OFFSET; + break; + case 8: + pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_8; + pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_8; + offset = offsetof(struct scratch_reg_conf, grp8_pte); + stream->reg_offset = PTE_GRP8_OFFSET; + break; + default: + dev_err(sdev->dev, "Invalid stream tag %d\n", stream_tag); + return -EINVAL; + } + + /* write phy_addr in scratch memory */ + + phy_addr_offset = sdev->debug_box.offset + + offsetof(struct scratch_reg_conf, reg_offset); + index = stream_tag - 1; + phy_addr_offset = phy_addr_offset + index * 4; + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + + phy_addr_offset, stream->reg_offset); + + /* Group Enable */ + offset = offset + sdev->debug_box.offset; + reg_val = desc->sram_pte_offset + offset; + snd_sof_dsp_write(sdev, ACP_DSP_BAR, pte_reg, reg_val | BIT(31)); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, pte_size, PAGE_SIZE_4K_ENABLE); + + for (page_idx = 0; page_idx < stream->num_pages; page_idx++) { + addr = snd_sgbuf_get_addr(stream->dmab, page_idx * PAGE_SIZE); + + /* Load the low address of page int ACP SRAM through SRBM */ + low = lower_32_bits(addr); + high = upper_32_bits(addr); + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + offset, low); + + high |= BIT(31); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SCRATCH_REG_0 + offset + 4, high); + /* Move to next physically contiguous page */ + offset += 8; + } + + /* Flush ATU Cache after PTE Update */ + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACPAXI2AXI_ATU_CTRL, ACP_ATU_CACHE_INVALID); + + return 0; +} + +struct acp_dsp_stream *acp_dsp_stream_get(struct snd_sof_dev *sdev, int tag) +{ + struct acp_dev_data *adata = sdev->pdata->hw_pdata; + struct acp_dsp_stream *stream = adata->stream_buf; + int i; + + for (i = 0; i < ACP_MAX_STREAM; i++, stream++) { + if (stream->active) + continue; + + /* return stream if tag not specified*/ + if (!tag) { + stream->active = 1; + return stream; + } + + /* check if this is the requested stream tag */ + if (stream->stream_tag == tag) { + stream->active = 1; + return stream; + } + } + + dev_err(sdev->dev, "stream %d active or no inactive stream\n", tag); + return NULL; +} +EXPORT_SYMBOL_NS(acp_dsp_stream_get, SND_SOC_SOF_AMD_COMMON); + +int acp_dsp_stream_put(struct snd_sof_dev *sdev, + struct acp_dsp_stream *acp_stream) +{ + struct acp_dev_data *adata = sdev->pdata->hw_pdata; + struct acp_dsp_stream *stream = adata->stream_buf; + int i; + + /* Free an active stream */ + for (i = 0; i < ACP_MAX_STREAM; i++, stream++) { + if (stream == acp_stream) { + stream->active = 0; + return 0; + } + } + + dev_err(sdev->dev, "Cannot find active stream tag %d\n", acp_stream->stream_tag); + return -EINVAL; +} +EXPORT_SYMBOL_NS(acp_dsp_stream_put, SND_SOC_SOF_AMD_COMMON); + +int acp_dsp_stream_init(struct snd_sof_dev *sdev) +{ + struct acp_dev_data *adata = sdev->pdata->hw_pdata; + int i; + + for (i = 0; i < ACP_MAX_STREAM; i++) { + adata->stream_buf[i].sdev = sdev; + adata->stream_buf[i].active = 0; + adata->stream_buf[i].stream_tag = i + 1; + } + return 0; +} +EXPORT_SYMBOL_NS(acp_dsp_stream_init, SND_SOC_SOF_AMD_COMMON); diff --git a/sound/soc/sof/amd/acp-trace.c b/sound/soc/sof/amd/acp-trace.c new file mode 100644 index 000000000000..c9482b27cbe3 --- /dev/null +++ b/sound/soc/sof/amd/acp-trace.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Advanced Micro Devices, Inc. All rights reserved. +// +// Authors: Vishnuvardhanrao Ravuapati <vishnuvardhanrao.ravulapati@amd.com> +// V Sujith Kumar Reddy <Vsujithkumar.Reddy@amd.com> + +/*This file support Host TRACE Logger driver callback for SOF FW */ + +#include "acp.h" + +#define ACP_LOGGER_STREAM 8 +#define NUM_PAGES 16 + +int acp_sof_trace_release(struct snd_sof_dev *sdev) +{ + struct acp_dsp_stream *stream; + struct acp_dev_data *adata; + int ret; + + adata = sdev->pdata->hw_pdata; + stream = adata->dtrace_stream; + ret = acp_dsp_stream_put(sdev, stream); + if (ret < 0) { + dev_err(sdev->dev, "Failed to release trace stream\n"); + return ret; + } + + adata->dtrace_stream = NULL; + return 0; +} +EXPORT_SYMBOL_NS(acp_sof_trace_release, SND_SOC_SOF_AMD_COMMON); + +int acp_sof_trace_init(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab, + struct sof_ipc_dma_trace_params_ext *dtrace_params) +{ + struct acp_dsp_stream *stream; + struct acp_dev_data *adata; + int ret; + + adata = sdev->pdata->hw_pdata; + stream = acp_dsp_stream_get(sdev, ACP_LOGGER_STREAM); + if (!stream) + return -ENODEV; + + stream->dmab = dmab; + stream->num_pages = NUM_PAGES; + + ret = acp_dsp_stream_config(sdev, stream); + if (ret < 0) { + acp_dsp_stream_put(sdev, stream); + return ret; + } + + adata->dtrace_stream = stream; + dtrace_params->stream_tag = stream->stream_tag; + dtrace_params->buffer.phy_addr = stream->reg_offset; + + return 0; +} +EXPORT_SYMBOL_NS(acp_sof_trace_init, SND_SOC_SOF_AMD_COMMON); diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c new file mode 100644 index 000000000000..36966643e36a --- /dev/null +++ b/sound/soc/sof/amd/acp.c @@ -0,0 +1,553 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Advanced Micro Devices, Inc. All rights reserved. +// +// Authors: Vijendar Mukunda <Vijendar.Mukunda@amd.com> +// Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> + +/* + * Hardware interface for generic AMD ACP processor + */ + +#include <linux/io.h> +#include <linux/module.h> +#include <linux/pci.h> + +#include "../ops.h" +#include "acp.h" +#include "acp-dsp-offset.h" + +static int smn_write(struct pci_dev *dev, u32 smn_addr, u32 data) +{ + pci_write_config_dword(dev, 0x60, smn_addr); + pci_write_config_dword(dev, 0x64, data); + + return 0; +} + +static int smn_read(struct pci_dev *dev, u32 smn_addr, u32 *data) +{ + pci_write_config_dword(dev, 0x60, smn_addr); + pci_read_config_dword(dev, 0x64, data); + + return 0; +} + +static void init_dma_descriptor(struct acp_dev_data *adata) +{ + struct snd_sof_dev *sdev = adata->dev; + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + unsigned int addr; + + addr = desc->sram_pte_offset + sdev->debug_box.offset + + offsetof(struct scratch_reg_conf, dma_desc); + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DMA_DESC_BASE_ADDR, addr); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DMA_DESC_MAX_NUM_DSCR, ACP_MAX_DESC_CNT); +} + +static void configure_dma_descriptor(struct acp_dev_data *adata, unsigned short idx, + struct dma_descriptor *dscr_info) +{ + struct snd_sof_dev *sdev = adata->dev; + unsigned int offset; + + offset = ACP_SCRATCH_REG_0 + sdev->debug_box.offset + + offsetof(struct scratch_reg_conf, dma_desc) + + idx * sizeof(struct dma_descriptor); + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, offset, dscr_info->src_addr); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, offset + 0x4, dscr_info->dest_addr); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, offset + 0x8, dscr_info->tx_cnt.u32_all); +} + +static int config_dma_channel(struct acp_dev_data *adata, unsigned int ch, + unsigned int idx, unsigned int dscr_count) +{ + struct snd_sof_dev *sdev = adata->dev; + unsigned int val, status; + int ret; + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DMA_CNTL_0 + ch * sizeof(u32), + ACP_DMA_CH_RST | ACP_DMA_CH_GRACEFUL_RST_EN); + + ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_DMA_CH_RST_STS, val, + val & (1 << ch), ACP_REG_POLL_INTERVAL, + ACP_REG_POLL_TIMEOUT_US); + if (ret < 0) { + status = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_ERROR_STATUS); + val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_DMA_ERR_STS_0 + ch * sizeof(u32)); + + dev_err(sdev->dev, "ACP_DMA_ERR_STS :0x%x ACP_ERROR_STATUS :0x%x\n", val, status); + return ret; + } + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, (ACP_DMA_CNTL_0 + ch * sizeof(u32)), 0); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DMA_DSCR_CNT_0 + ch * sizeof(u32), dscr_count); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DMA_DSCR_STRT_IDX_0 + ch * sizeof(u32), idx); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DMA_PRIO_0 + ch * sizeof(u32), 0); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_DMA_CNTL_0 + ch * sizeof(u32), ACP_DMA_CH_RUN); + + return ret; +} + +static int acpbus_dma_start(struct acp_dev_data *adata, unsigned int ch, + unsigned int dscr_count, struct dma_descriptor *dscr_info) +{ + struct snd_sof_dev *sdev = adata->dev; + int ret; + u16 dscr; + + if (!dscr_info || !dscr_count) + return -EINVAL; + + for (dscr = 0; dscr < dscr_count; dscr++) + configure_dma_descriptor(adata, dscr, dscr_info++); + + ret = config_dma_channel(adata, ch, 0, dscr_count); + if (ret < 0) + dev_err(sdev->dev, "config dma ch failed:%d\n", ret); + + return ret; +} + +int configure_and_run_dma(struct acp_dev_data *adata, unsigned int src_addr, + unsigned int dest_addr, int dsp_data_size) +{ + struct snd_sof_dev *sdev = adata->dev; + unsigned int desc_count, index; + int ret; + + for (desc_count = 0; desc_count < ACP_MAX_DESC && dsp_data_size >= 0; + desc_count++, dsp_data_size -= ACP_PAGE_SIZE) { + adata->dscr_info[desc_count].src_addr = src_addr + desc_count * ACP_PAGE_SIZE; + adata->dscr_info[desc_count].dest_addr = dest_addr + desc_count * ACP_PAGE_SIZE; + adata->dscr_info[desc_count].tx_cnt.bits.count = ACP_PAGE_SIZE; + if (dsp_data_size < ACP_PAGE_SIZE) + adata->dscr_info[desc_count].tx_cnt.bits.count = dsp_data_size; + } + + ret = acpbus_dma_start(adata, 0, desc_count, adata->dscr_info); + if (ret) + dev_err(sdev->dev, "acpbus_dma_start failed\n"); + + /* Clear descriptor array */ + for (index = 0; index < desc_count; index++) + memset(&adata->dscr_info[index], 0x00, sizeof(struct dma_descriptor)); + + return ret; +} + +/* + * psp_mbox_ready- function to poll ready bit of psp mbox + * @adata: acp device data + * @ack: bool variable to check ready bit status or psp ack + */ + +static int psp_mbox_ready(struct acp_dev_data *adata, bool ack) +{ + struct snd_sof_dev *sdev = adata->dev; + int timeout; + u32 data; + + for (timeout = ACP_PSP_TIMEOUT_COUNTER; timeout > 0; timeout--) { + msleep(20); + smn_read(adata->smn_dev, MP0_C2PMSG_114_REG, &data); + if (data & MBOX_READY_MASK) + return 0; + } + + dev_err(sdev->dev, "PSP error status %x\n", data & MBOX_STATUS_MASK); + + if (ack) + return -ETIMEDOUT; + + return -EBUSY; +} + +/* + * psp_send_cmd - function to send psp command over mbox + * @adata: acp device data + * @cmd: non zero integer value for command type + */ + +static int psp_send_cmd(struct acp_dev_data *adata, int cmd) +{ + struct snd_sof_dev *sdev = adata->dev; + int ret, timeout; + u32 data; + + if (!cmd) + return -EINVAL; + + /* Get a non-zero Doorbell value from PSP */ + for (timeout = ACP_PSP_TIMEOUT_COUNTER; timeout > 0; timeout--) { + msleep(MBOX_DELAY); + smn_read(adata->smn_dev, MP0_C2PMSG_73_REG, &data); + if (data) + break; + } + + if (!timeout) { + dev_err(sdev->dev, "Failed to get Doorbell from MBOX %x\n", MP0_C2PMSG_73_REG); + return -EINVAL; + } + + /* Check if PSP is ready for new command */ + ret = psp_mbox_ready(adata, 0); + if (ret) + return ret; + + smn_write(adata->smn_dev, MP0_C2PMSG_114_REG, cmd); + + /* Ring the Doorbell for PSP */ + smn_write(adata->smn_dev, MP0_C2PMSG_73_REG, data); + + /* Check MBOX ready as PSP ack */ + ret = psp_mbox_ready(adata, 1); + + return ret; +} + +int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr, + unsigned int start_addr, unsigned int dest_addr, + unsigned int image_length) +{ + struct snd_sof_dev *sdev = adata->dev; + unsigned int tx_count, fw_qualifier, val; + int ret; + + if (!image_addr) { + dev_err(sdev->dev, "SHA DMA image address is NULL\n"); + return -EINVAL; + } + + val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD); + if (val & ACP_SHA_RUN) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD, ACP_SHA_RESET); + ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD_STS, + val, val & ACP_SHA_RESET, + ACP_REG_POLL_INTERVAL, + ACP_REG_POLL_TIMEOUT_US); + if (ret < 0) { + dev_err(sdev->dev, "SHA DMA Failed to Reset\n"); + return ret; + } + } + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_STRT_ADDR, start_addr); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_DESTINATION_ADDR, dest_addr); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_MSG_LENGTH, image_length); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SHA_DMA_CMD, ACP_SHA_RUN); + + ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_SHA_TRANSFER_BYTE_CNT, + tx_count, tx_count == image_length, + ACP_REG_POLL_INTERVAL, ACP_DMA_COMPLETE_TIMEOUT_US); + if (ret < 0) { + dev_err(sdev->dev, "SHA DMA Failed to Transfer Length %x\n", tx_count); + return ret; + } + + ret = psp_send_cmd(adata, MBOX_ACP_SHA_DMA_COMMAND); + if (ret) + return ret; + + fw_qualifier = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SHA_DSP_FW_QUALIFIER); + if (!(fw_qualifier & DSP_FW_RUN_ENABLE)) { + dev_err(sdev->dev, "PSP validation failed\n"); + return -EINVAL; + } + + return 0; +} + +int acp_dma_status(struct acp_dev_data *adata, unsigned char ch) +{ + struct snd_sof_dev *sdev = adata->dev; + unsigned int val; + int ret = 0; + + val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_DMA_CNTL_0 + ch * sizeof(u32)); + if (val & ACP_DMA_CH_RUN) { + ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_DMA_CH_STS, val, !val, + ACP_REG_POLL_INTERVAL, + ACP_DMA_COMPLETE_TIMEOUT_US); + if (ret < 0) + dev_err(sdev->dev, "DMA_CHANNEL %d status timeout\n", ch); + } + + return ret; +} + +void memcpy_from_scratch(struct snd_sof_dev *sdev, u32 offset, unsigned int *dst, size_t bytes) +{ + unsigned int reg_offset = offset + ACP_SCRATCH_REG_0; + int i, j; + + for (i = 0, j = 0; i < bytes; i = i + 4, j++) + dst[j] = snd_sof_dsp_read(sdev, ACP_DSP_BAR, reg_offset + i); +} + +void memcpy_to_scratch(struct snd_sof_dev *sdev, u32 offset, unsigned int *src, size_t bytes) +{ + unsigned int reg_offset = offset + ACP_SCRATCH_REG_0; + int i, j; + + for (i = 0, j = 0; i < bytes; i = i + 4, j++) + snd_sof_dsp_write(sdev, ACP_DSP_BAR, reg_offset + i, src[j]); +} + +static int acp_memory_init(struct snd_sof_dev *sdev) +{ + struct acp_dev_data *adata = sdev->pdata->hw_pdata; + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + + snd_sof_dsp_update_bits(sdev, ACP_DSP_BAR, desc->dsp_intr_base + DSP_SW_INTR_CNTL_OFFSET, + ACP_DSP_INTR_EN_MASK, ACP_DSP_INTR_EN_MASK); + init_dma_descriptor(adata); + + return 0; +} + +static irqreturn_t acp_irq_thread(int irq, void *context) +{ + struct snd_sof_dev *sdev = context; + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + unsigned int base = desc->dsp_intr_base; + unsigned int val, count = ACP_HW_SEM_RETRY_COUNT; + + val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->ext_intr_stat); + if (val & ACP_SHA_STAT) { + /* Clear SHA interrupt raised by PSP */ + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, val); + return IRQ_HANDLED; + } + + val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, base + DSP_SW_INTR_STAT_OFFSET); + if (val & ACP_DSP_TO_HOST_IRQ) { + while (snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->hw_semaphore_offset)) { + /* Wait until acquired HW Semaphore lock or timeout */ + count--; + if (!count) { + dev_err(sdev->dev, "%s: Failed to acquire HW lock\n", __func__); + return IRQ_NONE; + } + } + + sof_ops(sdev)->irq_thread(irq, sdev); + val |= ACP_DSP_TO_HOST_IRQ; + snd_sof_dsp_write(sdev, ACP_DSP_BAR, base + DSP_SW_INTR_STAT_OFFSET, val); + + /* Unlock or Release HW Semaphore */ + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->hw_semaphore_offset, 0x0); + + return IRQ_HANDLED; + } + + return IRQ_NONE; +}; + +static irqreturn_t acp_irq_handler(int irq, void *dev_id) +{ + struct snd_sof_dev *sdev = dev_id; + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + unsigned int base = desc->dsp_intr_base; + unsigned int val; + + val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, base + DSP_SW_INTR_STAT_OFFSET); + if (val) + return IRQ_WAKE_THREAD; + + return IRQ_NONE; +} + +static int acp_power_on(struct snd_sof_dev *sdev) +{ + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + unsigned int base = desc->pgfsm_base; + unsigned int val; + int ret; + + val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, base + PGFSM_STATUS_OFFSET); + + if (val == ACP_POWERED_ON) + return 0; + + if (val & ACP_PGFSM_STATUS_MASK) + snd_sof_dsp_write(sdev, ACP_DSP_BAR, base + PGFSM_CONTROL_OFFSET, + ACP_PGFSM_CNTL_POWER_ON_MASK); + + ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, base + PGFSM_STATUS_OFFSET, val, + !val, ACP_REG_POLL_INTERVAL, ACP_REG_POLL_TIMEOUT_US); + if (ret < 0) + dev_err(sdev->dev, "timeout in ACP_PGFSM_STATUS read\n"); + + return ret; +} + +static int acp_reset(struct snd_sof_dev *sdev) +{ + unsigned int val; + int ret; + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SOFT_RESET, ACP_ASSERT_RESET); + + ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_SOFT_RESET, val, + val & ACP_SOFT_RESET_DONE_MASK, + ACP_REG_POLL_INTERVAL, ACP_REG_POLL_TIMEOUT_US); + if (ret < 0) { + dev_err(sdev->dev, "timeout asserting reset\n"); + return ret; + } + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_SOFT_RESET, ACP_RELEASE_RESET); + + ret = snd_sof_dsp_read_poll_timeout(sdev, ACP_DSP_BAR, ACP_SOFT_RESET, val, !val, + ACP_REG_POLL_INTERVAL, ACP_REG_POLL_TIMEOUT_US); + if (ret < 0) + dev_err(sdev->dev, "timeout in releasing reset\n"); + + return ret; +} + +static int acp_init(struct snd_sof_dev *sdev) +{ + int ret; + + /* power on */ + ret = acp_power_on(sdev); + if (ret) { + dev_err(sdev->dev, "ACP power on failed\n"); + return ret; + } + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_CONTROL, 0x01); + /* Reset */ + return acp_reset(sdev); +} + +int amd_sof_acp_suspend(struct snd_sof_dev *sdev, u32 target_state) +{ + int ret; + + ret = acp_reset(sdev); + if (ret) { + dev_err(sdev->dev, "ACP Reset failed\n"); + return ret; + } + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_CONTROL, 0x00); + + return 0; +} +EXPORT_SYMBOL_NS(amd_sof_acp_suspend, SND_SOC_SOF_AMD_COMMON); + +int amd_sof_acp_resume(struct snd_sof_dev *sdev) +{ + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + int ret; + + ret = acp_init(sdev); + if (ret) { + dev_err(sdev->dev, "ACP Init failed\n"); + return ret; + } + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->acp_clkmux_sel, 0x03); + + ret = acp_memory_init(sdev); + + return ret; +} +EXPORT_SYMBOL_NS(amd_sof_acp_resume, SND_SOC_SOF_AMD_COMMON); + +int amd_sof_acp_probe(struct snd_sof_dev *sdev) +{ + struct pci_dev *pci = to_pci_dev(sdev->dev); + struct acp_dev_data *adata; + const struct sof_amd_acp_desc *chip; + unsigned int addr; + int ret; + + adata = devm_kzalloc(sdev->dev, sizeof(struct acp_dev_data), + GFP_KERNEL); + if (!adata) + return -ENOMEM; + + adata->dev = sdev; + addr = pci_resource_start(pci, ACP_DSP_BAR); + sdev->bar[ACP_DSP_BAR] = devm_ioremap(sdev->dev, addr, pci_resource_len(pci, ACP_DSP_BAR)); + if (!sdev->bar[ACP_DSP_BAR]) { + dev_err(sdev->dev, "ioremap error\n"); + return -ENXIO; + } + + pci_set_master(pci); + + sdev->pdata->hw_pdata = adata; + + chip = get_chip_info(sdev->pdata); + if (!chip) { + dev_err(sdev->dev, "no such device supported, chip id:%x\n", pci->device); + return -EIO; + } + + adata->smn_dev = pci_get_device(PCI_VENDOR_ID_AMD, chip->host_bridge_id, NULL); + if (!adata->smn_dev) { + dev_err(sdev->dev, "Failed to get host bridge device\n"); + return -ENODEV; + } + + sdev->ipc_irq = pci->irq; + ret = request_threaded_irq(sdev->ipc_irq, acp_irq_handler, acp_irq_thread, + IRQF_SHARED, "AudioDSP", sdev); + if (ret < 0) { + dev_err(sdev->dev, "failed to register IRQ %d\n", + sdev->ipc_irq); + pci_dev_put(adata->smn_dev); + return ret; + } + + ret = acp_init(sdev); + if (ret < 0) { + free_irq(sdev->ipc_irq, sdev); + pci_dev_put(adata->smn_dev); + return ret; + } + + sdev->dsp_box.offset = 0; + sdev->dsp_box.size = BOX_SIZE_512; + + sdev->host_box.offset = sdev->dsp_box.offset + sdev->dsp_box.size; + sdev->host_box.size = BOX_SIZE_512; + + sdev->debug_box.offset = sdev->host_box.offset + sdev->host_box.size; + sdev->debug_box.size = BOX_SIZE_1024; + + acp_memory_init(sdev); + + acp_dsp_stream_init(sdev); + + return 0; +} +EXPORT_SYMBOL_NS(amd_sof_acp_probe, SND_SOC_SOF_AMD_COMMON); + +int amd_sof_acp_remove(struct snd_sof_dev *sdev) +{ + struct acp_dev_data *adata = sdev->pdata->hw_pdata; + + if (adata->smn_dev) + pci_dev_put(adata->smn_dev); + + if (sdev->ipc_irq) + free_irq(sdev->ipc_irq, sdev); + + return acp_reset(sdev); +} +EXPORT_SYMBOL_NS(amd_sof_acp_remove, SND_SOC_SOF_AMD_COMMON); + +MODULE_DESCRIPTION("AMD ACP sof driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h new file mode 100644 index 000000000000..dd3c072d0172 --- /dev/null +++ b/sound/soc/sof/amd/acp.h @@ -0,0 +1,251 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright(c) 2021 Advanced Micro Devices, Inc. All rights reserved. + * + * Author: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> + */ + +#ifndef __SOF_AMD_ACP_H +#define __SOF_AMD_ACP_H + +#include "../sof-priv.h" + +#define ACP_MAX_STREAM 8 + +#define ACP_DSP_BAR 0 + +#define ACP_HW_SEM_RETRY_COUNT 10000 +#define ACP_REG_POLL_INTERVAL 500 +#define ACP_REG_POLL_TIMEOUT_US 2000 +#define ACP_DMA_COMPLETE_TIMEOUT_US 5000 + +#define ACP_PGFSM_CNTL_POWER_ON_MASK 0x01 +#define ACP_PGFSM_STATUS_MASK 0x03 +#define ACP_POWERED_ON 0x00 +#define ACP_ASSERT_RESET 0x01 +#define ACP_RELEASE_RESET 0x00 +#define ACP_SOFT_RESET_DONE_MASK 0x00010001 + +#define ACP_DSP_INTR_EN_MASK 0x00000001 +#define ACP3X_SRAM_PTE_OFFSET 0x02050000 +#define ACP6X_SRAM_PTE_OFFSET 0x03800000 +#define PAGE_SIZE_4K_ENABLE 0x2 +#define ACP_PAGE_SIZE 0x1000 +#define ACP_DMA_CH_RUN 0x02 +#define ACP_MAX_DESC_CNT 0x02 +#define DSP_FW_RUN_ENABLE 0x01 +#define ACP_SHA_RUN 0x01 +#define ACP_SHA_RESET 0x02 +#define ACP_DMA_CH_RST 0x01 +#define ACP_DMA_CH_GRACEFUL_RST_EN 0x10 +#define ACP_ATU_CACHE_INVALID 0x01 +#define ACP_MAX_DESC 128 +#define ACPBUS_REG_BASE_OFFSET ACP_DMA_CNTL_0 + +#define ACP_DEFAULT_DRAM_LENGTH 0x00080000 +#define ACP3X_SCRATCH_MEMORY_ADDRESS 0x02050000 +#define ACP_SYSTEM_MEMORY_WINDOW 0x4000000 +#define ACP_IRAM_BASE_ADDRESS 0x000000 +#define ACP_DATA_RAM_BASE_ADDRESS 0x01000000 +#define ACP_DRAM_PAGE_COUNT 128 + +#define ACP_DSP_TO_HOST_IRQ 0x04 + +#define HOST_BRIDGE_CZN 0x1630 +#define HOST_BRIDGE_RMB 0x14B5 +#define ACP_SHA_STAT 0x8000 +#define ACP_PSP_TIMEOUT_COUNTER 5 +#define ACP_EXT_INTR_ERROR_STAT 0x20000000 +#define MP0_C2PMSG_114_REG 0x3810AC8 +#define MP0_C2PMSG_73_REG 0x3810A24 +#define MBOX_ACP_SHA_DMA_COMMAND 0x70000 +#define MBOX_DELAY 1000 +#define MBOX_READY_MASK 0x80000000 +#define MBOX_STATUS_MASK 0xFFFF + +#define BOX_SIZE_512 0x200 +#define BOX_SIZE_1024 0x400 + +struct acp_atu_grp_pte { + u32 low; + u32 high; +}; + +union dma_tx_cnt { + struct { + unsigned int count : 19; + unsigned int reserved : 12; + unsigned ioc : 1; + } bitfields, bits; + unsigned int u32_all; + signed int i32_all; +}; + +struct dma_descriptor { + unsigned int src_addr; + unsigned int dest_addr; + union dma_tx_cnt tx_cnt; + unsigned int reserved; +}; + +/* Scratch memory structure for communication b/w host and dsp */ +struct scratch_ipc_conf { + /* Debug memory */ + u8 sof_debug_box[1024]; + /* Exception memory*/ + u8 sof_except_box[1024]; + /* Stream buffer */ + u8 sof_stream_box[1024]; + /* Trace buffer */ + u8 sof_trace_box[1024]; + /* Host msg flag */ + u32 sof_host_msg_write; + /* Host ack flag*/ + u32 sof_host_ack_write; + /* DSP msg flag */ + u32 sof_dsp_msg_write; + /* Dsp ack flag */ + u32 sof_dsp_ack_write; +}; + +struct scratch_reg_conf { + struct scratch_ipc_conf info; + struct acp_atu_grp_pte grp1_pte[16]; + struct acp_atu_grp_pte grp2_pte[16]; + struct acp_atu_grp_pte grp3_pte[16]; + struct acp_atu_grp_pte grp4_pte[16]; + struct acp_atu_grp_pte grp5_pte[16]; + struct acp_atu_grp_pte grp6_pte[16]; + struct acp_atu_grp_pte grp7_pte[16]; + struct acp_atu_grp_pte grp8_pte[16]; + struct dma_descriptor dma_desc[64]; + unsigned int reg_offset[8]; + unsigned int buf_size[8]; + u8 acp_tx_fifo_buf[256]; + u8 acp_rx_fifo_buf[256]; + unsigned int reserve[]; +}; + +struct acp_dsp_stream { + struct list_head list; + struct snd_sof_dev *sdev; + struct snd_pcm_substream *substream; + struct snd_dma_buffer *dmab; + int num_pages; + int stream_tag; + int active; + unsigned int reg_offset; +}; + +struct sof_amd_acp_desc { + unsigned int rev; + unsigned int host_bridge_id; + unsigned int i2s_mode; + u32 pgfsm_base; + u32 ext_intr_stat; + u32 dsp_intr_base; + u32 sram_pte_offset; + u32 i2s_pin_config_offset; + u32 hw_semaphore_offset; + u32 acp_clkmux_sel; + u32 fusion_dsp_offset; +}; + +/* Common device data struct for ACP devices */ +struct acp_dev_data { + struct snd_sof_dev *dev; + unsigned int fw_bin_size; + unsigned int fw_data_bin_size; + u32 fw_bin_page_count; + dma_addr_t sha_dma_addr; + u8 *bin_buf; + dma_addr_t dma_addr; + u8 *data_buf; + struct dma_descriptor dscr_info[ACP_MAX_DESC]; + struct acp_dsp_stream stream_buf[ACP_MAX_STREAM]; + struct acp_dsp_stream *dtrace_stream; + struct pci_dev *smn_dev; +}; + +void memcpy_to_scratch(struct snd_sof_dev *sdev, u32 offset, unsigned int *src, size_t bytes); +void memcpy_from_scratch(struct snd_sof_dev *sdev, u32 offset, unsigned int *dst, size_t bytes); + +int acp_dma_status(struct acp_dev_data *adata, unsigned char ch); +int configure_and_run_dma(struct acp_dev_data *adata, unsigned int src_addr, + unsigned int dest_addr, int dsp_data_size); +int configure_and_run_sha_dma(struct acp_dev_data *adata, void *image_addr, + unsigned int start_addr, unsigned int dest_addr, + unsigned int image_length); + +/* ACP device probe/remove */ +int amd_sof_acp_probe(struct snd_sof_dev *sdev); +int amd_sof_acp_remove(struct snd_sof_dev *sdev); + +/* DSP Loader callbacks */ +int acp_sof_dsp_run(struct snd_sof_dev *sdev); +int acp_dsp_pre_fw_run(struct snd_sof_dev *sdev); +int acp_get_bar_index(struct snd_sof_dev *sdev, u32 type); + +/* Block IO callbacks */ +int acp_dsp_block_write(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type, + u32 offset, void *src, size_t size); +int acp_dsp_block_read(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type, + u32 offset, void *dest, size_t size); + +/* IPC callbacks */ +irqreturn_t acp_sof_ipc_irq_thread(int irq, void *context); +int acp_sof_ipc_msg_data(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, + void *p, size_t sz); +int acp_sof_ipc_send_msg(struct snd_sof_dev *sdev, + struct snd_sof_ipc_msg *msg); +int acp_sof_ipc_get_mailbox_offset(struct snd_sof_dev *sdev); +int acp_sof_ipc_get_window_offset(struct snd_sof_dev *sdev, u32 id); +void acp_mailbox_write(struct snd_sof_dev *sdev, u32 offset, void *message, size_t bytes); +void acp_mailbox_read(struct snd_sof_dev *sdev, u32 offset, void *message, size_t bytes); + +/* ACP - DSP stream callbacks */ +int acp_dsp_stream_config(struct snd_sof_dev *sdev, struct acp_dsp_stream *stream); +int acp_dsp_stream_init(struct snd_sof_dev *sdev); +struct acp_dsp_stream *acp_dsp_stream_get(struct snd_sof_dev *sdev, int tag); +int acp_dsp_stream_put(struct snd_sof_dev *sdev, struct acp_dsp_stream *acp_stream); + +/* + * DSP PCM Operations. + */ +int acp_pcm_open(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream); +int acp_pcm_close(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream); +int acp_pcm_hw_params(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_sof_platform_stream_params *platform_params); + +extern struct snd_sof_dsp_ops sof_acp_common_ops; + +extern struct snd_sof_dsp_ops sof_renoir_ops; +int sof_renoir_ops_init(struct snd_sof_dev *sdev); +extern struct snd_sof_dsp_ops sof_rembrandt_ops; +int sof_rembrandt_ops_init(struct snd_sof_dev *sdev); + +int acp_dai_probe(struct snd_soc_dai *dai); +struct snd_soc_acpi_mach *amd_sof_machine_select(struct snd_sof_dev *sdev); +/* Machine configuration */ +int snd_amd_acp_find_config(struct pci_dev *pci); + +/* Trace */ +int acp_sof_trace_init(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab, + struct sof_ipc_dma_trace_params_ext *dtrace_params); +int acp_sof_trace_release(struct snd_sof_dev *sdev); + +/* PM Callbacks */ +int amd_sof_acp_suspend(struct snd_sof_dev *sdev, u32 target_state); +int amd_sof_acp_resume(struct snd_sof_dev *sdev); + +static inline const struct sof_amd_acp_desc *get_chip_info(struct snd_sof_pdata *pdata) +{ + const struct sof_dev_desc *desc = pdata->desc; + + return desc->chip_info; +} +#endif diff --git a/sound/soc/sof/amd/pci-rmb.c b/sound/soc/sof/amd/pci-rmb.c new file mode 100644 index 000000000000..4e1de462b431 --- /dev/null +++ b/sound/soc/sof/amd/pci-rmb.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2022 Advanced Micro Devices, Inc. All rights reserved. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> + +/*. + * PCI interface for Rembrandt ACP device + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <sound/sof.h> +#include <sound/soc-acpi.h> + +#include "../ops.h" +#include "../sof-pci-dev.h" +#include "../../amd/mach-config.h" +#include "acp.h" +#include "acp-dsp-offset.h" + +#define ACP6x_REG_START 0x1240000 +#define ACP6x_REG_END 0x125C000 + +static struct platform_device *dmic_dev; +static struct platform_device *pdev; + +static const struct resource rembrandt_res[] = { + { + .start = 0, + .end = ACP6x_REG_END - ACP6x_REG_START, + .name = "acp_mem", + .flags = IORESOURCE_MEM, + }, + { + .start = 0, + .end = 0, + .name = "acp_dai_irq", + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct sof_amd_acp_desc rembrandt_chip_info = { + .rev = 6, + .host_bridge_id = HOST_BRIDGE_RMB, + .i2s_mode = 0x0a, + .pgfsm_base = ACP6X_PGFSM_BASE, + .ext_intr_stat = ACP6X_EXT_INTR_STAT, + .dsp_intr_base = ACP6X_DSP_SW_INTR_BASE, + .sram_pte_offset = ACP6X_SRAM_PTE_OFFSET, + .i2s_pin_config_offset = ACP6X_I2S_PIN_CONFIG, + .hw_semaphore_offset = ACP6X_AXI2DAGB_SEM_0, + .acp_clkmux_sel = ACP6X_CLKMUX_SEL, + .fusion_dsp_offset = ACP6X_DSP_FUSION_RUNSTALL, +}; + +static const struct sof_dev_desc rembrandt_desc = { + .machines = snd_soc_acpi_amd_rmb_sof_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .chip_info = &rembrandt_chip_info, + .ipc_supported_mask = BIT(SOF_IPC), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "amd/sof", + }, + .default_tplg_path = { + [SOF_IPC] = "amd/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-rmb.ri", + }, + .nocodec_tplg_filename = "sof-acp.tplg", + .ops = &sof_rembrandt_ops, + .ops_init = sof_rembrandt_ops_init, +}; + +static int acp_pci_rmb_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +{ + struct platform_device_info pdevinfo; + struct device *dev = &pci->dev; + const struct resource *res_i2s; + struct resource *res; + unsigned int flag, i, addr; + int ret; + + flag = snd_amd_acp_find_config(pci); + if (flag != FLAG_AMD_SOF && flag != FLAG_AMD_SOF_ONLY_DMIC) + return -ENODEV; + + ret = sof_pci_probe(pci, pci_id); + if (ret != 0) + return ret; + + dmic_dev = platform_device_register_data(dev, "dmic-codec", PLATFORM_DEVID_NONE, NULL, 0); + if (IS_ERR(dmic_dev)) { + dev_err(dev, "failed to create DMIC device\n"); + sof_pci_remove(pci); + return PTR_ERR(dmic_dev); + } + + /* Register platform device only if flag set to FLAG_AMD_SOF_ONLY_DMIC */ + if (flag != FLAG_AMD_SOF_ONLY_DMIC) + return 0; + + addr = pci_resource_start(pci, 0); + res = devm_kzalloc(&pci->dev, sizeof(struct resource) * ARRAY_SIZE(rembrandt_res), + GFP_KERNEL); + if (!res) { + platform_device_unregister(dmic_dev); + sof_pci_remove(pci); + return -ENOMEM; + } + + res_i2s = rembrandt_res; + for (i = 0; i < ARRAY_SIZE(rembrandt_res); i++, res_i2s++) { + res[i].name = res_i2s->name; + res[i].flags = res_i2s->flags; + res[i].start = addr + res_i2s->start; + res[i].end = addr + res_i2s->end; + if (res_i2s->flags == IORESOURCE_IRQ) { + res[i].start = pci->irq; + res[i].end = res[i].start; + } + } + + memset(&pdevinfo, 0, sizeof(pdevinfo)); + + /* + * We have common PCI driver probe for ACP device but we have to support I2S without SOF + * for some distributions. Register platform device that will be used to support non dsp + * ACP's audio ends points on some machines. + */ + pdevinfo.name = "acp_asoc_rembrandt"; + pdevinfo.id = 0; + pdevinfo.parent = &pci->dev; + pdevinfo.num_res = ARRAY_SIZE(rembrandt_res); + pdevinfo.res = &res[0]; + + pdev = platform_device_register_full(&pdevinfo); + if (IS_ERR(pdev)) { + dev_err(&pci->dev, "cannot register %s device\n", pdevinfo.name); + platform_device_unregister(dmic_dev); + sof_pci_remove(pci); + ret = PTR_ERR(pdev); + } + + return ret; +}; + +static void acp_pci_rmb_remove(struct pci_dev *pci) +{ + if (dmic_dev) + platform_device_unregister(dmic_dev); + if (pdev) + platform_device_unregister(pdev); + + sof_pci_remove(pci); +} + +/* PCI IDs */ +static const struct pci_device_id rmb_pci_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID), + .driver_data = (unsigned long)&rembrandt_desc}, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, rmb_pci_ids); + +/* pci_driver definition */ +static struct pci_driver snd_sof_pci_amd_rmb_driver = { + .name = KBUILD_MODNAME, + .id_table = rmb_pci_ids, + .probe = acp_pci_rmb_probe, + .remove = acp_pci_rmb_remove, +}; +module_pci_driver(snd_sof_pci_amd_rmb_driver); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_IMPORT_NS(SND_SOC_SOF_AMD_COMMON); +MODULE_IMPORT_NS(SND_SOC_SOF_PCI_DEV); diff --git a/sound/soc/sof/amd/pci-rn.c b/sound/soc/sof/amd/pci-rn.c new file mode 100644 index 000000000000..fca40b261671 --- /dev/null +++ b/sound/soc/sof/amd/pci-rn.c @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Advanced Micro Devices, Inc. All rights reserved. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> + +/* + * PCI interface for Renoir ACP device + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <sound/sof.h> +#include <sound/soc-acpi.h> + +#include "../ops.h" +#include "../sof-pci-dev.h" +#include "../../amd/mach-config.h" +#include "acp.h" +#include "acp-dsp-offset.h" + +#define ACP3x_REG_START 0x1240000 +#define ACP3x_REG_END 0x125C000 + +static struct platform_device *dmic_dev; +static struct platform_device *pdev; + +static const struct resource renoir_res[] = { + { + .start = 0, + .end = ACP3x_REG_END - ACP3x_REG_START, + .name = "acp_mem", + .flags = IORESOURCE_MEM, + }, + { + .start = 0, + .end = 0, + .name = "acp_dai_irq", + .flags = IORESOURCE_IRQ, + }, +}; + +static const struct sof_amd_acp_desc renoir_chip_info = { + .rev = 3, + .host_bridge_id = HOST_BRIDGE_CZN, + .i2s_mode = 0x04, + .pgfsm_base = ACP3X_PGFSM_BASE, + .ext_intr_stat = ACP3X_EXT_INTR_STAT, + .dsp_intr_base = ACP3X_DSP_SW_INTR_BASE, + .sram_pte_offset = ACP3X_SRAM_PTE_OFFSET, + .i2s_pin_config_offset = ACP3X_I2S_PIN_CONFIG, + .hw_semaphore_offset = ACP3X_AXI2DAGB_SEM_0, + .acp_clkmux_sel = ACP3X_CLKMUX_SEL, +}; + +static const struct sof_dev_desc renoir_desc = { + .machines = snd_soc_acpi_amd_sof_machines, + .use_acpi_target_states = true, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .chip_info = &renoir_chip_info, + .ipc_supported_mask = BIT(SOF_IPC), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "amd/sof", + }, + .default_tplg_path = { + [SOF_IPC] = "amd/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-rn.ri", + }, + .nocodec_tplg_filename = "sof-acp.tplg", + .ops = &sof_renoir_ops, + .ops_init = sof_renoir_ops_init, +}; + +static int acp_pci_rn_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +{ + struct platform_device_info pdevinfo; + struct device *dev = &pci->dev; + const struct resource *res_i2s; + struct resource *res; + unsigned int flag, i, addr; + int ret; + + flag = snd_amd_acp_find_config(pci); + if (flag != FLAG_AMD_SOF && flag != FLAG_AMD_SOF_ONLY_DMIC) + return -ENODEV; + + ret = sof_pci_probe(pci, pci_id); + if (ret != 0) + return ret; + + dmic_dev = platform_device_register_data(dev, "dmic-codec", PLATFORM_DEVID_NONE, NULL, 0); + if (IS_ERR(dmic_dev)) { + dev_err(dev, "failed to create DMIC device\n"); + sof_pci_remove(pci); + return PTR_ERR(dmic_dev); + } + + /* Register platform device only if flag set to FLAG_AMD_SOF_ONLY_DMIC */ + if (flag != FLAG_AMD_SOF_ONLY_DMIC) + return 0; + + addr = pci_resource_start(pci, 0); + res = devm_kzalloc(&pci->dev, sizeof(struct resource) * ARRAY_SIZE(renoir_res), GFP_KERNEL); + if (!res) { + sof_pci_remove(pci); + platform_device_unregister(dmic_dev); + return -ENOMEM; + } + + res_i2s = renoir_res; + for (i = 0; i < ARRAY_SIZE(renoir_res); i++, res_i2s++) { + res[i].name = res_i2s->name; + res[i].flags = res_i2s->flags; + res[i].start = addr + res_i2s->start; + res[i].end = addr + res_i2s->end; + if (res_i2s->flags == IORESOURCE_IRQ) { + res[i].start = pci->irq; + res[i].end = res[i].start; + } + } + + memset(&pdevinfo, 0, sizeof(pdevinfo)); + + /* + * We have common PCI driver probe for ACP device but we have to support I2S without SOF + * for some distributions. Register platform device that will be used to support non dsp + * ACP's audio ends points on some machines. + */ + + pdevinfo.name = "acp_asoc_renoir"; + pdevinfo.id = 0; + pdevinfo.parent = &pci->dev; + pdevinfo.num_res = ARRAY_SIZE(renoir_res); + pdevinfo.res = &res[0]; + + pdev = platform_device_register_full(&pdevinfo); + if (IS_ERR(pdev)) { + dev_err(&pci->dev, "cannot register %s device\n", pdevinfo.name); + sof_pci_remove(pci); + platform_device_unregister(dmic_dev); + ret = PTR_ERR(pdev); + } + + return ret; +}; + +static void acp_pci_rn_remove(struct pci_dev *pci) +{ + if (dmic_dev) + platform_device_unregister(dmic_dev); + if (pdev) + platform_device_unregister(pdev); + + return sof_pci_remove(pci); +} + +/* PCI IDs */ +static const struct pci_device_id rn_pci_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_PCI_DEV_ID), + .driver_data = (unsigned long)&renoir_desc}, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, rn_pci_ids); + +/* pci_driver definition */ +static struct pci_driver snd_sof_pci_amd_rn_driver = { + .name = KBUILD_MODNAME, + .id_table = rn_pci_ids, + .probe = acp_pci_rn_probe, + .remove = acp_pci_rn_remove, + .driver = { + .pm = &sof_pci_pm, + }, +}; +module_pci_driver(snd_sof_pci_amd_rn_driver); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_IMPORT_NS(SND_SOC_SOF_AMD_COMMON); +MODULE_IMPORT_NS(SND_SOC_SOF_PCI_DEV); diff --git a/sound/soc/sof/amd/rembrandt.c b/sound/soc/sof/amd/rembrandt.c new file mode 100644 index 000000000000..dcb64a23e121 --- /dev/null +++ b/sound/soc/sof/amd/rembrandt.c @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2022 Advanced Micro Devices, Inc. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> + +/* + * Hardware interface for Audio DSP on Rembrandt platform + */ + +#include <linux/platform_device.h> +#include <linux/module.h> + +#include "../ops.h" +#include "../sof-audio.h" +#include "acp.h" +#include "acp-dsp-offset.h" + +#define I2S_HS_INSTANCE 0 +#define I2S_BT_INSTANCE 1 +#define I2S_SP_INSTANCE 2 +#define PDM_DMIC_INSTANCE 3 + +static struct snd_soc_dai_driver rembrandt_sof_dai[] = { + [I2S_HS_INSTANCE] = { + .id = I2S_HS_INSTANCE, + .name = "acp-sof-hs", + .playback = { + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 96000, + }, + .capture = { + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + /* Supporting only stereo for I2S HS controller capture */ + .channels_min = 2, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .probe = &acp_dai_probe, + }, + + [I2S_BT_INSTANCE] = { + .id = I2S_BT_INSTANCE, + .name = "acp-sof-bt", + .playback = { + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 96000, + }, + .capture = { + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + /* Supporting only stereo for I2S BT controller capture */ + .channels_min = 2, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .probe = &acp_dai_probe, + }, + + [I2S_SP_INSTANCE] = { + .id = I2S_SP_INSTANCE, + .name = "acp-sof-sp", + .playback = { + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 96000, + }, + .capture = { + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + /* Supporting only stereo for I2S SP controller capture */ + .channels_min = 2, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .probe = &acp_dai_probe, + }, + + [PDM_DMIC_INSTANCE] = { + .id = PDM_DMIC_INSTANCE, + .name = "acp-sof-dmic", + .capture = { + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 4, + .rate_min = 8000, + .rate_max = 48000, + }, + }, +}; + +/* Rembrandt ops */ +struct snd_sof_dsp_ops sof_rembrandt_ops; +EXPORT_SYMBOL_NS(sof_rembrandt_ops, SND_SOC_SOF_AMD_COMMON); + +int sof_rembrandt_ops_init(struct snd_sof_dev *sdev) +{ + /* common defaults */ + memcpy(&sof_rembrandt_ops, &sof_acp_common_ops, sizeof(struct snd_sof_dsp_ops)); + + sof_rembrandt_ops.drv = rembrandt_sof_dai; + sof_rembrandt_ops.num_drv = ARRAY_SIZE(rembrandt_sof_dai); + + return 0; +} + +MODULE_IMPORT_NS(SND_SOC_SOF_AMD_COMMON); +MODULE_DESCRIPTION("REMBRANDT SOF Driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/amd/renoir.c b/sound/soc/sof/amd/renoir.c new file mode 100644 index 000000000000..6ea8727f977e --- /dev/null +++ b/sound/soc/sof/amd/renoir.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Advanced Micro Devices, Inc. +// +// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> + +/* + * Hardware interface for Audio DSP on Renoir platform + */ + +#include <linux/platform_device.h> +#include <linux/module.h> + +#include "../ops.h" +#include "../sof-audio.h" +#include "acp.h" +#include "acp-dsp-offset.h" + +#define I2S_BT_INSTANCE 0 +#define I2S_SP_INSTANCE 1 +#define PDM_DMIC_INSTANCE 2 + +static struct snd_soc_dai_driver renoir_sof_dai[] = { + [I2S_BT_INSTANCE] = { + .id = I2S_BT_INSTANCE, + .name = "acp-sof-bt", + .playback = { + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 96000, + }, + .capture = { + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + /* Supporting only stereo for I2S BT controller capture */ + .channels_min = 2, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .probe = &acp_dai_probe, + }, + + [I2S_SP_INSTANCE] = { + .id = I2S_SP_INSTANCE, + .name = "acp-sof-sp", + .playback = { + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 96000, + }, + .capture = { + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE, + /* Supporting only stereo for I2S SP controller capture */ + .channels_min = 2, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 48000, + }, + .probe = &acp_dai_probe, + }, + + [PDM_DMIC_INSTANCE] = { + .id = PDM_DMIC_INSTANCE, + .name = "acp-sof-dmic", + .capture = { + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 4, + .rate_min = 8000, + .rate_max = 48000, + }, + }, +}; + +/* Renoir ops */ +struct snd_sof_dsp_ops sof_renoir_ops; +EXPORT_SYMBOL_NS(sof_renoir_ops, SND_SOC_SOF_AMD_COMMON); + +int sof_renoir_ops_init(struct snd_sof_dev *sdev) +{ + /* common defaults */ + memcpy(&sof_renoir_ops, &sof_acp_common_ops, sizeof(struct snd_sof_dsp_ops)); + + sof_renoir_ops.drv = renoir_sof_dai; + sof_renoir_ops.num_drv = ARRAY_SIZE(renoir_sof_dai); + + return 0; +} + +MODULE_IMPORT_NS(SND_SOC_SOF_AMD_COMMON); +MODULE_DESCRIPTION("RENOIR SOF Driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/compress.c b/sound/soc/sof/compress.c index 01ca85f0b87f..8e1a9ba111ad 100644 --- a/sound/soc/sof/compress.c +++ b/sound/soc/sof/compress.c @@ -9,6 +9,23 @@ #include <sound/compress_driver.h> #include "sof-audio.h" #include "sof-priv.h" +#include "sof-utils.h" + +static void sof_set_transferred_bytes(struct sof_compr_stream *sstream, + u64 host_pos, u64 buffer_size) +{ + u64 prev_pos; + unsigned int copied; + + div64_u64_rem(sstream->copied_total, buffer_size, &prev_pos); + + if (host_pos < prev_pos) + copied = (buffer_size - prev_pos) + host_pos; + else + copied = host_pos - prev_pos; + + sstream->copied_total += copied; +} static void snd_sof_compr_fragment_elapsed_work(struct work_struct *work) { @@ -29,14 +46,18 @@ void snd_sof_compr_init_elapsed_work(struct work_struct *work) */ void snd_sof_compr_fragment_elapsed(struct snd_compr_stream *cstream) { - struct snd_soc_component *component; struct snd_soc_pcm_runtime *rtd; + struct snd_compr_runtime *crtd; + struct snd_soc_component *component; + struct sof_compr_stream *sstream; struct snd_sof_pcm *spcm; if (!cstream) return; rtd = cstream->private_data; + crtd = cstream->runtime; + sstream = crtd->private_data; component = snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME); spcm = snd_sof_find_spcm_dai(component, rtd); @@ -46,6 +67,320 @@ void snd_sof_compr_fragment_elapsed(struct snd_compr_stream *cstream) return; } + sof_set_transferred_bytes(sstream, spcm->stream[cstream->direction].posn.host_posn, + crtd->buffer_size); + /* use the same workqueue-based solution as for PCM, cf. snd_sof_pcm_elapsed */ schedule_work(&spcm->stream[cstream->direction].period_elapsed_work); } + +static int create_page_table(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + unsigned char *dma_area, size_t size) +{ + struct snd_dma_buffer *dmab = cstream->runtime->dma_buffer_p; + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + int dir = cstream->direction; + struct snd_sof_pcm *spcm; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) + return -EINVAL; + + return snd_sof_create_page_table(component->dev, dmab, + spcm->stream[dir].page_table.area, size); +} + +static int sof_compr_open(struct snd_soc_component *component, + struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_compr_runtime *crtd = cstream->runtime; + struct sof_compr_stream *sstream; + struct snd_sof_pcm *spcm; + int dir; + + sstream = kzalloc(sizeof(*sstream), GFP_KERNEL); + if (!sstream) + return -ENOMEM; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) { + kfree(sstream); + return -EINVAL; + } + + dir = cstream->direction; + + if (spcm->stream[dir].cstream) { + kfree(sstream); + return -EBUSY; + } + + spcm->stream[dir].cstream = cstream; + spcm->stream[dir].posn.host_posn = 0; + spcm->stream[dir].posn.dai_posn = 0; + spcm->prepared[dir] = false; + + crtd->private_data = sstream; + + return 0; +} + +static int sof_compr_free(struct snd_soc_component *component, + struct snd_compr_stream *cstream) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct sof_compr_stream *sstream = cstream->runtime->private_data; + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct sof_ipc_stream stream; + struct sof_ipc_reply reply; + struct snd_sof_pcm *spcm; + int ret = 0; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) + return -EINVAL; + + stream.hdr.size = sizeof(stream); + stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_FREE; + stream.comp_id = spcm->stream[cstream->direction].comp_id; + + if (spcm->prepared[cstream->direction]) { + ret = sof_ipc_tx_message(sdev->ipc, &stream, sizeof(stream), + &reply, sizeof(reply)); + if (!ret) + spcm->prepared[cstream->direction] = false; + } + + cancel_work_sync(&spcm->stream[cstream->direction].period_elapsed_work); + spcm->stream[cstream->direction].cstream = NULL; + kfree(sstream); + + return ret; +} + +static int sof_compr_set_params(struct snd_soc_component *component, + struct snd_compr_stream *cstream, struct snd_compr_params *params) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_compr_runtime *crtd = cstream->runtime; + struct sof_ipc_pcm_params_reply ipc_params_reply; + struct sof_ipc_fw_ready *ready = &sdev->fw_ready; + struct sof_ipc_fw_version *v = &ready->version; + struct sof_compr_stream *sstream; + struct sof_ipc_pcm_params *pcm; + struct snd_sof_pcm *spcm; + size_t ext_data_size; + int ret; + + if (v->abi_version < SOF_ABI_VER(3, 22, 0)) { + dev_err(component->dev, + "Compress params not supported with FW ABI version %d:%d:%d\n", + SOF_ABI_VERSION_MAJOR(v->abi_version), + SOF_ABI_VERSION_MINOR(v->abi_version), + SOF_ABI_VERSION_PATCH(v->abi_version)); + return -EINVAL; + } + + sstream = crtd->private_data; + + spcm = snd_sof_find_spcm_dai(component, rtd); + + if (!spcm) + return -EINVAL; + + ext_data_size = sizeof(params->codec); + + if (sizeof(*pcm) + ext_data_size > sdev->ipc->max_payload_size) + return -EINVAL; + + pcm = kzalloc(sizeof(*pcm) + ext_data_size, GFP_KERNEL); + if (!pcm) + return -ENOMEM; + + cstream->dma_buffer.dev.type = SNDRV_DMA_TYPE_DEV_SG; + cstream->dma_buffer.dev.dev = sdev->dev; + ret = snd_compr_malloc_pages(cstream, crtd->buffer_size); + if (ret < 0) + goto out; + + ret = create_page_table(component, cstream, crtd->dma_area, crtd->dma_bytes); + if (ret < 0) + goto out; + + pcm->params.buffer.pages = PFN_UP(crtd->dma_bytes); + pcm->hdr.size = sizeof(*pcm) + ext_data_size; + pcm->hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS; + + pcm->comp_id = spcm->stream[cstream->direction].comp_id; + pcm->params.hdr.size = sizeof(pcm->params) + ext_data_size; + pcm->params.buffer.phy_addr = spcm->stream[cstream->direction].page_table.addr; + pcm->params.buffer.size = crtd->dma_bytes; + pcm->params.direction = cstream->direction; + pcm->params.channels = params->codec.ch_out; + pcm->params.rate = params->codec.sample_rate; + pcm->params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED; + pcm->params.frame_fmt = SOF_IPC_FRAME_S32_LE; + pcm->params.sample_container_bytes = + snd_pcm_format_physical_width(SNDRV_PCM_FORMAT_S32) >> 3; + pcm->params.host_period_bytes = params->buffer.fragment_size; + pcm->params.ext_data_length = ext_data_size; + + memcpy((u8 *)pcm->params.ext_data, ¶ms->codec, ext_data_size); + + ret = sof_ipc_tx_message(sdev->ipc, pcm, sizeof(*pcm) + ext_data_size, + &ipc_params_reply, sizeof(ipc_params_reply)); + if (ret < 0) { + dev_err(component->dev, "error ipc failed\n"); + goto out; + } + + sstream->sampling_rate = params->codec.sample_rate; + sstream->channels = params->codec.ch_out; + sstream->sample_container_bytes = pcm->params.sample_container_bytes; + + spcm->prepared[cstream->direction] = true; + +out: + kfree(pcm); + + return ret; +} + +static int sof_compr_get_params(struct snd_soc_component *component, + struct snd_compr_stream *cstream, struct snd_codec *params) +{ + /* TODO: we don't query the supported codecs for now, if the + * application asks for an unsupported codec the set_params() will fail. + */ + return 0; +} + +static int sof_compr_trigger(struct snd_soc_component *component, + struct snd_compr_stream *cstream, int cmd) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct sof_ipc_stream stream; + struct sof_ipc_reply reply; + struct snd_sof_pcm *spcm; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) + return -EINVAL; + + stream.hdr.size = sizeof(stream); + stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG; + stream.comp_id = spcm->stream[cstream->direction].comp_id; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_START; + break; + case SNDRV_PCM_TRIGGER_STOP: + stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_STOP; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_PAUSE; + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_RELEASE; + break; + default: + dev_err(component->dev, "error: unhandled trigger cmd %d\n", cmd); + break; + } + + return sof_ipc_tx_message(sdev->ipc, &stream, sizeof(stream), + &reply, sizeof(reply)); +} + +static int sof_compr_copy_playback(struct snd_compr_runtime *rtd, + char __user *buf, size_t count) +{ + void *ptr; + unsigned int offset, n; + int ret; + + div_u64_rem(rtd->total_bytes_available, rtd->buffer_size, &offset); + ptr = rtd->dma_area + offset; + n = rtd->buffer_size - offset; + + if (count < n) { + ret = copy_from_user(ptr, buf, count); + } else { + ret = copy_from_user(ptr, buf, n); + ret += copy_from_user(rtd->dma_area, buf + n, count - n); + } + + return count - ret; +} + +static int sof_compr_copy_capture(struct snd_compr_runtime *rtd, + char __user *buf, size_t count) +{ + void *ptr; + unsigned int offset, n; + int ret; + + 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); + } + + return count - ret; +} + +static int sof_compr_copy(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + char __user *buf, size_t count) +{ + struct snd_compr_runtime *rtd = cstream->runtime; + + if (count > rtd->buffer_size) + count = rtd->buffer_size; + + if (cstream->direction == SND_COMPRESS_PLAYBACK) + return sof_compr_copy_playback(rtd, buf, count); + else + return sof_compr_copy_capture(rtd, buf, count); +} + +static int sof_compr_pointer(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp) +{ + struct snd_sof_pcm *spcm; + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct sof_compr_stream *sstream = cstream->runtime->private_data; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) + return -EINVAL; + + tstamp->sampling_rate = sstream->sampling_rate; + tstamp->copied_total = sstream->copied_total; + tstamp->pcm_io_frames = div_u64(spcm->stream[cstream->direction].posn.dai_posn, + sstream->channels * sstream->sample_container_bytes); + + return 0; +} + +struct snd_compress_ops sof_compressed_ops = { + .open = sof_compr_open, + .free = sof_compr_free, + .set_params = sof_compr_set_params, + .get_params = sof_compr_get_params, + .trigger = sof_compr_trigger, + .pointer = sof_compr_pointer, + .copy = sof_compr_copy, +}; +EXPORT_SYMBOL(sof_compressed_ops); diff --git a/sound/soc/sof/control.c b/sound/soc/sof/control.c index bb1dfe4f6d40..e0e9efd25d34 100644 --- a/sound/soc/sof/control.c +++ b/sound/soc/sof/control.c @@ -15,106 +15,17 @@ #include "sof-priv.h" #include "sof-audio.h" -static void update_mute_led(struct snd_sof_control *scontrol, - struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - int temp = 0; - int mask; - int i; - - mask = 1U << snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); - - for (i = 0; i < scontrol->num_channels; i++) { - if (ucontrol->value.integer.value[i]) { - temp |= mask; - break; - } - } - - if (temp == scontrol->led_ctl.led_value) - return; - - scontrol->led_ctl.led_value = temp; - -#if IS_REACHABLE(CONFIG_LEDS_TRIGGER_AUDIO) - if (!scontrol->led_ctl.direction) - ledtrig_audio_set(LED_AUDIO_MUTE, temp ? LED_OFF : LED_ON); - else - ledtrig_audio_set(LED_AUDIO_MICMUTE, temp ? LED_OFF : LED_ON); -#endif -} - -static inline u32 mixer_to_ipc(unsigned int value, u32 *volume_map, int size) -{ - if (value >= size) - return volume_map[size - 1]; - - return volume_map[value]; -} - -static inline u32 ipc_to_mixer(u32 value, u32 *volume_map, int size) -{ - int i; - - for (i = 0; i < size; i++) { - if (volume_map[i] >= value) - return i; - } - - return i - 1; -} - -static void snd_sof_refresh_control(struct snd_sof_control *scontrol) -{ - struct sof_ipc_ctrl_data *cdata = scontrol->control_data; - struct snd_soc_component *scomp = scontrol->scomp; - u32 ipc_cmd; - int ret; - - if (!scontrol->comp_data_dirty) - return; - - if (!pm_runtime_active(scomp->dev)) - return; - - if (scontrol->cmd == SOF_CTRL_CMD_BINARY) - ipc_cmd = SOF_IPC_COMP_GET_DATA; - else - ipc_cmd = SOF_IPC_COMP_GET_VALUE; - - /* set the ABI header values */ - cdata->data->magic = SOF_ABI_MAGIC; - cdata->data->abi = SOF_ABI_VERSION; - - /* refresh the component data from DSP */ - scontrol->comp_data_dirty = false; - ret = snd_sof_ipc_set_get_comp_data(scontrol, ipc_cmd, - SOF_CTRL_TYPE_VALUE_CHAN_GET, - scontrol->cmd, false); - if (ret < 0) { - dev_err(scomp->dev, "error: failed to get control data: %d\n", ret); - /* Set the flag to re-try next time to get the data */ - scontrol->comp_data_dirty = true; - } -} - int snd_sof_volume_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct soc_mixer_control *sm = - (struct soc_mixer_control *)kcontrol->private_value; + struct soc_mixer_control *sm = (struct soc_mixer_control *)kcontrol->private_value; struct snd_sof_control *scontrol = sm->dobj.private; - struct sof_ipc_ctrl_data *cdata = scontrol->control_data; - unsigned int i, channels = scontrol->num_channels; - - snd_sof_refresh_control(scontrol); + struct snd_soc_component *scomp = scontrol->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; - /* read back each channel */ - for (i = 0; i < channels; i++) - ucontrol->value.integer.value[i] = - ipc_to_mixer(cdata->chanv[i].value, - scontrol->volume_table, sm->max + 1); + if (tplg_ops->control->volume_get) + return tplg_ops->control->volume_get(scontrol, ucontrol); return 0; } @@ -122,32 +33,16 @@ int snd_sof_volume_get(struct snd_kcontrol *kcontrol, int snd_sof_volume_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct soc_mixer_control *sm = - (struct soc_mixer_control *)kcontrol->private_value; + struct soc_mixer_control *sm = (struct soc_mixer_control *)kcontrol->private_value; struct snd_sof_control *scontrol = sm->dobj.private; struct snd_soc_component *scomp = scontrol->scomp; - struct sof_ipc_ctrl_data *cdata = scontrol->control_data; - unsigned int i, channels = scontrol->num_channels; - bool change = false; - u32 value; - - /* update each channel */ - for (i = 0; i < channels; i++) { - value = mixer_to_ipc(ucontrol->value.integer.value[i], - scontrol->volume_table, sm->max + 1); - change = change || (value != cdata->chanv[i].value); - cdata->chanv[i].channel = i; - cdata->chanv[i].value = value; - } + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; - /* notify DSP of mixer updates */ - if (pm_runtime_active(scomp->dev)) - snd_sof_ipc_set_get_comp_data(scontrol, - SOF_IPC_COMP_SET_VALUE, - SOF_CTRL_TYPE_VALUE_CHAN_SET, - SOF_CTRL_CMD_VOLUME, - true); - return change; + if (tplg_ops->control->volume_put) + return tplg_ops->control->volume_put(scontrol, ucontrol); + + return false; } int snd_sof_volume_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) @@ -175,17 +70,14 @@ int snd_sof_volume_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info int snd_sof_switch_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct soc_mixer_control *sm = - (struct soc_mixer_control *)kcontrol->private_value; + struct soc_mixer_control *sm = (struct soc_mixer_control *)kcontrol->private_value; struct snd_sof_control *scontrol = sm->dobj.private; - struct sof_ipc_ctrl_data *cdata = scontrol->control_data; - unsigned int i, channels = scontrol->num_channels; - - snd_sof_refresh_control(scontrol); + struct snd_soc_component *scomp = scontrol->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; - /* read back each channel */ - for (i = 0; i < channels; i++) - ucontrol->value.integer.value[i] = cdata->chanv[i].value; + if (tplg_ops->control->switch_get) + return tplg_ops->control->switch_get(scontrol, ucontrol); return 0; } @@ -193,51 +85,29 @@ int snd_sof_switch_get(struct snd_kcontrol *kcontrol, int snd_sof_switch_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct soc_mixer_control *sm = - (struct soc_mixer_control *)kcontrol->private_value; + struct soc_mixer_control *sm = (struct soc_mixer_control *)kcontrol->private_value; struct snd_sof_control *scontrol = sm->dobj.private; struct snd_soc_component *scomp = scontrol->scomp; - struct sof_ipc_ctrl_data *cdata = scontrol->control_data; - unsigned int i, channels = scontrol->num_channels; - bool change = false; - u32 value; - - /* update each channel */ - for (i = 0; i < channels; i++) { - value = ucontrol->value.integer.value[i]; - change = change || (value != cdata->chanv[i].value); - cdata->chanv[i].channel = i; - cdata->chanv[i].value = value; - } + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; - if (scontrol->led_ctl.use_led) - update_mute_led(scontrol, kcontrol, ucontrol); + if (tplg_ops->control->switch_put) + return tplg_ops->control->switch_put(scontrol, ucontrol); - /* notify DSP of mixer updates */ - if (pm_runtime_active(scomp->dev)) - snd_sof_ipc_set_get_comp_data(scontrol, - SOF_IPC_COMP_SET_VALUE, - SOF_CTRL_TYPE_VALUE_CHAN_SET, - SOF_CTRL_CMD_SWITCH, - true); - - return change; + return false; } int snd_sof_enum_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct soc_enum *se = - (struct soc_enum *)kcontrol->private_value; + struct soc_enum *se = (struct soc_enum *)kcontrol->private_value; struct snd_sof_control *scontrol = se->dobj.private; - struct sof_ipc_ctrl_data *cdata = scontrol->control_data; - unsigned int i, channels = scontrol->num_channels; - - snd_sof_refresh_control(scontrol); + struct snd_soc_component *scomp = scontrol->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; - /* read back each channel */ - for (i = 0; i < channels; i++) - ucontrol->value.enumerated.item[i] = cdata->chanv[i].value; + if (tplg_ops->control->enum_get) + return tplg_ops->control->enum_get(scontrol, ucontrol); return 0; } @@ -245,66 +115,29 @@ int snd_sof_enum_get(struct snd_kcontrol *kcontrol, int snd_sof_enum_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct soc_enum *se = - (struct soc_enum *)kcontrol->private_value; + struct soc_enum *se = (struct soc_enum *)kcontrol->private_value; struct snd_sof_control *scontrol = se->dobj.private; struct snd_soc_component *scomp = scontrol->scomp; - struct sof_ipc_ctrl_data *cdata = scontrol->control_data; - unsigned int i, channels = scontrol->num_channels; - bool change = false; - u32 value; - - /* update each channel */ - for (i = 0; i < channels; i++) { - value = ucontrol->value.enumerated.item[i]; - change = change || (value != cdata->chanv[i].value); - cdata->chanv[i].channel = i; - cdata->chanv[i].value = value; - } + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; - /* notify DSP of enum updates */ - if (pm_runtime_active(scomp->dev)) - snd_sof_ipc_set_get_comp_data(scontrol, - SOF_IPC_COMP_SET_VALUE, - SOF_CTRL_TYPE_VALUE_CHAN_SET, - SOF_CTRL_CMD_ENUM, - true); + if (tplg_ops->control->enum_put) + return tplg_ops->control->enum_put(scontrol, ucontrol); - return change; + return false; } int snd_sof_bytes_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct soc_bytes_ext *be = - (struct soc_bytes_ext *)kcontrol->private_value; + struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value; struct snd_sof_control *scontrol = be->dobj.private; struct snd_soc_component *scomp = scontrol->scomp; - struct sof_ipc_ctrl_data *cdata = scontrol->control_data; - struct sof_abi_hdr *data = cdata->data; - size_t size; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; - snd_sof_refresh_control(scontrol); - - if (be->max > sizeof(ucontrol->value.bytes.data)) { - dev_err_ratelimited(scomp->dev, - "error: data max %d exceeds ucontrol data array size\n", - be->max); - return -EINVAL; - } - - /* 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); + if (tplg_ops->control->bytes_get) + return tplg_ops->control->bytes_get(scontrol, ucontrol); return 0; } @@ -312,41 +145,14 @@ int snd_sof_bytes_get(struct snd_kcontrol *kcontrol, int snd_sof_bytes_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct soc_bytes_ext *be = - (struct soc_bytes_ext *)kcontrol->private_value; + struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value; struct snd_sof_control *scontrol = be->dobj.private; struct snd_soc_component *scomp = scontrol->scomp; - struct sof_ipc_ctrl_data *cdata = scontrol->control_data; - struct sof_abi_hdr *data = cdata->data; - size_t size; - - if (be->max > sizeof(ucontrol->value.bytes.data)) { - dev_err_ratelimited(scomp->dev, - "error: data max %d exceeds ucontrol data array size\n", - be->max); - return -EINVAL; - } - - /* 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; - } + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; - size = data->size + sizeof(*data); - - /* copy from kcontrol */ - memcpy(data, ucontrol->value.bytes.data, size); - - /* notify DSP of byte control updates */ - if (pm_runtime_active(scomp->dev)) - snd_sof_ipc_set_get_comp_data(scontrol, - SOF_IPC_COMP_SET_DATA, - SOF_CTRL_TYPE_DATA_SET, - scontrol->cmd, - true); + if (tplg_ops->control->bytes_put) + return tplg_ops->control->bytes_put(scontrol, ucontrol); return 0; } @@ -355,78 +161,18 @@ int snd_sof_bytes_ext_put(struct snd_kcontrol *kcontrol, const unsigned int __user *binary_data, unsigned int size) { - struct soc_bytes_ext *be = - (struct soc_bytes_ext *)kcontrol->private_value; + struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value; struct snd_sof_control *scontrol = be->dobj.private; struct snd_soc_component *scomp = scontrol->scomp; - struct sof_ipc_ctrl_data *cdata = scontrol->control_data; - struct snd_ctl_tlv header; - const struct snd_ctl_tlv __user *tlvd = - (const struct snd_ctl_tlv __user *)binary_data; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; /* 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 - * length of data from tlvd->tlv. - */ - if (copy_from_user(&header, tlvd, sizeof(struct snd_ctl_tlv))) - return -EFAULT; - - /* make sure TLV info is consistent */ - if (header.length + sizeof(struct snd_ctl_tlv) > size) { - dev_err_ratelimited(scomp->dev, "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(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(scomp->dev, - "error: incorrect numid %d\n", - header.numid); - return -EINVAL; - } - - if (copy_from_user(cdata->data, tlvd->tlv, header.length)) - return -EFAULT; - - if (cdata->data->magic != SOF_ABI_MAGIC) { - 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(scomp->dev, "error: Incompatible ABI version 0x%08x.\n", - cdata->data->abi); - return -EINVAL; - } - - /* be->max has been verified to be >= sizeof(struct sof_abi_hdr) */ - if (cdata->data->size > be->max - sizeof(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(scomp->dev)) - snd_sof_ipc_set_get_comp_data(scontrol, - SOF_IPC_COMP_SET_DATA, - SOF_CTRL_TYPE_DATA_SET, - scontrol->cmd, - true); + if (tplg_ops->control->bytes_ext_put) + return tplg_ops->control->bytes_ext_put(scontrol, binary_data, size); return 0; } @@ -437,68 +183,23 @@ int snd_sof_bytes_ext_volatile_get(struct snd_kcontrol *kcontrol, unsigned int _ 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; - int ret; - int err; - - /* - * 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); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; + int ret, err; - ret = pm_runtime_get_sync(scomp->dev); + ret = pm_runtime_resume_and_get(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); + dev_err_ratelimited(scomp->dev, "%s: failed to resume %d\n", __func__, ret); return ret; } - /* set the ABI header values */ - cdata->data->magic = SOF_ABI_MAGIC; - cdata->data->abi = SOF_ABI_VERSION; - /* 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 (cdata->data->size > be->max - sizeof(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(struct sof_abi_hdr)); - ret = -EINVAL; - goto out; - } - - data_size = cdata->data->size + sizeof(struct sof_abi_hdr); - - /* make sure we don't exceed size provided by user space for data */ - if (data_size > size) { - ret = -ENOSPC; - goto out; - } - - header.numid = scontrol->cmd; - header.length = data_size; - if (copy_to_user(tlvd, &header, sizeof(struct snd_ctl_tlv))) { - ret = -EFAULT; - goto out; - } + if (tplg_ops->control->bytes_ext_volatile_get) + ret = tplg_ops->control->bytes_ext_volatile_get(scontrol, binary_data, size); - 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); + dev_err_ratelimited(scomp->dev, "%s: failed to idle %d\n", __func__, err); return ret; } @@ -507,193 +208,14 @@ int snd_sof_bytes_ext_get(struct snd_kcontrol *kcontrol, unsigned int __user *binary_data, unsigned int size) { - struct soc_bytes_ext *be = - (struct soc_bytes_ext *)kcontrol->private_value; + struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value; struct snd_sof_control *scontrol = be->dobj.private; struct snd_soc_component *scomp = scontrol->scomp; - struct sof_ipc_ctrl_data *cdata = scontrol->control_data; - struct snd_ctl_tlv header; - struct snd_ctl_tlv __user *tlvd = - (struct snd_ctl_tlv __user *)binary_data; - size_t data_size; - - snd_sof_refresh_control(scontrol); - - /* - * Decrement the limit by ext bytes header size to - * ensure the user space buffer is not exceeded. - */ - if (size < sizeof(struct snd_ctl_tlv)) - return -ENOSPC; - size -= sizeof(struct snd_ctl_tlv); - - /* set the ABI header values */ - cdata->data->magic = SOF_ABI_MAGIC; - cdata->data->abi = SOF_ABI_VERSION; - - /* check data size doesn't exceed max coming from topology */ - if (cdata->data->size > be->max - sizeof(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(struct sof_abi_hdr)); - return -EINVAL; - } + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; - data_size = cdata->data->size + sizeof(struct sof_abi_hdr); - - /* make sure we don't exceed size provided by user space for data */ - if (data_size > size) - return -ENOSPC; - - header.numid = scontrol->cmd; - header.length = data_size; - if (copy_to_user(tlvd, &header, sizeof(struct snd_ctl_tlv))) - return -EFAULT; - - if (copy_to_user(tlvd->tlv, cdata->data, data_size)) - return -EFAULT; + if (tplg_ops->control->bytes_ext_get) + return tplg_ops->control->bytes_ext_get(scontrol, binary_data, size); return 0; } - -static void snd_sof_update_control(struct snd_sof_control *scontrol, - struct sof_ipc_ctrl_data *cdata) -{ - struct snd_soc_component *scomp = scontrol->scomp; - struct sof_ipc_ctrl_data *local_cdata; - int i; - - local_cdata = scontrol->control_data; - - if (cdata->cmd == SOF_CTRL_CMD_BINARY) { - if (cdata->num_elems != local_cdata->data->size) { - dev_err(scomp->dev, - "error: cdata binary size mismatch %u - %u\n", - cdata->num_elems, local_cdata->data->size); - return; - } - - /* copy the new binary data */ - memcpy(local_cdata->data, cdata->data, cdata->num_elems); - } else if (cdata->num_elems != scontrol->num_channels) { - dev_err(scomp->dev, - "error: cdata channel count mismatch %u - %d\n", - cdata->num_elems, scontrol->num_channels); - } else { - /* copy the new values */ - for (i = 0; i < cdata->num_elems; i++) - local_cdata->chanv[i].value = cdata->chanv[i].value; - } -} - -void snd_sof_control_notify(struct snd_sof_dev *sdev, - struct sof_ipc_ctrl_data *cdata) -{ - struct snd_soc_dapm_widget *widget; - struct snd_sof_control *scontrol; - struct snd_sof_widget *swidget; - struct snd_kcontrol *kc = NULL; - struct soc_mixer_control *sm; - struct soc_bytes_ext *be; - size_t expected_size; - struct soc_enum *se; - bool found = false; - int i, type; - - /* Find the swidget first */ - list_for_each_entry(swidget, &sdev->widget_list, list) { - if (swidget->comp_id == cdata->comp_id) { - found = true; - break; - } - } - - if (!found) - return; - - /* Translate SOF cmd to TPLG type */ - switch (cdata->cmd) { - case SOF_CTRL_CMD_VOLUME: - case SOF_CTRL_CMD_SWITCH: - type = SND_SOC_TPLG_TYPE_MIXER; - break; - case SOF_CTRL_CMD_BINARY: - type = SND_SOC_TPLG_TYPE_BYTES; - break; - case SOF_CTRL_CMD_ENUM: - type = SND_SOC_TPLG_TYPE_ENUM; - break; - default: - dev_err(sdev->dev, "error: unknown cmd %u\n", cdata->cmd); - return; - } - - widget = swidget->widget; - for (i = 0; i < widget->num_kcontrols; i++) { - /* skip non matching types or non matching indexes within type */ - if (widget->dobj.widget.kcontrol_type[i] == type && - widget->kcontrol_news[i].index == cdata->index) { - kc = widget->kcontrols[i]; - break; - } - } - - if (!kc) - return; - - switch (cdata->cmd) { - case SOF_CTRL_CMD_VOLUME: - case SOF_CTRL_CMD_SWITCH: - sm = (struct soc_mixer_control *)kc->private_value; - scontrol = sm->dobj.private; - break; - case SOF_CTRL_CMD_BINARY: - be = (struct soc_bytes_ext *)kc->private_value; - scontrol = be->dobj.private; - break; - case SOF_CTRL_CMD_ENUM: - se = (struct soc_enum *)kc->private_value; - scontrol = se->dobj.private; - break; - default: - return; - } - - expected_size = sizeof(struct sof_ipc_ctrl_data); - switch (cdata->type) { - case SOF_CTRL_TYPE_VALUE_CHAN_GET: - case SOF_CTRL_TYPE_VALUE_CHAN_SET: - expected_size += cdata->num_elems * - sizeof(struct sof_ipc_ctrl_value_chan); - break; - case SOF_CTRL_TYPE_VALUE_COMP_GET: - case SOF_CTRL_TYPE_VALUE_COMP_SET: - expected_size += cdata->num_elems * - sizeof(struct sof_ipc_ctrl_value_comp); - break; - case SOF_CTRL_TYPE_DATA_GET: - case SOF_CTRL_TYPE_DATA_SET: - expected_size += cdata->num_elems + sizeof(struct sof_abi_hdr); - break; - default: - return; - } - - if (cdata->rhdr.hdr.size != expected_size) { - dev_err(sdev->dev, "error: component notification size mismatch\n"); - return; - } - - if (cdata->num_elems) - /* - * The message includes the updated value/data, update the - * control's local cache using the received notification - */ - snd_sof_update_control(scontrol, cdata); - else - /* Mark the scontrol that the value/data is changed in SOF */ - scontrol->comp_data_dirty = true; - - snd_ctl_notify_one(swidget->scomp->card->snd_card, - SNDRV_CTL_EVENT_MASK_VALUE, kc, 0); -} diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 2c3de295f11f..3e6141d03770 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -14,12 +14,12 @@ #include <sound/sof.h> #include "sof-priv.h" #include "ops.h" -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) -#include "sof-probes.h" -#endif + +#define CREATE_TRACE_POINTS +#include <trace/events/sof.h> /* see SOF_DBG_ flags */ -int sof_core_debug = IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE); +static int sof_core_debug = IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_FIRMWARE_TRACE); module_param_named(sof_debug, sof_core_debug, int, 0444); MODULE_PARM_DESC(sof_debug, "SOF core debug options (0x0 all off)"); @@ -27,6 +27,22 @@ MODULE_PARM_DESC(sof_debug, "SOF core debug options (0x0 all off)"); #define TIMEOUT_DEFAULT_IPC_MS 500 #define TIMEOUT_DEFAULT_BOOT_MS 2000 +/** + * sof_debug_check_flag - check if a given flag(s) is set in sof_core_debug + * @mask: Flag or combination of flags to check + * + * Returns true if all bits set in mask is also set in sof_core_debug, otherwise + * false + */ +bool sof_debug_check_flag(int mask) +{ + if ((sof_core_debug & mask) == mask) + return true; + + return false; +} +EXPORT_SYMBOL(sof_debug_check_flag); + /* * FW Panic/fault handling. */ @@ -52,23 +68,33 @@ static const struct sof_panic_msg panic_msg[] = { {SOF_IPC_PANIC_ASSERT, "assertion failed"}, }; -/* +/** + * sof_print_oops_and_stack - Handle the printing of DSP oops and stack trace + * @sdev: Pointer to the device's sdev + * @level: prink log level to use for the printing + * @panic_code: the panic code + * @tracep_code: tracepoint code + * @oops: Pointer to DSP specific oops data + * @panic_info: Pointer to the received panic information message + * @stack: Pointer to the call stack data + * @stack_words: Number of words in the stack data + * * helper to be called from .dbg_dump callbacks. No error code is * provided, it's left as an exercise for the caller of .dbg_dump * (typically IPC or loader) */ -void snd_sof_get_status(struct snd_sof_dev *sdev, u32 panic_code, - u32 tracep_code, void *oops, - struct sof_ipc_panic_info *panic_info, - void *stack, size_t stack_words) +void sof_print_oops_and_stack(struct snd_sof_dev *sdev, const char *level, + u32 panic_code, u32 tracep_code, void *oops, + struct sof_ipc_panic_info *panic_info, + void *stack, size_t stack_words) { u32 code; int i; /* is firmware dead ? */ if ((panic_code & SOF_IPC_PANIC_MAGIC_MASK) != SOF_IPC_PANIC_MAGIC) { - dev_err(sdev->dev, "unexpected fault %#010x trace %#010x\n", - panic_code, tracep_code); + dev_printk(level, sdev->dev, "unexpected fault %#010x trace %#010x\n", + panic_code, tracep_code); return; /* no fault ? */ } @@ -76,54 +102,76 @@ void snd_sof_get_status(struct snd_sof_dev *sdev, u32 panic_code, for (i = 0; i < ARRAY_SIZE(panic_msg); i++) { if (panic_msg[i].id == code) { - dev_err(sdev->dev, "reason: %s (%#x)\n", panic_msg[i].msg, - code & SOF_IPC_PANIC_CODE_MASK); - dev_err(sdev->dev, "trace point: %#010x\n", tracep_code); + dev_printk(level, sdev->dev, "reason: %s (%#x)\n", + panic_msg[i].msg, code & SOF_IPC_PANIC_CODE_MASK); + dev_printk(level, sdev->dev, "trace point: %#010x\n", tracep_code); goto out; } } /* unknown error */ - dev_err(sdev->dev, "unknown panic code: %#x\n", code & SOF_IPC_PANIC_CODE_MASK); - dev_err(sdev->dev, "trace point: %#010x\n", tracep_code); + dev_printk(level, sdev->dev, "unknown panic code: %#x\n", + code & SOF_IPC_PANIC_CODE_MASK); + dev_printk(level, sdev->dev, "trace point: %#010x\n", tracep_code); out: - dev_err(sdev->dev, "panic at %s:%d\n", panic_info->filename, - panic_info->linenum); - sof_oops(sdev, oops); - sof_stack(sdev, oops, stack, stack_words); + dev_printk(level, sdev->dev, "panic at %s:%d\n", panic_info->filename, + panic_info->linenum); + sof_oops(sdev, level, oops); + sof_stack(sdev, level, oops, stack, stack_words); } -EXPORT_SYMBOL(snd_sof_get_status); +EXPORT_SYMBOL(sof_print_oops_and_stack); + +/* Helper to manage DSP state */ +void sof_set_fw_state(struct snd_sof_dev *sdev, enum sof_fw_state new_state) +{ + if (sdev->fw_state == new_state) + return; + + dev_dbg(sdev->dev, "fw_state change: %d -> %d\n", sdev->fw_state, new_state); + sdev->fw_state = new_state; + + switch (new_state) { + case SOF_FW_BOOT_NOT_STARTED: + case SOF_FW_BOOT_COMPLETE: + case SOF_FW_CRASHED: + sof_client_fw_state_dispatcher(sdev); + fallthrough; + default: + break; + } +} +EXPORT_SYMBOL(sof_set_fw_state); /* * FW Boot State Transition Diagram * - * +-----------------------------------------------------------------------+ - * | | - * ------------------ ------------------ | - * | | | | | - * | BOOT_FAILED | | READY_FAILED |-------------------------+ | - * | | | | | | - * ------------------ ------------------ | | - * ^ ^ | | - * | | | | - * (FW Boot Timeout) (FW_READY FAIL) | | - * | | | | - * | | | | - * ------------------ | ------------------ | | - * | | | | | | | - * | IN_PROGRESS |---------------+------------->| COMPLETE | | | - * | | (FW Boot OK) (FW_READY OK) | | | | - * ------------------ ------------------ | | - * ^ | | | - * | | | | - * (FW Loading OK) (System Suspend/Runtime Suspend) - * | | | | - * | | | | - * ------------------ ------------------ | | | - * | | | |<-----+ | | - * | PREPARE | | NOT_STARTED |<---------------------+ | - * | | | |<---------------------------+ + * +----------------------------------------------------------------------+ + * | | + * ------------------ ------------------ | + * | | | | | + * | BOOT_FAILED |<-------| READY_FAILED | | + * | |<--+ | | ------------------ | + * ------------------ | ------------------ | | | + * ^ | ^ | CRASHED |---+ | + * | | | | | | | + * (FW Boot Timeout) | (FW_READY FAIL) ------------------ | | + * | | | ^ | | + * | | | |(DSP Panic) | | + * ------------------ | | ------------------ | | + * | | | | | | | | + * | IN_PROGRESS |---------------+------------->| COMPLETE | | | + * | | (FW Boot OK) (FW_READY OK) | | | | + * ------------------ | ------------------ | | + * ^ | | | | + * | | | | | + * (FW Loading OK) | (System Suspend/Runtime Suspend) + * | | | | | + * | (FW Loading Fail) | | | + * ------------------ | ------------------ | | | + * | | | | |<-----+ | | + * | PREPARE |---+ | NOT_STARTED |<---------------------+ | + * | | | |<--------------------------+ * ------------------ ------------------ * | ^ | ^ * | | | | @@ -144,7 +192,7 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) ret = snd_sof_probe(sdev); if (ret < 0) { dev_err(sdev->dev, "error: failed to probe DSP %d\n", ret); - return ret; + goto probe_err; } sof_set_fw_state(sdev, SOF_FW_BOOT_PREPARE); @@ -186,6 +234,7 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) if (ret < 0) { dev_err(sdev->dev, "error: failed to load DSP firmware %d\n", ret); + sof_set_fw_state(sdev, SOF_FW_BOOT_FAILED); goto fw_load_err; } @@ -199,18 +248,18 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) if (ret < 0) { dev_err(sdev->dev, "error: failed to boot DSP firmware %d\n", ret); + sof_set_fw_state(sdev, SOF_FW_BOOT_FAILED); goto fw_run_err; } - if (sof_core_debug & SOF_DBG_ENABLE_TRACE) { - sdev->dtrace_is_supported = true; + if (sof_debug_check_flag(SOF_DBG_ENABLE_TRACE)) { + sdev->fw_trace_is_supported = true; - /* init DMA trace */ - ret = snd_sof_init_trace(sdev); + /* init firmware tracing */ + ret = sof_fw_trace_init(sdev); if (ret < 0) { /* non fatal */ - dev_warn(sdev->dev, - "warning: failed to initialize trace %d\n", + dev_warn(sdev->dev, "failed to initialize firmware tracing %d\n", ret); } } else { @@ -237,6 +286,12 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) goto fw_trace_err; } + ret = sof_register_clients(sdev); + if (ret < 0) { + dev_err(sdev->dev, "failed to register clients %d\n", ret); + goto sof_machine_err; + } + /* * Some platforms in SOF, ex: BYT, may not have their platform PM * callbacks set. Increment the usage count so as to @@ -252,8 +307,10 @@ static int sof_probe_continue(struct snd_sof_dev *sdev) return 0; +sof_machine_err: + snd_sof_machine_unregister(sdev, plat_data); fw_trace_err: - snd_sof_free_trace(sdev); + sof_fw_trace_free(sdev); fw_run_err: snd_sof_fw_unload(sdev); fw_load_err: @@ -263,6 +320,8 @@ dbg_err: snd_sof_free_debug(sdev); dsp_err: snd_sof_remove(sdev); +probe_err: + sof_ops_free(sdev); /* all resources freed, update state to match */ sof_set_fw_state(sdev, SOF_FW_BOOT_NOT_STARTED); @@ -287,6 +346,7 @@ static void sof_probe_work(struct work_struct *work) int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) { struct snd_sof_dev *sdev; + int ret; sdev = devm_kzalloc(dev, sizeof(*sdev), GFP_KERNEL); if (!sdev) @@ -300,18 +360,26 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) sdev->pdata = plat_data; sdev->first_boot = true; - sof_set_fw_state(sdev, 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 IPC support */ + if (!(BIT(plat_data->ipc_type) & plat_data->desc->ipc_supported_mask)) { + dev_err(dev, "ipc_type %d is not supported on this platform, mask is %#x\n", + plat_data->ipc_type, plat_data->desc->ipc_supported_mask); + return -EINVAL; + } + + /* init ops, if necessary */ + ret = sof_ops_init(sdev); + if (ret < 0) + return ret; + /* check all mandatory ops */ if (!sof_ops(sdev) || !sof_ops(sdev)->probe || !sof_ops(sdev)->run || !sof_ops(sdev)->block_read || !sof_ops(sdev)->block_write || !sof_ops(sdev)->send_msg || !sof_ops(sdev)->load_firmware || - !sof_ops(sdev)->ipc_msg_data || !sof_ops(sdev)->ipc_pcm_params || - !sof_ops(sdev)->fw_ready) { + !sof_ops(sdev)->ipc_msg_data) { + sof_ops_free(sdev); dev_err(dev, "error: missing mandatory ops\n"); return -EINVAL; } @@ -320,10 +388,16 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) INIT_LIST_HEAD(&sdev->kcontrol_list); INIT_LIST_HEAD(&sdev->widget_list); INIT_LIST_HEAD(&sdev->dai_list); + INIT_LIST_HEAD(&sdev->dai_link_list); INIT_LIST_HEAD(&sdev->route_list); + INIT_LIST_HEAD(&sdev->ipc_client_list); + INIT_LIST_HEAD(&sdev->ipc_rx_handler_list); + INIT_LIST_HEAD(&sdev->fw_state_handler_list); spin_lock_init(&sdev->ipc_lock); spin_lock_init(&sdev->hw_lock); mutex_init(&sdev->power_state_access); + mutex_init(&sdev->ipc_client_mutex); + mutex_init(&sdev->client_event_handler_mutex); /* set default timeouts if none provided */ if (plat_data->desc->ipc_timeout == 0) @@ -335,6 +409,8 @@ int snd_sof_device_probe(struct device *dev, struct snd_sof_pdata *plat_data) else sdev->boot_timeout = plat_data->desc->boot_timeout; + sof_set_fw_state(sdev, SOF_FW_BOOT_NOT_STARTED); + if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) { INIT_WORK(&sdev->probe_work, sof_probe_work); schedule_work(&sdev->probe_work); @@ -362,7 +438,21 @@ int snd_sof_device_remove(struct device *dev) if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) cancel_work_sync(&sdev->probe_work); + /* + * Unregister any registered client device first before IPC and debugfs + * to allow client drivers to be removed cleanly + */ + sof_unregister_clients(sdev); + + /* + * Unregister machine driver. This will unbind the snd_card which + * will remove the component driver and unload the topology + * before freeing the snd_card. + */ + snd_sof_machine_unregister(sdev, pdata); + if (sdev->fw_state > SOF_FW_BOOT_NOT_STARTED) { + sof_fw_trace_free(sdev); ret = snd_sof_dsp_power_down_notify(sdev); if (ret < 0) dev_warn(dev, "error: %d failed to prepare DSP for device removal", @@ -370,24 +460,10 @@ int snd_sof_device_remove(struct device *dev) snd_sof_ipc_free(sdev); snd_sof_free_debug(sdev); - snd_sof_free_trace(sdev); + snd_sof_remove(sdev); } - /* - * Unregister machine driver. This will unbind the snd_card which - * will remove the component driver and unload the topology - * before freeing the snd_card. - */ - snd_sof_machine_unregister(sdev, pdata); - - /* - * Unregistering the machine driver results in unloading the topology. - * Some widgets, ex: scheduler, attempt to power down the core they are - * scheduled on, when they are unloaded. Therefore, the DSP must be - * removed only after the topology has been unloaded. - */ - if (sdev->fw_state > SOF_FW_BOOT_NOT_STARTED) - snd_sof_remove(sdev); + sof_ops_free(sdev); /* release firmware */ snd_sof_fw_unload(sdev); @@ -399,10 +475,19 @@ EXPORT_SYMBOL(snd_sof_device_remove); int snd_sof_device_shutdown(struct device *dev) { struct snd_sof_dev *sdev = dev_get_drvdata(dev); + struct snd_sof_pdata *pdata = sdev->pdata; if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) cancel_work_sync(&sdev->probe_work); + /* + * make sure clients and machine driver(s) are unregistered to force + * all userspace devices to be closed prior to the DSP shutdown sequence + */ + sof_unregister_clients(sdev); + + snd_sof_machine_unregister(sdev, pdata); + if (sdev->fw_state == SOF_FW_BOOT_COMPLETE) return snd_sof_shutdown(sdev); @@ -414,3 +499,4 @@ MODULE_AUTHOR("Liam Girdwood"); MODULE_DESCRIPTION("Sound Open Firmware (SOF) Core"); MODULE_LICENSE("Dual BSD/GPL"); MODULE_ALIAS("platform:sof-audio"); +MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); diff --git a/sound/soc/sof/debug.c b/sound/soc/sof/debug.c index dc1df5fb7b4c..d9a3ce7b69e1 100644 --- a/sound/soc/sof/debug.c +++ b/sound/soc/sof/debug.c @@ -19,335 +19,9 @@ #include "sof-priv.h" #include "ops.h" -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) -#include "sof-probes.h" - -/** - * strsplit_u32 - Split string into sequence of u32 tokens - * @buf: String to split into tokens. - * @delim: String containing delimiter characters. - * @tkns: Returned u32 sequence pointer. - * @num_tkns: Returned number of tokens obtained. - */ -static int -strsplit_u32(char **buf, const char *delim, u32 **tkns, size_t *num_tkns) -{ - char *s; - u32 *data, *tmp; - size_t count = 0; - size_t cap = 32; - int ret = 0; - - *tkns = NULL; - *num_tkns = 0; - data = kcalloc(cap, sizeof(*data), GFP_KERNEL); - if (!data) - return -ENOMEM; - - while ((s = strsep(buf, delim)) != NULL) { - ret = kstrtouint(s, 0, data + count); - if (ret) - goto exit; - if (++count >= cap) { - cap *= 2; - tmp = krealloc(data, cap * sizeof(*data), GFP_KERNEL); - if (!tmp) { - ret = -ENOMEM; - goto exit; - } - data = tmp; - } - } - - if (!count) - goto exit; - *tkns = kmemdup(data, count * sizeof(*data), GFP_KERNEL); - if (*tkns == NULL) { - ret = -ENOMEM; - goto exit; - } - *num_tkns = count; - -exit: - kfree(data); - return ret; -} - -static int tokenize_input(const char __user *from, size_t count, - loff_t *ppos, u32 **tkns, size_t *num_tkns) -{ - char *buf; - int ret; - - buf = kmalloc(count + 1, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - ret = simple_write_to_buffer(buf, count, ppos, from, count); - if (ret != count) { - ret = ret >= 0 ? -EIO : ret; - goto exit; - } - - buf[count] = '\0'; - ret = strsplit_u32((char **)&buf, ",", tkns, num_tkns); -exit: - kfree(buf); - return ret; -} - -static ssize_t probe_points_read(struct file *file, - char __user *to, size_t count, loff_t *ppos) -{ - struct snd_sof_dfsentry *dfse = file->private_data; - struct snd_sof_dev *sdev = dfse->sdev; - struct sof_probe_point_desc *desc; - size_t num_desc, len = 0; - char *buf; - int i, ret; - - if (sdev->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) { - dev_warn(sdev->dev, "no extractor stream running\n"); - return -ENOENT; - } - - buf = kzalloc(PAGE_SIZE, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - ret = sof_ipc_probe_points_info(sdev, &desc, &num_desc); - if (ret < 0) - goto exit; - - for (i = 0; i < num_desc; i++) { - ret = snprintf(buf + len, PAGE_SIZE - len, - "Id: %#010x Purpose: %d Node id: %#x\n", - desc[i].buffer_id, desc[i].purpose, desc[i].stream_tag); - if (ret < 0) - goto free_desc; - len += ret; - } - - ret = simple_read_from_buffer(to, count, ppos, buf, len); -free_desc: - kfree(desc); -exit: - kfree(buf); - return ret; -} - -static ssize_t probe_points_write(struct file *file, - const char __user *from, size_t count, loff_t *ppos) -{ - struct snd_sof_dfsentry *dfse = file->private_data; - struct snd_sof_dev *sdev = dfse->sdev; - struct sof_probe_point_desc *desc; - size_t num_tkns, bytes; - u32 *tkns; - int ret; - - if (sdev->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) { - dev_warn(sdev->dev, "no extractor stream running\n"); - return -ENOENT; - } - - ret = tokenize_input(from, count, ppos, &tkns, &num_tkns); - if (ret < 0) - return ret; - bytes = sizeof(*tkns) * num_tkns; - if (!num_tkns || (bytes % sizeof(*desc))) { - ret = -EINVAL; - goto exit; - } - - desc = (struct sof_probe_point_desc *)tkns; - ret = sof_ipc_probe_points_add(sdev, - desc, bytes / sizeof(*desc)); - if (!ret) - ret = count; -exit: - kfree(tkns); - return ret; -} - -static const struct file_operations probe_points_fops = { - .open = simple_open, - .read = probe_points_read, - .write = probe_points_write, - .llseek = default_llseek, -}; - -static ssize_t probe_points_remove_write(struct file *file, - const char __user *from, size_t count, loff_t *ppos) -{ - struct snd_sof_dfsentry *dfse = file->private_data; - struct snd_sof_dev *sdev = dfse->sdev; - size_t num_tkns; - u32 *tkns; - int ret; - - if (sdev->extractor_stream_tag == SOF_PROBE_INVALID_NODE_ID) { - dev_warn(sdev->dev, "no extractor stream running\n"); - return -ENOENT; - } - - ret = tokenize_input(from, count, ppos, &tkns, &num_tkns); - if (ret < 0) - return ret; - if (!num_tkns) { - ret = -EINVAL; - goto exit; - } - - ret = sof_ipc_probe_points_remove(sdev, tkns, num_tkns); - if (!ret) - ret = count; -exit: - kfree(tkns); - return ret; -} - -static const struct file_operations probe_points_remove_fops = { - .open = simple_open, - .write = probe_points_remove_write, - .llseek = default_llseek, -}; - -static int snd_sof_debugfs_probe_item(struct snd_sof_dev *sdev, - const char *name, mode_t mode, - const struct file_operations *fops) -{ - struct snd_sof_dfsentry *dfse; - - dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); - if (!dfse) - return -ENOMEM; - - dfse->type = SOF_DFSENTRY_TYPE_BUF; - dfse->sdev = sdev; - - debugfs_create_file(name, mode, sdev->debugfs_root, dfse, fops); - /* add to dfsentry list */ - list_add(&dfse->list, &sdev->dfsentry_list); - - return 0; -} -#endif - -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) -#define MAX_IPC_FLOOD_DURATION_MS 1000 -#define MAX_IPC_FLOOD_COUNT 10000 -#define IPC_FLOOD_TEST_RESULT_LEN 512 - -static int sof_debug_ipc_flood_test(struct snd_sof_dev *sdev, - struct snd_sof_dfsentry *dfse, - bool flood_duration_test, - unsigned long ipc_duration_ms, - unsigned long ipc_count) -{ - struct sof_ipc_cmd_hdr hdr; - struct sof_ipc_reply reply; - u64 min_response_time = U64_MAX; - ktime_t start, end, test_end; - u64 avg_response_time = 0; - u64 max_response_time = 0; - u64 ipc_response_time; - int i = 0; - int ret; - - /* configure test IPC */ - hdr.cmd = SOF_IPC_GLB_TEST_MSG | SOF_IPC_TEST_IPC_FLOOD; - hdr.size = sizeof(hdr); - - /* set test end time for duration flood test */ - if (flood_duration_test) - test_end = ktime_get_ns() + ipc_duration_ms * NSEC_PER_MSEC; - - /* send test IPC's */ - while (1) { - start = ktime_get(); - ret = sof_ipc_tx_message(sdev->ipc, hdr.cmd, &hdr, hdr.size, - &reply, sizeof(reply)); - end = ktime_get(); - - if (ret < 0) - break; - - /* compute min and max response times */ - ipc_response_time = ktime_to_ns(ktime_sub(end, start)); - min_response_time = min(min_response_time, ipc_response_time); - max_response_time = max(max_response_time, ipc_response_time); - - /* sum up response times */ - avg_response_time += ipc_response_time; - i++; - - /* test complete? */ - if (flood_duration_test) { - if (ktime_to_ns(end) >= test_end) - break; - } else { - if (i == ipc_count) - break; - } - } - - if (ret < 0) - dev_err(sdev->dev, - "error: ipc flood test failed at %d iterations\n", i); - - /* return if the first IPC fails */ - if (!i) - return ret; - - /* compute average response time */ - do_div(avg_response_time, i); - - /* clear previous test output */ - memset(dfse->cache_buf, 0, IPC_FLOOD_TEST_RESULT_LEN); - - if (flood_duration_test) { - dev_dbg(sdev->dev, "IPC Flood test duration: %lums\n", - ipc_duration_ms); - snprintf(dfse->cache_buf, IPC_FLOOD_TEST_RESULT_LEN, - "IPC Flood test duration: %lums\n", ipc_duration_ms); - } - - dev_dbg(sdev->dev, - "IPC Flood count: %d, Avg response time: %lluns\n", - i, avg_response_time); - dev_dbg(sdev->dev, "Max response time: %lluns\n", - max_response_time); - dev_dbg(sdev->dev, "Min response time: %lluns\n", - min_response_time); - - /* format output string */ - snprintf(dfse->cache_buf + strlen(dfse->cache_buf), - IPC_FLOOD_TEST_RESULT_LEN - strlen(dfse->cache_buf), - "IPC Flood count: %d\nAvg response time: %lluns\n", - i, avg_response_time); - - snprintf(dfse->cache_buf + strlen(dfse->cache_buf), - IPC_FLOOD_TEST_RESULT_LEN - strlen(dfse->cache_buf), - "Max response time: %lluns\nMin response time: %lluns\n", - max_response_time, min_response_time); - - return ret; -} -#endif - static ssize_t sof_dfsentry_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) { -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) - struct snd_sof_dfsentry *dfse = file->private_data; - struct snd_sof_dev *sdev = dfse->sdev; - unsigned long ipc_duration_ms = 0; - bool flood_duration_test = false; - unsigned long ipc_count = 0; - struct dentry *dentry; - int err; -#endif size_t size; char *string; int ret; @@ -359,78 +33,6 @@ static ssize_t sof_dfsentry_write(struct file *file, const char __user *buffer, size = simple_write_to_buffer(string, count, ppos, buffer, count); ret = size; -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) - /* - * write op is only supported for ipc_flood_count or - * ipc_flood_duration_ms debugfs entries atm. - * ipc_flood_count floods the DSP with the number of IPC's specified. - * ipc_duration_ms test floods the DSP for the time specified - * in the debugfs entry. - */ - dentry = file->f_path.dentry; - if (strcmp(dentry->d_name.name, "ipc_flood_count") && - strcmp(dentry->d_name.name, "ipc_flood_duration_ms")) { - ret = -EINVAL; - goto out; - } - - if (!strcmp(dentry->d_name.name, "ipc_flood_duration_ms")) - flood_duration_test = true; - - /* test completion criterion */ - if (flood_duration_test) - ret = kstrtoul(string, 0, &ipc_duration_ms); - else - ret = kstrtoul(string, 0, &ipc_count); - if (ret < 0) - goto out; - - /* limit max duration/ipc count for flood test */ - if (flood_duration_test) { - if (!ipc_duration_ms) { - ret = size; - goto out; - } - - /* find the minimum. min() is not used to avoid warnings */ - if (ipc_duration_ms > MAX_IPC_FLOOD_DURATION_MS) - ipc_duration_ms = MAX_IPC_FLOOD_DURATION_MS; - } else { - if (!ipc_count) { - ret = size; - goto out; - } - - /* find the minimum. min() is not used to avoid warnings */ - if (ipc_count > MAX_IPC_FLOOD_COUNT) - ipc_count = MAX_IPC_FLOOD_COUNT; - } - - ret = pm_runtime_get_sync(sdev->dev); - if (ret < 0 && ret != -EACCES) { - dev_err_ratelimited(sdev->dev, - "error: debugfs write failed to resume %d\n", - ret); - pm_runtime_put_noidle(sdev->dev); - goto out; - } - - /* flood test */ - ret = sof_debug_ipc_flood_test(sdev, dfse, flood_duration_test, - ipc_duration_ms, ipc_count); - - pm_runtime_mark_last_busy(sdev->dev); - err = pm_runtime_put_autosuspend(sdev->dev); - if (err < 0) - dev_err_ratelimited(sdev->dev, - "error: debugfs write failed to idle %d\n", - err); - - /* return size if test is successful */ - if (ret >= 0) - ret = size; -out: -#endif kfree(string); return ret; } @@ -446,24 +48,6 @@ static ssize_t sof_dfsentry_read(struct file *file, char __user *buffer, int size; u8 *buf; -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) - struct dentry *dentry; - - dentry = file->f_path.dentry; - if ((!strcmp(dentry->d_name.name, "ipc_flood_count") || - !strcmp(dentry->d_name.name, "ipc_flood_duration_ms"))) { - if (*ppos) - return 0; - - count = strlen(dfse->cache_buf); - size_ret = copy_to_user(buffer, dfse->cache_buf, count); - if (size_ret) - return -EFAULT; - - *ppos += count; - return count; - } -#endif size = dfse->size; /* validate position & count */ @@ -621,19 +205,6 @@ int snd_sof_debugfs_buf_item(struct snd_sof_dev *sdev, dfse->size = size; dfse->sdev = sdev; -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) - if (!strncmp(name, "ipc_flood", strlen("ipc_flood"))) { - /* - * cache_buf is unused for SOF_DFSENTRY_TYPE_BUF debugfs entries. - * So, use it to save the results of the last IPC flood test. - */ - dfse->cache_buf = devm_kzalloc(sdev->dev, IPC_FLOOD_TEST_RESULT_LEN, - GFP_KERNEL); - if (!dfse->cache_buf) - return -ENOMEM; - } -#endif - debugfs_create_file(name, mode, sdev->debugfs_root, dfse, &sof_dfs_fops); /* add to dfsentry list */ @@ -658,14 +229,13 @@ static int memory_info_update(struct snd_sof_dev *sdev, char *buf, size_t buff_s if (!reply) return -ENOMEM; - ret = pm_runtime_get_sync(sdev->dev); + ret = pm_runtime_resume_and_get(sdev->dev); if (ret < 0 && ret != -EACCES) { - pm_runtime_put_noidle(sdev->dev); dev_err(sdev->dev, "error: enabling device failed: %d\n", ret); goto error; } - ret = sof_ipc_tx_message(sdev->ipc, msg.cmd, &msg, msg.size, reply, SOF_IPC_MSG_MAX_SIZE); + ret = sof_ipc_tx_message(sdev->ipc, &msg, msg.size, reply, SOF_IPC_MSG_MAX_SIZE); pm_runtime_mark_last_busy(sdev->dev); pm_runtime_put_autosuspend(sdev->dev); if (ret < 0 || reply->rhdr.error < 0) { @@ -682,9 +252,9 @@ static int memory_info_update(struct snd_sof_dev *sdev, char *buf, size_t buff_s } for (i = 0, len = 0; i < reply->num_elems; i++) { - ret = snprintf(buf + len, buff_size - len, "zone %d.%d used %#8x free %#8x\n", - reply->elems[i].zone, reply->elems[i].id, - reply->elems[i].used, reply->elems[i].free); + ret = scnprintf(buf + len, buff_size - len, "zone %d.%d used %#8x free %#8x\n", + reply->elems[i].zone, reply->elems[i].id, + reply->elems[i].used, reply->elems[i].free); if (ret < 0) goto error; len += ret; @@ -760,7 +330,7 @@ EXPORT_SYMBOL_GPL(snd_sof_dbg_memory_info_init); int snd_sof_dbg_init(struct snd_sof_dev *sdev) { - const struct snd_sof_dsp_ops *ops = sof_ops(sdev); + struct snd_sof_dsp_ops *ops = sof_ops(sdev); const struct snd_sof_debugfs_map *map; int i; int err; @@ -783,35 +353,6 @@ int snd_sof_dbg_init(struct snd_sof_dev *sdev) return err; } -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) - err = snd_sof_debugfs_probe_item(sdev, "probe_points", - 0644, &probe_points_fops); - if (err < 0) - return err; - err = snd_sof_debugfs_probe_item(sdev, "probe_points_remove", - 0200, &probe_points_remove_fops); - if (err < 0) - return err; -#endif - -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) - /* create read-write ipc_flood_count debugfs entry */ - err = snd_sof_debugfs_buf_item(sdev, NULL, 0, - "ipc_flood_count", 0666); - - /* errors are only due to memory allocation, not debugfs */ - if (err < 0) - return err; - - /* create read-write ipc_flood_duration_ms debugfs entry */ - err = snd_sof_debugfs_buf_item(sdev, NULL, 0, - "ipc_flood_duration_ms", 0666); - - /* errors are only due to memory allocation, not debugfs */ - if (err < 0) - return err; -#endif - return 0; } EXPORT_SYMBOL_GPL(snd_sof_dbg_init); @@ -823,7 +364,7 @@ void snd_sof_free_debug(struct snd_sof_dev *sdev) EXPORT_SYMBOL_GPL(snd_sof_free_debug); static const struct soc_fw_state_info { - enum snd_sof_fw_state state; + enum sof_fw_state state; const char *name; } fw_state_dbg[] = { {SOF_FW_BOOT_NOT_STARTED, "SOF_FW_BOOT_NOT_STARTED"}, @@ -831,37 +372,47 @@ static const struct soc_fw_state_info { {SOF_FW_BOOT_IN_PROGRESS, "SOF_FW_BOOT_IN_PROGRESS"}, {SOF_FW_BOOT_FAILED, "SOF_FW_BOOT_FAILED"}, {SOF_FW_BOOT_READY_FAILED, "SOF_FW_BOOT_READY_FAILED"}, + {SOF_FW_BOOT_READY_OK, "SOF_FW_BOOT_READY_OK"}, {SOF_FW_BOOT_COMPLETE, "SOF_FW_BOOT_COMPLETE"}, + {SOF_FW_CRASHED, "SOF_FW_CRASHED"}, }; -static void snd_sof_dbg_print_fw_state(struct snd_sof_dev *sdev) +static void snd_sof_dbg_print_fw_state(struct snd_sof_dev *sdev, const char *level) { int i; for (i = 0; i < ARRAY_SIZE(fw_state_dbg); i++) { if (sdev->fw_state == fw_state_dbg[i].state) { - dev_err(sdev->dev, "fw_state: %s (%d)\n", fw_state_dbg[i].name, i); + dev_printk(level, sdev->dev, "fw_state: %s (%d)\n", + fw_state_dbg[i].name, i); return; } } - dev_err(sdev->dev, "fw_state: UNKNOWN (%d)\n", sdev->fw_state); + dev_printk(level, sdev->dev, "fw_state: UNKNOWN (%d)\n", sdev->fw_state); } -void snd_sof_dsp_dbg_dump(struct snd_sof_dev *sdev, u32 flags) +void snd_sof_dsp_dbg_dump(struct snd_sof_dev *sdev, const char *msg, u32 flags) { - bool print_all = !!(sof_core_debug & SOF_DBG_PRINT_ALL_DUMPS); + char *level = (flags & SOF_DBG_DUMP_OPTIONAL) ? KERN_DEBUG : KERN_ERR; + bool print_all = sof_debug_check_flag(SOF_DBG_PRINT_ALL_DUMPS); if (flags & SOF_DBG_DUMP_OPTIONAL && !print_all) return; if (sof_ops(sdev)->dbg_dump && !sdev->dbg_dump_printed) { - dev_err(sdev->dev, "------------[ DSP dump start ]------------\n"); - snd_sof_dbg_print_fw_state(sdev); + dev_printk(level, sdev->dev, + "------------[ DSP dump start ]------------\n"); + if (msg) + dev_printk(level, sdev->dev, "%s\n", msg); + snd_sof_dbg_print_fw_state(sdev, level); sof_ops(sdev)->dbg_dump(sdev, flags); - dev_err(sdev->dev, "------------[ DSP dump end ]------------\n"); + dev_printk(level, sdev->dev, + "------------[ DSP dump end ]------------\n"); if (!print_all) sdev->dbg_dump_printed = true; + } else if (msg) { + dev_printk(level, sdev->dev, "%s\n", msg); } } EXPORT_SYMBOL(snd_sof_dsp_dbg_dump); @@ -872,15 +423,15 @@ static void snd_sof_ipc_dump(struct snd_sof_dev *sdev) dev_err(sdev->dev, "------------[ IPC dump start ]------------\n"); sof_ops(sdev)->ipc_dump(sdev); dev_err(sdev->dev, "------------[ IPC dump end ]------------\n"); - if (!(sof_core_debug & SOF_DBG_PRINT_ALL_DUMPS)) + if (!sof_debug_check_flag(SOF_DBG_PRINT_ALL_DUMPS)) sdev->ipc_dump_printed = true; } } -void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev) +void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev, const char *msg) { if (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_RETAIN_DSP_CONTEXT) || - (sof_core_debug & SOF_DBG_RETAIN_CTX)) { + sof_debug_check_flag(SOF_DBG_RETAIN_CTX)) { /* should we prevent DSP entering D3 ? */ if (!sdev->ipc_dump_printed) dev_info(sdev->dev, @@ -890,7 +441,7 @@ void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev) /* dump vital information to the logs */ snd_sof_ipc_dump(sdev); - snd_sof_dsp_dbg_dump(sdev, SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX); - snd_sof_trace_notify_for_error(sdev); + snd_sof_dsp_dbg_dump(sdev, msg, SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX); + sof_fw_trace_fw_crashed(sdev); } EXPORT_SYMBOL(snd_sof_handle_fw_exception); diff --git a/sound/soc/sof/imx/Kconfig b/sound/soc/sof/imx/Kconfig index 34cf228c188f..4751b04d5e6f 100644 --- a/sound/soc/sof/imx/Kconfig +++ b/sound/soc/sof/imx/Kconfig @@ -11,53 +11,43 @@ config SND_SOC_SOF_IMX_TOPLEVEL if SND_SOC_SOF_IMX_TOPLEVEL -config SND_SOC_SOF_IMX_OF - def_tristate SND_SOC_SOF_OF - select SND_SOC_SOF_IMX8 if SND_SOC_SOF_IMX8_SUPPORT - select SND_SOC_SOF_IMX8M if SND_SOC_SOF_IMX8M_SUPPORT - help - This option is not user-selectable but automagically handled by - 'select' statements at a higher level. - config SND_SOC_SOF_IMX_COMMON tristate + select SND_SOC_SOF_OF_DEV + select SND_SOC_SOF + select SND_SOC_SOF_IPC3 + select SND_SOC_SOF_XTENSA + select SND_SOC_SOF_COMPRESS help This option is not user-selectable but automagically handled by 'select' statements at a higher level. -config SND_SOC_SOF_IMX8_SUPPORT - bool "SOF support for i.MX8" - depends on IMX_SCU=y || IMX_SCU=SND_SOC_SOF_IMX_OF - depends on IMX_DSP=y || IMX_DSP=SND_SOC_SOF_IMX_OF +config SND_SOC_SOF_IMX8 + tristate "SOF support for i.MX8" + depends on IMX_SCU + depends on IMX_DSP + select SND_SOC_SOF_IMX_COMMON help This adds support for Sound Open Firmware for NXP i.MX8 platforms. Say Y if you have such a device. If unsure select "N". -config SND_SOC_SOF_IMX8 - tristate +config SND_SOC_SOF_IMX8M + tristate "SOF support for i.MX8M" + depends on IMX_DSP select SND_SOC_SOF_IMX_COMMON - select SND_SOC_SOF_XTENSA - select SND_SOC_SOF_COMPRESS - help - This option is not user-selectable but automagically handled by - 'select' statements at a higher level. - -config SND_SOC_SOF_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 +config SND_SOC_SOF_IMX8ULP + tristate "SOF support for i.MX8ULP" + depends on IMX_DSP select SND_SOC_SOF_IMX_COMMON - select SND_SOC_SOF_XTENSA - select SND_SOC_SOF_COMPRESS help - This option is not user-selectable but automagically handled by - 'select' statements at a higher level. + This adds support for Sound Open Firmware for NXP i.MX8ULP platforms. + Say Y if you have such a device. + If unsure select "N". -endif ## SND_SOC_SOF_IMX_IMX_TOPLEVEL +endif ## SND_SOC_SOF_IMX_TOPLEVEL diff --git a/sound/soc/sof/imx/Makefile b/sound/soc/sof/imx/Makefile index dba93c3466ec..798b43a415bf 100644 --- a/sound/soc/sof/imx/Makefile +++ b/sound/soc/sof/imx/Makefile @@ -1,9 +1,11 @@ # SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) snd-sof-imx8-objs := imx8.o snd-sof-imx8m-objs := imx8m.o +snd-sof-imx8ulp-objs := imx8ulp.o snd-sof-imx-common-objs := imx-common.o obj-$(CONFIG_SND_SOC_SOF_IMX8) += snd-sof-imx8.o obj-$(CONFIG_SND_SOC_SOF_IMX8M) += snd-sof-imx8m.o +obj-$(CONFIG_SND_SOC_SOF_IMX8ULP) += snd-sof-imx8ulp.o obj-$(CONFIG_SND_SOC_SOF_IMX_COMMON) += imx-common.o diff --git a/sound/soc/sof/imx/imx-common.c b/sound/soc/sof/imx/imx-common.c index 8826ef94f04a..36e3d414a18f 100644 --- a/sound/soc/sof/imx/imx-common.c +++ b/sound/soc/sof/imx/imx-common.c @@ -69,9 +69,33 @@ void imx8_dump(struct snd_sof_dev *sdev, u32 flags) 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); + sof_print_oops_and_stack(sdev, KERN_ERR, status, status, &xoops, + &panic_info, stack, IMX8_STACK_DUMP_SIZE); } EXPORT_SYMBOL(imx8_dump); +int imx8_parse_clocks(struct snd_sof_dev *sdev, struct imx_clocks *clks) +{ + int ret; + + ret = devm_clk_bulk_get(sdev->dev, clks->num_dsp_clks, clks->dsp_clks); + if (ret) + dev_err(sdev->dev, "Failed to request DSP clocks\n"); + + return ret; +} +EXPORT_SYMBOL(imx8_parse_clocks); + +int imx8_enable_clocks(struct snd_sof_dev *sdev, struct imx_clocks *clks) +{ + return clk_bulk_prepare_enable(clks->num_dsp_clks, clks->dsp_clks); +} +EXPORT_SYMBOL(imx8_enable_clocks); + +void imx8_disable_clocks(struct snd_sof_dev *sdev, struct imx_clocks *clks) +{ + clk_bulk_disable_unprepare(clks->num_dsp_clks, clks->dsp_clks); +} +EXPORT_SYMBOL(imx8_disable_clocks); + MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/imx/imx-common.h b/sound/soc/sof/imx/imx-common.h index 1cc7d6704182..ec4b3a5c7496 100644 --- a/sound/soc/sof/imx/imx-common.h +++ b/sound/soc/sof/imx/imx-common.h @@ -3,6 +3,8 @@ #ifndef __IMX_COMMON_H__ #define __IMX_COMMON_H__ +#include <linux/clk.h> + #define EXCEPT_MAX_HDR_SIZE 0x400 #define IMX8_STACK_DUMP_SIZE 32 @@ -13,4 +15,13 @@ void imx8_get_registers(struct snd_sof_dev *sdev, void imx8_dump(struct snd_sof_dev *sdev, u32 flags); +struct imx_clocks { + struct clk_bulk_data *dsp_clks; + int num_dsp_clks; +}; + +int imx8_parse_clocks(struct snd_sof_dev *sdev, struct imx_clocks *clks); +int imx8_enable_clocks(struct snd_sof_dev *sdev, struct imx_clocks *clks); +void imx8_disable_clocks(struct snd_sof_dev *sdev, struct imx_clocks *clks); + #endif diff --git a/sound/soc/sof/imx/imx-ops.h b/sound/soc/sof/imx/imx-ops.h deleted file mode 100644 index 24235ef8c8fa..000000000000 --- a/sound/soc/sof/imx/imx-ops.h +++ /dev/null @@ -1,10 +0,0 @@ -/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ - -#ifndef __IMX_OPS_H__ -#define __IMX_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; - -#endif diff --git a/sound/soc/sof/imx/imx8.c b/sound/soc/sof/imx/imx8.c index dd59a74480d6..2844d9a8040a 100644 --- a/sound/soc/sof/imx/imx8.c +++ b/sound/soc/sof/imx/imx8.c @@ -21,8 +21,8 @@ #include <linux/firmware/imx/svc/misc.h> #include <dt-bindings/firmware/imx/rsrc.h> #include "../ops.h" +#include "../sof-of-dev.h" #include "imx-common.h" -#include "imx-ops.h" /* DSP memories */ #define IRAM_OFFSET 0x10000 @@ -41,6 +41,13 @@ #define MBOX_OFFSET 0x800000 #define MBOX_SIZE 0x1000 +/* DSP clocks */ +static struct clk_bulk_data imx8_dsp_clks[] = { + { .id = "ipg" }, + { .id = "ocram" }, + { .id = "core" }, +}; + struct imx8_priv { struct device *dev; struct snd_sof_dev *sdev; @@ -57,42 +64,9 @@ struct imx8_priv { struct device **pd_dev; struct device_link **link; + struct imx_clocks *clks; }; -static void imx8_get_reply(struct snd_sof_dev *sdev) -{ - struct snd_sof_ipc_msg *msg = sdev->msg; - struct sof_ipc_reply reply; - int ret = 0; - - if (!msg) { - dev_warn(sdev->dev, "unexpected ipc interrupt\n"); - return; - } - - /* get reply */ - sof_mailbox_read(sdev, sdev->host_box.offset, &reply, sizeof(reply)); - - if (reply.error < 0) { - memcpy(msg->reply_data, &reply, sizeof(reply)); - ret = reply.error; - } else { - /* reply has correct size? */ - if (reply.hdr.size != msg->reply_size) { - dev_err(sdev->dev, "error: reply expected %zu got %u bytes\n", - msg->reply_size, reply.hdr.size); - ret = -EINVAL; - } - - /* read the message */ - if (msg->reply_size > 0) - sof_mailbox_read(sdev, sdev->host_box.offset, - msg->reply_data, msg->reply_size); - } - - msg->reply_error = ret; -} - static int imx8_get_mailbox_offset(struct snd_sof_dev *sdev) { return MBOX_OFFSET; @@ -109,8 +83,7 @@ static void imx8_dsp_handle_reply(struct imx_dsp_ipc *ipc) unsigned long flags; spin_lock_irqsave(&priv->sdev->ipc_lock, flags); - imx8_get_reply(priv->sdev); - snd_sof_ipc_reply(priv->sdev, 0); + snd_sof_ipc_process_reply(priv->sdev, 0); spin_unlock_irqrestore(&priv->sdev->ipc_lock, flags); } @@ -124,7 +97,7 @@ static void imx8_dsp_handle_request(struct imx_dsp_ipc *ipc) /* 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); + snd_sof_dsp_panic(priv->sdev, p, true); else snd_sof_ipc_msgs_rx(priv->sdev); } @@ -223,6 +196,11 @@ static int imx8_probe(struct snd_sof_dev *sdev) if (!priv) return -ENOMEM; + priv->clks = devm_kzalloc(&pdev->dev, sizeof(*priv->clks), GFP_KERNEL); + if (!priv->clks) + return -ENOMEM; + + sdev->num_cores = 1; sdev->pdata->hw_pdata = priv; priv->dev = sdev->dev; priv->sdev = sdev; @@ -335,6 +313,18 @@ static int imx8_probe(struct snd_sof_dev *sdev) /* set default mailbox offset for FW ready message */ sdev->dsp_box.offset = MBOX_OFFSET; + /* init clocks info */ + priv->clks->dsp_clks = imx8_dsp_clks; + priv->clks->num_dsp_clks = ARRAY_SIZE(imx8_dsp_clks); + + ret = imx8_parse_clocks(sdev, priv->clks); + if (ret < 0) + goto exit_pdev_unregister; + + ret = imx8_enable_clocks(sdev, priv->clks); + if (ret < 0) + goto exit_pdev_unregister; + return 0; exit_pdev_unregister: @@ -353,6 +343,7 @@ static int imx8_remove(struct snd_sof_dev *sdev) struct imx8_priv *priv = sdev->pdata->hw_pdata; int i; + imx8_disable_clocks(sdev, priv->clks); platform_device_unregister(priv->ipc_dev); for (i = 0; i < priv->num_domains; i++) { @@ -376,6 +367,92 @@ static int imx8_get_bar_index(struct snd_sof_dev *sdev, u32 type) } } +static void imx8_suspend(struct snd_sof_dev *sdev) +{ + int i; + struct imx8_priv *priv = (struct imx8_priv *)sdev->pdata->hw_pdata; + + for (i = 0; i < DSP_MU_CHAN_NUM; i++) + imx_dsp_free_channel(priv->dsp_ipc, i); + + imx8_disable_clocks(sdev, priv->clks); +} + +static int imx8_resume(struct snd_sof_dev *sdev) +{ + struct imx8_priv *priv = (struct imx8_priv *)sdev->pdata->hw_pdata; + int ret; + int i; + + ret = imx8_enable_clocks(sdev, priv->clks); + if (ret < 0) + return ret; + + for (i = 0; i < DSP_MU_CHAN_NUM; i++) + imx_dsp_request_channel(priv->dsp_ipc, i); + + return 0; +} + +static int imx8_dsp_runtime_resume(struct snd_sof_dev *sdev) +{ + int ret; + const struct sof_dsp_power_state target_dsp_state = { + .state = SOF_DSP_PM_D0, + }; + + ret = imx8_resume(sdev); + if (ret < 0) + return ret; + + return snd_sof_dsp_set_power_state(sdev, &target_dsp_state); +} + +static int imx8_dsp_runtime_suspend(struct snd_sof_dev *sdev) +{ + const struct sof_dsp_power_state target_dsp_state = { + .state = SOF_DSP_PM_D3, + }; + + imx8_suspend(sdev); + + return snd_sof_dsp_set_power_state(sdev, &target_dsp_state); +} + +static int imx8_dsp_suspend(struct snd_sof_dev *sdev, unsigned int target_state) +{ + const struct sof_dsp_power_state target_dsp_state = { + .state = target_state, + }; + + if (!pm_runtime_suspended(sdev->dev)) + imx8_suspend(sdev); + + return snd_sof_dsp_set_power_state(sdev, &target_dsp_state); +} + +static int imx8_dsp_resume(struct snd_sof_dev *sdev) +{ + int ret; + const struct sof_dsp_power_state target_dsp_state = { + .state = SOF_DSP_PM_D0, + }; + + ret = imx8_resume(sdev); + if (ret < 0) + return ret; + + if (pm_runtime_suspended(sdev->dev)) { + pm_runtime_disable(sdev->dev); + pm_runtime_set_active(sdev->dev); + pm_runtime_mark_last_busy(sdev->dev); + pm_runtime_enable(sdev->dev); + pm_runtime_idle(sdev->dev); + } + + return snd_sof_dsp_set_power_state(sdev, &target_dsp_state); +} + static struct snd_soc_dai_driver imx8_dai[] = { { .name = "esai0", @@ -401,8 +478,16 @@ static struct snd_soc_dai_driver imx8_dai[] = { }, }; +static int imx8_dsp_set_power_state(struct snd_sof_dev *sdev, + const struct sof_dsp_power_state *target_state) +{ + sdev->dsp_power_state = *target_state; + + return 0; +} + /* i.MX8 ops */ -struct snd_sof_dsp_ops sof_imx8_ops = { +static struct snd_sof_dsp_ops sof_imx8_ops = { /* probe and remove */ .probe = imx8_probe, .remove = imx8_remove, @@ -419,16 +504,14 @@ struct snd_sof_dsp_ops sof_imx8_ops = { /* 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 = sof_ipc_msg_data, - .ipc_pcm_params = sof_ipc_pcm_params, + .set_stream_data_offset = sof_set_stream_data_offset, - /* module loading */ - .load_module = snd_sof_parse_module_memcpy, .get_bar_index = imx8_get_bar_index, + /* firmware loading */ .load_firmware = snd_sof_load_firmware_memcpy, @@ -453,11 +536,19 @@ struct snd_sof_dsp_ops sof_imx8_ops = { SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, + + /* PM */ + .runtime_suspend = imx8_dsp_runtime_suspend, + .runtime_resume = imx8_dsp_runtime_resume, + + .suspend = imx8_dsp_suspend, + .resume = imx8_dsp_resume, + + .set_power_state = imx8_dsp_set_power_state, }; -EXPORT_SYMBOL(sof_imx8_ops); /* i.MX8X ops */ -struct snd_sof_dsp_ops sof_imx8x_ops = { +static struct snd_sof_dsp_ops sof_imx8x_ops = { /* probe and remove */ .probe = imx8_probe, .remove = imx8_remove, @@ -474,16 +565,14 @@ struct snd_sof_dsp_ops sof_imx8x_ops = { /* 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 = sof_ipc_msg_data, - .ipc_pcm_params = sof_ipc_pcm_params, + .set_stream_data_offset = sof_set_stream_data_offset, - /* module loading */ - .load_module = snd_sof_parse_module_memcpy, .get_bar_index = imx8_get_bar_index, + /* firmware loading */ .load_firmware = snd_sof_load_firmware_memcpy, @@ -502,6 +591,15 @@ struct snd_sof_dsp_ops sof_imx8x_ops = { .drv = imx8_dai, .num_drv = ARRAY_SIZE(imx8_dai), + /* PM */ + .runtime_suspend = imx8_dsp_runtime_suspend, + .runtime_resume = imx8_dsp_runtime_resume, + + .suspend = imx8_dsp_suspend, + .resume = imx8_dsp_resume, + + .set_power_state = imx8_dsp_set_power_state, + /* ALSA HW info flags */ .hw_info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | @@ -509,7 +607,57 @@ struct snd_sof_dsp_ops sof_imx8x_ops = { SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_NO_PERIOD_WAKEUP }; -EXPORT_SYMBOL(sof_imx8x_ops); + +static struct sof_dev_desc sof_of_imx8qxp_desc = { + .ipc_supported_mask = BIT(SOF_IPC), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "imx/sof", + }, + .default_tplg_path = { + [SOF_IPC] = "imx/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-imx8x.ri", + }, + .nocodec_tplg_filename = "sof-imx8-nocodec.tplg", + .ops = &sof_imx8x_ops, +}; + +static struct sof_dev_desc sof_of_imx8qm_desc = { + .ipc_supported_mask = BIT(SOF_IPC), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "imx/sof", + }, + .default_tplg_path = { + [SOF_IPC] = "imx/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-imx8.ri", + }, + .nocodec_tplg_filename = "sof-imx8-nocodec.tplg", + .ops = &sof_imx8_ops, +}; + +static const struct of_device_id sof_of_imx8_ids[] = { + { .compatible = "fsl,imx8qxp-dsp", .data = &sof_of_imx8qxp_desc}, + { .compatible = "fsl,imx8qm-dsp", .data = &sof_of_imx8qm_desc}, + { } +}; +MODULE_DEVICE_TABLE(of, sof_of_imx8_ids); + +/* DT driver definition */ +static struct platform_driver snd_sof_of_imx8_driver = { + .probe = sof_of_probe, + .remove = sof_of_remove, + .driver = { + .name = "sof-audio-of-imx8", + .pm = &sof_of_pm, + .of_match_table = sof_of_imx8_ids, + }, +}; +module_platform_driver(snd_sof_of_imx8_driver); MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA); MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/imx/imx8m.c b/sound/soc/sof/imx/imx8m.c index e4618980cf8b..1243f8a6141e 100644 --- a/sound/soc/sof/imx/imx8m.c +++ b/sound/soc/sof/imx/imx8m.c @@ -6,10 +6,13 @@ // // Hardware interface for audio DSP on i.MX8M +#include <linux/bits.h> #include <linux/firmware.h> +#include <linux/mfd/syscon.h> #include <linux/of_platform.h> #include <linux/of_address.h> #include <linux/of_irq.h> +#include <linux/regmap.h> #include <linux/module.h> #include <sound/sof.h> @@ -17,12 +20,32 @@ #include <linux/firmware/imx/dsp.h> #include "../ops.h" +#include "../sof-of-dev.h" #include "imx-common.h" -#include "imx-ops.h" #define MBOX_OFFSET 0x800000 #define MBOX_SIZE 0x1000 +static struct clk_bulk_data imx8m_dsp_clks[] = { + { .id = "ipg" }, + { .id = "ocram" }, + { .id = "core" }, +}; + +/* DAP registers */ +#define IMX8M_DAP_DEBUG 0x28800000 +#define IMX8M_DAP_DEBUG_SIZE (64 * 1024) +#define IMX8M_DAP_PWRCTL (0x4000 + 0x3020) +#define IMX8M_PWRCTL_CORERESET BIT(16) + +/* DSP audio mix registers */ +#define AudioDSP_REG0 0x100 +#define AudioDSP_REG1 0x104 +#define AudioDSP_REG2 0x108 +#define AudioDSP_REG3 0x10c + +#define AudioDSP_REG2_RUNSTALL BIT(5) + struct imx8m_priv { struct device *dev; struct snd_sof_dev *sdev; @@ -30,41 +53,12 @@ struct imx8m_priv { /* DSP IPC handler */ struct imx_dsp_ipc *dsp_ipc; struct platform_device *ipc_dev; -}; -static void imx8m_get_reply(struct snd_sof_dev *sdev) -{ - struct snd_sof_ipc_msg *msg = sdev->msg; - struct sof_ipc_reply reply; - int ret = 0; - - if (!msg) { - dev_warn(sdev->dev, "unexpected ipc interrupt\n"); - return; - } - - /* get reply */ - sof_mailbox_read(sdev, sdev->host_box.offset, &reply, sizeof(reply)); + struct imx_clocks *clks; - 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; -} + void __iomem *dap; + struct regmap *regmap; +}; static int imx8m_get_mailbox_offset(struct snd_sof_dev *sdev) { @@ -82,8 +76,7 @@ static void imx8m_dsp_handle_reply(struct imx_dsp_ipc *ipc) unsigned long flags; spin_lock_irqsave(&priv->sdev->ipc_lock, flags); - imx8m_get_reply(priv->sdev); - snd_sof_ipc_reply(priv->sdev, 0); + snd_sof_ipc_process_reply(priv->sdev, 0); spin_unlock_irqrestore(&priv->sdev->ipc_lock, flags); } @@ -97,7 +90,7 @@ static void imx8m_dsp_handle_request(struct imx_dsp_ipc *ipc) /* 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); + snd_sof_dsp_panic(priv->sdev, p, true); else snd_sof_ipc_msgs_rx(priv->sdev); } @@ -123,7 +116,34 @@ static int imx8m_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) */ static int imx8m_run(struct snd_sof_dev *sdev) { - /* TODO: start DSP using Audio MIX bits */ + struct imx8m_priv *priv = (struct imx8m_priv *)sdev->pdata->hw_pdata; + + regmap_update_bits(priv->regmap, AudioDSP_REG2, AudioDSP_REG2_RUNSTALL, 0); + + return 0; +} + +static int imx8m_reset(struct snd_sof_dev *sdev) +{ + struct imx8m_priv *priv = (struct imx8m_priv *)sdev->pdata->hw_pdata; + u32 pwrctl; + + /* put DSP into reset and stall */ + pwrctl = readl(priv->dap + IMX8M_DAP_PWRCTL); + pwrctl |= IMX8M_PWRCTL_CORERESET; + writel(pwrctl, priv->dap + IMX8M_DAP_PWRCTL); + + /* keep reset asserted for 10 cycles */ + usleep_range(1, 2); + + regmap_update_bits(priv->regmap, AudioDSP_REG2, + AudioDSP_REG2_RUNSTALL, AudioDSP_REG2_RUNSTALL); + + /* take the DSP out of reset and keep stalled for FW loading */ + pwrctl = readl(priv->dap + IMX8M_DAP_PWRCTL); + pwrctl &= ~IMX8M_PWRCTL_CORERESET; + writel(pwrctl, priv->dap + IMX8M_DAP_PWRCTL); + return 0; } @@ -143,6 +163,11 @@ static int imx8m_probe(struct snd_sof_dev *sdev) if (!priv) return -ENOMEM; + priv->clks = devm_kzalloc(&pdev->dev, sizeof(*priv->clks), GFP_KERNEL); + if (!priv->clks) + return -ENOMEM; + + sdev->num_cores = 1; sdev->pdata->hw_pdata = priv; priv->dev = sdev->dev; priv->sdev = sdev; @@ -175,6 +200,13 @@ static int imx8m_probe(struct snd_sof_dev *sdev) goto exit_pdev_unregister; } + priv->dap = devm_ioremap(sdev->dev, IMX8M_DAP_DEBUG, IMX8M_DAP_DEBUG_SIZE); + if (!priv->dap) { + dev_err(sdev->dev, "error: failed to map DAP debug memory area"); + ret = -ENODEV; + goto exit_pdev_unregister; + } + sdev->bar[SOF_FW_BLK_TYPE_IRAM] = devm_ioremap(sdev->dev, base, size); if (!sdev->bar[SOF_FW_BLK_TYPE_IRAM]) { dev_err(sdev->dev, "failed to ioremap base 0x%x size 0x%x\n", @@ -192,6 +224,7 @@ static int imx8m_probe(struct snd_sof_dev *sdev) } ret = of_address_to_resource(res_node, 0, &res); + of_node_put(res_node); if (ret) { dev_err(&pdev->dev, "failed to get reserved region address\n"); goto exit_pdev_unregister; @@ -210,6 +243,25 @@ static int imx8m_probe(struct snd_sof_dev *sdev) /* set default mailbox offset for FW ready message */ sdev->dsp_box.offset = MBOX_OFFSET; + priv->regmap = syscon_regmap_lookup_by_compatible("fsl,dsp-ctrl"); + if (IS_ERR(priv->regmap)) { + dev_err(sdev->dev, "cannot find dsp-ctrl registers"); + ret = PTR_ERR(priv->regmap); + goto exit_pdev_unregister; + } + + /* init clocks info */ + priv->clks->dsp_clks = imx8m_dsp_clks; + priv->clks->num_dsp_clks = ARRAY_SIZE(imx8m_dsp_clks); + + ret = imx8_parse_clocks(sdev, priv->clks); + if (ret < 0) + goto exit_pdev_unregister; + + ret = imx8_enable_clocks(sdev, priv->clks); + if (ret < 0) + goto exit_pdev_unregister; + return 0; exit_pdev_unregister: @@ -221,6 +273,7 @@ static int imx8m_remove(struct snd_sof_dev *sdev) { struct imx8m_priv *priv = sdev->pdata->hw_pdata; + imx8_disable_clocks(sdev, priv->clks); platform_device_unregister(priv->ipc_dev); return 0; @@ -264,13 +317,108 @@ static struct snd_soc_dai_driver imx8m_dai[] = { }, }; +static int imx8m_dsp_set_power_state(struct snd_sof_dev *sdev, + const struct sof_dsp_power_state *target_state) +{ + sdev->dsp_power_state = *target_state; + + return 0; +} + +static int imx8m_resume(struct snd_sof_dev *sdev) +{ + struct imx8m_priv *priv = (struct imx8m_priv *)sdev->pdata->hw_pdata; + int ret; + int i; + + ret = imx8_enable_clocks(sdev, priv->clks); + if (ret < 0) + return ret; + + for (i = 0; i < DSP_MU_CHAN_NUM; i++) + imx_dsp_request_channel(priv->dsp_ipc, i); + + return 0; +} + +static void imx8m_suspend(struct snd_sof_dev *sdev) +{ + struct imx8m_priv *priv = (struct imx8m_priv *)sdev->pdata->hw_pdata; + int i; + + for (i = 0; i < DSP_MU_CHAN_NUM; i++) + imx_dsp_free_channel(priv->dsp_ipc, i); + + imx8_disable_clocks(sdev, priv->clks); +} + +static int imx8m_dsp_runtime_resume(struct snd_sof_dev *sdev) +{ + int ret; + const struct sof_dsp_power_state target_dsp_state = { + .state = SOF_DSP_PM_D0, + }; + + ret = imx8m_resume(sdev); + if (ret < 0) + return ret; + + return snd_sof_dsp_set_power_state(sdev, &target_dsp_state); +} + +static int imx8m_dsp_runtime_suspend(struct snd_sof_dev *sdev) +{ + const struct sof_dsp_power_state target_dsp_state = { + .state = SOF_DSP_PM_D3, + }; + + imx8m_suspend(sdev); + + return snd_sof_dsp_set_power_state(sdev, &target_dsp_state); +} + +static int imx8m_dsp_resume(struct snd_sof_dev *sdev) +{ + int ret; + const struct sof_dsp_power_state target_dsp_state = { + .state = SOF_DSP_PM_D0, + }; + + ret = imx8m_resume(sdev); + if (ret < 0) + return ret; + + if (pm_runtime_suspended(sdev->dev)) { + pm_runtime_disable(sdev->dev); + pm_runtime_set_active(sdev->dev); + pm_runtime_mark_last_busy(sdev->dev); + pm_runtime_enable(sdev->dev); + pm_runtime_idle(sdev->dev); + } + + return snd_sof_dsp_set_power_state(sdev, &target_dsp_state); +} + +static int imx8m_dsp_suspend(struct snd_sof_dev *sdev, unsigned int target_state) +{ + const struct sof_dsp_power_state target_dsp_state = { + .state = target_state, + }; + + if (!pm_runtime_suspended(sdev->dev)) + imx8m_suspend(sdev); + + return snd_sof_dsp_set_power_state(sdev, &target_dsp_state); +} + /* i.MX8 ops */ -struct snd_sof_dsp_ops sof_imx8m_ops = { +static struct snd_sof_dsp_ops sof_imx8m_ops = { /* probe and remove */ .probe = imx8m_probe, .remove = imx8m_remove, /* DSP core boot */ .run = imx8m_run, + .reset = imx8m_reset, /* Block IO */ .block_read = sof_block_read, @@ -282,16 +430,14 @@ struct snd_sof_dsp_ops sof_imx8m_ops = { /* 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 = sof_ipc_msg_data, - .ipc_pcm_params = sof_ipc_pcm_params, + .set_stream_data_offset = sof_set_stream_data_offset, - /* module loading */ - .load_module = snd_sof_parse_module_memcpy, .get_bar_index = imx8m_get_bar_index, + /* firmware loading */ .load_firmware = snd_sof_load_firmware_memcpy, @@ -309,13 +455,54 @@ struct snd_sof_dsp_ops sof_imx8m_ops = { .drv = imx8m_dai, .num_drv = ARRAY_SIZE(imx8m_dai), + .suspend = imx8m_dsp_suspend, + .resume = imx8m_dsp_resume, + + .runtime_suspend = imx8m_dsp_runtime_suspend, + .runtime_resume = imx8m_dsp_runtime_resume, + + .set_power_state = imx8m_dsp_set_power_state, + .hw_info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, }; -EXPORT_SYMBOL(sof_imx8m_ops); + +static struct sof_dev_desc sof_of_imx8mp_desc = { + .ipc_supported_mask = BIT(SOF_IPC), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "imx/sof", + }, + .default_tplg_path = { + [SOF_IPC] = "imx/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-imx8m.ri", + }, + .nocodec_tplg_filename = "sof-imx8-nocodec.tplg", + .ops = &sof_imx8m_ops, +}; + +static const struct of_device_id sof_of_imx8m_ids[] = { + { .compatible = "fsl,imx8mp-dsp", .data = &sof_of_imx8mp_desc}, + { } +}; +MODULE_DEVICE_TABLE(of, sof_of_imx8m_ids); + +/* DT driver definition */ +static struct platform_driver snd_sof_of_imx8m_driver = { + .probe = sof_of_probe, + .remove = sof_of_remove, + .driver = { + .name = "sof-audio-of-imx8m", + .pm = &sof_of_pm, + .of_match_table = sof_of_imx8m_ids, + }, +}; +module_platform_driver(snd_sof_of_imx8m_driver); MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA); MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/imx/imx8ulp.c b/sound/soc/sof/imx/imx8ulp.c new file mode 100644 index 000000000000..4a562c9856e9 --- /dev/null +++ b/sound/soc/sof/imx/imx8ulp.c @@ -0,0 +1,515 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// Copyright 2021-2022 NXP +// +// Author: Peng Zhang <peng.zhang_8@nxp.com> +// +// Hardware interface for audio DSP on i.MX8ULP + +#include <linux/arm-smccc.h> +#include <linux/clk.h> +#include <linux/firmware.h> +#include <linux/firmware/imx/dsp.h> +#include <linux/firmware/imx/ipc.h> +#include <linux/firmware/imx/svc/misc.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/of_reserved_mem.h> + +#include <sound/sof.h> +#include <sound/sof/xtensa.h> + +#include "../ops.h" +#include "../sof-of-dev.h" +#include "imx-common.h" + +#define FSL_SIP_HIFI_XRDC 0xc200000e + +/* SIM Domain register */ +#define SYSCTRL0 0x8 +#define EXECUTE_BIT BIT(13) +#define RESET_BIT BIT(16) +#define HIFI4_CLK_BIT BIT(17) +#define PB_CLK_BIT BIT(18) +#define PLAT_CLK_BIT BIT(19) +#define DEBUG_LOGIC_BIT BIT(25) + +#define MBOX_OFFSET 0x800000 +#define MBOX_SIZE 0x1000 + +static struct clk_bulk_data imx8ulp_dsp_clks[] = { + { .id = "core" }, + { .id = "ipg" }, + { .id = "ocram" }, + { .id = "mu" }, +}; + +struct imx8ulp_priv { + struct device *dev; + struct snd_sof_dev *sdev; + + /* DSP IPC handler */ + struct imx_dsp_ipc *dsp_ipc; + struct platform_device *ipc_dev; + + struct regmap *regmap; + struct imx_clocks *clks; +}; + +static void imx8ulp_sim_lpav_start(struct imx8ulp_priv *priv) +{ + /* Controls the HiFi4 DSP Reset: 1 in reset, 0 out of reset */ + regmap_update_bits(priv->regmap, SYSCTRL0, RESET_BIT, 0); + + /* Reset HiFi4 DSP Debug logic: 1 debug reset, 0 out of reset*/ + regmap_update_bits(priv->regmap, SYSCTRL0, DEBUG_LOGIC_BIT, 0); + + /* Stall HIFI4 DSP Execution: 1 stall, 0 run */ + regmap_update_bits(priv->regmap, SYSCTRL0, EXECUTE_BIT, 0); +} + +static int imx8ulp_get_mailbox_offset(struct snd_sof_dev *sdev) +{ + return MBOX_OFFSET; +} + +static int imx8ulp_get_window_offset(struct snd_sof_dev *sdev, u32 id) +{ + return MBOX_OFFSET; +} + +static void imx8ulp_dsp_handle_reply(struct imx_dsp_ipc *ipc) +{ + struct imx8ulp_priv *priv = imx_dsp_get_data(ipc); + unsigned long flags; + + spin_lock_irqsave(&priv->sdev->ipc_lock, flags); + + snd_sof_ipc_process_reply(priv->sdev, 0); + + spin_unlock_irqrestore(&priv->sdev->ipc_lock, flags); +} + +static void imx8ulp_dsp_handle_request(struct imx_dsp_ipc *ipc) +{ + struct imx8ulp_priv *priv = imx_dsp_get_data(ipc); + u32 p; /* panic code */ + + /* Read the message from the debug box. */ + sof_mailbox_read(priv->sdev, priv->sdev->debug_box.offset + 4, &p, sizeof(p)); + + /* Check to see if the message is a panic code (0x0dead***) */ + if ((p & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) + snd_sof_dsp_panic(priv->sdev, p, true); + else + snd_sof_ipc_msgs_rx(priv->sdev); +} + +static struct imx_dsp_ops dsp_ops = { + .handle_reply = imx8ulp_dsp_handle_reply, + .handle_request = imx8ulp_dsp_handle_request, +}; + +static int imx8ulp_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) +{ + struct imx8ulp_priv *priv = sdev->pdata->hw_pdata; + + sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data, + msg->msg_size); + imx_dsp_ring_doorbell(priv->dsp_ipc, 0); + + return 0; +} + +static int imx8ulp_run(struct snd_sof_dev *sdev) +{ + struct imx8ulp_priv *priv = sdev->pdata->hw_pdata; + + imx8ulp_sim_lpav_start(priv); + + return 0; +} + +static int imx8ulp_reset(struct snd_sof_dev *sdev) +{ + struct imx8ulp_priv *priv = sdev->pdata->hw_pdata; + struct arm_smccc_res smc_resource; + + /* HiFi4 Platform Clock Enable: 1 enabled, 0 disabled */ + regmap_update_bits(priv->regmap, SYSCTRL0, PLAT_CLK_BIT, PLAT_CLK_BIT); + + /* HiFi4 PBCLK clock enable: 1 enabled, 0 disabled */ + regmap_update_bits(priv->regmap, SYSCTRL0, PB_CLK_BIT, PB_CLK_BIT); + + /* HiFi4 Clock Enable: 1 enabled, 0 disabled */ + regmap_update_bits(priv->regmap, SYSCTRL0, HIFI4_CLK_BIT, HIFI4_CLK_BIT); + + regmap_update_bits(priv->regmap, SYSCTRL0, RESET_BIT, RESET_BIT); + usleep_range(1, 2); + + /* Stall HIFI4 DSP Execution: 1 stall, 0 not stall */ + regmap_update_bits(priv->regmap, SYSCTRL0, EXECUTE_BIT, EXECUTE_BIT); + usleep_range(1, 2); + + arm_smccc_smc(FSL_SIP_HIFI_XRDC, 0, 0, 0, 0, 0, 0, 0, &smc_resource); + + return 0; +} + +static int imx8ulp_probe(struct snd_sof_dev *sdev) +{ + struct platform_device *pdev = + container_of(sdev->dev, struct platform_device, dev); + struct device_node *np = pdev->dev.of_node; + struct device_node *res_node; + struct resource *mmio; + struct imx8ulp_priv *priv; + struct resource res; + u32 base, size; + int ret = 0; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->clks = devm_kzalloc(&pdev->dev, sizeof(*priv->clks), GFP_KERNEL); + if (!priv->clks) + return -ENOMEM; + + sdev->num_cores = 1; + sdev->pdata->hw_pdata = priv; + priv->dev = sdev->dev; + priv->sdev = sdev; + + /* System integration module(SIM) control dsp configuration */ + priv->regmap = syscon_regmap_lookup_by_phandle(np, "fsl,dsp-ctrl"); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + priv->ipc_dev = platform_device_register_data(sdev->dev, "imx-dsp", + PLATFORM_DEVID_NONE, + pdev, sizeof(*pdev)); + if (IS_ERR(priv->ipc_dev)) + return PTR_ERR(priv->ipc_dev); + + priv->dsp_ipc = dev_get_drvdata(&priv->ipc_dev->dev); + if (!priv->dsp_ipc) { + /* DSP IPC driver not probed yet, try later */ + ret = -EPROBE_DEFER; + dev_err(sdev->dev, "Failed to get drvdata\n"); + goto exit_pdev_unregister; + } + + imx_dsp_set_data(priv->dsp_ipc, priv); + priv->dsp_ipc->ops = &dsp_ops; + + /* DSP base */ + mmio = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (mmio) { + base = mmio->start; + size = resource_size(mmio); + } else { + dev_err(sdev->dev, "error: failed to get DSP base at idx 0\n"); + ret = -EINVAL; + goto exit_pdev_unregister; + } + + sdev->bar[SOF_FW_BLK_TYPE_IRAM] = devm_ioremap(sdev->dev, base, size); + if (!sdev->bar[SOF_FW_BLK_TYPE_IRAM]) { + dev_err(sdev->dev, "failed to ioremap base 0x%x size 0x%x\n", + base, size); + ret = -ENODEV; + goto exit_pdev_unregister; + } + sdev->mmio_bar = SOF_FW_BLK_TYPE_IRAM; + + res_node = of_parse_phandle(np, "memory-reserved", 0); + if (!res_node) { + dev_err(&pdev->dev, "failed to get memory region node\n"); + ret = -ENODEV; + goto exit_pdev_unregister; + } + + ret = of_address_to_resource(res_node, 0, &res); + of_node_put(res_node); + if (ret) { + dev_err(&pdev->dev, "failed to get reserved region address\n"); + goto exit_pdev_unregister; + } + + sdev->bar[SOF_FW_BLK_TYPE_SRAM] = devm_ioremap_wc(sdev->dev, res.start, + resource_size(&res)); + if (!sdev->bar[SOF_FW_BLK_TYPE_SRAM]) { + dev_err(sdev->dev, "failed to ioremap mem 0x%x size 0x%x\n", + base, size); + ret = -ENOMEM; + goto exit_pdev_unregister; + } + sdev->mailbox_bar = SOF_FW_BLK_TYPE_SRAM; + + /* set default mailbox offset for FW ready message */ + sdev->dsp_box.offset = MBOX_OFFSET; + + ret = of_reserved_mem_device_init(sdev->dev); + if (ret) { + dev_err(&pdev->dev, "failed to init reserved memory region %d\n", ret); + goto exit_pdev_unregister; + } + + priv->clks->dsp_clks = imx8ulp_dsp_clks; + priv->clks->num_dsp_clks = ARRAY_SIZE(imx8ulp_dsp_clks); + + ret = imx8_parse_clocks(sdev, priv->clks); + if (ret < 0) + goto exit_pdev_unregister; + + ret = imx8_enable_clocks(sdev, priv->clks); + if (ret < 0) + goto exit_pdev_unregister; + + return 0; + +exit_pdev_unregister: + platform_device_unregister(priv->ipc_dev); + + return ret; +} + +static int imx8ulp_remove(struct snd_sof_dev *sdev) +{ + struct imx8ulp_priv *priv = sdev->pdata->hw_pdata; + + imx8_disable_clocks(sdev, priv->clks); + platform_device_unregister(priv->ipc_dev); + + return 0; +} + +/* on i.MX8 there is 1 to 1 match between type and BAR idx */ +static int imx8ulp_get_bar_index(struct snd_sof_dev *sdev, u32 type) +{ + return type; +} + +static int imx8ulp_suspend(struct snd_sof_dev *sdev) +{ + int i; + struct imx8ulp_priv *priv = (struct imx8ulp_priv *)sdev->pdata->hw_pdata; + + /*Stall DSP, release in .run() */ + regmap_update_bits(priv->regmap, SYSCTRL0, EXECUTE_BIT, EXECUTE_BIT); + + for (i = 0; i < DSP_MU_CHAN_NUM; i++) + imx_dsp_free_channel(priv->dsp_ipc, i); + + imx8_disable_clocks(sdev, priv->clks); + + return 0; +} + +static int imx8ulp_resume(struct snd_sof_dev *sdev) +{ + struct imx8ulp_priv *priv = (struct imx8ulp_priv *)sdev->pdata->hw_pdata; + int i; + + imx8_enable_clocks(sdev, priv->clks); + + for (i = 0; i < DSP_MU_CHAN_NUM; i++) + imx_dsp_request_channel(priv->dsp_ipc, i); + + return 0; +} + +static int imx8ulp_dsp_runtime_resume(struct snd_sof_dev *sdev) +{ + const struct sof_dsp_power_state target_dsp_state = { + .state = SOF_DSP_PM_D0, + .substate = 0, + }; + + imx8ulp_resume(sdev); + + return snd_sof_dsp_set_power_state(sdev, &target_dsp_state); +} + +static int imx8ulp_dsp_runtime_suspend(struct snd_sof_dev *sdev) +{ + const struct sof_dsp_power_state target_dsp_state = { + .state = SOF_DSP_PM_D3, + .substate = 0, + }; + + imx8ulp_suspend(sdev); + + return snd_sof_dsp_set_power_state(sdev, &target_dsp_state); +} + +static int imx8ulp_dsp_suspend(struct snd_sof_dev *sdev, unsigned int target_state) +{ + const struct sof_dsp_power_state target_dsp_state = { + .state = target_state, + .substate = 0, + }; + + if (!pm_runtime_suspended(sdev->dev)) + imx8ulp_suspend(sdev); + + return snd_sof_dsp_set_power_state(sdev, &target_dsp_state); +} + +static int imx8ulp_dsp_resume(struct snd_sof_dev *sdev) +{ + const struct sof_dsp_power_state target_dsp_state = { + .state = SOF_DSP_PM_D0, + .substate = 0, + }; + + imx8ulp_resume(sdev); + + if (pm_runtime_suspended(sdev->dev)) { + pm_runtime_disable(sdev->dev); + pm_runtime_set_active(sdev->dev); + pm_runtime_mark_last_busy(sdev->dev); + pm_runtime_enable(sdev->dev); + pm_runtime_idle(sdev->dev); + } + + return snd_sof_dsp_set_power_state(sdev, &target_dsp_state); +} + +static struct snd_soc_dai_driver imx8ulp_dai[] = { + { + .name = "sai5", + .playback = { + .channels_min = 1, + .channels_max = 32, + }, + .capture = { + .channels_min = 1, + .channels_max = 32, + }, + }, + { + .name = "sai6", + .playback = { + .channels_min = 1, + .channels_max = 32, + }, + .capture = { + .channels_min = 1, + .channels_max = 32, + }, + }, +}; + +static int imx8ulp_dsp_set_power_state(struct snd_sof_dev *sdev, + const struct sof_dsp_power_state *target_state) +{ + sdev->dsp_power_state = *target_state; + + return 0; +} + +/* i.MX8 ops */ +static struct snd_sof_dsp_ops sof_imx8ulp_ops = { + /* probe and remove */ + .probe = imx8ulp_probe, + .remove = imx8ulp_remove, + /* DSP core boot */ + .run = imx8ulp_run, + .reset = imx8ulp_reset, + + /* Block IO */ + .block_read = sof_block_read, + .block_write = sof_block_write, + + /* Module IO */ + .read64 = sof_io_read64, + + /* Mailbox IO */ + .mailbox_read = sof_mailbox_read, + .mailbox_write = sof_mailbox_write, + + /* ipc */ + .send_msg = imx8ulp_send_msg, + .get_mailbox_offset = imx8ulp_get_mailbox_offset, + .get_window_offset = imx8ulp_get_window_offset, + + .ipc_msg_data = sof_ipc_msg_data, + .set_stream_data_offset = sof_set_stream_data_offset, + + /* stream callbacks */ + .pcm_open = sof_stream_pcm_open, + .pcm_close = sof_stream_pcm_close, + + /* module loading */ + .get_bar_index = imx8ulp_get_bar_index, + /* firmware loading */ + .load_firmware = snd_sof_load_firmware_memcpy, + + /* Debug information */ + .dbg_dump = imx8_dump, + + /* Firmware ops */ + .dsp_arch_ops = &sof_xtensa_arch_ops, + + /* DAI drivers */ + .drv = imx8ulp_dai, + .num_drv = ARRAY_SIZE(imx8ulp_dai), + + /* ALSA HW info flags */ + .hw_info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, + + /* PM */ + .runtime_suspend = imx8ulp_dsp_runtime_suspend, + .runtime_resume = imx8ulp_dsp_runtime_resume, + + .suspend = imx8ulp_dsp_suspend, + .resume = imx8ulp_dsp_resume, + + .set_power_state = imx8ulp_dsp_set_power_state, +}; + +static struct sof_dev_desc sof_of_imx8ulp_desc = { + .ipc_supported_mask = BIT(SOF_IPC), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "imx/sof", + }, + .default_tplg_path = { + [SOF_IPC] = "imx/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-imx8ulp.ri", + }, + .nocodec_tplg_filename = "sof-imx8ulp-nocodec.tplg", + .ops = &sof_imx8ulp_ops, +}; + +static const struct of_device_id sof_of_imx8ulp_ids[] = { + { .compatible = "fsl,imx8ulp-dsp", .data = &sof_of_imx8ulp_desc}, + { } +}; +MODULE_DEVICE_TABLE(of, sof_of_imx8ulp_ids); + +/* DT driver definition */ +static struct platform_driver snd_sof_of_imx8ulp_driver = { + .probe = sof_of_probe, + .remove = sof_of_remove, + .driver = { + .name = "sof-audio-of-imx8ulp", + .pm = &sof_of_pm, + .of_match_table = sof_of_imx8ulp_ids, + }, +}; +module_platform_driver(snd_sof_of_imx8ulp_driver); + +MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/intel/Kconfig b/sound/soc/sof/intel/Kconfig index 88b6176af021..7af495fb6125 100644 --- a/sound/soc/sof/intel/Kconfig +++ b/sound/soc/sof/intel/Kconfig @@ -40,6 +40,7 @@ if SND_SOC_SOF_ACPI config SND_SOC_SOF_BAYTRAIL tristate "SOF support for Baytrail, Braswell and Cherrytrail" default SND_SOC_SOF_ACPI + select SND_SOC_SOF_IPC3 select SND_SOC_SOF_INTEL_COMMON select SND_SOC_SOF_INTEL_ATOM_HIFI_EP select SND_SOC_SOF_ACPI_DEV @@ -60,6 +61,7 @@ config SND_SOC_SOF_BAYTRAIL config SND_SOC_SOF_BROADWELL tristate "SOF support for Broadwell" default SND_SOC_SOF_ACPI + select SND_SOC_SOF_IPC3 select SND_SOC_SOF_INTEL_COMMON select SND_SOC_SOF_INTEL_HIFI_EP_IPC select SND_SOC_SOF_ACPI_DEV @@ -84,6 +86,8 @@ if SND_SOC_SOF_PCI config SND_SOC_SOF_MERRIFIELD tristate "SOF support for Tangier/Merrifield" default SND_SOC_SOF_PCI + select SND_SOC_SOF_PCI_DEV + select SND_SOC_SOF_IPC3 select SND_SOC_SOF_INTEL_ATOM_HIFI_EP help This adds support for Sound Open Firmware for Intel(R) platforms @@ -91,9 +95,36 @@ config SND_SOC_SOF_MERRIFIELD Say Y if you have such a device. If unsure select "N". +config SND_SOC_SOF_INTEL_SKL + tristate + select SND_SOC_SOF_HDA_COMMON + select SND_SOC_SOF_INTEL_IPC4 + +config SND_SOC_SOF_SKYLAKE + tristate "SOF support for SkyLake" + default SND_SOC_SOF_PCI + select SND_SOC_SOF_INTEL_SKL + help + This adds support for the Intel(R) platforms using the SkyLake processors. + Say Y if you have such a device. + If unsure select "N". + This is intended only for developers and not a recommend option for distros. + +config SND_SOC_SOF_KABYLAKE + tristate "SOF support for KabyLake" + default SND_SOC_SOF_PCI + select SND_SOC_SOF_INTEL_SKL + help + This adds support for the Intel(R) platforms using the KabyLake processors. + Say Y if you have such a device. + If unsure select "N". + This is intended only for developers and not a recommend option for distros. + config SND_SOC_SOF_INTEL_APL tristate select SND_SOC_SOF_HDA_COMMON + select SND_SOC_SOF_IPC3 + select SND_SOC_SOF_INTEL_IPC4 config SND_SOC_SOF_APOLLOLAKE tristate "SOF support for Apollolake" @@ -119,6 +150,8 @@ config SND_SOC_SOF_INTEL_CNL tristate select SND_SOC_SOF_HDA_COMMON select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE + select SND_SOC_SOF_IPC3 + select SND_SOC_SOF_INTEL_IPC4 config SND_SOC_SOF_CANNONLAKE tristate "SOF support for Cannonlake" @@ -153,6 +186,8 @@ config SND_SOC_SOF_INTEL_ICL tristate select SND_SOC_SOF_HDA_COMMON select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE + select SND_SOC_SOF_IPC3 + select SND_SOC_SOF_INTEL_IPC4 config SND_SOC_SOF_ICELAKE tristate "SOF support for Icelake" @@ -178,6 +213,8 @@ config SND_SOC_SOF_INTEL_TGL tristate select SND_SOC_SOF_HDA_COMMON select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE + select SND_SOC_SOF_IPC3 + select SND_SOC_SOF_INTEL_IPC4 config SND_SOC_SOF_TIGERLAKE tristate "SOF support for Tigerlake" @@ -209,12 +246,29 @@ config SND_SOC_SOF_ALDERLAKE Say Y if you have such a device. If unsure select "N". +config SND_SOC_SOF_INTEL_MTL + tristate + select SND_SOC_SOF_HDA_COMMON + select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE + select SND_SOC_SOF_INTEL_IPC4 + +config SND_SOC_SOF_METEORLAKE + tristate "SOF support for Meteorlake" + default SND_SOC_SOF_PCI + select SND_SOC_SOF_INTEL_MTL + help + This adds support for Sound Open Firmware for Intel(R) platforms + using the Meteorlake processors. + Say Y if you have such a device. + If unsure select "N". + config SND_SOC_SOF_HDA_COMMON tristate select SND_SOC_SOF_INTEL_COMMON select SND_SOC_SOF_PCI_DEV select SND_INTEL_DSP_CONFIG select SND_SOC_SOF_HDA_LINK_BASELINE + select SND_SOC_SOF_HDA_PROBES help This option is not user-selectable but automagically handled by 'select' statements at a higher level. @@ -240,15 +294,6 @@ config SND_SOC_SOF_HDA_AUDIO_CODEC 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) - Skylake and newer platforms. - Say Y if you want to enable probes. - If unsure, select "N". - endif ## SND_SOC_SOF_HDA_COMMON config SND_SOC_SOF_HDA_LINK_BASELINE @@ -266,6 +311,15 @@ config SND_SOC_SOF_HDA This option is not user-selectable but automagically handled by 'select' statements at a higher level. +config SND_SOC_SOF_HDA_PROBES + tristate + select SND_SOC_SOF_DEBUG_PROBES + help + The option enables the data probing for Intel(R) Skylake and newer + (HDA) platforms. + This option is not user-selectable but automagically handled by + 'select' statements at a higher level. + config SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE tristate select SOUNDWIRE_INTEL if SND_SOC_SOF_INTEL_SOUNDWIRE diff --git a/sound/soc/sof/intel/Makefile b/sound/soc/sof/intel/Makefile index 1f473d4d8416..8b8ea0361785 100644 --- a/sound/soc/sof/intel/Makefile +++ b/sound/soc/sof/intel/Makefile @@ -6,7 +6,9 @@ snd-sof-acpi-intel-bdw-objs := bdw.o snd-sof-intel-hda-common-objs := hda.o hda-loader.o hda-stream.o hda-trace.o \ hda-dsp.o hda-ipc.o hda-ctrl.o hda-pcm.o \ hda-dai.o hda-bus.o \ - apl.o cnl.o tgl.o icl.o + skl.o hda-loader-skl.o \ + apl.o cnl.o tgl.o icl.o mtl.o hda-common-ops.o + snd-sof-intel-hda-common-$(CONFIG_SND_SOC_SOF_HDA_PROBES) += hda-probes.o snd-sof-intel-hda-objs := hda-codec.o @@ -20,13 +22,17 @@ obj-$(CONFIG_SND_SOC_SOF_HDA_COMMON) += snd-sof-intel-hda-common.o obj-$(CONFIG_SND_SOC_SOF_HDA) += snd-sof-intel-hda.o snd-sof-pci-intel-tng-objs := pci-tng.o +snd-sof-pci-intel-skl-objs := pci-skl.o snd-sof-pci-intel-apl-objs := pci-apl.o snd-sof-pci-intel-cnl-objs := pci-cnl.o snd-sof-pci-intel-icl-objs := pci-icl.o snd-sof-pci-intel-tgl-objs := pci-tgl.o +snd-sof-pci-intel-mtl-objs := pci-mtl.o obj-$(CONFIG_SND_SOC_SOF_MERRIFIELD) += snd-sof-pci-intel-tng.o +obj-$(CONFIG_SND_SOC_SOF_INTEL_SKL) += snd-sof-pci-intel-skl.o obj-$(CONFIG_SND_SOC_SOF_INTEL_APL) += snd-sof-pci-intel-apl.o obj-$(CONFIG_SND_SOC_SOF_INTEL_CNL) += snd-sof-pci-intel-cnl.o obj-$(CONFIG_SND_SOC_SOF_INTEL_ICL) += snd-sof-pci-intel-icl.o obj-$(CONFIG_SND_SOC_SOF_INTEL_TGL) += snd-sof-pci-intel-tgl.o +obj-$(CONFIG_SND_SOC_SOF_INTEL_MTL) += snd-sof-pci-intel-mtl.o diff --git a/sound/soc/sof/intel/apl.c b/sound/soc/sof/intel/apl.c index 917f78cf6daf..1549ca7587a4 100644 --- a/sound/soc/sof/intel/apl.c +++ b/sound/soc/sof/intel/apl.c @@ -15,6 +15,8 @@ * Hardware interface for audio DSP on Apollolake and GeminiLake */ +#include <sound/sof/ext_manifest4.h> +#include "../ipc4-priv.h" #include "../sof-priv.h" #include "hda.h" #include "../sof-audio.h" @@ -26,113 +28,69 @@ static const struct snd_sof_debugfs_map apl_dsp_debugfs[] = { }; /* apollolake ops */ -const struct snd_sof_dsp_ops sof_apl_ops = { +struct snd_sof_dsp_ops sof_apl_ops; +EXPORT_SYMBOL_NS(sof_apl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); + +int sof_apl_ops_init(struct snd_sof_dev *sdev) +{ + /* common defaults */ + memcpy(&sof_apl_ops, &sof_hda_common_ops, sizeof(struct snd_sof_dsp_ops)); + /* probe/remove/shutdown */ - .probe = hda_dsp_probe, - .remove = hda_dsp_remove, - .shutdown = hda_dsp_shutdown, - - /* 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, - - /* Mailbox IO */ - .mailbox_read = sof_mailbox_read, - .mailbox_write = sof_mailbox_write, - - /* doorbell */ - .irq_thread = hda_dsp_ipc_irq_thread, - - /* ipc */ - .send_msg = hda_dsp_ipc_send_msg, - .fw_ready = sof_fw_ready, - .get_mailbox_offset = hda_dsp_ipc_get_mailbox_offset, - .get_window_offset = hda_dsp_ipc_get_window_offset, - - .ipc_msg_data = hda_ipc_msg_data, - .ipc_pcm_params = hda_ipc_pcm_params, - - /* machine driver */ - .machine_select = hda_machine_select, - .machine_register = sof_machine_register, - .machine_unregister = sof_machine_unregister, - .set_mach_params = hda_set_mach_params, + sof_apl_ops.shutdown = hda_dsp_shutdown; + + if (sdev->pdata->ipc_type == SOF_IPC) { + /* doorbell */ + sof_apl_ops.irq_thread = hda_dsp_ipc_irq_thread; + + /* ipc */ + sof_apl_ops.send_msg = hda_dsp_ipc_send_msg; + + /* debug */ + sof_apl_ops.ipc_dump = hda_ipc_dump; + } + + if (sdev->pdata->ipc_type == SOF_INTEL_IPC4) { + struct sof_ipc4_fw_data *ipc4_data; + + sdev->private = devm_kzalloc(sdev->dev, sizeof(*ipc4_data), GFP_KERNEL); + if (!sdev->private) + return -ENOMEM; + + ipc4_data = sdev->private; + ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET; + + ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_1_5; + + /* doorbell */ + sof_apl_ops.irq_thread = hda_dsp_ipc4_irq_thread; + + /* ipc */ + sof_apl_ops.send_msg = hda_dsp_ipc4_send_msg; + + /* debug */ + sof_apl_ops.ipc_dump = hda_ipc4_dump; + } + + /* set DAI driver ops */ + hda_set_dai_drv_ops(sdev, &sof_apl_ops); /* debug */ - .debug_map = apl_dsp_debugfs, - .debug_map_count = ARRAY_SIZE(apl_dsp_debugfs), - .dbg_dump = hda_dsp_dump, - .ipc_dump = hda_ipc_dump, - .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem, - - /* stream callbacks */ - .pcm_open = hda_dsp_pcm_open, - .pcm_close = hda_dsp_pcm_close, - .pcm_hw_params = hda_dsp_pcm_hw_params, - .pcm_hw_free = hda_dsp_stream_hw_free, - .pcm_trigger = hda_dsp_pcm_trigger, - .pcm_pointer = hda_dsp_pcm_pointer, - -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) - /* probe callbacks */ - .probe_assign = hda_probe_compr_assign, - .probe_free = hda_probe_compr_free, - .probe_set_params = hda_probe_compr_set_params, - .probe_trigger = hda_probe_compr_trigger, - .probe_pointer = hda_probe_compr_pointer, -#endif - - /* firmware loading */ - .load_firmware = snd_sof_load_firmware_raw, + sof_apl_ops.debug_map = apl_dsp_debugfs; + sof_apl_ops.debug_map_count = ARRAY_SIZE(apl_dsp_debugfs); /* firmware run */ - .run = hda_dsp_cl_boot_firmware, + sof_apl_ops.run = hda_dsp_cl_boot_firmware; /* pre/post fw run */ - .pre_fw_run = hda_dsp_pre_fw_run, - .post_fw_run = hda_dsp_post_fw_run, - - /* parse platform specific extended manifest */ - .parse_platform_ext_manifest = hda_dsp_ext_man_get_cavs_config_data, - - /* dsp core power up/down */ - .core_power_up = hda_dsp_enable_core, - .core_power_down = hda_dsp_core_reset_power_down, - - /* trace callback */ - .trace_init = hda_dsp_trace_init, - .trace_release = hda_dsp_trace_release, - .trace_trigger = hda_dsp_trace_trigger, - - /* DAI drivers */ - .drv = skl_dai, - .num_drv = SOF_SKL_NUM_DAIS, - - /* PM */ - .suspend = hda_dsp_suspend, - .resume = hda_dsp_resume, - .runtime_suspend = hda_dsp_runtime_suspend, - .runtime_resume = hda_dsp_runtime_resume, - .runtime_idle = hda_dsp_runtime_idle, - .set_hw_params_upon_resume = hda_dsp_set_hw_params_upon_resume, - .set_power_state = hda_dsp_set_power_state, - - /* ALSA HW info flags */ - .hw_info = SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_PAUSE | - SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, - - .dsp_arch_ops = &sof_xtensa_arch_ops, + sof_apl_ops.post_fw_run = hda_dsp_post_fw_run; + + /* dsp core get/put */ + sof_apl_ops.core_get = hda_dsp_core_get; + + return 0; }; -EXPORT_SYMBOL_NS(sof_apl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); +EXPORT_SYMBOL_NS(sof_apl_ops_init, SND_SOC_SOF_INTEL_HDA_COMMON); const struct sof_intel_dsp_desc apl_chip_info = { /* Apollolake */ @@ -144,8 +102,15 @@ const struct sof_intel_dsp_desc apl_chip_info = { .ipc_ack = HDA_DSP_REG_HIPCIE, .ipc_ack_mask = HDA_DSP_REG_HIPCIE_DONE, .ipc_ctl = HDA_DSP_REG_HIPCCTL, + .rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS, .rom_init_timeout = 150, .ssp_count = APL_SSP_COUNT, .ssp_base_offset = APL_SSP_BASE_OFFSET, + .quirks = SOF_INTEL_PROCEN_FMT_QUIRK, + .check_ipc_irq = hda_dsp_check_ipc_irq, + .cl_init = cl_dsp_init, + .power_down_dsp = hda_power_down_dsp, + .disable_interrupts = hda_dsp_disable_interrupts, + .hw_ip_version = SOF_INTEL_CAVS_1_5_PLUS, }; EXPORT_SYMBOL_NS(apl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON); diff --git a/sound/soc/sof/intel/atom.c b/sound/soc/sof/intel/atom.c index 74c630bb9847..bd9789b483b1 100644 --- a/sound/soc/sof/intel/atom.c +++ b/sound/soc/sof/intel/atom.c @@ -27,7 +27,6 @@ static void atom_host_done(struct snd_sof_dev *sdev); static void atom_dsp_done(struct snd_sof_dev *sdev); -static void atom_get_reply(struct snd_sof_dev *sdev); /* * Debug @@ -71,8 +70,8 @@ void atom_dump(struct snd_sof_dev *sdev, u32 flags) panic = snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_IPCX); atom_get_registers(sdev, &xoops, &panic_info, stack, STACK_DUMP_SIZE); - snd_sof_get_status(sdev, status, panic, &xoops, &panic_info, stack, - STACK_DUMP_SIZE); + sof_print_oops_and_stack(sdev, KERN_ERR, status, panic, &xoops, + &panic_info, stack, STACK_DUMP_SIZE); /* provide some context for firmware debug */ imrx = snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_IMRX); @@ -154,8 +153,7 @@ irqreturn_t atom_irq_thread(int irq, void *context) * because the done bit can't be set in cmd_done function * which is triggered by msg */ - atom_get_reply(sdev); - snd_sof_ipc_reply(sdev, ipcx); + snd_sof_ipc_process_reply(sdev, ipcx); atom_dsp_done(sdev); @@ -167,8 +165,8 @@ irqreturn_t atom_irq_thread(int irq, void *context) /* Handle messages from DSP Core */ if ((ipcd & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) { - snd_sof_dsp_panic(sdev, PANIC_OFFSET(ipcd) + - MBOX_OFFSET); + snd_sof_dsp_panic(sdev, PANIC_OFFSET(ipcd) + MBOX_OFFSET, + true); } else { snd_sof_ipc_msgs_rx(sdev); } @@ -195,45 +193,6 @@ int atom_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) } EXPORT_SYMBOL_NS(atom_send_msg, SND_SOC_SOF_INTEL_ATOM_HIFI_EP); -static void atom_get_reply(struct snd_sof_dev *sdev) -{ - struct snd_sof_ipc_msg *msg = sdev->msg; - struct sof_ipc_reply reply; - int ret = 0; - - /* - * Sometimes, there is unexpected reply ipc arriving. The reply - * ipc belongs to none of the ipcs sent from driver. - * In this case, the driver must ignore the ipc. - */ - if (!msg) { - dev_warn(sdev->dev, "unexpected ipc interrupt raised!\n"); - return; - } - - /* get reply */ - sof_mailbox_read(sdev, sdev->host_box.offset, &reply, sizeof(reply)); - - if (reply.error < 0) { - memcpy(msg->reply_data, &reply, sizeof(reply)); - ret = reply.error; - } else { - /* reply correct size ? */ - if (reply.hdr.size != msg->reply_size) { - dev_err(sdev->dev, "error: reply expected %zu got %u bytes\n", - msg->reply_size, reply.hdr.size); - ret = -EINVAL; - } - - /* read the message */ - if (msg->reply_size > 0) - sof_mailbox_read(sdev, sdev->host_box.offset, - msg->reply_data, msg->reply_size); - } - - msg->reply_error = ret; -} - int atom_get_mailbox_offset(struct snd_sof_dev *sdev) { return MBOX_OFFSET; @@ -315,26 +274,26 @@ static const char *fixup_tplg_name(struct snd_sof_dev *sdev, const char *ssp_str) { const char *tplg_filename = NULL; - char *filename; - char *split_ext; + const char *split_ext; + char *filename, *tmp; - filename = devm_kstrdup(sdev->dev, sof_tplg_filename, GFP_KERNEL); + filename = kstrdup(sof_tplg_filename, GFP_KERNEL); if (!filename) return NULL; /* this assumes a .tplg extension */ - split_ext = strsep(&filename, "."); - if (split_ext) { + tmp = filename; + split_ext = strsep(&tmp, "."); + if (split_ext) tplg_filename = devm_kasprintf(sdev->dev, GFP_KERNEL, "%s-%s.tplg", split_ext, ssp_str); - if (!tplg_filename) - return NULL; - } + kfree(filename); + return tplg_filename; } -void atom_machine_select(struct snd_sof_dev *sdev) +struct snd_soc_acpi_mach *atom_machine_select(struct snd_sof_dev *sdev) { struct snd_sof_pdata *sof_pdata = sdev->pdata; const struct sof_dev_desc *desc = sof_pdata->desc; @@ -345,7 +304,7 @@ void atom_machine_select(struct snd_sof_dev *sdev) mach = snd_soc_acpi_find_machine(desc->machines); if (!mach) { dev_warn(sdev->dev, "warning: No matching ASoC machine driver found\n"); - return; + return NULL; } pdev = to_platform_device(sdev->dev); @@ -363,12 +322,13 @@ void atom_machine_select(struct snd_sof_dev *sdev) if (!tplg_filename) { dev_dbg(sdev->dev, "error: no topology filename\n"); - return; + return NULL; } sof_pdata->tplg_filename = tplg_filename; mach->mach_params.acpi_ipc_irq_index = desc->irqindex_host_ipc; - sof_pdata->machine = mach; + + return mach; } EXPORT_SYMBOL_NS(atom_machine_select, SND_SOC_SOF_INTEL_ATOM_HIFI_EP); @@ -443,14 +403,14 @@ struct snd_soc_dai_driver atom_dai[] = { }; EXPORT_SYMBOL_NS(atom_dai, SND_SOC_SOF_INTEL_ATOM_HIFI_EP); -void atom_set_mach_params(const struct snd_soc_acpi_mach *mach, +void atom_set_mach_params(struct snd_soc_acpi_mach *mach, struct snd_sof_dev *sdev) { struct snd_sof_pdata *pdata = sdev->pdata; const struct sof_dev_desc *desc = pdata->desc; struct snd_soc_acpi_mach_params *mach_params; - mach_params = (struct snd_soc_acpi_mach_params *)&mach->mach_params; + mach_params = &mach->mach_params; mach_params->platform = dev_name(sdev->dev); mach_params->num_dai_drivers = desc->ops->num_drv; mach_params->dai_drivers = desc->ops->drv; diff --git a/sound/soc/sof/intel/atom.h b/sound/soc/sof/intel/atom.h index 96a462c7a2e5..b965e5e080a6 100644 --- a/sound/soc/sof/intel/atom.h +++ b/sound/soc/sof/intel/atom.h @@ -65,8 +65,8 @@ int atom_run(struct snd_sof_dev *sdev); int atom_reset(struct snd_sof_dev *sdev); void atom_dump(struct snd_sof_dev *sdev, u32 flags); -void atom_machine_select(struct snd_sof_dev *sdev); -void atom_set_mach_params(const struct snd_soc_acpi_mach *mach, +struct snd_soc_acpi_mach *atom_machine_select(struct snd_sof_dev *sdev); +void atom_set_mach_params(struct snd_soc_acpi_mach *mach, struct snd_sof_dev *sdev); extern struct snd_soc_dai_driver atom_dai[]; diff --git a/sound/soc/sof/intel/bdw.c b/sound/soc/sof/intel/bdw.c index 2c09a523288e..a446154f2803 100644 --- a/sound/soc/sof/intel/bdw.c +++ b/sound/soc/sof/intel/bdw.c @@ -75,7 +75,6 @@ static const struct snd_sof_debugfs_map bdw_debugfs[] = { static void bdw_host_done(struct snd_sof_dev *sdev); static void bdw_dsp_done(struct snd_sof_dev *sdev); -static void bdw_get_reply(struct snd_sof_dev *sdev); /* * DSP Control. @@ -259,8 +258,8 @@ static void bdw_dump(struct snd_sof_dev *sdev, u32 flags) panic = snd_sof_dsp_read(sdev, BDW_DSP_BAR, SHIM_IPCX); bdw_get_registers(sdev, &xoops, &panic_info, stack, BDW_STACK_DUMP_SIZE); - snd_sof_get_status(sdev, status, panic, &xoops, &panic_info, stack, - BDW_STACK_DUMP_SIZE); + sof_print_oops_and_stack(sdev, KERN_ERR, status, panic, &xoops, + &panic_info, stack, BDW_STACK_DUMP_SIZE); /* provide some context for firmware debug */ imrx = snd_sof_dsp_read(sdev, BDW_DSP_BAR, SHIM_IMRX); @@ -326,8 +325,7 @@ static irqreturn_t bdw_irq_thread(int irq, void *context) * because the done bit can't be set in cmd_done function * which is triggered by msg */ - bdw_get_reply(sdev); - snd_sof_ipc_reply(sdev, ipcx); + snd_sof_ipc_process_reply(sdev, ipcx); bdw_dsp_done(sdev); @@ -346,8 +344,8 @@ static irqreturn_t bdw_irq_thread(int irq, void *context) /* Handle messages from DSP Core */ if ((ipcd & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) { - snd_sof_dsp_panic(sdev, BDW_PANIC_OFFSET(ipcx) + - MBOX_OFFSET); + snd_sof_dsp_panic(sdev, BDW_PANIC_OFFSET(ipcx) + MBOX_OFFSET, + true); } else { snd_sof_ipc_msgs_rx(sdev); } @@ -372,45 +370,6 @@ static int bdw_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) return 0; } -static void bdw_get_reply(struct snd_sof_dev *sdev) -{ - struct snd_sof_ipc_msg *msg = sdev->msg; - struct sof_ipc_reply reply; - int ret = 0; - - /* - * Sometimes, there is unexpected reply ipc arriving. The reply - * ipc belongs to none of the ipcs sent from driver. - * In this case, the driver must ignore the ipc. - */ - if (!msg) { - dev_warn(sdev->dev, "unexpected ipc interrupt raised!\n"); - return; - } - - /* get reply */ - sof_mailbox_read(sdev, sdev->host_box.offset, &reply, sizeof(reply)); - - if (reply.error < 0) { - memcpy(msg->reply_data, &reply, sizeof(reply)); - ret = reply.error; - } else { - /* reply correct size ? */ - if (reply.hdr.size != msg->reply_size) { - dev_err(sdev->dev, "error: reply expected %zu got %u bytes\n", - msg->reply_size, reply.hdr.size); - ret = -EINVAL; - } - - /* read the message */ - if (msg->reply_size > 0) - sof_mailbox_read(sdev, sdev->host_box.offset, - msg->reply_data, msg->reply_size); - } - - msg->reply_error = ret; -} - static int bdw_get_mailbox_offset(struct snd_sof_dev *sdev) { return MBOX_OFFSET; @@ -453,10 +412,19 @@ static int bdw_probe(struct snd_sof_dev *sdev) const struct sof_dev_desc *desc = pdata->desc; struct platform_device *pdev = container_of(sdev->dev, struct platform_device, dev); + const struct sof_intel_dsp_desc *chip; struct resource *mmio; u32 base, size; int ret; + chip = get_chip_info(sdev->pdata); + if (!chip) { + dev_err(sdev->dev, "error: no such device supported\n"); + return -EIO; + } + + sdev->num_cores = chip->cores_num; + /* LPE base */ mmio = platform_get_resource(pdev, IORESOURCE_MEM, desc->resindex_lpe_base); @@ -541,7 +509,7 @@ static int bdw_probe(struct snd_sof_dev *sdev) return ret; } -static void bdw_machine_select(struct snd_sof_dev *sdev) +static struct snd_soc_acpi_mach *bdw_machine_select(struct snd_sof_dev *sdev) { struct snd_sof_pdata *sof_pdata = sdev->pdata; const struct sof_dev_desc *desc = sof_pdata->desc; @@ -550,22 +518,23 @@ static void bdw_machine_select(struct snd_sof_dev *sdev) mach = snd_soc_acpi_find_machine(desc->machines); if (!mach) { dev_warn(sdev->dev, "warning: No matching ASoC machine driver found\n"); - return; + return NULL; } sof_pdata->tplg_filename = mach->sof_tplg_filename; mach->mach_params.acpi_ipc_irq_index = desc->irqindex_host_ipc; - sof_pdata->machine = mach; + + return mach; } -static void bdw_set_mach_params(const struct snd_soc_acpi_mach *mach, +static void bdw_set_mach_params(struct snd_soc_acpi_mach *mach, struct snd_sof_dev *sdev) { struct snd_sof_pdata *pdata = sdev->pdata; const struct sof_dev_desc *desc = pdata->desc; struct snd_soc_acpi_mach_params *mach_params; - mach_params = (struct snd_soc_acpi_mach_params *)&mach->mach_params; + mach_params = &mach->mach_params; mach_params->platform = dev_name(sdev->dev); mach_params->num_dai_drivers = desc->ops->num_drv; mach_params->dai_drivers = desc->ops->drv; @@ -598,7 +567,7 @@ static struct snd_soc_dai_driver bdw_dai[] = { }; /* broadwell ops */ -static const struct snd_sof_dsp_ops sof_bdw_ops = { +static struct snd_sof_dsp_ops sof_bdw_ops = { /*Device init */ .probe = bdw_probe, @@ -622,12 +591,11 @@ static const struct snd_sof_dsp_ops sof_bdw_ops = { /* ipc */ .send_msg = bdw_send_msg, - .fw_ready = sof_fw_ready, .get_mailbox_offset = bdw_get_mailbox_offset, .get_window_offset = bdw_get_window_offset, .ipc_msg_data = sof_ipc_msg_data, - .ipc_pcm_params = sof_ipc_pcm_params, + .set_stream_data_offset = sof_set_stream_data_offset, /* machine driver */ .machine_select = bdw_machine_select, @@ -645,9 +613,6 @@ static const struct snd_sof_dsp_ops sof_bdw_ops = { .pcm_open = sof_stream_pcm_open, .pcm_close = sof_stream_pcm_close, - /* Module loading */ - .load_module = snd_sof_parse_module_memcpy, - /*Firmware loading */ .load_firmware = snd_sof_load_firmware_memcpy, @@ -668,6 +633,7 @@ static const struct snd_sof_dsp_ops sof_bdw_ops = { static const struct sof_intel_dsp_desc bdw_chip_info = { .cores_num = 1, .host_managed_cores_mask = 1, + .hw_ip_version = SOF_INTEL_BROADWELL, }; static const struct sof_dev_desc sof_acpi_broadwell_desc = { @@ -677,9 +643,17 @@ static const struct sof_dev_desc sof_acpi_broadwell_desc = { .resindex_imr_base = -1, .irqindex_host_ipc = 0, .chip_info = &bdw_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-bdw.ri", + .ipc_supported_mask = BIT(SOF_IPC), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-bdw.ri", + }, .nocodec_tplg_filename = "sof-bdw-nocodec.tplg", .ops = &sof_bdw_ops, }; @@ -707,11 +681,8 @@ static int sof_broadwell_probe(struct platform_device *pdev) return -ENODEV; } - desc = device_get_match_data(dev); - if (!desc) - return -ENODEV; - - return sof_acpi_probe(pdev, device_get_match_data(dev)); + desc = (const struct sof_dev_desc *)id->driver_data; + return sof_acpi_probe(pdev, desc); } /* acpi_driver definition */ diff --git a/sound/soc/sof/intel/byt.c b/sound/soc/sof/intel/byt.c index e2fa08f1ae74..e6dc4ff531c3 100644 --- a/sound/soc/sof/intel/byt.c +++ b/sound/soc/sof/intel/byt.c @@ -113,10 +113,19 @@ static int byt_acpi_probe(struct snd_sof_dev *sdev) const struct sof_dev_desc *desc = pdata->desc; struct platform_device *pdev = container_of(sdev->dev, struct platform_device, dev); + const struct sof_intel_dsp_desc *chip; struct resource *mmio; u32 base, size; int ret; + chip = get_chip_info(sdev->pdata); + if (!chip) { + dev_err(sdev->dev, "error: no such device supported\n"); + return -EIO; + } + + sdev->num_cores = chip->cores_num; + /* DSP DMA can only access low 31 bits of host memory */ ret = dma_coerce_mask_and_coherent(sdev->dev, DMA_BIT_MASK(31)); if (ret < 0) { @@ -207,7 +216,7 @@ irq: } /* baytrail ops */ -static const struct snd_sof_dsp_ops sof_byt_ops = { +static struct snd_sof_dsp_ops sof_byt_ops = { /* device init */ .probe = byt_acpi_probe, .remove = byt_remove, @@ -236,12 +245,11 @@ static const struct snd_sof_dsp_ops sof_byt_ops = { /* ipc */ .send_msg = atom_send_msg, - .fw_ready = sof_fw_ready, .get_mailbox_offset = atom_get_mailbox_offset, .get_window_offset = atom_get_window_offset, .ipc_msg_data = sof_ipc_msg_data, - .ipc_pcm_params = sof_ipc_pcm_params, + .set_stream_data_offset = sof_set_stream_data_offset, /* machine driver */ .machine_select = atom_machine_select, @@ -259,9 +267,6 @@ static const struct snd_sof_dsp_ops sof_byt_ops = { .pcm_open = sof_stream_pcm_open, .pcm_close = sof_stream_pcm_close, - /* module loading */ - .load_module = snd_sof_parse_module_memcpy, - /*Firmware loading */ .load_firmware = snd_sof_load_firmware_memcpy, @@ -286,10 +291,11 @@ static const struct snd_sof_dsp_ops sof_byt_ops = { static const struct sof_intel_dsp_desc byt_chip_info = { .cores_num = 1, .host_managed_cores_mask = 1, + .hw_ip_version = SOF_INTEL_BAYTRAIL, }; /* cherrytrail and braswell ops */ -static const struct snd_sof_dsp_ops sof_cht_ops = { +static struct snd_sof_dsp_ops sof_cht_ops = { /* device init */ .probe = byt_acpi_probe, .remove = byt_remove, @@ -318,12 +324,11 @@ static const struct snd_sof_dsp_ops sof_cht_ops = { /* ipc */ .send_msg = atom_send_msg, - .fw_ready = sof_fw_ready, .get_mailbox_offset = atom_get_mailbox_offset, .get_window_offset = atom_get_window_offset, .ipc_msg_data = sof_ipc_msg_data, - .ipc_pcm_params = sof_ipc_pcm_params, + .set_stream_data_offset = sof_set_stream_data_offset, /* machine driver */ .machine_select = atom_machine_select, @@ -341,9 +346,6 @@ static const struct snd_sof_dsp_ops sof_cht_ops = { .pcm_open = sof_stream_pcm_open, .pcm_close = sof_stream_pcm_close, - /* module loading */ - .load_module = snd_sof_parse_module_memcpy, - /*Firmware loading */ .load_firmware = snd_sof_load_firmware_memcpy, @@ -369,6 +371,7 @@ static const struct snd_sof_dsp_ops sof_cht_ops = { static const struct sof_intel_dsp_desc cht_chip_info = { .cores_num = 1, .host_managed_cores_mask = 1, + .hw_ip_version = SOF_INTEL_BAYTRAIL, }; /* BYTCR uses different IRQ index */ @@ -379,9 +382,17 @@ static const struct sof_dev_desc sof_acpi_baytrailcr_desc = { .resindex_imr_base = 2, .irqindex_host_ipc = 0, .chip_info = &byt_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-byt.ri", + .ipc_supported_mask = BIT(SOF_IPC), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-byt.ri", + }, .nocodec_tplg_filename = "sof-byt-nocodec.tplg", .ops = &sof_byt_ops, }; @@ -393,9 +404,17 @@ static const struct sof_dev_desc sof_acpi_baytrail_desc = { .resindex_imr_base = 2, .irqindex_host_ipc = 5, .chip_info = &byt_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-byt.ri", + .ipc_supported_mask = BIT(SOF_IPC), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-byt.ri", + }, .nocodec_tplg_filename = "sof-byt-nocodec.tplg", .ops = &sof_byt_ops, }; @@ -407,9 +426,17 @@ static const struct sof_dev_desc sof_acpi_cherrytrail_desc = { .resindex_imr_base = 2, .irqindex_host_ipc = 5, .chip_info = &cht_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-cht.ri", + .ipc_supported_mask = BIT(SOF_IPC), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-cht.ri", + }, .nocodec_tplg_filename = "sof-cht-nocodec.tplg", .ops = &sof_cht_ops, }; @@ -438,10 +465,7 @@ static int sof_baytrail_probe(struct platform_device *pdev) return -ENODEV; } - desc = device_get_match_data(&pdev->dev); - if (!desc) - return -ENODEV; - + desc = (const struct sof_dev_desc *)id->driver_data; if (desc == &sof_acpi_baytrail_desc && soc_intel_is_byt_cr(pdev)) desc = &sof_acpi_baytrailcr_desc; diff --git a/sound/soc/sof/intel/cnl.c b/sound/soc/sof/intel/cnl.c index 3957e2b3db32..19d0b1909bfd 100644 --- a/sound/soc/sof/intel/cnl.c +++ b/sound/soc/sof/intel/cnl.c @@ -15,6 +15,10 @@ * Hardware interface for audio DSP on Cannonlake. */ +#include <sound/sof/ext_manifest4.h> +#include <sound/sof/ipc4/header.h> +#include <trace/events/sof_intel.h> +#include "../ipc4-priv.h" #include "../ops.h" #include "hda.h" #include "hda-ipc.h" @@ -29,6 +33,74 @@ static const struct snd_sof_debugfs_map cnl_dsp_debugfs[] = { static void cnl_ipc_host_done(struct snd_sof_dev *sdev); static void cnl_ipc_dsp_done(struct snd_sof_dev *sdev); +irqreturn_t cnl_ipc4_irq_thread(int irq, void *context) +{ + struct sof_ipc4_msg notification_data = {{ 0 }}; + struct snd_sof_dev *sdev = context; + bool ipc_irq = false; + u32 hipcida, hipctdr; + + hipcida = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDA); + if (hipcida & CNL_DSP_REG_HIPCIDA_DONE) { + /* DSP received the message */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, + CNL_DSP_REG_HIPCCTL, + CNL_DSP_REG_HIPCCTL_DONE, 0); + cnl_ipc_dsp_done(sdev); + + ipc_irq = true; + } + + hipctdr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCTDR); + if (hipctdr & CNL_DSP_REG_HIPCTDR_BUSY) { + /* Message from DSP (reply or notification) */ + u32 hipctdd = snd_sof_dsp_read(sdev, HDA_DSP_BAR, + CNL_DSP_REG_HIPCTDD); + u32 primary = hipctdr & CNL_DSP_REG_HIPCTDR_MSG_MASK; + u32 extension = hipctdd & CNL_DSP_REG_HIPCTDD_MSG_MASK; + + if (primary & SOF_IPC4_MSG_DIR_MASK) { + /* Reply received */ + if (likely(sdev->fw_state == SOF_FW_BOOT_COMPLETE)) { + struct sof_ipc4_msg *data = sdev->ipc->msg.reply_data; + + data->primary = primary; + data->extension = extension; + + spin_lock_irq(&sdev->ipc_lock); + + snd_sof_ipc_get_reply(sdev); + snd_sof_ipc_reply(sdev, data->primary); + + spin_unlock_irq(&sdev->ipc_lock); + } else { + dev_dbg_ratelimited(sdev->dev, + "IPC reply before FW_READY: %#x|%#x\n", + primary, extension); + } + } else { + /* Notification received */ + notification_data.primary = primary; + notification_data.extension = extension; + + sdev->ipc->msg.rx_data = ¬ification_data; + snd_sof_ipc_msgs_rx(sdev); + sdev->ipc->msg.rx_data = NULL; + } + + /* Let DSP know that we have finished processing the message */ + cnl_ipc_host_done(sdev); + + ipc_irq = true; + } + + if (!ipc_irq) + /* This interrupt is not shared so no need to return IRQ_NONE. */ + dev_dbg_ratelimited(sdev->dev, "nothing to do in IPC IRQ thread\n"); + + return IRQ_HANDLED; +} + irqreturn_t cnl_ipc_irq_thread(int irq, void *context) { struct snd_sof_dev *sdev = context; @@ -50,24 +122,27 @@ irqreturn_t cnl_ipc_irq_thread(int irq, void *context) msg_ext = hipci & CNL_DSP_REG_HIPCIDR_MSG_MASK; msg = hipcida & CNL_DSP_REG_HIPCIDA_MSG_MASK; - dev_vdbg(sdev->dev, - "ipc: firmware response, msg:0x%x, msg_ext:0x%x\n", - msg, msg_ext); + trace_sof_intel_ipc_firmware_response(sdev, msg, msg_ext); /* mask Done interrupt */ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCCTL, CNL_DSP_REG_HIPCCTL_DONE, 0); - spin_lock_irq(&sdev->ipc_lock); + if (likely(sdev->fw_state == SOF_FW_BOOT_COMPLETE)) { + spin_lock_irq(&sdev->ipc_lock); - /* handle immediate reply from DSP core */ - hda_dsp_ipc_get_reply(sdev); - snd_sof_ipc_reply(sdev, msg); + /* handle immediate reply from DSP core */ + hda_dsp_ipc_get_reply(sdev); + snd_sof_ipc_reply(sdev, msg); - cnl_ipc_dsp_done(sdev); + cnl_ipc_dsp_done(sdev); - spin_unlock_irq(&sdev->ipc_lock); + spin_unlock_irq(&sdev->ipc_lock); + } else { + dev_dbg_ratelimited(sdev->dev, "IPC reply before FW_READY: %#x\n", + msg); + } ipc_irq = true; } @@ -77,14 +152,27 @@ irqreturn_t cnl_ipc_irq_thread(int irq, void *context) msg = hipctdr & CNL_DSP_REG_HIPCTDR_MSG_MASK; msg_ext = hipctdd & CNL_DSP_REG_HIPCTDD_MSG_MASK; - dev_vdbg(sdev->dev, - "ipc: firmware initiated, msg:0x%x, msg_ext:0x%x\n", - msg, msg_ext); + trace_sof_intel_ipc_firmware_initiated(sdev, msg, msg_ext); /* handle messages from DSP */ - if ((hipctdr & SOF_IPC_PANIC_MAGIC_MASK) == - SOF_IPC_PANIC_MAGIC) { - snd_sof_dsp_panic(sdev, HDA_DSP_PANIC_OFFSET(msg_ext)); + if ((hipctdr & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) { + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + bool non_recoverable = true; + + /* + * This is a PANIC message! + * + * If it is arriving during firmware boot and it is not + * the last boot attempt then change the non_recoverable + * to false as the DSP might be able to boot in the next + * iteration(s) + */ + if (sdev->fw_state == SOF_FW_BOOT_IN_PROGRESS && + hda->boot_iteration < HDA_FW_BOOT_ATTEMPTS) + non_recoverable = false; + + snd_sof_dsp_panic(sdev, HDA_DSP_PANIC_OFFSET(msg_ext), + non_recoverable); } else { snd_sof_ipc_msgs_rx(sdev); } @@ -146,11 +234,9 @@ static void cnl_ipc_dsp_done(struct snd_sof_dev *sdev) static bool cnl_compact_ipc_compress(struct snd_sof_ipc_msg *msg, u32 *dr, u32 *dd) { - struct sof_ipc_pm_gate *pm_gate; - - if (msg->header == (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_GATE)) { - pm_gate = msg->msg_data; + struct sof_ipc_pm_gate *pm_gate = msg->msg_data; + if (pm_gate->hdr.cmd == (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_GATE)) { /* send the compact message via the primary register */ *dr = HDA_IPC_MSG_COMPACT | HDA_IPC_PM_GATE; @@ -163,6 +249,22 @@ static bool cnl_compact_ipc_compress(struct snd_sof_ipc_msg *msg, return false; } +int cnl_ipc4_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) +{ + struct sof_ipc4_msg *msg_data = msg->msg_data; + + /* send the message via mailbox */ + if (msg_data->data_size) + sof_mailbox_write(sdev, sdev->host_box.offset, msg_data->data_ptr, + msg_data->data_size); + + snd_sof_dsp_write(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDD, msg_data->extension); + snd_sof_dsp_write(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDR, + msg_data->primary | CNL_DSP_REG_HIPCIDR_BUSY); + + return 0; +} + int cnl_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) { struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata; @@ -230,114 +332,92 @@ void cnl_ipc_dump(struct snd_sof_dev *sdev) hipcida, hipctdr, hipcctl); } +void cnl_ipc4_dump(struct snd_sof_dev *sdev) +{ + u32 hipcidr, hipcidd, hipcida, hipctdr, hipctdd, hipctda, hipcctl; + + hda_ipc_irq_dump(sdev); + + hipcidr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDR); + hipcidd = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDD); + hipcida = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDA); + hipctdr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCTDR); + hipctdd = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCTDD); + hipctda = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCTDA); + hipcctl = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCCTL); + + /* dump the IPC regs */ + /* TODO: parse the raw msg */ + dev_err(sdev->dev, + "Host IPC initiator: %#x|%#x|%#x, target: %#x|%#x|%#x, ctl: %#x\n", + hipcidr, hipcidd, hipcida, hipctdr, hipctdd, hipctda, hipcctl); +} + /* cannonlake ops */ -const struct snd_sof_dsp_ops sof_cnl_ops = { +struct snd_sof_dsp_ops sof_cnl_ops; +EXPORT_SYMBOL_NS(sof_cnl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); + +int sof_cnl_ops_init(struct snd_sof_dev *sdev) +{ + /* common defaults */ + memcpy(&sof_cnl_ops, &sof_hda_common_ops, sizeof(struct snd_sof_dsp_ops)); + /* probe/remove/shutdown */ - .probe = hda_dsp_probe, - .remove = hda_dsp_remove, - .shutdown = hda_dsp_shutdown, + sof_cnl_ops.shutdown = hda_dsp_shutdown; - /* Register IO */ - .write = sof_io_write, - .read = sof_io_read, - .write64 = sof_io_write64, - .read64 = sof_io_read64, + /* ipc */ + if (sdev->pdata->ipc_type == SOF_IPC) { + /* doorbell */ + sof_cnl_ops.irq_thread = cnl_ipc_irq_thread; - /* Block IO */ - .block_read = sof_block_read, - .block_write = sof_block_write, + /* ipc */ + sof_cnl_ops.send_msg = cnl_ipc_send_msg; - /* Mailbox IO */ - .mailbox_read = sof_mailbox_read, - .mailbox_write = sof_mailbox_write, + /* debug */ + sof_cnl_ops.ipc_dump = cnl_ipc_dump; + } - /* doorbell */ - .irq_thread = cnl_ipc_irq_thread, + if (sdev->pdata->ipc_type == SOF_INTEL_IPC4) { + struct sof_ipc4_fw_data *ipc4_data; - /* 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, + sdev->private = devm_kzalloc(sdev->dev, sizeof(*ipc4_data), GFP_KERNEL); + if (!sdev->private) + return -ENOMEM; + + ipc4_data = sdev->private; + ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET; + + ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_1_8; - .ipc_msg_data = hda_ipc_msg_data, - .ipc_pcm_params = hda_ipc_pcm_params, + /* doorbell */ + sof_cnl_ops.irq_thread = cnl_ipc4_irq_thread; - /* machine driver */ - .machine_select = hda_machine_select, - .machine_register = sof_machine_register, - .machine_unregister = sof_machine_unregister, - .set_mach_params = hda_set_mach_params, + /* ipc */ + sof_cnl_ops.send_msg = cnl_ipc4_send_msg; + + /* debug */ + sof_cnl_ops.ipc_dump = cnl_ipc4_dump; + } + + /* set DAI driver ops */ + hda_set_dai_drv_ops(sdev, &sof_cnl_ops); /* debug */ - .debug_map = cnl_dsp_debugfs, - .debug_map_count = ARRAY_SIZE(cnl_dsp_debugfs), - .dbg_dump = hda_dsp_dump, - .ipc_dump = cnl_ipc_dump, - .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem, - - /* stream callbacks */ - .pcm_open = hda_dsp_pcm_open, - .pcm_close = hda_dsp_pcm_close, - .pcm_hw_params = hda_dsp_pcm_hw_params, - .pcm_hw_free = hda_dsp_stream_hw_free, - .pcm_trigger = hda_dsp_pcm_trigger, - .pcm_pointer = hda_dsp_pcm_pointer, - -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) - /* probe callbacks */ - .probe_assign = hda_probe_compr_assign, - .probe_free = hda_probe_compr_free, - .probe_set_params = hda_probe_compr_set_params, - .probe_trigger = hda_probe_compr_trigger, - .probe_pointer = hda_probe_compr_pointer, -#endif - - /* firmware loading */ - .load_firmware = snd_sof_load_firmware_raw, + sof_cnl_ops.debug_map = cnl_dsp_debugfs; + sof_cnl_ops.debug_map_count = ARRAY_SIZE(cnl_dsp_debugfs); /* pre/post fw run */ - .pre_fw_run = hda_dsp_pre_fw_run, - .post_fw_run = hda_dsp_post_fw_run, + sof_cnl_ops.post_fw_run = hda_dsp_post_fw_run; - /* parse platform specific extended manifest */ - .parse_platform_ext_manifest = hda_dsp_ext_man_get_cavs_config_data, + /* firmware run */ + sof_cnl_ops.run = hda_dsp_cl_boot_firmware; - /* dsp core power up/down */ - .core_power_up = hda_dsp_enable_core, - .core_power_down = hda_dsp_core_reset_power_down, + /* dsp core get/put */ + sof_cnl_ops.core_get = hda_dsp_core_get; - /* firmware run */ - .run = hda_dsp_cl_boot_firmware, - - /* trace callback */ - .trace_init = hda_dsp_trace_init, - .trace_release = hda_dsp_trace_release, - .trace_trigger = hda_dsp_trace_trigger, - - /* DAI drivers */ - .drv = skl_dai, - .num_drv = SOF_SKL_NUM_DAIS, - - /* PM */ - .suspend = hda_dsp_suspend, - .resume = hda_dsp_resume, - .runtime_suspend = hda_dsp_runtime_suspend, - .runtime_resume = hda_dsp_runtime_resume, - .runtime_idle = hda_dsp_runtime_idle, - .set_hw_params_upon_resume = hda_dsp_set_hw_params_upon_resume, - .set_power_state = hda_dsp_set_power_state, - - /* ALSA HW info flags */ - .hw_info = SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_PAUSE | - SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, - - .dsp_arch_ops = &sof_xtensa_arch_ops, + return 0; }; -EXPORT_SYMBOL_NS(sof_cnl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); +EXPORT_SYMBOL_NS(sof_cnl_ops_init, SND_SOC_SOF_INTEL_HDA_COMMON); const struct sof_intel_dsp_desc cnl_chip_info = { /* Cannonlake */ @@ -349,15 +429,28 @@ const struct sof_intel_dsp_desc cnl_chip_info = { .ipc_ack = CNL_DSP_REG_HIPCIDA, .ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE, .ipc_ctl = CNL_DSP_REG_HIPCCTL, + .rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS, .rom_init_timeout = 300, .ssp_count = CNL_SSP_COUNT, .ssp_base_offset = CNL_SSP_BASE_OFFSET, .sdw_shim_base = SDW_SHIM_BASE, .sdw_alh_base = SDW_ALH_BASE, .check_sdw_irq = hda_common_check_sdw_irq, + .check_ipc_irq = hda_dsp_check_ipc_irq, + .cl_init = cl_dsp_init, + .power_down_dsp = hda_power_down_dsp, + .disable_interrupts = hda_dsp_disable_interrupts, + .hw_ip_version = SOF_INTEL_CAVS_1_8, }; EXPORT_SYMBOL_NS(cnl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON); +/* + * JasperLake is technically derived from IceLake, and should be in + * described in icl.c. However since JasperLake was designed with + * two cores, it cannot support the IceLake-specific power-up sequences + * which rely on core3. To simplify, JasperLake uses the CannonLake ops and + * is described in cnl.c + */ const struct sof_intel_dsp_desc jsl_chip_info = { /* Jasperlake */ .cores_num = 2, @@ -368,11 +461,17 @@ const struct sof_intel_dsp_desc jsl_chip_info = { .ipc_ack = CNL_DSP_REG_HIPCIDA, .ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE, .ipc_ctl = CNL_DSP_REG_HIPCCTL, + .rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS, .rom_init_timeout = 300, .ssp_count = ICL_SSP_COUNT, .ssp_base_offset = CNL_SSP_BASE_OFFSET, .sdw_shim_base = SDW_SHIM_BASE, .sdw_alh_base = SDW_ALH_BASE, .check_sdw_irq = hda_common_check_sdw_irq, + .check_ipc_irq = hda_dsp_check_ipc_irq, + .cl_init = cl_dsp_init, + .power_down_dsp = hda_power_down_dsp, + .disable_interrupts = hda_dsp_disable_interrupts, + .hw_ip_version = SOF_INTEL_CAVS_2_0, }; EXPORT_SYMBOL_NS(jsl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON); diff --git a/sound/soc/sof/intel/hda-codec.c b/sound/soc/sof/intel/hda-codec.c index 13cd96e6724a..f2ec2a6c2e0f 100644 --- a/sound/soc/sof/intel/hda-codec.c +++ b/sound/soc/sof/intel/hda-codec.c @@ -20,9 +20,10 @@ #include "../../codecs/hdac_hda.h" #endif /* CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC */ +#define CODEC_PROBE_RETRIES 3 + #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) #define IDISP_VID_INTEL 0x80860000 -#define CODEC_PROBE_RETRIES 3 /* load the legacy HDA codec driver */ static int request_codec_module(struct hda_codec *codec) @@ -108,17 +109,39 @@ EXPORT_SYMBOL_NS(hda_codec_jack_check, SND_SOC_SOF_HDA_AUDIO_CODEC); #define is_generic_config(x) 0 #endif +static struct hda_codec *hda_codec_device_init(struct hdac_bus *bus, int addr, int type) +{ + struct hda_codec *codec; + int ret; + + codec = snd_hda_codec_device_init(to_hda_bus(bus), addr, "ehdaudio%dD%d", bus->idx, addr); + if (IS_ERR(codec)) { + dev_err(bus->dev, "device init failed for hdac device\n"); + return codec; + } + + codec->core.type = type; + + ret = snd_hdac_device_register(&codec->core); + if (ret) { + dev_err(bus->dev, "failed to register hdac device\n"); + put_device(&codec->core.dev); + return ERR_PTR(ret); + } + + return codec; +} + /* probe individual codec */ 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; + struct hda_codec *codec; u32 hda_cmd = (address << 28) | (AC_NODE_ROOT << 20) | (AC_VERB_PARAMETERS << 8) | AC_PAR_VENDOR_ID; u32 resp = -1; @@ -141,20 +164,20 @@ static int hda_codec_probe(struct snd_sof_dev *sdev, int address, if (!hda_priv) return -ENOMEM; - hda_priv->codec.bus = hbus; - hdev = &hda_priv->codec.core; - codec = &hda_priv->codec; - /* 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); + codec = hda_codec_device_init(&hbus->core, address, type); + ret = PTR_ERR_OR_ZERO(codec); if (ret < 0) return ret; + hda_priv->codec = codec; + dev_set_drvdata(&codec->core.dev, hda_priv); + if ((resp & 0xFFFF0000) == IDISP_VID_INTEL) { - if (!hdev->bus->audio_component) { + if (!hbus->core.audio_component) { dev_dbg(sdev->dev, "iDisp hw present but no driver\n"); ret = -ENOENT; @@ -180,15 +203,12 @@ static int hda_codec_probe(struct snd_sof_dev *sdev, int address, out: if (ret < 0) { - snd_hdac_device_unregister(hdev); - put_device(&hdev->dev); + snd_hdac_device_unregister(&codec->core); + put_device(&codec->core.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, HDA_DEV_ASOC); + codec = hda_codec_device_init(&hbus->core, address, HDA_DEV_ASOC); + ret = PTR_ERR_OR_ZERO(codec); #endif return ret; diff --git a/sound/soc/sof/intel/hda-common-ops.c b/sound/soc/sof/intel/hda-common-ops.c new file mode 100644 index 000000000000..b2326396c870 --- /dev/null +++ b/sound/soc/sof/intel/hda-common-ops.c @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// + +/* + * common ops for SKL+ HDAudio platforms + */ + +#include "../sof-priv.h" +#include "hda.h" +#include "../sof-audio.h" + +struct snd_sof_dsp_ops sof_hda_common_ops = { + /* probe/remove/shutdown */ + .probe = 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, + + /* Mailbox IO */ + .mailbox_read = sof_mailbox_read, + .mailbox_write = sof_mailbox_write, + + /* ipc */ + .get_mailbox_offset = hda_dsp_ipc_get_mailbox_offset, + .get_window_offset = hda_dsp_ipc_get_window_offset, + + .ipc_msg_data = hda_ipc_msg_data, + .set_stream_data_offset = hda_set_stream_data_offset, + + /* machine driver */ + .machine_select = hda_machine_select, + .machine_register = sof_machine_register, + .machine_unregister = sof_machine_unregister, + .set_mach_params = hda_set_mach_params, + + /* debug */ + .dbg_dump = hda_dsp_dump, + .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem, + + /* stream callbacks */ + .pcm_open = hda_dsp_pcm_open, + .pcm_close = hda_dsp_pcm_close, + .pcm_hw_params = hda_dsp_pcm_hw_params, + .pcm_hw_free = hda_dsp_stream_hw_free, + .pcm_trigger = hda_dsp_pcm_trigger, + .pcm_pointer = hda_dsp_pcm_pointer, + .pcm_ack = hda_dsp_pcm_ack, + + /* firmware loading */ + .load_firmware = snd_sof_load_firmware_raw, + + /* pre/post fw run */ + .pre_fw_run = hda_dsp_pre_fw_run, + + /* firmware run */ + .run = hda_dsp_cl_boot_firmware, + + /* parse platform specific extended manifest */ + .parse_platform_ext_manifest = hda_dsp_ext_man_get_cavs_config_data, + + /* dsp core get/put */ + + /* trace callback */ + .trace_init = hda_dsp_trace_init, + .trace_release = hda_dsp_trace_release, + .trace_trigger = hda_dsp_trace_trigger, + + /* client ops */ + .register_ipc_clients = hda_register_clients, + .unregister_ipc_clients = hda_unregister_clients, + + /* DAI drivers */ + .drv = skl_dai, + .num_drv = SOF_SKL_NUM_DAIS, + + /* 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, + + .dsp_arch_ops = &sof_xtensa_arch_ops, +}; diff --git a/sound/soc/sof/intel/hda-ctrl.c b/sound/soc/sof/intel/hda-ctrl.c index fa5f0a718901..0c29bb196e59 100644 --- a/sound/soc/sof/intel/hda-ctrl.c +++ b/sound/soc/sof/intel/hda-ctrl.c @@ -353,7 +353,7 @@ void hda_dsp_ctrl_stop_chip(struct snd_sof_dev *sdev) snd_hdac_bus_stop_cmd_io(bus); #endif /* disable position buffer */ - if (bus->posbuf.addr) { + if (bus->use_posbuf && bus->posbuf.addr) { snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_ADSP_DPLBASE, 0); snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c index 76579383d290..556e883a32ed 100644 --- a/sound/soc/sof/intel/hda-dai.c +++ b/sound/soc/sof/intel/hda-dai.c @@ -10,19 +10,26 @@ #include <sound/pcm_params.h> #include <sound/hdaudio_ext.h> +#include <sound/intel-nhlt.h> +#include <sound/sof/ipc4/header.h> +#include <uapi/sound/sof/header.h> +#include "../ipc4-priv.h" +#include "../ipc4-topology.h" #include "../sof-priv.h" #include "../sof-audio.h" #include "hda.h" -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) +/* + * The default method is to fetch NHLT from BIOS. With this parameter set + * it is possible to override that with NHLT in the SOF topology manifest. + */ +static bool hda_use_tplg_nhlt; +module_param_named(sof_use_tplg_nhlt, hda_use_tplg_nhlt, bool, 0444); +MODULE_PARM_DESC(sof_use_tplg_nhlt, "SOF topology nhlt override"); -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) -#include "../sof-probes.h" -#endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) struct hda_pipe_params { - u8 host_dma_id; - u8 link_dma_id; u32 ch; u32 s_freq; u32 s_fmt; @@ -30,7 +37,6 @@ struct hda_pipe_params { snd_pcm_format_t format; int link_index; int stream; - unsigned int host_bps; unsigned int link_bps; }; @@ -57,13 +63,15 @@ static bool hda_check_fes(struct snd_soc_pcm_runtime *rtd, } static struct hdac_ext_stream * - hda_link_stream_assign(struct hdac_bus *bus, - struct snd_pcm_substream *substream) +hda_link_stream_assign(struct hdac_bus *bus, + struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct sof_intel_hda_stream *hda_stream; + const struct sof_intel_dsp_desc *chip; + struct snd_sof_dev *sdev; struct hdac_ext_stream *res = NULL; - struct hdac_stream *stream = NULL; + struct hdac_stream *hstream = NULL; int stream_dir = substream->stream; @@ -73,28 +81,39 @@ static struct hdac_ext_stream * } 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); - if (stream->direction != substream->stream) + list_for_each_entry(hstream, &bus->stream_list, list) { + struct hdac_ext_stream *hext_stream = + stream_to_hdac_ext_stream(hstream); + if (hstream->direction != substream->stream) continue; - hda_stream = hstream_to_sof_hda_stream(hstream); + hda_stream = hstream_to_sof_hda_stream(hext_stream); + sdev = hda_stream->sdev; + chip = get_chip_info(sdev->pdata); /* check if link is available */ - if (!hstream->link_locked) { - if (stream->opened) { + if (!hext_stream->link_locked) { + /* + * choose the first available link for platforms that do not have the + * PROCEN_FMT_QUIRK set. + */ + if (!(chip->quirks & SOF_INTEL_PROCEN_FMT_QUIRK)) { + res = hext_stream; + break; + } + + if (hstream->opened) { /* * check if the stream tag matches the stream * tag of one of the connected FEs */ if (hda_check_fes(rtd, stream_dir, - stream->stream_tag)) { - res = hstream; + hstream->stream_tag)) { + res = hext_stream; break; } } else { - res = hstream; + res = hext_stream; /* * This must be a hostless stream. @@ -107,12 +126,8 @@ static struct hdac_ext_stream * } if (res) { - /* - * Decouple host and link DMA. The decoupled flag - * is updated in snd_hdac_ext_stream_decouple(). - */ - if (!res->decoupled) - snd_hdac_ext_stream_decouple_locked(bus, res, true); + /* Make sure that host and link DMA is decoupled. */ + snd_hdac_ext_stream_decouple_locked(bus, res, true); res->link_locked = 1; res->link_substream = substream; @@ -122,17 +137,50 @@ static struct hdac_ext_stream * return res; } -static int hda_link_dma_params(struct hdac_ext_stream *stream, +static int hda_link_dma_cleanup(struct snd_pcm_substream *substream, + struct hdac_stream *hstream, + struct snd_soc_dai *cpu_dai, + struct snd_soc_dai *codec_dai, + bool trigger_suspend_stop) +{ + struct hdac_ext_stream *hext_stream = snd_soc_dai_get_dma_data(cpu_dai, substream); + struct hdac_bus *bus = hstream->bus; + struct sof_intel_hda_stream *hda_stream; + struct hdac_ext_link *link; + int stream_tag; + + link = snd_hdac_ext_bus_get_link(bus, codec_dai->component->name); + if (!link) + return -EINVAL; + + if (trigger_suspend_stop) + snd_hdac_ext_link_stream_clear(hext_stream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + stream_tag = hdac_stream(hext_stream)->stream_tag; + snd_hdac_ext_link_clear_stream_id(link, stream_tag); + } + snd_soc_dai_set_dma_data(cpu_dai, substream, NULL); + snd_hdac_ext_stream_release(hext_stream, HDAC_EXT_STREAM_TYPE_LINK); + hext_stream->link_prepared = 0; + + /* free the host DMA channel reserved by hostless streams */ + hda_stream = hstream_to_sof_hda_stream(hext_stream); + hda_stream->host_reserved = 0; + + return 0; +} + +static int hda_link_dma_params(struct hdac_ext_stream *hext_stream, struct hda_pipe_params *params) { - struct hdac_stream *hstream = &stream->hstream; + struct hdac_stream *hstream = &hext_stream->hstream; unsigned char stream_tag = hstream->stream_tag; struct hdac_bus *bus = hstream->bus; struct hdac_ext_link *link; unsigned int format_val; - snd_hdac_ext_stream_decouple(bus, stream, true); - snd_hdac_ext_link_stream_reset(stream); + snd_hdac_ext_link_stream_reset(hext_stream); format_val = snd_hdac_calc_stream_format(params->s_freq, params->ch, params->format, @@ -141,9 +189,9 @@ static int hda_link_dma_params(struct hdac_ext_stream *stream, dev_dbg(bus->dev, "format_val=%d, rate=%d, ch=%d, format=%d\n", format_val, params->s_freq, params->ch, params->format); - snd_hdac_ext_link_stream_setup(stream, format_val); + snd_hdac_ext_link_stream_setup(hext_stream, format_val); - if (stream->hstream.direction == SNDRV_PCM_STREAM_PLAYBACK) { + if (hext_stream->hstream.direction == SNDRV_PCM_STREAM_PLAYBACK) { list_for_each_entry(link, &bus->hlink_list, list) { if (link->index == params->link_index) snd_hdac_ext_link_set_stream_id(link, @@ -151,292 +199,402 @@ static int hda_link_dma_params(struct hdac_ext_stream *stream, } } - stream->link_prepared = 1; + hext_stream->link_prepared = 1; return 0; } -/* Update config for the DAI widget */ -static struct sof_ipc_dai_config *hda_dai_update_config(struct snd_soc_dapm_widget *w, - int channel) +static int hda_link_dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) { - struct snd_sof_widget *swidget = w->dobj.private; - struct sof_ipc_dai_config *config; - struct snd_sof_dai *sof_dai; - - if (!swidget) - return NULL; + struct hdac_stream *hstream = substream->runtime->private_data; + struct hdac_ext_stream *hext_stream; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct hda_pipe_params p_params = {0}; + struct hdac_bus *bus = hstream->bus; + struct hdac_ext_link *link; - sof_dai = swidget->private; + hext_stream = snd_soc_dai_get_dma_data(cpu_dai, substream); + if (!hext_stream) { + hext_stream = hda_link_stream_assign(bus, substream); + if (!hext_stream) + return -EBUSY; - if (!sof_dai || !sof_dai->dai_config) { - dev_err(swidget->scomp->dev, "error: No config for DAI %s\n", w->name); - return NULL; + snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)hext_stream); } - config = &sof_dai->dai_config[sof_dai->current_config]; + link = snd_hdac_ext_bus_get_link(bus, codec_dai->component->name); + if (!link) + return -EINVAL; + + /* set the hdac_stream in the codec dai */ + snd_soc_dai_set_stream(codec_dai, hdac_stream(hext_stream), substream->stream); + + p_params.s_fmt = snd_pcm_format_width(params_format(params)); + p_params.ch = params_channels(params); + p_params.s_freq = params_rate(params); + p_params.stream = substream->stream; + p_params.link_index = link->index; + p_params.format = params_format(params); - /* update config with stream tag */ - config->hda.link_dma_ch = channel; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + p_params.link_bps = codec_dai->driver->playback.sig_bits; + else + p_params.link_bps = codec_dai->driver->capture.sig_bits; + + return hda_link_dma_params(hext_stream, &p_params); +} + +static int hda_link_dma_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + int stream = substream->stream; - return config; + return hda_link_dma_hw_params(substream, &rtd->dpcm[stream].hw_params); } -static int hda_link_config_ipc(struct sof_intel_hda_stream *hda_stream, - struct snd_soc_dapm_widget *w, int channel) +static int hda_link_dma_trigger(struct snd_pcm_substream *substream, int cmd) { - struct snd_sof_dev *sdev = hda_stream->sdev; - struct sof_ipc_dai_config *config; - struct sof_ipc_reply reply; + struct hdac_stream *hstream = substream->runtime->private_data; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct hdac_ext_stream *hext_stream = snd_soc_dai_get_dma_data(cpu_dai, substream); + int ret; + + if (!hext_stream) + return 0; - config = hda_dai_update_config(w, channel); - if (!config) { - dev_err(sdev->dev, "error: no config for DAI %s\n", w->name); - return -ENOENT; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + snd_hdac_ext_link_stream_start(hext_stream); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + ret = hda_link_dma_cleanup(substream, hstream, cpu_dai, codec_dai, true); + if (ret < 0) + return ret; + + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + snd_hdac_ext_link_stream_clear(hext_stream); + + break; + default: + return -EINVAL; } + return 0; +} - /* send DAI_CONFIG IPC */ - return sof_ipc_tx_message(sdev->ipc, config->hdr.cmd, config, config->hdr.size, - &reply, sizeof(reply)); +static int hda_link_dma_hw_free(struct snd_pcm_substream *substream) +{ + struct hdac_stream *hstream = substream->runtime->private_data; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct hdac_ext_stream *hext_stream; + + hext_stream = snd_soc_dai_get_dma_data(cpu_dai, substream); + if (!hext_stream) + return 0; + + return hda_link_dma_cleanup(substream, hstream, cpu_dai, codec_dai, false); } -static int hda_link_dai_widget_update(struct sof_intel_hda_stream *hda_stream, - struct snd_soc_dapm_widget *w, - int channel, bool widget_setup) +static int hda_dai_widget_update(struct snd_soc_dapm_widget *w, + int channel, bool widget_setup) { - struct snd_sof_dev *sdev = hda_stream->sdev; - struct sof_ipc_dai_config *config; + struct snd_sof_dai_config_data data; - config = hda_dai_update_config(w, channel); - if (!config) { - dev_err(sdev->dev, "error: no config for DAI %s\n", w->name); - return -ENOENT; - } + data.dai_data = channel; /* set up/free DAI widget and send DAI_CONFIG IPC */ if (widget_setup) - return hda_ctrl_dai_widget_setup(w); + return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_2_STEP_STOP, &data); - return hda_ctrl_dai_widget_free(w); + return hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE, &data); } -static int hda_link_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params, - struct snd_soc_dai *dai) +static int hda_dai_hw_params_update(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) { - struct hdac_stream *hstream = substream->runtime->private_data; - struct hdac_bus *bus = hstream->bus; - struct hdac_ext_stream *link_dev; - struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); - struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); - struct sof_intel_hda_stream *hda_stream; - struct hda_pipe_params p_params = {0}; + struct hdac_ext_stream *hext_stream; struct snd_soc_dapm_widget *w; - struct hdac_ext_link *link; int stream_tag; - int ret; - /* get stored dma data if resuming from system suspend */ - link_dev = snd_soc_dai_get_dma_data(dai, substream); - if (!link_dev) { - link_dev = hda_link_stream_assign(bus, substream); - if (!link_dev) - return -EBUSY; + hext_stream = snd_soc_dai_get_dma_data(dai, substream); + if (!hext_stream) + return -EINVAL; - snd_soc_dai_set_dma_data(dai, substream, (void *)link_dev); - } + stream_tag = hdac_stream(hext_stream)->stream_tag; - stream_tag = hdac_stream(link_dev)->stream_tag; + w = snd_soc_dai_get_widget(dai, substream->stream); - hda_stream = hstream_to_sof_hda_stream(link_dev); + /* set up the DAI widget and send the DAI_CONFIG with the new tag */ + return hda_dai_widget_update(w, stream_tag - 1, true); +} - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - w = dai->playback_widget; - else - w = dai->capture_widget; +static int hda_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct hdac_ext_stream *hext_stream = + snd_soc_dai_get_dma_data(dai, substream); + int ret; - /* set up the DAI widget and send the DAI_CONFIG with the new tag */ - ret = hda_link_dai_widget_update(hda_stream, w, stream_tag - 1, true); + if (hext_stream && hext_stream->link_prepared) + return 0; + + ret = hda_link_dma_hw_params(substream, params); if (ret < 0) return ret; - link = snd_hdac_ext_bus_get_link(bus, codec_dai->component->name); - if (!link) - return -EINVAL; + return hda_dai_hw_params_update(substream, params, dai); +} - /* set the stream tag in the codec dai dma params */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - snd_soc_dai_set_tdm_slot(codec_dai, stream_tag, 0, 0, 0); - else - snd_soc_dai_set_tdm_slot(codec_dai, 0, stream_tag, 0, 0); - p_params.s_fmt = snd_pcm_format_width(params_format(params)); - p_params.ch = params_channels(params); - p_params.s_freq = params_rate(params); - p_params.stream = substream->stream; - p_params.link_dma_id = stream_tag - 1; - p_params.link_index = link->index; - p_params.format = params_format(params); +static int hda_dai_config_pause_push_ipc(struct snd_soc_dapm_widget *w) +{ + struct snd_sof_widget *swidget = w->dobj.private; + struct snd_soc_component *component = swidget->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; + int ret = 0; - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - p_params.link_bps = codec_dai->driver->playback.sig_bits; - else - p_params.link_bps = codec_dai->driver->capture.sig_bits; + if (tplg_ops->dai_config) { + ret = tplg_ops->dai_config(sdev, swidget, SOF_DAI_CONFIG_FLAGS_PAUSE, NULL); + if (ret < 0) + dev_err(sdev->dev, "%s: DAI config failed for widget %s\n", __func__, + w->name); + } - return hda_link_dma_params(link_dev, &p_params); + return ret; } -static int hda_link_pcm_prepare(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) +static int hda_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct hdac_ext_stream *link_dev = + struct hdac_ext_stream *hext_stream = snd_soc_dai_get_dma_data(dai, substream); - struct snd_sof_dev *sdev = - snd_soc_component_get_drvdata(dai->component); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); int stream = substream->stream; + int ret; - if (link_dev->link_prepared) + if (hext_stream && hext_stream->link_prepared) return 0; - dev_dbg(sdev->dev, "hda: prepare stream dir %d\n", substream->stream); + dev_dbg(sdev->dev, "prepare stream dir %d\n", substream->stream); + + ret = hda_link_dma_prepare(substream); + if (ret < 0) + return ret; + + return hda_dai_hw_params_update(substream, &rtd->dpcm[stream].hw_params, dai); +} + +static int hda_dai_hw_free_ipc(int stream, /* direction */ + struct snd_soc_dai *dai) +{ + struct snd_soc_dapm_widget *w; + + w = snd_soc_dai_get_widget(dai, stream); - return hda_link_hw_params(substream, &rtd->dpcm[stream].hw_params, - dai); + /* free the link DMA channel in the FW and the DAI widget */ + return hda_dai_widget_update(w, DMA_CHAN_INVALID, false); } -static int hda_link_pcm_trigger(struct snd_pcm_substream *substream, +static int ipc3_hda_dai_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { - struct hdac_ext_stream *link_dev = - snd_soc_dai_get_dma_data(dai, substream); - struct sof_intel_hda_stream *hda_stream; - struct snd_soc_pcm_runtime *rtd; struct snd_soc_dapm_widget *w; - struct hdac_ext_link *link; - struct hdac_stream *hstream; - struct hdac_bus *bus; - int stream_tag; int ret; - hstream = substream->runtime->private_data; - bus = hstream->bus; - rtd = asoc_substream_to_rtd(substream); + dev_dbg(dai->dev, "cmd=%d dai %s direction %d\n", cmd, + dai->name, substream->stream); - link = snd_hdac_ext_bus_get_link(bus, asoc_rtd_to_codec(rtd, 0)->component->name); - if (!link) - return -EINVAL; + ret = hda_link_dma_trigger(substream, cmd); + if (ret < 0) + return ret; - hda_stream = hstream_to_sof_hda_stream(link_dev); + w = snd_soc_dai_get_widget(dai, substream->stream); - dev_dbg(dai->dev, "In %s cmd=%d\n", __func__, cmd); switch (cmd) { - case SNDRV_PCM_TRIGGER_RESUME: - /* set up hw_params */ - ret = hda_link_pcm_prepare(substream, dai); - if (ret < 0) { - dev_err(dai->dev, - "error: setting up hw_params during resume\n"); - return ret; - } - - fallthrough; - case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - snd_hdac_ext_link_stream_start(link_dev); - break; case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_STOP: - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - w = dai->playback_widget; - else - w = dai->capture_widget; - /* - * clear link DMA channel. It will be assigned when - * hw_params is set up again after resume. + * free DAI widget during stop/suspend to keep widget use_count's balanced. */ - ret = hda_link_config_ipc(hda_stream, w, DMA_CHAN_INVALID); + ret = hda_dai_hw_free_ipc(substream->stream, dai); if (ret < 0) return ret; - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - stream_tag = hdac_stream(link_dev)->stream_tag; - snd_hdac_ext_link_clear_stream_id(link, stream_tag); - } - - link_dev->link_prepared = 0; - - fallthrough; + break; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - snd_hdac_ext_link_stream_clear(link_dev); + ret = hda_dai_config_pause_push_ipc(w); + if (ret < 0) + return ret; break; + default: - return -EINVAL; + break; } return 0; } -static int hda_link_hw_free(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) +/* + * In contrast to IPC3, the dai trigger in IPC4 mixes pipeline state changes + * (over IPC channel) and DMA state change (direct host register changes). + */ +static int ipc4_hda_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) { - unsigned int stream_tag; - struct sof_intel_hda_stream *hda_stream; - struct hdac_bus *bus; - struct hdac_ext_link *link; - struct hdac_stream *hstream; + struct hdac_ext_stream *hext_stream = snd_soc_dai_get_dma_data(dai, substream); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); struct snd_soc_pcm_runtime *rtd; - struct hdac_ext_stream *link_dev; + struct snd_sof_widget *swidget; struct snd_soc_dapm_widget *w; + struct snd_soc_dai *codec_dai; + struct hdac_stream *hstream; + struct snd_soc_dai *cpu_dai; int ret; + dev_dbg(dai->dev, "cmd=%d dai %s direction %d\n", cmd, + dai->name, substream->stream); + hstream = substream->runtime->private_data; - bus = hstream->bus; rtd = asoc_substream_to_rtd(substream); - link_dev = snd_soc_dai_get_dma_data(dai, substream); + cpu_dai = asoc_rtd_to_cpu(rtd, 0); + codec_dai = asoc_rtd_to_codec(rtd, 0); - if (!link_dev) { - dev_dbg(dai->dev, - "%s: link_dev is not assigned\n", __func__); + w = snd_soc_dai_get_widget(dai, substream->stream); + swidget = w->dobj.private; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + snd_hdac_ext_link_stream_start(hext_stream); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + { + struct snd_sof_widget *pipe_widget = swidget->pipe_widget; + struct sof_ipc4_pipeline *pipeline = pipe_widget->private; + + ret = sof_ipc4_set_pipeline_state(sdev, swidget->pipeline_id, + SOF_IPC4_PIPE_PAUSED); + if (ret < 0) + return ret; + + pipeline->state = SOF_IPC4_PIPE_PAUSED; + + snd_hdac_ext_link_stream_clear(hext_stream); + + ret = sof_ipc4_set_pipeline_state(sdev, swidget->pipeline_id, + SOF_IPC4_PIPE_RESET); + if (ret < 0) + return ret; + + pipeline->state = SOF_IPC4_PIPE_RESET; + + ret = hda_link_dma_cleanup(substream, hstream, cpu_dai, codec_dai, false); + if (ret < 0) { + dev_err(sdev->dev, "%s: failed to clean up link DMA\n", __func__); + return ret; + } + break; + } + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + { + struct snd_sof_widget *pipe_widget = swidget->pipe_widget; + struct sof_ipc4_pipeline *pipeline = pipe_widget->private; + + ret = sof_ipc4_set_pipeline_state(sdev, swidget->pipeline_id, + SOF_IPC4_PIPE_PAUSED); + if (ret < 0) + return ret; + + pipeline->state = SOF_IPC4_PIPE_PAUSED; + + snd_hdac_ext_link_stream_clear(hext_stream); + break; + } + default: + dev_err(sdev->dev, "%s: unknown trigger command %d\n", __func__, cmd); return -EINVAL; } - hda_stream = hstream_to_sof_hda_stream(link_dev); + return 0; +} - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - w = dai->playback_widget; - else - w = dai->capture_widget; +static int hda_dai_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int ret; - /* free the link DMA channel in the FW and the DAI widget */ - ret = hda_link_dai_widget_update(hda_stream, w, DMA_CHAN_INVALID, false); + ret = hda_link_dma_hw_free(substream); if (ret < 0) return ret; - link = snd_hdac_ext_bus_get_link(bus, asoc_rtd_to_codec(rtd, 0)->component->name); - if (!link) - return -EINVAL; + return hda_dai_hw_free_ipc(substream->stream, dai); +} - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - stream_tag = hdac_stream(link_dev)->stream_tag; - snd_hdac_ext_link_clear_stream_id(link, stream_tag); - } +static const struct snd_soc_dai_ops ipc3_hda_dai_ops = { + .hw_params = hda_dai_hw_params, + .hw_free = hda_dai_hw_free, + .trigger = ipc3_hda_dai_trigger, + .prepare = hda_dai_prepare, +}; - snd_soc_dai_set_dma_data(dai, substream, NULL); - snd_hdac_ext_stream_release(link_dev, HDAC_EXT_STREAM_TYPE_LINK); - link_dev->link_prepared = 0; +static int hda_dai_suspend(struct hdac_bus *bus) +{ + struct snd_soc_pcm_runtime *rtd; + struct hdac_ext_stream *hext_stream; + struct hdac_stream *s; + int ret; - /* free the host DMA channel reserved by hostless streams */ - hda_stream->host_reserved = 0; + /* set internal flag for BE */ + list_for_each_entry(s, &bus->stream_list, list) { + + hext_stream = stream_to_hdac_ext_stream(s); + + /* + * clear stream. This should already be taken care for running + * streams when the SUSPEND trigger is called. But paused + * streams do not get suspended, so this needs to be done + * explicitly during suspend. + */ + if (hext_stream->link_substream) { + struct snd_soc_dai *cpu_dai; + struct snd_soc_dai *codec_dai; + + rtd = asoc_substream_to_rtd(hext_stream->link_substream); + cpu_dai = asoc_rtd_to_cpu(rtd, 0); + codec_dai = asoc_rtd_to_codec(rtd, 0); + + ret = hda_link_dma_cleanup(hext_stream->link_substream, s, + cpu_dai, codec_dai, false); + if (ret < 0) + return ret; + + /* for consistency with TRIGGER_SUSPEND we free DAI resources */ + ret = hda_dai_hw_free_ipc(hdac_stream(hext_stream)->direction, cpu_dai); + if (ret < 0) + return ret; + } + } return 0; } -static const struct snd_soc_dai_ops hda_link_dai_ops = { - .hw_params = hda_link_hw_params, - .hw_free = hda_link_hw_free, - .trigger = hda_link_pcm_trigger, - .prepare = hda_link_pcm_prepare, +static const struct snd_soc_dai_ops ipc4_hda_dai_ops = { + .hw_params = hda_dai_hw_params, + .hw_free = hda_dai_hw_free, + .trigger = ipc4_hda_dai_trigger, + .prepare = hda_dai_prepare, }; #endif @@ -449,30 +607,14 @@ struct ssp_dai_dma_data { static int ssp_dai_setup_or_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai, bool setup) { - struct snd_soc_component *component; - struct snd_sof_widget *swidget; struct snd_soc_dapm_widget *w; - struct sof_ipc_fw_version *v; - struct snd_sof_dev *sdev; - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - w = dai->playback_widget; - else - w = dai->capture_widget; - - swidget = w->dobj.private; - component = swidget->scomp; - sdev = snd_soc_component_get_drvdata(component); - v = &sdev->fw_ready.version; - - /* DAI_CONFIG IPC during hw_params is not supported in older firmware */ - if (v->abi_version < SOF_ABI_VER(3, 18, 0)) - return 0; + w = snd_soc_dai_get_widget(dai, substream->stream); if (setup) - return hda_ctrl_dai_widget_setup(w); + return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_NONE, NULL); - return hda_ctrl_dai_widget_free(w); + return hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE, NULL); } static int ssp_dai_startup(struct snd_pcm_substream *substream, @@ -528,8 +670,8 @@ static int ssp_dai_prepare(struct snd_pcm_substream *substream, return ssp_dai_setup(substream, dai, true); } -static int ssp_dai_trigger(struct snd_pcm_substream *substream, - int cmd, struct snd_soc_dai *dai) +static int ipc3_ssp_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) { if (cmd != SNDRV_PCM_TRIGGER_SUSPEND) return 0; @@ -557,15 +699,137 @@ static void ssp_dai_shutdown(struct snd_pcm_substream *substream, kfree(dma_data); } -static const struct snd_soc_dai_ops ssp_dai_ops = { +static const struct snd_soc_dai_ops ipc3_ssp_dai_ops = { .startup = ssp_dai_startup, .hw_params = ssp_dai_hw_params, .prepare = ssp_dai_prepare, - .trigger = ssp_dai_trigger, + .trigger = ipc3_ssp_dai_trigger, .hw_free = ssp_dai_hw_free, .shutdown = ssp_dai_shutdown, }; +static int ipc4_be_dai_common_trigger(struct snd_soc_dai *dai, int cmd, int stream) +{ + struct snd_sof_widget *pipe_widget; + struct sof_ipc4_pipeline *pipeline; + struct snd_sof_widget *swidget; + struct snd_soc_dapm_widget *w; + struct snd_sof_dev *sdev; + int ret; + + w = snd_soc_dai_get_widget(dai, stream); + swidget = w->dobj.private; + pipe_widget = swidget->pipe_widget; + pipeline = pipe_widget->private; + sdev = snd_soc_component_get_drvdata(swidget->scomp); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + ret = sof_ipc4_set_pipeline_state(sdev, swidget->pipeline_id, + SOF_IPC4_PIPE_PAUSED); + if (ret < 0) + return ret; + pipeline->state = SOF_IPC4_PIPE_PAUSED; + + ret = sof_ipc4_set_pipeline_state(sdev, swidget->pipeline_id, + SOF_IPC4_PIPE_RESET); + if (ret < 0) + return ret; + pipeline->state = SOF_IPC4_PIPE_RESET; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = sof_ipc4_set_pipeline_state(sdev, swidget->pipeline_id, + SOF_IPC4_PIPE_PAUSED); + if (ret < 0) + return ret; + pipeline->state = SOF_IPC4_PIPE_PAUSED; + break; + default: + break; + } + + return 0; +} + +static int ipc4_be_dai_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + return ipc4_be_dai_common_trigger(dai, cmd, substream->stream); +} + +static const struct snd_soc_dai_ops ipc4_dmic_dai_ops = { + .trigger = ipc4_be_dai_trigger, +}; + +static const struct snd_soc_dai_ops ipc4_ssp_dai_ops = { + .trigger = ipc4_be_dai_trigger, +}; + +void hda_set_dai_drv_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *ops) +{ + int i; + + switch (sdev->pdata->ipc_type) { + case SOF_IPC: + for (i = 0; i < ops->num_drv; i++) { + if (strstr(ops->drv[i].name, "SSP")) { + ops->drv[i].ops = &ipc3_ssp_dai_ops; + continue; + } +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + if (strstr(ops->drv[i].name, "iDisp") || + strstr(ops->drv[i].name, "Analog") || + strstr(ops->drv[i].name, "Digital")) + ops->drv[i].ops = &ipc3_hda_dai_ops; +#endif + } + break; + case SOF_INTEL_IPC4: + { + struct sof_ipc4_fw_data *ipc4_data = sdev->private; + + for (i = 0; i < ops->num_drv; i++) { + if (strstr(ops->drv[i].name, "DMIC")) { + ops->drv[i].ops = &ipc4_dmic_dai_ops; + continue; + } + if (strstr(ops->drv[i].name, "SSP")) { + ops->drv[i].ops = &ipc4_ssp_dai_ops; + continue; + } +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + if (strstr(ops->drv[i].name, "iDisp") || + strstr(ops->drv[i].name, "Analog") || + strstr(ops->drv[i].name, "Digital")) + ops->drv[i].ops = &ipc4_hda_dai_ops; +#endif + } + + if (!hda_use_tplg_nhlt) + ipc4_data->nhlt = intel_nhlt_init(sdev->dev); + + if (IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE)) + sdw_callback.trigger = ipc4_be_dai_common_trigger; + + break; + } + default: + break; + } +} + +void hda_ops_free(struct snd_sof_dev *sdev) +{ + if (sdev->pdata->ipc_type == SOF_INTEL_IPC4) { + struct sof_ipc4_fw_data *ipc4_data = sdev->private; + + if (!hda_use_tplg_nhlt) + intel_nhlt_free(ipc4_data->nhlt); + } +} +EXPORT_SYMBOL_NS(hda_ops_free, SND_SOC_SOF_INTEL_HDA_COMMON); + /* * common dai driver for skl+ platforms. * some products who use this DAI array only physically have a subset of @@ -574,7 +838,6 @@ static const struct snd_soc_dai_ops ssp_dai_ops = { struct snd_soc_dai_driver skl_dai[] = { { .name = "SSP0 Pin", - .ops = &ssp_dai_ops, .playback = { .channels_min = 1, .channels_max = 8, @@ -586,7 +849,6 @@ struct snd_soc_dai_driver skl_dai[] = { }, { .name = "SSP1 Pin", - .ops = &ssp_dai_ops, .playback = { .channels_min = 1, .channels_max = 8, @@ -598,7 +860,6 @@ struct snd_soc_dai_driver skl_dai[] = { }, { .name = "SSP2 Pin", - .ops = &ssp_dai_ops, .playback = { .channels_min = 1, .channels_max = 8, @@ -610,7 +871,6 @@ struct snd_soc_dai_driver skl_dai[] = { }, { .name = "SSP3 Pin", - .ops = &ssp_dai_ops, .playback = { .channels_min = 1, .channels_max = 8, @@ -622,7 +882,6 @@ struct snd_soc_dai_driver skl_dai[] = { }, { .name = "SSP4 Pin", - .ops = &ssp_dai_ops, .playback = { .channels_min = 1, .channels_max = 8, @@ -634,7 +893,6 @@ struct snd_soc_dai_driver skl_dai[] = { }, { .name = "SSP5 Pin", - .ops = &ssp_dai_ops, .playback = { .channels_min = 1, .channels_max = 8, @@ -661,7 +919,6 @@ struct snd_soc_dai_driver skl_dai[] = { #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) { .name = "iDisp1 Pin", - .ops = &hda_link_dai_ops, .playback = { .channels_min = 1, .channels_max = 8, @@ -669,7 +926,6 @@ struct snd_soc_dai_driver skl_dai[] = { }, { .name = "iDisp2 Pin", - .ops = &hda_link_dai_ops, .playback = { .channels_min = 1, .channels_max = 8, @@ -677,7 +933,6 @@ struct snd_soc_dai_driver skl_dai[] = { }, { .name = "iDisp3 Pin", - .ops = &hda_link_dai_ops, .playback = { .channels_min = 1, .channels_max = 8, @@ -685,7 +940,6 @@ struct snd_soc_dai_driver skl_dai[] = { }, { .name = "iDisp4 Pin", - .ops = &hda_link_dai_ops, .playback = { .channels_min = 1, .channels_max = 8, @@ -693,7 +947,6 @@ struct snd_soc_dai_driver skl_dai[] = { }, { .name = "Analog CPU DAI", - .ops = &hda_link_dai_ops, .playback = { .channels_min = 1, .channels_max = 16, @@ -705,7 +958,6 @@ struct snd_soc_dai_driver skl_dai[] = { }, { .name = "Digital CPU DAI", - .ops = &hda_link_dai_ops, .playback = { .channels_min = 1, .channels_max = 16, @@ -717,7 +969,6 @@ struct snd_soc_dai_driver skl_dai[] = { }, { .name = "Alt Analog CPU DAI", - .ops = &hda_link_dai_ops, .playback = { .channels_min = 1, .channels_max = 16, @@ -727,20 +978,24 @@ struct snd_soc_dai_driver skl_dai[] = { .channels_max = 16, }, }, -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) -{ - .name = "Probe Extraction CPU DAI", - .compress_new = snd_soc_new_compress, - .cops = &sof_probe_compr_ops, - .capture = { - .stream_name = "Probe Extraction", - .channels_min = 1, - .channels_max = 8, - .rates = SNDRV_PCM_RATE_48000, - .rate_min = 48000, - .rate_max = 48000, - }, -}, -#endif #endif }; + +int hda_dsp_dais_suspend(struct snd_sof_dev *sdev) +{ + /* + * In the corner case where a SUSPEND happens during a PAUSE, the ALSA core + * does not throw the TRIGGER_SUSPEND. This leaves the DAIs in an unbalanced state. + * Since the component suspend is called last, we can trap this corner case + * and force the DAIs to release their resources. + */ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) + int ret; + + ret = hda_dai_suspend(sof_to_bus(sdev)); + if (ret < 0) + return ret; +#endif + + return 0; +} diff --git a/sound/soc/sof/intel/hda-dsp.c b/sound/soc/sof/intel/hda-dsp.c index 287dc0eb6686..3c76f843454b 100644 --- a/sound/soc/sof/intel/hda-dsp.c +++ b/sound/soc/sof/intel/hda-dsp.c @@ -18,6 +18,7 @@ #include <linux/module.h> #include <sound/hdaudio_ext.h> #include <sound/hda_register.h> +#include <trace/events/sof_intel.h> #include "../sof-audio.h" #include "../ops.h" #include "hda.h" @@ -113,7 +114,7 @@ static int hda_dsp_core_reset_leave(struct snd_sof_dev *sdev, unsigned int core_ return ret; } -static int hda_dsp_core_stall_reset(struct snd_sof_dev *sdev, unsigned int core_mask) +int hda_dsp_core_stall_reset(struct snd_sof_dev *sdev, unsigned int core_mask) { /* stall core */ snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR, @@ -125,7 +126,7 @@ static int hda_dsp_core_stall_reset(struct snd_sof_dev *sdev, unsigned int core_ return hda_dsp_core_reset_enter(sdev, core_mask); } -static bool hda_dsp_core_is_enabled(struct snd_sof_dev *sdev, unsigned int core_mask) +bool hda_dsp_core_is_enabled(struct snd_sof_dev *sdev, unsigned int core_mask) { int val; bool is_enable; @@ -181,12 +182,20 @@ int hda_dsp_core_run(struct snd_sof_dev *sdev, unsigned int core_mask) * Power Management. */ -static int hda_dsp_core_power_up(struct snd_sof_dev *sdev, unsigned int core_mask) +int hda_dsp_core_power_up(struct snd_sof_dev *sdev, unsigned int core_mask) { + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + const struct sof_intel_dsp_desc *chip = hda->desc; unsigned int cpa; u32 adspcs; int ret; + /* restrict core_mask to host managed cores mask */ + core_mask &= chip->host_managed_cores_mask; + /* return if core_mask is not valid */ + if (!core_mask) + return 0; + /* update bits */ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPCS, HDA_DSP_ADSPCS_SPA_MASK(core_mask), @@ -363,9 +372,8 @@ static int hda_dsp_send_pm_gate_ipc(struct snd_sof_dev *sdev, u32 flags) 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)); + return sof_ipc_tx_message_no_pm(sdev->ipc, &pm_gate, sizeof(pm_gate), + &reply, sizeof(reply)); } static int hda_dsp_update_d0i3c_register(struct snd_sof_dev *sdev, u8 value) @@ -390,8 +398,7 @@ static int hda_dsp_update_d0i3c_register(struct snd_sof_dev *sdev, u8 value) return ret; } - dev_vdbg(bus->dev, "D0I3C updated, register = 0x%x\n", - snd_hdac_chip_readb(bus, VS_D0I3C)); + trace_sof_intel_D0I3C_updated(sdev, snd_hdac_chip_readb(bus, VS_D0I3C)); return 0; } @@ -433,7 +440,7 @@ static int hda_dsp_set_D0_state(struct snd_sof_dev *sdev, * when the DSP enters D0I3 while the system is in S0 * for debug purpose. */ - if (!sdev->dtrace_is_supported || + if (!sdev->fw_trace_is_supported || !hda_enable_trace_D0I3_S0 || sdev->system_suspend_target != SOF_SUSPEND_NONE) flags = HDA_PM_NO_DMA_TRACE; @@ -498,15 +505,9 @@ static void hda_dsp_state_log(struct snd_sof_dev *sdev) case SOF_DSP_PM_D2: dev_dbg(sdev->dev, "Current DSP power state: D2\n"); break; - case SOF_DSP_PM_D3_HOT: - dev_dbg(sdev->dev, "Current DSP power state: D3_HOT\n"); - break; case SOF_DSP_PM_D3: dev_dbg(sdev->dev, "Current DSP power state: D3\n"); break; - case SOF_DSP_PM_D3_COLD: - dev_dbg(sdev->dev, "Current DSP power state: D3_COLD\n"); - break; default: dev_dbg(sdev->dev, "Unknown DSP power state: %d\n", sdev->dsp_power_state.state); @@ -614,12 +615,23 @@ static int hda_suspend(struct snd_sof_dev *sdev, bool runtime_suspend) #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) struct hdac_bus *bus = sof_to_bus(sdev); #endif - int ret; + int ret, j; - hda_sdw_int_enable(sdev, false); + /* + * The memory used for IMR boot loses its content in deeper than S3 state + * We must not try IMR boot on next power up (as it will fail). + * + * In case of firmware crash or boot failure set the skip_imr_boot to true + * as well in order to try to re-load the firmware to do a 'cold' boot. + */ + if (sdev->system_suspend_target > SOF_SUSPEND_S3 || + sdev->fw_state == SOF_FW_CRASHED || + sdev->fw_state == SOF_FW_BOOT_FAILED) + hda->skip_imr_boot = true; - /* disable IPC interrupts */ - hda_dsp_ipc_int_disable(sdev); + ret = chip->disable_interrupts(sdev); + if (ret < 0) + return ret; #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) hda_codec_jack_wake_enable(sdev, runtime_suspend); @@ -628,14 +640,16 @@ static int hda_suspend(struct snd_sof_dev *sdev, bool runtime_suspend) snd_hdac_ext_bus_link_power_down_all(bus); #endif - /* power down DSP */ - ret = snd_sof_dsp_core_power_down(sdev, chip->host_managed_cores_mask); + ret = chip->power_down_dsp(sdev); if (ret < 0) { - dev_err(sdev->dev, - "error: failed to power down core during suspend\n"); + dev_err(sdev->dev, "failed to power down DSP during suspend\n"); return ret; } + /* reset ref counts for all cores */ + for (j = 0; j < chip->cores_num; j++) + sdev->dsp_core_ref_count[j] = 0; + /* disable ppcap interrupt */ hda_dsp_ctrl_ppcap_enable(sdev, false); hda_dsp_ctrl_ppcap_int_enable(sdev, false); @@ -738,7 +752,7 @@ int hda_dsp_resume(struct snd_sof_dev *sdev) if (hlink->ref_count) { ret = snd_hdac_ext_bus_link_power_up(hlink); if (ret < 0) { - dev_dbg(sdev->dev, + dev_err(sdev->dev, "error %d in %s: failed to power up links", ret, __func__); return ret; @@ -866,7 +880,7 @@ int hda_dsp_suspend(struct snd_sof_dev *sdev, u32 target_state) /* 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, + dev_err(sdev->dev, "error %d in %s: failed to power down links", ret, __func__); return ret; @@ -897,44 +911,14 @@ int hda_dsp_shutdown(struct snd_sof_dev *sdev) int hda_dsp_set_hw_params_upon_resume(struct snd_sof_dev *sdev) { -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) - struct hdac_bus *bus = sof_to_bus(sdev); - struct snd_soc_pcm_runtime *rtd; - struct hdac_ext_stream *stream; - struct hdac_ext_link *link; - struct hdac_stream *s; - const char *name; - int stream_tag; - - /* set internal flag for BE */ - list_for_each_entry(s, &bus->stream_list, list) { - stream = stream_to_hdac_ext_stream(s); - - /* - * clear stream. This should already be taken care for running - * streams when the SUSPEND trigger is called. But paused - * streams do not get suspended, so this needs to be done - * explicitly during suspend. - */ - if (stream->link_substream) { - rtd = asoc_substream_to_rtd(stream->link_substream); - name = asoc_rtd_to_codec(rtd, 0)->component->name; - link = snd_hdac_ext_bus_get_link(bus, name); - if (!link) - return -EINVAL; - - stream->link_prepared = 0; + int ret; - if (hdac_stream(stream)->direction == - SNDRV_PCM_STREAM_CAPTURE) - continue; + /* make sure all DAI resources are freed */ + ret = hda_dsp_dais_suspend(sdev); + if (ret < 0) + dev_warn(sdev->dev, "%s: failure in hda_dsp_dais_suspend\n", __func__); - stream_tag = hdac_stream(stream)->stream_tag; - snd_hdac_ext_link_clear_stream_id(link, stream_tag); - } - } -#endif - return 0; + return ret; } void hda_dsp_d0i3_work(struct work_struct *work) @@ -962,3 +946,51 @@ void hda_dsp_d0i3_work(struct work_struct *work) "error: failed to set DSP state %d substate %d\n", target_state.state, target_state.substate); } + +int hda_dsp_core_get(struct snd_sof_dev *sdev, int core) +{ + const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm; + int ret, ret1; + + /* power up core */ + ret = hda_dsp_enable_core(sdev, BIT(core)); + if (ret < 0) { + dev_err(sdev->dev, "failed to power up core %d with err: %d\n", + core, ret); + return ret; + } + + /* No need to send IPC for primary core or if FW boot is not complete */ + if (sdev->fw_state != SOF_FW_BOOT_COMPLETE || core == SOF_DSP_PRIMARY_CORE) + return 0; + + /* No need to continue the set_core_state ops is not available */ + if (!pm_ops->set_core_state) + return 0; + + /* Now notify DSP for secondary cores */ + ret = pm_ops->set_core_state(sdev, core, true); + if (ret < 0) { + dev_err(sdev->dev, "failed to enable secondary core '%d' failed with %d\n", + core, ret); + goto power_down; + } + + return ret; + +power_down: + /* power down core if it is host managed and return the original error if this fails too */ + ret1 = hda_dsp_core_reset_power_down(sdev, BIT(core)); + if (ret1 < 0) + dev_err(sdev->dev, "failed to power down core: %d with err: %d\n", core, ret1); + + return ret; +} + +int hda_dsp_disable_interrupts(struct snd_sof_dev *sdev) +{ + hda_sdw_int_enable(sdev, false); + hda_dsp_ipc_int_disable(sdev); + + return 0; +} diff --git a/sound/soc/sof/intel/hda-ipc.c b/sound/soc/sof/intel/hda-ipc.c index 11f20a5a62df..9b3667c705e4 100644 --- a/sound/soc/sof/intel/hda-ipc.c +++ b/sound/soc/sof/intel/hda-ipc.c @@ -15,6 +15,8 @@ * Hardware interface for generic Intel audio DSP HDA IP */ +#include <sound/sof/ipc4/header.h> +#include <trace/events/sof_intel.h> #include "../ops.h" #include "hda.h" @@ -65,12 +67,27 @@ int hda_dsp_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) return 0; } +int hda_dsp_ipc4_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) +{ + struct sof_ipc4_msg *msg_data = msg->msg_data; + + /* send the message via mailbox */ + if (msg_data->data_size) + sof_mailbox_write(sdev, sdev->host_box.offset, msg_data->data_ptr, + msg_data->data_size); + + snd_sof_dsp_write(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCIE, msg_data->extension); + snd_sof_dsp_write(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCI, + msg_data->primary | HDA_DSP_REG_HIPCI_BUSY); + + return 0; +} + void hda_dsp_ipc_get_reply(struct snd_sof_dev *sdev) { struct snd_sof_ipc_msg *msg = sdev->msg; struct sof_ipc_reply reply; struct sof_ipc_cmd_hdr *hdr; - int ret = 0; /* * Sometimes, there is unexpected reply ipc arriving. The reply @@ -94,35 +111,82 @@ void hda_dsp_ipc_get_reply(struct snd_sof_dev *sdev) reply.hdr.cmd = SOF_IPC_GLB_REPLY; reply.hdr.size = sizeof(reply); memcpy(msg->reply_data, &reply, sizeof(reply)); - goto out; + + msg->reply_error = 0; + } else { + snd_sof_ipc_get_reply(sdev); } +} - /* get IPC reply from DSP in the mailbox */ - sof_mailbox_read(sdev, sdev->host_box.offset, &reply, - sizeof(reply)); +irqreturn_t hda_dsp_ipc4_irq_thread(int irq, void *context) +{ + struct sof_ipc4_msg notification_data = {{ 0 }}; + struct snd_sof_dev *sdev = context; + bool ipc_irq = false; + u32 hipcie, hipct; - if (reply.error < 0) { - memcpy(msg->reply_data, &reply, sizeof(reply)); - ret = reply.error; - } else { - /* reply correct size ? */ - if (reply.hdr.size != msg->reply_size && - /* getter payload is never known upfront */ - ((reply.hdr.cmd & SOF_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; + hipcie = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCIE); + if (hipcie & HDA_DSP_REG_HIPCIE_DONE) { + /* DSP received the message */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCCTL, + HDA_DSP_REG_HIPCCTL_DONE, 0); + hda_dsp_ipc_dsp_done(sdev); + + ipc_irq = true; + } + + hipct = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCT); + if (hipct & HDA_DSP_REG_HIPCT_BUSY) { + /* Message from DSP (reply or notification) */ + u32 hipcte = snd_sof_dsp_read(sdev, HDA_DSP_BAR, + HDA_DSP_REG_HIPCTE); + u32 primary = hipct & HDA_DSP_REG_HIPCT_MSG_MASK; + u32 extension = hipcte & HDA_DSP_REG_HIPCTE_MSG_MASK; + + /* mask BUSY interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCCTL, + HDA_DSP_REG_HIPCCTL_BUSY, 0); + + if (primary & SOF_IPC4_MSG_DIR_MASK) { + /* Reply received */ + if (likely(sdev->fw_state == SOF_FW_BOOT_COMPLETE)) { + struct sof_ipc4_msg *data = sdev->ipc->msg.reply_data; + + data->primary = primary; + data->extension = extension; + + spin_lock_irq(&sdev->ipc_lock); + + snd_sof_ipc_get_reply(sdev); + snd_sof_ipc_reply(sdev, data->primary); + + spin_unlock_irq(&sdev->ipc_lock); + } else { + dev_dbg_ratelimited(sdev->dev, + "IPC reply before FW_READY: %#x|%#x\n", + primary, extension); + } + } else { + /* Notification received */ + + notification_data.primary = primary; + notification_data.extension = extension; + sdev->ipc->msg.rx_data = ¬ification_data; + snd_sof_ipc_msgs_rx(sdev); + sdev->ipc->msg.rx_data = NULL; } - /* read the message */ - if (msg->reply_size > 0) - sof_mailbox_read(sdev, sdev->host_box.offset, - msg->reply_data, msg->reply_size); + /* Let DSP know that we have finished processing the message */ + hda_dsp_ipc_host_done(sdev); + + ipc_irq = true; } -out: - msg->reply_error = ret; + if (!ipc_irq) + /* This interrupt is not shared so no need to return IRQ_NONE. */ + dev_dbg_ratelimited(sdev->dev, "nothing to do in IPC IRQ thread\n"); + return IRQ_HANDLED; } /* IPC handler thread */ @@ -149,9 +213,7 @@ irqreturn_t hda_dsp_ipc_irq_thread(int irq, void *context) msg = hipci & HDA_DSP_REG_HIPCI_MSG_MASK; msg_ext = hipcie & HDA_DSP_REG_HIPCIE_MSG_MASK; - dev_vdbg(sdev->dev, - "ipc: firmware response, msg:0x%x, msg_ext:0x%x\n", - msg, msg_ext); + trace_sof_intel_ipc_firmware_response(sdev, msg, msg_ext); /* mask Done interrupt */ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, @@ -168,16 +230,21 @@ irqreturn_t hda_dsp_ipc_irq_thread(int irq, void *context) * place, the message might not yet be marked as expecting a * reply. */ - spin_lock_irq(&sdev->ipc_lock); + if (likely(sdev->fw_state == SOF_FW_BOOT_COMPLETE)) { + spin_lock_irq(&sdev->ipc_lock); - /* handle immediate reply from DSP core */ - hda_dsp_ipc_get_reply(sdev); - snd_sof_ipc_reply(sdev, msg); + /* handle immediate reply from DSP core */ + hda_dsp_ipc_get_reply(sdev); + snd_sof_ipc_reply(sdev, msg); - /* set the done bit */ - hda_dsp_ipc_dsp_done(sdev); + /* set the done bit */ + hda_dsp_ipc_dsp_done(sdev); - spin_unlock_irq(&sdev->ipc_lock); + spin_unlock_irq(&sdev->ipc_lock); + } else { + dev_dbg_ratelimited(sdev->dev, "IPC reply before FW_READY: %#x\n", + msg); + } ipc_irq = true; } @@ -187,9 +254,7 @@ irqreturn_t hda_dsp_ipc_irq_thread(int irq, void *context) msg = hipct & HDA_DSP_REG_HIPCT_MSG_MASK; msg_ext = hipcte & HDA_DSP_REG_HIPCTE_MSG_MASK; - dev_vdbg(sdev->dev, - "ipc: firmware initiated, msg:0x%x, msg_ext:0x%x\n", - msg, msg_ext); + trace_sof_intel_ipc_firmware_initiated(sdev, msg, msg_ext); /* mask BUSY interrupt */ snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, @@ -198,8 +263,23 @@ irqreturn_t hda_dsp_ipc_irq_thread(int irq, void *context) /* handle messages from DSP */ if ((hipct & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) { - /* this is a PANIC message !! */ - snd_sof_dsp_panic(sdev, HDA_DSP_PANIC_OFFSET(msg_ext)); + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + bool non_recoverable = true; + + /* + * This is a PANIC message! + * + * If it is arriving during firmware boot and it is not + * the last boot attempt then change the non_recoverable + * to false as the DSP might be able to boot in the next + * iteration(s) + */ + if (sdev->fw_state == SOF_FW_BOOT_IN_PROGRESS && + hda->boot_iteration < HDA_FW_BOOT_ATTEMPTS) + non_recoverable = false; + + snd_sof_dsp_panic(sdev, HDA_DSP_PANIC_OFFSET(msg_ext), + non_recoverable); } else { /* normal message - process normally */ snd_sof_ipc_msgs_rx(sdev); @@ -224,12 +304,13 @@ irqreturn_t hda_dsp_ipc_irq_thread(int irq, void *context) /* Check if an IPC IRQ occurred */ bool hda_dsp_check_ipc_irq(struct snd_sof_dev *sdev) { + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; bool ret = false; u32 irq_status; /* store status */ irq_status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIS); - dev_vdbg(sdev->dev, "irq handler: irq_status:0x%x\n", irq_status); + trace_sof_intel_hda_irq_ipc_check(sdev, irq_status); /* invalid message ? */ if (irq_status == 0xffffffff) @@ -239,6 +320,13 @@ bool hda_dsp_check_ipc_irq(struct snd_sof_dev *sdev) if (irq_status & HDA_DSP_ADSPIS_IPC) ret = true; + /* CLDMA message ? */ + if (irq_status & HDA_DSP_ADSPIS_CL_DMA) { + hda->code_loading = 0; + wake_up(&hda->waitq); + ret = false; + } + out: return ret; } @@ -265,39 +353,37 @@ int hda_ipc_msg_data(struct snd_sof_dev *sdev, hda_stream = container_of(hstream, struct sof_intel_hda_stream, - hda_stream.hstream); + hext_stream.hstream); /* The stream might already be closed */ if (!hstream) return -ESTRPIPE; - sof_mailbox_read(sdev, hda_stream->stream.posn_offset, p, sz); + sof_mailbox_read(sdev, hda_stream->sof_intel_stream.posn_offset, p, sz); } return 0; } -int hda_ipc_pcm_params(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream, - const struct sof_ipc_pcm_params_reply *reply) +int hda_set_stream_data_offset(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + size_t posn_offset) { struct hdac_stream *hstream = substream->runtime->private_data; struct sof_intel_hda_stream *hda_stream; - /* validate offset */ - size_t posn_offset = reply->posn_offset; hda_stream = container_of(hstream, struct sof_intel_hda_stream, - hda_stream.hstream); + hext_stream.hstream); /* check for unaligned offset or overflow */ if (posn_offset > sdev->stream_box.size || posn_offset % sizeof(struct sof_ipc_stream_posn) != 0) return -EINVAL; - hda_stream->stream.posn_offset = sdev->stream_box.offset + posn_offset; + hda_stream->sof_intel_stream.posn_offset = sdev->stream_box.offset + posn_offset; dev_dbg(sdev->dev, "pcm: stream dir %d, posn mailbox offset is %zu", - substream->stream, hda_stream->stream.posn_offset); + substream->stream, hda_stream->sof_intel_stream.posn_offset); return 0; } diff --git a/sound/soc/sof/intel/hda-ipc.h b/sound/soc/sof/intel/hda-ipc.h index 10fbca5939db..8ec5e9f6f8d7 100644 --- a/sound/soc/sof/intel/hda-ipc.h +++ b/sound/soc/sof/intel/hda-ipc.h @@ -51,5 +51,6 @@ irqreturn_t cnl_ipc_irq_thread(int irq, void *context); int cnl_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg); void cnl_ipc_dump(struct snd_sof_dev *sdev); +void cnl_ipc4_dump(struct snd_sof_dev *sdev); #endif diff --git a/sound/soc/sof/intel/hda-loader-skl.c b/sound/soc/sof/intel/hda-loader-skl.c new file mode 100644 index 000000000000..0193fb3964a0 --- /dev/null +++ b/sound/soc/sof/intel/hda-loader-skl.c @@ -0,0 +1,580 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2018-2022 Intel Corporation. All rights reserved. +// + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/firmware.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <sound/hdaudio_ext.h> +#include <sound/sof.h> +#include <sound/pcm_params.h> + +#include "../sof-priv.h" +#include "../ops.h" +#include "hda.h" + +#define HDA_SKL_WAIT_TIMEOUT 500 /* 500 msec */ +#define HDA_SKL_CLDMA_MAX_BUFFER_SIZE (32 * PAGE_SIZE) + +/* Stream Reset */ +#define HDA_CL_SD_CTL_SRST_SHIFT 0 +#define HDA_CL_SD_CTL_SRST(x) (((x) & 0x1) << \ + HDA_CL_SD_CTL_SRST_SHIFT) + +/* Stream Run */ +#define HDA_CL_SD_CTL_RUN_SHIFT 1 +#define HDA_CL_SD_CTL_RUN(x) (((x) & 0x1) << \ + HDA_CL_SD_CTL_RUN_SHIFT) + +/* Interrupt On Completion Enable */ +#define HDA_CL_SD_CTL_IOCE_SHIFT 2 +#define HDA_CL_SD_CTL_IOCE(x) (((x) & 0x1) << \ + HDA_CL_SD_CTL_IOCE_SHIFT) + +/* FIFO Error Interrupt Enable */ +#define HDA_CL_SD_CTL_FEIE_SHIFT 3 +#define HDA_CL_SD_CTL_FEIE(x) (((x) & 0x1) << \ + HDA_CL_SD_CTL_FEIE_SHIFT) + +/* Descriptor Error Interrupt Enable */ +#define HDA_CL_SD_CTL_DEIE_SHIFT 4 +#define HDA_CL_SD_CTL_DEIE(x) (((x) & 0x1) << \ + HDA_CL_SD_CTL_DEIE_SHIFT) + +/* FIFO Limit Change */ +#define HDA_CL_SD_CTL_FIFOLC_SHIFT 5 +#define HDA_CL_SD_CTL_FIFOLC(x) (((x) & 0x1) << \ + HDA_CL_SD_CTL_FIFOLC_SHIFT) + +/* Stripe Control */ +#define HDA_CL_SD_CTL_STRIPE_SHIFT 16 +#define HDA_CL_SD_CTL_STRIPE(x) (((x) & 0x3) << \ + HDA_CL_SD_CTL_STRIPE_SHIFT) + +/* Traffic Priority */ +#define HDA_CL_SD_CTL_TP_SHIFT 18 +#define HDA_CL_SD_CTL_TP(x) (((x) & 0x1) << \ + HDA_CL_SD_CTL_TP_SHIFT) + +/* Bidirectional Direction Control */ +#define HDA_CL_SD_CTL_DIR_SHIFT 19 +#define HDA_CL_SD_CTL_DIR(x) (((x) & 0x1) << \ + HDA_CL_SD_CTL_DIR_SHIFT) + +/* Stream Number */ +#define HDA_CL_SD_CTL_STRM_SHIFT 20 +#define HDA_CL_SD_CTL_STRM(x) (((x) & 0xf) << \ + HDA_CL_SD_CTL_STRM_SHIFT) + +#define HDA_CL_SD_CTL_INT(x) \ + (HDA_CL_SD_CTL_IOCE(x) | \ + HDA_CL_SD_CTL_FEIE(x) | \ + HDA_CL_SD_CTL_DEIE(x)) + +#define HDA_CL_SD_CTL_INT_MASK \ + (HDA_CL_SD_CTL_IOCE(1) | \ + HDA_CL_SD_CTL_FEIE(1) | \ + HDA_CL_SD_CTL_DEIE(1)) + +#define DMA_ADDRESS_128_BITS_ALIGNMENT 7 +#define BDL_ALIGN(x) ((x) >> DMA_ADDRESS_128_BITS_ALIGNMENT) + +/* Buffer Descriptor List Lower Base Address */ +#define HDA_CL_SD_BDLPLBA_SHIFT 7 +#define HDA_CL_SD_BDLPLBA_MASK GENMASK(31, 7) +#define HDA_CL_SD_BDLPLBA(x) \ + ((BDL_ALIGN(lower_32_bits(x)) << HDA_CL_SD_BDLPLBA_SHIFT) & \ + HDA_CL_SD_BDLPLBA_MASK) + +/* Buffer Descriptor List Upper Base Address */ +#define HDA_CL_SD_BDLPUBA(x) \ + (upper_32_bits(x)) + +/* Software Position in Buffer Enable */ +#define HDA_CL_SPBFIFO_SPBFCCTL_SPIBE_SHIFT 0 +#define HDA_CL_SPBFIFO_SPBFCCTL_SPIBE_MASK \ + (1 << HDA_CL_SPBFIFO_SPBFCCTL_SPIBE_SHIFT) + +#define HDA_CL_SPBFIFO_SPBFCCTL_SPIBE(x) \ + (((x) << HDA_CL_SPBFIFO_SPBFCCTL_SPIBE_SHIFT) & \ + HDA_CL_SPBFIFO_SPBFCCTL_SPIBE_MASK) + +#define HDA_CL_DMA_SD_INT_COMPLETE 0x4 + +static int cl_skl_cldma_setup_bdle(struct snd_sof_dev *sdev, + struct snd_dma_buffer *dmab_data, + __le32 **bdlp, int size, int with_ioc) +{ + phys_addr_t addr = virt_to_phys(dmab_data->area); + __le32 *bdl = *bdlp; + + /* + * This code is simplified by using one fragment of physical memory and assuming + * all the code fits. This could be improved with scatter-gather but the firmware + * size is limited by DSP memory anyways + */ + bdl[0] = cpu_to_le32(lower_32_bits(addr)); + bdl[1] = cpu_to_le32(upper_32_bits(addr)); + bdl[2] = cpu_to_le32(size); + bdl[3] = (!with_ioc) ? 0 : cpu_to_le32(0x01); + + return 1; /* one fragment */ +} + +static void cl_skl_cldma_stream_run(struct snd_sof_dev *sdev, bool enable) +{ + int sd_offset = SOF_HDA_ADSP_LOADER_BASE; + unsigned char val; + int retries; + u32 run = enable ? 0x1 : 0; + + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_CTL, + HDA_CL_SD_CTL_RUN(1), HDA_CL_SD_CTL_RUN(run)); + + retries = 300; + do { + udelay(3); + + /* waiting for hardware to report the stream Run bit set */ + val = snd_sof_dsp_read(sdev, HDA_DSP_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_CTL); + val &= HDA_CL_SD_CTL_RUN(1); + if (enable && val) + break; + else if (!enable && !val) + break; + } while (--retries); + + if (retries == 0) + dev_err(sdev->dev, "%s: failed to set Run bit=%d enable=%d\n", + __func__, val, enable); +} + +static void cl_skl_cldma_stream_clear(struct snd_sof_dev *sdev) +{ + int sd_offset = SOF_HDA_ADSP_LOADER_BASE; + + /* make sure Run bit is cleared before setting stream register */ + cl_skl_cldma_stream_run(sdev, 0); + + /* Disable the Interrupt On Completion, FIFO Error Interrupt, + * Descriptor Error Interrupt and set the cldma stream number to 0. + */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_CTL, + HDA_CL_SD_CTL_INT_MASK, HDA_CL_SD_CTL_INT(0)); + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_CTL, + HDA_CL_SD_CTL_STRM(0xf), HDA_CL_SD_CTL_STRM(0)); + + snd_sof_dsp_write(sdev, HDA_DSP_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPL, HDA_CL_SD_BDLPLBA(0)); + snd_sof_dsp_write(sdev, HDA_DSP_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPU, 0); + + /* Set the Cyclic Buffer Length to 0. */ + snd_sof_dsp_write(sdev, HDA_DSP_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_CBL, 0); + /* Set the Last Valid Index. */ + snd_sof_dsp_write(sdev, HDA_DSP_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_LVI, 0); +} + +static void cl_skl_cldma_setup_spb(struct snd_sof_dev *sdev, + unsigned int size, bool enable) +{ + int sd_offset = SOF_DSP_REG_CL_SPBFIFO; + + if (enable) + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SPBFIFO_SPBFCCTL, + HDA_CL_SPBFIFO_SPBFCCTL_SPIBE_MASK, + HDA_CL_SPBFIFO_SPBFCCTL_SPIBE(1)); + + snd_sof_dsp_write(sdev, HDA_DSP_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SPBFIFO_SPIB, size); +} + +static void cl_skl_cldma_set_intr(struct snd_sof_dev *sdev, bool enable) +{ + u32 val = enable ? HDA_DSP_ADSPIC_CL_DMA : 0; + + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIC, + HDA_DSP_ADSPIC_CL_DMA, val); +} + +static void cl_skl_cldma_cleanup_spb(struct snd_sof_dev *sdev) +{ + int sd_offset = SOF_DSP_REG_CL_SPBFIFO; + + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SPBFIFO_SPBFCCTL, + HDA_CL_SPBFIFO_SPBFCCTL_SPIBE_MASK, + HDA_CL_SPBFIFO_SPBFCCTL_SPIBE(0)); + + snd_sof_dsp_write(sdev, HDA_DSP_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SPBFIFO_SPIB, 0); +} + +static void cl_skl_cldma_setup_controller(struct snd_sof_dev *sdev, + struct snd_dma_buffer *dmab_bdl, + unsigned int max_size, u32 count) +{ + int sd_offset = SOF_HDA_ADSP_LOADER_BASE; + + /* Clear the stream first and then set it. */ + cl_skl_cldma_stream_clear(sdev); + + /* setting the stream register */ + snd_sof_dsp_write(sdev, HDA_DSP_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPL, + HDA_CL_SD_BDLPLBA(dmab_bdl->addr)); + snd_sof_dsp_write(sdev, HDA_DSP_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPU, + HDA_CL_SD_BDLPUBA(dmab_bdl->addr)); + + /* Set the Cyclic Buffer Length. */ + snd_sof_dsp_write(sdev, HDA_DSP_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_CBL, max_size); + /* Set the Last Valid Index. */ + snd_sof_dsp_write(sdev, HDA_DSP_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_LVI, count - 1); + + /* Set the Interrupt On Completion, FIFO Error Interrupt, + * Descriptor Error Interrupt and the cldma stream number. + */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_CTL, + HDA_CL_SD_CTL_INT_MASK, HDA_CL_SD_CTL_INT(1)); + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_CTL, + HDA_CL_SD_CTL_STRM(0xf), + HDA_CL_SD_CTL_STRM(1)); +} + +static int cl_stream_prepare_skl(struct snd_sof_dev *sdev, + struct snd_dma_buffer *dmab, + struct snd_dma_buffer *dmab_bdl) + +{ + unsigned int bufsize = HDA_SKL_CLDMA_MAX_BUFFER_SIZE; + __le32 *bdl; + int frags; + int ret; + + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->dev, bufsize, dmab); + if (ret < 0) { + dev_err(sdev->dev, "%s: failed to alloc fw buffer: %x\n", __func__, ret); + return ret; + } + + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->dev, bufsize, dmab_bdl); + if (ret < 0) { + dev_err(sdev->dev, "%s: failed to alloc blde: %x\n", __func__, ret); + snd_dma_free_pages(dmab); + return ret; + } + + bdl = (__le32 *)dmab_bdl->area; + frags = cl_skl_cldma_setup_bdle(sdev, dmab, &bdl, bufsize, 1); + cl_skl_cldma_setup_controller(sdev, dmab_bdl, bufsize, frags); + + return ret; +} + +static void cl_cleanup_skl(struct snd_sof_dev *sdev, + struct snd_dma_buffer *dmab, + struct snd_dma_buffer *dmab_bdl) +{ + cl_skl_cldma_cleanup_spb(sdev); + cl_skl_cldma_stream_clear(sdev); + snd_dma_free_pages(dmab); + snd_dma_free_pages(dmab_bdl); +} + +static int cl_dsp_init_skl(struct snd_sof_dev *sdev, + struct snd_dma_buffer *dmab, + struct snd_dma_buffer *dmab_bdl) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + const struct sof_intel_dsp_desc *chip = hda->desc; + unsigned int status; + u32 flags; + int ret; + + /* check if the init_core is already enabled, if yes, reset and make it run, + * if not, powerdown and enable it again. + */ + if (hda_dsp_core_is_enabled(sdev, chip->init_core_mask)) { + /* if enabled, reset it, and run the init_core. */ + ret = hda_dsp_core_stall_reset(sdev, chip->init_core_mask); + if (ret < 0) + goto err; + + ret = hda_dsp_core_run(sdev, chip->init_core_mask); + if (ret < 0) { + dev_err(sdev->dev, "%s: dsp core start failed %d\n", __func__, ret); + goto err; + } + } else { + /* if not enabled, power down it first and then powerup and run + * the init_core. + */ + ret = hda_dsp_core_reset_power_down(sdev, chip->init_core_mask); + if (ret < 0) { + dev_err(sdev->dev, "%s: dsp core0 disable fail: %d\n", __func__, ret); + goto err; + } + ret = hda_dsp_enable_core(sdev, chip->init_core_mask); + if (ret < 0) { + dev_err(sdev->dev, "%s: dsp core0 enable fail: %d\n", __func__, ret); + goto err; + } + } + + /* prepare DMA for code loader stream */ + ret = cl_stream_prepare_skl(sdev, dmab, dmab_bdl); + if (ret < 0) { + dev_err(sdev->dev, "%s: dma prepare fw loading err: %x\n", __func__, ret); + return ret; + } + + /* enable the interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIC, + HDA_DSP_ADSPIC_IPC, HDA_DSP_ADSPIC_IPC); + + /* enable IPC DONE interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, chip->ipc_ctl, + HDA_DSP_REG_HIPCCTL_DONE, + HDA_DSP_REG_HIPCCTL_DONE); + + /* enable IPC BUSY interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, chip->ipc_ctl, + HDA_DSP_REG_HIPCCTL_BUSY, + HDA_DSP_REG_HIPCCTL_BUSY); + + /* polling the ROM init status information. */ + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, + chip->rom_status_reg, status, + (FSR_TO_STATE_CODE(status) + == FSR_STATE_INIT_DONE), + HDA_DSP_REG_POLL_INTERVAL_US, + chip->rom_init_timeout * + USEC_PER_MSEC); + if (ret < 0) + goto err; + + return ret; + +err: + flags = SOF_DBG_DUMP_PCI | SOF_DBG_DUMP_MBOX; + + snd_sof_dsp_dbg_dump(sdev, "Boot failed\n", flags); + cl_cleanup_skl(sdev, dmab, dmab_bdl); + hda_dsp_core_reset_power_down(sdev, chip->init_core_mask); + return ret; +} + +static void cl_skl_cldma_fill_buffer(struct snd_sof_dev *sdev, + struct snd_dma_buffer *dmab, + unsigned int bufsize, + unsigned int copysize, + const void *curr_pos, + bool intr_enable) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + + /* copy the image into the buffer with the maximum buffer size. */ + unsigned int size = (bufsize == copysize) ? bufsize : copysize; + + memcpy(dmab->area, curr_pos, size); + + /* Set the wait condition for every load. */ + hda->code_loading = 1; + + /* Set the interrupt. */ + if (intr_enable) + cl_skl_cldma_set_intr(sdev, true); + + /* Set the SPB. */ + cl_skl_cldma_setup_spb(sdev, size, true); + + /* Trigger the code loading stream. */ + cl_skl_cldma_stream_run(sdev, true); +} + +static int cl_skl_cldma_wait_interruptible(struct snd_sof_dev *sdev, + bool intr_wait) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + const struct sof_intel_dsp_desc *chip = hda->desc; + int sd_offset = SOF_HDA_ADSP_LOADER_BASE; + u8 cl_dma_intr_status; + + /* + * Wait for CLDMA interrupt to inform the binary segment transfer is + * complete. + */ + if (!wait_event_timeout(hda->waitq, !hda->code_loading, + msecs_to_jiffies(HDA_SKL_WAIT_TIMEOUT))) { + dev_err(sdev->dev, "cldma copy timeout\n"); + dev_err(sdev->dev, "ROM code=%#x: FW status=%#x\n", + snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_SRAM_REG_ROM_ERROR), + snd_sof_dsp_read(sdev, HDA_DSP_BAR, chip->rom_status_reg)); + return -EIO; + } + + /* now check DMA interrupt status */ + cl_dma_intr_status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, + sd_offset + SOF_HDA_ADSP_REG_CL_SD_STS); + + if (!(cl_dma_intr_status & HDA_CL_DMA_SD_INT_COMPLETE)) { + dev_err(sdev->dev, "cldma copy failed\n"); + return -EIO; + } + + dev_dbg(sdev->dev, "cldma buffer copy complete\n"); + return 0; +} + +static int +cl_skl_cldma_copy_to_buf(struct snd_sof_dev *sdev, + struct snd_dma_buffer *dmab, + const void *bin, + u32 total_size, u32 bufsize) +{ + unsigned int bytes_left = total_size; + const void *curr_pos = bin; + int ret; + + if (total_size <= 0) + return -EINVAL; + + while (bytes_left > 0) { + if (bytes_left > bufsize) { + dev_dbg(sdev->dev, "cldma copy %#x bytes\n", bufsize); + + cl_skl_cldma_fill_buffer(sdev, dmab, bufsize, bufsize, curr_pos, true); + + ret = cl_skl_cldma_wait_interruptible(sdev, false); + if (ret < 0) { + dev_err(sdev->dev, "%s: fw failed to load. %#x bytes remaining\n", + __func__, bytes_left); + return ret; + } + + bytes_left -= bufsize; + curr_pos += bufsize; + } else { + dev_dbg(sdev->dev, "cldma copy %#x bytes\n", bytes_left); + + cl_skl_cldma_set_intr(sdev, false); + cl_skl_cldma_fill_buffer(sdev, dmab, bufsize, bytes_left, curr_pos, false); + return 0; + } + } + + return bytes_left; +} + +static int cl_copy_fw_skl(struct snd_sof_dev *sdev, + struct snd_dma_buffer *dmab) + +{ + struct snd_sof_pdata *plat_data = sdev->pdata; + const struct firmware *fw = plat_data->fw; + struct firmware stripped_firmware; + unsigned int bufsize = HDA_SKL_CLDMA_MAX_BUFFER_SIZE; + int ret; + + stripped_firmware.data = plat_data->fw->data + plat_data->fw_offset; + stripped_firmware.size = plat_data->fw->size - plat_data->fw_offset; + + dev_dbg(sdev->dev, "firmware size: %#zx buffer size %#x\n", fw->size, bufsize); + + ret = cl_skl_cldma_copy_to_buf(sdev, dmab, stripped_firmware.data, + stripped_firmware.size, bufsize); + if (ret < 0) + dev_err(sdev->dev, "%s: fw copy failed %d\n", __func__, ret); + + return ret; +} + +int hda_dsp_cl_boot_firmware_skl(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + const struct sof_intel_dsp_desc *chip = hda->desc; + struct snd_dma_buffer dmab_bdl; + struct snd_dma_buffer dmab; + unsigned int reg; + u32 flags; + int ret; + + ret = cl_dsp_init_skl(sdev, &dmab, &dmab_bdl); + + /* retry enabling core and ROM load. seemed to help */ + if (ret < 0) { + ret = cl_dsp_init_skl(sdev, &dmab, &dmab_bdl); + if (ret < 0) { + dev_err(sdev->dev, "Error code=%#x: FW status=%#x\n", + snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_SRAM_REG_ROM_ERROR), + snd_sof_dsp_read(sdev, HDA_DSP_BAR, chip->rom_status_reg)); + dev_err(sdev->dev, "Core En/ROM load fail:%d\n", ret); + return ret; + } + } + + dev_dbg(sdev->dev, "ROM init successful\n"); + + /* at this point DSP ROM has been initialized and should be ready for + * code loading and firmware boot + */ + ret = cl_copy_fw_skl(sdev, &dmab); + if (ret < 0) { + dev_err(sdev->dev, "%s: load firmware failed : %d\n", __func__, ret); + goto err; + } + + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, + chip->rom_status_reg, reg, + (FSR_TO_STATE_CODE(reg) + == FSR_STATE_ROM_BASEFW_ENTERED), + HDA_DSP_REG_POLL_INTERVAL_US, + HDA_DSP_BASEFW_TIMEOUT_US); + + dev_dbg(sdev->dev, "Firmware download successful, booting...\n"); + + cl_skl_cldma_stream_run(sdev, false); + cl_cleanup_skl(sdev, &dmab, &dmab_bdl); + + if (!ret) + return chip->init_core_mask; + + return ret; + +err: + flags = SOF_DBG_DUMP_PCI | SOF_DBG_DUMP_MBOX; + + snd_sof_dsp_dbg_dump(sdev, "Boot failed\n", flags); + + /* power down DSP */ + hda_dsp_core_reset_power_down(sdev, chip->init_core_mask); + cl_skl_cldma_stream_run(sdev, false); + cl_cleanup_skl(sdev, &dmab, &dmab_bdl); + + dev_err(sdev->dev, "%s: load fw failed err: %d\n", __func__, ret); + return ret; +} diff --git a/sound/soc/sof/intel/hda-loader.c b/sound/soc/sof/intel/hda-loader.c index abad6d0ceb83..98812d51b31c 100644 --- a/sound/soc/sof/intel/hda-loader.c +++ b/sound/soc/sof/intel/hda-loader.c @@ -21,34 +21,49 @@ #include <sound/sof.h> #include "ext_manifest.h" #include "../ops.h" +#include "../sof-priv.h" #include "hda.h" -#define HDA_FW_BOOT_ATTEMPTS 3 -#define HDA_CL_STREAM_FORMAT 0x40 +static void hda_ssp_set_cbp_cfp(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + const struct sof_intel_dsp_desc *chip = hda->desc; + int i; + + /* DSP is powered up, set all SSPs to clock consumer/codec provider mode */ + for (i = 0; i < chip->ssp_count; i++) { + snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR, + chip->ssp_base_offset + + i * SSP_DEV_MEM_SIZE + + SSP_SSC1_OFFSET, + SSP_SET_CBP_CFP, + SSP_SET_CBP_CFP); + } +} -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 *hda_cl_stream_prepare(struct snd_sof_dev *sdev, unsigned int format, + unsigned int size, struct snd_dma_buffer *dmab, + int direction) { - struct hdac_ext_stream *dsp_stream; + struct hdac_ext_stream *hext_stream; struct hdac_stream *hstream; struct pci_dev *pci = to_pci_dev(sdev->dev); int ret; - dsp_stream = hda_dsp_stream_get(sdev, direction, 0); + hext_stream = hda_dsp_stream_get(sdev, direction, 0); - if (!dsp_stream) { + if (!hext_stream) { dev_err(sdev->dev, "error: no stream available\n"); return ERR_PTR(-ENODEV); } - hstream = &dsp_stream->hstream; + hstream = &hext_stream->hstream; hstream->substream = NULL; /* allocate DMA buffer */ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV_SG, &pci->dev, size, dmab); if (ret < 0) { dev_err(sdev->dev, "error: memory alloc failed: %d\n", ret); - goto error; + goto out_put; } hstream->period_bytes = 0;/* initialize period_bytes */ @@ -56,67 +71,63 @@ static struct hdac_ext_stream *cl_stream_prepare(struct snd_sof_dev *sdev, unsig hstream->bufsize = size; if (direction == SNDRV_PCM_STREAM_CAPTURE) { - ret = hda_dsp_iccmax_stream_hw_params(sdev, dsp_stream, dmab, NULL); + ret = hda_dsp_iccmax_stream_hw_params(sdev, hext_stream, dmab, NULL); if (ret < 0) { dev_err(sdev->dev, "error: iccmax stream prepare failed: %d\n", ret); - goto error; + goto out_free; } } else { - ret = hda_dsp_stream_hw_params(sdev, dsp_stream, dmab, NULL); + ret = hda_dsp_stream_hw_params(sdev, hext_stream, dmab, NULL); if (ret < 0) { dev_err(sdev->dev, "error: hdac prepare failed: %d\n", ret); - goto error; + goto out_free; } - hda_dsp_stream_spib_config(sdev, dsp_stream, HDA_DSP_SPIB_ENABLE, size); + hda_dsp_stream_spib_config(sdev, hext_stream, HDA_DSP_SPIB_ENABLE, size); } - return dsp_stream; + return hext_stream; -error: - hda_dsp_stream_put(sdev, direction, hstream->stream_tag); +out_free: snd_dma_free_pages(dmab); +out_put: + hda_dsp_stream_put(sdev, direction, hstream->stream_tag); return ERR_PTR(ret); } /* - * first boot sequence has some extra steps. core 0 waits for power - * status on core 1, so power up core 1 also momentarily, keep it in - * reset/stall and then turn it off + * first boot sequence has some extra steps. + * power on all host managed cores and only unstall/run the boot core to boot the + * DSP then turn off all non boot cores (if any) is powered on. */ -static int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag) +int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag, bool imr_boot) { struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; const struct sof_intel_dsp_desc *chip = hda->desc; - unsigned int status; - u32 flags; + unsigned int status, target_status; + u32 flags, ipc_hdr, j; + unsigned long mask; + char *dump_msg; int ret; - int i; /* step 1: power up corex */ - ret = snd_sof_dsp_core_power_up(sdev, chip->host_managed_cores_mask); + ret = hda_dsp_core_power_up(sdev, chip->host_managed_cores_mask); if (ret < 0) { if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS) dev_err(sdev->dev, "error: dsp core 0/1 power up failed\n"); goto err; } - /* DSP is powered up, set all SSPs to slave mode */ - for (i = 0; i < chip->ssp_count; i++) { - snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR, - chip->ssp_base_offset - + i * SSP_DEV_MEM_SIZE - + SSP_SSC1_OFFSET, - SSP_SET_SLAVE, - SSP_SET_SLAVE); - } + hda_ssp_set_cbp_cfp(sdev); + + /* step 2: Send ROM_CONTROL command (stream_tag is ignored for IMR boot) */ + ipc_hdr = chip->ipc_req_mask | HDA_DSP_ROM_IPC_CONTROL; + if (!imr_boot) + ipc_hdr |= HDA_DSP_ROM_IPC_PURGE_FW | ((stream_tag - 1) << 9); - /* step 2: purge FW request */ - snd_sof_dsp_write(sdev, HDA_DSP_BAR, chip->ipc_req, - chip->ipc_req_mask | (HDA_DSP_IPC_PURGE_FW | - ((stream_tag - 1) << 9))); + snd_sof_dsp_write(sdev, HDA_DSP_BAR, chip->ipc_req, ipc_hdr); /* step 3: unset core 0 reset state & unstall/run core 0 */ - ret = hda_dsp_core_run(sdev, BIT(0)); + ret = hda_dsp_core_run(sdev, chip->init_core_mask); if (ret < 0) { if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS) dev_err(sdev->dev, @@ -148,8 +159,8 @@ static int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag) chip->ipc_ack_mask); /* step 5: power down cores that are no longer needed */ - ret = snd_sof_dsp_core_power_down(sdev, chip->host_managed_cores_mask & - ~(chip->init_core_mask)); + ret = hda_dsp_core_reset_power_down(sdev, chip->host_managed_cores_mask & + ~(chip->init_core_mask)); if (ret < 0) { if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS) dev_err(sdev->dev, @@ -160,21 +171,35 @@ static int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag) /* step 6: enable IPC interrupts */ hda_dsp_ipc_int_enable(sdev); - /* step 7: wait for ROM init */ + /* + * step 7: + * - Cold/Full boot: wait for ROM init to proceed to download the firmware + * - IMR boot: wait for ROM firmware entered (firmware booted up from IMR) + */ + if (imr_boot) + target_status = FSR_STATE_FW_ENTERED; + else + target_status = FSR_STATE_INIT_DONE; + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, - HDA_DSP_SRAM_REG_ROM_STATUS, status, - ((status & HDA_DSP_ROM_STS_MASK) - == HDA_DSP_ROM_INIT), + chip->rom_status_reg, status, + (FSR_TO_STATE_CODE(status) == target_status), HDA_DSP_REG_POLL_INTERVAL_US, chip->rom_init_timeout * USEC_PER_MSEC); - if (!ret) + if (!ret) { + /* set enabled cores mask and increment ref count for cores in init_core_mask */ + sdev->enabled_cores_mask |= chip->init_core_mask; + mask = sdev->enabled_cores_mask; + for_each_set_bit(j, &mask, SOF_MAX_DSP_NUM_CORES) + sdev->dsp_core_ref_count[j]++; return 0; + } if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS) dev_err(sdev->dev, - "error: %s: timeout HDA_DSP_SRAM_REG_ROM_STATUS read\n", - __func__); + "%s: timeout with rom_status_reg (%#x) read\n", + __func__, chip->rom_status_reg); err: flags = SOF_DBG_DUMP_PCI | SOF_DBG_DUMP_MBOX | SOF_DBG_DUMP_OPTIONAL; @@ -183,16 +208,19 @@ err: if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS) flags &= ~SOF_DBG_DUMP_OPTIONAL; - snd_sof_dsp_dbg_dump(sdev, flags); - snd_sof_dsp_core_power_down(sdev, chip->host_managed_cores_mask); + dump_msg = kasprintf(GFP_KERNEL, "Boot iteration failed: %d/%d", + hda->boot_iteration, HDA_FW_BOOT_ATTEMPTS); + snd_sof_dsp_dbg_dump(sdev, dump_msg, flags); + hda_dsp_core_reset_power_down(sdev, chip->host_managed_cores_mask); + kfree(dump_msg); return ret; } static int cl_trigger(struct snd_sof_dev *sdev, - struct hdac_ext_stream *stream, int cmd) + struct hdac_ext_stream *hext_stream, int cmd) { - struct hdac_stream *hstream = &stream->hstream; + struct hdac_stream *hstream = &hext_stream->hstream; int sd_offset = SOF_STREAM_SD_OFFSET(hstream); /* code loader is special case that reuses stream ops */ @@ -212,19 +240,19 @@ static int cl_trigger(struct snd_sof_dev *sdev, hstream->running = true; return 0; default: - return hda_dsp_stream_trigger(sdev, stream, cmd); + return hda_dsp_stream_trigger(sdev, hext_stream, cmd); } } -static int cl_cleanup(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab, - struct hdac_ext_stream *stream) +int hda_cl_cleanup(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab, + struct hdac_ext_stream *hext_stream) { - struct hdac_stream *hstream = &stream->hstream; + struct hdac_stream *hstream = &hext_stream->hstream; int sd_offset = SOF_STREAM_SD_OFFSET(hstream); int ret = 0; if (hstream->direction == SNDRV_PCM_STREAM_PLAYBACK) - ret = hda_dsp_stream_spib_config(sdev, stream, HDA_DSP_SPIB_DISABLE, 0); + ret = hda_dsp_stream_spib_config(sdev, hext_stream, HDA_DSP_SPIB_DISABLE, 0); else snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, SOF_HDA_SD_CTL_DMA_START, 0); @@ -248,21 +276,22 @@ static int cl_cleanup(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab, return ret; } -static int cl_copy_fw(struct snd_sof_dev *sdev, struct hdac_ext_stream *stream) +int hda_cl_copy_fw(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream) { + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + const struct sof_intel_dsp_desc *chip = hda->desc; unsigned int reg; int ret, status; - ret = cl_trigger(sdev, stream, SNDRV_PCM_TRIGGER_START); + ret = cl_trigger(sdev, hext_stream, SNDRV_PCM_TRIGGER_START); if (ret < 0) { dev_err(sdev->dev, "error: DMA trigger start failed\n"); return ret; } status = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, - HDA_DSP_SRAM_REG_ROM_STATUS, reg, - ((reg & HDA_DSP_ROM_STS_MASK) - == HDA_DSP_ROM_FW_ENTERED), + chip->rom_status_reg, reg, + (FSR_TO_STATE_CODE(reg) == FSR_STATE_FW_ENTERED), HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_BASEFW_TIMEOUT_US); @@ -273,11 +302,11 @@ static int cl_copy_fw(struct snd_sof_dev *sdev, struct hdac_ext_stream *stream) if (status < 0) { dev_err(sdev->dev, - "error: %s: timeout HDA_DSP_SRAM_REG_ROM_STATUS read\n", - __func__); + "%s: timeout with rom_status_reg (%#x) read\n", + __func__, chip->rom_status_reg); } - ret = cl_trigger(sdev, stream, SNDRV_PCM_TRIGGER_STOP); + ret = cl_trigger(sdev, hext_stream, SNDRV_PCM_TRIGGER_STOP); if (ret < 0) { dev_err(sdev->dev, "error: DMA trigger stop failed\n"); if (!status) @@ -293,6 +322,7 @@ int hda_dsp_cl_boot_firmware_iccmax(struct snd_sof_dev *sdev) struct hdac_ext_stream *iccmax_stream; struct hdac_bus *bus = sof_to_bus(sdev); struct firmware stripped_firmware; + struct snd_dma_buffer dmab_bdl; int ret, ret1; u8 original_gb; @@ -307,8 +337,8 @@ int hda_dsp_cl_boot_firmware_iccmax(struct snd_sof_dev *sdev) 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); + iccmax_stream = hda_cl_stream_prepare(sdev, HDA_CL_STREAM_FORMAT, stripped_firmware.size, + &dmab_bdl, SNDRV_PCM_STREAM_CAPTURE); if (IS_ERR(iccmax_stream)) { dev_err(sdev->dev, "error: dma prepare for ICCMAX stream failed\n"); return PTR_ERR(iccmax_stream); @@ -320,7 +350,7 @@ int hda_dsp_cl_boot_firmware_iccmax(struct snd_sof_dev *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); + ret1 = hda_cl_cleanup(sdev, &dmab_bdl, iccmax_stream); if (ret1 < 0) { dev_err(sdev->dev, "error: ICCMAX stream cleanup failed\n"); @@ -335,16 +365,44 @@ int hda_dsp_cl_boot_firmware_iccmax(struct snd_sof_dev *sdev) return ret; } +static int hda_dsp_boot_imr(struct snd_sof_dev *sdev) +{ + const struct sof_intel_dsp_desc *chip_info; + int ret; + + chip_info = get_chip_info(sdev->pdata); + if (chip_info->cl_init) + ret = chip_info->cl_init(sdev, 0, true); + else + ret = -EINVAL; + + if (!ret) + hda_sdw_process_wakeen(sdev); + + return ret; +} + int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev) { struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; struct snd_sof_pdata *plat_data = sdev->pdata; const struct sof_dev_desc *desc = plat_data->desc; const struct sof_intel_dsp_desc *chip_info; - struct hdac_ext_stream *stream; + struct hdac_ext_stream *hext_stream; struct firmware stripped_firmware; + struct snd_dma_buffer dmab; int ret, ret1, i; + if (hda->imrboot_supported && !sdev->first_boot && !hda->skip_imr_boot) { + dev_dbg(sdev->dev, "IMR restore supported, booting from IMR directly\n"); + hda->boot_iteration = 0; + ret = hda_dsp_boot_imr(sdev); + if (!ret) + return 0; + + dev_warn(sdev->dev, "IMR restore failed, trying to cold boot\n"); + } + chip_info = desc->chip_info; if (plat_data->fw->size <= plat_data->fw_offset) { @@ -359,14 +417,15 @@ int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev) init_waitqueue_head(&sdev->boot_wait); /* prepare DMA for code loader stream */ - stream = cl_stream_prepare(sdev, HDA_CL_STREAM_FORMAT, stripped_firmware.size, - &sdev->dmab, SNDRV_PCM_STREAM_PLAYBACK); - if (IS_ERR(stream)) { + hext_stream = hda_cl_stream_prepare(sdev, HDA_CL_STREAM_FORMAT, + stripped_firmware.size, + &dmab, SNDRV_PCM_STREAM_PLAYBACK); + if (IS_ERR(hext_stream)) { dev_err(sdev->dev, "error: dma prepare for fw loading failed\n"); - return PTR_ERR(stream); + return PTR_ERR(hext_stream); } - memcpy(sdev->dmab.area, stripped_firmware.data, + memcpy(dmab.area, stripped_firmware.data, stripped_firmware.size); /* try ROM init a few times before giving up */ @@ -375,7 +434,10 @@ int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev) "Attempting iteration %d of Core En/ROM load...\n", i); hda->boot_iteration = i + 1; - ret = cl_dsp_init(sdev, stream->hstream.stream_tag); + if (chip_info->cl_init) + ret = chip_info->cl_init(sdev, hext_stream->hstream.stream_tag, false); + else + ret = -EINVAL; /* don't retry anymore if successful */ if (!ret) @@ -407,15 +469,21 @@ int hda_dsp_cl_boot_firmware(struct snd_sof_dev *sdev) hda_sdw_process_wakeen(sdev); /* - * at this point DSP ROM has been initialized and - * should be ready for code loading and firmware boot + * Set the boot_iteration to the last attempt, indicating that the + * DSP ROM has been initialized and from this point there will be no + * retry done to boot. + * + * Continue with code loading and firmware boot */ - ret = cl_copy_fw(sdev, stream); + hda->boot_iteration = HDA_FW_BOOT_ATTEMPTS; + ret = hda_cl_copy_fw(sdev, hext_stream); if (!ret) { dev_dbg(sdev->dev, "Firmware download successful, booting...\n"); + hda->skip_imr_boot = false; } else { - snd_sof_dsp_dbg_dump(sdev, SOF_DBG_DUMP_PCI | SOF_DBG_DUMP_MBOX); - dev_err(sdev->dev, "error: load fw failed ret: %d\n", ret); + snd_sof_dsp_dbg_dump(sdev, "Firmware download failed", + SOF_DBG_DUMP_PCI | SOF_DBG_DUMP_MBOX); + hda->skip_imr_boot = true; } cleanup: @@ -424,7 +492,7 @@ cleanup: * This should be done even if firmware loading fails. * If the cleanup also fails, we return the initial error */ - ret1 = cl_cleanup(sdev, &sdev->dmab, stream); + ret1 = hda_cl_cleanup(sdev, &dmab, hext_stream); if (ret1 < 0) { dev_err(sdev->dev, "error: Code loader DSP cleanup failed\n"); @@ -460,56 +528,24 @@ 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); -} - -/* - * post fw run operations for ICL, - * Core 3 will be powered up and in stall when HPRO is enabled - */ -int hda_dsp_post_fw_run_icl(struct snd_sof_dev *sdev) -{ - struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; - int ret; + struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata; - 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; } + + /* Check if IMR boot is usable */ + if (!sof_debug_check_flag(SOF_DBG_IGNORE_D3_PERSISTENT) && + (sdev->fw_ready.flags & SOF_IPC_INFO_D3_PERSISTENT || + sdev->pdata->ipc_type == SOF_INTEL_IPC4)) + hdev->imrboot_supported = true; } hda_sdw_int_enable(sdev, true); - /* - * The recommended HW programming sequence for ICL is to - * power up core 3 and keep it in stall if HPRO is enabled. - * Major difference between ICL and TGL, on ICL core 3 is managed by - * the host whereas on TGL it is handled by the firmware. - */ - if (!hda->clk_config_lpro) { - ret = snd_sof_dsp_core_power_up(sdev, BIT(3)); - if (ret < 0) { - dev_err(sdev->dev, "error: dsp core power up failed on core 3\n"); - return ret; - } - - snd_sof_dsp_stall(sdev, BIT(3)); - } - /* re-enable clock gating and power gating */ return hda_dsp_ctrl_clock_power_gating(sdev, true); } @@ -551,24 +587,3 @@ int hda_dsp_ext_man_get_cavs_config_data(struct snd_sof_dev *sdev, return 0; } - -int hda_dsp_core_stall_icl(struct snd_sof_dev *sdev, unsigned int core_mask) -{ - struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; - const struct sof_intel_dsp_desc *chip = hda->desc; - - /* make sure core_mask in host managed cores */ - core_mask &= chip->host_managed_cores_mask; - if (!core_mask) { - dev_err(sdev->dev, "error: core_mask is not in host managed cores\n"); - return -EINVAL; - } - - /* stall core */ - snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR, - HDA_DSP_REG_ADSPCS, - HDA_DSP_ADSPCS_CSTALL_MASK(core_mask), - HDA_DSP_ADSPCS_CSTALL_MASK(core_mask)); - - return 0; -} diff --git a/sound/soc/sof/intel/hda-pcm.c b/sound/soc/sof/intel/hda-pcm.c index cc8ddef37f37..0a9c80216a8c 100644 --- a/sound/soc/sof/intel/hda-pcm.c +++ b/sound/soc/sof/intel/hda-pcm.c @@ -18,6 +18,7 @@ #include <linux/moduleparam.h> #include <sound/hda_register.h> #include <sound/pcm_params.h> +#include <trace/events/sof_intel.h> #include "../sof-audio.h" #include "../ops.h" #include "hda.h" @@ -32,6 +33,10 @@ static bool hda_always_enable_dmi_l1; module_param_named(always_enable_dmi_l1, hda_always_enable_dmi_l1, bool, 0444); MODULE_PARM_DESC(always_enable_dmi_l1, "SOF HDA always enable DMI l1"); +static bool hda_disable_rewinds = IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_DISABLE_REWINDS); +module_param_named(disable_rewinds, hda_disable_rewinds, bool, 0444); +MODULE_PARM_DESC(disable_rewinds, "SOF HDA disable rewinds"); + u32 hda_dsp_get_mult_div(struct snd_sof_dev *sdev, int rate) { switch (rate) { @@ -89,13 +94,12 @@ u32 hda_dsp_get_bits(struct snd_sof_dev *sdev, int sample_bits) int hda_dsp_pcm_hw_params(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, - struct sof_ipc_stream_params *ipc_params) + struct snd_sof_platform_stream_params *platform_params) { struct hdac_stream *hstream = substream->runtime->private_data; - struct hdac_ext_stream *stream = stream_to_hdac_ext_stream(hstream); + struct hdac_ext_stream *hext_stream = stream_to_hdac_ext_stream(hstream); struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; struct snd_dma_buffer *dmab; - struct sof_ipc_fw_version *v = &sdev->fw_ready.version; int ret; u32 size, rate, bits; @@ -114,28 +118,45 @@ int hda_dsp_pcm_hw_params(struct snd_sof_dev *sdev, (params->info & SNDRV_PCM_INFO_NO_PERIOD_WAKEUP) && (params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP); - ret = hda_dsp_stream_hw_params(sdev, stream, dmab, params); + ret = hda_dsp_stream_hw_params(sdev, hext_stream, dmab, params); if (ret < 0) { dev_err(sdev->dev, "error: hdac prepare failed: %d\n", ret); return ret; } - /* disable SPIB, to enable buffer wrap for stream */ - hda_dsp_stream_spib_config(sdev, stream, HDA_DSP_SPIB_DISABLE, 0); - - /* update no_stream_position flag for ipc params */ - if (hda && hda->no_ipc_position) { - /* For older ABIs set host_period_bytes to zero to inform - * FW we don't want position updates. Newer versions use - * no_stream_position for this purpose. - */ - if (v->abi_version < SOF_ABI_VER(3, 10, 0)) - ipc_params->host_period_bytes = 0; - else - ipc_params->no_stream_position = 1; - } + /* enable SPIB when rewinds are disabled */ + if (hda_disable_rewinds) + hda_dsp_stream_spib_config(sdev, hext_stream, HDA_DSP_SPIB_ENABLE, 0); + else + hda_dsp_stream_spib_config(sdev, hext_stream, HDA_DSP_SPIB_DISABLE, 0); + + if (hda) + platform_params->no_ipc_position = hda->no_ipc_position; + + platform_params->stream_tag = hstream->stream_tag; + + return 0; +} + +/* update SPIB register with appl position */ +int hda_dsp_pcm_ack(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream) +{ + struct hdac_stream *hstream = substream->runtime->private_data; + struct hdac_ext_stream *hext_stream = stream_to_hdac_ext_stream(hstream); + struct snd_pcm_runtime *runtime = substream->runtime; + ssize_t appl_pos, buf_size; + u32 spib; - ipc_params->stream_tag = hstream->stream_tag; + appl_pos = frames_to_bytes(runtime, runtime->control->appl_ptr); + buf_size = frames_to_bytes(runtime, runtime->buffer_size); + + spib = appl_pos % buf_size; + + /* Allowable value for SPIB is 1 byte to max buffer size */ + if (!spib) + spib = buf_size; + + sof_io_write(sdev, hext_stream->spib_addr, spib); return 0; } @@ -144,9 +165,9 @@ int hda_dsp_pcm_trigger(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, int cmd) { struct hdac_stream *hstream = substream->runtime->private_data; - struct hdac_ext_stream *stream = stream_to_hdac_ext_stream(hstream); + struct hdac_ext_stream *hext_stream = stream_to_hdac_ext_stream(hstream); - return hda_dsp_stream_trigger(sdev, stream, cmd); + return hda_dsp_stream_trigger(sdev, hext_stream, cmd); } snd_pcm_uframes_t hda_dsp_pcm_pointer(struct snd_sof_dev *sdev, @@ -172,48 +193,11 @@ snd_pcm_uframes_t hda_dsp_pcm_pointer(struct snd_sof_dev *sdev, goto found; } - /* - * DPIB/posbuf position mode: - * For Playback, Use DPIB register from HDA space which - * reflects the actual data transferred. - * For Capture, Use the position buffer for pointer, as DPIB - * is not accurate enough, its update may be completed - * earlier than the data written to DDR. - */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - pos = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, - AZX_REG_VS_SDXDPIB_XBASE + - (AZX_REG_VS_SDXDPIB_XINTERVAL * - hstream->index)); - } else { - /* - * For capture stream, we need more workaround to fix the - * position incorrect issue: - * - * 1. Wait at least 20us before reading position buffer after - * the interrupt generated(IOC), to make sure position update - * happens on frame boundary i.e. 20.833uSec for 48KHz. - * 2. Perform a dummy Read to DPIB register to flush DMA - * position value. - * 3. Read the DMA Position from posbuf. Now the readback - * value should be >= period boundary. - */ - usleep_range(20, 21); - snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, - AZX_REG_VS_SDXDPIB_XBASE + - (AZX_REG_VS_SDXDPIB_XINTERVAL * - hstream->index)); - pos = snd_hdac_stream_get_pos_posbuf(hstream); - } - - if (pos >= hstream->bufsize) - pos = 0; - + pos = hda_dsp_stream_get_position(hstream, substream->stream, true); found: pos = bytes_to_frames(substream->runtime, pos); - dev_vdbg(sdev->dev, "PCM: stream %d dir %d position %lu\n", - hstream->index, substream->stream, pos); + trace_sof_intel_hda_dsp_pcm(sdev, hstream, substream, pos); return pos; } @@ -235,6 +219,13 @@ int hda_dsp_pcm_open(struct snd_sof_dev *sdev, } /* + * if we want the .ack to work, we need to prevent the control from being mapped. + * The status can still be mapped. + */ + if (hda_disable_rewinds) + runtime->hw.info |= SNDRV_PCM_INFO_NO_REWINDS | SNDRV_PCM_INFO_SYNC_APPLPTR; + + /* * All playback streams are DMI L1 capable, capture streams need * pause push/release to be disabled */ @@ -242,6 +233,7 @@ int hda_dsp_pcm_open(struct snd_sof_dev *sdev, runtime->hw.info &= ~SNDRV_PCM_INFO_PAUSE; if (hda_always_enable_dmi_l1 || + direction == SNDRV_PCM_STREAM_PLAYBACK || spcm->stream[substream->stream].d0i3_compatible) flags |= SOF_HDA_STREAM_DMI_L1_COMPATIBLE; diff --git a/sound/soc/sof/intel/hda-probes.c b/sound/soc/sof/intel/hda-probes.c index fe2f3f7d236b..56a533c63cb0 100644 --- a/sound/soc/sof/intel/hda-probes.c +++ b/sound/soc/sof/intel/hda-probes.c @@ -3,14 +3,20 @@ // 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. +// Copyright(c) 2019-2021 Intel Corporation. All rights reserved. // // Author: Cezary Rojewski <cezary.rojewski@intel.com> +// Converted to SOF client: +// Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// Peter Ujfalusi <peter.ujfalusi@linux.intel.com> // +#include <linux/module.h> #include <sound/hdaudio_ext.h> #include <sound/soc.h> #include "../sof-priv.h" +#include "../sof-client-probes.h" +#include "../sof-client.h" #include "hda.h" static inline struct hdac_ext_stream * @@ -19,50 +25,55 @@ 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) +static int hda_probes_compr_startup(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, + struct snd_soc_dai *dai, u32 *stream_id) { - struct hdac_ext_stream *stream; + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + struct hdac_ext_stream *hext_stream; - stream = hda_dsp_stream_get(sdev, cstream->direction, 0); - if (!stream) + hext_stream = hda_dsp_stream_get(sdev, cstream->direction, 0); + if (!hext_stream) return -EBUSY; - hdac_stream(stream)->curr_pos = 0; - hdac_stream(stream)->cstream = cstream; - cstream->runtime->private_data = stream; + hdac_stream(hext_stream)->curr_pos = 0; + hdac_stream(hext_stream)->cstream = cstream; + cstream->runtime->private_data = hext_stream; - return hdac_stream(stream)->stream_tag; + *stream_id = hdac_stream(hext_stream)->stream_tag; + + return 0; } -int hda_probe_compr_free(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_soc_dai *dai) +static int hda_probes_compr_shutdown(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, + struct snd_soc_dai *dai) { - struct hdac_ext_stream *stream = hda_compr_get_stream(cstream); + struct hdac_ext_stream *hext_stream = hda_compr_get_stream(cstream); + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); int ret; ret = hda_dsp_stream_put(sdev, cstream->direction, - hdac_stream(stream)->stream_tag); + hdac_stream(hext_stream)->stream_tag); if (ret < 0) { dev_dbg(sdev->dev, "stream put failed: %d\n", ret); return ret; } - hdac_stream(stream)->cstream = NULL; + hdac_stream(hext_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) +static int hda_probes_compr_set_params(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, + struct snd_compr_params *params, + struct snd_soc_dai *dai) { - struct hdac_ext_stream *stream = hda_compr_get_stream(cstream); - struct hdac_stream *hstream = hdac_stream(stream); + struct hdac_ext_stream *hext_stream = hda_compr_get_stream(cstream); + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + struct hdac_stream *hstream = hdac_stream(hext_stream); struct snd_dma_buffer *dmab; u32 bits, rate; int bps, ret; @@ -80,7 +91,7 @@ int hda_probe_compr_set_params(struct snd_sof_dev *sdev, hstream->period_bytes = cstream->runtime->fragment_size; hstream->no_period_wakeup = 0; - ret = hda_dsp_stream_hw_params(sdev, stream, dmab, NULL); + ret = hda_dsp_stream_hw_params(sdev, hext_stream, dmab, NULL); if (ret < 0) { dev_err(sdev->dev, "error: hdac prepare failed: %d\n", ret); return ret; @@ -89,26 +100,49 @@ int hda_probe_compr_set_params(struct snd_sof_dev *sdev, return 0; } -int hda_probe_compr_trigger(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, int cmd, - struct snd_soc_dai *dai) +static int hda_probes_compr_trigger(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, + int cmd, struct snd_soc_dai *dai) { - struct hdac_ext_stream *stream = hda_compr_get_stream(cstream); + struct hdac_ext_stream *hext_stream = hda_compr_get_stream(cstream); + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); - return hda_dsp_stream_trigger(sdev, stream, cmd); + return hda_dsp_stream_trigger(sdev, hext_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) +static int hda_probes_compr_pointer(struct sof_client_dev *cdev, + struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp, + struct snd_soc_dai *dai) { - struct hdac_ext_stream *stream = hda_compr_get_stream(cstream); + struct hdac_ext_stream *hext_stream = hda_compr_get_stream(cstream); struct snd_soc_pcm_stream *pstream; pstream = &dai->driver->capture; - tstamp->copied_total = hdac_stream(stream)->curr_pos; + tstamp->copied_total = hdac_stream(hext_stream)->curr_pos; tstamp->sampling_rate = snd_pcm_rate_bit_to_rate(pstream->rates); return 0; } + +/* SOF client implementation */ +static const struct sof_probes_host_ops hda_probes_ops = { + .startup = hda_probes_compr_startup, + .shutdown = hda_probes_compr_shutdown, + .set_params = hda_probes_compr_set_params, + .trigger = hda_probes_compr_trigger, + .pointer = hda_probes_compr_pointer, +}; + +int hda_probes_register(struct snd_sof_dev *sdev) +{ + return sof_client_dev_register(sdev, "hda-probes", 0, &hda_probes_ops, + sizeof(hda_probes_ops)); +} + +void hda_probes_unregister(struct snd_sof_dev *sdev) +{ + sof_client_dev_unregister(sdev, "hda-probes", 0); +} + +MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); diff --git a/sound/soc/sof/intel/hda-stream.c b/sound/soc/sof/intel/hda-stream.c index 1d845c2cbc33..be60e7785da9 100644 --- a/sound/soc/sof/intel/hda-stream.c +++ b/sound/soc/sof/intel/hda-stream.c @@ -19,6 +19,7 @@ #include <sound/hdaudio_ext.h> #include <sound/hda_register.h> #include <sound/sof.h> +#include <trace/events/sof_intel.h> #include "../ops.h" #include "../sof-audio.h" #include "hda.h" @@ -57,7 +58,7 @@ static char *hda_hstream_dbg_get_stream_info_str(struct hdac_stream *hstream) */ static int hda_setup_bdle(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab, - struct hdac_stream *stream, + struct hdac_stream *hstream, struct sof_intel_dsp_bdl **bdlp, int offset, int size, int ioc) { @@ -68,7 +69,7 @@ static int hda_setup_bdle(struct snd_sof_dev *sdev, dma_addr_t addr; int chunk; - if (stream->frags >= HDA_DSP_MAX_BDL_ENTRIES) { + if (hstream->frags >= HDA_DSP_MAX_BDL_ENTRIES) { dev_err(sdev->dev, "error: stream frags exceeded\n"); return -EINVAL; } @@ -91,11 +92,8 @@ static int hda_setup_bdle(struct snd_sof_dev *sdev, size -= chunk; bdl->ioc = (size || !ioc) ? 0 : cpu_to_le32(0x01); bdl++; - stream->frags++; + hstream->frags++; offset += chunk; - - dev_vdbg(sdev->dev, "bdl, frags:%d, chunk size:0x%x;\n", - stream->frags, chunk); } *bdlp = bdl; @@ -108,47 +106,47 @@ static int hda_setup_bdle(struct snd_sof_dev *sdev, */ int hda_dsp_stream_setup_bdl(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab, - struct hdac_stream *stream) + struct hdac_stream *hstream) { struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; struct sof_intel_dsp_bdl *bdl; int i, offset, period_bytes, periods; int remain, ioc; - period_bytes = stream->period_bytes; - dev_dbg(sdev->dev, "%s: period_bytes:0x%x\n", __func__, period_bytes); + period_bytes = hstream->period_bytes; + dev_dbg(sdev->dev, "period_bytes:0x%x\n", period_bytes); if (!period_bytes) - period_bytes = stream->bufsize; + period_bytes = hstream->bufsize; - periods = stream->bufsize / period_bytes; + periods = hstream->bufsize / period_bytes; - dev_dbg(sdev->dev, "%s: periods:%d\n", __func__, periods); + dev_dbg(sdev->dev, "periods:%d\n", periods); - remain = stream->bufsize % period_bytes; + remain = hstream->bufsize % period_bytes; if (remain) periods++; /* program the initial BDL entries */ - bdl = (struct sof_intel_dsp_bdl *)stream->bdl.area; + bdl = (struct sof_intel_dsp_bdl *)hstream->bdl.area; offset = 0; - stream->frags = 0; + hstream->frags = 0; /* * set IOC if don't use position IPC * and period_wakeup needed. */ ioc = hda->no_ipc_position ? - !stream->no_period_wakeup : 0; + !hstream->no_period_wakeup : 0; for (i = 0; i < periods; i++) { if (i == (periods - 1) && remain) /* set the last small entry */ offset = hda_setup_bdle(sdev, dmab, - stream, &bdl, offset, + hstream, &bdl, offset, remain, 0); else offset = hda_setup_bdle(sdev, dmab, - stream, &bdl, offset, + hstream, &bdl, offset, period_bytes, ioc); } @@ -156,10 +154,10 @@ int hda_dsp_stream_setup_bdl(struct snd_sof_dev *sdev, } int hda_dsp_stream_spib_config(struct snd_sof_dev *sdev, - struct hdac_ext_stream *stream, + struct hdac_ext_stream *hext_stream, int enable, u32 size) { - struct hdac_stream *hstream = &stream->hstream; + struct hdac_stream *hstream = &hext_stream->hstream; u32 mask; if (!sdev->bar[HDA_DSP_SPIB_BAR]) { @@ -175,7 +173,7 @@ int hda_dsp_stream_spib_config(struct snd_sof_dev *sdev, enable << hstream->index); /* set the SPIB value */ - sof_io_write(sdev, stream->spib_addr, size); + sof_io_write(sdev, hext_stream->spib_addr, size); return 0; } @@ -186,7 +184,7 @@ hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction, u32 flags) { struct hdac_bus *bus = sof_to_bus(sdev); struct sof_intel_hda_stream *hda_stream; - struct hdac_ext_stream *stream = NULL; + struct hdac_ext_stream *hext_stream = NULL; struct hdac_stream *s; spin_lock_irq(&bus->reg_lock); @@ -194,10 +192,10 @@ hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction, u32 flags) /* get an unused stream */ list_for_each_entry(s, &bus->stream_list, list) { if (s->direction == direction && !s->opened) { - stream = stream_to_hdac_ext_stream(s); - hda_stream = container_of(stream, + hext_stream = stream_to_hdac_ext_stream(s); + hda_stream = container_of(hext_stream, struct sof_intel_hda_stream, - hda_stream); + hext_stream); /* check if the host DMA channel is reserved */ if (hda_stream->host_reserved) continue; @@ -210,11 +208,11 @@ hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction, u32 flags) spin_unlock_irq(&bus->reg_lock); /* stream found ? */ - if (!stream) { + if (!hext_stream) { dev_err(sdev->dev, "error: no free %s streams\n", direction == SNDRV_PCM_STREAM_PLAYBACK ? "playback" : "capture"); - return stream; + return hext_stream; } hda_stream->flags = flags; @@ -229,7 +227,7 @@ hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction, u32 flags) HDA_VS_INTEL_EM2, HDA_VS_INTEL_EM2_L1SEN, 0); - return stream; + return hext_stream; } /* free a stream */ @@ -237,7 +235,7 @@ int hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stream_tag) { struct hdac_bus *bus = sof_to_bus(sdev); struct sof_intel_hda_stream *hda_stream; - struct hdac_ext_stream *stream; + struct hdac_ext_stream *hext_stream; struct hdac_stream *s; bool dmi_l1_enable = true; bool found = false; @@ -249,8 +247,8 @@ int hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stream_tag) * that are DMI L1 incompatible. */ list_for_each_entry(s, &bus->stream_list, list) { - stream = stream_to_hdac_ext_stream(s); - hda_stream = container_of(stream, struct sof_intel_hda_stream, hda_stream); + hext_stream = stream_to_hdac_ext_stream(s); + hda_stream = container_of(hext_stream, struct sof_intel_hda_stream, hext_stream); if (!s->opened) continue; @@ -271,7 +269,7 @@ int hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stream_tag) HDA_VS_INTEL_EM2_L1SEN, HDA_VS_INTEL_EM2_L1SEN); if (!found) { - dev_dbg(sdev->dev, "%s: stream_tag %d not opened!\n", + dev_err(sdev->dev, "%s: stream_tag %d not opened!\n", __func__, stream_tag); return -ENODEV; } @@ -279,10 +277,49 @@ int hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stream_tag) return 0; } +static int hda_dsp_stream_reset(struct snd_sof_dev *sdev, struct hdac_stream *hstream) +{ + int sd_offset = SOF_STREAM_SD_OFFSET(hstream); + int timeout = HDA_DSP_STREAM_RESET_TIMEOUT; + u32 val; + + /* enter stream reset */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, SOF_STREAM_SD_OFFSET_CRST, + SOF_STREAM_SD_OFFSET_CRST); + do { + val = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, sd_offset); + if (val & SOF_STREAM_SD_OFFSET_CRST) + break; + } while (--timeout); + if (timeout == 0) { + dev_err(sdev->dev, "timeout waiting for stream reset\n"); + return -ETIMEDOUT; + } + + timeout = HDA_DSP_STREAM_RESET_TIMEOUT; + + /* exit stream reset and wait to read a zero before reading any other register */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, SOF_STREAM_SD_OFFSET_CRST, 0x0); + + /* wait for hardware to report that stream is out of reset */ + udelay(3); + do { + val = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, sd_offset); + if ((val & SOF_STREAM_SD_OFFSET_CRST) == 0) + break; + } while (--timeout); + if (timeout == 0) { + dev_err(sdev->dev, "timeout waiting for stream to exit reset\n"); + return -ETIMEDOUT; + } + + return 0; +} + int hda_dsp_stream_trigger(struct snd_sof_dev *sdev, - struct hdac_ext_stream *stream, int cmd) + struct hdac_ext_stream *hext_stream, int cmd) { - struct hdac_stream *hstream = &stream->hstream; + struct hdac_stream *hstream = &hext_stream->hstream; int sd_offset = SOF_STREAM_SD_OFFSET(hstream); u32 dma_start = SOF_HDA_SD_CTL_DMA_START; int ret = 0; @@ -290,7 +327,6 @@ int hda_dsp_stream_trigger(struct snd_sof_dev *sdev, /* cmd must be for audio stream */ switch (cmd) { - case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_START: snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL, @@ -358,21 +394,26 @@ int hda_dsp_stream_trigger(struct snd_sof_dev *sdev, } /* minimal recommended programming for ICCMAX stream */ -int hda_dsp_iccmax_stream_hw_params(struct snd_sof_dev *sdev, struct hdac_ext_stream *stream, +int hda_dsp_iccmax_stream_hw_params(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream, struct snd_dma_buffer *dmab, struct snd_pcm_hw_params *params) { struct hdac_bus *bus = sof_to_bus(sdev); - struct hdac_stream *hstream = &stream->hstream; + struct hdac_stream *hstream = &hext_stream->hstream; int sd_offset = SOF_STREAM_SD_OFFSET(hstream); int ret; u32 mask = 0x1 << hstream->index; - if (!stream) { + if (!hext_stream) { dev_err(sdev->dev, "error: no stream available\n"); return -ENODEV; } + if (!dmab) { + dev_err(sdev->dev, "error: no dma buffer allocated!\n"); + return -ENODEV; + } + if (hstream->posbuf) *hstream->posbuf = 0; @@ -429,33 +470,34 @@ int hda_dsp_iccmax_stream_hw_params(struct snd_sof_dev *sdev, struct hdac_ext_st * and normal stream. */ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, - struct hdac_ext_stream *stream, + struct hdac_ext_stream *hext_stream, struct snd_dma_buffer *dmab, struct snd_pcm_hw_params *params) { + const struct sof_intel_dsp_desc *chip = get_chip_info(sdev->pdata); struct hdac_bus *bus = sof_to_bus(sdev); - struct hdac_stream *hstream = &stream->hstream; + struct hdac_stream *hstream = &hext_stream->hstream; int sd_offset = SOF_STREAM_SD_OFFSET(hstream); - int ret, timeout = HDA_DSP_STREAM_RESET_TIMEOUT; + int ret; u32 dma_start = SOF_HDA_SD_CTL_DMA_START; - u32 val, mask; + u32 mask; u32 run; - if (!stream) { + if (!hext_stream) { dev_err(sdev->dev, "error: no stream available\n"); return -ENODEV; } - /* decouple host and link DMA */ - mask = 0x1 << hstream->index; - snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, - mask, mask); - if (!dmab) { dev_err(sdev->dev, "error: no dma buffer allocated!\n"); return -ENODEV; } + /* decouple host and link DMA */ + mask = 0x1 << hstream->index; + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, + mask, mask); + /* clear stream status */ snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, SOF_HDA_CL_DMA_SD_INT_MASK | @@ -483,36 +525,9 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, SOF_HDA_CL_DMA_SD_INT_MASK); /* stream reset */ - snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, 0x1, - 0x1); - udelay(3); - do { - val = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, - sd_offset); - if (val & 0x1) - break; - } while (--timeout); - if (timeout == 0) { - dev_err(sdev->dev, "error: stream reset failed\n"); - return -ETIMEDOUT; - } - - timeout = HDA_DSP_STREAM_RESET_TIMEOUT; - snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, sd_offset, 0x1, - 0x0); - - /* wait for hardware to report that stream is out of reset */ - udelay(3); - do { - val = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, - sd_offset); - if ((val & 0x1) == 0) - break; - } while (--timeout); - if (timeout == 0) { - dev_err(sdev->dev, "error: timeout waiting for stream reset\n"); - return -ETIMEDOUT; - } + ret = hda_dsp_stream_reset(sdev, hstream); + if (ret < 0) + return ret; if (hstream->posbuf) *hstream->posbuf = 0; @@ -572,6 +587,7 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, /* * Recommended hardware programming sequence for HDAudio DMA format + * on earlier platforms - this is not needed on newer platforms * * 1. Put DMA into coupled mode by clearing PPCTL.PROCEN bit * for corresponding stream index before the time of writing @@ -581,9 +597,11 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, * enable decoupled mode */ - /* couple host and link DMA, disable DSP features */ - snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, - mask, 0); + if (chip->quirks & SOF_INTEL_PROCEN_FMT_QUIRK) { + /* couple host and link DMA, disable DSP features */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, + mask, 0); + } /* program stream format */ snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, @@ -591,9 +609,11 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, SOF_HDA_ADSP_REG_CL_SD_FORMAT, 0xffff, hstream->format_val); - /* decouple host and link DMA, enable DSP features */ - snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, - mask, mask); + if (chip->quirks & SOF_INTEL_PROCEN_FMT_QUIRK) { + /* decouple host and link DMA, enable DSP features */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, + mask, mask); + } /* program last valid index */ snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, @@ -608,9 +628,10 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, sd_offset + SOF_HDA_ADSP_REG_CL_SD_BDLPU, upper_32_bits(hstream->bdl.addr)); - /* enable position buffer */ - if (!(snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_ADSP_DPLBASE) - & SOF_HDA_ADSP_DPLBASE_ENABLE)) { + /* enable position buffer, if needed */ + if (bus->use_posbuf && bus->posbuf.addr && + !(snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_ADSP_DPLBASE) + & SOF_HDA_ADSP_DPLBASE_ENABLE)) { snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_ADSP_DPUBASE, upper_32_bits(bus->posbuf.addr)); snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_ADSP_DPLBASE, @@ -641,21 +662,28 @@ int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, int hda_dsp_stream_hw_free(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream) { - struct hdac_stream *stream = substream->runtime->private_data; - struct hdac_ext_stream *link_dev = container_of(stream, - struct hdac_ext_stream, - hstream); + struct hdac_stream *hstream = substream->runtime->private_data; + struct hdac_ext_stream *hext_stream = container_of(hstream, + struct hdac_ext_stream, + hstream); struct hdac_bus *bus = sof_to_bus(sdev); - u32 mask = 0x1 << stream->index; + u32 mask = 0x1 << hstream->index; + int ret; + + ret = hda_dsp_stream_reset(sdev, hstream); + if (ret < 0) + return ret; spin_lock_irq(&bus->reg_lock); /* couple host and link DMA if link DMA channel is idle */ - if (!link_dev->link_locked) + if (!hext_stream->link_locked) snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, mask, 0); spin_unlock_irq(&bus->reg_lock); - stream->substream = NULL; + hda_dsp_stream_spib_config(sdev, hext_stream, HDA_DSP_SPIB_DISABLE, 0); + + hstream->substream = NULL; return 0; } @@ -670,7 +698,7 @@ bool hda_dsp_check_stream_irq(struct snd_sof_dev *sdev) 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); + trace_sof_intel_hda_dsp_check_stream_irq(sdev, status); /* if Register inaccessible, ignore it.*/ if (status != 0xffffffff) @@ -682,12 +710,13 @@ bool hda_dsp_check_stream_irq(struct snd_sof_dev *sdev) } static void -hda_dsp_set_bytes_transferred(struct hdac_stream *hstream, u64 buffer_size) +hda_dsp_compr_bytes_transferred(struct hdac_stream *hstream, int direction) { + u64 buffer_size = hstream->bufsize; u64 prev_pos, pos, num_bytes; div64_u64_rem(hstream->curr_pos, buffer_size, &prev_pos); - pos = snd_hdac_stream_get_pos_posbuf(hstream); + pos = hda_dsp_stream_get_position(hstream, direction, false); if (pos < prev_pos) num_bytes = (buffer_size - prev_pos) + pos; @@ -708,8 +737,7 @@ static bool hda_dsp_stream_check(struct hdac_bus *bus, u32 status) if (status & BIT(s->index) && s->opened) { sd_status = snd_hdac_stream_readb(s, SD_STS); - dev_vdbg(bus->dev, "stream %d status 0x%x\n", - s->index, sd_status); + trace_sof_intel_hda_dsp_stream_status(bus->dev, s, sd_status); snd_hdac_stream_writeb(s, SD_STS, sd_status); @@ -723,8 +751,7 @@ static bool hda_dsp_stream_check(struct hdac_bus *bus, u32 status) if (s->substream && sof_hda->no_ipc_position) { snd_sof_pcm_period_elapsed(s->substream); } else if (s->cstream) { - hda_dsp_set_bytes_transferred(s, - s->cstream->runtime->buffer_size); + hda_dsp_compr_bytes_transferred(s, s->cstream->direction); snd_compr_fragment_elapsed(s->cstream); } } @@ -783,7 +810,7 @@ irqreturn_t hda_dsp_stream_threaded_handler(int irq, void *context) int hda_dsp_stream_init(struct snd_sof_dev *sdev) { struct hdac_bus *bus = sof_to_bus(sdev); - struct hdac_ext_stream *stream; + struct hdac_ext_stream *hext_stream; struct hdac_stream *hstream; struct pci_dev *pci = to_pci_dev(sdev->dev); struct sof_intel_hda_dev *sof_hda = bus_to_sof_hda(bus); @@ -847,27 +874,27 @@ int hda_dsp_stream_init(struct snd_sof_dev *sdev) hda_stream->sdev = sdev; - stream = &hda_stream->hda_stream; + hext_stream = &hda_stream->hext_stream; - stream->pphc_addr = sdev->bar[HDA_DSP_PP_BAR] + + hext_stream->pphc_addr = sdev->bar[HDA_DSP_PP_BAR] + SOF_HDA_PPHC_BASE + SOF_HDA_PPHC_INTERVAL * i; - stream->pplc_addr = sdev->bar[HDA_DSP_PP_BAR] + + hext_stream->pplc_addr = sdev->bar[HDA_DSP_PP_BAR] + SOF_HDA_PPLC_BASE + SOF_HDA_PPLC_MULTI * num_total + SOF_HDA_PPLC_INTERVAL * i; /* do we support SPIB */ if (sdev->bar[HDA_DSP_SPIB_BAR]) { - stream->spib_addr = sdev->bar[HDA_DSP_SPIB_BAR] + + hext_stream->spib_addr = sdev->bar[HDA_DSP_SPIB_BAR] + SOF_HDA_SPIB_BASE + SOF_HDA_SPIB_INTERVAL * i + SOF_HDA_SPIB_SPIB; - stream->fifo_addr = sdev->bar[HDA_DSP_SPIB_BAR] + + hext_stream->fifo_addr = sdev->bar[HDA_DSP_SPIB_BAR] + SOF_HDA_SPIB_BASE + SOF_HDA_SPIB_INTERVAL * i + SOF_HDA_SPIB_MAXFIFO; } - hstream = &stream->hstream; + hstream = &hext_stream->hstream; hstream->bus = bus; hstream->sd_int_sta_mask = 1 << i; hstream->index = i; @@ -902,28 +929,28 @@ int hda_dsp_stream_init(struct snd_sof_dev *sdev) hda_stream->sdev = sdev; - stream = &hda_stream->hda_stream; + hext_stream = &hda_stream->hext_stream; /* we always have DSP support */ - stream->pphc_addr = sdev->bar[HDA_DSP_PP_BAR] + + hext_stream->pphc_addr = sdev->bar[HDA_DSP_PP_BAR] + SOF_HDA_PPHC_BASE + SOF_HDA_PPHC_INTERVAL * i; - stream->pplc_addr = sdev->bar[HDA_DSP_PP_BAR] + + hext_stream->pplc_addr = sdev->bar[HDA_DSP_PP_BAR] + SOF_HDA_PPLC_BASE + SOF_HDA_PPLC_MULTI * num_total + SOF_HDA_PPLC_INTERVAL * i; /* do we support SPIB */ if (sdev->bar[HDA_DSP_SPIB_BAR]) { - stream->spib_addr = sdev->bar[HDA_DSP_SPIB_BAR] + + hext_stream->spib_addr = sdev->bar[HDA_DSP_SPIB_BAR] + SOF_HDA_SPIB_BASE + SOF_HDA_SPIB_INTERVAL * i + SOF_HDA_SPIB_SPIB; - stream->fifo_addr = sdev->bar[HDA_DSP_SPIB_BAR] + + hext_stream->fifo_addr = sdev->bar[HDA_DSP_SPIB_BAR] + SOF_HDA_SPIB_BASE + SOF_HDA_SPIB_INTERVAL * i + SOF_HDA_SPIB_MAXFIFO; } - hstream = &stream->hstream; + hstream = &hext_stream->hstream; hstream->bus = bus; hstream->sd_int_sta_mask = 1 << i; hstream->index = i; @@ -958,7 +985,7 @@ void hda_dsp_stream_free(struct snd_sof_dev *sdev) { struct hdac_bus *bus = sof_to_bus(sdev); struct hdac_stream *s, *_s; - struct hdac_ext_stream *stream; + struct hdac_ext_stream *hext_stream; struct sof_intel_hda_stream *hda_stream; /* free position buffer */ @@ -978,9 +1005,95 @@ void hda_dsp_stream_free(struct snd_sof_dev *sdev) if (s->bdl.area) snd_dma_free_pages(&s->bdl); list_del(&s->list); - stream = stream_to_hdac_ext_stream(s); - hda_stream = container_of(stream, struct sof_intel_hda_stream, - hda_stream); + hext_stream = stream_to_hdac_ext_stream(s); + hda_stream = container_of(hext_stream, struct sof_intel_hda_stream, + hext_stream); devm_kfree(sdev->dev, hda_stream); } } + +snd_pcm_uframes_t hda_dsp_stream_get_position(struct hdac_stream *hstream, + int direction, bool can_sleep) +{ + struct hdac_ext_stream *hext_stream = stream_to_hdac_ext_stream(hstream); + struct sof_intel_hda_stream *hda_stream = hstream_to_sof_hda_stream(hext_stream); + struct snd_sof_dev *sdev = hda_stream->sdev; + snd_pcm_uframes_t pos; + + switch (sof_hda_position_quirk) { + case SOF_HDA_POSITION_QUIRK_USE_SKYLAKE_LEGACY: + /* + * This legacy code, inherited from the Skylake driver, + * mixes DPIB registers and DPIB DDR updates and + * does not seem to follow any known hardware recommendations. + * It's not clear e.g. why there is a different flow + * for capture and playback, the only information that matters is + * what traffic class is used, and on all SOF-enabled platforms + * only VC0 is supported so the work-around was likely not necessary + * and quite possibly wrong. + */ + + /* DPIB/posbuf position mode: + * For Playback, Use DPIB register from HDA space which + * reflects the actual data transferred. + * For Capture, Use the position buffer for pointer, as DPIB + * is not accurate enough, its update may be completed + * earlier than the data written to DDR. + */ + if (direction == SNDRV_PCM_STREAM_PLAYBACK) { + pos = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, + AZX_REG_VS_SDXDPIB_XBASE + + (AZX_REG_VS_SDXDPIB_XINTERVAL * + hstream->index)); + } else { + /* + * For capture stream, we need more workaround to fix the + * position incorrect issue: + * + * 1. Wait at least 20us before reading position buffer after + * the interrupt generated(IOC), to make sure position update + * happens on frame boundary i.e. 20.833uSec for 48KHz. + * 2. Perform a dummy Read to DPIB register to flush DMA + * position value. + * 3. Read the DMA Position from posbuf. Now the readback + * value should be >= period boundary. + */ + if (can_sleep) + usleep_range(20, 21); + + snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, + AZX_REG_VS_SDXDPIB_XBASE + + (AZX_REG_VS_SDXDPIB_XINTERVAL * + hstream->index)); + pos = snd_hdac_stream_get_pos_posbuf(hstream); + } + break; + case SOF_HDA_POSITION_QUIRK_USE_DPIB_REGISTERS: + /* + * In case VC1 traffic is disabled this is the recommended option + */ + pos = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, + AZX_REG_VS_SDXDPIB_XBASE + + (AZX_REG_VS_SDXDPIB_XINTERVAL * + hstream->index)); + break; + case SOF_HDA_POSITION_QUIRK_USE_DPIB_DDR_UPDATE: + /* + * This is the recommended option when VC1 is enabled. + * While this isn't needed for SOF platforms it's added for + * consistency and debug. + */ + pos = snd_hdac_stream_get_pos_posbuf(hstream); + break; + default: + dev_err_once(sdev->dev, "hda_position_quirk value %d not supported\n", + sof_hda_position_quirk); + pos = 0; + break; + } + + if (pos >= hstream->bufsize) + pos = 0; + + return pos; +} diff --git a/sound/soc/sof/intel/hda-trace.c b/sound/soc/sof/intel/hda-trace.c index 29e3da3c63db..cbb9bd7770e6 100644 --- a/sound/soc/sof/intel/hda-trace.c +++ b/sound/soc/sof/intel/hda-trace.c @@ -19,25 +19,25 @@ #include "../ops.h" #include "hda.h" -static int hda_dsp_trace_prepare(struct snd_sof_dev *sdev) +static int hda_dsp_trace_prepare(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab) { struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; - struct hdac_ext_stream *stream = hda->dtrace_stream; - struct hdac_stream *hstream = &stream->hstream; - struct snd_dma_buffer *dmab = &sdev->dmatb; + struct hdac_ext_stream *hext_stream = hda->dtrace_stream; + struct hdac_stream *hstream = &hext_stream->hstream; int ret; hstream->period_bytes = 0;/* initialize period_bytes */ - hstream->bufsize = sdev->dmatb.bytes; + hstream->bufsize = dmab->bytes; - ret = hda_dsp_stream_hw_params(sdev, stream, dmab, NULL); + ret = hda_dsp_stream_hw_params(sdev, hext_stream, dmab, NULL); if (ret < 0) dev_err(sdev->dev, "error: hdac prepare failed: %d\n", ret); return ret; } -int hda_dsp_trace_init(struct snd_sof_dev *sdev, u32 *stream_tag) +int hda_dsp_trace_init(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab, + struct sof_ipc_dma_trace_params_ext *dtrace_params) { struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; int ret; @@ -51,18 +51,19 @@ int hda_dsp_trace_init(struct snd_sof_dev *sdev, u32 *stream_tag) return -ENODEV; } - *stream_tag = hda->dtrace_stream->hstream.stream_tag; + dtrace_params->stream_tag = hda->dtrace_stream->hstream.stream_tag; /* * initialize capture stream, set BDL address and return corresponding * stream tag which will be sent to the firmware by IPC message. */ - ret = hda_dsp_trace_prepare(sdev); + ret = hda_dsp_trace_prepare(sdev, dmab); if (ret < 0) { dev_err(sdev->dev, "error: hdac trace init failed: %d\n", ret); - hda_dsp_stream_put(sdev, SNDRV_PCM_STREAM_CAPTURE, *stream_tag); + hda_dsp_stream_put(sdev, SNDRV_PCM_STREAM_CAPTURE, + dtrace_params->stream_tag); hda->dtrace_stream = NULL; - *stream_tag = 0; + dtrace_params->stream_tag = 0; } return ret; diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c index 2c0d4d06ab36..1188ec51816b 100644 --- a/sound/soc/sof/intel/hda.c +++ b/sound/soc/sof/intel/hda.c @@ -31,6 +31,9 @@ #include "../ops.h" #include "hda.h" +#define CREATE_TRACE_POINTS +#include <trace/events/sof_intel.h> + #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) #include <sound/soc-acpi-intel-match.h> #endif @@ -41,108 +44,68 @@ #define EXCEPT_MAX_HDR_SIZE 0x400 #define HDA_EXT_ROM_STATUS_SIZE 8 -int hda_ctrl_dai_widget_setup(struct snd_soc_dapm_widget *w) +int hda_ctrl_dai_widget_setup(struct snd_soc_dapm_widget *w, unsigned int quirk_flags, + struct snd_sof_dai_config_data *data) { struct snd_sof_widget *swidget = w->dobj.private; struct snd_soc_component *component = swidget->scomp; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); - struct sof_ipc_dai_config *config; - struct snd_sof_dai *sof_dai; - struct sof_ipc_reply reply; + const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; + struct snd_sof_dai *sof_dai = swidget->private; int ret; - sof_dai = swidget->private; - - if (!sof_dai || !sof_dai->dai_config) { - dev_err(sdev->dev, "No config for DAI %s\n", w->name); + if (!sof_dai) { + dev_err(sdev->dev, "%s: No DAI for DAI widget %s\n", __func__, w->name); return -EINVAL; } - /* DAI already configured, reset it before reconfiguring it */ - if (sof_dai->configured) { - ret = hda_ctrl_dai_widget_free(w); - if (ret < 0) - return ret; - } - - config = &sof_dai->dai_config[sof_dai->current_config]; - - /* - * For static pipelines, the DAI widget would already be set up and calling - * sof_widget_setup() simply returns without doing anything. - * For dynamic pipelines, the DAI widget will be set up now. - */ - ret = sof_widget_setup(sdev, swidget); - if (ret < 0) { - dev_err(sdev->dev, "error: failed setting up DAI widget %s\n", w->name); - return ret; - } + if (tplg_ops->dai_config) { + unsigned int flags; - /* set HW_PARAMS flag */ - config->flags = FIELD_PREP(SOF_DAI_CONFIG_FLAGS_MASK, SOF_DAI_CONFIG_FLAGS_HW_PARAMS); + /* set HW_PARAMS flag along with quirks */ + flags = SOF_DAI_CONFIG_FLAGS_HW_PARAMS | + quirk_flags << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT; - /* send DAI_CONFIG IPC */ - 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 setting DAI config for %s\n", w->name); - return ret; + ret = tplg_ops->dai_config(sdev, swidget, flags, data); + if (ret < 0) { + dev_err(sdev->dev, "%s: DAI config failed for widget %s\n", __func__, + w->name); + return ret; + } } - sof_dai->configured = true; - return 0; } -int hda_ctrl_dai_widget_free(struct snd_soc_dapm_widget *w) +int hda_ctrl_dai_widget_free(struct snd_soc_dapm_widget *w, unsigned int quirk_flags, + struct snd_sof_dai_config_data *data) { struct snd_sof_widget *swidget = w->dobj.private; struct snd_soc_component *component = swidget->scomp; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); - struct sof_ipc_dai_config *config; - struct snd_sof_dai *sof_dai; - struct sof_ipc_reply reply; - int ret; + const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; + struct snd_sof_dai *sof_dai = swidget->private; - sof_dai = swidget->private; - - if (!sof_dai || !sof_dai->dai_config) { - dev_err(sdev->dev, "error: No config to free DAI %s\n", w->name); + if (!sof_dai) { + dev_err(sdev->dev, "%s: No DAI for BE DAI widget %s\n", __func__, w->name); return -EINVAL; } - /* nothing to do if hw_free() is called without restarting the stream after resume. */ - if (!sof_dai->configured) - return 0; - - config = &sof_dai->dai_config[sof_dai->current_config]; - - /* set HW_FREE flag */ - config->flags = FIELD_PREP(SOF_DAI_CONFIG_FLAGS_MASK, SOF_DAI_CONFIG_FLAGS_HW_FREE); - - 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 resetting DAI config for %s\n", w->name); - - /* - * Reset the configured_flag and free the widget even if the IPC fails to keep - * the widget use_count balanced - */ - sof_dai->configured = false; - - return sof_widget_free(sdev, swidget); -} + if (tplg_ops->dai_config) { + unsigned int flags; + int ret; -static const struct sof_intel_dsp_desc - *get_chip_info(struct snd_sof_pdata *pdata) -{ - const struct sof_dev_desc *desc = pdata->desc; - const struct sof_intel_dsp_desc *chip_info; + /* set HW_FREE flag along with any quirks */ + flags = SOF_DAI_CONFIG_FLAGS_HW_FREE | + quirk_flags << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT; - chip_info = desc->chip_info; + ret = tplg_ops->dai_config(sdev, swidget, flags, data); + if (ret < 0) + dev_err(sdev->dev, "%s: DAI config failed for widget '%s'\n", __func__, + w->name); + } - return chip_info; + return 0; } #if IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE) @@ -157,73 +120,37 @@ 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_dai_config_ipc(struct snd_sof_dev *sdev, - struct snd_soc_dapm_widget *w, - int link_id, int alh_stream_id, int dai_id, bool setup) -{ - struct snd_sof_widget *swidget = w->dobj.private; - struct sof_ipc_dai_config *config; - struct snd_sof_dai *sof_dai; - - if (!swidget) { - dev_err(sdev->dev, "error: No private data for widget %s\n", w->name); - return -EINVAL; - } - - sof_dai = swidget->private; - - if (!sof_dai || !sof_dai->dai_config) { - dev_err(sdev->dev, "error: No config for DAI %s\n", w->name); - return -EINVAL; - } - - config = &sof_dai->dai_config[sof_dai->current_config]; - - /* update config with link and stream ID */ - config->dai_index = (link_id << 8) | dai_id; - config->alh.stream_id = alh_stream_id; - - if (setup) - return hda_ctrl_dai_widget_setup(w); - - return hda_ctrl_dai_widget_free(w); -} - static int sdw_params_stream(struct device *dev, struct sdw_intel_stream_params_data *params_data) { - struct snd_pcm_substream *substream = params_data->substream; - struct snd_sof_dev *sdev = dev_get_drvdata(dev); struct snd_soc_dai *d = params_data->dai; + struct snd_sof_dai_config_data data; struct snd_soc_dapm_widget *w; - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - w = d->playback_widget; - else - w = d->capture_widget; + w = snd_soc_dai_get_widget(d, params_data->stream); + data.dai_index = (params_data->link_id << 8) | d->id; + data.dai_data = params_data->alh_stream_id; - return sdw_dai_config_ipc(sdev, w, params_data->link_id, params_data->alh_stream_id, - d->id, true); + return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_NONE, &data); } static int sdw_free_stream(struct device *dev, struct sdw_intel_stream_free_data *free_data) { - struct snd_pcm_substream *substream = free_data->substream; - struct snd_sof_dev *sdev = dev_get_drvdata(dev); struct snd_soc_dai *d = free_data->dai; + struct snd_sof_dai_config_data data; struct snd_soc_dapm_widget *w; - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - w = d->playback_widget; - else - w = d->capture_widget; + w = snd_soc_dai_get_widget(d, free_data->stream); + data.dai_index = (free_data->link_id << 8) | d->id; /* send invalid stream_id */ - return sdw_dai_config_ipc(sdev, w, free_data->link_id, 0xFFFF, d->id, false); + data.dai_data = 0xFFFF; + + return hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE, &data); } -static const struct sdw_intel_ops sdw_callback = { +struct sdw_intel_ops sdw_callback = { .params_stream = sdw_params_stream, .free_stream = sdw_free_stream, }; @@ -429,7 +356,7 @@ static inline bool hda_sdw_check_wakeen_irq(struct snd_sof_dev *sdev) struct hda_dsp_msg_code { u32 code; - const char *msg; + const char *text; }; #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG) @@ -440,15 +367,21 @@ MODULE_PARM_DESC(use_msi, "SOF HDA use PCI MSI mode"); #define hda_use_msi (1) #endif +int sof_hda_position_quirk = SOF_HDA_POSITION_QUIRK_USE_DPIB_REGISTERS; +module_param_named(position_quirk, sof_hda_position_quirk, int, 0444); +MODULE_PARM_DESC(position_quirk, "SOF HDaudio position quirk"); + static char *hda_model; module_param(hda_model, charp, 0444); MODULE_PARM_DESC(hda_model, "Use the given HDA board model."); -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) || IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE) -static int hda_dmic_num = -1; -module_param_named(dmic_num, hda_dmic_num, int, 0444); +static int dmic_num_override = -1; +module_param_named(dmic_num, dmic_num_override, int, 0444); MODULE_PARM_DESC(dmic_num, "SOF HDA DMIC number"); -#endif + +static int mclk_id_override = -1; +module_param_named(mclk_id, mclk_id_override, int, 0444); +MODULE_PARM_DESC(mclk_id, "SOF SSP mclk_id"); #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) static bool hda_codec_use_common_hdmi = IS_ENABLED(CONFIG_SND_HDA_CODEC_HDMI); @@ -456,10 +389,7 @@ 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[] = { - {HDA_DSP_ROM_FW_MANIFEST_LOADED, "status: manifest loaded"}, - {HDA_DSP_ROM_FW_FW_LOADED, "status: fw loaded"}, - {HDA_DSP_ROM_FW_ENTERED, "status: fw entered"}, +static const struct hda_dsp_msg_code hda_dsp_rom_fw_error_texts[] = { {HDA_DSP_ROM_CSE_ERROR, "error: cse error"}, {HDA_DSP_ROM_CSE_WRONG_RESPONSE, "error: cse wrong response"}, {HDA_DSP_ROM_IMR_TO_SMALL, "error: IMR too small"}, @@ -478,24 +408,136 @@ static const struct hda_dsp_msg_code hda_dsp_rom_msg[] = { {HDA_DSP_ROM_NULL_FW_ENTRY, "error: null FW entry point"}, }; -static void hda_dsp_get_status(struct snd_sof_dev *sdev) +#define FSR_ROM_STATE_ENTRY(state) {FSR_STATE_ROM_##state, #state} +static const struct hda_dsp_msg_code fsr_rom_state_names[] = { + FSR_ROM_STATE_ENTRY(INIT), + FSR_ROM_STATE_ENTRY(INIT_DONE), + FSR_ROM_STATE_ENTRY(CSE_MANIFEST_LOADED), + FSR_ROM_STATE_ENTRY(FW_MANIFEST_LOADED), + FSR_ROM_STATE_ENTRY(FW_FW_LOADED), + FSR_ROM_STATE_ENTRY(FW_ENTERED), + FSR_ROM_STATE_ENTRY(VERIFY_FEATURE_MASK), + FSR_ROM_STATE_ENTRY(GET_LOAD_OFFSET), + FSR_ROM_STATE_ENTRY(FETCH_ROM_EXT), + FSR_ROM_STATE_ENTRY(FETCH_ROM_EXT_DONE), + /* CSE states */ + FSR_ROM_STATE_ENTRY(CSE_IMR_REQUEST), + FSR_ROM_STATE_ENTRY(CSE_IMR_GRANTED), + FSR_ROM_STATE_ENTRY(CSE_VALIDATE_IMAGE_REQUEST), + FSR_ROM_STATE_ENTRY(CSE_IMAGE_VALIDATED), + FSR_ROM_STATE_ENTRY(CSE_IPC_IFACE_INIT), + FSR_ROM_STATE_ENTRY(CSE_IPC_RESET_PHASE_1), + FSR_ROM_STATE_ENTRY(CSE_IPC_OPERATIONAL_ENTRY), + FSR_ROM_STATE_ENTRY(CSE_IPC_OPERATIONAL), + FSR_ROM_STATE_ENTRY(CSE_IPC_DOWN), +}; + +#define FSR_BRINGUP_STATE_ENTRY(state) {FSR_STATE_BRINGUP_##state, #state} +static const struct hda_dsp_msg_code fsr_bringup_state_names[] = { + FSR_BRINGUP_STATE_ENTRY(INIT), + FSR_BRINGUP_STATE_ENTRY(INIT_DONE), + FSR_BRINGUP_STATE_ENTRY(HPSRAM_LOAD), + FSR_BRINGUP_STATE_ENTRY(UNPACK_START), + FSR_BRINGUP_STATE_ENTRY(IMR_RESTORE), + FSR_BRINGUP_STATE_ENTRY(FW_ENTERED), +}; + +#define FSR_WAIT_STATE_ENTRY(state) {FSR_WAIT_FOR_##state, #state} +static const struct hda_dsp_msg_code fsr_wait_state_names[] = { + FSR_WAIT_STATE_ENTRY(IPC_BUSY), + FSR_WAIT_STATE_ENTRY(IPC_DONE), + FSR_WAIT_STATE_ENTRY(CACHE_INVALIDATION), + FSR_WAIT_STATE_ENTRY(LP_SRAM_OFF), + FSR_WAIT_STATE_ENTRY(DMA_BUFFER_FULL), + FSR_WAIT_STATE_ENTRY(CSE_CSR), +}; + +#define FSR_MODULE_NAME_ENTRY(mod) [FSR_MOD_##mod] = #mod +static const char * const fsr_module_names[] = { + FSR_MODULE_NAME_ENTRY(ROM), + FSR_MODULE_NAME_ENTRY(ROM_BYP), + FSR_MODULE_NAME_ENTRY(BASE_FW), + FSR_MODULE_NAME_ENTRY(LP_BOOT), + FSR_MODULE_NAME_ENTRY(BRNGUP), + FSR_MODULE_NAME_ENTRY(ROM_EXT), +}; + +static const char * +hda_dsp_get_state_text(u32 code, const struct hda_dsp_msg_code *msg_code, + size_t array_size) { - u32 status; int i; - status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, - HDA_DSP_SRAM_REG_ROM_STATUS); - - for (i = 0; i < ARRAY_SIZE(hda_dsp_rom_msg); i++) { - if (status == hda_dsp_rom_msg[i].code) { - dev_err(sdev->dev, "%s - code %8.8x\n", - hda_dsp_rom_msg[i].msg, status); - return; - } + for (i = 0; i < array_size; i++) { + if (code == msg_code[i].code) + return msg_code[i].text; } + return NULL; +} + +static void hda_dsp_get_state(struct snd_sof_dev *sdev, const char *level) +{ + const struct sof_intel_dsp_desc *chip = get_chip_info(sdev->pdata); + const char *state_text, *error_text, *module_text; + u32 fsr, state, wait_state, module, error_code; + + fsr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, chip->rom_status_reg); + state = FSR_TO_STATE_CODE(fsr); + wait_state = FSR_TO_WAIT_STATE_CODE(fsr); + module = FSR_TO_MODULE_CODE(fsr); + + if (module > FSR_MOD_ROM_EXT) + module_text = "unknown"; + else + module_text = fsr_module_names[module]; + + if (module == FSR_MOD_BRNGUP) + state_text = hda_dsp_get_state_text(state, fsr_bringup_state_names, + ARRAY_SIZE(fsr_bringup_state_names)); + else + state_text = hda_dsp_get_state_text(state, fsr_rom_state_names, + ARRAY_SIZE(fsr_rom_state_names)); + /* not for us, must be generic sof message */ - dev_dbg(sdev->dev, "unknown ROM status value %8.8x\n", status); + if (!state_text) { + dev_printk(level, sdev->dev, "%#010x: unknown ROM status value\n", fsr); + return; + } + + if (wait_state) { + const char *wait_state_text; + + wait_state_text = hda_dsp_get_state_text(wait_state, fsr_wait_state_names, + ARRAY_SIZE(fsr_wait_state_names)); + if (!wait_state_text) + wait_state_text = "unknown"; + + dev_printk(level, sdev->dev, + "%#010x: module: %s, state: %s, waiting for: %s, %s\n", + fsr, module_text, state_text, wait_state_text, + fsr & FSR_HALTED ? "not running" : "running"); + } else { + dev_printk(level, sdev->dev, "%#010x: module: %s, state: %s, %s\n", + fsr, module_text, state_text, + fsr & FSR_HALTED ? "not running" : "running"); + } + + error_code = snd_sof_dsp_read(sdev, HDA_DSP_BAR, chip->rom_status_reg + 4); + if (!error_code) + return; + + error_text = hda_dsp_get_state_text(error_code, hda_dsp_rom_fw_error_texts, + ARRAY_SIZE(hda_dsp_rom_fw_error_texts)); + if (!error_text) + error_text = "unknown"; + + if (state == FSR_STATE_FW_ENTERED) + dev_printk(level, sdev->dev, "status code: %#x (%s)\n", error_code, + error_text); + else + dev_printk(level, sdev->dev, "error code: %#x (%s)\n", error_code, + error_text); } static void hda_dsp_get_registers(struct snd_sof_dev *sdev, @@ -527,44 +569,60 @@ static void hda_dsp_get_registers(struct snd_sof_dev *sdev, } /* dump the first 8 dwords representing the extended ROM status */ -static void hda_dsp_dump_ext_rom_status(struct snd_sof_dev *sdev, u32 flags) +static void hda_dsp_dump_ext_rom_status(struct snd_sof_dev *sdev, const char *level, + u32 flags) { + const struct sof_intel_dsp_desc *chip; char msg[128]; int len = 0; u32 value; int i; + chip = get_chip_info(sdev->pdata); for (i = 0; i < HDA_EXT_ROM_STATUS_SIZE; i++) { - value = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_SRAM_REG_ROM_STATUS + i * 0x4); - len += snprintf(msg + len, sizeof(msg) - len, " 0x%x", value); + value = snd_sof_dsp_read(sdev, HDA_DSP_BAR, chip->rom_status_reg + i * 0x4); + len += scnprintf(msg + len, sizeof(msg) - len, " 0x%x", value); } - dev_err(sdev->dev, "extended rom status: %s", msg); + dev_printk(level, sdev->dev, "extended rom status: %s", msg); } void hda_dsp_dump(struct snd_sof_dev *sdev, u32 flags) { + char *level = (flags & SOF_DBG_DUMP_OPTIONAL) ? KERN_DEBUG : KERN_ERR; struct sof_ipc_dsp_oops_xtensa xoops; struct sof_ipc_panic_info panic_info; u32 stack[HDA_DSP_STACK_DUMP_SIZE]; /* print ROM/FW status */ - hda_dsp_get_status(sdev); + hda_dsp_get_state(sdev, level); - if (flags & SOF_DBG_DUMP_REGS) { + /* The firmware register dump only available with IPC3 */ + if (flags & SOF_DBG_DUMP_REGS && sdev->pdata->ipc_type == SOF_IPC) { u32 status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_SRAM_REG_FW_STATUS); u32 panic = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_SRAM_REG_FW_TRACEP); hda_dsp_get_registers(sdev, &xoops, &panic_info, stack, HDA_DSP_STACK_DUMP_SIZE); - snd_sof_get_status(sdev, status, panic, &xoops, &panic_info, - stack, HDA_DSP_STACK_DUMP_SIZE); + sof_print_oops_and_stack(sdev, level, status, panic, &xoops, + &panic_info, stack, HDA_DSP_STACK_DUMP_SIZE); } else { - hda_dsp_dump_ext_rom_status(sdev, flags); + hda_dsp_dump_ext_rom_status(sdev, level, flags); } } +static bool hda_check_ipc_irq(struct snd_sof_dev *sdev) +{ + const struct sof_intel_dsp_desc *chip; + + chip = get_chip_info(sdev->pdata); + if (chip && chip->check_ipc_irq) + return chip->check_ipc_irq(sdev); + + return false; +} + void hda_ipc_irq_dump(struct snd_sof_dev *sdev) { struct hdac_bus *bus = sof_to_bus(sdev); @@ -605,6 +663,24 @@ void hda_ipc_dump(struct snd_sof_dev *sdev) hipcie, hipct, hipcctl); } +void hda_ipc4_dump(struct snd_sof_dev *sdev) +{ + u32 hipci, hipcie, hipct, hipcte, hipcctl; + + hda_ipc_irq_dump(sdev); + + hipci = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCI); + hipcie = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCIE); + hipct = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCT); + hipcte = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCTE); + hipcctl = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCCTL); + + /* dump the IPC regs */ + /* TODO: parse the raw msg */ + dev_err(sdev->dev, "Host IPC initiator: %#x|%#x, target: %#x|%#x, ctl: %#x\n", + hipci, hipcie, hipct, hipcte, hipcctl); +} + static int hda_init(struct snd_sof_dev *sdev) { struct hda_bus *hbus; @@ -618,7 +694,10 @@ static int hda_init(struct snd_sof_dev *sdev) /* HDA bus init */ sof_hda_bus_init(bus, &pci->dev); - bus->use_posbuf = 1; + if (sof_hda_position_quirk == SOF_HDA_POSITION_QUIRK_USE_DPIB_REGISTERS) + bus->use_posbuf = 0; + else + bus->use_posbuf = 1; bus->bdl_pos_adj = 0; bus->sync_write = 1; @@ -651,24 +730,65 @@ static int hda_init(struct snd_sof_dev *sdev) return ret; } -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) || IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE) - -static int check_nhlt_dmic(struct snd_sof_dev *sdev) +static int check_dmic_num(struct snd_sof_dev *sdev) { + struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata; struct nhlt_acpi_table *nhlt; - int dmic_num; + int dmic_num = 0; - nhlt = intel_nhlt_init(sdev->dev); - if (nhlt) { + nhlt = hdev->nhlt; + if (nhlt) dmic_num = intel_nhlt_get_dmic_geo(sdev->dev, nhlt); - intel_nhlt_free(nhlt); - if (dmic_num >= 1 && dmic_num <= 4) - return dmic_num; + + /* allow for module parameter override */ + if (dmic_num_override != -1) { + dev_dbg(sdev->dev, + "overriding DMICs detected in NHLT tables %d by kernel param %d\n", + dmic_num, dmic_num_override); + dmic_num = dmic_num_override; } - return 0; + if (dmic_num < 0 || dmic_num > 4) { + dev_dbg(sdev->dev, "invalid dmic_number %d\n", dmic_num); + dmic_num = 0; + } + + return dmic_num; +} + +static int check_nhlt_ssp_mask(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata; + struct nhlt_acpi_table *nhlt; + int ssp_mask = 0; + + nhlt = hdev->nhlt; + if (!nhlt) + return ssp_mask; + + if (intel_nhlt_has_endpoint_type(nhlt, NHLT_LINK_SSP)) { + ssp_mask = intel_nhlt_ssp_endpoint_mask(nhlt, NHLT_DEVICE_I2S); + if (ssp_mask) + dev_info(sdev->dev, "NHLT_DEVICE_I2S detected, ssp_mask %#x\n", ssp_mask); + } + + return ssp_mask; +} + +static int check_nhlt_ssp_mclk_mask(struct snd_sof_dev *sdev, int ssp_num) +{ + struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata; + struct nhlt_acpi_table *nhlt; + + nhlt = hdev->nhlt; + if (!nhlt) + return 0; + + return intel_nhlt_ssp_mclk_mask(nhlt, ssp_num); } +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) || IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE) + static const char *fixup_tplg_name(struct snd_sof_dev *sdev, const char *sof_tplg_filename, const char *idisp_str, @@ -694,26 +814,17 @@ static const char *fixup_tplg_name(struct snd_sof_dev *sdev, return tplg_filename; } -static int dmic_topology_fixup(struct snd_sof_dev *sdev, - const char **tplg_filename, - const char *idisp_str, - int *dmic_found) +static int dmic_detect_topology_fixup(struct snd_sof_dev *sdev, + const char **tplg_filename, + const char *idisp_str, + int *dmic_found, + bool tplg_fixup) { - const char *default_tplg_filename = *tplg_filename; - const char *fixed_tplg_filename; const char *dmic_str; int dmic_num; - /* first check NHLT for DMICs */ - dmic_num = check_nhlt_dmic(sdev); - - /* allow for module parameter override */ - if (hda_dmic_num != -1) { - dev_dbg(sdev->dev, - "overriding DMICs detected in NHLT tables %d by kernel param %d\n", - dmic_num, hda_dmic_num); - dmic_num = hda_dmic_num; - } + /* first check for DMICs (using NHLT or module parameter) */ + dmic_num = check_dmic_num(sdev); switch (dmic_num) { case 1: @@ -734,14 +845,19 @@ static int dmic_topology_fixup(struct snd_sof_dev *sdev, break; } - fixed_tplg_filename = fixup_tplg_name(sdev, default_tplg_filename, - idisp_str, dmic_str); - if (!fixed_tplg_filename) - return -ENOMEM; + if (tplg_fixup) { + const char *default_tplg_filename = *tplg_filename; + const char *fixed_tplg_filename; + + fixed_tplg_filename = fixup_tplg_name(sdev, default_tplg_filename, + idisp_str, dmic_str); + if (!fixed_tplg_filename) + return -ENOMEM; + *tplg_filename = fixed_tplg_filename; + } dev_info(sdev->dev, "DMICs detected in NHLT tables: %d\n", dmic_num); *dmic_found = dmic_num; - *tplg_filename = fixed_tplg_filename; return 0; } @@ -860,17 +976,25 @@ static irqreturn_t hda_dsp_interrupt_thread(int irq, void *context) struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata; /* deal with streams and controller first */ - if (hda_dsp_check_stream_irq(sdev)) + if (hda_dsp_check_stream_irq(sdev)) { + trace_sof_intel_hda_irq(sdev, "stream"); hda_dsp_stream_threaded_handler(irq, sdev); + } - if (hda_dsp_check_ipc_irq(sdev)) + if (hda_check_ipc_irq(sdev)) { + trace_sof_intel_hda_irq(sdev, "ipc"); sof_ops(sdev)->irq_thread(irq, sdev); + } - if (hda_dsp_check_sdw_irq(sdev)) + if (hda_dsp_check_sdw_irq(sdev)) { + trace_sof_intel_hda_irq(sdev, "sdw"); hda_dsp_sdw_thread(irq, hdev->sdw); + } - if (hda_sdw_check_wakeen_irq(sdev)) + if (hda_sdw_check_wakeen_irq(sdev)) { + trace_sof_intel_hda_irq(sdev, "wakeen"); hda_sdw_process_wakeen(sdev); + } hda_check_for_state_change(sdev); @@ -915,6 +1039,8 @@ int hda_dsp_probe(struct snd_sof_dev *sdev) goto err; } + sdev->num_cores = chip->cores_num; + hdev = devm_kzalloc(sdev->dev, sizeof(*hdev), GFP_KERNEL); if (!hdev) return -ENOMEM; @@ -961,6 +1087,7 @@ int hda_dsp_probe(struct snd_sof_dev *sdev) dev_dbg(sdev->dev, "DMA mask is 32 bit\n"); dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(32)); } + dma_set_max_seg_size(&pci->dev, UINT_MAX); /* init streams */ ret = hda_dsp_stream_init(sdev); @@ -1028,6 +1155,10 @@ int hda_dsp_probe(struct snd_sof_dev *sdev) INIT_DELAYED_WORK(&hdev->d0i3_work, hda_dsp_d0i3_work); + init_waitqueue_head(&hdev->waitq); + + hdev->nhlt = intel_nhlt_init(sdev->dev); + return 0; free_ipc_irq: @@ -1050,9 +1181,13 @@ err: int hda_dsp_remove(struct snd_sof_dev *sdev) { struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + const struct sof_intel_dsp_desc *chip = hda->desc; struct hdac_bus *bus = sof_to_bus(sdev); struct pci_dev *pci = to_pci_dev(sdev->dev); - const struct sof_intel_dsp_desc *chip = hda->desc; + struct nhlt_acpi_table *nhlt = hda->nhlt; + + if (nhlt) + intel_nhlt_free(nhlt); /* cancel any attempt for DSP D0I3 */ cancel_delayed_work_sync(&hda->d0i3_work); @@ -1075,9 +1210,9 @@ int hda_dsp_remove(struct snd_sof_dev *sdev) snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL, SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN, 0); - /* disable cores */ - if (chip) - snd_sof_dsp_core_power_down(sdev, chip->host_managed_cores_mask); + /* no need to check for error as the DSP will be disabled anyway */ + if (chip && chip->power_down_dsp) + chip->power_down_dsp(sdev); /* disable DSP */ snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, @@ -1103,8 +1238,17 @@ int hda_dsp_remove(struct snd_sof_dev *sdev) return 0; } +int hda_power_down_dsp(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + const struct sof_intel_dsp_desc *chip = hda->desc; + + return hda_dsp_core_reset_power_down(sdev, chip->host_managed_cores_mask); +} + #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA) -static int hda_generic_machine_select(struct snd_sof_dev *sdev) +static void hda_generic_machine_select(struct snd_sof_dev *sdev, + struct snd_soc_acpi_mach **mach) { struct hdac_bus *bus = sof_to_bus(sdev); struct snd_soc_acpi_mach_params *mach_params; @@ -1136,7 +1280,9 @@ static int hda_generic_machine_select(struct snd_sof_dev *sdev) * - one HDMI codec, and/or * - one external HDAudio codec */ - if (!pdata->machine && codec_num <= 2) { + if (!*mach && codec_num <= 2) { + bool tplg_fixup; + hda_mach = snd_soc_acpi_intel_hda_machines; dev_info(bus->dev, "using HDA machine driver %s now\n", @@ -1148,13 +1294,19 @@ static int hda_generic_machine_select(struct snd_sof_dev *sdev) idisp_str = ""; /* topology: use the info from hda_machines */ - tplg_filename = hda_mach->sof_tplg_filename; - ret = dmic_topology_fixup(sdev, &tplg_filename, idisp_str, &dmic_num); + if (pdata->tplg_filename) { + tplg_fixup = false; + tplg_filename = pdata->tplg_filename; + } else { + tplg_fixup = true; + tplg_filename = hda_mach->sof_tplg_filename; + } + ret = dmic_detect_topology_fixup(sdev, &tplg_filename, idisp_str, &dmic_num, + tplg_fixup); if (ret < 0) - return ret; + return; hda_mach->mach_params.dmic_num = dmic_num; - pdata->machine = hda_mach; pdata->tplg_filename = tplg_filename; if (codec_num == 2) { @@ -1164,27 +1316,30 @@ static int hda_generic_machine_select(struct snd_sof_dev *sdev) */ hda_mach->mach_params.link_mask = 0; } + + *mach = hda_mach; } } /* used by hda machine driver to create dai links */ - if (pdata->machine) { - mach_params = (struct snd_soc_acpi_mach_params *) - &pdata->machine->mach_params; + if (*mach) { + mach_params = &(*mach)->mach_params; mach_params->codec_mask = bus->codec_mask; mach_params->common_hdmi_codec_drv = hda_codec_use_common_hdmi; } - - return 0; } #else -static int hda_generic_machine_select(struct snd_sof_dev *sdev) +static void hda_generic_machine_select(struct snd_sof_dev *sdev, + struct snd_soc_acpi_mach **mach) { - return 0; } #endif #if IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE) + +#define SDW_CODEC_ADR_MASK(_adr) ((_adr) & (SDW_DISCO_LINK_ID_MASK | SDW_VERSION_MASK | \ + SDW_MFG_ID_MASK | SDW_PART_ID_MASK)) + /* 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, @@ -1193,7 +1348,7 @@ static bool link_slaves_found(struct snd_sof_dev *sdev, 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; + unsigned int part_id, link_id, unique_id, mfg_id, version; int i, j, k; for (i = 0; i < link->num_adr; i++) { @@ -1203,12 +1358,14 @@ static bool link_slaves_found(struct snd_sof_dev *sdev, mfg_id = SDW_MFG_ID(adr); part_id = SDW_PART_ID(adr); link_id = SDW_DISCO_LINK_ID(adr); + version = SDW_VERSION(adr); for (j = 0; j < num_slaves; j++) { /* find out how many identical parts were reported on that link */ if (ids[j].link_id == link_id && ids[j].id.part_id == part_id && - ids[j].id.mfg_id == mfg_id) + ids[j].id.mfg_id == mfg_id && + ids[j].id.sdw_version == version) reported_part_count++; } @@ -1217,21 +1374,15 @@ static bool link_slaves_found(struct snd_sof_dev *sdev, if (ids[j].link_id != link_id || ids[j].id.part_id != part_id || - ids[j].id.mfg_id != mfg_id) + ids[j].id.mfg_id != mfg_id || + ids[j].id.sdw_version != version) continue; /* find out how many identical parts are expected */ for (k = 0; k < link->num_adr; k++) { u64 adr2 = link->adr_d[k].adr; - unsigned int part_id2, link_id2, mfg_id2; - - mfg_id2 = SDW_MFG_ID(adr2); - part_id2 = SDW_PART_ID(adr2); - link_id2 = SDW_DISCO_LINK_ID(adr2); - if (link_id2 == link_id && - part_id2 == part_id && - mfg_id2 == mfg_id) + if (SDW_CODEC_ADR_MASK(adr2) == SDW_CODEC_ADR_MASK(adr)) expected_part_count++; } @@ -1263,7 +1414,7 @@ static bool link_slaves_found(struct snd_sof_dev *sdev, return true; } -static int hda_sdw_machine_select(struct snd_sof_dev *sdev) +static struct snd_soc_acpi_mach *hda_sdw_machine_select(struct snd_sof_dev *sdev) { struct snd_sof_pdata *pdata = sdev->pdata; const struct snd_soc_acpi_link_adr *link; @@ -1281,7 +1432,7 @@ static int hda_sdw_machine_select(struct snd_sof_dev *sdev) * machines, for mixed cases with I2C/I2S the detection relies * on the HID list. */ - if (link_mask && !pdata->machine) { + if (link_mask) { for (mach = pdata->desc->alt_machines; mach && mach->link_mask; mach++) { /* @@ -1315,105 +1466,201 @@ static int hda_sdw_machine_select(struct snd_sof_dev *sdev) } if (mach && mach->link_mask) { int dmic_num = 0; + bool tplg_fixup; + const char *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); - if (mach->sof_fw_filename) - pdata->fw_filename = mach->sof_fw_filename; - else - pdata->fw_filename = pdata->desc->default_fw_filename; - pdata->tplg_filename = mach->sof_tplg_filename; + + if (pdata->tplg_filename) { + tplg_fixup = false; + } else { + tplg_fixup = true; + tplg_filename = mach->sof_tplg_filename; + } /* * DMICs use up to 4 pins and are typically pin-muxed with SoundWire - * link 2 and 3, thus we only try to enable dmics if all conditions - * are true: - * a) link 2 and 3 are not used by SoundWire + * link 2 and 3, or link 1 and 2, thus we only try to enable dmics + * if all conditions are true: + * a) 2 or fewer links are used by SoundWire * b) the NHLT table reports the presence of microphones */ - if (!(mach->link_mask & GENMASK(3, 2))) { - const char *tplg_filename = mach->sof_tplg_filename; + if (hweight_long(mach->link_mask) <= 2) { int ret; - ret = dmic_topology_fixup(sdev, &tplg_filename, "", &dmic_num); - + ret = dmic_detect_topology_fixup(sdev, &tplg_filename, "", + &dmic_num, tplg_fixup); if (ret < 0) - return ret; - - pdata->tplg_filename = tplg_filename; + return NULL; } + if (tplg_fixup) + pdata->tplg_filename = tplg_filename; mach->mach_params.dmic_num = dmic_num; dev_dbg(sdev->dev, "SoundWire machine driver %s topology %s\n", mach->drv_name, pdata->tplg_filename); - } else { - dev_info(sdev->dev, - "No SoundWire machine driver found\n"); + + return mach; } + + dev_info(sdev->dev, "No SoundWire machine driver found\n"); } - return 0; + return NULL; } #else -static int hda_sdw_machine_select(struct snd_sof_dev *sdev) +static struct snd_soc_acpi_mach *hda_sdw_machine_select(struct snd_sof_dev *sdev) { - return 0; + return NULL; } #endif -void hda_set_mach_params(const struct snd_soc_acpi_mach *mach, +void hda_set_mach_params(struct snd_soc_acpi_mach *mach, struct snd_sof_dev *sdev) { struct snd_sof_pdata *pdata = sdev->pdata; const struct sof_dev_desc *desc = pdata->desc; struct snd_soc_acpi_mach_params *mach_params; - mach_params = (struct snd_soc_acpi_mach_params *)&mach->mach_params; + mach_params = &mach->mach_params; mach_params->platform = dev_name(sdev->dev); mach_params->num_dai_drivers = desc->ops->num_drv; mach_params->dai_drivers = desc->ops->drv; } -void hda_machine_select(struct snd_sof_dev *sdev) +struct snd_soc_acpi_mach *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; + const char *tplg_filename; mach = snd_soc_acpi_find_machine(desc->machines); if (mach) { + bool add_extension = false; + bool tplg_fixup = false; + /* * If tplg file name is overridden, use it instead of * the one set in mach table */ - if (!sof_pdata->tplg_filename) + if (!sof_pdata->tplg_filename) { sof_pdata->tplg_filename = mach->sof_tplg_filename; + tplg_fixup = true; + } - sof_pdata->machine = mach; + /* report to machine driver if any DMICs are found */ + mach->mach_params.dmic_num = check_dmic_num(sdev); + + if (tplg_fixup && + mach->tplg_quirk_mask & SND_SOC_ACPI_TPLG_INTEL_DMIC_NUMBER && + mach->mach_params.dmic_num) { + tplg_filename = devm_kasprintf(sdev->dev, GFP_KERNEL, + "%s%s%d%s", + sof_pdata->tplg_filename, + "-dmic", + mach->mach_params.dmic_num, + "ch"); + if (!tplg_filename) + return NULL; + + sof_pdata->tplg_filename = tplg_filename; + add_extension = true; + } if (mach->link_mask) { mach->mach_params.links = mach->links; mach->mach_params.link_mask = mach->link_mask; } + + /* report SSP link mask to machine driver */ + mach->mach_params.i2s_link_mask = check_nhlt_ssp_mask(sdev); + + if (tplg_fixup && + mach->tplg_quirk_mask & SND_SOC_ACPI_TPLG_INTEL_SSP_NUMBER && + mach->mach_params.i2s_link_mask) { + const struct sof_intel_dsp_desc *chip = get_chip_info(sdev->pdata); + int ssp_num; + int mclk_mask; + + if (hweight_long(mach->mach_params.i2s_link_mask) > 1 && + !(mach->tplg_quirk_mask & SND_SOC_ACPI_TPLG_INTEL_SSP_MSB)) + dev_warn(sdev->dev, "More than one SSP exposed by NHLT, choosing MSB\n"); + + /* fls returns 1-based results, SSPs indices are 0-based */ + ssp_num = fls(mach->mach_params.i2s_link_mask) - 1; + + if (ssp_num >= chip->ssp_count) { + dev_err(sdev->dev, "Invalid SSP %d, max on this platform is %d\n", + ssp_num, chip->ssp_count); + return NULL; + } + + tplg_filename = devm_kasprintf(sdev->dev, GFP_KERNEL, + "%s%s%d", + sof_pdata->tplg_filename, + "-ssp", + ssp_num); + if (!tplg_filename) + return NULL; + + sof_pdata->tplg_filename = tplg_filename; + add_extension = true; + + mclk_mask = check_nhlt_ssp_mclk_mask(sdev, ssp_num); + + if (mclk_mask < 0) { + dev_err(sdev->dev, "Invalid MCLK configuration\n"); + return NULL; + } + + dev_dbg(sdev->dev, "MCLK mask %#x found in NHLT\n", mclk_mask); + + if (mclk_mask) { + dev_info(sdev->dev, "Overriding topology with MCLK mask %#x from NHLT\n", mclk_mask); + sdev->mclk_id_override = true; + sdev->mclk_id_quirk = (mclk_mask & BIT(0)) ? 0 : 1; + } + } + + if (tplg_fixup && add_extension) { + tplg_filename = devm_kasprintf(sdev->dev, GFP_KERNEL, + "%s%s", + sof_pdata->tplg_filename, + ".tplg"); + if (!tplg_filename) + return NULL; + + sof_pdata->tplg_filename = tplg_filename; + } + + /* check if mclk_id should be modified from topology defaults */ + if (mclk_id_override >= 0) { + dev_info(sdev->dev, "Overriding topology with MCLK %d from kernel_parameter\n", mclk_id_override); + sdev->mclk_id_override = true; + sdev->mclk_id_quirk = mclk_id_override; + } } /* * If I2S fails, try SoundWire */ - hda_sdw_machine_select(sdev); + if (!mach) + mach = hda_sdw_machine_select(sdev); /* * Choose HDA generic machine driver if mach is NULL. * Otherwise, set certain mach params. */ - hda_generic_machine_select(sdev); - - if (!sof_pdata->machine) + hda_generic_machine_select(sdev, &mach); + if (!mach) dev_warn(sdev->dev, "warning: No matching ASoC machine driver found\n"); + + return mach; } int hda_pci_intel_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) @@ -1430,6 +1677,16 @@ int hda_pci_intel_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) } EXPORT_SYMBOL_NS(hda_pci_intel_probe, SND_SOC_SOF_INTEL_HDA_COMMON); +int hda_register_clients(struct snd_sof_dev *sdev) +{ + return hda_probes_register(sdev); +} + +void hda_unregister_clients(struct snd_sof_dev *sdev) +{ + hda_probes_unregister(sdev); +} + MODULE_LICENSE("Dual BSD/GPL"); MODULE_IMPORT_NS(SND_SOC_SOF_PCI_DEV); MODULE_IMPORT_NS(SND_SOC_SOF_HDA_AUDIO_CODEC); diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h index 1195018a1f4f..2ab3c3840b92 100644 --- a/sound/soc/sof/intel/hda.h +++ b/sound/soc/sof/intel/hda.h @@ -16,6 +16,8 @@ #include <sound/compress_driver.h> #include <sound/hda_codec.h> #include <sound/hdaudio_ext.h> +#include "../sof-client-probes.h" +#include "../sof-audio.h" #include "shim.h" /* PCI registers */ @@ -185,13 +187,71 @@ #define HDA_DSP_STACK_DUMP_SIZE 32 +/* ROM/FW status register */ +#define FSR_STATE_MASK GENMASK(23, 0) +#define FSR_WAIT_STATE_MASK GENMASK(27, 24) +#define FSR_MODULE_MASK GENMASK(30, 28) +#define FSR_HALTED BIT(31) +#define FSR_TO_STATE_CODE(x) ((x) & FSR_STATE_MASK) +#define FSR_TO_WAIT_STATE_CODE(x) (((x) & FSR_WAIT_STATE_MASK) >> 24) +#define FSR_TO_MODULE_CODE(x) (((x) & FSR_MODULE_MASK) >> 28) + +/* Wait states */ +#define FSR_WAIT_FOR_IPC_BUSY 0x1 +#define FSR_WAIT_FOR_IPC_DONE 0x2 +#define FSR_WAIT_FOR_CACHE_INVALIDATION 0x3 +#define FSR_WAIT_FOR_LP_SRAM_OFF 0x4 +#define FSR_WAIT_FOR_DMA_BUFFER_FULL 0x5 +#define FSR_WAIT_FOR_CSE_CSR 0x6 + +/* Module codes */ +#define FSR_MOD_ROM 0x0 +#define FSR_MOD_ROM_BYP 0x1 +#define FSR_MOD_BASE_FW 0x2 +#define FSR_MOD_LP_BOOT 0x3 +#define FSR_MOD_BRNGUP 0x4 +#define FSR_MOD_ROM_EXT 0x5 + +/* State codes (module dependent) */ +/* Module independent states */ +#define FSR_STATE_INIT 0x0 +#define FSR_STATE_INIT_DONE 0x1 +#define FSR_STATE_FW_ENTERED 0x5 + +/* ROM states */ +#define FSR_STATE_ROM_INIT FSR_STATE_INIT +#define FSR_STATE_ROM_INIT_DONE FSR_STATE_INIT_DONE +#define FSR_STATE_ROM_CSE_MANIFEST_LOADED 0x2 +#define FSR_STATE_ROM_FW_MANIFEST_LOADED 0x3 +#define FSR_STATE_ROM_FW_FW_LOADED 0x4 +#define FSR_STATE_ROM_FW_ENTERED FSR_STATE_FW_ENTERED +#define FSR_STATE_ROM_VERIFY_FEATURE_MASK 0x6 +#define FSR_STATE_ROM_GET_LOAD_OFFSET 0x7 +#define FSR_STATE_ROM_FETCH_ROM_EXT 0x8 +#define FSR_STATE_ROM_FETCH_ROM_EXT_DONE 0x9 +#define FSR_STATE_ROM_BASEFW_ENTERED 0xf /* SKL */ + +/* (ROM) CSE states */ +#define FSR_STATE_ROM_CSE_IMR_REQUEST 0x10 +#define FSR_STATE_ROM_CSE_IMR_GRANTED 0x11 +#define FSR_STATE_ROM_CSE_VALIDATE_IMAGE_REQUEST 0x12 +#define FSR_STATE_ROM_CSE_IMAGE_VALIDATED 0x13 + +#define FSR_STATE_ROM_CSE_IPC_IFACE_INIT 0x20 +#define FSR_STATE_ROM_CSE_IPC_RESET_PHASE_1 0x21 +#define FSR_STATE_ROM_CSE_IPC_OPERATIONAL_ENTRY 0x22 +#define FSR_STATE_ROM_CSE_IPC_OPERATIONAL 0x23 +#define FSR_STATE_ROM_CSE_IPC_DOWN 0x24 + +/* BRINGUP (or BRNGUP) states */ +#define FSR_STATE_BRINGUP_INIT FSR_STATE_INIT +#define FSR_STATE_BRINGUP_INIT_DONE FSR_STATE_INIT_DONE +#define FSR_STATE_BRINGUP_HPSRAM_LOAD 0x2 +#define FSR_STATE_BRINGUP_UNPACK_START 0X3 +#define FSR_STATE_BRINGUP_IMR_RESTORE 0x4 +#define FSR_STATE_BRINGUP_FW_ENTERED FSR_STATE_FW_ENTERED + /* ROM status/error values */ -#define HDA_DSP_ROM_STS_MASK GENMASK(23, 0) -#define HDA_DSP_ROM_INIT 0x1 -#define HDA_DSP_ROM_FW_MANIFEST_LOADED 0x3 -#define HDA_DSP_ROM_FW_FW_LOADED 0x4 -#define HDA_DSP_ROM_FW_ENTERED 0x5 -#define HDA_DSP_ROM_RFW_START 0xf #define HDA_DSP_ROM_CSE_ERROR 40 #define HDA_DSP_ROM_CSE_WRONG_RESPONSE 41 #define HDA_DSP_ROM_IMR_TO_SMALL 42 @@ -208,7 +268,9 @@ #define HDA_DSP_ROM_USER_EXCEPTION 0xBEEF0000 #define HDA_DSP_ROM_UNEXPECTED_RESET 0xDECAF000 #define HDA_DSP_ROM_NULL_FW_ENTRY 0x4c4c4e55 -#define HDA_DSP_IPC_PURGE_FW 0x01004000 + +#define HDA_DSP_ROM_IPC_CONTROL 0x01000000 +#define HDA_DSP_ROM_IPC_PURGE_FW 0x00004000 /* various timeout values */ #define HDA_DSP_PU_TIMEOUT 50 @@ -221,8 +283,8 @@ #define HDA_DSP_REG_POLL_INTERVAL_US 500 /* 0.5 msec */ #define HDA_DSP_REG_POLL_RETRY_COUNT 50 -#define HDA_DSP_ADSPIC_IPC 1 -#define HDA_DSP_ADSPIS_IPC 1 +#define HDA_DSP_ADSPIC_IPC BIT(0) +#define HDA_DSP_ADSPIS_IPC BIT(0) /* Intel HD Audio General DSP Registers */ #define HDA_DSP_GEN_BASE 0x0 @@ -266,14 +328,14 @@ /* HIPCTE */ #define HDA_DSP_REG_HIPCTE_MSG_MASK 0x3FFFFFFF -#define HDA_DSP_ADSPIC_CL_DMA 0x2 -#define HDA_DSP_ADSPIS_CL_DMA 0x2 +#define HDA_DSP_ADSPIC_CL_DMA BIT(1) +#define HDA_DSP_ADSPIS_CL_DMA BIT(1) /* Delay before scheduling D0i3 entry */ #define BXT_D0I3_DELAY 5000 #define FW_CL_STREAM_NUMBER 0x1 -#define HDA_FW_BOOT_ATTEMPTS 3 +#define HDA_FW_BOOT_ATTEMPTS 3 /* ADSPCS - Audio DSP Control & Status */ @@ -351,18 +413,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 /* Intel HD Audio SRAM Window 0*/ +#define HDA_DSP_SRAM_REG_ROM_STATUS_SKL 0x8000 #define HDA_ADSP_SRAM0_BASE_SKL 0x8000 /* Firmware status window */ @@ -380,12 +437,14 @@ #define APL_SSP_COUNT 6 #define CNL_SSP_COUNT 3 #define ICL_SSP_COUNT 6 +#define TGL_SSP_COUNT 3 +#define MTL_SSP_COUNT 3 /* SSP Registers */ #define SSP_SSC1_OFFSET 0x4 -#define SSP_SET_SCLK_SLAVE BIT(25) -#define SSP_SET_SFRM_SLAVE BIT(24) -#define SSP_SET_SLAVE (SSP_SET_SCLK_SLAVE | SSP_SET_SFRM_SLAVE) +#define SSP_SET_SCLK_CONSUMER BIT(25) +#define SSP_SET_SFRM_CONSUMER BIT(24) +#define SSP_SET_CBP_CFP (SSP_SET_SCLK_CONSUMER | SSP_SET_SFRM_CONSUMER) #define HDA_IDISP_ADDR 2 #define HDA_IDISP_CODEC(x) ((x) & BIT(HDA_IDISP_ADDR)) @@ -420,6 +479,9 @@ enum sof_hda_D0_substate { /* represents DSP HDA controller frontend - i.e. host facing control */ struct sof_intel_hda_dev { + bool imrboot_supported; + bool skip_imr_boot; + int boot_iteration; struct hda_bus hbus; @@ -453,6 +515,12 @@ struct sof_intel_hda_dev { /* FW clock config, 0:HPRO, 1:LPRO */ bool clk_config_lpro; + + wait_queue_head_t waitq; + bool code_loading; + + /* Intel NHLT information */ + struct nhlt_acpi_table *nhlt; }; static inline struct hdac_bus *sof_to_bus(struct snd_sof_dev *s) @@ -471,14 +539,14 @@ static inline struct hda_bus *sof_to_hbus(struct snd_sof_dev *s) struct sof_intel_hda_stream { struct snd_sof_dev *sdev; - struct hdac_ext_stream hda_stream; - struct sof_intel_stream stream; + struct hdac_ext_stream hext_stream; + struct sof_intel_stream sof_intel_stream; int host_reserved; /* reserve host DMA channel */ u32 flags; }; #define hstream_to_sof_hda_stream(hstream) \ - container_of(hstream, struct sof_intel_hda_stream, hda_stream) + container_of(hstream, struct sof_intel_hda_stream, hext_stream) #define bus_to_sof_hda(bus) \ container_of(bus, struct sof_intel_hda_dev, hbus.core) @@ -487,17 +555,23 @@ struct sof_intel_hda_stream { (SOF_HDA_ADSP_SD_ENTRY_SIZE * ((s)->index) \ + SOF_HDA_ADSP_LOADER_BASE) +#define SOF_STREAM_SD_OFFSET_CRST 0x1 + /* * DSP Core services. */ int hda_dsp_probe(struct snd_sof_dev *sdev); int hda_dsp_remove(struct snd_sof_dev *sdev); +int hda_dsp_core_power_up(struct snd_sof_dev *sdev, unsigned int core_mask); int hda_dsp_core_run(struct snd_sof_dev *sdev, unsigned int core_mask); int hda_dsp_enable_core(struct snd_sof_dev *sdev, unsigned int core_mask); int hda_dsp_core_reset_power_down(struct snd_sof_dev *sdev, unsigned int core_mask); +int hda_power_down_dsp(struct snd_sof_dev *sdev); +int hda_dsp_core_get(struct snd_sof_dev *sdev, int core); void hda_dsp_ipc_int_enable(struct snd_sof_dev *sdev); void hda_dsp_ipc_int_disable(struct snd_sof_dev *sdev); +bool hda_dsp_core_is_enabled(struct snd_sof_dev *sdev, unsigned int core_mask); int hda_dsp_set_power_state(struct snd_sof_dev *sdev, const struct sof_dsp_power_state *target_state); @@ -513,6 +587,7 @@ 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); +int hda_dsp_disable_interrupts(struct snd_sof_dev *sdev); /* * DSP PCM Operations. @@ -526,13 +601,14 @@ int hda_dsp_pcm_close(struct snd_sof_dev *sdev, int hda_dsp_pcm_hw_params(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, - struct sof_ipc_stream_params *ipc_params); + struct snd_sof_platform_stream_params *platform_params); int hda_dsp_stream_hw_free(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream); int hda_dsp_pcm_trigger(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, int cmd); snd_pcm_uframes_t hda_dsp_pcm_pointer(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream); +int hda_dsp_pcm_ack(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream); /* * DSP Stream Operations. @@ -541,57 +617,38 @@ snd_pcm_uframes_t hda_dsp_pcm_pointer(struct snd_sof_dev *sdev, int hda_dsp_stream_init(struct snd_sof_dev *sdev); void hda_dsp_stream_free(struct snd_sof_dev *sdev); int hda_dsp_stream_hw_params(struct snd_sof_dev *sdev, - struct hdac_ext_stream *stream, + struct hdac_ext_stream *hext_stream, struct snd_dma_buffer *dmab, struct snd_pcm_hw_params *params); -int hda_dsp_iccmax_stream_hw_params(struct snd_sof_dev *sdev, struct hdac_ext_stream *stream, +int hda_dsp_iccmax_stream_hw_params(struct snd_sof_dev *sdev, + struct hdac_ext_stream *hext_stream, struct snd_dma_buffer *dmab, struct snd_pcm_hw_params *params); int hda_dsp_stream_trigger(struct snd_sof_dev *sdev, - struct hdac_ext_stream *stream, int cmd); + struct hdac_ext_stream *hext_stream, int cmd); irqreturn_t hda_dsp_stream_threaded_handler(int irq, void *context); int hda_dsp_stream_setup_bdl(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab, - struct hdac_stream *stream); + struct hdac_stream *hstream); bool hda_dsp_check_ipc_irq(struct snd_sof_dev *sdev); bool hda_dsp_check_stream_irq(struct snd_sof_dev *sdev); +snd_pcm_uframes_t hda_dsp_stream_get_position(struct hdac_stream *hstream, + int direction, bool can_sleep); + struct hdac_ext_stream * hda_dsp_stream_get(struct snd_sof_dev *sdev, int direction, u32 flags); int hda_dsp_stream_put(struct snd_sof_dev *sdev, int direction, int stream_tag); int hda_dsp_stream_spib_config(struct snd_sof_dev *sdev, - struct hdac_ext_stream *stream, + struct hdac_ext_stream *hext_stream, int enable, u32 size); int hda_ipc_msg_data(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, void *p, size_t sz); -int hda_ipc_pcm_params(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream, - const struct sof_ipc_pcm_params_reply *reply); - -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) -/* - * Probe Compress Operations. - */ -int hda_probe_compr_assign(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_soc_dai *dai); -int hda_probe_compr_free(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_soc_dai *dai); -int hda_probe_compr_set_params(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_compr_params *params, - struct snd_soc_dai *dai); -int hda_probe_compr_trigger(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, int cmd, - struct snd_soc_dai *dai); -int hda_probe_compr_pointer(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_compr_tstamp *tstamp, - struct snd_soc_dai *dai); -#endif +int hda_set_stream_data_offset(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + size_t posn_offset); /* * DSP IPC Operations. @@ -610,12 +667,18 @@ int hda_dsp_ipc_cmd_done(struct snd_sof_dev *sdev, int dir); */ 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_cl_copy_fw(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream); +struct hdac_ext_stream *hda_cl_stream_prepare(struct snd_sof_dev *sdev, unsigned int format, + unsigned int size, struct snd_dma_buffer *dmab, + int direction); +int hda_cl_cleanup(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab, + struct hdac_ext_stream *hext_stream); +int cl_dsp_init(struct snd_sof_dev *sdev, int stream_tag, bool imr_boot); +#define HDA_CL_STREAM_FORMAT 0x40 /* pre and post fw run ops */ int hda_dsp_pre_fw_run(struct snd_sof_dev *sdev); int hda_dsp_post_fw_run(struct snd_sof_dev *sdev); -int hda_dsp_post_fw_run_icl(struct snd_sof_dev *sdev); -int hda_dsp_core_stall_icl(struct snd_sof_dev *sdev, unsigned int core_mask); /* parse platform specific ext manifest ops */ int hda_dsp_ext_man_get_cavs_config_data(struct snd_sof_dev *sdev, @@ -668,7 +731,8 @@ static inline int hda_codec_i915_exit(struct snd_sof_dev *sdev) { return 0; } /* * Trace Control. */ -int hda_dsp_trace_init(struct snd_sof_dev *sdev, u32 *stream_tag); +int hda_dsp_trace_init(struct snd_sof_dev *sdev, struct snd_dma_buffer *dmab, + struct sof_ipc_dma_trace_params_ext *dtrace_params); int hda_dsp_trace_release(struct snd_sof_dev *sdev); int hda_dsp_trace_trigger(struct snd_sof_dev *sdev, int cmd); @@ -706,28 +770,59 @@ static inline bool hda_common_check_sdw_irq(struct snd_sof_dev *sdev) /* common dai driver */ extern struct snd_soc_dai_driver skl_dai[]; +int hda_dsp_dais_suspend(struct snd_sof_dev *sdev); /* * Platform Specific HW abstraction Ops. */ -extern const struct snd_sof_dsp_ops sof_apl_ops; -extern const struct snd_sof_dsp_ops sof_cnl_ops; -extern const struct snd_sof_dsp_ops sof_tgl_ops; -extern const struct snd_sof_dsp_ops sof_icl_ops; +extern struct snd_sof_dsp_ops sof_hda_common_ops; + +extern struct snd_sof_dsp_ops sof_skl_ops; +int sof_skl_ops_init(struct snd_sof_dev *sdev); +extern struct snd_sof_dsp_ops sof_apl_ops; +int sof_apl_ops_init(struct snd_sof_dev *sdev); +extern struct snd_sof_dsp_ops sof_cnl_ops; +int sof_cnl_ops_init(struct snd_sof_dev *sdev); +extern struct snd_sof_dsp_ops sof_tgl_ops; +int sof_tgl_ops_init(struct snd_sof_dev *sdev); +extern struct snd_sof_dsp_ops sof_icl_ops; +int sof_icl_ops_init(struct snd_sof_dev *sdev); +extern struct snd_sof_dsp_ops sof_mtl_ops; +int sof_mtl_ops_init(struct snd_sof_dev *sdev); +extern const struct sof_intel_dsp_desc skl_chip_info; extern const struct sof_intel_dsp_desc apl_chip_info; extern const struct sof_intel_dsp_desc cnl_chip_info; -extern const struct sof_intel_dsp_desc skl_chip_info; extern const struct sof_intel_dsp_desc icl_chip_info; extern const struct sof_intel_dsp_desc tgl_chip_info; extern const struct sof_intel_dsp_desc tglh_chip_info; extern const struct sof_intel_dsp_desc ehl_chip_info; extern const struct sof_intel_dsp_desc jsl_chip_info; extern const struct sof_intel_dsp_desc adls_chip_info; +extern const struct sof_intel_dsp_desc mtl_chip_info; + +/* Probes support */ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) +int hda_probes_register(struct snd_sof_dev *sdev); +void hda_probes_unregister(struct snd_sof_dev *sdev); +#else +static inline int hda_probes_register(struct snd_sof_dev *sdev) +{ + return 0; +} + +static inline void hda_probes_unregister(struct snd_sof_dev *sdev) +{ +} +#endif /* CONFIG_SND_SOC_SOF_HDA_PROBES */ + +/* SOF client registration for HDA platforms */ +int hda_register_clients(struct snd_sof_dev *sdev); +void hda_unregister_clients(struct snd_sof_dev *sdev); /* machine driver select */ -void hda_machine_select(struct snd_sof_dev *sdev); -void hda_set_mach_params(const struct snd_soc_acpi_mach *mach, +struct snd_soc_acpi_mach *hda_machine_select(struct snd_sof_dev *sdev); +void hda_set_mach_params(struct snd_soc_acpi_mach *mach, struct snd_sof_dev *sdev); /* PCI driver selection and probe */ @@ -735,7 +830,30 @@ int hda_pci_intel_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) struct snd_sof_dai; struct sof_ipc_dai_config; -int hda_ctrl_dai_widget_setup(struct snd_soc_dapm_widget *w); -int hda_ctrl_dai_widget_free(struct snd_soc_dapm_widget *w); +int hda_ctrl_dai_widget_setup(struct snd_soc_dapm_widget *w, unsigned int quirk_flags, + struct snd_sof_dai_config_data *data); +int hda_ctrl_dai_widget_free(struct snd_soc_dapm_widget *w, unsigned int quirk_flags, + struct snd_sof_dai_config_data *data); + +#define SOF_HDA_POSITION_QUIRK_USE_SKYLAKE_LEGACY (0) /* previous implementation */ +#define SOF_HDA_POSITION_QUIRK_USE_DPIB_REGISTERS (1) /* recommended if VC0 only */ +#define SOF_HDA_POSITION_QUIRK_USE_DPIB_DDR_UPDATE (2) /* recommended with VC0 or VC1 */ + +extern int sof_hda_position_quirk; + +void hda_set_dai_drv_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *ops); +void hda_ops_free(struct snd_sof_dev *sdev); + +/* SKL/KBL */ +int hda_dsp_cl_boot_firmware_skl(struct snd_sof_dev *sdev); +int hda_dsp_core_stall_reset(struct snd_sof_dev *sdev, unsigned int core_mask); + +/* IPC4 */ +irqreturn_t cnl_ipc4_irq_thread(int irq, void *context); +int cnl_ipc4_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg); +irqreturn_t hda_dsp_ipc4_irq_thread(int irq, void *context); +int hda_dsp_ipc4_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg); +void hda_ipc4_dump(struct snd_sof_dev *sdev); +extern struct sdw_intel_ops sdw_callback; #endif diff --git a/sound/soc/sof/intel/icl.c b/sound/soc/sof/intel/icl.c index 0b2cc331d55b..6d5877108a3d 100644 --- a/sound/soc/sof/intel/icl.c +++ b/sound/soc/sof/intel/icl.c @@ -13,126 +13,153 @@ #include <linux/kconfig.h> #include <linux/export.h> #include <linux/bits.h> +#include "../ipc4-priv.h" #include "../ops.h" #include "hda.h" #include "hda-ipc.h" #include "../sof-audio.h" +#define ICL_DSP_HPRO_CORE_ID 3 + static const struct snd_sof_debugfs_map icl_dsp_debugfs[] = { {"hda", HDA_DSP_HDA_BAR, 0, 0x4000, SOF_DEBUGFS_ACCESS_ALWAYS}, {"pp", HDA_DSP_PP_BAR, 0, 0x1000, SOF_DEBUGFS_ACCESS_ALWAYS}, {"dsp", HDA_DSP_BAR, 0, 0x10000, SOF_DEBUGFS_ACCESS_ALWAYS}, }; +static int icl_dsp_core_stall(struct snd_sof_dev *sdev, unsigned int core_mask) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + const struct sof_intel_dsp_desc *chip = hda->desc; + + /* make sure core_mask in host managed cores */ + core_mask &= chip->host_managed_cores_mask; + if (!core_mask) { + dev_err(sdev->dev, "error: core_mask is not in host managed cores\n"); + return -EINVAL; + } + + /* stall core */ + snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPCS, + HDA_DSP_ADSPCS_CSTALL_MASK(core_mask), + HDA_DSP_ADSPCS_CSTALL_MASK(core_mask)); + + return 0; +} + +/* + * post fw run operation for ICL. + * Core 3 will be powered up and in stall when HPRO is enabled + */ +static int icl_dsp_post_fw_run(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + int ret; + + if (sdev->first_boot) { + struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata; + + ret = hda_sdw_startup(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: could not startup SoundWire links\n"); + return ret; + } + + /* Check if IMR boot is usable */ + if (!sof_debug_check_flag(SOF_DBG_IGNORE_D3_PERSISTENT) && + sdev->fw_ready.flags & SOF_IPC_INFO_D3_PERSISTENT) + hdev->imrboot_supported = true; + } + + hda_sdw_int_enable(sdev, true); + + /* + * The recommended HW programming sequence for ICL is to + * power up core 3 and keep it in stall if HPRO is enabled. + */ + if (!hda->clk_config_lpro) { + ret = hda_dsp_enable_core(sdev, BIT(ICL_DSP_HPRO_CORE_ID)); + if (ret < 0) { + dev_err(sdev->dev, "error: dsp core power up failed on core %d\n", + ICL_DSP_HPRO_CORE_ID); + return ret; + } + + sdev->enabled_cores_mask |= BIT(ICL_DSP_HPRO_CORE_ID); + sdev->dsp_core_ref_count[ICL_DSP_HPRO_CORE_ID]++; + + snd_sof_dsp_stall(sdev, BIT(ICL_DSP_HPRO_CORE_ID)); + } + + /* re-enable clock gating and power gating */ + return hda_dsp_ctrl_clock_power_gating(sdev, true); +} + /* Icelake ops */ -const struct snd_sof_dsp_ops sof_icl_ops = { +struct snd_sof_dsp_ops sof_icl_ops; +EXPORT_SYMBOL_NS(sof_icl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); + +int sof_icl_ops_init(struct snd_sof_dev *sdev) +{ + /* common defaults */ + memcpy(&sof_icl_ops, &sof_hda_common_ops, sizeof(struct snd_sof_dsp_ops)); + /* probe/remove/shutdown */ - .probe = hda_dsp_probe, - .remove = hda_dsp_remove, - .shutdown = hda_dsp_shutdown, - - /* 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, - - /* Mailbox IO */ - .mailbox_read = sof_mailbox_read, - .mailbox_write = sof_mailbox_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, + sof_icl_ops.shutdown = hda_dsp_shutdown; + + if (sdev->pdata->ipc_type == SOF_IPC) { + /* doorbell */ + sof_icl_ops.irq_thread = cnl_ipc_irq_thread; + + /* ipc */ + sof_icl_ops.send_msg = cnl_ipc_send_msg; + + /* debug */ + sof_icl_ops.ipc_dump = cnl_ipc_dump; + } + + if (sdev->pdata->ipc_type == SOF_INTEL_IPC4) { + struct sof_ipc4_fw_data *ipc4_data; + + sdev->private = devm_kzalloc(sdev->dev, sizeof(*ipc4_data), GFP_KERNEL); + if (!sdev->private) + return -ENOMEM; + + ipc4_data = sdev->private; + ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET; + + ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_2; + + /* doorbell */ + sof_icl_ops.irq_thread = cnl_ipc4_irq_thread; + + /* ipc */ + sof_icl_ops.send_msg = cnl_ipc4_send_msg; + + /* debug */ + sof_icl_ops.ipc_dump = cnl_ipc4_dump; + } /* debug */ - .debug_map = icl_dsp_debugfs, - .debug_map_count = ARRAY_SIZE(icl_dsp_debugfs), - .dbg_dump = hda_dsp_dump, - .ipc_dump = cnl_ipc_dump, - .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem, - - /* stream callbacks */ - .pcm_open = hda_dsp_pcm_open, - .pcm_close = hda_dsp_pcm_close, - .pcm_hw_params = hda_dsp_pcm_hw_params, - .pcm_hw_free = hda_dsp_stream_hw_free, - .pcm_trigger = hda_dsp_pcm_trigger, - .pcm_pointer = hda_dsp_pcm_pointer, - -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) - /* probe callbacks */ - .probe_assign = hda_probe_compr_assign, - .probe_free = hda_probe_compr_free, - .probe_set_params = hda_probe_compr_set_params, - .probe_trigger = hda_probe_compr_trigger, - .probe_pointer = hda_probe_compr_pointer, -#endif - - /* firmware loading */ - .load_firmware = snd_sof_load_firmware_raw, + sof_icl_ops.debug_map = icl_dsp_debugfs; + sof_icl_ops.debug_map_count = ARRAY_SIZE(icl_dsp_debugfs); /* pre/post fw run */ - .pre_fw_run = hda_dsp_pre_fw_run, - .post_fw_run = hda_dsp_post_fw_run_icl, + sof_icl_ops.post_fw_run = icl_dsp_post_fw_run; + + /* firmware run */ + sof_icl_ops.run = hda_dsp_cl_boot_firmware_iccmax; + sof_icl_ops.stall = icl_dsp_core_stall; - /* parse platform specific extended manifest */ - .parse_platform_ext_manifest = hda_dsp_ext_man_get_cavs_config_data, + /* dsp core get/put */ + sof_icl_ops.core_get = hda_dsp_core_get; - /* dsp core power up/down */ - .core_power_up = hda_dsp_enable_core, - .core_power_down = hda_dsp_core_reset_power_down, + /* set DAI driver ops */ + hda_set_dai_drv_ops(sdev, &sof_icl_ops); - /* firmware run */ - .run = hda_dsp_cl_boot_firmware_iccmax, - .stall = hda_dsp_core_stall_icl, - - /* 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, - - .dsp_arch_ops = &sof_xtensa_arch_ops, + return 0; }; -EXPORT_SYMBOL_NS(sof_icl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); +EXPORT_SYMBOL_NS(sof_icl_ops_init, SND_SOC_SOF_INTEL_HDA_COMMON); const struct sof_intel_dsp_desc icl_chip_info = { /* Icelake */ @@ -144,11 +171,17 @@ const struct sof_intel_dsp_desc icl_chip_info = { .ipc_ack = CNL_DSP_REG_HIPCIDA, .ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE, .ipc_ctl = CNL_DSP_REG_HIPCCTL, + .rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS, .rom_init_timeout = 300, .ssp_count = ICL_SSP_COUNT, .ssp_base_offset = CNL_SSP_BASE_OFFSET, .sdw_shim_base = SDW_SHIM_BASE, .sdw_alh_base = SDW_ALH_BASE, .check_sdw_irq = hda_common_check_sdw_irq, + .check_ipc_irq = hda_dsp_check_ipc_irq, + .cl_init = cl_dsp_init, + .power_down_dsp = hda_power_down_dsp, + .disable_interrupts = hda_dsp_disable_interrupts, + .hw_ip_version = SOF_INTEL_CAVS_2_0, }; EXPORT_SYMBOL_NS(icl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON); diff --git a/sound/soc/sof/intel/mtl.c b/sound/soc/sof/intel/mtl.c new file mode 100644 index 000000000000..10298532816f --- /dev/null +++ b/sound/soc/sof/intel/mtl.c @@ -0,0 +1,673 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// Authors: Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// + +/* + * Hardware interface for audio DSP on Meteorlake. + */ + +#include <linux/firmware.h> +#include <sound/sof/ipc4/header.h> +#include <trace/events/sof_intel.h> +#include "../ipc4-priv.h" +#include "../ops.h" +#include "hda.h" +#include "hda-ipc.h" +#include "../sof-audio.h" +#include "mtl.h" + +static const struct snd_sof_debugfs_map mtl_dsp_debugfs[] = { + {"hda", HDA_DSP_HDA_BAR, 0, 0x4000, SOF_DEBUGFS_ACCESS_ALWAYS}, + {"pp", HDA_DSP_PP_BAR, 0, 0x1000, SOF_DEBUGFS_ACCESS_ALWAYS}, + {"dsp", HDA_DSP_BAR, 0, 0x10000, SOF_DEBUGFS_ACCESS_ALWAYS}, +}; + +static void mtl_ipc_host_done(struct snd_sof_dev *sdev) +{ + /* + * clear busy interrupt to tell dsp controller this interrupt has been accepted, + * not trigger it again + */ + snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXTDR, + MTL_DSP_REG_HFIPCXTDR_BUSY, MTL_DSP_REG_HFIPCXTDR_BUSY); + /* + * clear busy bit to ack dsp the msg has been processed and send reply msg to dsp + */ + snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXTDA, + MTL_DSP_REG_HFIPCXTDA_BUSY, 0); +} + +static void mtl_ipc_dsp_done(struct snd_sof_dev *sdev) +{ + /* + * set DONE bit - tell DSP we have received the reply msg from DSP, and processed it, + * don't send more reply to host + */ + snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXIDA, + MTL_DSP_REG_HFIPCXIDA_DONE, MTL_DSP_REG_HFIPCXIDA_DONE); + + /* unmask Done interrupt */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXCTL, + MTL_DSP_REG_HFIPCXCTL_DONE, MTL_DSP_REG_HFIPCXCTL_DONE); +} + +/* Check if an IPC IRQ occurred */ +static bool mtl_dsp_check_ipc_irq(struct snd_sof_dev *sdev) +{ + u32 irq_status; + u32 hfintipptr; + + /* read Interrupt IP Pointer */ + hfintipptr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFINTIPPTR) & MTL_HFINTIPPTR_PTR_MASK; + irq_status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, hfintipptr + MTL_DSP_IRQSTS); + + trace_sof_intel_hda_irq_ipc_check(sdev, irq_status); + + if (irq_status != U32_MAX && (irq_status & MTL_DSP_IRQSTS_IPC)) + return true; + + return false; +} + +/* Check if an SDW IRQ occurred */ +static bool mtl_dsp_check_sdw_irq(struct snd_sof_dev *sdev) +{ + u32 irq_status; + u32 hfintipptr; + + /* read Interrupt IP Pointer */ + hfintipptr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFINTIPPTR) & MTL_HFINTIPPTR_PTR_MASK; + irq_status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, hfintipptr + MTL_DSP_IRQSTS); + + if (irq_status != U32_MAX && (irq_status & MTL_DSP_IRQSTS_SDW)) + return true; + + return false; +} + +static int mtl_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg) +{ + struct sof_ipc4_msg *msg_data = msg->msg_data; + + /* send the message via mailbox */ + if (msg_data->data_size) + sof_mailbox_write(sdev, sdev->host_box.offset, msg_data->data_ptr, + msg_data->data_size); + + snd_sof_dsp_write(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXIDDY, + msg_data->extension); + snd_sof_dsp_write(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXIDR, + msg_data->primary | MTL_DSP_REG_HFIPCXIDR_BUSY); + + return 0; +} + +static void mtl_enable_ipc_interrupts(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + const struct sof_intel_dsp_desc *chip = hda->desc; + + /* enable IPC DONE and BUSY interrupts */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, chip->ipc_ctl, + MTL_DSP_REG_HFIPCXCTL_BUSY | MTL_DSP_REG_HFIPCXCTL_DONE, + MTL_DSP_REG_HFIPCXCTL_BUSY | MTL_DSP_REG_HFIPCXCTL_DONE); +} + +static void mtl_disable_ipc_interrupts(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + const struct sof_intel_dsp_desc *chip = hda->desc; + + /* disable IPC DONE and BUSY interrupts */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, chip->ipc_ctl, + MTL_DSP_REG_HFIPCXCTL_BUSY | MTL_DSP_REG_HFIPCXCTL_DONE, 0); +} + +static int mtl_enable_interrupts(struct snd_sof_dev *sdev) +{ + u32 hfintipptr; + u32 irqinten; + u32 host_ipc; + u32 hipcie; + int ret; + + /* read Interrupt IP Pointer */ + hfintipptr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFINTIPPTR) & MTL_HFINTIPPTR_PTR_MASK; + + /* Enable Host IPC and SOUNDWIRE */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, hfintipptr, + MTL_IRQ_INTEN_L_HOST_IPC_MASK | MTL_IRQ_INTEN_L_SOUNDWIRE_MASK, + MTL_IRQ_INTEN_L_HOST_IPC_MASK | MTL_IRQ_INTEN_L_SOUNDWIRE_MASK); + + /* check if operation was successful */ + host_ipc = MTL_IRQ_INTEN_L_HOST_IPC_MASK | MTL_IRQ_INTEN_L_SOUNDWIRE_MASK; + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, hfintipptr, irqinten, + (irqinten & host_ipc) == host_ipc, + HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_RESET_TIMEOUT_US); + if (ret < 0) { + dev_err(sdev->dev, "failed to enable Host IPC and/or SOUNDWIRE\n"); + return ret; + } + + /* Set Host IPC interrupt enable */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfHIPCIE, + MTL_DSP_REG_HfHIPCIE_IE_MASK, MTL_DSP_REG_HfHIPCIE_IE_MASK); + + /* check if operation was successful */ + host_ipc = MTL_DSP_REG_HfHIPCIE_IE_MASK; + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfHIPCIE, hipcie, + (hipcie & host_ipc) == host_ipc, + HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_RESET_TIMEOUT_US); + if (ret < 0) { + dev_err(sdev->dev, "failed to set Host IPC interrupt enable\n"); + return ret; + } + + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfSNDWIE, + MTL_DSP_REG_HfSNDWIE_IE_MASK, MTL_DSP_REG_HfSNDWIE_IE_MASK); + host_ipc = MTL_DSP_REG_HfSNDWIE_IE_MASK; + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfSNDWIE, hipcie, + (hipcie & host_ipc) == host_ipc, + HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_RESET_TIMEOUT_US); + if (ret < 0) + dev_err(sdev->dev, "failed to set SoundWire IPC interrupt enable\n"); + + return ret; +} + +static int mtl_disable_interrupts(struct snd_sof_dev *sdev) +{ + u32 hfintipptr; + u32 irqinten; + u32 host_ipc; + u32 hipcie; + int ret1; + int ret; + + /* read Interrupt IP Pointer */ + hfintipptr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFINTIPPTR) & MTL_HFINTIPPTR_PTR_MASK; + + /* Disable Host IPC and SOUNDWIRE */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, hfintipptr, + MTL_IRQ_INTEN_L_HOST_IPC_MASK | MTL_IRQ_INTEN_L_SOUNDWIRE_MASK, 0); + + /* check if operation was successful */ + host_ipc = MTL_IRQ_INTEN_L_HOST_IPC_MASK | MTL_IRQ_INTEN_L_SOUNDWIRE_MASK; + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, hfintipptr, irqinten, + (irqinten & host_ipc) == 0, + HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_RESET_TIMEOUT_US); + /* Continue to disable other interrupts when error happens */ + if (ret < 0) + dev_err(sdev->dev, "failed to disable Host IPC and SoundWire\n"); + + /* Set Host IPC interrupt disable */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfHIPCIE, + MTL_DSP_REG_HfHIPCIE_IE_MASK, 0); + + /* check if operation was successful */ + host_ipc = MTL_DSP_REG_HfHIPCIE_IE_MASK; + ret1 = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfHIPCIE, hipcie, + (hipcie & host_ipc) == 0, + HDA_DSP_REG_POLL_INTERVAL_US, + HDA_DSP_RESET_TIMEOUT_US); + if (ret1 < 0) { + dev_err(sdev->dev, "failed to set Host IPC interrupt disable\n"); + if (!ret) + ret = ret1; + } + + /* Set SoundWire IPC interrupt disable */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfSNDWIE, + MTL_DSP_REG_HfSNDWIE_IE_MASK, 0); + host_ipc = MTL_DSP_REG_HfSNDWIE_IE_MASK; + ret1 = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfSNDWIE, hipcie, + (hipcie & host_ipc) == 0, + HDA_DSP_REG_POLL_INTERVAL_US, + HDA_DSP_RESET_TIMEOUT_US); + if (ret1 < 0) { + dev_err(sdev->dev, "failed to set SoundWire IPC interrupt disable\n"); + if (!ret) + ret = ret1; + } + + return ret; +} + +/* pre fw run operations */ +static int mtl_dsp_pre_fw_run(struct snd_sof_dev *sdev) +{ + u32 dsphfpwrsts; + u32 dsphfdsscs; + u32 cpa; + u32 pgs; + int ret; + + /* Set the DSP subsystem power on */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_HFDSSCS, + MTL_HFDSSCS_SPA_MASK, MTL_HFDSSCS_SPA_MASK); + + /* Wait for unstable CPA read (1 then 0 then 1) just after setting SPA bit */ + usleep_range(1000, 1010); + + /* poll with timeout to check if operation successful */ + cpa = MTL_HFDSSCS_CPA_MASK; + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_HFDSSCS, dsphfdsscs, + (dsphfdsscs & cpa) == cpa, HDA_DSP_REG_POLL_INTERVAL_US, + HDA_DSP_RESET_TIMEOUT_US); + if (ret < 0) { + dev_err(sdev->dev, "failed to enable DSP subsystem\n"); + return ret; + } + + /* Power up gated-DSP-0 domain in order to access the DSP shim register block. */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_HFPWRCTL, + MTL_HFPWRCTL_WPDSPHPXPG, MTL_HFPWRCTL_WPDSPHPXPG); + + usleep_range(1000, 1010); + + /* poll with timeout to check if operation successful */ + pgs = MTL_HFPWRSTS_DSPHPXPGS_MASK; + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_HFPWRSTS, dsphfpwrsts, + (dsphfpwrsts & pgs) == pgs, + HDA_DSP_REG_POLL_INTERVAL_US, + HDA_DSP_RESET_TIMEOUT_US); + if (ret < 0) + dev_err(sdev->dev, "failed to power up gated DSP domain\n"); + + /* make sure SoundWire is not power-gated */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, MTL_HFPWRCTL, + MTL_HfPWRCTL_WPIOXPG(1), MTL_HfPWRCTL_WPIOXPG(1)); + return ret; +} + +static int mtl_dsp_post_fw_run(struct snd_sof_dev *sdev) +{ + int ret; + + if (sdev->first_boot) { + struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata; + + ret = hda_sdw_startup(sdev); + if (ret < 0) { + dev_err(sdev->dev, "could not startup SoundWire links\n"); + return ret; + } + + /* Check if IMR boot is usable */ + if (!sof_debug_check_flag(SOF_DBG_IGNORE_D3_PERSISTENT)) + hdev->imrboot_supported = true; + } + + hda_sdw_int_enable(sdev, true); + return 0; +} + +static void mtl_dsp_dump(struct snd_sof_dev *sdev, u32 flags) +{ + char *level = (flags & SOF_DBG_DUMP_OPTIONAL) ? KERN_DEBUG : KERN_ERR; + u32 romdbgsts; + u32 romdbgerr; + u32 fwsts; + u32 fwlec; + + fwsts = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_ROM_STS); + fwlec = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_ROM_ERROR); + romdbgsts = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFFLGPXQWY); + romdbgerr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFFLGPXQWY_ERROR); + + dev_err(sdev->dev, "ROM status: %#x, ROM error: %#x\n", fwsts, fwlec); + dev_err(sdev->dev, "ROM debug status: %#x, ROM debug error: %#x\n", romdbgsts, + romdbgerr); + romdbgsts = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFFLGPXQWY + 0x8 * 3); + dev_printk(level, sdev->dev, "ROM feature bit%s enabled\n", + romdbgsts & BIT(24) ? "" : " not"); +} + +static bool mtl_dsp_primary_core_is_enabled(struct snd_sof_dev *sdev) +{ + int val; + + val = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP2CXCTL_PRIMARY_CORE); + if (val != U32_MAX && val & MTL_DSP2CXCTL_PRIMARY_CORE_CPA_MASK) + return true; + + return false; +} + +static int mtl_dsp_core_power_up(struct snd_sof_dev *sdev, int core) +{ + unsigned int cpa; + u32 dspcxctl; + int ret; + + /* Only the primary core can be powered up by the host */ + if (core != SOF_DSP_PRIMARY_CORE || mtl_dsp_primary_core_is_enabled(sdev)) + return 0; + + /* Program the owner of the IP & shim registers (10: Host CPU) */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP2CXCTL_PRIMARY_CORE, + MTL_DSP2CXCTL_PRIMARY_CORE_OSEL, + 0x2 << MTL_DSP2CXCTL_PRIMARY_CORE_OSEL_SHIFT); + + /* enable SPA bit */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP2CXCTL_PRIMARY_CORE, + MTL_DSP2CXCTL_PRIMARY_CORE_SPA_MASK, + MTL_DSP2CXCTL_PRIMARY_CORE_SPA_MASK); + + /* Wait for unstable CPA read (1 then 0 then 1) just after setting SPA bit */ + usleep_range(1000, 1010); + + /* poll with timeout to check if operation successful */ + cpa = MTL_DSP2CXCTL_PRIMARY_CORE_CPA_MASK; + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_DSP2CXCTL_PRIMARY_CORE, dspcxctl, + (dspcxctl & cpa) == cpa, HDA_DSP_REG_POLL_INTERVAL_US, + HDA_DSP_RESET_TIMEOUT_US); + if (ret < 0) + dev_err(sdev->dev, "%s: timeout on MTL_DSP2CXCTL_PRIMARY_CORE read\n", + __func__); + + return ret; +} + +static int mtl_dsp_core_power_down(struct snd_sof_dev *sdev, int core) +{ + u32 dspcxctl; + int ret; + + /* Only the primary core can be powered down by the host */ + if (core != SOF_DSP_PRIMARY_CORE || !mtl_dsp_primary_core_is_enabled(sdev)) + return 0; + + /* disable SPA bit */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP2CXCTL_PRIMARY_CORE, + MTL_DSP2CXCTL_PRIMARY_CORE_SPA_MASK, 0); + + /* Wait for unstable CPA read (1 then 0 then 1) just after setting SPA bit */ + usleep_range(1000, 1010); + + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_DSP2CXCTL_PRIMARY_CORE, dspcxctl, + !(dspcxctl & MTL_DSP2CXCTL_PRIMARY_CORE_CPA_MASK), + HDA_DSP_REG_POLL_INTERVAL_US, + HDA_DSP_PD_TIMEOUT * USEC_PER_MSEC); + if (ret < 0) + dev_err(sdev->dev, "failed to power down primary core\n"); + + return ret; +} + +static int mtl_power_down_dsp(struct snd_sof_dev *sdev) +{ + u32 dsphfdsscs, cpa; + int ret; + + /* first power down core */ + ret = mtl_dsp_core_power_down(sdev, SOF_DSP_PRIMARY_CORE); + if (ret) { + dev_err(sdev->dev, "mtl dsp power down error, %d\n", ret); + return ret; + } + + /* Set the DSP subsystem power down */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_HFDSSCS, + MTL_HFDSSCS_SPA_MASK, 0); + + /* Wait for unstable CPA read (1 then 0 then 1) just after setting SPA bit */ + usleep_range(1000, 1010); + + /* poll with timeout to check if operation successful */ + cpa = MTL_HFDSSCS_CPA_MASK; + dsphfdsscs = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFDSSCS); + return snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_HFDSSCS, dsphfdsscs, + (dsphfdsscs & cpa) == 0, HDA_DSP_REG_POLL_INTERVAL_US, + HDA_DSP_RESET_TIMEOUT_US); +} + +static int mtl_dsp_cl_init(struct snd_sof_dev *sdev, int stream_tag, bool imr_boot) +{ + struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; + const struct sof_intel_dsp_desc *chip = hda->desc; + unsigned int status; + u32 ipc_hdr; + int ret; + + /* step 1: purge FW request */ + ipc_hdr = chip->ipc_req_mask | HDA_DSP_ROM_IPC_CONTROL; + if (!imr_boot) + ipc_hdr |= HDA_DSP_ROM_IPC_PURGE_FW | ((stream_tag - 1) << 9); + + snd_sof_dsp_write(sdev, HDA_DSP_BAR, chip->ipc_req, ipc_hdr); + + /* step 2: power up primary core */ + ret = mtl_dsp_core_power_up(sdev, SOF_DSP_PRIMARY_CORE); + if (ret < 0) { + if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS) + dev_err(sdev->dev, "dsp core 0/1 power up failed\n"); + goto err; + } + + dev_dbg(sdev->dev, "Primary core power up successful\n"); + + /* step 3: wait for IPC DONE bit from ROM */ + ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, chip->ipc_ack, status, + ((status & chip->ipc_ack_mask) == chip->ipc_ack_mask), + HDA_DSP_REG_POLL_INTERVAL_US, MTL_DSP_PURGE_TIMEOUT_US); + if (ret < 0) { + if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS) + dev_err(sdev->dev, "timeout waiting for purge IPC done\n"); + goto err; + } + + /* set DONE bit to clear the reply IPC message */ + snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, chip->ipc_ack, chip->ipc_ack_mask, + chip->ipc_ack_mask); + + /* step 4: enable interrupts */ + ret = mtl_enable_interrupts(sdev); + if (ret < 0) { + if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS) + dev_err(sdev->dev, "%s: failed to enable interrupts\n", __func__); + goto err; + } + + mtl_enable_ipc_interrupts(sdev); + + /* + * ACE workaround: don't wait for ROM INIT. + * The platform cannot catch ROM_INIT_DONE because of a very short + * timing window. Follow the recommendations and skip this part. + */ + + return 0; + +err: + snd_sof_dsp_dbg_dump(sdev, "MTL DSP init fail", 0); + mtl_dsp_core_power_down(sdev, SOF_DSP_PRIMARY_CORE); + return ret; +} + +static irqreturn_t mtl_ipc_irq_thread(int irq, void *context) +{ + struct sof_ipc4_msg notification_data = {{ 0 }}; + struct snd_sof_dev *sdev = context; + bool ipc_irq = false; + u32 hipcida; + u32 hipctdr; + + hipcida = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXIDA); + + /* reply message from DSP */ + if (hipcida & MTL_DSP_REG_HFIPCXIDA_DONE) { + /* DSP received the message */ + snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXCTL, + MTL_DSP_REG_HFIPCXCTL_DONE, 0); + + mtl_ipc_dsp_done(sdev); + + ipc_irq = true; + } + + hipctdr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXTDR); + if (hipctdr & MTL_DSP_REG_HFIPCXTDR_BUSY) { + /* Message from DSP (reply or notification) */ + u32 extension = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXTDDY); + u32 primary = hipctdr & MTL_DSP_REG_HFIPCXTDR_MSG_MASK; + + /* + * ACE fw sends a new fw ipc message to host to + * notify the status of the last host ipc message + */ + if (primary & SOF_IPC4_MSG_DIR_MASK) { + /* Reply received */ + if (likely(sdev->fw_state == SOF_FW_BOOT_COMPLETE)) { + struct sof_ipc4_msg *data = sdev->ipc->msg.reply_data; + + data->primary = primary; + data->extension = extension; + + spin_lock_irq(&sdev->ipc_lock); + + snd_sof_ipc_get_reply(sdev); + snd_sof_ipc_reply(sdev, data->primary); + + spin_unlock_irq(&sdev->ipc_lock); + } else { + dev_dbg_ratelimited(sdev->dev, + "IPC reply before FW_READY: %#x|%#x\n", + primary, extension); + } + } else { + /* Notification received */ + notification_data.primary = primary; + notification_data.extension = extension; + + sdev->ipc->msg.rx_data = ¬ification_data; + snd_sof_ipc_msgs_rx(sdev); + sdev->ipc->msg.rx_data = NULL; + } + + mtl_ipc_host_done(sdev); + + ipc_irq = true; + } + + if (!ipc_irq) { + /* This interrupt is not shared so no need to return IRQ_NONE. */ + dev_dbg_ratelimited(sdev->dev, "nothing to do in IPC IRQ thread\n"); + } + + return IRQ_HANDLED; +} + +static int mtl_dsp_ipc_get_mailbox_offset(struct snd_sof_dev *sdev) +{ + return MTL_DSP_MBOX_UPLINK_OFFSET; +} + +static int mtl_dsp_ipc_get_window_offset(struct snd_sof_dev *sdev, u32 id) +{ + return MTL_SRAM_WINDOW_OFFSET(id); +} + +static void mtl_ipc_dump(struct snd_sof_dev *sdev) +{ + u32 hipcidr, hipcidd, hipcida, hipctdr, hipctdd, hipctda, hipcctl; + + hipcidr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXIDR); + hipcidd = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXIDDY); + hipcida = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXIDA); + hipctdr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXTDR); + hipctdd = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXTDDY); + hipctda = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXTDA); + hipcctl = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXCTL); + + dev_err(sdev->dev, + "Host IPC initiator: %#x|%#x|%#x, target: %#x|%#x|%#x, ctl: %#x\n", + hipcidr, hipcidd, hipcida, hipctdr, hipctdd, hipctda, hipcctl); +} + +static int mtl_dsp_disable_interrupts(struct snd_sof_dev *sdev) +{ + mtl_disable_ipc_interrupts(sdev); + return mtl_disable_interrupts(sdev); +} + +/* Meteorlake ops */ +struct snd_sof_dsp_ops sof_mtl_ops; +EXPORT_SYMBOL_NS(sof_mtl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); + +int sof_mtl_ops_init(struct snd_sof_dev *sdev) +{ + struct sof_ipc4_fw_data *ipc4_data; + + /* common defaults */ + memcpy(&sof_mtl_ops, &sof_hda_common_ops, sizeof(struct snd_sof_dsp_ops)); + + /* shutdown */ + sof_mtl_ops.shutdown = hda_dsp_shutdown; + + /* doorbell */ + sof_mtl_ops.irq_thread = mtl_ipc_irq_thread; + + /* ipc */ + sof_mtl_ops.send_msg = mtl_ipc_send_msg; + sof_mtl_ops.get_mailbox_offset = mtl_dsp_ipc_get_mailbox_offset; + sof_mtl_ops.get_window_offset = mtl_dsp_ipc_get_window_offset; + + /* debug */ + sof_mtl_ops.debug_map = mtl_dsp_debugfs; + sof_mtl_ops.debug_map_count = ARRAY_SIZE(mtl_dsp_debugfs); + sof_mtl_ops.dbg_dump = mtl_dsp_dump; + sof_mtl_ops.ipc_dump = mtl_ipc_dump; + + /* pre/post fw run */ + sof_mtl_ops.pre_fw_run = mtl_dsp_pre_fw_run; + sof_mtl_ops.post_fw_run = mtl_dsp_post_fw_run; + + /* parse platform specific extended manifest */ + sof_mtl_ops.parse_platform_ext_manifest = NULL; + + /* dsp core get/put */ + /* TODO: add core_get and core_put */ + + sdev->private = devm_kzalloc(sdev->dev, sizeof(struct sof_ipc4_fw_data), GFP_KERNEL); + if (!sdev->private) + return -ENOMEM; + + ipc4_data = sdev->private; + ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET; + + ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_2; + + /* set DAI ops */ + hda_set_dai_drv_ops(sdev, &sof_mtl_ops); + + return 0; +}; +EXPORT_SYMBOL_NS(sof_mtl_ops_init, SND_SOC_SOF_INTEL_HDA_COMMON); + +const struct sof_intel_dsp_desc mtl_chip_info = { + .cores_num = 3, + .init_core_mask = BIT(0), + .host_managed_cores_mask = BIT(0), + .ipc_req = MTL_DSP_REG_HFIPCXIDR, + .ipc_req_mask = MTL_DSP_REG_HFIPCXIDR_BUSY, + .ipc_ack = MTL_DSP_REG_HFIPCXIDA, + .ipc_ack_mask = MTL_DSP_REG_HFIPCXIDA_DONE, + .ipc_ctl = MTL_DSP_REG_HFIPCXCTL, + .rom_status_reg = MTL_DSP_ROM_STS, + .rom_init_timeout = 300, + .ssp_count = MTL_SSP_COUNT, + .ssp_base_offset = CNL_SSP_BASE_OFFSET, + .sdw_shim_base = SDW_SHIM_BASE_ACE, + .sdw_alh_base = SDW_ALH_BASE_ACE, + .check_sdw_irq = mtl_dsp_check_sdw_irq, + .check_ipc_irq = mtl_dsp_check_ipc_irq, + .cl_init = mtl_dsp_cl_init, + .power_down_dsp = mtl_power_down_dsp, + .disable_interrupts = mtl_dsp_disable_interrupts, + .hw_ip_version = SOF_INTEL_ACE_1_0, +}; +EXPORT_SYMBOL_NS(mtl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON); diff --git a/sound/soc/sof/intel/mtl.h b/sound/soc/sof/intel/mtl.h new file mode 100644 index 000000000000..788bf0e3ea87 --- /dev/null +++ b/sound/soc/sof/intel/mtl.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright(c) 2020-2022 Intel Corporation. All rights reserved. + */ + +/* DSP Registers */ +#define MTL_HFDSSCS 0x1000 +#define MTL_HFDSSCS_SPA_MASK BIT(16) +#define MTL_HFDSSCS_CPA_MASK BIT(24) +#define MTL_HFSNDWIE 0x114C +#define MTL_HFPWRCTL 0x1D18 +#define MTL_HfPWRCTL_WPIOXPG(x) BIT((x) + 8) +#define MTL_HFPWRCTL_WPDSPHPXPG BIT(0) +#define MTL_HFPWRSTS 0x1D1C +#define MTL_HFPWRSTS_DSPHPXPGS_MASK BIT(0) +#define MTL_HFINTIPPTR 0x1108 +#define MTL_IRQ_INTEN_L_HOST_IPC_MASK BIT(0) +#define MTL_IRQ_INTEN_L_SOUNDWIRE_MASK BIT(6) +#define MTL_HFINTIPPTR_PTR_MASK GENMASK(20, 0) + +#define MTL_DSP2CXCAP_PRIMARY_CORE 0x178D00 +#define MTL_DSP2CXCTL_PRIMARY_CORE 0x178D04 +#define MTL_DSP2CXCTL_PRIMARY_CORE_SPA_MASK BIT(0) +#define MTL_DSP2CXCTL_PRIMARY_CORE_CPA_MASK BIT(8) +#define MTL_DSP2CXCTL_PRIMARY_CORE_OSEL GENMASK(25, 24) +#define MTL_DSP2CXCTL_PRIMARY_CORE_OSEL_SHIFT 24 + +/* IPC Registers */ +#define MTL_DSP_REG_HFIPCXTDR 0x73200 +#define MTL_DSP_REG_HFIPCXTDR_BUSY BIT(31) +#define MTL_DSP_REG_HFIPCXTDR_MSG_MASK GENMASK(30, 0) +#define MTL_DSP_REG_HFIPCXTDA 0x73204 +#define MTL_DSP_REG_HFIPCXTDA_BUSY BIT(31) +#define MTL_DSP_REG_HFIPCXIDR 0x73210 +#define MTL_DSP_REG_HFIPCXIDR_BUSY BIT(31) +#define MTL_DSP_REG_HFIPCXIDR_MSG_MASK GENMASK(30, 0) +#define MTL_DSP_REG_HFIPCXIDA 0x73214 +#define MTL_DSP_REG_HFIPCXIDA_DONE BIT(31) +#define MTL_DSP_REG_HFIPCXIDA_MSG_MASK GENMASK(30, 0) +#define MTL_DSP_REG_HFIPCXCTL 0x73228 +#define MTL_DSP_REG_HFIPCXCTL_BUSY BIT(0) +#define MTL_DSP_REG_HFIPCXCTL_DONE BIT(1) +#define MTL_DSP_REG_HFIPCXTDDY 0x73300 +#define MTL_DSP_REG_HFIPCXIDDY 0x73380 +#define MTL_DSP_REG_HfHIPCIE 0x1140 +#define MTL_DSP_REG_HfHIPCIE_IE_MASK BIT(0) +#define MTL_DSP_REG_HfSNDWIE 0x114C +#define MTL_DSP_REG_HfSNDWIE_IE_MASK GENMASK(3, 0) + +#define MTL_DSP_IRQSTS 0x20 +#define MTL_DSP_IRQSTS_IPC BIT(0) +#define MTL_DSP_IRQSTS_SDW BIT(6) + +#define MTL_DSP_PURGE_TIMEOUT_US 20000000 /* 20s */ +#define MTL_DSP_REG_POLL_INTERVAL_US 10 /* 10 us */ + +/* Memory windows */ +#define MTL_SRAM_WINDOW_OFFSET(x) (0x180000 + 0x8000 * (x)) + +#define MTL_DSP_MBOX_UPLINK_OFFSET (MTL_SRAM_WINDOW_OFFSET(0) + 0x1000) +#define MTL_DSP_MBOX_UPLINK_SIZE 0x1000 +#define MTL_DSP_MBOX_DOWNLINK_OFFSET MTL_SRAM_WINDOW_OFFSET(1) +#define MTL_DSP_MBOX_DOWNLINK_SIZE 0x1000 + +/* FW registers */ +#define MTL_DSP_ROM_STS MTL_SRAM_WINDOW_OFFSET(0) /* ROM status */ +#define MTL_DSP_ROM_ERROR (MTL_SRAM_WINDOW_OFFSET(0) + 0x4) /* ROM error code */ + +#define MTL_DSP_REG_HFFLGPXQWY 0x163200 /* ROM debug status */ +#define MTL_DSP_REG_HFFLGPXQWY_ERROR 0x163204 /* ROM debug error code */ +#define MTL_DSP_REG_HfIMRIS1 0x162088 +#define MTL_DSP_REG_HfIMRIS1_IU_MASK BIT(0) + diff --git a/sound/soc/sof/intel/pci-apl.c b/sound/soc/sof/intel/pci-apl.c index a023b3cc0af4..998e219011f0 100644 --- a/sound/soc/sof/intel/pci-apl.c +++ b/sound/soc/sof/intel/pci-apl.c @@ -27,11 +27,24 @@ static const struct sof_dev_desc bxt_desc = { .resindex_imr_base = -1, .irqindex_host_ipc = -1, .chip_info = &apl_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-apl.ri", + .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + [SOF_INTEL_IPC4] = "intel/avs/apl", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-apl.ri", + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, .nocodec_tplg_filename = "sof-apl-nocodec.tplg", .ops = &sof_apl_ops, + .ops_init = sof_apl_ops_init, + .ops_free = hda_ops_free, }; static const struct sof_dev_desc glk_desc = { @@ -42,11 +55,23 @@ static const struct sof_dev_desc glk_desc = { .resindex_imr_base = -1, .irqindex_host_ipc = -1, .chip_info = &apl_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-glk.ri", + .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + [SOF_INTEL_IPC4] = "intel/avs/glk", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-glk.ri", + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, .nocodec_tplg_filename = "sof-glk-nocodec.tplg", .ops = &sof_apl_ops, + .ops_init = sof_apl_ops_init, }; /* PCI IDs */ diff --git a/sound/soc/sof/intel/pci-cnl.c b/sound/soc/sof/intel/pci-cnl.c index 40cf1cd00042..c797356f7028 100644 --- a/sound/soc/sof/intel/pci-cnl.c +++ b/sound/soc/sof/intel/pci-cnl.c @@ -28,11 +28,23 @@ static const struct sof_dev_desc cnl_desc = { .resindex_imr_base = -1, .irqindex_host_ipc = -1, .chip_info = &cnl_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-cnl.ri", + .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + [SOF_INTEL_IPC4] = "intel/avs/cnl", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-cnl.ri", + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, .nocodec_tplg_filename = "sof-cnl-nocodec.tplg", .ops = &sof_cnl_ops, + .ops_init = sof_cnl_ops_init, }; static const struct sof_dev_desc cfl_desc = { @@ -44,11 +56,24 @@ static const struct sof_dev_desc cfl_desc = { .resindex_imr_base = -1, .irqindex_host_ipc = -1, .chip_info = &cnl_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-cfl.ri", + .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + [SOF_INTEL_IPC4] = "intel/avs/cnl", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-cfl.ri", + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, .nocodec_tplg_filename = "sof-cnl-nocodec.tplg", .ops = &sof_cnl_ops, + .ops_init = sof_cnl_ops_init, + .ops_free = hda_ops_free, }; static const struct sof_dev_desc cml_desc = { @@ -60,11 +85,23 @@ static const struct sof_dev_desc cml_desc = { .resindex_imr_base = -1, .irqindex_host_ipc = -1, .chip_info = &cnl_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-cml.ri", + .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + [SOF_INTEL_IPC4] = "intel/avs/cnl", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-cml.ri", + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, .nocodec_tplg_filename = "sof-cnl-nocodec.tplg", .ops = &sof_cnl_ops, + .ops_init = sof_cnl_ops_init, }; /* PCI IDs */ diff --git a/sound/soc/sof/intel/pci-icl.c b/sound/soc/sof/intel/pci-icl.c index 39c84121b313..48f24f8ace26 100644 --- a/sound/soc/sof/intel/pci-icl.c +++ b/sound/soc/sof/intel/pci-icl.c @@ -28,11 +28,24 @@ static const struct sof_dev_desc icl_desc = { .resindex_imr_base = -1, .irqindex_host_ipc = -1, .chip_info = &icl_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-icl.ri", + .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + [SOF_INTEL_IPC4] = "intel/avs/icl", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-icl.ri", + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, .nocodec_tplg_filename = "sof-icl-nocodec.tplg", .ops = &sof_icl_ops, + .ops_init = sof_icl_ops_init, + .ops_free = hda_ops_free, }; static const struct sof_dev_desc jsl_desc = { @@ -43,11 +56,23 @@ static const struct sof_dev_desc jsl_desc = { .resindex_imr_base = -1, .irqindex_host_ipc = -1, .chip_info = &jsl_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-jsl.ri", + .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + [SOF_INTEL_IPC4] = "intel/avs/jsl", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-jsl.ri", + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, .nocodec_tplg_filename = "sof-jsl-nocodec.tplg", .ops = &sof_cnl_ops, + .ops_init = sof_cnl_ops_init, }; /* PCI IDs */ diff --git a/sound/soc/sof/intel/pci-mtl.c b/sound/soc/sof/intel/pci-mtl.c new file mode 100644 index 000000000000..9f39da984e9f --- /dev/null +++ b/sound/soc/sof/intel/pci-mtl.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2018-2022 Intel Corporation. All rights reserved. +// +// Author: Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// + +#include <linux/module.h> +#include <linux/pci.h> +#include <sound/soc-acpi.h> +#include <sound/soc-acpi-intel-match.h> +#include <sound/sof.h> +#include "../ops.h" +#include "../sof-pci-dev.h" + +/* platform specific devices */ +#include "hda.h" +#include "mtl.h" + +static const struct sof_dev_desc mtl_desc = { + .use_acpi_target_states = true, + .machines = snd_soc_acpi_intel_mtl_machines, + .alt_machines = snd_soc_acpi_intel_mtl_sdw_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .chip_info = &mtl_chip_info, + .ipc_supported_mask = BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_INTEL_IPC4, + .default_fw_path = { + [SOF_INTEL_IPC4] = "intel/sof-ipc4/mtl", + }, + .default_tplg_path = { + [SOF_INTEL_IPC4] = "intel/sof-ace-tplg", + }, + .default_fw_filename = { + [SOF_INTEL_IPC4] = "sof-mtl.ri", + }, + .nocodec_tplg_filename = "sof-mtl-nocodec.tplg", + .ops = &sof_mtl_ops, + .ops_init = sof_mtl_ops_init, +}; + +/* PCI IDs */ +static const struct pci_device_id sof_pci_ids[] = { + { PCI_DEVICE(0x8086, 0x7E28), /* MTL */ + .driver_data = (unsigned long)&mtl_desc}, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, sof_pci_ids); + +/* pci_driver definition */ +static struct pci_driver snd_sof_pci_intel_mtl_driver = { + .name = "sof-audio-pci-intel-mtl", + .id_table = sof_pci_ids, + .probe = hda_pci_intel_probe, + .remove = sof_pci_remove, + .shutdown = sof_pci_shutdown, + .driver = { + .pm = &sof_pci_pm, + }, +}; +module_pci_driver(snd_sof_pci_intel_mtl_driver); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_HDA_COMMON); +MODULE_IMPORT_NS(SND_SOC_SOF_PCI_DEV); diff --git a/sound/soc/sof/intel/pci-skl.c b/sound/soc/sof/intel/pci-skl.c new file mode 100644 index 000000000000..3a99dc444f92 --- /dev/null +++ b/sound/soc/sof/intel/pci-skl.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2018-2022 Intel Corporation. All rights reserved. +// + +#include <linux/module.h> +#include <linux/pci.h> +#include <sound/soc-acpi.h> +#include <sound/soc-acpi-intel-match.h> +#include <sound/sof.h> +#include "../ops.h" +#include "../sof-pci-dev.h" + +/* platform specific devices */ +#include "hda.h" + +static struct sof_dev_desc skl_desc = { + .machines = snd_soc_acpi_intel_skl_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .chip_info = &skl_chip_info, + .irqindex_host_ipc = -1, + .ipc_supported_mask = BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_INTEL_IPC4, + .default_fw_path = { + [SOF_INTEL_IPC4] = "intel/avs/skl", + }, + .default_tplg_path = { + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, + .nocodec_tplg_filename = "sof-skl-nocodec.tplg", + .ops = &sof_skl_ops, + .ops_init = sof_skl_ops_init, +}; + +static struct sof_dev_desc kbl_desc = { + .machines = snd_soc_acpi_intel_kbl_machines, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .chip_info = &skl_chip_info, + .irqindex_host_ipc = -1, + .ipc_supported_mask = BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_INTEL_IPC4, + .default_fw_path = { + [SOF_INTEL_IPC4] = "intel/avs/kbl", + }, + .default_tplg_path = { + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, + .nocodec_tplg_filename = "sof-kbl-nocodec.tplg", + .ops = &sof_skl_ops, + .ops_init = sof_skl_ops_init, +}; + +/* PCI IDs */ +static const struct pci_device_id sof_pci_ids[] = { + /* Sunrise Point-LP */ + { PCI_DEVICE(0x8086, 0x9d70), .driver_data = (unsigned long)&skl_desc}, + /* KBL */ + { PCI_DEVICE(0x8086, 0x9d71), .driver_data = (unsigned long)&kbl_desc}, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, sof_pci_ids); + +/* pci_driver definition */ +static struct pci_driver snd_sof_pci_intel_skl_driver = { + .name = "sof-audio-pci-intel-skl", + .id_table = sof_pci_ids, + .probe = hda_pci_intel_probe, + .remove = sof_pci_remove, + .shutdown = sof_pci_shutdown, + .driver = { + .pm = &sof_pci_pm, + }, +}; +module_pci_driver(snd_sof_pci_intel_skl_driver); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_HDA_COMMON); +MODULE_IMPORT_NS(SND_SOC_SOF_PCI_DEV); diff --git a/sound/soc/sof/intel/pci-tgl.c b/sound/soc/sof/intel/pci-tgl.c index fd46210f1730..4cfe4f242fc5 100644 --- a/sound/soc/sof/intel/pci-tgl.c +++ b/sound/soc/sof/intel/pci-tgl.c @@ -28,11 +28,23 @@ static const struct sof_dev_desc tgl_desc = { .resindex_imr_base = -1, .irqindex_host_ipc = -1, .chip_info = &tgl_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-tgl.ri", + .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + [SOF_INTEL_IPC4] = "intel/avs/tgl", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-tgl.ri", + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, .nocodec_tplg_filename = "sof-tgl-nocodec.tplg", .ops = &sof_tgl_ops, + .ops_init = sof_tgl_ops_init, }; static const struct sof_dev_desc tglh_desc = { @@ -44,11 +56,24 @@ static const struct sof_dev_desc tglh_desc = { .resindex_imr_base = -1, .irqindex_host_ipc = -1, .chip_info = &tglh_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-tgl-h.ri", + .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + [SOF_INTEL_IPC4] = "intel/avs/tgl-h", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-tgl-h.ri", + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, .nocodec_tplg_filename = "sof-tgl-nocodec.tplg", .ops = &sof_tgl_ops, + .ops_init = sof_tgl_ops_init, + .ops_free = hda_ops_free, }; static const struct sof_dev_desc ehl_desc = { @@ -59,11 +84,23 @@ static const struct sof_dev_desc ehl_desc = { .resindex_imr_base = -1, .irqindex_host_ipc = -1, .chip_info = &ehl_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-ehl.ri", + .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + [SOF_INTEL_IPC4] = "intel/avs/ehl", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-ehl.ri", + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, .nocodec_tplg_filename = "sof-ehl-nocodec.tplg", .ops = &sof_tgl_ops, + .ops_init = sof_tgl_ops_init, }; static const struct sof_dev_desc adls_desc = { @@ -75,11 +112,23 @@ static const struct sof_dev_desc adls_desc = { .resindex_imr_base = -1, .irqindex_host_ipc = -1, .chip_info = &adls_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-adl-s.ri", + .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + [SOF_INTEL_IPC4] = "intel/avs/adl-s", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-adl-s.ri", + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, .nocodec_tplg_filename = "sof-adl-nocodec.tplg", .ops = &sof_tgl_ops, + .ops_init = sof_tgl_ops_init, }; static const struct sof_dev_desc adl_desc = { @@ -91,11 +140,107 @@ static const struct sof_dev_desc adl_desc = { .resindex_imr_base = -1, .irqindex_host_ipc = -1, .chip_info = &tgl_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-adl.ri", + .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + [SOF_INTEL_IPC4] = "intel/avs/adl", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-adl.ri", + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, + .nocodec_tplg_filename = "sof-adl-nocodec.tplg", + .ops = &sof_tgl_ops, + .ops_init = sof_tgl_ops_init, +}; + +static const struct sof_dev_desc adl_n_desc = { + .machines = snd_soc_acpi_intel_adl_machines, + .alt_machines = snd_soc_acpi_intel_adl_sdw_machines, + .use_acpi_target_states = true, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .chip_info = &tgl_chip_info, + .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + [SOF_INTEL_IPC4] = "intel/avs/adl-n", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-adl-n.ri", + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, .nocodec_tplg_filename = "sof-adl-nocodec.tplg", .ops = &sof_tgl_ops, + .ops_init = sof_tgl_ops_init, +}; + +static const struct sof_dev_desc rpls_desc = { + .machines = snd_soc_acpi_intel_rpl_machines, + .alt_machines = snd_soc_acpi_intel_rpl_sdw_machines, + .use_acpi_target_states = true, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .chip_info = &adls_chip_info, + .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + [SOF_INTEL_IPC4] = "intel/avs/rpl-s", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-rpl-s.ri", + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, + .nocodec_tplg_filename = "sof-rpl-nocodec.tplg", + .ops = &sof_tgl_ops, + .ops_init = sof_tgl_ops_init, +}; + +static const struct sof_dev_desc rpl_desc = { + .machines = snd_soc_acpi_intel_rpl_machines, + .alt_machines = snd_soc_acpi_intel_rpl_sdw_machines, + .use_acpi_target_states = true, + .resindex_lpe_base = 0, + .resindex_pcicfg_base = -1, + .resindex_imr_base = -1, + .irqindex_host_ipc = -1, + .chip_info = &tgl_chip_info, + .ipc_supported_mask = BIT(SOF_IPC) | BIT(SOF_INTEL_IPC4), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + [SOF_INTEL_IPC4] = "intel/avs/rpl", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + [SOF_INTEL_IPC4] = "intel/avs-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-rpl.ri", + [SOF_INTEL_IPC4] = "dsp_basefw.bin", + }, + .nocodec_tplg_filename = "sof-rpl-nocodec.tplg", + .ops = &sof_tgl_ops, + .ops_init = sof_tgl_ops_init, }; /* PCI IDs */ @@ -110,14 +255,26 @@ static const struct pci_device_id sof_pci_ids[] = { .driver_data = (unsigned long)&ehl_desc}, { PCI_DEVICE(0x8086, 0x7ad0), /* ADL-S */ .driver_data = (unsigned long)&adls_desc}, + { PCI_DEVICE(0x8086, 0x7a50), /* RPL-S */ + .driver_data = (unsigned long)&rpls_desc}, { PCI_DEVICE(0x8086, 0x51c8), /* ADL-P */ .driver_data = (unsigned long)&adl_desc}, - { PCI_DEVICE(0x8086, 0x51cd), /* ADL-P */ + { PCI_DEVICE(0x8086, 0x51c9), /* ADL-PS */ .driver_data = (unsigned long)&adl_desc}, + { PCI_DEVICE(0x8086, 0x51ca), /* RPL-P */ + .driver_data = (unsigned long)&rpl_desc}, + { PCI_DEVICE(0x8086, 0x51cb), /* RPL-P */ + .driver_data = (unsigned long)&rpl_desc}, { PCI_DEVICE(0x8086, 0x51cc), /* ADL-M */ .driver_data = (unsigned long)&adl_desc}, - { PCI_DEVICE(0x8086, 0x54c8), /* ADL-N */ + { PCI_DEVICE(0x8086, 0x51cd), /* ADL-P */ .driver_data = (unsigned long)&adl_desc}, + { PCI_DEVICE(0x8086, 0x51ce), /* RPL-M */ + .driver_data = (unsigned long)&rpl_desc}, + { PCI_DEVICE(0x8086, 0x51cf), /* RPL-PX */ + .driver_data = (unsigned long)&rpl_desc}, + { PCI_DEVICE(0x8086, 0x54c8), /* ADL-N */ + .driver_data = (unsigned long)&adl_n_desc}, { 0, } }; MODULE_DEVICE_TABLE(pci, sof_pci_ids); @@ -138,4 +295,3 @@ module_pci_driver(snd_sof_pci_intel_tgl_driver); MODULE_LICENSE("Dual BSD/GPL"); MODULE_IMPORT_NS(SND_SOC_SOF_INTEL_HDA_COMMON); MODULE_IMPORT_NS(SND_SOC_SOF_PCI_DEV); - diff --git a/sound/soc/sof/intel/pci-tng.c b/sound/soc/sof/intel/pci-tng.c index 18eb41b8a8f4..f0f6d9ba8803 100644 --- a/sound/soc/sof/intel/pci-tng.c +++ b/sound/soc/sof/intel/pci-tng.c @@ -25,7 +25,6 @@ static struct snd_soc_acpi_mach sof_tng_machines[] = { { .id = "INT343A", .drv_name = "edison", - .sof_fw_filename = "sof-byt.ri", .sof_tplg_filename = "sof-byt.tplg", }, {} @@ -55,9 +54,18 @@ static int tangier_pci_probe(struct snd_sof_dev *sdev) struct snd_sof_pdata *pdata = sdev->pdata; const struct sof_dev_desc *desc = pdata->desc; struct pci_dev *pci = to_pci_dev(sdev->dev); + const struct sof_intel_dsp_desc *chip; u32 base, size; int ret; + chip = get_chip_info(sdev->pdata); + if (!chip) { + dev_err(sdev->dev, "error: no such device supported\n"); + return -EIO; + } + + sdev->num_cores = chip->cores_num; + /* DSP DMA can only access low 31 bits of host memory */ ret = dma_coerce_mask_and_coherent(&pci->dev, DMA_BIT_MASK(31)); if (ret < 0) { @@ -67,7 +75,11 @@ static int tangier_pci_probe(struct snd_sof_dev *sdev) /* LPE base */ base = pci_resource_start(pci, desc->resindex_lpe_base) - IRAM_OFFSET; - size = PCI_BAR_SIZE; + size = pci_resource_len(pci, desc->resindex_lpe_base); + if (size < PCI_BAR_SIZE) { + dev_err(sdev->dev, "error: I/O region is too small.\n"); + return -ENODEV; + } dev_dbg(sdev->dev, "LPE PHY base at 0x%x size 0x%x", base, size); sdev->bar[DSP_BAR] = devm_ioremap(sdev->dev, base, size); @@ -124,7 +136,7 @@ irq: return ret; } -const struct snd_sof_dsp_ops sof_tng_ops = { +struct snd_sof_dsp_ops sof_tng_ops = { /* device init */ .probe = tangier_pci_probe, @@ -152,12 +164,11 @@ const struct snd_sof_dsp_ops sof_tng_ops = { /* ipc */ .send_msg = atom_send_msg, - .fw_ready = sof_fw_ready, .get_mailbox_offset = atom_get_mailbox_offset, .get_window_offset = atom_get_window_offset, .ipc_msg_data = sof_ipc_msg_data, - .ipc_pcm_params = sof_ipc_pcm_params, + .set_stream_data_offset = sof_set_stream_data_offset, /* machine driver */ .machine_select = atom_machine_select, @@ -175,9 +186,6 @@ const struct snd_sof_dsp_ops sof_tng_ops = { .pcm_open = sof_stream_pcm_open, .pcm_close = sof_stream_pcm_close, - /* module loading */ - .load_module = snd_sof_parse_module_memcpy, - /*Firmware loading */ .load_firmware = snd_sof_load_firmware_memcpy, @@ -198,6 +206,7 @@ const struct snd_sof_dsp_ops sof_tng_ops = { const struct sof_intel_dsp_desc tng_chip_info = { .cores_num = 1, .host_managed_cores_mask = 1, + .hw_ip_version = SOF_INTEL_TANGIER, }; static const struct sof_dev_desc tng_desc = { @@ -207,9 +216,17 @@ static const struct sof_dev_desc tng_desc = { .resindex_imr_base = 0, .irqindex_host_ipc = -1, .chip_info = &tng_chip_info, - .default_fw_path = "intel/sof", - .default_tplg_path = "intel/sof-tplg", - .default_fw_filename = "sof-byt.ri", + .ipc_supported_mask = BIT(SOF_IPC), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "intel/sof", + }, + .default_tplg_path = { + [SOF_IPC] = "intel/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-byt.ri", + }, .nocodec_tplg_filename = "sof-byt.tplg", .ops = &sof_tng_ops, }; diff --git a/sound/soc/sof/intel/shim.h b/sound/soc/sof/intel/shim.h index e9f7d4d7fcce..3ceba5c39317 100644 --- a/sound/soc/sof/intel/shim.h +++ b/sound/soc/sof/intel/shim.h @@ -11,6 +11,18 @@ #ifndef __SOF_INTEL_SHIM_H #define __SOF_INTEL_SHIM_H +enum sof_intel_hw_ip_version { + SOF_INTEL_TANGIER, + SOF_INTEL_BAYTRAIL, + SOF_INTEL_BROADWELL, + SOF_INTEL_CAVS_1_5, /* SkyLake, KabyLake, AmberLake */ + SOF_INTEL_CAVS_1_5_PLUS,/* ApolloLake, GeminiLake */ + SOF_INTEL_CAVS_1_8, /* CannonLake, CometLake, CoffeeLake */ + SOF_INTEL_CAVS_2_0, /* IceLake, JasperLake */ + SOF_INTEL_CAVS_2_5, /* TigerLake, AlderLake */ + SOF_INTEL_ACE_1_0, /* MeteorLake */ +}; + /* * SHIM registers for BYT, BSW, CHT, BDW */ @@ -151,6 +163,9 @@ #define PCI_PMCS 0x84 #define PCI_PMCS_PS_MASK 0x3 +/* Intel quirks */ +#define SOF_INTEL_PROCEN_FMT_QUIRK BIT(0) + /* DSP hardware descriptor */ struct sof_intel_dsp_desc { int cores_num; @@ -161,15 +176,22 @@ struct sof_intel_dsp_desc { int ipc_ack; int ipc_ack_mask; int ipc_ctl; + int rom_status_reg; int rom_init_timeout; int ssp_count; /* ssp count of the platform */ int ssp_base_offset; /* base address of the SSPs */ u32 sdw_shim_base; u32 sdw_alh_base; + u32 quirks; + enum sof_intel_hw_ip_version hw_ip_version; bool (*check_sdw_irq)(struct snd_sof_dev *sdev); + bool (*check_ipc_irq)(struct snd_sof_dev *sdev); + int (*power_down_dsp)(struct snd_sof_dev *sdev); + int (*disable_interrupts)(struct snd_sof_dev *sdev); + int (*cl_init)(struct snd_sof_dev *sdev, int stream_tag, bool imr_boot); }; -extern const struct snd_sof_dsp_ops sof_tng_ops; +extern struct snd_sof_dsp_ops sof_tng_ops; extern const struct sof_intel_dsp_desc tng_chip_info; @@ -177,4 +199,11 @@ struct sof_intel_stream { size_t posn_offset; }; +static inline const struct sof_intel_dsp_desc *get_chip_info(struct snd_sof_pdata *pdata) +{ + const struct sof_dev_desc *desc = pdata->desc; + + return desc->chip_info; +} + #endif diff --git a/sound/soc/sof/intel/skl.c b/sound/soc/sof/intel/skl.c new file mode 100644 index 000000000000..13efdb94d071 --- /dev/null +++ b/sound/soc/sof/intel/skl.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2018-2022 Intel Corporation. All rights reserved. +// + +/* + * Hardware interface for audio DSP on Skylake and Kabylake. + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/firmware.h> +#include <linux/fs.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/pm_runtime.h> +#include <sound/hdaudio_ext.h> +#include <sound/pcm_params.h> +#include <sound/sof.h> +#include <sound/sof/ext_manifest4.h> + +#include "../sof-priv.h" +#include "../ipc4-priv.h" +#include "../ops.h" +#include "hda.h" +#include "../sof-audio.h" + +#define SRAM_MEMORY_WINDOW_BASE 0x8000 + +static const __maybe_unused struct snd_sof_debugfs_map skl_dsp_debugfs[] = { + {"hda", HDA_DSP_HDA_BAR, 0, 0x4000}, + {"pp", HDA_DSP_PP_BAR, 0, 0x1000}, + {"dsp", HDA_DSP_BAR, 0, 0x10000}, +}; + +static int skl_dsp_ipc_get_window_offset(struct snd_sof_dev *sdev, u32 id) +{ + return SRAM_MEMORY_WINDOW_BASE + (0x2000 * id); +} + +static int skl_dsp_ipc_get_mailbox_offset(struct snd_sof_dev *sdev) +{ + return SRAM_MEMORY_WINDOW_BASE + 0x1000; +} + +/* skylake ops */ +struct snd_sof_dsp_ops sof_skl_ops; +EXPORT_SYMBOL_NS(sof_skl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); + +int sof_skl_ops_init(struct snd_sof_dev *sdev) +{ + struct sof_ipc4_fw_data *ipc4_data; + + /* common defaults */ + memcpy(&sof_skl_ops, &sof_hda_common_ops, sizeof(struct snd_sof_dsp_ops)); + + /* probe/remove/shutdown */ + sof_skl_ops.shutdown = hda_dsp_shutdown; + + sdev->private = devm_kzalloc(sdev->dev, sizeof(*ipc4_data), GFP_KERNEL); + if (!sdev->private) + return -ENOMEM; + + ipc4_data = sdev->private; + ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET_CAVS_1_5; + + ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_1_5; + + sof_skl_ops.get_window_offset = skl_dsp_ipc_get_window_offset; + sof_skl_ops.get_mailbox_offset = skl_dsp_ipc_get_mailbox_offset; + + /* doorbell */ + sof_skl_ops.irq_thread = hda_dsp_ipc4_irq_thread; + + /* ipc */ + sof_skl_ops.send_msg = hda_dsp_ipc4_send_msg; + + /* set DAI driver ops */ + hda_set_dai_drv_ops(sdev, &sof_skl_ops); + + /* debug */ + sof_skl_ops.debug_map = skl_dsp_debugfs; + sof_skl_ops.debug_map_count = ARRAY_SIZE(skl_dsp_debugfs); + sof_skl_ops.ipc_dump = hda_ipc4_dump; + + /* firmware run */ + sof_skl_ops.run = hda_dsp_cl_boot_firmware_skl; + + /* pre/post fw run */ + sof_skl_ops.post_fw_run = hda_dsp_post_fw_run; + + return 0; +}; +EXPORT_SYMBOL_NS(sof_skl_ops_init, SND_SOC_SOF_INTEL_HDA_COMMON); + +const struct sof_intel_dsp_desc skl_chip_info = { + .cores_num = 2, + .init_core_mask = 1, + .host_managed_cores_mask = GENMASK(1, 0), + .ipc_req = HDA_DSP_REG_HIPCI, + .ipc_req_mask = HDA_DSP_REG_HIPCI_BUSY, + .ipc_ack = HDA_DSP_REG_HIPCIE, + .ipc_ack_mask = HDA_DSP_REG_HIPCIE_DONE, + .ipc_ctl = HDA_DSP_REG_HIPCCTL, + .rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS_SKL, + .rom_init_timeout = 300, + .check_ipc_irq = hda_dsp_check_ipc_irq, + .power_down_dsp = hda_power_down_dsp, + .disable_interrupts = hda_dsp_disable_interrupts, + .hw_ip_version = SOF_INTEL_CAVS_1_5, +}; +EXPORT_SYMBOL_NS(skl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON); diff --git a/sound/soc/sof/intel/tgl.c b/sound/soc/sof/intel/tgl.c index 48da8e7a67bc..9ae2890e9dac 100644 --- a/sound/soc/sof/intel/tgl.c +++ b/sound/soc/sof/intel/tgl.c @@ -9,6 +9,8 @@ * Hardware interface for audio DSP on Tigerlake. */ +#include <sound/sof/ext_manifest4.h> +#include "../ipc4-priv.h" #include "../ops.h" #include "hda.h" #include "hda-ipc.h" @@ -20,114 +22,99 @@ static const struct snd_sof_debugfs_map tgl_dsp_debugfs[] = { {"dsp", HDA_DSP_BAR, 0, 0x10000, SOF_DEBUGFS_ACCESS_ALWAYS}, }; +static int tgl_dsp_core_get(struct snd_sof_dev *sdev, int core) +{ + const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm; + + /* power up primary core if not already powered up and return */ + if (core == SOF_DSP_PRIMARY_CORE) + return hda_dsp_enable_core(sdev, BIT(core)); + + if (pm_ops->set_core_state) + return pm_ops->set_core_state(sdev, core, true); + + return 0; +} + +static int tgl_dsp_core_put(struct snd_sof_dev *sdev, int core) +{ + const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm; + + /* power down primary core and return */ + if (core == SOF_DSP_PRIMARY_CORE) + return hda_dsp_core_reset_power_down(sdev, BIT(core)); + + if (pm_ops->set_core_state) + return pm_ops->set_core_state(sdev, core, false); + + return 0; +} + /* Tigerlake ops */ -const struct snd_sof_dsp_ops sof_tgl_ops = { +struct snd_sof_dsp_ops sof_tgl_ops; +EXPORT_SYMBOL_NS(sof_tgl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); + +int sof_tgl_ops_init(struct snd_sof_dev *sdev) +{ + /* common defaults */ + memcpy(&sof_tgl_ops, &sof_hda_common_ops, sizeof(struct snd_sof_dsp_ops)); + /* probe/remove/shutdown */ - .probe = hda_dsp_probe, - .remove = hda_dsp_remove, - .shutdown = hda_dsp_shutdown, - - /* 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, - - /* Mailbox IO */ - .mailbox_read = sof_mailbox_read, - .mailbox_write = sof_mailbox_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, + sof_tgl_ops.shutdown = hda_dsp_shutdown; + + if (sdev->pdata->ipc_type == SOF_IPC) { + /* doorbell */ + sof_tgl_ops.irq_thread = cnl_ipc_irq_thread; + + /* ipc */ + sof_tgl_ops.send_msg = cnl_ipc_send_msg; + + /* debug */ + sof_tgl_ops.ipc_dump = cnl_ipc_dump; + } + + if (sdev->pdata->ipc_type == SOF_INTEL_IPC4) { + struct sof_ipc4_fw_data *ipc4_data; + + sdev->private = devm_kzalloc(sdev->dev, sizeof(*ipc4_data), GFP_KERNEL); + if (!sdev->private) + return -ENOMEM; + + ipc4_data = sdev->private; + ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET; + + ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_2; + + /* doorbell */ + sof_tgl_ops.irq_thread = cnl_ipc4_irq_thread; + + /* ipc */ + sof_tgl_ops.send_msg = cnl_ipc4_send_msg; + + /* debug */ + sof_tgl_ops.ipc_dump = cnl_ipc4_dump; + } + + /* set DAI driver ops */ + hda_set_dai_drv_ops(sdev, &sof_tgl_ops); /* debug */ - .debug_map = tgl_dsp_debugfs, - .debug_map_count = ARRAY_SIZE(tgl_dsp_debugfs), - .dbg_dump = hda_dsp_dump, - .ipc_dump = cnl_ipc_dump, - .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem, - - /* stream callbacks */ - .pcm_open = hda_dsp_pcm_open, - .pcm_close = hda_dsp_pcm_close, - .pcm_hw_params = hda_dsp_pcm_hw_params, - .pcm_hw_free = hda_dsp_stream_hw_free, - .pcm_trigger = hda_dsp_pcm_trigger, - .pcm_pointer = hda_dsp_pcm_pointer, - -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_PROBES) - /* probe callbacks */ - .probe_assign = hda_probe_compr_assign, - .probe_free = hda_probe_compr_free, - .probe_set_params = hda_probe_compr_set_params, - .probe_trigger = hda_probe_compr_trigger, - .probe_pointer = hda_probe_compr_pointer, -#endif - - /* firmware loading */ - .load_firmware = snd_sof_load_firmware_raw, + sof_tgl_ops.debug_map = tgl_dsp_debugfs; + sof_tgl_ops.debug_map_count = ARRAY_SIZE(tgl_dsp_debugfs); /* pre/post fw run */ - .pre_fw_run = hda_dsp_pre_fw_run, - .post_fw_run = hda_dsp_post_fw_run, + sof_tgl_ops.post_fw_run = hda_dsp_post_fw_run; - /* parse platform specific extended manifest */ - .parse_platform_ext_manifest = hda_dsp_ext_man_get_cavs_config_data, + /* firmware run */ + sof_tgl_ops.run = hda_dsp_cl_boot_firmware_iccmax; - /* dsp core power up/down */ - .core_power_up = hda_dsp_enable_core, - .core_power_down = hda_dsp_core_reset_power_down, + /* dsp core get/put */ + sof_tgl_ops.core_get = tgl_dsp_core_get; + sof_tgl_ops.core_put = tgl_dsp_core_put; - /* 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, - - .dsp_arch_ops = &sof_xtensa_arch_ops, + return 0; }; -EXPORT_SYMBOL_NS(sof_tgl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); +EXPORT_SYMBOL_NS(sof_tgl_ops_init, SND_SOC_SOF_INTEL_HDA_COMMON); const struct sof_intel_dsp_desc tgl_chip_info = { /* Tigerlake , Alderlake */ @@ -139,12 +126,18 @@ const struct sof_intel_dsp_desc tgl_chip_info = { .ipc_ack = CNL_DSP_REG_HIPCIDA, .ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE, .ipc_ctl = CNL_DSP_REG_HIPCCTL, + .rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS, .rom_init_timeout = 300, - .ssp_count = ICL_SSP_COUNT, + .ssp_count = TGL_SSP_COUNT, .ssp_base_offset = CNL_SSP_BASE_OFFSET, .sdw_shim_base = SDW_SHIM_BASE, .sdw_alh_base = SDW_ALH_BASE, .check_sdw_irq = hda_common_check_sdw_irq, + .check_ipc_irq = hda_dsp_check_ipc_irq, + .cl_init = cl_dsp_init, + .power_down_dsp = hda_power_down_dsp, + .disable_interrupts = hda_dsp_disable_interrupts, + .hw_ip_version = SOF_INTEL_CAVS_2_5, }; EXPORT_SYMBOL_NS(tgl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON); @@ -158,12 +151,18 @@ const struct sof_intel_dsp_desc tglh_chip_info = { .ipc_ack = CNL_DSP_REG_HIPCIDA, .ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE, .ipc_ctl = CNL_DSP_REG_HIPCCTL, + .rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS, .rom_init_timeout = 300, - .ssp_count = ICL_SSP_COUNT, + .ssp_count = TGL_SSP_COUNT, .ssp_base_offset = CNL_SSP_BASE_OFFSET, .sdw_shim_base = SDW_SHIM_BASE, .sdw_alh_base = SDW_ALH_BASE, .check_sdw_irq = hda_common_check_sdw_irq, + .check_ipc_irq = hda_dsp_check_ipc_irq, + .cl_init = cl_dsp_init, + .power_down_dsp = hda_power_down_dsp, + .disable_interrupts = hda_dsp_disable_interrupts, + .hw_ip_version = SOF_INTEL_CAVS_2_5, }; EXPORT_SYMBOL_NS(tglh_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON); @@ -177,12 +176,18 @@ const struct sof_intel_dsp_desc ehl_chip_info = { .ipc_ack = CNL_DSP_REG_HIPCIDA, .ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE, .ipc_ctl = CNL_DSP_REG_HIPCCTL, + .rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS, .rom_init_timeout = 300, - .ssp_count = ICL_SSP_COUNT, + .ssp_count = TGL_SSP_COUNT, .ssp_base_offset = CNL_SSP_BASE_OFFSET, .sdw_shim_base = SDW_SHIM_BASE, .sdw_alh_base = SDW_ALH_BASE, .check_sdw_irq = hda_common_check_sdw_irq, + .check_ipc_irq = hda_dsp_check_ipc_irq, + .cl_init = cl_dsp_init, + .power_down_dsp = hda_power_down_dsp, + .disable_interrupts = hda_dsp_disable_interrupts, + .hw_ip_version = SOF_INTEL_CAVS_2_5, }; EXPORT_SYMBOL_NS(ehl_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON); @@ -196,11 +201,17 @@ const struct sof_intel_dsp_desc adls_chip_info = { .ipc_ack = CNL_DSP_REG_HIPCIDA, .ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE, .ipc_ctl = CNL_DSP_REG_HIPCCTL, + .rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS, .rom_init_timeout = 300, - .ssp_count = ICL_SSP_COUNT, + .ssp_count = TGL_SSP_COUNT, .ssp_base_offset = CNL_SSP_BASE_OFFSET, .sdw_shim_base = SDW_SHIM_BASE, .sdw_alh_base = SDW_ALH_BASE, .check_sdw_irq = hda_common_check_sdw_irq, + .check_ipc_irq = hda_dsp_check_ipc_irq, + .cl_init = cl_dsp_init, + .power_down_dsp = hda_power_down_dsp, + .disable_interrupts = hda_dsp_disable_interrupts, + .hw_ip_version = SOF_INTEL_CAVS_2_5, }; EXPORT_SYMBOL_NS(adls_chip_info, SND_SOC_SOF_INTEL_HDA_COMMON); diff --git a/sound/soc/sof/utils.c b/sound/soc/sof/iomem-utils.c index 66fa6602fb67..3f57f6cf6542 100644 --- a/sound/soc/sof/utils.c +++ b/sound/soc/sof/iomem-utils.c @@ -3,7 +3,7 @@ // This file is provided under a dual BSD/GPLv2 license. When using or // redistributing this file, you may do so under either license. // -// Copyright(c) 2018 Intel Corporation. All rights reserved. +// Copyright(c) 2018-2022 Intel Corporation. All rights reserved. // // Author: Keyon Jie <yang.jie@linux.intel.com> // @@ -125,62 +125,3 @@ int sof_block_read(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type, return 0; } 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/ipc.c b/sound/soc/sof/ipc.c index e6c53c6c470e..6ed3f9b6a0c4 100644 --- a/sound/soc/sof/ipc.c +++ b/sound/soc/sof/ipc.c @@ -18,296 +18,47 @@ #include "sof-audio.h" #include "ops.h" -static void ipc_trace_message(struct snd_sof_dev *sdev, u32 msg_type); -static void ipc_stream_message(struct snd_sof_dev *sdev, u32 msg_cmd); - -/* - * IPC message Tx/Rx message handling. +/** + * sof_ipc_send_msg - generic function to prepare and send one IPC message + * @sdev: pointer to SOF core device struct + * @msg_data: pointer to a message to send + * @msg_bytes: number of bytes in the message + * @reply_bytes: number of bytes available for the reply. + * The buffer for the reply data is not passed to this + * function, the available size is an information for the + * reply handling functions. + * + * On success the function returns 0, otherwise negative error number. + * + * Note: higher level sdev->ipc->tx_mutex must be held to make sure that + * transfers are synchronized. */ - -/* SOF generic IPC data */ -struct snd_sof_ipc { - struct snd_sof_dev *sdev; - - /* protects messages and the disable flag */ - struct mutex tx_mutex; - /* disables further sending of ipc's */ - bool disable_ipc_tx; - - struct snd_sof_ipc_msg msg; -}; - -struct sof_ipc_ctrl_data_params { - size_t msg_bytes; - size_t hdr_bytes; - size_t pl_size; - size_t elems; - u32 num_msg; - u8 *src; - u8 *dst; -}; - -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_VERBOSE_IPC) -static void ipc_log_header(struct device *dev, u8 *text, u32 cmd) -{ - u8 *str; - u8 *str2 = NULL; - u32 glb; - u32 type; - bool vdbg = false; - - glb = cmd & SOF_GLB_TYPE_MASK; - type = cmd & SOF_CMD_TYPE_MASK; - - switch (glb) { - case SOF_IPC_GLB_REPLY: - str = "GLB_REPLY"; break; - case SOF_IPC_GLB_COMPOUND: - str = "GLB_COMPOUND"; break; - case SOF_IPC_GLB_TPLG_MSG: - str = "GLB_TPLG_MSG"; - switch (type) { - case SOF_IPC_TPLG_COMP_NEW: - str2 = "COMP_NEW"; break; - case SOF_IPC_TPLG_COMP_FREE: - str2 = "COMP_FREE"; break; - case SOF_IPC_TPLG_COMP_CONNECT: - str2 = "COMP_CONNECT"; break; - case SOF_IPC_TPLG_PIPE_NEW: - str2 = "PIPE_NEW"; break; - case SOF_IPC_TPLG_PIPE_FREE: - str2 = "PIPE_FREE"; break; - case SOF_IPC_TPLG_PIPE_CONNECT: - str2 = "PIPE_CONNECT"; break; - case SOF_IPC_TPLG_PIPE_COMPLETE: - str2 = "PIPE_COMPLETE"; break; - case SOF_IPC_TPLG_BUFFER_NEW: - str2 = "BUFFER_NEW"; break; - case SOF_IPC_TPLG_BUFFER_FREE: - str2 = "BUFFER_FREE"; break; - default: - str2 = "unknown type"; break; - } - break; - case SOF_IPC_GLB_PM_MSG: - str = "GLB_PM_MSG"; - switch (type) { - case SOF_IPC_PM_CTX_SAVE: - str2 = "CTX_SAVE"; break; - case SOF_IPC_PM_CTX_RESTORE: - str2 = "CTX_RESTORE"; break; - case SOF_IPC_PM_CTX_SIZE: - str2 = "CTX_SIZE"; break; - case SOF_IPC_PM_CLK_SET: - str2 = "CLK_SET"; break; - case SOF_IPC_PM_CLK_GET: - str2 = "CLK_GET"; break; - case SOF_IPC_PM_CLK_REQ: - str2 = "CLK_REQ"; break; - case SOF_IPC_PM_CORE_ENABLE: - str2 = "CORE_ENABLE"; break; - case SOF_IPC_PM_GATE: - str2 = "GATE"; break; - default: - str2 = "unknown type"; break; - } - break; - case SOF_IPC_GLB_COMP_MSG: - str = "GLB_COMP_MSG"; - switch (type) { - case SOF_IPC_COMP_SET_VALUE: - str2 = "SET_VALUE"; break; - case SOF_IPC_COMP_GET_VALUE: - str2 = "GET_VALUE"; break; - case SOF_IPC_COMP_SET_DATA: - str2 = "SET_DATA"; break; - case SOF_IPC_COMP_GET_DATA: - str2 = "GET_DATA"; break; - default: - str2 = "unknown type"; break; - } - break; - case SOF_IPC_GLB_STREAM_MSG: - str = "GLB_STREAM_MSG"; - switch (type) { - case SOF_IPC_STREAM_PCM_PARAMS: - str2 = "PCM_PARAMS"; break; - case SOF_IPC_STREAM_PCM_PARAMS_REPLY: - str2 = "PCM_REPLY"; break; - case SOF_IPC_STREAM_PCM_FREE: - str2 = "PCM_FREE"; break; - case SOF_IPC_STREAM_TRIG_START: - str2 = "TRIG_START"; break; - case SOF_IPC_STREAM_TRIG_STOP: - str2 = "TRIG_STOP"; break; - case SOF_IPC_STREAM_TRIG_PAUSE: - str2 = "TRIG_PAUSE"; break; - case SOF_IPC_STREAM_TRIG_RELEASE: - str2 = "TRIG_RELEASE"; break; - case SOF_IPC_STREAM_TRIG_DRAIN: - str2 = "TRIG_DRAIN"; break; - case SOF_IPC_STREAM_TRIG_XRUN: - str2 = "TRIG_XRUN"; break; - case SOF_IPC_STREAM_POSITION: - vdbg = true; - str2 = "POSITION"; break; - case SOF_IPC_STREAM_VORBIS_PARAMS: - str2 = "VORBIS_PARAMS"; break; - case SOF_IPC_STREAM_VORBIS_FREE: - str2 = "VORBIS_FREE"; break; - default: - str2 = "unknown type"; break; - } - break; - case SOF_IPC_FW_READY: - str = "FW_READY"; break; - case SOF_IPC_GLB_DAI_MSG: - str = "GLB_DAI_MSG"; - switch (type) { - case SOF_IPC_DAI_CONFIG: - str2 = "CONFIG"; break; - case SOF_IPC_DAI_LOOPBACK: - str2 = "LOOPBACK"; break; - default: - str2 = "unknown type"; break; - } - break; - case SOF_IPC_GLB_TRACE_MSG: - str = "GLB_TRACE_MSG"; break; - case SOF_IPC_GLB_TEST_MSG: - str = "GLB_TEST_MSG"; - switch (type) { - case SOF_IPC_TEST_IPC_FLOOD: - str2 = "IPC_FLOOD"; break; - default: - str2 = "unknown type"; break; - } - break; - case SOF_IPC_GLB_DEBUG: - str = "GLB_DEBUG"; - switch (type) { - case SOF_IPC_DEBUG_MEM_USAGE: - str2 = "MEM_USAGE"; break; - default: - str2 = "unknown type"; break; - } - break; - case SOF_IPC_GLB_PROBE: - str = "GLB_PROBE"; - switch (type) { - case SOF_IPC_PROBE_INIT: - str2 = "INIT"; break; - case SOF_IPC_PROBE_DEINIT: - str2 = "DEINIT"; break; - case SOF_IPC_PROBE_DMA_ADD: - str2 = "DMA_ADD"; break; - case SOF_IPC_PROBE_DMA_INFO: - str2 = "DMA_INFO"; break; - case SOF_IPC_PROBE_DMA_REMOVE: - str2 = "DMA_REMOVE"; break; - case SOF_IPC_PROBE_POINT_ADD: - str2 = "POINT_ADD"; break; - case SOF_IPC_PROBE_POINT_INFO: - str2 = "POINT_INFO"; break; - case SOF_IPC_PROBE_POINT_REMOVE: - str2 = "POINT_REMOVE"; break; - default: - str2 = "unknown type"; break; - } - break; - default: - str = "unknown GLB command"; break; - } - - if (str2) { - if (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) -{ - if ((cmd & SOF_GLB_TYPE_MASK) != SOF_IPC_GLB_TRACE_MSG) - dev_dbg(dev, "%s: 0x%x\n", text, cmd); -} -#endif - -/* wait for IPC message reply */ -static int tx_wait_done(struct snd_sof_ipc *ipc, struct snd_sof_ipc_msg *msg, - void *reply_data) -{ - struct snd_sof_dev *sdev = ipc->sdev; - struct sof_ipc_cmd_hdr *hdr = msg->msg_data; - int ret; - - /* wait for DSP IPC completion */ - ret = wait_event_timeout(msg->waitq, msg->ipc_complete, - msecs_to_jiffies(sdev->ipc_timeout)); - - if (ret == 0) { - dev_err(sdev->dev, - "ipc tx timed out for %#x (msg/reply size: %d/%zu)\n", - hdr->cmd, hdr->size, msg->reply_size); - snd_sof_handle_fw_exception(ipc->sdev); - ret = -ETIMEDOUT; - } else { - ret = msg->reply_error; - if (ret < 0) { - dev_err(sdev->dev, - "ipc tx error for %#x (msg/reply size: %d/%zu): %d\n", - hdr->cmd, hdr->size, msg->reply_size, ret); - } else { - ipc_log_header(sdev->dev, "ipc tx succeeded", hdr->cmd); - if (msg->reply_size) - /* copy the data returned from DSP */ - memcpy(reply_data, msg->reply_data, - msg->reply_size); - } - - /* re-enable dumps after successful IPC tx */ - if (sdev->ipc_dump_printed) { - sdev->dbg_dump_printed = false; - sdev->ipc_dump_printed = false; - } - } - - return ret; -} - -/* send IPC message from host to DSP */ -static int sof_ipc_tx_message_unlocked(struct snd_sof_ipc *ipc, u32 header, - void *msg_data, size_t msg_bytes, - void *reply_data, size_t reply_bytes) +int sof_ipc_send_msg(struct snd_sof_dev *sdev, void *msg_data, size_t msg_bytes, + size_t reply_bytes) { - struct snd_sof_dev *sdev = ipc->sdev; + struct snd_sof_ipc *ipc = sdev->ipc; struct snd_sof_ipc_msg *msg; int ret; - if (ipc->disable_ipc_tx) + if (ipc->disable_ipc_tx || sdev->fw_state != SOF_FW_BOOT_COMPLETE) return -ENODEV; /* - * The spin-lock is also still needed to protect message objects against - * other atomic contexts. + * The spin-lock is needed to protect message objects against other + * atomic contexts. */ spin_lock_irq(&sdev->ipc_lock); /* initialise the message */ msg = &ipc->msg; - msg->header = header; + /* attach message data */ + msg->msg_data = msg_data; msg->msg_size = msg_bytes; + msg->reply_size = reply_bytes; msg->reply_error = 0; - /* attach any data */ - if (msg_bytes) - memcpy(msg->msg_data, msg_data, msg_bytes); - sdev->msg = msg; ret = snd_sof_dsp_send_msg(sdev, msg); @@ -317,38 +68,19 @@ static int sof_ipc_tx_message_unlocked(struct snd_sof_ipc *ipc, u32 header, spin_unlock_irq(&sdev->ipc_lock); - if (ret) { - dev_err_ratelimited(sdev->dev, - "error: ipc tx failed with error %d\n", - ret); - return ret; - } - - ipc_log_header(sdev->dev, "ipc tx", msg->header); - - /* now wait for completion */ - return tx_wait_done(ipc, msg, reply_data); + return ret; } /* send IPC message from host to DSP */ -int sof_ipc_tx_message(struct snd_sof_ipc *ipc, u32 header, - void *msg_data, size_t msg_bytes, void *reply_data, - size_t reply_bytes) +int sof_ipc_tx_message(struct snd_sof_ipc *ipc, void *msg_data, size_t msg_bytes, + void *reply_data, size_t reply_bytes) { - const struct sof_dsp_power_state target_state = { - .state = SOF_DSP_PM_D0, - }; - int ret; - - /* ensure the DSP is in D0 before sending a new IPC */ - ret = snd_sof_dsp_set_power_state(ipc->sdev, &target_state); - if (ret < 0) { - dev_err(ipc->sdev->dev, "error: resuming DSP %d\n", ret); - return ret; - } + if (msg_bytes > ipc->max_payload_size || + reply_bytes > ipc->max_payload_size) + return -ENOBUFS; - return sof_ipc_tx_message_no_pm(ipc, header, msg_data, msg_bytes, - reply_data, reply_bytes); + return ipc->ops->tx_msg(ipc->sdev, msg_data, msg_bytes, reply_data, + reply_bytes, false); } EXPORT_SYMBOL(sof_ipc_tx_message); @@ -357,27 +89,34 @@ EXPORT_SYMBOL(sof_ipc_tx_message); * This will be used for IPC's that can be handled by the DSP * even in a low-power D0 substate. */ -int sof_ipc_tx_message_no_pm(struct snd_sof_ipc *ipc, u32 header, - void *msg_data, size_t msg_bytes, +int sof_ipc_tx_message_no_pm(struct snd_sof_ipc *ipc, void *msg_data, size_t msg_bytes, void *reply_data, size_t reply_bytes) { - int ret; - - if (msg_bytes > SOF_IPC_MSG_MAX_SIZE || - reply_bytes > SOF_IPC_MSG_MAX_SIZE) + if (msg_bytes > ipc->max_payload_size || + reply_bytes > ipc->max_payload_size) return -ENOBUFS; - /* Serialise IPC TX */ - mutex_lock(&ipc->tx_mutex); - - ret = sof_ipc_tx_message_unlocked(ipc, header, msg_data, msg_bytes, - reply_data, reply_bytes); + return ipc->ops->tx_msg(ipc->sdev, msg_data, msg_bytes, reply_data, + reply_bytes, true); +} +EXPORT_SYMBOL(sof_ipc_tx_message_no_pm); - mutex_unlock(&ipc->tx_mutex); +/* Generic helper function to retrieve the reply */ +void snd_sof_ipc_get_reply(struct snd_sof_dev *sdev) +{ + /* + * Sometimes, there is unexpected reply ipc arriving. The reply + * ipc belongs to none of the ipcs sent from driver. + * In this case, the driver must ignore the ipc. + */ + if (!sdev->msg) { + dev_warn(sdev->dev, "unexpected ipc interrupt raised!\n"); + return; + } - return ret; + sdev->msg->reply_error = sdev->ipc->ops->get_reply(sdev); } -EXPORT_SYMBOL(sof_ipc_tx_message_no_pm); +EXPORT_SYMBOL(snd_sof_ipc_get_reply); /* handle reply message from DSP */ void snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id) @@ -397,561 +136,71 @@ void snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id) } EXPORT_SYMBOL(snd_sof_ipc_reply); -static void ipc_comp_notification(struct snd_sof_dev *sdev, - struct sof_ipc_cmd_hdr *hdr) -{ - u32 msg_type = hdr->cmd & SOF_CMD_TYPE_MASK; - struct sof_ipc_ctrl_data *cdata; - int ret; - - switch (msg_type) { - case SOF_IPC_COMP_GET_VALUE: - case SOF_IPC_COMP_GET_DATA: - cdata = kmalloc(hdr->size, GFP_KERNEL); - if (!cdata) - return; - - /* read back full message */ - ret = snd_sof_ipc_msg_data(sdev, NULL, cdata, hdr->size); - if (ret < 0) { - dev_err(sdev->dev, - "error: failed to read component event: %d\n", ret); - goto err; - } - break; - default: - dev_err(sdev->dev, "error: unhandled component message %#x\n", msg_type); - return; - } - - snd_sof_control_notify(sdev, cdata); - -err: - kfree(cdata); -} - -/* DSP firmware has sent host a message */ -void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev) +struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev) { - struct sof_ipc_cmd_hdr hdr; - u32 cmd, type; - int err; - - /* read back header */ - err = snd_sof_ipc_msg_data(sdev, NULL, &hdr, sizeof(hdr)); - if (err < 0) { - dev_warn(sdev->dev, "failed to read IPC header: %d\n", err); - return; - } - ipc_log_header(sdev->dev, "ipc rx", hdr.cmd); - - cmd = hdr.cmd & SOF_GLB_TYPE_MASK; - type = hdr.cmd & SOF_CMD_TYPE_MASK; + struct snd_sof_ipc *ipc; + struct snd_sof_ipc_msg *msg; + const struct sof_ipc_ops *ops; - /* check message type */ - switch (cmd) { - case SOF_IPC_GLB_REPLY: - dev_err(sdev->dev, "error: ipc reply unknown\n"); - break; - case SOF_IPC_FW_READY: - /* check for FW boot completion */ - if (sdev->fw_state == SOF_FW_BOOT_IN_PROGRESS) { - err = sof_ops(sdev)->fw_ready(sdev, cmd); - if (err < 0) - sof_set_fw_state(sdev, SOF_FW_BOOT_READY_FAILED); - else - sof_set_fw_state(sdev, SOF_FW_BOOT_COMPLETE); - - /* wake up firmware loader */ - wake_up(&sdev->boot_wait); - } - break; - case SOF_IPC_GLB_COMPOUND: - case SOF_IPC_GLB_TPLG_MSG: - case SOF_IPC_GLB_PM_MSG: - break; - case SOF_IPC_GLB_COMP_MSG: - ipc_comp_notification(sdev, &hdr); - break; - case SOF_IPC_GLB_STREAM_MSG: - /* need to pass msg id into the function */ - ipc_stream_message(sdev, hdr.cmd); - break; - case SOF_IPC_GLB_TRACE_MSG: - ipc_trace_message(sdev, type); - break; - default: - dev_err(sdev->dev, "error: unknown DSP message 0x%x\n", cmd); - break; - } + ipc = devm_kzalloc(sdev->dev, sizeof(*ipc), GFP_KERNEL); + if (!ipc) + return NULL; - ipc_log_header(sdev->dev, "ipc rx done", hdr.cmd); -} -EXPORT_SYMBOL(snd_sof_ipc_msgs_rx); + mutex_init(&ipc->tx_mutex); + ipc->sdev = sdev; + msg = &ipc->msg; -/* - * IPC trace mechanism. - */ + /* indicate that we aren't sending a message ATM */ + msg->ipc_complete = true; -static void ipc_trace_message(struct snd_sof_dev *sdev, u32 msg_type) -{ - struct sof_ipc_dma_trace_posn posn; - int ret; + init_waitqueue_head(&msg->waitq); - switch (msg_type) { - case SOF_IPC_TRACE_DMA_POSITION: - /* read back full message */ - ret = snd_sof_ipc_msg_data(sdev, NULL, &posn, sizeof(posn)); - if (ret < 0) - dev_warn(sdev->dev, "failed to read trace position: %d\n", ret); - else - snd_sof_trace_update_pos(sdev, &posn); + switch (sdev->pdata->ipc_type) { +#if defined(CONFIG_SND_SOC_SOF_IPC3) + case SOF_IPC: + ops = &ipc3_ops; break; - default: - dev_err(sdev->dev, "error: unhandled trace message %#x\n", msg_type); - break; - } -} - -/* - * IPC stream position. - */ - -static void ipc_period_elapsed(struct snd_sof_dev *sdev, u32 msg_id) -{ - struct snd_soc_component *scomp = sdev->component; - struct snd_sof_pcm_stream *stream; - struct sof_ipc_stream_posn posn; - struct snd_sof_pcm *spcm; - int direction, ret; - - spcm = snd_sof_find_spcm_comp(scomp, msg_id, &direction); - if (!spcm) { - dev_err(sdev->dev, - "error: period elapsed for unknown stream, msg_id %d\n", - msg_id); - return; - } - - stream = &spcm->stream[direction]; - ret = snd_sof_ipc_msg_data(sdev, stream->substream, &posn, sizeof(posn)); - if (ret < 0) { - dev_warn(sdev->dev, "failed to read stream position: %d\n", ret); - return; - } - - 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)); - - if (spcm->pcm.compress) - snd_sof_compr_fragment_elapsed(stream->cstream); - else if (!stream->substream->runtime->no_period_wakeup) - /* only inform ALSA for period_wakeup mode */ - snd_sof_pcm_period_elapsed(stream->substream); -} - -/* DSP notifies host of an XRUN within FW */ -static void 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, ret; - - spcm = snd_sof_find_spcm_comp(scomp, msg_id, &direction); - if (!spcm) { - dev_err(sdev->dev, "error: XRUN for unknown stream, msg_id %d\n", - msg_id); - return; - } - - stream = &spcm->stream[direction]; - ret = snd_sof_ipc_msg_data(sdev, stream->substream, &posn, sizeof(posn)); - if (ret < 0) { - dev_warn(sdev->dev, "failed to read overrun position: %d\n", ret); - return; - } - - dev_dbg(sdev->dev, "posn XRUN: host %llx comp %d size %d\n", - posn.host_posn, posn.xrun_comp_id, posn.xrun_size); - -#if defined(CONFIG_SND_SOC_SOF_DEBUG_XRUN_STOP) - /* stop PCM on XRUN - used for pipeline debug */ - memcpy(&stream->posn, &posn, sizeof(posn)); - snd_pcm_stop_xrun(stream->substream); #endif -} - -/* stream notifications from DSP FW */ -static void ipc_stream_message(struct snd_sof_dev *sdev, u32 msg_cmd) -{ - /* get msg cmd type and msd id */ - u32 msg_type = msg_cmd & SOF_CMD_TYPE_MASK; - u32 msg_id = SOF_IPC_MESSAGE_ID(msg_cmd); - - switch (msg_type) { - case SOF_IPC_STREAM_POSITION: - ipc_period_elapsed(sdev, msg_id); - break; - case SOF_IPC_STREAM_TRIG_XRUN: - ipc_xrun(sdev, msg_id); - break; - default: - dev_err(sdev->dev, "error: unhandled stream message %#x\n", - msg_id); - break; - } -} - -/* get stream position IPC - use faster MMIO method if available on platform */ -int snd_sof_ipc_stream_posn(struct snd_soc_component *scomp, - struct snd_sof_pcm *spcm, int direction, - struct sof_ipc_stream_posn *posn) -{ - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - struct sof_ipc_stream stream; - int err; - - /* read position via slower IPC */ - stream.hdr.size = sizeof(stream); - stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_POSITION; - stream.comp_id = spcm->stream[direction].comp_id; - - /* send IPC to the DSP */ - err = sof_ipc_tx_message(sdev->ipc, - stream.hdr.cmd, &stream, sizeof(stream), posn, - sizeof(*posn)); - if (err < 0) { - dev_err(sdev->dev, "error: failed to get stream %d position\n", - stream.comp_id); - return err; - } - - return 0; -} -EXPORT_SYMBOL(snd_sof_ipc_stream_posn); - -static int sof_get_ctrl_copy_params(enum sof_ipc_ctrl_type ctrl_type, - struct sof_ipc_ctrl_data *src, - struct sof_ipc_ctrl_data *dst, - struct sof_ipc_ctrl_data_params *sparams) -{ - switch (ctrl_type) { - case SOF_CTRL_TYPE_VALUE_CHAN_GET: - case SOF_CTRL_TYPE_VALUE_CHAN_SET: - sparams->src = (u8 *)src->chanv; - sparams->dst = (u8 *)dst->chanv; - break; - case SOF_CTRL_TYPE_VALUE_COMP_GET: - case SOF_CTRL_TYPE_VALUE_COMP_SET: - sparams->src = (u8 *)src->compv; - sparams->dst = (u8 *)dst->compv; - break; - case SOF_CTRL_TYPE_DATA_GET: - case SOF_CTRL_TYPE_DATA_SET: - sparams->src = (u8 *)src->data->data; - sparams->dst = (u8 *)dst->data->data; - break; - default: - return -EINVAL; - } - - /* calculate payload size and number of messages */ - sparams->pl_size = SOF_IPC_MSG_MAX_SIZE - sparams->hdr_bytes; - sparams->num_msg = DIV_ROUND_UP(sparams->msg_bytes, sparams->pl_size); - - return 0; -} - -static int sof_set_get_large_ctrl_data(struct snd_sof_dev *sdev, - struct sof_ipc_ctrl_data *cdata, - struct sof_ipc_ctrl_data_params *sparams, - bool send) -{ - struct sof_ipc_ctrl_data *partdata; - size_t send_bytes; - size_t offset = 0; - size_t msg_bytes; - size_t pl_size; - int err; - int i; - - /* allocate max ipc size because we have at least one */ - partdata = kzalloc(SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); - if (!partdata) - return -ENOMEM; - - if (send) - err = sof_get_ctrl_copy_params(cdata->type, cdata, partdata, - sparams); - else - err = sof_get_ctrl_copy_params(cdata->type, partdata, cdata, - sparams); - if (err < 0) { - kfree(partdata); - return err; - } - - msg_bytes = sparams->msg_bytes; - pl_size = sparams->pl_size; - - /* copy the header data */ - memcpy(partdata, cdata, sparams->hdr_bytes); - - /* Serialise IPC TX */ - mutex_lock(&sdev->ipc->tx_mutex); - - /* copy the payload data in a loop */ - for (i = 0; i < sparams->num_msg; i++) { - send_bytes = min(msg_bytes, pl_size); - partdata->num_elems = send_bytes; - partdata->rhdr.hdr.size = sparams->hdr_bytes + send_bytes; - partdata->msg_index = i; - msg_bytes -= send_bytes; - partdata->elems_remaining = msg_bytes; - - if (send) - memcpy(sparams->dst, sparams->src + offset, send_bytes); - - err = sof_ipc_tx_message_unlocked(sdev->ipc, - partdata->rhdr.hdr.cmd, - partdata, - partdata->rhdr.hdr.size, - partdata, - partdata->rhdr.hdr.size); - if (err < 0) - break; - - if (!send) - memcpy(sparams->dst + offset, sparams->src, send_bytes); - - offset += pl_size; - } - - mutex_unlock(&sdev->ipc->tx_mutex); - - kfree(partdata); - return err; -} - -/* - * IPC get()/set() for kcontrols. - */ -int snd_sof_ipc_set_get_comp_data(struct snd_sof_control *scontrol, - u32 ipc_cmd, - enum sof_ipc_ctrl_type ctrl_type, - enum sof_ipc_ctrl_cmd ctrl_cmd, - bool send) -{ - struct snd_soc_component *scomp = scontrol->scomp; - struct sof_ipc_ctrl_data *cdata = scontrol->control_data; - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - struct sof_ipc_fw_ready *ready = &sdev->fw_ready; - struct sof_ipc_fw_version *v = &ready->version; - struct sof_ipc_ctrl_data_params sparams; - struct snd_sof_widget *swidget; - bool widget_found = false; - size_t send_bytes; - int err; - - list_for_each_entry(swidget, &sdev->widget_list, list) { - if (swidget->comp_id == scontrol->comp_id) { - widget_found = true; - break; - } - } - - if (!widget_found) { - dev_err(sdev->dev, "error: can't find widget with id %d\n", scontrol->comp_id); - return -EINVAL; - } - - /* - * Volatile controls should always be part of static pipelines and the widget use_count - * would always be > 0 in this case. For the others, just return the cached value if the - * widget is not set up. - */ - if (!swidget->use_count) - return 0; - - /* read or write firmware volume */ - if (scontrol->readback_offset != 0) { - /* write/read value header via mmaped region */ - send_bytes = sizeof(struct sof_ipc_ctrl_value_chan) * - cdata->num_elems; - if (send) - err = snd_sof_dsp_block_write(sdev, SOF_FW_BLK_TYPE_IRAM, - scontrol->readback_offset, - cdata->chanv, send_bytes); - - else - err = snd_sof_dsp_block_read(sdev, SOF_FW_BLK_TYPE_IRAM, - scontrol->readback_offset, - cdata->chanv, send_bytes); - - if (err) - dev_err_once(sdev->dev, "error: %s TYPE_IRAM failed\n", - send ? "write to" : "read from"); - return err; - } - - cdata->rhdr.hdr.cmd = SOF_IPC_GLB_COMP_MSG | ipc_cmd; - cdata->cmd = ctrl_cmd; - cdata->type = ctrl_type; - cdata->comp_id = scontrol->comp_id; - cdata->msg_index = 0; - - /* calculate header and data size */ - switch (cdata->type) { - case SOF_CTRL_TYPE_VALUE_CHAN_GET: - case SOF_CTRL_TYPE_VALUE_CHAN_SET: - sparams.msg_bytes = scontrol->num_channels * - sizeof(struct sof_ipc_ctrl_value_chan); - sparams.hdr_bytes = sizeof(struct sof_ipc_ctrl_data); - sparams.elems = scontrol->num_channels; - break; - case SOF_CTRL_TYPE_VALUE_COMP_GET: - case SOF_CTRL_TYPE_VALUE_COMP_SET: - sparams.msg_bytes = scontrol->num_channels * - sizeof(struct sof_ipc_ctrl_value_comp); - sparams.hdr_bytes = sizeof(struct sof_ipc_ctrl_data); - sparams.elems = scontrol->num_channels; - break; - case SOF_CTRL_TYPE_DATA_GET: - case SOF_CTRL_TYPE_DATA_SET: - sparams.msg_bytes = cdata->data->size; - sparams.hdr_bytes = sizeof(struct sof_ipc_ctrl_data) + - sizeof(struct sof_abi_hdr); - sparams.elems = cdata->data->size; +#if defined(CONFIG_SND_SOC_SOF_INTEL_IPC4) + case SOF_INTEL_IPC4: + ops = &ipc4_ops; break; +#endif default: - return -EINVAL; - } - - cdata->rhdr.hdr.size = sparams.msg_bytes + sparams.hdr_bytes; - cdata->num_elems = sparams.elems; - cdata->elems_remaining = 0; - - /* send normal size ipc in one part */ - if (cdata->rhdr.hdr.size <= SOF_IPC_MSG_MAX_SIZE) { - err = sof_ipc_tx_message(sdev->ipc, cdata->rhdr.hdr.cmd, cdata, - cdata->rhdr.hdr.size, cdata, - cdata->rhdr.hdr.size); - - if (err < 0) - dev_err(sdev->dev, "error: set/get ctrl ipc comp %d\n", - cdata->comp_id); - - return err; + dev_err(sdev->dev, "Not supported IPC version: %d\n", + sdev->pdata->ipc_type); + return NULL; } - /* data is bigger than max ipc size, chop into smaller pieces */ - dev_dbg(sdev->dev, "large ipc size %u, control size %u\n", - cdata->rhdr.hdr.size, scontrol->size); - - /* large messages is only supported from ABI 3.3.0 onwards */ - if (v->abi_version < SOF_ABI_VER(3, 3, 0)) { - dev_err(sdev->dev, "error: incompatible FW ABI version\n"); - return -EINVAL; + /* check for mandatory ops */ + if (!ops->tx_msg || !ops->rx_msg || !ops->set_get_data || !ops->get_reply) { + dev_err(sdev->dev, "Missing IPC message handling ops\n"); + return NULL; } - err = sof_set_get_large_ctrl_data(sdev, cdata, &sparams, send); - - if (err < 0) - dev_err(sdev->dev, "error: set/get large ctrl ipc comp %d\n", - cdata->comp_id); - - return err; -} -EXPORT_SYMBOL(snd_sof_ipc_set_get_comp_data); - -int snd_sof_ipc_valid(struct snd_sof_dev *sdev) -{ - struct sof_ipc_fw_ready *ready = &sdev->fw_ready; - struct sof_ipc_fw_version *v = &ready->version; - - dev_info(sdev->dev, - "Firmware info: version %d:%d:%d-%s\n", v->major, v->minor, - v->micro, v->tag); - dev_info(sdev->dev, - "Firmware: ABI %d:%d:%d Kernel ABI %d:%d:%d\n", - SOF_ABI_VERSION_MAJOR(v->abi_version), - SOF_ABI_VERSION_MINOR(v->abi_version), - SOF_ABI_VERSION_PATCH(v->abi_version), - SOF_ABI_MAJOR, SOF_ABI_MINOR, SOF_ABI_PATCH); - - if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, v->abi_version)) { - dev_err(sdev->dev, "error: incompatible FW ABI version\n"); - return -EINVAL; + if (!ops->fw_loader || !ops->fw_loader->validate || + !ops->fw_loader->parse_ext_manifest) { + dev_err(sdev->dev, "Missing IPC firmware loading ops\n"); + return NULL; } - if (SOF_ABI_VERSION_MINOR(v->abi_version) > SOF_ABI_MINOR) { - if (!IS_ENABLED(CONFIG_SND_SOC_SOF_STRICT_ABI_CHECKS)) { - dev_warn(sdev->dev, "warn: FW ABI is more recent than kernel\n"); - } else { - dev_err(sdev->dev, "error: FW ABI is more recent than kernel\n"); - return -EINVAL; - } + if (!ops->pcm) { + dev_err(sdev->dev, "Missing IPC PCM ops\n"); + return NULL; } - if (ready->flags & SOF_IPC_INFO_BUILD) { - dev_info(sdev->dev, - "Firmware debug build %d on %s-%s - options:\n" - " GDB: %s\n" - " lock debug: %s\n" - " lock vdebug: %s\n", - v->build, v->date, v->time, - (ready->flags & SOF_IPC_INFO_GDB) ? - "enabled" : "disabled", - (ready->flags & SOF_IPC_INFO_LOCKS) ? - "enabled" : "disabled", - (ready->flags & SOF_IPC_INFO_LOCKSV) ? - "enabled" : "disabled"); + if (!ops->tplg || !ops->tplg->widget || !ops->tplg->control) { + dev_err(sdev->dev, "Missing IPC topology ops\n"); + return NULL; } - /* copy the fw_version into debugfs at first boot */ - memcpy(&sdev->fw_version, v, sizeof(*v)); - - return 0; -} -EXPORT_SYMBOL(snd_sof_ipc_valid); - -int sof_ipc_init_msg_memory(struct snd_sof_dev *sdev) -{ - struct snd_sof_ipc_msg *msg; - - msg = &sdev->ipc->msg; - msg->msg_data = devm_kzalloc(sdev->dev, SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); - if (!msg->msg_data) - return -ENOMEM; - - msg->reply_data = devm_kzalloc(sdev->dev, SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); - if (!msg->reply_data) - return -ENOMEM; - - return 0; -} - -struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev) -{ - struct snd_sof_ipc *ipc; - struct snd_sof_ipc_msg *msg; - - ipc = devm_kzalloc(sdev->dev, sizeof(*ipc), GFP_KERNEL); - if (!ipc) + if (ops->fw_tracing && (!ops->fw_tracing->init || !ops->fw_tracing->suspend || + !ops->fw_tracing->resume)) { + dev_err(sdev->dev, "Missing firmware tracing ops\n"); return NULL; + } - mutex_init(&ipc->tx_mutex); - ipc->sdev = sdev; - msg = &ipc->msg; - - /* indicate that we aren't sending a message ATM */ - msg->ipc_complete = true; - - init_waitqueue_head(&msg->waitq); + ipc->ops = ops; return ipc; } diff --git a/sound/soc/sof/ipc3-control.c b/sound/soc/sof/ipc3-control.c new file mode 100644 index 000000000000..3fdc0d854e65 --- /dev/null +++ b/sound/soc/sof/ipc3-control.c @@ -0,0 +1,709 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Intel Corporation. All rights reserved. +// +// + +#include "sof-priv.h" +#include "sof-audio.h" +#include "ipc3-priv.h" + +/* IPC set()/get() for kcontrols. */ +static int sof_ipc3_set_get_kcontrol_data(struct snd_sof_control *scontrol, bool set) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scontrol->scomp); + struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; + const struct sof_ipc_ops *iops = sdev->ipc->ops; + enum sof_ipc_ctrl_type ctrl_type; + struct snd_sof_widget *swidget; + bool widget_found = false; + u32 ipc_cmd, msg_bytes; + + list_for_each_entry(swidget, &sdev->widget_list, list) { + if (swidget->comp_id == scontrol->comp_id) { + widget_found = true; + break; + } + } + + if (!widget_found) { + dev_err(sdev->dev, "%s: can't find widget with id %d\n", __func__, + scontrol->comp_id); + return -EINVAL; + } + + /* + * Volatile controls should always be part of static pipelines and the widget use_count + * would always be > 0 in this case. For the others, just return the cached value if the + * widget is not set up. + */ + if (!swidget->use_count) + return 0; + + /* + * Select the IPC cmd and the ctrl_type based on the ctrl_cmd and the + * direction + * Note: SOF_CTRL_TYPE_VALUE_COMP_* is not used and supported currently + * for ctrl_type + */ + if (cdata->cmd == SOF_CTRL_CMD_BINARY) { + ipc_cmd = set ? SOF_IPC_COMP_SET_DATA : SOF_IPC_COMP_GET_DATA; + ctrl_type = set ? SOF_CTRL_TYPE_DATA_SET : SOF_CTRL_TYPE_DATA_GET; + } else { + ipc_cmd = set ? SOF_IPC_COMP_SET_VALUE : SOF_IPC_COMP_GET_VALUE; + ctrl_type = set ? SOF_CTRL_TYPE_VALUE_CHAN_SET : SOF_CTRL_TYPE_VALUE_CHAN_GET; + } + + cdata->rhdr.hdr.cmd = SOF_IPC_GLB_COMP_MSG | ipc_cmd; + cdata->type = ctrl_type; + cdata->comp_id = scontrol->comp_id; + cdata->msg_index = 0; + + /* calculate header and data size */ + switch (cdata->type) { + case SOF_CTRL_TYPE_VALUE_CHAN_GET: + case SOF_CTRL_TYPE_VALUE_CHAN_SET: + cdata->num_elems = scontrol->num_channels; + + msg_bytes = scontrol->num_channels * + sizeof(struct sof_ipc_ctrl_value_chan); + msg_bytes += sizeof(struct sof_ipc_ctrl_data); + break; + case SOF_CTRL_TYPE_DATA_GET: + case SOF_CTRL_TYPE_DATA_SET: + cdata->num_elems = cdata->data->size; + + msg_bytes = cdata->data->size; + msg_bytes += sizeof(struct sof_ipc_ctrl_data) + + sizeof(struct sof_abi_hdr); + break; + default: + return -EINVAL; + } + + cdata->rhdr.hdr.size = msg_bytes; + cdata->elems_remaining = 0; + + return iops->set_get_data(sdev, cdata, cdata->rhdr.hdr.size, set); +} + +static void snd_sof_refresh_control(struct snd_sof_control *scontrol) +{ + struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; + struct snd_soc_component *scomp = scontrol->scomp; + int ret; + + if (!scontrol->comp_data_dirty) + return; + + if (!pm_runtime_active(scomp->dev)) + return; + + /* set the ABI header values */ + cdata->data->magic = SOF_ABI_MAGIC; + cdata->data->abi = SOF_ABI_VERSION; + + /* refresh the component data from DSP */ + scontrol->comp_data_dirty = false; + ret = sof_ipc3_set_get_kcontrol_data(scontrol, false); + if (ret < 0) { + dev_err(scomp->dev, "Failed to get control data: %d\n", ret); + + /* Set the flag to re-try next time to get the data */ + scontrol->comp_data_dirty = true; + } +} + +static int sof_ipc3_volume_get(struct snd_sof_control *scontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; + unsigned int channels = scontrol->num_channels; + unsigned int i; + + snd_sof_refresh_control(scontrol); + + /* read back each channel */ + for (i = 0; i < channels; i++) + ucontrol->value.integer.value[i] = ipc_to_mixer(cdata->chanv[i].value, + scontrol->volume_table, + scontrol->max + 1); + + return 0; +} + +static bool sof_ipc3_volume_put(struct snd_sof_control *scontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; + struct snd_soc_component *scomp = scontrol->scomp; + unsigned int channels = scontrol->num_channels; + unsigned int i; + bool change = false; + + /* update each channel */ + for (i = 0; i < channels; i++) { + u32 value = mixer_to_ipc(ucontrol->value.integer.value[i], + scontrol->volume_table, scontrol->max + 1); + + change = change || (value != cdata->chanv[i].value); + cdata->chanv[i].channel = i; + cdata->chanv[i].value = value; + } + + /* notify DSP of mixer updates */ + if (pm_runtime_active(scomp->dev)) { + int ret = sof_ipc3_set_get_kcontrol_data(scontrol, true); + + if (ret < 0) { + dev_err(scomp->dev, "Failed to set mixer updates for %s\n", + scontrol->name); + return false; + } + } + + return change; +} + +static int sof_ipc3_switch_get(struct snd_sof_control *scontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; + unsigned int channels = scontrol->num_channels; + unsigned int i; + + snd_sof_refresh_control(scontrol); + + /* read back each channel */ + for (i = 0; i < channels; i++) + ucontrol->value.integer.value[i] = cdata->chanv[i].value; + + return 0; +} + +static bool sof_ipc3_switch_put(struct snd_sof_control *scontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; + struct snd_soc_component *scomp = scontrol->scomp; + unsigned int channels = scontrol->num_channels; + unsigned int i; + bool change = false; + u32 value; + + /* update each channel */ + for (i = 0; i < channels; i++) { + value = ucontrol->value.integer.value[i]; + change = change || (value != cdata->chanv[i].value); + cdata->chanv[i].channel = i; + cdata->chanv[i].value = value; + } + + /* notify DSP of mixer updates */ + if (pm_runtime_active(scomp->dev)) { + int ret = sof_ipc3_set_get_kcontrol_data(scontrol, true); + + if (ret < 0) { + dev_err(scomp->dev, "Failed to set mixer updates for %s\n", + scontrol->name); + return false; + } + } + + return change; +} + +static int sof_ipc3_enum_get(struct snd_sof_control *scontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; + unsigned int channels = scontrol->num_channels; + unsigned int i; + + snd_sof_refresh_control(scontrol); + + /* read back each channel */ + for (i = 0; i < channels; i++) + ucontrol->value.enumerated.item[i] = cdata->chanv[i].value; + + return 0; +} + +static bool sof_ipc3_enum_put(struct snd_sof_control *scontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; + struct snd_soc_component *scomp = scontrol->scomp; + unsigned int channels = scontrol->num_channels; + unsigned int i; + bool change = false; + u32 value; + + /* update each channel */ + for (i = 0; i < channels; i++) { + value = ucontrol->value.enumerated.item[i]; + change = change || (value != cdata->chanv[i].value); + cdata->chanv[i].channel = i; + cdata->chanv[i].value = value; + } + + /* notify DSP of enum updates */ + if (pm_runtime_active(scomp->dev)) { + int ret = sof_ipc3_set_get_kcontrol_data(scontrol, true); + + if (ret < 0) { + dev_err(scomp->dev, "Failed to set enum updates for %s\n", + scontrol->name); + return false; + } + } + + return change; +} + +static int sof_ipc3_bytes_get(struct snd_sof_control *scontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; + struct snd_soc_component *scomp = scontrol->scomp; + struct sof_abi_hdr *data = cdata->data; + size_t size; + + snd_sof_refresh_control(scontrol); + + if (scontrol->max_size > sizeof(ucontrol->value.bytes.data)) { + dev_err_ratelimited(scomp->dev, "data max %zu exceeds ucontrol data array size\n", + scontrol->max_size); + return -EINVAL; + } + + /* be->max has been verified to be >= sizeof(struct sof_abi_hdr) */ + if (data->size > scontrol->max_size - sizeof(*data)) { + dev_err_ratelimited(scomp->dev, + "%u bytes of control data is invalid, max is %zu\n", + data->size, scontrol->max_size - sizeof(*data)); + return -EINVAL; + } + + size = data->size + sizeof(*data); + + /* copy back to kcontrol */ + memcpy(ucontrol->value.bytes.data, data, size); + + return 0; +} + +static int sof_ipc3_bytes_put(struct snd_sof_control *scontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; + struct snd_soc_component *scomp = scontrol->scomp; + struct sof_abi_hdr *data = cdata->data; + size_t size; + + if (scontrol->max_size > sizeof(ucontrol->value.bytes.data)) { + dev_err_ratelimited(scomp->dev, "data max %zu exceeds ucontrol data array size\n", + scontrol->max_size); + return -EINVAL; + } + + /* scontrol->max_size has been verified to be >= sizeof(struct sof_abi_hdr) */ + if (data->size > scontrol->max_size - sizeof(*data)) { + dev_err_ratelimited(scomp->dev, "data size too big %u bytes max is %zu\n", + data->size, scontrol->max_size - sizeof(*data)); + return -EINVAL; + } + + size = data->size + sizeof(*data); + + /* copy from kcontrol */ + memcpy(data, ucontrol->value.bytes.data, size); + + /* notify DSP of byte control updates */ + if (pm_runtime_active(scomp->dev)) + return sof_ipc3_set_get_kcontrol_data(scontrol, true); + + return 0; +} + +static int sof_ipc3_bytes_ext_get(struct snd_sof_control *scontrol, + const unsigned int __user *binary_data, unsigned int size) +{ + struct snd_ctl_tlv __user *tlvd = (struct snd_ctl_tlv __user *)binary_data; + struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; + struct snd_soc_component *scomp = scontrol->scomp; + struct snd_ctl_tlv header; + size_t data_size; + + snd_sof_refresh_control(scontrol); + + /* + * Decrement the limit by ext bytes header size to + * ensure the user space buffer is not exceeded. + */ + if (size < sizeof(struct snd_ctl_tlv)) + return -ENOSPC; + + size -= sizeof(struct snd_ctl_tlv); + + /* set the ABI header values */ + cdata->data->magic = SOF_ABI_MAGIC; + cdata->data->abi = SOF_ABI_VERSION; + + /* check data size doesn't exceed max coming from topology */ + if (cdata->data->size > scontrol->max_size - sizeof(struct sof_abi_hdr)) { + dev_err_ratelimited(scomp->dev, "User data size %d exceeds max size %zu\n", + cdata->data->size, + scontrol->max_size - sizeof(struct sof_abi_hdr)); + return -EINVAL; + } + + data_size = cdata->data->size + sizeof(struct sof_abi_hdr); + + /* make sure we don't exceed size provided by user space for data */ + if (data_size > size) + return -ENOSPC; + + header.numid = cdata->cmd; + header.length = data_size; + if (copy_to_user(tlvd, &header, sizeof(struct snd_ctl_tlv))) + return -EFAULT; + + if (copy_to_user(tlvd->tlv, cdata->data, data_size)) + return -EFAULT; + + return 0; +} + +static int sof_ipc3_bytes_ext_put(struct snd_sof_control *scontrol, + const unsigned int __user *binary_data, + unsigned int size) +{ + const struct snd_ctl_tlv __user *tlvd = (const struct snd_ctl_tlv __user *)binary_data; + struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; + struct snd_soc_component *scomp = scontrol->scomp; + struct snd_ctl_tlv header; + + /* + * The beginning of bytes data contains a header from where + * the length (as bytes) is needed to know the correct copy + * length of data from tlvd->tlv. + */ + if (copy_from_user(&header, tlvd, sizeof(struct snd_ctl_tlv))) + return -EFAULT; + + /* make sure TLV info is consistent */ + if (header.length + sizeof(struct snd_ctl_tlv) > size) { + dev_err_ratelimited(scomp->dev, "Inconsistent TLV, data %d + header %zu > %d\n", + header.length, sizeof(struct snd_ctl_tlv), size); + return -EINVAL; + } + + /* be->max is coming from topology */ + if (header.length > scontrol->max_size) { + dev_err_ratelimited(scomp->dev, "Bytes data size %d exceeds max %zu\n", + header.length, scontrol->max_size); + return -EINVAL; + } + + /* Check that header id matches the command */ + if (header.numid != cdata->cmd) { + dev_err_ratelimited(scomp->dev, "Incorrect command for bytes put %d\n", + header.numid); + return -EINVAL; + } + + if (copy_from_user(cdata->data, tlvd->tlv, header.length)) + return -EFAULT; + + if (cdata->data->magic != SOF_ABI_MAGIC) { + dev_err_ratelimited(scomp->dev, "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(scomp->dev, "Incompatible ABI version 0x%08x\n", + cdata->data->abi); + return -EINVAL; + } + + /* be->max has been verified to be >= sizeof(struct sof_abi_hdr) */ + if (cdata->data->size > scontrol->max_size - sizeof(struct sof_abi_hdr)) { + dev_err_ratelimited(scomp->dev, "Mismatch in ABI data size (truncated?)\n"); + return -EINVAL; + } + + /* notify DSP of byte control updates */ + if (pm_runtime_active(scomp->dev)) + return sof_ipc3_set_get_kcontrol_data(scontrol, true); + + return 0; +} + +static int sof_ipc3_bytes_ext_volatile_get(struct snd_sof_control *scontrol, + const unsigned int __user *binary_data, + unsigned int size) +{ + struct snd_ctl_tlv __user *tlvd = (struct snd_ctl_tlv __user *)binary_data; + struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; + struct snd_soc_component *scomp = scontrol->scomp; + struct snd_ctl_tlv header; + size_t data_size; + int ret; + + /* + * Decrement the limit by ext bytes header size to + * ensure the user space buffer is not exceeded. + */ + if (size < sizeof(struct snd_ctl_tlv)) + return -ENOSPC; + + size -= sizeof(struct snd_ctl_tlv); + + /* set the ABI header values */ + cdata->data->magic = SOF_ABI_MAGIC; + cdata->data->abi = SOF_ABI_VERSION; + + /* get all the component data from DSP */ + ret = sof_ipc3_set_get_kcontrol_data(scontrol, false); + if (ret < 0) + return ret; + + /* check data size doesn't exceed max coming from topology */ + if (cdata->data->size > scontrol->max_size - sizeof(struct sof_abi_hdr)) { + dev_err_ratelimited(scomp->dev, "User data size %d exceeds max size %zu\n", + cdata->data->size, + scontrol->max_size - sizeof(struct sof_abi_hdr)); + return -EINVAL; + } + + data_size = cdata->data->size + sizeof(struct sof_abi_hdr); + + /* make sure we don't exceed size provided by user space for data */ + if (data_size > size) + return -ENOSPC; + + header.numid = cdata->cmd; + header.length = data_size; + if (copy_to_user(tlvd, &header, sizeof(struct snd_ctl_tlv))) + return -EFAULT; + + if (copy_to_user(tlvd->tlv, cdata->data, data_size)) + return -EFAULT; + + return ret; +} + +static void snd_sof_update_control(struct snd_sof_control *scontrol, + struct sof_ipc_ctrl_data *cdata) +{ + struct snd_soc_component *scomp = scontrol->scomp; + struct sof_ipc_ctrl_data *local_cdata; + int i; + + local_cdata = scontrol->ipc_control_data; + + if (cdata->cmd == SOF_CTRL_CMD_BINARY) { + if (cdata->num_elems != local_cdata->data->size) { + dev_err(scomp->dev, "cdata binary size mismatch %u - %u\n", + cdata->num_elems, local_cdata->data->size); + return; + } + + /* copy the new binary data */ + memcpy(local_cdata->data, cdata->data, cdata->num_elems); + } else if (cdata->num_elems != scontrol->num_channels) { + dev_err(scomp->dev, "cdata channel count mismatch %u - %d\n", + cdata->num_elems, scontrol->num_channels); + } else { + /* copy the new values */ + for (i = 0; i < cdata->num_elems; i++) + local_cdata->chanv[i].value = cdata->chanv[i].value; + } +} + +static void sof_ipc3_control_update(struct snd_sof_dev *sdev, void *ipc_control_message) +{ + struct sof_ipc_ctrl_data *cdata = ipc_control_message; + struct snd_soc_dapm_widget *widget; + struct snd_sof_control *scontrol; + struct snd_sof_widget *swidget; + struct snd_kcontrol *kc = NULL; + struct soc_mixer_control *sm; + struct soc_bytes_ext *be; + size_t expected_size; + struct soc_enum *se; + bool found = false; + int i, type; + + if (cdata->type == SOF_CTRL_TYPE_VALUE_COMP_GET || + cdata->type == SOF_CTRL_TYPE_VALUE_COMP_SET) { + dev_err(sdev->dev, "Component data is not supported in control notification\n"); + return; + } + + /* Find the swidget first */ + list_for_each_entry(swidget, &sdev->widget_list, list) { + if (swidget->comp_id == cdata->comp_id) { + found = true; + break; + } + } + + if (!found) + return; + + /* Translate SOF cmd to TPLG type */ + switch (cdata->cmd) { + case SOF_CTRL_CMD_VOLUME: + case SOF_CTRL_CMD_SWITCH: + type = SND_SOC_TPLG_TYPE_MIXER; + break; + case SOF_CTRL_CMD_BINARY: + type = SND_SOC_TPLG_TYPE_BYTES; + break; + case SOF_CTRL_CMD_ENUM: + type = SND_SOC_TPLG_TYPE_ENUM; + break; + default: + dev_err(sdev->dev, "Unknown cmd %u in %s\n", cdata->cmd, __func__); + return; + } + + widget = swidget->widget; + for (i = 0; i < widget->num_kcontrols; i++) { + /* skip non matching types or non matching indexes within type */ + if (widget->dobj.widget.kcontrol_type[i] == type && + widget->kcontrol_news[i].index == cdata->index) { + kc = widget->kcontrols[i]; + break; + } + } + + if (!kc) + return; + + switch (cdata->cmd) { + case SOF_CTRL_CMD_VOLUME: + case SOF_CTRL_CMD_SWITCH: + sm = (struct soc_mixer_control *)kc->private_value; + scontrol = sm->dobj.private; + break; + case SOF_CTRL_CMD_BINARY: + be = (struct soc_bytes_ext *)kc->private_value; + scontrol = be->dobj.private; + break; + case SOF_CTRL_CMD_ENUM: + se = (struct soc_enum *)kc->private_value; + scontrol = se->dobj.private; + break; + default: + return; + } + + expected_size = sizeof(struct sof_ipc_ctrl_data); + switch (cdata->type) { + case SOF_CTRL_TYPE_VALUE_CHAN_GET: + case SOF_CTRL_TYPE_VALUE_CHAN_SET: + expected_size += cdata->num_elems * + sizeof(struct sof_ipc_ctrl_value_chan); + break; + case SOF_CTRL_TYPE_DATA_GET: + case SOF_CTRL_TYPE_DATA_SET: + expected_size += cdata->num_elems + sizeof(struct sof_abi_hdr); + break; + default: + return; + } + + if (cdata->rhdr.hdr.size != expected_size) { + dev_err(sdev->dev, "Component notification size mismatch\n"); + return; + } + + if (cdata->num_elems) + /* + * The message includes the updated value/data, update the + * control's local cache using the received notification + */ + snd_sof_update_control(scontrol, cdata); + else + /* Mark the scontrol that the value/data is changed in SOF */ + scontrol->comp_data_dirty = true; + + snd_ctl_notify_one(swidget->scomp->card->snd_card, SNDRV_CTL_EVENT_MASK_VALUE, kc, 0); +} + +static int sof_ipc3_widget_kcontrol_setup(struct snd_sof_dev *sdev, + struct snd_sof_widget *swidget) +{ + struct snd_sof_control *scontrol; + int ret; + + /* set up all controls for the widget */ + list_for_each_entry(scontrol, &sdev->kcontrol_list, list) + if (scontrol->comp_id == swidget->comp_id) { + /* set kcontrol data in DSP */ + ret = sof_ipc3_set_get_kcontrol_data(scontrol, true); + if (ret < 0) { + dev_err(sdev->dev, + "kcontrol %d set up failed for widget %s\n", + scontrol->comp_id, swidget->widget->name); + return ret; + } + + /* + * Read back the data from the DSP for static widgets. + * This is particularly useful for binary kcontrols + * associated with static pipeline widgets to initialize + * the data size to match that in the DSP. + */ + if (swidget->dynamic_pipeline_widget) + continue; + + ret = sof_ipc3_set_get_kcontrol_data(scontrol, false); + if (ret < 0) + dev_warn(sdev->dev, + "kcontrol %d read failed for widget %s\n", + scontrol->comp_id, swidget->widget->name); + } + + return 0; +} + +static int +sof_ipc3_set_up_volume_table(struct snd_sof_control *scontrol, int tlv[SOF_TLV_ITEMS], int size) +{ + int i; + + /* init the volume table */ + scontrol->volume_table = kcalloc(size, sizeof(u32), GFP_KERNEL); + if (!scontrol->volume_table) + return -ENOMEM; + + /* populate the volume table */ + for (i = 0; i < size ; i++) + scontrol->volume_table[i] = vol_compute_gain(i, tlv); + + return 0; +} + +const struct sof_ipc_tplg_control_ops tplg_ipc3_control_ops = { + .volume_put = sof_ipc3_volume_put, + .volume_get = sof_ipc3_volume_get, + .switch_put = sof_ipc3_switch_put, + .switch_get = sof_ipc3_switch_get, + .enum_put = sof_ipc3_enum_put, + .enum_get = sof_ipc3_enum_get, + .bytes_put = sof_ipc3_bytes_put, + .bytes_get = sof_ipc3_bytes_get, + .bytes_ext_put = sof_ipc3_bytes_ext_put, + .bytes_ext_get = sof_ipc3_bytes_ext_get, + .bytes_ext_volatile_get = sof_ipc3_bytes_ext_volatile_get, + .update = sof_ipc3_control_update, + .widget_kcontrol_setup = sof_ipc3_widget_kcontrol_setup, + .set_up_volume_table = sof_ipc3_set_up_volume_table, +}; diff --git a/sound/soc/sof/ipc3-dtrace.c b/sound/soc/sof/ipc3-dtrace.c new file mode 100644 index 000000000000..b815b0244d9e --- /dev/null +++ b/sound/soc/sof/ipc3-dtrace.c @@ -0,0 +1,676 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> + +#include <linux/debugfs.h> +#include <linux/sched/signal.h> +#include "sof-priv.h" +#include "sof-audio.h" +#include "ops.h" +#include "sof-utils.h" +#include "ipc3-priv.h" + +#define TRACE_FILTER_ELEMENTS_PER_ENTRY 4 +#define TRACE_FILTER_MAX_CONFIG_STRING_LENGTH 1024 + +enum sof_dtrace_state { + SOF_DTRACE_DISABLED, + SOF_DTRACE_STOPPED, + SOF_DTRACE_INITIALIZING, + SOF_DTRACE_ENABLED, +}; + +struct sof_dtrace_priv { + struct snd_dma_buffer dmatb; + struct snd_dma_buffer dmatp; + int dma_trace_pages; + wait_queue_head_t trace_sleep; + u32 host_offset; + bool dtrace_error; + bool dtrace_draining; + enum sof_dtrace_state dtrace_state; +}; + +static bool trace_pos_update_expected(struct sof_dtrace_priv *priv) +{ + if (priv->dtrace_state == SOF_DTRACE_ENABLED || + priv->dtrace_state == SOF_DTRACE_INITIALIZING) + return true; + + return false; +} + +static int trace_filter_append_elem(struct snd_sof_dev *sdev, u32 key, u32 value, + struct sof_ipc_trace_filter_elem *elem_list, + int capacity, int *counter) +{ + if (*counter >= capacity) + return -ENOMEM; + + elem_list[*counter].key = key; + elem_list[*counter].value = value; + ++*counter; + + return 0; +} + +static int trace_filter_parse_entry(struct snd_sof_dev *sdev, const char *line, + struct sof_ipc_trace_filter_elem *elem, + int capacity, int *counter) +{ + int log_level, pipe_id, comp_id, read, ret; + int len = strlen(line); + int cnt = *counter; + u32 uuid_id; + + /* ignore empty content */ + ret = sscanf(line, " %n", &read); + if (!ret && read == len) + return len; + + ret = sscanf(line, " %d %x %d %d %n", &log_level, &uuid_id, &pipe_id, &comp_id, &read); + if (ret != TRACE_FILTER_ELEMENTS_PER_ENTRY || read != len) { + dev_err(sdev->dev, "Invalid trace filter entry '%s'\n", line); + return -EINVAL; + } + + if (uuid_id > 0) { + ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_BY_UUID, + uuid_id, elem, capacity, &cnt); + if (ret) + return ret; + } + if (pipe_id >= 0) { + ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_BY_PIPE, + pipe_id, elem, capacity, &cnt); + if (ret) + return ret; + } + if (comp_id >= 0) { + ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_BY_COMP, + comp_id, elem, capacity, &cnt); + if (ret) + return ret; + } + + ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_SET_LEVEL | + SOF_IPC_TRACE_FILTER_ELEM_FIN, + log_level, elem, capacity, &cnt); + if (ret) + return ret; + + /* update counter only when parsing whole entry passed */ + *counter = cnt; + + return len; +} + +static int trace_filter_parse(struct snd_sof_dev *sdev, char *string, + int *out_elem_cnt, + struct sof_ipc_trace_filter_elem **out) +{ + static const char entry_delimiter[] = ";"; + char *entry = string; + int capacity = 0; + int entry_len; + int cnt = 0; + + /* + * Each entry contains at least 1, up to TRACE_FILTER_ELEMENTS_PER_ENTRY + * IPC elements, depending on content. Calculate IPC elements capacity + * for the input string where each element is set. + */ + while (entry) { + capacity += TRACE_FILTER_ELEMENTS_PER_ENTRY; + entry = strchr(entry + 1, entry_delimiter[0]); + } + *out = kmalloc(capacity * sizeof(**out), GFP_KERNEL); + if (!*out) + return -ENOMEM; + + /* split input string by ';', and parse each entry separately in trace_filter_parse_entry */ + while ((entry = strsep(&string, entry_delimiter))) { + entry_len = trace_filter_parse_entry(sdev, entry, *out, capacity, &cnt); + if (entry_len < 0) { + dev_err(sdev->dev, + "Parsing filter entry '%s' failed with %d\n", + entry, entry_len); + return -EINVAL; + } + } + + *out_elem_cnt = cnt; + + return 0; +} + +static int ipc3_trace_update_filter(struct snd_sof_dev *sdev, int num_elems, + struct sof_ipc_trace_filter_elem *elems) +{ + struct sof_ipc_trace_filter *msg; + struct sof_ipc_reply reply; + size_t size; + int ret; + + size = struct_size(msg, elems, num_elems); + if (size > SOF_IPC_MSG_MAX_SIZE) + return -EINVAL; + + msg = kmalloc(size, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + msg->hdr.size = size; + msg->hdr.cmd = SOF_IPC_GLB_TRACE_MSG | SOF_IPC_TRACE_FILTER_UPDATE; + msg->elem_cnt = num_elems; + memcpy(&msg->elems[0], elems, num_elems * sizeof(*elems)); + + ret = pm_runtime_resume_and_get(sdev->dev); + if (ret < 0 && ret != -EACCES) { + dev_err(sdev->dev, "enabling device failed: %d\n", ret); + goto error; + } + ret = sof_ipc_tx_message(sdev->ipc, msg, msg->hdr.size, &reply, sizeof(reply)); + pm_runtime_mark_last_busy(sdev->dev); + pm_runtime_put_autosuspend(sdev->dev); + +error: + kfree(msg); + return ret ? ret : reply.error; +} + +static ssize_t dfsentry_trace_filter_write(struct file *file, const char __user *from, + size_t count, loff_t *ppos) +{ + struct snd_sof_dfsentry *dfse = file->private_data; + struct sof_ipc_trace_filter_elem *elems = NULL; + struct snd_sof_dev *sdev = dfse->sdev; + loff_t pos = 0; + int num_elems; + char *string; + int ret; + + if (count > TRACE_FILTER_MAX_CONFIG_STRING_LENGTH) { + dev_err(sdev->dev, "%s too long input, %zu > %d\n", __func__, count, + TRACE_FILTER_MAX_CONFIG_STRING_LENGTH); + return -EINVAL; + } + + string = kmalloc(count + 1, GFP_KERNEL); + if (!string) + return -ENOMEM; + + /* assert null termination */ + string[count] = 0; + ret = simple_write_to_buffer(string, count, &pos, from, count); + if (ret < 0) + goto error; + + ret = trace_filter_parse(sdev, string, &num_elems, &elems); + if (ret < 0) + goto error; + + if (num_elems) { + ret = ipc3_trace_update_filter(sdev, num_elems, elems); + if (ret < 0) { + dev_err(sdev->dev, "Filter update failed: %d\n", ret); + goto error; + } + } + ret = count; +error: + kfree(string); + kfree(elems); + return ret; +} + +static const struct file_operations sof_dfs_trace_filter_fops = { + .open = simple_open, + .write = dfsentry_trace_filter_write, + .llseek = default_llseek, +}; + +static int debugfs_create_trace_filter(struct snd_sof_dev *sdev) +{ + struct snd_sof_dfsentry *dfse; + + dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); + if (!dfse) + return -ENOMEM; + + dfse->sdev = sdev; + dfse->type = SOF_DFSENTRY_TYPE_BUF; + + debugfs_create_file("filter", 0200, sdev->debugfs_root, dfse, + &sof_dfs_trace_filter_fops); + /* add to dfsentry list */ + list_add(&dfse->list, &sdev->dfsentry_list); + + return 0; +} + +static bool sof_dtrace_set_host_offset(struct sof_dtrace_priv *priv, u32 new_offset) +{ + u32 host_offset = READ_ONCE(priv->host_offset); + + if (host_offset != new_offset) { + /* This is a bit paranoid and unlikely that it is needed */ + u32 ret = cmpxchg(&priv->host_offset, host_offset, new_offset); + + if (ret == host_offset) + return true; + } + + return false; +} + +static size_t sof_dtrace_avail(struct snd_sof_dev *sdev, + loff_t pos, size_t buffer_size) +{ + struct sof_dtrace_priv *priv = sdev->fw_trace_data; + loff_t host_offset = READ_ONCE(priv->host_offset); + + /* + * If host offset is less than local pos, it means write pointer of + * host DMA buffer has been wrapped. We should output the trace data + * at the end of host DMA buffer at first. + */ + if (host_offset < pos) + return buffer_size - pos; + + /* If there is available trace data now, it is unnecessary to wait. */ + if (host_offset > pos) + return host_offset - pos; + + return 0; +} + +static size_t sof_wait_dtrace_avail(struct snd_sof_dev *sdev, loff_t pos, + size_t buffer_size) +{ + size_t ret = sof_dtrace_avail(sdev, pos, buffer_size); + struct sof_dtrace_priv *priv = sdev->fw_trace_data; + wait_queue_entry_t wait; + + /* data immediately available */ + if (ret) + return ret; + + if (priv->dtrace_draining && !trace_pos_update_expected(priv)) { + /* + * tracing has ended and all traces have been + * read by client, return EOF + */ + priv->dtrace_draining = false; + return 0; + } + + /* wait for available trace data from FW */ + init_waitqueue_entry(&wait, current); + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&priv->trace_sleep, &wait); + + if (!signal_pending(current)) { + /* set timeout to max value, no error code */ + schedule_timeout(MAX_SCHEDULE_TIMEOUT); + } + remove_wait_queue(&priv->trace_sleep, &wait); + + return sof_dtrace_avail(sdev, pos, buffer_size); +} + +static ssize_t dfsentry_dtrace_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct snd_sof_dfsentry *dfse = file->private_data; + struct snd_sof_dev *sdev = dfse->sdev; + struct sof_dtrace_priv *priv = sdev->fw_trace_data; + unsigned long rem; + loff_t lpos = *ppos; + size_t avail, buffer_size = dfse->size; + u64 lpos_64; + + /* make sure we know about any failures on the DSP side */ + priv->dtrace_error = false; + + /* check pos and count */ + if (lpos < 0) + return -EINVAL; + if (!count) + return 0; + + /* check for buffer wrap and count overflow */ + lpos_64 = lpos; + lpos = do_div(lpos_64, buffer_size); + + /* get available count based on current host offset */ + avail = sof_wait_dtrace_avail(sdev, lpos, buffer_size); + if (priv->dtrace_error) { + dev_err(sdev->dev, "trace IO error\n"); + return -EIO; + } + + /* no new trace data */ + if (!avail) + return 0; + + /* make sure count is <= avail */ + if (count > avail) + count = avail; + + /* + * make sure that all trace data is available for the CPU as the trace + * data buffer might be allocated from non consistent memory. + * Note: snd_dma_buffer_sync() is called for normal audio playback and + * capture streams also. + */ + snd_dma_buffer_sync(&priv->dmatb, SNDRV_DMA_SYNC_CPU); + /* copy available trace data to debugfs */ + rem = copy_to_user(buffer, ((u8 *)(dfse->buf) + lpos), count); + if (rem) + return -EFAULT; + + *ppos += count; + + /* move debugfs reading position */ + return count; +} + +static int dfsentry_dtrace_release(struct inode *inode, struct file *file) +{ + struct snd_sof_dfsentry *dfse = inode->i_private; + struct snd_sof_dev *sdev = dfse->sdev; + struct sof_dtrace_priv *priv = sdev->fw_trace_data; + + /* avoid duplicate traces at next open */ + if (priv->dtrace_state != SOF_DTRACE_ENABLED) + sof_dtrace_set_host_offset(priv, 0); + + return 0; +} + +static const struct file_operations sof_dfs_dtrace_fops = { + .open = simple_open, + .read = dfsentry_dtrace_read, + .llseek = default_llseek, + .release = dfsentry_dtrace_release, +}; + +static int debugfs_create_dtrace(struct snd_sof_dev *sdev) +{ + struct sof_dtrace_priv *priv; + struct snd_sof_dfsentry *dfse; + int ret; + + if (!sdev) + return -EINVAL; + + priv = sdev->fw_trace_data; + + ret = debugfs_create_trace_filter(sdev); + if (ret < 0) + dev_warn(sdev->dev, "failed to create filter debugfs file: %d", ret); + + dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); + if (!dfse) + return -ENOMEM; + + dfse->type = SOF_DFSENTRY_TYPE_BUF; + dfse->buf = priv->dmatb.area; + dfse->size = priv->dmatb.bytes; + dfse->sdev = sdev; + + debugfs_create_file("trace", 0444, sdev->debugfs_root, dfse, + &sof_dfs_dtrace_fops); + + return 0; +} + +static int ipc3_dtrace_enable(struct snd_sof_dev *sdev) +{ + struct sof_dtrace_priv *priv = sdev->fw_trace_data; + struct sof_ipc_fw_ready *ready = &sdev->fw_ready; + struct sof_ipc_fw_version *v = &ready->version; + struct sof_ipc_dma_trace_params_ext params; + struct sof_ipc_reply ipc_reply; + int ret; + + if (!sdev->fw_trace_is_supported) + return 0; + + if (priv->dtrace_state == SOF_DTRACE_ENABLED || !priv->dma_trace_pages) + return -EINVAL; + + if (priv->dtrace_state == SOF_DTRACE_STOPPED) + goto start; + + /* set IPC parameters */ + params.hdr.cmd = SOF_IPC_GLB_TRACE_MSG; + /* PARAMS_EXT is only supported from ABI 3.7.0 onwards */ + if (v->abi_version >= SOF_ABI_VER(3, 7, 0)) { + params.hdr.size = sizeof(struct sof_ipc_dma_trace_params_ext); + params.hdr.cmd |= SOF_IPC_TRACE_DMA_PARAMS_EXT; + params.timestamp_ns = ktime_get(); /* in nanosecond */ + } else { + params.hdr.size = sizeof(struct sof_ipc_dma_trace_params); + params.hdr.cmd |= SOF_IPC_TRACE_DMA_PARAMS; + } + params.buffer.phy_addr = priv->dmatp.addr; + params.buffer.size = priv->dmatb.bytes; + params.buffer.pages = priv->dma_trace_pages; + params.stream_tag = 0; + + sof_dtrace_set_host_offset(priv, 0); + priv->dtrace_draining = false; + + ret = sof_dtrace_host_init(sdev, &priv->dmatb, ¶ms); + if (ret < 0) { + dev_err(sdev->dev, "Host dtrace init failed: %d\n", ret); + return ret; + } + dev_dbg(sdev->dev, "stream_tag: %d\n", params.stream_tag); + + /* send IPC to the DSP */ + priv->dtrace_state = SOF_DTRACE_INITIALIZING; + ret = sof_ipc_tx_message(sdev->ipc, ¶ms, sizeof(params), &ipc_reply, sizeof(ipc_reply)); + if (ret < 0) { + dev_err(sdev->dev, "can't set params for DMA for trace %d\n", ret); + goto trace_release; + } + +start: + priv->dtrace_state = SOF_DTRACE_ENABLED; + + ret = sof_dtrace_host_trigger(sdev, SNDRV_PCM_TRIGGER_START); + if (ret < 0) { + dev_err(sdev->dev, "Host dtrace trigger start failed: %d\n", ret); + goto trace_release; + } + + return 0; + +trace_release: + priv->dtrace_state = SOF_DTRACE_DISABLED; + sof_dtrace_host_release(sdev); + return ret; +} + +static int ipc3_dtrace_init(struct snd_sof_dev *sdev) +{ + struct sof_dtrace_priv *priv; + int ret; + + /* dtrace is only supported with SOF_IPC */ + if (sdev->pdata->ipc_type != SOF_IPC) + return -EOPNOTSUPP; + + if (sdev->fw_trace_data) { + dev_err(sdev->dev, "fw_trace_data has been already allocated\n"); + return -EBUSY; + } + + priv = devm_kzalloc(sdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + sdev->fw_trace_data = priv; + + /* set false before start initialization */ + priv->dtrace_state = SOF_DTRACE_DISABLED; + + /* allocate trace page table buffer */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->dev, + PAGE_SIZE, &priv->dmatp); + if (ret < 0) { + dev_err(sdev->dev, "can't alloc page table for trace %d\n", ret); + return ret; + } + + /* allocate trace data buffer */ + ret = snd_dma_alloc_dir_pages(SNDRV_DMA_TYPE_DEV_SG, sdev->dev, + DMA_FROM_DEVICE, DMA_BUF_SIZE_FOR_TRACE, + &priv->dmatb); + if (ret < 0) { + dev_err(sdev->dev, "can't alloc buffer for trace %d\n", ret); + goto page_err; + } + + /* create compressed page table for audio firmware */ + ret = snd_sof_create_page_table(sdev->dev, &priv->dmatb, + priv->dmatp.area, priv->dmatb.bytes); + if (ret < 0) + goto table_err; + + priv->dma_trace_pages = ret; + dev_dbg(sdev->dev, "dma_trace_pages: %d\n", priv->dma_trace_pages); + + if (sdev->first_boot) { + ret = debugfs_create_dtrace(sdev); + if (ret < 0) + goto table_err; + } + + init_waitqueue_head(&priv->trace_sleep); + + ret = ipc3_dtrace_enable(sdev); + if (ret < 0) + goto table_err; + + return 0; +table_err: + priv->dma_trace_pages = 0; + snd_dma_free_pages(&priv->dmatb); +page_err: + snd_dma_free_pages(&priv->dmatp); + return ret; +} + +int ipc3_dtrace_posn_update(struct snd_sof_dev *sdev, + struct sof_ipc_dma_trace_posn *posn) +{ + struct sof_dtrace_priv *priv = sdev->fw_trace_data; + + if (!sdev->fw_trace_is_supported) + return 0; + + if (trace_pos_update_expected(priv) && + sof_dtrace_set_host_offset(priv, posn->host_offset)) + wake_up(&priv->trace_sleep); + + if (posn->overflow != 0) + dev_err(sdev->dev, + "DSP trace buffer overflow %u bytes. Total messages %d\n", + posn->overflow, posn->messages); + + return 0; +} + +/* an error has occurred within the DSP that prevents further trace */ +static void ipc3_dtrace_fw_crashed(struct snd_sof_dev *sdev) +{ + struct sof_dtrace_priv *priv = sdev->fw_trace_data; + + if (priv->dtrace_state == SOF_DTRACE_ENABLED) { + priv->dtrace_error = true; + wake_up(&priv->trace_sleep); + } +} + +static void ipc3_dtrace_release(struct snd_sof_dev *sdev, bool only_stop) +{ + struct sof_dtrace_priv *priv = sdev->fw_trace_data; + struct sof_ipc_fw_ready *ready = &sdev->fw_ready; + struct sof_ipc_fw_version *v = &ready->version; + struct sof_ipc_cmd_hdr hdr; + struct sof_ipc_reply ipc_reply; + int ret; + + if (!sdev->fw_trace_is_supported || priv->dtrace_state == SOF_DTRACE_DISABLED) + return; + + ret = sof_dtrace_host_trigger(sdev, SNDRV_PCM_TRIGGER_STOP); + if (ret < 0) + dev_err(sdev->dev, "Host dtrace trigger stop failed: %d\n", ret); + priv->dtrace_state = SOF_DTRACE_STOPPED; + + /* + * stop and free trace DMA in the DSP. TRACE_DMA_FREE is only supported from + * ABI 3.20.0 onwards + */ + if (v->abi_version >= SOF_ABI_VER(3, 20, 0)) { + hdr.size = sizeof(hdr); + hdr.cmd = SOF_IPC_GLB_TRACE_MSG | SOF_IPC_TRACE_DMA_FREE; + + ret = sof_ipc_tx_message(sdev->ipc, &hdr, hdr.size, + &ipc_reply, sizeof(ipc_reply)); + if (ret < 0) + dev_err(sdev->dev, "DMA_TRACE_FREE failed with error: %d\n", ret); + } + + if (only_stop) + goto out; + + ret = sof_dtrace_host_release(sdev); + if (ret < 0) + dev_err(sdev->dev, "Host dtrace release failed %d\n", ret); + + priv->dtrace_state = SOF_DTRACE_DISABLED; + +out: + priv->dtrace_draining = true; + wake_up(&priv->trace_sleep); +} + +static void ipc3_dtrace_suspend(struct snd_sof_dev *sdev, pm_message_t pm_state) +{ + ipc3_dtrace_release(sdev, pm_state.event == SOF_DSP_PM_D0); +} + +static int ipc3_dtrace_resume(struct snd_sof_dev *sdev) +{ + return ipc3_dtrace_enable(sdev); +} + +static void ipc3_dtrace_free(struct snd_sof_dev *sdev) +{ + struct sof_dtrace_priv *priv = sdev->fw_trace_data; + + /* release trace */ + ipc3_dtrace_release(sdev, false); + + if (priv->dma_trace_pages) { + snd_dma_free_pages(&priv->dmatb); + snd_dma_free_pages(&priv->dmatp); + priv->dma_trace_pages = 0; + } +} + +const struct sof_ipc_fw_tracing_ops ipc3_dtrace_ops = { + .init = ipc3_dtrace_init, + .free = ipc3_dtrace_free, + .fw_crashed = ipc3_dtrace_fw_crashed, + .suspend = ipc3_dtrace_suspend, + .resume = ipc3_dtrace_resume, +}; diff --git a/sound/soc/sof/ipc3-loader.c b/sound/soc/sof/ipc3-loader.c new file mode 100644 index 000000000000..bf423ca4e97b --- /dev/null +++ b/sound/soc/sof/ipc3-loader.c @@ -0,0 +1,416 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. + +#include <linux/firmware.h> +#include "sof-priv.h" +#include "sof-audio.h" +#include "ipc3-priv.h" +#include "ops.h" + +static int ipc3_fw_ext_man_get_version(struct snd_sof_dev *sdev, + const struct sof_ext_man_elem_header *hdr) +{ + const struct sof_ext_man_fw_version *v = + container_of(hdr, struct sof_ext_man_fw_version, hdr); + + memcpy(&sdev->fw_ready.version, &v->version, sizeof(v->version)); + sdev->fw_ready.flags = v->flags; + + /* log ABI versions and check FW compatibility */ + return sof_ipc3_validate_fw_version(sdev); +} + +static int ipc3_fw_ext_man_get_windows(struct snd_sof_dev *sdev, + const struct sof_ext_man_elem_header *hdr) +{ + const struct sof_ext_man_window *w; + + w = container_of(hdr, struct sof_ext_man_window, hdr); + + return sof_ipc3_get_ext_windows(sdev, &w->ipc_window.ext_hdr); +} + +static int ipc3_fw_ext_man_get_cc_info(struct snd_sof_dev *sdev, + const struct sof_ext_man_elem_header *hdr) +{ + const struct sof_ext_man_cc_version *cc; + + cc = container_of(hdr, struct sof_ext_man_cc_version, hdr); + + return sof_ipc3_get_cc_info(sdev, &cc->cc_version.ext_hdr); +} + +static int ipc3_fw_ext_man_get_dbg_abi_info(struct snd_sof_dev *sdev, + const struct sof_ext_man_elem_header *hdr) +{ + const struct ext_man_dbg_abi *dbg_abi = + container_of(hdr, struct ext_man_dbg_abi, hdr); + + if (sdev->first_boot) + dev_dbg(sdev->dev, + "Firmware: DBG_ABI %d:%d:%d\n", + SOF_ABI_VERSION_MAJOR(dbg_abi->dbg_abi.abi_dbg_version), + SOF_ABI_VERSION_MINOR(dbg_abi->dbg_abi.abi_dbg_version), + SOF_ABI_VERSION_PATCH(dbg_abi->dbg_abi.abi_dbg_version)); + + return 0; +} + +static int ipc3_fw_ext_man_get_config_data(struct snd_sof_dev *sdev, + const struct sof_ext_man_elem_header *hdr) +{ + const struct sof_ext_man_config_data *config = + container_of(hdr, struct sof_ext_man_config_data, hdr); + const struct sof_config_elem *elem; + int elems_counter; + int elems_size; + int ret = 0; + int i; + + /* calculate elements counter */ + elems_size = config->hdr.size - sizeof(struct sof_ext_man_elem_header); + elems_counter = elems_size / sizeof(struct sof_config_elem); + + dev_dbg(sdev->dev, "manifest can hold up to %d config elements\n", elems_counter); + + for (i = 0; i < elems_counter; ++i) { + elem = &config->elems[i]; + dev_dbg(sdev->dev, "get index %d token %d val %d\n", + i, elem->token, elem->value); + switch (elem->token) { + case SOF_EXT_MAN_CONFIG_EMPTY: + /* unused memory space is zero filled - mapped to EMPTY elements */ + break; + case SOF_EXT_MAN_CONFIG_IPC_MSG_SIZE: + /* TODO: use ipc msg size from config data */ + break; + case SOF_EXT_MAN_CONFIG_MEMORY_USAGE_SCAN: + if (sdev->first_boot && elem->value) + ret = snd_sof_dbg_memory_info_init(sdev); + break; + default: + dev_info(sdev->dev, + "Unknown firmware configuration token %d value %d", + elem->token, elem->value); + break; + } + if (ret < 0) { + dev_err(sdev->dev, + "%s: processing failed for token %d value %#x, %d\n", + __func__, elem->token, elem->value, ret); + return ret; + } + } + + return 0; +} + +static ssize_t ipc3_fw_ext_man_size(struct snd_sof_dev *sdev, const struct firmware *fw) +{ + const struct sof_ext_man_header *head; + + head = (struct sof_ext_man_header *)fw->data; + + /* + * assert fw size is big enough to contain extended manifest header, + * it prevents from reading unallocated memory from `head` in following + * step. + */ + if (fw->size < sizeof(*head)) + return -EINVAL; + + /* + * When fw points to extended manifest, + * then first u32 must be equal SOF_EXT_MAN_MAGIC_NUMBER. + */ + if (head->magic == SOF_EXT_MAN_MAGIC_NUMBER) + return head->full_size; + + /* otherwise given fw don't have an extended manifest */ + dev_dbg(sdev->dev, "Unexpected extended manifest magic number: %#x\n", + head->magic); + return 0; +} + +static size_t sof_ipc3_fw_parse_ext_man(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *plat_data = sdev->pdata; + const struct firmware *fw = plat_data->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 = ipc3_fw_ext_man_size(sdev, fw); + + /* Assert firmware starts with extended manifest */ + if (ext_man_size <= 0) + return ext_man_size; + + /* incompatible version */ + if (SOF_EXT_MAN_VERSION_INCOMPATIBLE(SOF_EXT_MAN_VERSION, + head->header_version)) { + dev_err(sdev->dev, + "extended manifest version %#x differ from used %#x\n", + head->header_version, SOF_EXT_MAN_VERSION); + return -EINVAL; + } + + /* get first extended manifest element header */ + iptr = (uintptr_t)fw->data + head->header_size; + + while (remaining > sizeof(*elem_hdr)) { + elem_hdr = (struct sof_ext_man_elem_header *)iptr; + + dev_dbg(sdev->dev, "found sof_ext_man header type %d size %#x\n", + elem_hdr->type, elem_hdr->size); + + if (elem_hdr->size < sizeof(*elem_hdr) || + elem_hdr->size > remaining) { + dev_err(sdev->dev, + "invalid sof_ext_man header size, type %d size %#x\n", + elem_hdr->type, elem_hdr->size); + return -EINVAL; + } + + /* process structure data */ + switch (elem_hdr->type) { + case SOF_EXT_MAN_ELEM_FW_VERSION: + ret = ipc3_fw_ext_man_get_version(sdev, elem_hdr); + break; + case SOF_EXT_MAN_ELEM_WINDOW: + ret = ipc3_fw_ext_man_get_windows(sdev, elem_hdr); + break; + case SOF_EXT_MAN_ELEM_CC_VERSION: + ret = ipc3_fw_ext_man_get_cc_info(sdev, elem_hdr); + break; + case SOF_EXT_MAN_ELEM_DBG_ABI: + ret = ipc3_fw_ext_man_get_dbg_abi_info(sdev, elem_hdr); + break; + case SOF_EXT_MAN_ELEM_CONFIG_DATA: + ret = ipc3_fw_ext_man_get_config_data(sdev, elem_hdr); + break; + case SOF_EXT_MAN_ELEM_PLATFORM_CONFIG_DATA: + ret = snd_sof_dsp_parse_platform_ext_manifest(sdev, elem_hdr); + break; + default: + dev_info(sdev->dev, + "unknown sof_ext_man header type %d size %#x\n", + elem_hdr->type, elem_hdr->size); + break; + } + + if (ret < 0) { + dev_err(sdev->dev, + "failed to parse sof_ext_man header type %d size %#x\n", + elem_hdr->type, elem_hdr->size); + return ret; + } + + remaining -= elem_hdr->size; + iptr += elem_hdr->size; + } + + if (remaining) { + dev_err(sdev->dev, "error: sof_ext_man header is inconsistent\n"); + return -EINVAL; + } + + return ext_man_size; +} + +/* generic module parser for mmaped DSPs */ +static int sof_ipc3_parse_module_memcpy(struct snd_sof_dev *sdev, + struct snd_sof_mod_hdr *module) +{ + struct snd_sof_blk_hdr *block; + int count, ret; + u32 offset; + size_t remaining; + + dev_dbg(sdev->dev, "new module size %#x blocks %#x type %#x\n", + module->size, module->num_blocks, module->type); + + block = (struct snd_sof_blk_hdr *)((u8 *)module + sizeof(*module)); + + /* module->size doesn't include header size */ + remaining = module->size; + for (count = 0; count < module->num_blocks; count++) { + /* check for wrap */ + if (remaining < sizeof(*block)) { + dev_err(sdev->dev, "not enough data remaining\n"); + return -EINVAL; + } + + /* minus header size of block */ + remaining -= sizeof(*block); + + if (block->size == 0) { + dev_warn(sdev->dev, + "warning: block %d size zero\n", count); + dev_warn(sdev->dev, " type %#x offset %#x\n", + block->type, block->offset); + continue; + } + + switch (block->type) { + case SOF_FW_BLK_TYPE_RSRVD0: + case SOF_FW_BLK_TYPE_ROM...SOF_FW_BLK_TYPE_RSRVD14: + continue; /* not handled atm */ + case SOF_FW_BLK_TYPE_IRAM: + case SOF_FW_BLK_TYPE_DRAM: + case SOF_FW_BLK_TYPE_SRAM: + offset = block->offset; + break; + default: + dev_err(sdev->dev, "%s: bad type %#x for block %#x\n", + __func__, block->type, count); + return -EINVAL; + } + + dev_dbg(sdev->dev, "block %d type %#x size %#x ==> offset %#x\n", + count, block->type, block->size, offset); + + /* checking block->size to avoid unaligned access */ + if (block->size % sizeof(u32)) { + dev_err(sdev->dev, "%s: invalid block size %#x\n", + __func__, block->size); + return -EINVAL; + } + ret = snd_sof_dsp_block_write(sdev, block->type, offset, + block + 1, block->size); + if (ret < 0) { + dev_err(sdev->dev, "%s: write to block type %#x failed\n", + __func__, block->type); + return ret; + } + + if (remaining < block->size) { + dev_err(sdev->dev, "%s: not enough data remaining\n", __func__); + return -EINVAL; + } + + /* minus body size of block */ + remaining -= block->size; + /* next block */ + block = (struct snd_sof_blk_hdr *)((u8 *)block + sizeof(*block) + + block->size); + } + + return 0; +} + +static int sof_ipc3_load_fw_to_dsp(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *plat_data = sdev->pdata; + const struct firmware *fw = plat_data->fw; + struct snd_sof_fw_header *header; + struct snd_sof_mod_hdr *module; + int (*load_module)(struct snd_sof_dev *sof_dev, struct snd_sof_mod_hdr *hdr); + size_t remaining; + int ret, count; + + if (!plat_data->fw) + return -EINVAL; + + header = (struct snd_sof_fw_header *)(fw->data + plat_data->fw_offset); + load_module = sof_ops(sdev)->load_module; + if (!load_module) { + dev_dbg(sdev->dev, "Using generic module loading\n"); + load_module = sof_ipc3_parse_module_memcpy; + } else { + dev_dbg(sdev->dev, "Using custom module loading\n"); + } + + /* parse each module */ + module = (struct snd_sof_mod_hdr *)(fw->data + plat_data->fw_offset + + sizeof(*header)); + remaining = fw->size - sizeof(*header) - plat_data->fw_offset; + /* check for wrap */ + if (remaining > fw->size) { + dev_err(sdev->dev, "%s: fw size smaller than header size\n", __func__); + return -EINVAL; + } + + for (count = 0; count < header->num_modules; count++) { + /* check for wrap */ + if (remaining < sizeof(*module)) { + dev_err(sdev->dev, "%s: not enough data for a module\n", + __func__); + return -EINVAL; + } + + /* minus header size of module */ + remaining -= sizeof(*module); + + /* module */ + ret = load_module(sdev, module); + if (ret < 0) { + dev_err(sdev->dev, "%s: invalid module %d\n", __func__, count); + return ret; + } + + if (remaining < module->size) { + dev_err(sdev->dev, "%s: not enough data remaining\n", __func__); + return -EINVAL; + } + + /* minus body size of module */ + remaining -= module->size; + module = (struct snd_sof_mod_hdr *)((u8 *)module + + sizeof(*module) + module->size); + } + + return 0; +} + +static int sof_ipc3_validate_firmware(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *plat_data = sdev->pdata; + const struct firmware *fw = plat_data->fw; + struct snd_sof_fw_header *header; + size_t fw_size = fw->size - plat_data->fw_offset; + + if (fw->size <= plat_data->fw_offset) { + dev_err(sdev->dev, + "firmware size must be greater than firmware offset\n"); + return -EINVAL; + } + + /* Read the header information from the data pointer */ + header = (struct snd_sof_fw_header *)(fw->data + plat_data->fw_offset); + + /* verify FW sig */ + if (strncmp(header->sig, SND_SOF_FW_SIG, SND_SOF_FW_SIG_SIZE) != 0) { + dev_err(sdev->dev, "invalid firmware signature\n"); + return -EINVAL; + } + + /* check size is valid */ + if (fw_size != header->file_size + sizeof(*header)) { + dev_err(sdev->dev, + "invalid filesize mismatch got 0x%zx expected 0x%zx\n", + fw_size, header->file_size + sizeof(*header)); + return -EINVAL; + } + + dev_dbg(sdev->dev, "header size=0x%x modules=0x%x abi=0x%x size=%zu\n", + header->file_size, header->num_modules, + header->abi, sizeof(*header)); + + return 0; +} + +const struct sof_ipc_fw_loader_ops ipc3_loader_ops = { + .validate = sof_ipc3_validate_firmware, + .parse_ext_manifest = sof_ipc3_fw_parse_ext_man, + .load_fw_to_dsp = sof_ipc3_load_fw_to_dsp, +}; diff --git a/sound/soc/sof/ipc3-pcm.c b/sound/soc/sof/ipc3-pcm.c new file mode 100644 index 000000000000..dad57bef38f6 --- /dev/null +++ b/sound/soc/sof/ipc3-pcm.c @@ -0,0 +1,382 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Intel Corporation. All rights reserved. +// +// + +#include <sound/pcm_params.h> +#include "ipc3-priv.h" +#include "ops.h" +#include "sof-priv.h" +#include "sof-audio.h" + +static int sof_ipc3_pcm_hw_free(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct sof_ipc_stream stream; + struct sof_ipc_reply reply; + struct snd_sof_pcm *spcm; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) + return -EINVAL; + + if (!spcm->prepared[substream->stream]) + return 0; + + stream.hdr.size = sizeof(stream); + stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_FREE; + stream.comp_id = spcm->stream[substream->stream].comp_id; + + /* send IPC to the DSP */ + return sof_ipc_tx_message(sdev->ipc, &stream, sizeof(stream), &reply, sizeof(reply)); +} + +static int sof_ipc3_pcm_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_sof_platform_stream_params *platform_params) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct sof_ipc_fw_version *v = &sdev->fw_ready.version; + struct snd_pcm_runtime *runtime = substream->runtime; + struct sof_ipc_pcm_params_reply ipc_params_reply; + struct sof_ipc_pcm_params pcm; + struct snd_sof_pcm *spcm; + int ret; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) + return -EINVAL; + + memset(&pcm, 0, sizeof(pcm)); + + /* number of pages should be rounded up */ + pcm.params.buffer.pages = PFN_UP(runtime->dma_bytes); + + /* set IPC PCM parameters */ + pcm.hdr.size = sizeof(pcm); + pcm.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS; + pcm.comp_id = spcm->stream[substream->stream].comp_id; + pcm.params.hdr.size = sizeof(pcm.params); + pcm.params.buffer.phy_addr = spcm->stream[substream->stream].page_table.addr; + pcm.params.buffer.size = runtime->dma_bytes; + pcm.params.direction = substream->stream; + pcm.params.sample_valid_bytes = params_width(params) >> 3; + pcm.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED; + pcm.params.rate = params_rate(params); + pcm.params.channels = params_channels(params); + pcm.params.host_period_bytes = params_period_bytes(params); + + /* container size */ + ret = snd_pcm_format_physical_width(params_format(params)); + if (ret < 0) + return ret; + pcm.params.sample_container_bytes = ret >> 3; + + /* format */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16: + pcm.params.frame_fmt = SOF_IPC_FRAME_S16_LE; + break; + case SNDRV_PCM_FORMAT_S24: + pcm.params.frame_fmt = SOF_IPC_FRAME_S24_4LE; + break; + case SNDRV_PCM_FORMAT_S32: + pcm.params.frame_fmt = SOF_IPC_FRAME_S32_LE; + break; + case SNDRV_PCM_FORMAT_FLOAT: + pcm.params.frame_fmt = SOF_IPC_FRAME_FLOAT; + break; + default: + return -EINVAL; + } + + /* Update the IPC message with information from the platform */ + pcm.params.stream_tag = platform_params->stream_tag; + + if (platform_params->use_phy_address) + pcm.params.buffer.phy_addr = platform_params->phy_addr; + + if (platform_params->no_ipc_position) { + /* For older ABIs set host_period_bytes to zero to inform + * FW we don't want position updates. Newer versions use + * no_stream_position for this purpose. + */ + if (v->abi_version < SOF_ABI_VER(3, 10, 0)) + pcm.params.host_period_bytes = 0; + else + pcm.params.no_stream_position = 1; + } + + if (platform_params->cont_update_posn) + pcm.params.cont_update_posn = 1; + + dev_dbg(component->dev, "stream_tag %d", pcm.params.stream_tag); + + /* send hw_params IPC to the DSP */ + ret = sof_ipc_tx_message(sdev->ipc, &pcm, sizeof(pcm), + &ipc_params_reply, sizeof(ipc_params_reply)); + if (ret < 0) { + dev_err(component->dev, "HW params ipc failed for stream %d\n", + pcm.params.stream_tag); + return ret; + } + + ret = snd_sof_set_stream_data_offset(sdev, substream, ipc_params_reply.posn_offset); + if (ret < 0) { + dev_err(component->dev, "%s: invalid stream data offset for PCM %d\n", + __func__, spcm->pcm.pcm_id); + return ret; + } + + return ret; +} + +static int sof_ipc3_pcm_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int cmd) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct sof_ipc_stream stream; + struct sof_ipc_reply reply; + struct snd_sof_pcm *spcm; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) + return -EINVAL; + + stream.hdr.size = sizeof(stream); + stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG; + stream.comp_id = spcm->stream[substream->stream].comp_id; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_PAUSE; + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_RELEASE; + break; + case SNDRV_PCM_TRIGGER_START: + stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_START; + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + fallthrough; + case SNDRV_PCM_TRIGGER_STOP: + stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_STOP; + break; + default: + dev_err(component->dev, "Unhandled trigger cmd %d\n", cmd); + return -EINVAL; + } + + /* send IPC to the DSP */ + return sof_ipc_tx_message(sdev->ipc, &stream, sizeof(stream), &reply, sizeof(reply)); +} + +static void ssp_dai_config_pcm_params_match(struct snd_sof_dev *sdev, const char *link_name, + struct snd_pcm_hw_params *params) +{ + struct sof_ipc_dai_config *config; + struct snd_sof_dai *dai; + int i; + + /* + * Search for all matching DAIs as we can have both playback and capture DAI + * associated with the same link. + */ + list_for_each_entry(dai, &sdev->dai_list, list) { + if (!dai->name || strcmp(link_name, dai->name)) + continue; + for (i = 0; i < dai->number_configs; i++) { + struct sof_dai_private_data *private = dai->private; + + config = &private->dai_config[i]; + if (config->ssp.fsync_rate == params_rate(params)) { + dev_dbg(sdev->dev, "DAI config %d matches pcm hw params\n", i); + dai->current_config = i; + break; + } + } + } +} + +static int sof_ipc3_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME); + struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_sof_dai *dai = snd_sof_find_dai(component, (char *)rtd->dai_link->name); + struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct sof_dai_private_data *private; + struct snd_soc_dpcm *dpcm; + + if (!dai) { + dev_err(component->dev, "%s: No DAI found with name %s\n", __func__, + rtd->dai_link->name); + return -EINVAL; + } + + private = dai->private; + if (!private) { + dev_err(component->dev, "%s: No private data found for DAI %s\n", __func__, + rtd->dai_link->name); + return -EINVAL; + } + + /* read format from topology */ + snd_mask_none(fmt); + + switch (private->comp_dai->config.frame_fmt) { + case SOF_IPC_FRAME_S16_LE: + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); + break; + case SOF_IPC_FRAME_S24_4LE: + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + break; + case SOF_IPC_FRAME_S32_LE: + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE); + break; + default: + dev_err(component->dev, "No available DAI format!\n"); + return -EINVAL; + } + + /* read rate and channels from topology */ + switch (private->dai_config->type) { + case SOF_DAI_INTEL_SSP: + /* search for config to pcm params match, if not found use default */ + ssp_dai_config_pcm_params_match(sdev, (char *)rtd->dai_link->name, params); + + rate->min = private->dai_config[dai->current_config].ssp.fsync_rate; + rate->max = private->dai_config[dai->current_config].ssp.fsync_rate; + channels->min = private->dai_config[dai->current_config].ssp.tdm_slots; + channels->max = private->dai_config[dai->current_config].ssp.tdm_slots; + + dev_dbg(component->dev, "rate_min: %d rate_max: %d\n", rate->min, rate->max); + dev_dbg(component->dev, "channels_min: %d channels_max: %d\n", + channels->min, channels->max); + + break; + case SOF_DAI_INTEL_DMIC: + /* DMIC only supports 16 or 32 bit formats */ + if (private->comp_dai->config.frame_fmt == SOF_IPC_FRAME_S24_4LE) { + dev_err(component->dev, "Invalid fmt %d for DAI type %d\n", + private->comp_dai->config.frame_fmt, + private->dai_config->type); + } + break; + case SOF_DAI_INTEL_HDA: + /* + * HDAudio does not follow the default trigger + * sequence due to firmware implementation + */ + for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_PLAYBACK, dpcm) { + struct snd_soc_pcm_runtime *fe = dpcm->fe; + + fe->dai_link->trigger[SNDRV_PCM_STREAM_PLAYBACK] = + SND_SOC_DPCM_TRIGGER_POST; + } + break; + case SOF_DAI_INTEL_ALH: + /* + * Dai could run with different channel count compared with + * front end, so get dai channel count from topology + */ + channels->min = private->dai_config->alh.channels; + channels->max = private->dai_config->alh.channels; + break; + case SOF_DAI_IMX_ESAI: + rate->min = private->dai_config->esai.fsync_rate; + rate->max = private->dai_config->esai.fsync_rate; + channels->min = private->dai_config->esai.tdm_slots; + channels->max = private->dai_config->esai.tdm_slots; + + dev_dbg(component->dev, "rate_min: %d rate_max: %d\n", rate->min, rate->max); + dev_dbg(component->dev, "channels_min: %d channels_max: %d\n", + channels->min, channels->max); + break; + case SOF_DAI_MEDIATEK_AFE: + rate->min = private->dai_config->afe.rate; + rate->max = private->dai_config->afe.rate; + channels->min = private->dai_config->afe.channels; + channels->max = private->dai_config->afe.channels; + + dev_dbg(component->dev, "rate_min: %d rate_max: %d\n", rate->min, rate->max); + dev_dbg(component->dev, "channels_min: %d channels_max: %d\n", + channels->min, channels->max); + break; + case SOF_DAI_IMX_SAI: + rate->min = private->dai_config->sai.fsync_rate; + rate->max = private->dai_config->sai.fsync_rate; + channels->min = private->dai_config->sai.tdm_slots; + channels->max = private->dai_config->sai.tdm_slots; + + dev_dbg(component->dev, "rate_min: %d rate_max: %d\n", rate->min, rate->max); + dev_dbg(component->dev, "channels_min: %d channels_max: %d\n", + channels->min, channels->max); + break; + case SOF_DAI_AMD_BT: + rate->min = private->dai_config->acpbt.fsync_rate; + rate->max = private->dai_config->acpbt.fsync_rate; + channels->min = private->dai_config->acpbt.tdm_slots; + channels->max = private->dai_config->acpbt.tdm_slots; + + dev_dbg(component->dev, + "AMD_BT rate_min: %d rate_max: %d\n", rate->min, rate->max); + dev_dbg(component->dev, "AMD_BT channels_min: %d channels_max: %d\n", + channels->min, channels->max); + break; + case SOF_DAI_AMD_SP: + rate->min = private->dai_config->acpsp.fsync_rate; + rate->max = private->dai_config->acpsp.fsync_rate; + channels->min = private->dai_config->acpsp.tdm_slots; + channels->max = private->dai_config->acpsp.tdm_slots; + + dev_dbg(component->dev, + "AMD_SP rate_min: %d rate_max: %d\n", rate->min, rate->max); + dev_dbg(component->dev, "AMD_SP channels_min: %d channels_max: %d\n", + channels->min, channels->max); + break; + case SOF_DAI_AMD_HS: + rate->min = private->dai_config->acphs.fsync_rate; + rate->max = private->dai_config->acphs.fsync_rate; + channels->min = private->dai_config->acphs.tdm_slots; + channels->max = private->dai_config->acphs.tdm_slots; + + dev_dbg(component->dev, + "AMD_HS channel_max: %d rate_max: %d\n", channels->max, rate->max); + break; + case SOF_DAI_AMD_DMIC: + rate->min = private->dai_config->acpdmic.pdm_rate; + rate->max = private->dai_config->acpdmic.pdm_rate; + channels->min = private->dai_config->acpdmic.pdm_ch; + channels->max = private->dai_config->acpdmic.pdm_ch; + + dev_dbg(component->dev, + "AMD_DMIC rate_min: %d rate_max: %d\n", rate->min, rate->max); + dev_dbg(component->dev, "AMD_DMIC channels_min: %d channels_max: %d\n", + channels->min, channels->max); + break; + default: + dev_err(component->dev, "Invalid DAI type %d\n", private->dai_config->type); + break; + } + + return 0; +} + +const struct sof_ipc_pcm_ops ipc3_pcm_ops = { + .hw_params = sof_ipc3_pcm_hw_params, + .hw_free = sof_ipc3_pcm_hw_free, + .trigger = sof_ipc3_pcm_trigger, + .dai_link_fixup = sof_ipc3_pcm_dai_link_fixup, +}; diff --git a/sound/soc/sof/ipc3-priv.h b/sound/soc/sof/ipc3-priv.h new file mode 100644 index 000000000000..f5044202f3c5 --- /dev/null +++ b/sound/soc/sof/ipc3-priv.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright(c) 2021 Intel Corporation. All rights reserved. + */ + +#ifndef __SOUND_SOC_SOF_IPC3_PRIV_H +#define __SOUND_SOC_SOF_IPC3_PRIV_H + +#include "sof-priv.h" + +/* IPC3 specific ops */ +extern const struct sof_ipc_pcm_ops ipc3_pcm_ops; +extern const struct sof_ipc_tplg_ops ipc3_tplg_ops; +extern const struct sof_ipc_tplg_control_ops tplg_ipc3_control_ops; +extern const struct sof_ipc_fw_loader_ops ipc3_loader_ops; +extern const struct sof_ipc_fw_tracing_ops ipc3_dtrace_ops; + +/* helpers for fw_ready and ext_manifest parsing */ +int sof_ipc3_get_ext_windows(struct snd_sof_dev *sdev, + const struct sof_ipc_ext_data_hdr *ext_hdr); +int sof_ipc3_get_cc_info(struct snd_sof_dev *sdev, + const struct sof_ipc_ext_data_hdr *ext_hdr); +int sof_ipc3_validate_fw_version(struct snd_sof_dev *sdev); + +/* dtrace position update */ +int ipc3_dtrace_posn_update(struct snd_sof_dev *sdev, + struct sof_ipc_dma_trace_posn *posn); + +/* dtrace platform callback wrappers */ +static inline int sof_dtrace_host_init(struct snd_sof_dev *sdev, + struct snd_dma_buffer *dmatb, + struct sof_ipc_dma_trace_params_ext *dtrace_params) +{ + struct snd_sof_dsp_ops *dsp_ops = sdev->pdata->desc->ops; + + if (dsp_ops->trace_init) + return dsp_ops->trace_init(sdev, dmatb, dtrace_params); + + return 0; +} + +static inline int sof_dtrace_host_release(struct snd_sof_dev *sdev) +{ + struct snd_sof_dsp_ops *dsp_ops = sdev->pdata->desc->ops; + + if (dsp_ops->trace_release) + return dsp_ops->trace_release(sdev); + + return 0; +} + +static inline int sof_dtrace_host_trigger(struct snd_sof_dev *sdev, int cmd) +{ + struct snd_sof_dsp_ops *dsp_ops = sdev->pdata->desc->ops; + + if (dsp_ops->trace_trigger) + return dsp_ops->trace_trigger(sdev, cmd); + + return 0; +} + +#endif diff --git a/sound/soc/sof/ipc3-topology.c b/sound/soc/sof/ipc3-topology.c new file mode 100644 index 000000000000..c148715aa0f9 --- /dev/null +++ b/sound/soc/sof/ipc3-topology.c @@ -0,0 +1,2512 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Intel Corporation. All rights reserved. +// +// + +#include <uapi/sound/sof/tokens.h> +#include <sound/pcm_params.h> +#include "sof-priv.h" +#include "sof-audio.h" +#include "ipc3-priv.h" +#include "ops.h" + +/* Full volume for default values */ +#define VOL_ZERO_DB BIT(VOLUME_FWL) + +/* size of tplg ABI in bytes */ +#define SOF_IPC3_TPLG_ABI_SIZE 3 + +struct sof_widget_data { + int ctrl_type; + int ipc_cmd; + void *pdata; + size_t pdata_size; + struct snd_sof_control *control; +}; + +struct sof_process_types { + const char *name; + enum sof_ipc_process_type type; + enum sof_comp_type comp_type; +}; + +static const struct sof_process_types sof_process[] = { + {"EQFIR", SOF_PROCESS_EQFIR, SOF_COMP_EQ_FIR}, + {"EQIIR", SOF_PROCESS_EQIIR, SOF_COMP_EQ_IIR}, + {"KEYWORD_DETECT", SOF_PROCESS_KEYWORD_DETECT, SOF_COMP_KEYWORD_DETECT}, + {"KPB", SOF_PROCESS_KPB, SOF_COMP_KPB}, + {"CHAN_SELECTOR", SOF_PROCESS_CHAN_SELECTOR, SOF_COMP_SELECTOR}, + {"MUX", SOF_PROCESS_MUX, SOF_COMP_MUX}, + {"DEMUX", SOF_PROCESS_DEMUX, SOF_COMP_DEMUX}, + {"DCBLOCK", SOF_PROCESS_DCBLOCK, SOF_COMP_DCBLOCK}, + {"SMART_AMP", SOF_PROCESS_SMART_AMP, SOF_COMP_SMART_AMP}, +}; + +static enum sof_ipc_process_type find_process(const char *name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sof_process); i++) { + if (strcmp(name, sof_process[i].name) == 0) + return sof_process[i].type; + } + + return SOF_PROCESS_NONE; +} + +static int get_token_process_type(void *elem, void *object, u32 offset) +{ + u32 *val = (u32 *)((u8 *)object + offset); + + *val = find_process((const char *)elem); + return 0; +} + +/* Buffers */ +static const struct sof_topology_token buffer_tokens[] = { + {SOF_TKN_BUF_SIZE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_buffer, size)}, + {SOF_TKN_BUF_CAPS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_buffer, caps)}, +}; + +/* DAI */ +static const struct sof_topology_token dai_tokens[] = { + {SOF_TKN_DAI_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_dai_type, + offsetof(struct sof_ipc_comp_dai, type)}, + {SOF_TKN_DAI_INDEX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_dai, dai_index)}, + {SOF_TKN_DAI_DIRECTION, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_dai, direction)}, +}; + +/* BE DAI link */ +static const struct sof_topology_token dai_link_tokens[] = { + {SOF_TKN_DAI_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_dai_type, + offsetof(struct sof_ipc_dai_config, type)}, + {SOF_TKN_DAI_INDEX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_config, dai_index)}, +}; + +/* scheduling */ +static const struct sof_topology_token sched_tokens[] = { + {SOF_TKN_SCHED_PERIOD, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_pipe_new, period)}, + {SOF_TKN_SCHED_PRIORITY, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_pipe_new, priority)}, + {SOF_TKN_SCHED_MIPS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_pipe_new, period_mips)}, + {SOF_TKN_SCHED_CORE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_pipe_new, core)}, + {SOF_TKN_SCHED_FRAMES, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_pipe_new, frames_per_sched)}, + {SOF_TKN_SCHED_TIME_DOMAIN, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_pipe_new, time_domain)}, +}; + +static const struct sof_topology_token pipeline_tokens[] = { + {SOF_TKN_SCHED_DYNAMIC_PIPELINE, SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16, + offsetof(struct snd_sof_widget, dynamic_pipeline_widget)}, + +}; + +/* volume */ +static const struct sof_topology_token volume_tokens[] = { + {SOF_TKN_VOLUME_RAMP_STEP_TYPE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_volume, ramp)}, + {SOF_TKN_VOLUME_RAMP_STEP_MS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_volume, initial_ramp)}, +}; + +/* SRC */ +static const struct sof_topology_token src_tokens[] = { + {SOF_TKN_SRC_RATE_IN, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_src, source_rate)}, + {SOF_TKN_SRC_RATE_OUT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_src, sink_rate)}, +}; + +/* ASRC */ +static const struct sof_topology_token asrc_tokens[] = { + {SOF_TKN_ASRC_RATE_IN, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_asrc, source_rate)}, + {SOF_TKN_ASRC_RATE_OUT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_asrc, sink_rate)}, + {SOF_TKN_ASRC_ASYNCHRONOUS_MODE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_asrc, asynchronous_mode)}, + {SOF_TKN_ASRC_OPERATION_MODE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_asrc, operation_mode)}, +}; + +/* EFFECT */ +static const struct sof_topology_token process_tokens[] = { + {SOF_TKN_PROCESS_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_process_type, + offsetof(struct sof_ipc_comp_process, type)}, +}; + +/* PCM */ +static const struct sof_topology_token pcm_tokens[] = { + {SOF_TKN_PCM_DMAC_CONFIG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_host, dmac_config)}, +}; + +/* Generic components */ +static const struct sof_topology_token comp_tokens[] = { + {SOF_TKN_COMP_PERIOD_SINK_COUNT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_config, periods_sink)}, + {SOF_TKN_COMP_PERIOD_SOURCE_COUNT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_config, periods_source)}, + {SOF_TKN_COMP_FORMAT, + SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_comp_format, + offsetof(struct sof_ipc_comp_config, frame_fmt)}, +}; + +/* SSP */ +static const struct sof_topology_token ssp_tokens[] = { + {SOF_TKN_INTEL_SSP_CLKS_CONTROL, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_ssp_params, clks_control)}, + {SOF_TKN_INTEL_SSP_MCLK_ID, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_ssp_params, mclk_id)}, + {SOF_TKN_INTEL_SSP_SAMPLE_BITS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_ssp_params, sample_valid_bits)}, + {SOF_TKN_INTEL_SSP_FRAME_PULSE_WIDTH, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_ssp_params, frame_pulse_width)}, + {SOF_TKN_INTEL_SSP_QUIRKS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_ssp_params, quirks)}, + {SOF_TKN_INTEL_SSP_TDM_PADDING_PER_SLOT, SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16, + offsetof(struct sof_ipc_dai_ssp_params, tdm_per_slot_padding_flag)}, + {SOF_TKN_INTEL_SSP_BCLK_DELAY, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_ssp_params, bclk_delay)}, +}; + +/* ALH */ +static const struct sof_topology_token alh_tokens[] = { + {SOF_TKN_INTEL_ALH_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_alh_params, rate)}, + {SOF_TKN_INTEL_ALH_CH, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_alh_params, channels)}, +}; + +/* DMIC */ +static const struct sof_topology_token dmic_tokens[] = { + {SOF_TKN_INTEL_DMIC_DRIVER_VERSION, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_dmic_params, driver_ipc_version)}, + {SOF_TKN_INTEL_DMIC_CLK_MIN, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_dmic_params, pdmclk_min)}, + {SOF_TKN_INTEL_DMIC_CLK_MAX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_dmic_params, pdmclk_max)}, + {SOF_TKN_INTEL_DMIC_SAMPLE_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_dmic_params, fifo_fs)}, + {SOF_TKN_INTEL_DMIC_DUTY_MIN, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_params, duty_min)}, + {SOF_TKN_INTEL_DMIC_DUTY_MAX, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_params, duty_max)}, + {SOF_TKN_INTEL_DMIC_NUM_PDM_ACTIVE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_dmic_params, num_pdm_active)}, + {SOF_TKN_INTEL_DMIC_FIFO_WORD_LENGTH, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_params, fifo_bits)}, + {SOF_TKN_INTEL_DMIC_UNMUTE_RAMP_TIME_MS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_dmic_params, unmute_ramp_time)}, +}; + +/* ESAI */ +static const struct sof_topology_token esai_tokens[] = { + {SOF_TKN_IMX_ESAI_MCLK_ID, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_esai_params, mclk_id)}, +}; + +/* SAI */ +static const struct sof_topology_token sai_tokens[] = { + {SOF_TKN_IMX_SAI_MCLK_ID, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_sai_params, mclk_id)}, +}; + +/* + * DMIC PDM Tokens + * SOF_TKN_INTEL_DMIC_PDM_CTRL_ID should be the first token + * as it increments the index while parsing the array of pdm tokens + * and determines the correct offset + */ +static const struct sof_topology_token dmic_pdm_tokens[] = { + {SOF_TKN_INTEL_DMIC_PDM_CTRL_ID, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, id)}, + {SOF_TKN_INTEL_DMIC_PDM_MIC_A_Enable, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, enable_mic_a)}, + {SOF_TKN_INTEL_DMIC_PDM_MIC_B_Enable, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, enable_mic_b)}, + {SOF_TKN_INTEL_DMIC_PDM_POLARITY_A, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, polarity_mic_a)}, + {SOF_TKN_INTEL_DMIC_PDM_POLARITY_B, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, polarity_mic_b)}, + {SOF_TKN_INTEL_DMIC_PDM_CLK_EDGE, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, clk_edge)}, + {SOF_TKN_INTEL_DMIC_PDM_SKEW, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, + offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, skew)}, +}; + +/* HDA */ +static const struct sof_topology_token hda_tokens[] = { + {SOF_TKN_INTEL_HDA_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_hda_params, rate)}, + {SOF_TKN_INTEL_HDA_CH, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_hda_params, channels)}, +}; + +/* AFE */ +static const struct sof_topology_token afe_tokens[] = { + {SOF_TKN_MEDIATEK_AFE_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_mtk_afe_params, rate)}, + {SOF_TKN_MEDIATEK_AFE_CH, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_mtk_afe_params, channels)}, + {SOF_TKN_MEDIATEK_AFE_FORMAT, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_comp_format, + offsetof(struct sof_ipc_dai_mtk_afe_params, format)}, +}; + +/* ACPDMIC */ +static const struct sof_topology_token acpdmic_tokens[] = { + {SOF_TKN_AMD_ACPDMIC_RATE, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_acpdmic_params, pdm_rate)}, + {SOF_TKN_AMD_ACPDMIC_CH, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_dai_acpdmic_params, pdm_ch)}, +}; + +/* Core tokens */ +static const struct sof_topology_token core_tokens[] = { + {SOF_TKN_COMP_CORE_ID, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp, core)}, +}; + +/* Component extended tokens */ +static const struct sof_topology_token comp_ext_tokens[] = { + {SOF_TKN_COMP_UUID, SND_SOC_TPLG_TUPLE_TYPE_UUID, get_token_uuid, + offsetof(struct snd_sof_widget, uuid)}, +}; + +static const struct sof_token_info ipc3_token_list[SOF_TOKEN_COUNT] = { + [SOF_PCM_TOKENS] = {"PCM tokens", pcm_tokens, ARRAY_SIZE(pcm_tokens)}, + [SOF_PIPELINE_TOKENS] = {"Pipeline tokens", pipeline_tokens, ARRAY_SIZE(pipeline_tokens)}, + [SOF_SCHED_TOKENS] = {"Scheduler tokens", sched_tokens, ARRAY_SIZE(sched_tokens)}, + [SOF_COMP_TOKENS] = {"Comp tokens", comp_tokens, ARRAY_SIZE(comp_tokens)}, + [SOF_CORE_TOKENS] = {"Core tokens", core_tokens, ARRAY_SIZE(core_tokens)}, + [SOF_COMP_EXT_TOKENS] = {"AFE tokens", comp_ext_tokens, ARRAY_SIZE(comp_ext_tokens)}, + [SOF_BUFFER_TOKENS] = {"Buffer tokens", buffer_tokens, ARRAY_SIZE(buffer_tokens)}, + [SOF_VOLUME_TOKENS] = {"Volume tokens", volume_tokens, ARRAY_SIZE(volume_tokens)}, + [SOF_SRC_TOKENS] = {"SRC tokens", src_tokens, ARRAY_SIZE(src_tokens)}, + [SOF_ASRC_TOKENS] = {"ASRC tokens", asrc_tokens, ARRAY_SIZE(asrc_tokens)}, + [SOF_PROCESS_TOKENS] = {"Process tokens", process_tokens, ARRAY_SIZE(process_tokens)}, + [SOF_DAI_TOKENS] = {"DAI tokens", dai_tokens, ARRAY_SIZE(dai_tokens)}, + [SOF_DAI_LINK_TOKENS] = {"DAI link tokens", dai_link_tokens, ARRAY_SIZE(dai_link_tokens)}, + [SOF_HDA_TOKENS] = {"HDA tokens", hda_tokens, ARRAY_SIZE(hda_tokens)}, + [SOF_SSP_TOKENS] = {"SSP tokens", ssp_tokens, ARRAY_SIZE(ssp_tokens)}, + [SOF_ALH_TOKENS] = {"ALH tokens", alh_tokens, ARRAY_SIZE(alh_tokens)}, + [SOF_DMIC_TOKENS] = {"DMIC tokens", dmic_tokens, ARRAY_SIZE(dmic_tokens)}, + [SOF_DMIC_PDM_TOKENS] = {"DMIC PDM tokens", dmic_pdm_tokens, ARRAY_SIZE(dmic_pdm_tokens)}, + [SOF_ESAI_TOKENS] = {"ESAI tokens", esai_tokens, ARRAY_SIZE(esai_tokens)}, + [SOF_SAI_TOKENS] = {"SAI tokens", sai_tokens, ARRAY_SIZE(sai_tokens)}, + [SOF_AFE_TOKENS] = {"AFE tokens", afe_tokens, ARRAY_SIZE(afe_tokens)}, + [SOF_ACPDMIC_TOKENS] = {"ACPDMIC tokens", acpdmic_tokens, ARRAY_SIZE(acpdmic_tokens)}, +}; + +/** + * sof_comp_alloc - allocate and initialize buffer for a new component + * @swidget: pointer to struct snd_sof_widget containing extended data + * @ipc_size: IPC payload size that will be updated depending on valid + * extended data. + * @index: ID of the pipeline the component belongs to + * + * Return: The pointer to the new allocated component, NULL if failed. + */ +static void *sof_comp_alloc(struct snd_sof_widget *swidget, size_t *ipc_size, + int index) +{ + struct sof_ipc_comp *comp; + size_t total_size = *ipc_size; + size_t ext_size = sizeof(swidget->uuid); + + /* only non-zero UUID is valid */ + if (!guid_is_null(&swidget->uuid)) + total_size += ext_size; + + comp = kzalloc(total_size, GFP_KERNEL); + if (!comp) + return NULL; + + /* configure comp new IPC message */ + comp->hdr.size = total_size; + comp->hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW; + comp->id = swidget->comp_id; + comp->pipeline_id = index; + comp->core = swidget->core; + + /* handle the extended data if needed */ + if (total_size > *ipc_size) { + /* append extended data to the end of the component */ + memcpy((u8 *)comp + *ipc_size, &swidget->uuid, ext_size); + comp->ext_data_length = ext_size; + } + + /* update ipc_size and return */ + *ipc_size = total_size; + return comp; +} + +static void sof_dbg_comp_config(struct snd_soc_component *scomp, struct sof_ipc_comp_config *config) +{ + dev_dbg(scomp->dev, " config: periods snk %d src %d fmt %d\n", + config->periods_sink, config->periods_source, + config->frame_fmt); +} + +static int sof_ipc3_widget_setup_comp_host(struct snd_sof_widget *swidget) +{ + struct snd_soc_component *scomp = swidget->scomp; + struct sof_ipc_comp_host *host; + size_t ipc_size = sizeof(*host); + int ret; + + host = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id); + if (!host) + return -ENOMEM; + swidget->private = host; + + /* configure host comp IPC message */ + host->comp.type = SOF_COMP_HOST; + host->config.hdr.size = sizeof(host->config); + + if (swidget->id == snd_soc_dapm_aif_out) + host->direction = SOF_IPC_STREAM_CAPTURE; + else + host->direction = SOF_IPC_STREAM_PLAYBACK; + + /* parse one set of pcm_tokens */ + ret = sof_update_ipc_object(scomp, host, SOF_PCM_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(*host), 1); + if (ret < 0) + goto err; + + /* parse one set of comp_tokens */ + ret = sof_update_ipc_object(scomp, &host->config, SOF_COMP_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(host->config), 1); + if (ret < 0) + goto err; + + dev_dbg(scomp->dev, "loaded host %s\n", swidget->widget->name); + sof_dbg_comp_config(scomp, &host->config); + + return 0; +err: + kfree(swidget->private); + swidget->private = NULL; + + return ret; +} + +static void sof_ipc3_widget_free_comp(struct snd_sof_widget *swidget) +{ + kfree(swidget->private); +} + +static int sof_ipc3_widget_setup_comp_tone(struct snd_sof_widget *swidget) +{ + struct snd_soc_component *scomp = swidget->scomp; + struct sof_ipc_comp_tone *tone; + size_t ipc_size = sizeof(*tone); + int ret; + + tone = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id); + if (!tone) + return -ENOMEM; + + swidget->private = tone; + + /* configure siggen IPC message */ + tone->comp.type = SOF_COMP_TONE; + tone->config.hdr.size = sizeof(tone->config); + + /* parse one set of comp tokens */ + ret = sof_update_ipc_object(scomp, &tone->config, SOF_COMP_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(tone->config), 1); + if (ret < 0) { + kfree(swidget->private); + swidget->private = NULL; + return ret; + } + + dev_dbg(scomp->dev, "tone %s: frequency %d amplitude %d\n", + swidget->widget->name, tone->frequency, tone->amplitude); + sof_dbg_comp_config(scomp, &tone->config); + + return 0; +} + +static int sof_ipc3_widget_setup_comp_mixer(struct snd_sof_widget *swidget) +{ + struct snd_soc_component *scomp = swidget->scomp; + struct sof_ipc_comp_mixer *mixer; + size_t ipc_size = sizeof(*mixer); + int ret; + + mixer = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id); + if (!mixer) + return -ENOMEM; + + swidget->private = mixer; + + /* configure mixer IPC message */ + mixer->comp.type = SOF_COMP_MIXER; + mixer->config.hdr.size = sizeof(mixer->config); + + /* parse one set of comp tokens */ + ret = sof_update_ipc_object(scomp, &mixer->config, SOF_COMP_TOKENS, + swidget->tuples, swidget->num_tuples, + sizeof(mixer->config), 1); + if (ret < 0) { + kfree(swidget->private); + swidget->private = NULL; + + return ret; + } + + dev_dbg(scomp->dev, "loaded mixer %s\n", swidget->widget->name); + sof_dbg_comp_config(scomp, &mixer->config); + + return 0; +} + +static int sof_ipc3_widget_setup_comp_pipeline(struct snd_sof_widget *swidget) +{ + struct snd_soc_component *scomp = swidget->scomp; + struct sof_ipc_pipe_new *pipeline; + struct snd_sof_widget *comp_swidget; + int ret; + + pipeline = kzalloc(sizeof(*pipeline), GFP_KERNEL); + if (!pipeline) + return -ENOMEM; + + /* configure pipeline IPC message */ + pipeline->hdr.size = sizeof(*pipeline); + pipeline->hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_PIPE_NEW; + pipeline->pipeline_id = swidget->pipeline_id; + pipeline->comp_id = swidget->comp_id; + + swidget->private = pipeline; + + /* component at start of pipeline is our stream id */ + comp_swidget = snd_sof_find_swidget(scomp, swidget->widget->sname); + if (!comp_swidget) { + dev_err(scomp->dev, "scheduler %s refers to non existent widget %s\n", + swidget->widget->name, swidget->widget->sname); + ret = -EINVAL; + goto err; + } + + pipeline->sched_id = comp_swidget->comp_id; + + /* parse one set of scheduler tokens */ + ret = sof_update_ipc_object(scomp, pipeline, SOF_SCHED_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(*pipeline), 1); + if (ret < 0) + goto err; + + /* parse one set of pipeline tokens */ + ret = sof_update_ipc_object(scomp, swidget, SOF_PIPELINE_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(*swidget), 1); + if (ret < 0) + goto err; + + if (sof_debug_check_flag(SOF_DBG_DISABLE_MULTICORE)) + pipeline->core = SOF_DSP_PRIMARY_CORE; + + if (sof_debug_check_flag(SOF_DBG_DYNAMIC_PIPELINES_OVERRIDE)) + swidget->dynamic_pipeline_widget = + sof_debug_check_flag(SOF_DBG_DYNAMIC_PIPELINES_ENABLE); + + dev_dbg(scomp->dev, "pipeline %s: period %d pri %d mips %d core %d frames %d dynamic %d\n", + swidget->widget->name, pipeline->period, pipeline->priority, + pipeline->period_mips, pipeline->core, pipeline->frames_per_sched, + swidget->dynamic_pipeline_widget); + + swidget->core = pipeline->core; + + return 0; + +err: + kfree(swidget->private); + swidget->private = NULL; + + return ret; +} + +static int sof_ipc3_widget_setup_comp_buffer(struct snd_sof_widget *swidget) +{ + struct snd_soc_component *scomp = swidget->scomp; + struct sof_ipc_buffer *buffer; + int ret; + + buffer = kzalloc(sizeof(*buffer), GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + swidget->private = buffer; + + /* configure dai IPC message */ + buffer->comp.hdr.size = sizeof(*buffer); + buffer->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_BUFFER_NEW; + buffer->comp.id = swidget->comp_id; + buffer->comp.type = SOF_COMP_BUFFER; + buffer->comp.pipeline_id = swidget->pipeline_id; + buffer->comp.core = swidget->core; + + /* parse one set of buffer tokens */ + ret = sof_update_ipc_object(scomp, buffer, SOF_BUFFER_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(*buffer), 1); + if (ret < 0) { + kfree(swidget->private); + swidget->private = NULL; + return ret; + } + + dev_dbg(scomp->dev, "buffer %s: size %d caps 0x%x\n", + swidget->widget->name, buffer->size, buffer->caps); + + return 0; +} + +static int sof_ipc3_widget_setup_comp_src(struct snd_sof_widget *swidget) +{ + struct snd_soc_component *scomp = swidget->scomp; + struct sof_ipc_comp_src *src; + size_t ipc_size = sizeof(*src); + int ret; + + src = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id); + if (!src) + return -ENOMEM; + + swidget->private = src; + + /* configure src IPC message */ + src->comp.type = SOF_COMP_SRC; + src->config.hdr.size = sizeof(src->config); + + /* parse one set of src tokens */ + ret = sof_update_ipc_object(scomp, src, SOF_SRC_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(*src), 1); + if (ret < 0) + goto err; + + /* parse one set of comp tokens */ + ret = sof_update_ipc_object(scomp, &src->config, SOF_COMP_TOKENS, + swidget->tuples, swidget->num_tuples, sizeof(src->config), 1); + if (ret < 0) + goto err; + + dev_dbg(scomp->dev, "src %s: source rate %d sink rate %d\n", + swidget->widget->name, src->source_rate, src->sink_rate); + sof_dbg_comp_config(scomp, &src->config); + + return 0; +err: + kfree(swidget->private); + swidget->private = NULL; + + return ret; +} + +static int sof_ipc3_widget_setup_comp_asrc(struct snd_sof_widget *swidget) +{ + struct snd_soc_component *scomp = swidget->scomp; + struct sof_ipc_comp_asrc *asrc; + size_t ipc_size = sizeof(*asrc); + int ret; + + asrc = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id); + if (!asrc) + return -ENOMEM; + + swidget->private = asrc; + + /* configure ASRC IPC message */ + asrc->comp.type = SOF_COMP_ASRC; + asrc->config.hdr.size = sizeof(asrc->config); + + /* parse one set of asrc tokens */ + ret = sof_update_ipc_object(scomp, asrc, SOF_ASRC_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(*asrc), 1); + if (ret < 0) + goto err; + + /* parse one set of comp tokens */ + ret = sof_update_ipc_object(scomp, &asrc->config, SOF_COMP_TOKENS, + swidget->tuples, swidget->num_tuples, sizeof(asrc->config), 1); + if (ret < 0) + goto err; + + dev_dbg(scomp->dev, "asrc %s: source rate %d sink rate %d asynch %d operation %d\n", + swidget->widget->name, asrc->source_rate, asrc->sink_rate, + asrc->asynchronous_mode, asrc->operation_mode); + + sof_dbg_comp_config(scomp, &asrc->config); + + return 0; +err: + kfree(swidget->private); + swidget->private = NULL; + + return ret; +} + +/* + * Mux topology + */ +static int sof_ipc3_widget_setup_comp_mux(struct snd_sof_widget *swidget) +{ + struct snd_soc_component *scomp = swidget->scomp; + struct sof_ipc_comp_mux *mux; + size_t ipc_size = sizeof(*mux); + int ret; + + mux = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id); + if (!mux) + return -ENOMEM; + + swidget->private = mux; + + /* configure mux IPC message */ + mux->comp.type = SOF_COMP_MUX; + mux->config.hdr.size = sizeof(mux->config); + + /* parse one set of comp tokens */ + ret = sof_update_ipc_object(scomp, &mux->config, SOF_COMP_TOKENS, + swidget->tuples, swidget->num_tuples, sizeof(mux->config), 1); + if (ret < 0) { + kfree(swidget->private); + swidget->private = NULL; + return ret; + } + + dev_dbg(scomp->dev, "loaded mux %s\n", swidget->widget->name); + sof_dbg_comp_config(scomp, &mux->config); + + return 0; +} + +/* + * PGA Topology + */ + +static int sof_ipc3_widget_setup_comp_pga(struct snd_sof_widget *swidget) +{ + struct snd_soc_component *scomp = swidget->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_ipc_comp_volume *volume; + struct snd_sof_control *scontrol; + size_t ipc_size = sizeof(*volume); + int min_step, max_step; + int ret; + + volume = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id); + if (!volume) + return -ENOMEM; + + swidget->private = volume; + + /* configure volume IPC message */ + volume->comp.type = SOF_COMP_VOLUME; + volume->config.hdr.size = sizeof(volume->config); + + /* parse one set of volume tokens */ + ret = sof_update_ipc_object(scomp, volume, SOF_VOLUME_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(*volume), 1); + if (ret < 0) + goto err; + + /* parse one set of comp tokens */ + ret = sof_update_ipc_object(scomp, &volume->config, SOF_COMP_TOKENS, + swidget->tuples, swidget->num_tuples, + sizeof(volume->config), 1); + if (ret < 0) + goto err; + + dev_dbg(scomp->dev, "loaded PGA %s\n", swidget->widget->name); + sof_dbg_comp_config(scomp, &volume->config); + + list_for_each_entry(scontrol, &sdev->kcontrol_list, list) { + if (scontrol->comp_id == swidget->comp_id && + scontrol->volume_table) { + min_step = scontrol->min_volume_step; + max_step = scontrol->max_volume_step; + volume->min_value = scontrol->volume_table[min_step]; + volume->max_value = scontrol->volume_table[max_step]; + volume->channels = scontrol->num_channels; + break; + } + } + + return 0; +err: + kfree(swidget->private); + swidget->private = NULL; + + return ret; +} + +static int sof_get_control_data(struct snd_soc_component *scomp, + struct snd_soc_dapm_widget *widget, + struct sof_widget_data *wdata, size_t *size) +{ + const struct snd_kcontrol_new *kc; + struct sof_ipc_ctrl_data *cdata; + struct soc_mixer_control *sm; + struct soc_bytes_ext *sbe; + struct soc_enum *se; + int i; + + *size = 0; + + for (i = 0; i < widget->num_kcontrols; i++) { + kc = &widget->kcontrol_news[i]; + + switch (widget->dobj.widget.kcontrol_type[i]) { + case SND_SOC_TPLG_TYPE_MIXER: + sm = (struct soc_mixer_control *)kc->private_value; + wdata[i].control = sm->dobj.private; + break; + case SND_SOC_TPLG_TYPE_BYTES: + sbe = (struct soc_bytes_ext *)kc->private_value; + wdata[i].control = sbe->dobj.private; + break; + case SND_SOC_TPLG_TYPE_ENUM: + se = (struct soc_enum *)kc->private_value; + wdata[i].control = se->dobj.private; + break; + default: + dev_err(scomp->dev, "Unknown kcontrol type %u in widget %s\n", + widget->dobj.widget.kcontrol_type[i], widget->name); + return -EINVAL; + } + + if (!wdata[i].control) { + dev_err(scomp->dev, "No scontrol for widget %s\n", widget->name); + return -EINVAL; + } + + cdata = wdata[i].control->ipc_control_data; + + if (widget->dobj.widget.kcontrol_type[i] == SND_SOC_TPLG_TYPE_BYTES) { + /* make sure data is valid - data can be updated at runtime */ + if (cdata->data->magic != SOF_ABI_MAGIC) + return -EINVAL; + + wdata[i].pdata = cdata->data->data; + wdata[i].pdata_size = cdata->data->size; + } else { + /* points to the control data union */ + wdata[i].pdata = cdata->chanv; + /* + * wdata[i].control->size is calculated with struct_size + * and includes the size of struct sof_ipc_ctrl_data + */ + wdata[i].pdata_size = wdata[i].control->size - + sizeof(struct sof_ipc_ctrl_data); + } + + *size += wdata[i].pdata_size; + + /* get data type */ + switch (cdata->cmd) { + case SOF_CTRL_CMD_VOLUME: + case SOF_CTRL_CMD_ENUM: + case SOF_CTRL_CMD_SWITCH: + wdata[i].ipc_cmd = SOF_IPC_COMP_SET_VALUE; + wdata[i].ctrl_type = SOF_CTRL_TYPE_VALUE_CHAN_SET; + break; + case SOF_CTRL_CMD_BINARY: + wdata[i].ipc_cmd = SOF_IPC_COMP_SET_DATA; + wdata[i].ctrl_type = SOF_CTRL_TYPE_DATA_SET; + break; + default: + break; + } + } + + return 0; +} + +static int sof_process_load(struct snd_soc_component *scomp, + struct snd_sof_widget *swidget, int type) +{ + struct snd_soc_dapm_widget *widget = swidget->widget; + struct sof_ipc_comp_process *process; + struct sof_widget_data *wdata = NULL; + size_t ipc_data_size = 0; + size_t ipc_size; + int offset = 0; + int ret; + int i; + + /* allocate struct for widget control data sizes and types */ + if (widget->num_kcontrols) { + wdata = kcalloc(widget->num_kcontrols, sizeof(*wdata), GFP_KERNEL); + if (!wdata) + return -ENOMEM; + + /* get possible component controls and get size of all pdata */ + ret = sof_get_control_data(scomp, widget, wdata, &ipc_data_size); + if (ret < 0) + goto out; + } + + ipc_size = sizeof(struct sof_ipc_comp_process) + ipc_data_size; + + /* we are exceeding max ipc size, config needs to be sent separately */ + if (ipc_size > SOF_IPC_MSG_MAX_SIZE) { + ipc_size -= ipc_data_size; + ipc_data_size = 0; + } + + process = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id); + if (!process) { + ret = -ENOMEM; + goto out; + } + + swidget->private = process; + + /* configure iir IPC message */ + process->comp.type = type; + process->config.hdr.size = sizeof(process->config); + + /* parse one set of comp tokens */ + ret = sof_update_ipc_object(scomp, &process->config, SOF_COMP_TOKENS, + swidget->tuples, swidget->num_tuples, + sizeof(process->config), 1); + if (ret < 0) + goto err; + + dev_dbg(scomp->dev, "loaded process %s\n", swidget->widget->name); + sof_dbg_comp_config(scomp, &process->config); + + /* + * found private data in control, so copy it. + * get possible component controls - get size of all pdata, + * then memcpy with headers + */ + if (ipc_data_size) { + for (i = 0; i < widget->num_kcontrols; i++) { + if (!wdata[i].pdata_size) + continue; + + memcpy(&process->data[offset], wdata[i].pdata, + wdata[i].pdata_size); + offset += wdata[i].pdata_size; + } + } + + process->size = ipc_data_size; + + kfree(wdata); + + return 0; +err: + kfree(swidget->private); + swidget->private = NULL; +out: + kfree(wdata); + return ret; +} + +static enum sof_comp_type find_process_comp_type(enum sof_ipc_process_type type) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sof_process); i++) { + if (sof_process[i].type == type) + return sof_process[i].comp_type; + } + + return SOF_COMP_NONE; +} + +/* + * Processing Component Topology - can be "effect", "codec", or general + * "processing". + */ + +static int sof_widget_update_ipc_comp_process(struct snd_sof_widget *swidget) +{ + struct snd_soc_component *scomp = swidget->scomp; + struct sof_ipc_comp_process config; + int ret; + + memset(&config, 0, sizeof(config)); + config.comp.core = swidget->core; + + /* parse one set of process tokens */ + ret = sof_update_ipc_object(scomp, &config, SOF_PROCESS_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(config), 1); + if (ret < 0) + return ret; + + /* now load process specific data and send IPC */ + return sof_process_load(scomp, swidget, find_process_comp_type(config.type)); +} + +static int sof_link_hda_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink, + struct sof_ipc_dai_config *config, struct snd_sof_dai *dai) +{ + struct sof_dai_private_data *private = dai->private; + u32 size = sizeof(*config); + int ret; + + /* init IPC */ + memset(&config->hda, 0, sizeof(config->hda)); + config->hdr.size = size; + + /* parse one set of HDA tokens */ + ret = sof_update_ipc_object(scomp, &config->hda, SOF_HDA_TOKENS, slink->tuples, + slink->num_tuples, size, 1); + if (ret < 0) + return ret; + + dev_dbg(scomp->dev, "HDA config rate %d channels %d\n", + config->hda.rate, config->hda.channels); + + config->hda.link_dma_ch = DMA_CHAN_INVALID; + + dai->number_configs = 1; + dai->current_config = 0; + private->dai_config = kmemdup(config, size, GFP_KERNEL); + if (!private->dai_config) + return -ENOMEM; + + return 0; +} + +static void sof_dai_set_format(struct snd_soc_tplg_hw_config *hw_config, + struct sof_ipc_dai_config *config) +{ + /* clock directions wrt codec */ + config->format &= ~SOF_DAI_FMT_CLOCK_PROVIDER_MASK; + if (hw_config->bclk_provider == SND_SOC_TPLG_BCLK_CP) { + /* codec is bclk provider */ + if (hw_config->fsync_provider == SND_SOC_TPLG_FSYNC_CP) + config->format |= SOF_DAI_FMT_CBP_CFP; + else + config->format |= SOF_DAI_FMT_CBP_CFC; + } else { + /* codec is bclk consumer */ + if (hw_config->fsync_provider == SND_SOC_TPLG_FSYNC_CP) + config->format |= SOF_DAI_FMT_CBC_CFP; + else + config->format |= SOF_DAI_FMT_CBC_CFC; + } + + /* inverted clocks ? */ + config->format &= ~SOF_DAI_FMT_INV_MASK; + if (hw_config->invert_bclk) { + if (hw_config->invert_fsync) + config->format |= SOF_DAI_FMT_IB_IF; + else + config->format |= SOF_DAI_FMT_IB_NF; + } else { + if (hw_config->invert_fsync) + config->format |= SOF_DAI_FMT_NB_IF; + else + config->format |= SOF_DAI_FMT_NB_NF; + } +} + +static int sof_link_sai_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink, + struct sof_ipc_dai_config *config, struct snd_sof_dai *dai) +{ + struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs; + struct sof_dai_private_data *private = dai->private; + u32 size = sizeof(*config); + int ret; + + /* handle master/slave and inverted clocks */ + sof_dai_set_format(hw_config, config); + + /* init IPC */ + memset(&config->sai, 0, sizeof(config->sai)); + config->hdr.size = size; + + /* parse one set of SAI tokens */ + ret = sof_update_ipc_object(scomp, &config->sai, SOF_SAI_TOKENS, slink->tuples, + slink->num_tuples, size, 1); + if (ret < 0) + return ret; + + config->sai.mclk_rate = le32_to_cpu(hw_config->mclk_rate); + config->sai.bclk_rate = le32_to_cpu(hw_config->bclk_rate); + config->sai.fsync_rate = le32_to_cpu(hw_config->fsync_rate); + config->sai.mclk_direction = hw_config->mclk_direction; + + config->sai.tdm_slots = le32_to_cpu(hw_config->tdm_slots); + config->sai.tdm_slot_width = le32_to_cpu(hw_config->tdm_slot_width); + config->sai.rx_slots = le32_to_cpu(hw_config->rx_slots); + config->sai.tx_slots = le32_to_cpu(hw_config->tx_slots); + + dev_info(scomp->dev, + "tplg: config SAI%d fmt 0x%x mclk %d width %d slots %d mclk id %d\n", + config->dai_index, config->format, + config->sai.mclk_rate, config->sai.tdm_slot_width, + config->sai.tdm_slots, config->sai.mclk_id); + + if (config->sai.tdm_slots < 1 || config->sai.tdm_slots > 8) { + dev_err(scomp->dev, "Invalid channel count for SAI%d\n", config->dai_index); + return -EINVAL; + } + + dai->number_configs = 1; + dai->current_config = 0; + private->dai_config = kmemdup(config, size, GFP_KERNEL); + if (!private->dai_config) + return -ENOMEM; + + return 0; +} + +static int sof_link_esai_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink, + struct sof_ipc_dai_config *config, struct snd_sof_dai *dai) +{ + struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs; + struct sof_dai_private_data *private = dai->private; + u32 size = sizeof(*config); + int ret; + + /* handle master/slave and inverted clocks */ + sof_dai_set_format(hw_config, config); + + /* init IPC */ + memset(&config->esai, 0, sizeof(config->esai)); + config->hdr.size = size; + + /* parse one set of ESAI tokens */ + ret = sof_update_ipc_object(scomp, &config->esai, SOF_ESAI_TOKENS, slink->tuples, + slink->num_tuples, size, 1); + if (ret < 0) + return ret; + + config->esai.mclk_rate = le32_to_cpu(hw_config->mclk_rate); + config->esai.bclk_rate = le32_to_cpu(hw_config->bclk_rate); + config->esai.fsync_rate = le32_to_cpu(hw_config->fsync_rate); + config->esai.mclk_direction = hw_config->mclk_direction; + config->esai.tdm_slots = le32_to_cpu(hw_config->tdm_slots); + config->esai.tdm_slot_width = le32_to_cpu(hw_config->tdm_slot_width); + config->esai.rx_slots = le32_to_cpu(hw_config->rx_slots); + config->esai.tx_slots = le32_to_cpu(hw_config->tx_slots); + + dev_info(scomp->dev, + "tplg: config ESAI%d fmt 0x%x mclk %d width %d slots %d mclk id %d\n", + config->dai_index, config->format, + config->esai.mclk_rate, config->esai.tdm_slot_width, + config->esai.tdm_slots, config->esai.mclk_id); + + if (config->esai.tdm_slots < 1 || config->esai.tdm_slots > 8) { + dev_err(scomp->dev, "Invalid channel count for ESAI%d\n", config->dai_index); + return -EINVAL; + } + + dai->number_configs = 1; + dai->current_config = 0; + private->dai_config = kmemdup(config, size, GFP_KERNEL); + if (!private->dai_config) + return -ENOMEM; + + return 0; +} + +static int sof_link_acp_dmic_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink, + struct sof_ipc_dai_config *config, struct snd_sof_dai *dai) +{ + struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs; + struct sof_dai_private_data *private = dai->private; + u32 size = sizeof(*config); + int ret; + + /* handle master/slave and inverted clocks */ + sof_dai_set_format(hw_config, config); + + config->hdr.size = size; + + /* parse the required set of ACPDMIC tokens based on num_hw_cfgs */ + ret = sof_update_ipc_object(scomp, &config->acpdmic, SOF_ACPDMIC_TOKENS, slink->tuples, + slink->num_tuples, size, slink->num_hw_configs); + if (ret < 0) + return ret; + + dev_info(scomp->dev, "ACP_DMIC config ACP%d channel %d rate %d\n", + config->dai_index, config->acpdmic.pdm_ch, + config->acpdmic.pdm_rate); + + dai->number_configs = 1; + dai->current_config = 0; + private->dai_config = kmemdup(config, size, GFP_KERNEL); + if (!private->dai_config) + return -ENOMEM; + + return 0; +} + +static int sof_link_acp_bt_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink, + struct sof_ipc_dai_config *config, struct snd_sof_dai *dai) +{ + struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs; + struct sof_dai_private_data *private = dai->private; + u32 size = sizeof(*config); + + /* handle master/slave and inverted clocks */ + sof_dai_set_format(hw_config, config); + + /* init IPC */ + memset(&config->acpbt, 0, sizeof(config->acpbt)); + config->hdr.size = size; + + config->acpbt.fsync_rate = le32_to_cpu(hw_config->fsync_rate); + config->acpbt.tdm_slots = le32_to_cpu(hw_config->tdm_slots); + + dev_info(scomp->dev, "ACP_BT config ACP%d channel %d rate %d\n", + config->dai_index, config->acpbt.tdm_slots, + config->acpbt.fsync_rate); + + dai->number_configs = 1; + dai->current_config = 0; + private->dai_config = kmemdup(config, size, GFP_KERNEL); + if (!private->dai_config) + return -ENOMEM; + + return 0; +} + +static int sof_link_acp_sp_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink, + struct sof_ipc_dai_config *config, struct snd_sof_dai *dai) +{ + struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs; + struct sof_dai_private_data *private = dai->private; + u32 size = sizeof(*config); + + /* handle master/slave and inverted clocks */ + sof_dai_set_format(hw_config, config); + + /* init IPC */ + memset(&config->acpsp, 0, sizeof(config->acpsp)); + config->hdr.size = size; + + config->acpsp.fsync_rate = le32_to_cpu(hw_config->fsync_rate); + config->acpsp.tdm_slots = le32_to_cpu(hw_config->tdm_slots); + + dev_info(scomp->dev, "ACP_SP config ACP%d channel %d rate %d\n", + config->dai_index, config->acpsp.tdm_slots, + config->acpsp.fsync_rate); + + dai->number_configs = 1; + dai->current_config = 0; + private->dai_config = kmemdup(config, size, GFP_KERNEL); + if (!private->dai_config) + return -ENOMEM; + + return 0; +} + +static int sof_link_acp_hs_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink, + struct sof_ipc_dai_config *config, struct snd_sof_dai *dai) +{ + struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs; + struct sof_dai_private_data *private = dai->private; + u32 size = sizeof(*config); + + /* Configures the DAI hardware format and inverted clocks */ + sof_dai_set_format(hw_config, config); + + /* init IPC */ + memset(&config->acphs, 0, sizeof(config->acphs)); + config->hdr.size = size; + + config->acphs.fsync_rate = le32_to_cpu(hw_config->fsync_rate); + config->acphs.tdm_slots = le32_to_cpu(hw_config->tdm_slots); + + dev_info(scomp->dev, "ACP_HS config ACP%d channel %d rate %d\n", + config->dai_index, config->acphs.tdm_slots, + config->acphs.fsync_rate); + + dai->number_configs = 1; + dai->current_config = 0; + private->dai_config = kmemdup(config, size, GFP_KERNEL); + if (!private->dai_config) + return -ENOMEM; + + return 0; +} + +static int sof_link_afe_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink, + struct sof_ipc_dai_config *config, struct snd_sof_dai *dai) +{ + struct sof_dai_private_data *private = dai->private; + u32 size = sizeof(*config); + int ret; + + config->hdr.size = size; + + /* parse the required set of AFE tokens based on num_hw_cfgs */ + ret = sof_update_ipc_object(scomp, &config->afe, SOF_AFE_TOKENS, slink->tuples, + slink->num_tuples, size, slink->num_hw_configs); + if (ret < 0) + return ret; + + dev_dbg(scomp->dev, "AFE config rate %d channels %d format:%d\n", + config->afe.rate, config->afe.channels, config->afe.format); + + config->afe.stream_id = DMA_CHAN_INVALID; + + dai->number_configs = 1; + dai->current_config = 0; + private->dai_config = kmemdup(config, size, GFP_KERNEL); + if (!private->dai_config) + return -ENOMEM; + + return 0; +} + +static int sof_link_ssp_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink, + struct sof_ipc_dai_config *config, struct snd_sof_dai *dai) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs; + struct sof_dai_private_data *private = dai->private; + u32 size = sizeof(*config); + int current_config = 0; + int i, ret; + + /* + * Parse common data, we should have 1 common data per hw_config. + */ + ret = sof_update_ipc_object(scomp, &config->ssp, SOF_SSP_TOKENS, slink->tuples, + slink->num_tuples, size, slink->num_hw_configs); + if (ret < 0) + return ret; + + /* process all possible hw configs */ + for (i = 0; i < slink->num_hw_configs; i++) { + if (le32_to_cpu(hw_config[i].id) == slink->default_hw_cfg_id) + current_config = i; + + /* handle master/slave and inverted clocks */ + sof_dai_set_format(&hw_config[i], &config[i]); + + config[i].hdr.size = size; + + if (sdev->mclk_id_override) { + dev_dbg(scomp->dev, "tplg: overriding topology mclk_id %d by quirk %d\n", + config[i].ssp.mclk_id, sdev->mclk_id_quirk); + config[i].ssp.mclk_id = sdev->mclk_id_quirk; + } + + /* copy differentiating hw configs to ipc structs */ + config[i].ssp.mclk_rate = le32_to_cpu(hw_config[i].mclk_rate); + config[i].ssp.bclk_rate = le32_to_cpu(hw_config[i].bclk_rate); + config[i].ssp.fsync_rate = le32_to_cpu(hw_config[i].fsync_rate); + config[i].ssp.tdm_slots = le32_to_cpu(hw_config[i].tdm_slots); + config[i].ssp.tdm_slot_width = le32_to_cpu(hw_config[i].tdm_slot_width); + config[i].ssp.mclk_direction = hw_config[i].mclk_direction; + config[i].ssp.rx_slots = le32_to_cpu(hw_config[i].rx_slots); + config[i].ssp.tx_slots = le32_to_cpu(hw_config[i].tx_slots); + + dev_dbg(scomp->dev, "tplg: config SSP%d fmt %#x mclk %d bclk %d fclk %d width (%d)%d slots %d mclk id %d quirks %d clks_control %#x\n", + config[i].dai_index, config[i].format, + config[i].ssp.mclk_rate, config[i].ssp.bclk_rate, + config[i].ssp.fsync_rate, config[i].ssp.sample_valid_bits, + config[i].ssp.tdm_slot_width, config[i].ssp.tdm_slots, + config[i].ssp.mclk_id, config[i].ssp.quirks, config[i].ssp.clks_control); + + /* validate SSP fsync rate and channel count */ + if (config[i].ssp.fsync_rate < 8000 || config[i].ssp.fsync_rate > 192000) { + dev_err(scomp->dev, "Invalid fsync rate for SSP%d\n", config[i].dai_index); + return -EINVAL; + } + + if (config[i].ssp.tdm_slots < 1 || config[i].ssp.tdm_slots > 8) { + dev_err(scomp->dev, "Invalid channel count for SSP%d\n", + config[i].dai_index); + return -EINVAL; + } + } + + dai->number_configs = slink->num_hw_configs; + dai->current_config = current_config; + private->dai_config = kmemdup(config, size * slink->num_hw_configs, GFP_KERNEL); + if (!private->dai_config) + return -ENOMEM; + + return 0; +} + +static int sof_link_dmic_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink, + struct sof_ipc_dai_config *config, struct snd_sof_dai *dai) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_dai_private_data *private = dai->private; + struct sof_ipc_fw_ready *ready = &sdev->fw_ready; + struct sof_ipc_fw_version *v = &ready->version; + size_t size = sizeof(*config); + int i, ret; + + /* Ensure the entire DMIC config struct is zeros */ + memset(&config->dmic, 0, sizeof(config->dmic)); + + /* parse the required set of DMIC tokens based on num_hw_cfgs */ + ret = sof_update_ipc_object(scomp, &config->dmic, SOF_DMIC_TOKENS, slink->tuples, + slink->num_tuples, size, slink->num_hw_configs); + if (ret < 0) + return ret; + + /* parse the required set of DMIC PDM tokens based on number of active PDM's */ + ret = sof_update_ipc_object(scomp, &config->dmic.pdm[0], SOF_DMIC_PDM_TOKENS, + slink->tuples, slink->num_tuples, + sizeof(struct sof_ipc_dai_dmic_pdm_ctrl), + config->dmic.num_pdm_active); + if (ret < 0) + return ret; + + /* set IPC header size */ + config->hdr.size = size; + + /* debug messages */ + dev_dbg(scomp->dev, "tplg: config DMIC%d driver version %d\n", + config->dai_index, config->dmic.driver_ipc_version); + dev_dbg(scomp->dev, "pdmclk_min %d pdm_clkmax %d duty_min %d\n", + config->dmic.pdmclk_min, config->dmic.pdmclk_max, + config->dmic.duty_min); + dev_dbg(scomp->dev, "duty_max %d fifo_fs %d num_pdms active %d\n", + config->dmic.duty_max, config->dmic.fifo_fs, + config->dmic.num_pdm_active); + dev_dbg(scomp->dev, "fifo word length %d\n", config->dmic.fifo_bits); + + for (i = 0; i < config->dmic.num_pdm_active; i++) { + dev_dbg(scomp->dev, "pdm %d mic a %d mic b %d\n", + config->dmic.pdm[i].id, + config->dmic.pdm[i].enable_mic_a, + config->dmic.pdm[i].enable_mic_b); + dev_dbg(scomp->dev, "pdm %d polarity a %d polarity b %d\n", + config->dmic.pdm[i].id, + config->dmic.pdm[i].polarity_mic_a, + config->dmic.pdm[i].polarity_mic_b); + dev_dbg(scomp->dev, "pdm %d clk_edge %d skew %d\n", + config->dmic.pdm[i].id, + config->dmic.pdm[i].clk_edge, + config->dmic.pdm[i].skew); + } + + /* + * this takes care of backwards compatible handling of fifo_bits_b. + * It is deprecated since firmware ABI version 3.0.1. + */ + if (SOF_ABI_VER(v->major, v->minor, v->micro) < SOF_ABI_VER(3, 0, 1)) + config->dmic.fifo_bits_b = config->dmic.fifo_bits; + + dai->number_configs = 1; + dai->current_config = 0; + private->dai_config = kmemdup(config, size, GFP_KERNEL); + if (!private->dai_config) + return -ENOMEM; + + return 0; +} + +static int sof_link_alh_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink, + struct sof_ipc_dai_config *config, struct snd_sof_dai *dai) +{ + struct sof_dai_private_data *private = dai->private; + u32 size = sizeof(*config); + int ret; + + /* parse the required set of ALH tokens based on num_hw_cfgs */ + ret = sof_update_ipc_object(scomp, &config->alh, SOF_ALH_TOKENS, slink->tuples, + slink->num_tuples, size, slink->num_hw_configs); + if (ret < 0) + return ret; + + /* init IPC */ + config->hdr.size = size; + + /* set config for all DAI's with name matching the link name */ + dai->number_configs = 1; + dai->current_config = 0; + private->dai_config = kmemdup(config, size, GFP_KERNEL); + if (!private->dai_config) + return -ENOMEM; + + return 0; +} + +static int sof_ipc3_widget_setup_comp_dai(struct snd_sof_widget *swidget) +{ + struct snd_soc_component *scomp = swidget->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_sof_dai *dai = swidget->private; + struct sof_dai_private_data *private; + struct sof_ipc_comp_dai *comp_dai; + size_t ipc_size = sizeof(*comp_dai); + struct sof_ipc_dai_config *config; + struct snd_sof_dai_link *slink; + int ret; + + private = kzalloc(sizeof(*private), GFP_KERNEL); + if (!private) + return -ENOMEM; + + dai->private = private; + + private->comp_dai = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id); + if (!private->comp_dai) { + ret = -ENOMEM; + goto free; + } + + /* configure dai IPC message */ + comp_dai = private->comp_dai; + comp_dai->comp.type = SOF_COMP_DAI; + comp_dai->config.hdr.size = sizeof(comp_dai->config); + + /* parse one set of DAI tokens */ + ret = sof_update_ipc_object(scomp, comp_dai, SOF_DAI_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(*comp_dai), 1); + if (ret < 0) + goto free; + + /* update comp_tokens */ + ret = sof_update_ipc_object(scomp, &comp_dai->config, SOF_COMP_TOKENS, + swidget->tuples, swidget->num_tuples, + sizeof(comp_dai->config), 1); + if (ret < 0) + goto free; + + dev_dbg(scomp->dev, "dai %s: type %d index %d\n", + swidget->widget->name, comp_dai->type, comp_dai->dai_index); + sof_dbg_comp_config(scomp, &comp_dai->config); + + /* now update DAI config */ + list_for_each_entry(slink, &sdev->dai_link_list, list) { + struct sof_ipc_dai_config common_config; + int i; + + if (strcmp(slink->link->name, dai->name)) + continue; + + /* Reserve memory for all hw configs, eventually freed by widget */ + config = kcalloc(slink->num_hw_configs, sizeof(*config), GFP_KERNEL); + if (!config) { + ret = -ENOMEM; + goto free_comp; + } + + /* parse one set of DAI link tokens */ + ret = sof_update_ipc_object(scomp, &common_config, SOF_DAI_LINK_TOKENS, + slink->tuples, slink->num_tuples, + sizeof(common_config), 1); + if (ret < 0) + goto free_config; + + for (i = 0; i < slink->num_hw_configs; i++) { + config[i].hdr.cmd = SOF_IPC_GLB_DAI_MSG | SOF_IPC_DAI_CONFIG; + config[i].format = le32_to_cpu(slink->hw_configs[i].fmt); + config[i].type = common_config.type; + config[i].dai_index = comp_dai->dai_index; + } + + switch (common_config.type) { + case SOF_DAI_INTEL_SSP: + ret = sof_link_ssp_load(scomp, slink, config, dai); + break; + case SOF_DAI_INTEL_DMIC: + ret = sof_link_dmic_load(scomp, slink, config, dai); + break; + case SOF_DAI_INTEL_HDA: + ret = sof_link_hda_load(scomp, slink, config, dai); + break; + case SOF_DAI_INTEL_ALH: + ret = sof_link_alh_load(scomp, slink, config, dai); + break; + case SOF_DAI_IMX_SAI: + ret = sof_link_sai_load(scomp, slink, config, dai); + break; + case SOF_DAI_IMX_ESAI: + ret = sof_link_esai_load(scomp, slink, config, dai); + break; + case SOF_DAI_AMD_BT: + ret = sof_link_acp_bt_load(scomp, slink, config, dai); + break; + case SOF_DAI_AMD_SP: + ret = sof_link_acp_sp_load(scomp, slink, config, dai); + break; + case SOF_DAI_AMD_HS: + ret = sof_link_acp_hs_load(scomp, slink, config, dai); + break; + case SOF_DAI_AMD_DMIC: + ret = sof_link_acp_dmic_load(scomp, slink, config, dai); + break; + case SOF_DAI_MEDIATEK_AFE: + ret = sof_link_afe_load(scomp, slink, config, dai); + break; + default: + break; + } + if (ret < 0) { + dev_err(scomp->dev, "failed to load config for dai %s\n", dai->name); + goto free_config; + } + + kfree(config); + } + + return 0; +free_config: + kfree(config); +free_comp: + kfree(comp_dai); +free: + kfree(private); + dai->private = NULL; + return ret; +} + +static void sof_ipc3_widget_free_comp_dai(struct snd_sof_widget *swidget) +{ + switch (swidget->id) { + case snd_soc_dapm_dai_in: + case snd_soc_dapm_dai_out: + { + struct snd_sof_dai *dai = swidget->private; + struct sof_dai_private_data *dai_data; + + if (!dai) + return; + + dai_data = dai->private; + if (dai_data) { + kfree(dai_data->comp_dai); + kfree(dai_data->dai_config); + kfree(dai_data); + } + kfree(dai); + break; + } + default: + break; + } +} + +static int sof_ipc3_route_setup(struct snd_sof_dev *sdev, struct snd_sof_route *sroute) +{ + struct sof_ipc_pipe_comp_connect connect; + struct sof_ipc_reply reply; + int ret; + + connect.hdr.size = sizeof(connect); + connect.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_CONNECT; + connect.source_id = sroute->src_widget->comp_id; + connect.sink_id = sroute->sink_widget->comp_id; + + dev_dbg(sdev->dev, "setting up route %s -> %s\n", + sroute->src_widget->widget->name, + sroute->sink_widget->widget->name); + + /* send ipc */ + ret = sof_ipc_tx_message(sdev->ipc, &connect, sizeof(connect), &reply, sizeof(reply)); + if (ret < 0) + dev_err(sdev->dev, "%s: route %s -> %s failed\n", __func__, + sroute->src_widget->widget->name, sroute->sink_widget->widget->name); + + return ret; +} + +static int sof_ipc3_control_load_bytes(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol) +{ + struct sof_ipc_ctrl_data *cdata; + int ret; + + if (scontrol->max_size < (sizeof(*cdata) + sizeof(struct sof_abi_hdr))) { + dev_err(sdev->dev, "%s: insufficient size for a bytes control: %zu.\n", + __func__, scontrol->max_size); + return -EINVAL; + } + + if (scontrol->priv_size > scontrol->max_size - sizeof(*cdata)) { + dev_err(sdev->dev, + "%s: bytes data size %zu exceeds max %zu.\n", __func__, + scontrol->priv_size, scontrol->max_size - sizeof(*cdata)); + return -EINVAL; + } + + scontrol->ipc_control_data = kzalloc(scontrol->max_size, GFP_KERNEL); + if (!scontrol->ipc_control_data) + return -ENOMEM; + + scontrol->size = sizeof(struct sof_ipc_ctrl_data) + scontrol->priv_size; + + cdata = scontrol->ipc_control_data; + cdata->cmd = SOF_CTRL_CMD_BINARY; + cdata->index = scontrol->index; + + if (scontrol->priv_size > 0) { + memcpy(cdata->data, scontrol->priv, scontrol->priv_size); + kfree(scontrol->priv); + scontrol->priv = NULL; + + if (cdata->data->magic != SOF_ABI_MAGIC) { + dev_err(sdev->dev, "Wrong ABI magic 0x%08x.\n", cdata->data->magic); + ret = -EINVAL; + goto err; + } + + if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, cdata->data->abi)) { + dev_err(sdev->dev, "Incompatible ABI version 0x%08x.\n", + cdata->data->abi); + ret = -EINVAL; + goto err; + } + + if (cdata->data->size + sizeof(struct sof_abi_hdr) != scontrol->priv_size) { + dev_err(sdev->dev, "Conflict in bytes vs. priv size.\n"); + ret = -EINVAL; + goto err; + } + } + + return 0; +err: + kfree(scontrol->ipc_control_data); + scontrol->ipc_control_data = NULL; + return ret; +} + +static int sof_ipc3_control_load_volume(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol) +{ + struct sof_ipc_ctrl_data *cdata; + int i; + + /* init the volume get/put data */ + scontrol->size = struct_size(cdata, chanv, scontrol->num_channels); + + scontrol->ipc_control_data = kzalloc(scontrol->size, GFP_KERNEL); + if (!scontrol->ipc_control_data) + return -ENOMEM; + + cdata = scontrol->ipc_control_data; + cdata->index = scontrol->index; + + /* set cmd for mixer control */ + if (scontrol->max == 1) { + cdata->cmd = SOF_CTRL_CMD_SWITCH; + return 0; + } + + cdata->cmd = SOF_CTRL_CMD_VOLUME; + + /* set default volume values to 0dB in control */ + for (i = 0; i < scontrol->num_channels; i++) { + cdata->chanv[i].channel = i; + cdata->chanv[i].value = VOL_ZERO_DB; + } + + return 0; +} + +static int sof_ipc3_control_load_enum(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol) +{ + struct sof_ipc_ctrl_data *cdata; + + /* init the enum get/put data */ + scontrol->size = struct_size(cdata, chanv, scontrol->num_channels); + + scontrol->ipc_control_data = kzalloc(scontrol->size, GFP_KERNEL); + if (!scontrol->ipc_control_data) + return -ENOMEM; + + cdata = scontrol->ipc_control_data; + cdata->index = scontrol->index; + cdata->cmd = SOF_CTRL_CMD_ENUM; + + return 0; +} + +static int sof_ipc3_control_setup(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol) +{ + switch (scontrol->info_type) { + case SND_SOC_TPLG_CTL_VOLSW: + case SND_SOC_TPLG_CTL_VOLSW_SX: + case SND_SOC_TPLG_CTL_VOLSW_XR_SX: + return sof_ipc3_control_load_volume(sdev, scontrol); + case SND_SOC_TPLG_CTL_BYTES: + return sof_ipc3_control_load_bytes(sdev, scontrol); + case SND_SOC_TPLG_CTL_ENUM: + case SND_SOC_TPLG_CTL_ENUM_VALUE: + return sof_ipc3_control_load_enum(sdev, scontrol); + default: + break; + } + + return 0; +} + +static int sof_ipc3_control_free(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol) +{ + struct sof_ipc_free fcomp; + + fcomp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_FREE; + fcomp.hdr.size = sizeof(fcomp); + fcomp.id = scontrol->comp_id; + + /* send IPC to the DSP */ + return sof_ipc_tx_message(sdev->ipc, &fcomp, sizeof(fcomp), NULL, 0); +} + +/* send pcm params ipc */ +static int sof_ipc3_keyword_detect_pcm_params(struct snd_sof_widget *swidget, int dir) +{ + struct snd_soc_component *scomp = swidget->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_ipc_pcm_params_reply ipc_params_reply; + struct snd_pcm_hw_params *params; + struct sof_ipc_pcm_params pcm; + struct snd_sof_pcm *spcm; + int ret; + + /* get runtime PCM params using widget's stream name */ + spcm = snd_sof_find_spcm_name(scomp, swidget->widget->sname); + if (!spcm) { + dev_err(scomp->dev, "Cannot find PCM for %s\n", swidget->widget->name); + return -EINVAL; + } + + params = &spcm->params[dir]; + + /* set IPC PCM params */ + memset(&pcm, 0, sizeof(pcm)); + pcm.hdr.size = sizeof(pcm); + pcm.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS; + pcm.comp_id = swidget->comp_id; + pcm.params.hdr.size = sizeof(pcm.params); + pcm.params.direction = dir; + pcm.params.sample_valid_bytes = params_width(params) >> 3; + pcm.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED; + pcm.params.rate = params_rate(params); + pcm.params.channels = params_channels(params); + pcm.params.host_period_bytes = params_period_bytes(params); + + /* set format */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16: + pcm.params.frame_fmt = SOF_IPC_FRAME_S16_LE; + break; + case SNDRV_PCM_FORMAT_S24: + pcm.params.frame_fmt = SOF_IPC_FRAME_S24_4LE; + break; + case SNDRV_PCM_FORMAT_S32: + pcm.params.frame_fmt = SOF_IPC_FRAME_S32_LE; + break; + default: + return -EINVAL; + } + + /* send IPC to the DSP */ + ret = sof_ipc_tx_message(sdev->ipc, &pcm, sizeof(pcm), + &ipc_params_reply, sizeof(ipc_params_reply)); + if (ret < 0) + dev_err(scomp->dev, "%s: PCM params failed for %s\n", __func__, + swidget->widget->name); + + return ret; +} + + /* send stream trigger ipc */ +static int sof_ipc3_keyword_detect_trigger(struct snd_sof_widget *swidget, int cmd) +{ + struct snd_soc_component *scomp = swidget->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_ipc_stream stream; + struct sof_ipc_reply reply; + int ret; + + /* set IPC stream params */ + stream.hdr.size = sizeof(stream); + stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | cmd; + stream.comp_id = swidget->comp_id; + + /* send IPC to the DSP */ + ret = sof_ipc_tx_message(sdev->ipc, &stream, sizeof(stream), &reply, sizeof(reply)); + if (ret < 0) + dev_err(scomp->dev, "%s: Failed to trigger %s\n", __func__, swidget->widget->name); + + return ret; +} + +static int sof_ipc3_keyword_dapm_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_sof_widget *swidget = w->dobj.private; + struct snd_soc_component *scomp; + int stream = SNDRV_PCM_STREAM_CAPTURE; + struct snd_sof_pcm *spcm; + int ret = 0; + + if (!swidget) + return 0; + + scomp = swidget->scomp; + + dev_dbg(scomp->dev, "received event %d for widget %s\n", + event, w->name); + + /* get runtime PCM params using widget's stream name */ + spcm = snd_sof_find_spcm_name(scomp, swidget->widget->sname); + if (!spcm) { + dev_err(scomp->dev, "%s: Cannot find PCM for %s\n", __func__, + swidget->widget->name); + return -EINVAL; + } + + /* process events */ + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (spcm->stream[stream].suspend_ignored) { + dev_dbg(scomp->dev, "PRE_PMU event ignored, KWD pipeline is already RUNNING\n"); + return 0; + } + + /* set pcm params */ + ret = sof_ipc3_keyword_detect_pcm_params(swidget, stream); + if (ret < 0) { + dev_err(scomp->dev, "%s: Failed to set pcm params for widget %s\n", + __func__, swidget->widget->name); + break; + } + + /* start trigger */ + ret = sof_ipc3_keyword_detect_trigger(swidget, SOF_IPC_STREAM_TRIG_START); + if (ret < 0) + dev_err(scomp->dev, "%s: Failed to trigger widget %s\n", __func__, + swidget->widget->name); + break; + case SND_SOC_DAPM_POST_PMD: + if (spcm->stream[stream].suspend_ignored) { + dev_dbg(scomp->dev, + "POST_PMD event ignored, KWD pipeline will remain RUNNING\n"); + return 0; + } + + /* stop trigger */ + ret = sof_ipc3_keyword_detect_trigger(swidget, SOF_IPC_STREAM_TRIG_STOP); + if (ret < 0) + dev_err(scomp->dev, "%s: Failed to trigger widget %s\n", __func__, + swidget->widget->name); + + /* pcm free */ + ret = sof_ipc3_keyword_detect_trigger(swidget, SOF_IPC_STREAM_PCM_FREE); + if (ret < 0) + dev_err(scomp->dev, "%s: Failed to free PCM for widget %s\n", __func__, + swidget->widget->name); + break; + default: + break; + } + + return ret; +} + +/* event handlers for keyword detect component */ +static const struct snd_soc_tplg_widget_events sof_kwd_events[] = { + {SOF_KEYWORD_DETECT_DAPM_EVENT, sof_ipc3_keyword_dapm_event}, +}; + +static int sof_ipc3_widget_bind_event(struct snd_soc_component *scomp, + struct snd_sof_widget *swidget, u16 event_type) +{ + struct sof_ipc_comp *ipc_comp; + + /* validate widget event type */ + switch (event_type) { + case SOF_KEYWORD_DETECT_DAPM_EVENT: + /* only KEYWORD_DETECT comps should handle this */ + if (swidget->id != snd_soc_dapm_effect) + break; + + ipc_comp = swidget->private; + if (ipc_comp && ipc_comp->type != SOF_COMP_KEYWORD_DETECT) + break; + + /* bind event to keyword detect comp */ + return snd_soc_tplg_widget_bind_event(swidget->widget, sof_kwd_events, + ARRAY_SIZE(sof_kwd_events), event_type); + default: + break; + } + + dev_err(scomp->dev, "Invalid event type %d for widget %s\n", event_type, + swidget->widget->name); + + return -EINVAL; +} + +static int sof_ipc3_complete_pipeline(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) +{ + struct sof_ipc_pipe_ready ready; + struct sof_ipc_reply reply; + int ret; + + dev_dbg(sdev->dev, "tplg: complete pipeline %s id %d\n", + swidget->widget->name, swidget->comp_id); + + memset(&ready, 0, sizeof(ready)); + ready.hdr.size = sizeof(ready); + ready.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_PIPE_COMPLETE; + ready.comp_id = swidget->comp_id; + + ret = sof_ipc_tx_message(sdev->ipc, &ready, sizeof(ready), &reply, sizeof(reply)); + if (ret < 0) + return ret; + + return 1; +} + +static int sof_ipc3_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) +{ + struct sof_ipc_free ipc_free = { + .hdr = { + .size = sizeof(ipc_free), + .cmd = SOF_IPC_GLB_TPLG_MSG, + }, + .id = swidget->comp_id, + }; + struct sof_ipc_reply reply; + int ret; + + if (!swidget->private) + return 0; + + switch (swidget->id) { + case snd_soc_dapm_scheduler: + { + ipc_free.hdr.cmd |= SOF_IPC_TPLG_PIPE_FREE; + break; + } + case snd_soc_dapm_buffer: + ipc_free.hdr.cmd |= SOF_IPC_TPLG_BUFFER_FREE; + break; + default: + ipc_free.hdr.cmd |= SOF_IPC_TPLG_COMP_FREE; + break; + } + + ret = sof_ipc_tx_message(sdev->ipc, &ipc_free, sizeof(ipc_free), + &reply, sizeof(reply)); + if (ret < 0) + dev_err(sdev->dev, "failed to free widget %s\n", swidget->widget->name); + + return ret; +} + +static int sof_ipc3_dai_config(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget, + unsigned int flags, struct snd_sof_dai_config_data *data) +{ + struct sof_ipc_fw_version *v = &sdev->fw_ready.version; + struct snd_sof_dai *dai = swidget->private; + struct sof_dai_private_data *private; + struct sof_ipc_dai_config *config; + struct sof_ipc_reply reply; + int ret = 0; + + if (!dai || !dai->private) { + dev_err(sdev->dev, "No private data for DAI %s\n", swidget->widget->name); + return -EINVAL; + } + + private = dai->private; + if (!private->dai_config) { + dev_err(sdev->dev, "No config for DAI %s\n", dai->name); + return -EINVAL; + } + + config = &private->dai_config[dai->current_config]; + if (!config) { + dev_err(sdev->dev, "Invalid current config for DAI %s\n", dai->name); + return -EINVAL; + } + + switch (config->type) { + case SOF_DAI_INTEL_SSP: + /* + * DAI_CONFIG IPC during hw_params/hw_free for SSP DAI's is not supported in older + * firmware + */ + if (v->abi_version < SOF_ABI_VER(3, 18, 0) && + ((flags & SOF_DAI_CONFIG_FLAGS_HW_PARAMS) || + (flags & SOF_DAI_CONFIG_FLAGS_HW_FREE))) + return 0; + break; + case SOF_DAI_INTEL_HDA: + if (data) + config->hda.link_dma_ch = data->dai_data; + break; + case SOF_DAI_INTEL_ALH: + if (data) { + config->dai_index = data->dai_index; + config->alh.stream_id = data->dai_data; + } + break; + default: + break; + } + + config->flags = flags; + + /* only send the IPC if the widget is set up in the DSP */ + if (swidget->use_count > 0) { + ret = sof_ipc_tx_message(sdev->ipc, config, config->hdr.size, + &reply, sizeof(reply)); + if (ret < 0) + dev_err(sdev->dev, "Failed to set dai config for %s\n", dai->name); + } + + return ret; +} + +static int sof_ipc3_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) +{ + struct sof_ipc_comp_reply reply; + int ret; + + if (!swidget->private) + return 0; + + switch (swidget->id) { + case snd_soc_dapm_dai_in: + case snd_soc_dapm_dai_out: + { + struct snd_sof_dai *dai = swidget->private; + struct sof_dai_private_data *dai_data = dai->private; + struct sof_ipc_comp *comp = &dai_data->comp_dai->comp; + + ret = sof_ipc_tx_message(sdev->ipc, dai_data->comp_dai, + comp->hdr.size, &reply, sizeof(reply)); + break; + } + case snd_soc_dapm_scheduler: + { + struct sof_ipc_pipe_new *pipeline; + + pipeline = swidget->private; + ret = sof_ipc_tx_message(sdev->ipc, pipeline, sizeof(*pipeline), + &reply, sizeof(reply)); + break; + } + default: + { + struct sof_ipc_cmd_hdr *hdr; + + hdr = swidget->private; + ret = sof_ipc_tx_message(sdev->ipc, swidget->private, hdr->size, + &reply, sizeof(reply)); + break; + } + } + if (ret < 0) + dev_err(sdev->dev, "Failed to setup widget %s\n", swidget->widget->name); + + return ret; +} + +static int sof_ipc3_set_up_all_pipelines(struct snd_sof_dev *sdev, bool verify) +{ + struct sof_ipc_fw_version *v = &sdev->fw_ready.version; + struct snd_sof_widget *swidget; + struct snd_sof_route *sroute; + int ret; + + /* restore pipeline components */ + list_for_each_entry(swidget, &sdev->widget_list, list) { + /* only set up the widgets belonging to static pipelines */ + if (!verify && swidget->dynamic_pipeline_widget) + continue; + + /* + * For older firmware, skip scheduler widgets in this loop, + * sof_widget_setup() will be called in the 'complete pipeline' loop + */ + if (v->abi_version < SOF_ABI_VER(3, 19, 0) && + swidget->id == snd_soc_dapm_scheduler) + continue; + + /* update DAI config. The IPC will be sent in sof_widget_setup() */ + if (WIDGET_IS_DAI(swidget->id)) { + struct snd_sof_dai *dai = swidget->private; + struct sof_dai_private_data *private; + struct sof_ipc_dai_config *config; + + if (!dai || !dai->private) + continue; + private = dai->private; + if (!private->dai_config) + continue; + + config = private->dai_config; + /* + * The link DMA channel would be invalidated for running + * streams but not for streams that were in the PAUSED + * state during suspend. So invalidate it here before setting + * the dai config in the DSP. + */ + if (config->type == SOF_DAI_INTEL_HDA) + config->hda.link_dma_ch = DMA_CHAN_INVALID; + } + + ret = sof_widget_setup(sdev, swidget); + if (ret < 0) + return ret; + } + + /* restore pipeline connections */ + list_for_each_entry(sroute, &sdev->route_list, list) { + /* only set up routes belonging to static pipelines */ + if (!verify && (sroute->src_widget->dynamic_pipeline_widget || + sroute->sink_widget->dynamic_pipeline_widget)) + continue; + + /* + * For virtual routes, both sink and source are not buffer. IPC3 only supports + * connections between a buffer and a component. Ignore the rest. + */ + if (sroute->src_widget->id != snd_soc_dapm_buffer && + sroute->sink_widget->id != snd_soc_dapm_buffer) + continue; + + ret = sof_route_setup(sdev, sroute->src_widget->widget, + sroute->sink_widget->widget); + if (ret < 0) { + dev_err(sdev->dev, "%s: route set up failed\n", __func__); + return ret; + } + } + + /* complete pipeline */ + list_for_each_entry(swidget, &sdev->widget_list, list) { + switch (swidget->id) { + case snd_soc_dapm_scheduler: + /* only complete static pipelines */ + if (!verify && swidget->dynamic_pipeline_widget) + continue; + + if (v->abi_version < SOF_ABI_VER(3, 19, 0)) { + ret = sof_widget_setup(sdev, swidget); + if (ret < 0) + return ret; + } + + swidget->complete = sof_ipc3_complete_pipeline(sdev, swidget); + if (swidget->complete < 0) + return swidget->complete; + break; + default: + break; + } + } + + return 0; +} + +/* + * Free the PCM, its associated widgets and set the prepared flag to false for all PCMs that + * did not get suspended(ex: paused streams) so the widgets can be set up again during resume. + */ +static int sof_tear_down_left_over_pipelines(struct snd_sof_dev *sdev) +{ + struct snd_sof_widget *swidget; + struct snd_sof_pcm *spcm; + int dir, ret; + + /* + * free all PCMs and their associated DAPM widgets if their connected DAPM widget + * list is not NULL. This should only be true for paused streams at this point. + * This is equivalent to the handling of FE DAI suspend trigger for running streams. + */ + list_for_each_entry(spcm, &sdev->pcm_list, list) { + for_each_pcm_streams(dir) { + struct snd_pcm_substream *substream = spcm->stream[dir].substream; + + if (!substream || !substream->runtime) + continue; + + if (spcm->stream[dir].list) { + ret = sof_pcm_stream_free(sdev, substream, spcm, dir, true); + if (ret < 0) + return ret; + } + } + } + + /* + * free any left over DAI widgets. This is equivalent to the handling of suspend trigger + * for the BE DAI for running streams. + */ + list_for_each_entry(swidget, &sdev->widget_list, list) + if (WIDGET_IS_DAI(swidget->id) && swidget->use_count == 1) { + ret = sof_widget_free(sdev, swidget); + if (ret < 0) + return ret; + } + + return 0; +} + +/* + * For older firmware, this function doesn't free widgets for static pipelines during suspend. + * It only resets use_count for all widgets. + */ +static int sof_ipc3_tear_down_all_pipelines(struct snd_sof_dev *sdev, bool verify) +{ + struct sof_ipc_fw_version *v = &sdev->fw_ready.version; + struct snd_sof_widget *swidget; + struct snd_sof_route *sroute; + int ret; + + /* + * This function is called during suspend and for one-time topology verification during + * first boot. In both cases, there is no need to protect swidget->use_count and + * sroute->setup because during suspend all running streams are suspended and during + * topology loading the sound card unavailable to open PCMs. + */ + list_for_each_entry(swidget, &sdev->widget_list, list) { + if (swidget->dynamic_pipeline_widget) + continue; + + /* Do not free widgets for static pipelines with FW ABI older than 3.19 */ + if (!verify && !swidget->dynamic_pipeline_widget && + v->abi_version < SOF_ABI_VER(3, 19, 0)) { + swidget->use_count = 0; + swidget->complete = 0; + continue; + } + + ret = sof_widget_free(sdev, swidget); + if (ret < 0) + return ret; + } + + /* + * Tear down all pipelines associated with PCMs that did not get suspended + * and unset the prepare flag so that they can be set up again during resume. + * Skip this step for older firmware. + */ + if (!verify && v->abi_version >= SOF_ABI_VER(3, 19, 0)) { + ret = sof_tear_down_left_over_pipelines(sdev); + if (ret < 0) { + dev_err(sdev->dev, "failed to tear down paused pipelines\n"); + return ret; + } + } + + list_for_each_entry(sroute, &sdev->route_list, list) + sroute->setup = false; + + /* + * before suspending, make sure the refcounts are all zeroed out. There's no way + * to recover at this point but this will help root cause bad sequences leading to + * more issues on resume + */ + list_for_each_entry(swidget, &sdev->widget_list, list) { + if (swidget->use_count != 0) { + dev_err(sdev->dev, "%s: widget %s is still in use: count %d\n", + __func__, swidget->widget->name, swidget->use_count); + } + } + + return 0; +} + +static int sof_ipc3_dai_get_clk(struct snd_sof_dev *sdev, struct snd_sof_dai *dai, int clk_type) +{ + struct sof_dai_private_data *private = dai->private; + + if (!private || !private->dai_config) + return 0; + + switch (private->dai_config->type) { + case SOF_DAI_INTEL_SSP: + switch (clk_type) { + case SOF_DAI_CLK_INTEL_SSP_MCLK: + return private->dai_config->ssp.mclk_rate; + case SOF_DAI_CLK_INTEL_SSP_BCLK: + return private->dai_config->ssp.bclk_rate; + default: + break; + } + dev_err(sdev->dev, "fail to get SSP clk %d rate\n", clk_type); + break; + default: + /* not yet implemented for platforms other than the above */ + dev_err(sdev->dev, "DAI type %d not supported yet!\n", private->dai_config->type); + break; + } + + return -EINVAL; +} + +static int sof_ipc3_parse_manifest(struct snd_soc_component *scomp, int index, + struct snd_soc_tplg_manifest *man) +{ + u32 size = le32_to_cpu(man->priv.size); + u32 abi_version; + + /* backward compatible with tplg without ABI info */ + if (!size) { + dev_dbg(scomp->dev, "No topology ABI info\n"); + return 0; + } + + if (size != SOF_IPC3_TPLG_ABI_SIZE) { + dev_err(scomp->dev, "%s: Invalid topology ABI size: %u\n", + __func__, size); + return -EINVAL; + } + + dev_info(scomp->dev, + "Topology: ABI %d:%d:%d Kernel ABI %d:%d:%d\n", + man->priv.data[0], man->priv.data[1], man->priv.data[2], + SOF_ABI_MAJOR, SOF_ABI_MINOR, SOF_ABI_PATCH); + + abi_version = SOF_ABI_VER(man->priv.data[0], man->priv.data[1], man->priv.data[2]); + + if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, abi_version)) { + dev_err(scomp->dev, "%s: Incompatible topology ABI version\n", __func__); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_SND_SOC_SOF_STRICT_ABI_CHECKS) && + SOF_ABI_VERSION_MINOR(abi_version) > SOF_ABI_MINOR) { + dev_err(scomp->dev, "%s: Topology ABI is more recent than kernel\n", __func__); + return -EINVAL; + } + + return 0; +} + +/* token list for each topology object */ +static enum sof_tokens host_token_list[] = { + SOF_CORE_TOKENS, + SOF_COMP_EXT_TOKENS, + SOF_PCM_TOKENS, + SOF_COMP_TOKENS, +}; + +static enum sof_tokens comp_generic_token_list[] = { + SOF_CORE_TOKENS, + SOF_COMP_EXT_TOKENS, + SOF_COMP_TOKENS, +}; + +static enum sof_tokens buffer_token_list[] = { + SOF_BUFFER_TOKENS, +}; + +static enum sof_tokens pipeline_token_list[] = { + SOF_CORE_TOKENS, + SOF_COMP_EXT_TOKENS, + SOF_PIPELINE_TOKENS, + SOF_SCHED_TOKENS, +}; + +static enum sof_tokens asrc_token_list[] = { + SOF_CORE_TOKENS, + SOF_COMP_EXT_TOKENS, + SOF_ASRC_TOKENS, + SOF_COMP_TOKENS, +}; + +static enum sof_tokens src_token_list[] = { + SOF_CORE_TOKENS, + SOF_COMP_EXT_TOKENS, + SOF_SRC_TOKENS, + SOF_COMP_TOKENS +}; + +static enum sof_tokens pga_token_list[] = { + SOF_CORE_TOKENS, + SOF_COMP_EXT_TOKENS, + SOF_VOLUME_TOKENS, + SOF_COMP_TOKENS, +}; + +static enum sof_tokens dai_token_list[] = { + SOF_CORE_TOKENS, + SOF_COMP_EXT_TOKENS, + SOF_DAI_TOKENS, + SOF_COMP_TOKENS, +}; + +static enum sof_tokens process_token_list[] = { + SOF_CORE_TOKENS, + SOF_COMP_EXT_TOKENS, + SOF_PROCESS_TOKENS, + SOF_COMP_TOKENS, +}; + +static const struct sof_ipc_tplg_widget_ops tplg_ipc3_widget_ops[SND_SOC_DAPM_TYPE_COUNT] = { + [snd_soc_dapm_aif_in] = {sof_ipc3_widget_setup_comp_host, sof_ipc3_widget_free_comp, + host_token_list, ARRAY_SIZE(host_token_list), NULL}, + [snd_soc_dapm_aif_out] = {sof_ipc3_widget_setup_comp_host, sof_ipc3_widget_free_comp, + host_token_list, ARRAY_SIZE(host_token_list), NULL}, + + [snd_soc_dapm_dai_in] = {sof_ipc3_widget_setup_comp_dai, sof_ipc3_widget_free_comp_dai, + dai_token_list, ARRAY_SIZE(dai_token_list), NULL}, + [snd_soc_dapm_dai_out] = {sof_ipc3_widget_setup_comp_dai, sof_ipc3_widget_free_comp_dai, + dai_token_list, ARRAY_SIZE(dai_token_list), NULL}, + [snd_soc_dapm_buffer] = {sof_ipc3_widget_setup_comp_buffer, sof_ipc3_widget_free_comp, + buffer_token_list, ARRAY_SIZE(buffer_token_list), NULL}, + [snd_soc_dapm_mixer] = {sof_ipc3_widget_setup_comp_mixer, sof_ipc3_widget_free_comp, + comp_generic_token_list, ARRAY_SIZE(comp_generic_token_list), + NULL}, + [snd_soc_dapm_src] = {sof_ipc3_widget_setup_comp_src, sof_ipc3_widget_free_comp, + src_token_list, ARRAY_SIZE(src_token_list), NULL}, + [snd_soc_dapm_asrc] = {sof_ipc3_widget_setup_comp_asrc, sof_ipc3_widget_free_comp, + asrc_token_list, ARRAY_SIZE(asrc_token_list), NULL}, + [snd_soc_dapm_siggen] = {sof_ipc3_widget_setup_comp_tone, sof_ipc3_widget_free_comp, + comp_generic_token_list, ARRAY_SIZE(comp_generic_token_list), + NULL}, + [snd_soc_dapm_scheduler] = {sof_ipc3_widget_setup_comp_pipeline, sof_ipc3_widget_free_comp, + pipeline_token_list, ARRAY_SIZE(pipeline_token_list), NULL}, + [snd_soc_dapm_pga] = {sof_ipc3_widget_setup_comp_pga, sof_ipc3_widget_free_comp, + pga_token_list, ARRAY_SIZE(pga_token_list), NULL}, + [snd_soc_dapm_mux] = {sof_ipc3_widget_setup_comp_mux, sof_ipc3_widget_free_comp, + comp_generic_token_list, ARRAY_SIZE(comp_generic_token_list), NULL}, + [snd_soc_dapm_demux] = {sof_ipc3_widget_setup_comp_mux, sof_ipc3_widget_free_comp, + comp_generic_token_list, ARRAY_SIZE(comp_generic_token_list), + NULL}, + [snd_soc_dapm_effect] = {sof_widget_update_ipc_comp_process, sof_ipc3_widget_free_comp, + process_token_list, ARRAY_SIZE(process_token_list), + sof_ipc3_widget_bind_event}, +}; + +const struct sof_ipc_tplg_ops ipc3_tplg_ops = { + .widget = tplg_ipc3_widget_ops, + .control = &tplg_ipc3_control_ops, + .route_setup = sof_ipc3_route_setup, + .control_setup = sof_ipc3_control_setup, + .control_free = sof_ipc3_control_free, + .pipeline_complete = sof_ipc3_complete_pipeline, + .token_list = ipc3_token_list, + .widget_free = sof_ipc3_widget_free, + .widget_setup = sof_ipc3_widget_setup, + .dai_config = sof_ipc3_dai_config, + .dai_get_clk = sof_ipc3_dai_get_clk, + .set_up_all_pipelines = sof_ipc3_set_up_all_pipelines, + .tear_down_all_pipelines = sof_ipc3_tear_down_all_pipelines, + .parse_manifest = sof_ipc3_parse_manifest, +}; diff --git a/sound/soc/sof/ipc3.c b/sound/soc/sof/ipc3.c new file mode 100644 index 000000000000..b28af3a48b70 --- /dev/null +++ b/sound/soc/sof/ipc3.c @@ -0,0 +1,1097 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2021 Intel Corporation. All rights reserved. +// +// + +#include <sound/sof/stream.h> +#include <sound/sof/control.h> +#include <trace/events/sof.h> +#include "sof-priv.h" +#include "sof-audio.h" +#include "ipc3-priv.h" +#include "ops.h" + +typedef void (*ipc3_rx_callback)(struct snd_sof_dev *sdev, void *msg_buf); + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_VERBOSE_IPC) +static void ipc3_log_header(struct device *dev, u8 *text, u32 cmd) +{ + u8 *str; + u8 *str2 = NULL; + u32 glb; + u32 type; + bool is_sof_ipc_stream_position = false; + + glb = cmd & SOF_GLB_TYPE_MASK; + type = cmd & SOF_CMD_TYPE_MASK; + + switch (glb) { + case SOF_IPC_GLB_REPLY: + str = "GLB_REPLY"; break; + case SOF_IPC_GLB_COMPOUND: + str = "GLB_COMPOUND"; break; + case SOF_IPC_GLB_TPLG_MSG: + str = "GLB_TPLG_MSG"; + switch (type) { + case SOF_IPC_TPLG_COMP_NEW: + str2 = "COMP_NEW"; break; + case SOF_IPC_TPLG_COMP_FREE: + str2 = "COMP_FREE"; break; + case SOF_IPC_TPLG_COMP_CONNECT: + str2 = "COMP_CONNECT"; break; + case SOF_IPC_TPLG_PIPE_NEW: + str2 = "PIPE_NEW"; break; + case SOF_IPC_TPLG_PIPE_FREE: + str2 = "PIPE_FREE"; break; + case SOF_IPC_TPLG_PIPE_CONNECT: + str2 = "PIPE_CONNECT"; break; + case SOF_IPC_TPLG_PIPE_COMPLETE: + str2 = "PIPE_COMPLETE"; break; + case SOF_IPC_TPLG_BUFFER_NEW: + str2 = "BUFFER_NEW"; break; + case SOF_IPC_TPLG_BUFFER_FREE: + str2 = "BUFFER_FREE"; break; + default: + str2 = "unknown type"; break; + } + break; + case SOF_IPC_GLB_PM_MSG: + str = "GLB_PM_MSG"; + switch (type) { + case SOF_IPC_PM_CTX_SAVE: + str2 = "CTX_SAVE"; break; + case SOF_IPC_PM_CTX_RESTORE: + str2 = "CTX_RESTORE"; break; + case SOF_IPC_PM_CTX_SIZE: + str2 = "CTX_SIZE"; break; + case SOF_IPC_PM_CLK_SET: + str2 = "CLK_SET"; break; + case SOF_IPC_PM_CLK_GET: + str2 = "CLK_GET"; break; + case SOF_IPC_PM_CLK_REQ: + str2 = "CLK_REQ"; break; + case SOF_IPC_PM_CORE_ENABLE: + str2 = "CORE_ENABLE"; break; + case SOF_IPC_PM_GATE: + str2 = "GATE"; break; + default: + str2 = "unknown type"; break; + } + break; + case SOF_IPC_GLB_COMP_MSG: + str = "GLB_COMP_MSG"; + switch (type) { + case SOF_IPC_COMP_SET_VALUE: + str2 = "SET_VALUE"; break; + case SOF_IPC_COMP_GET_VALUE: + str2 = "GET_VALUE"; break; + case SOF_IPC_COMP_SET_DATA: + str2 = "SET_DATA"; break; + case SOF_IPC_COMP_GET_DATA: + str2 = "GET_DATA"; break; + default: + str2 = "unknown type"; break; + } + break; + case SOF_IPC_GLB_STREAM_MSG: + str = "GLB_STREAM_MSG"; + switch (type) { + case SOF_IPC_STREAM_PCM_PARAMS: + str2 = "PCM_PARAMS"; break; + case SOF_IPC_STREAM_PCM_PARAMS_REPLY: + str2 = "PCM_REPLY"; break; + case SOF_IPC_STREAM_PCM_FREE: + str2 = "PCM_FREE"; break; + case SOF_IPC_STREAM_TRIG_START: + str2 = "TRIG_START"; break; + case SOF_IPC_STREAM_TRIG_STOP: + str2 = "TRIG_STOP"; break; + case SOF_IPC_STREAM_TRIG_PAUSE: + str2 = "TRIG_PAUSE"; break; + case SOF_IPC_STREAM_TRIG_RELEASE: + str2 = "TRIG_RELEASE"; break; + case SOF_IPC_STREAM_TRIG_DRAIN: + str2 = "TRIG_DRAIN"; break; + case SOF_IPC_STREAM_TRIG_XRUN: + str2 = "TRIG_XRUN"; break; + case SOF_IPC_STREAM_POSITION: + is_sof_ipc_stream_position = true; + str2 = "POSITION"; break; + case SOF_IPC_STREAM_VORBIS_PARAMS: + str2 = "VORBIS_PARAMS"; break; + case SOF_IPC_STREAM_VORBIS_FREE: + str2 = "VORBIS_FREE"; break; + default: + str2 = "unknown type"; break; + } + break; + case SOF_IPC_FW_READY: + str = "FW_READY"; break; + case SOF_IPC_GLB_DAI_MSG: + str = "GLB_DAI_MSG"; + switch (type) { + case SOF_IPC_DAI_CONFIG: + str2 = "CONFIG"; break; + case SOF_IPC_DAI_LOOPBACK: + str2 = "LOOPBACK"; break; + default: + str2 = "unknown type"; break; + } + break; + case SOF_IPC_GLB_TRACE_MSG: + str = "GLB_TRACE_MSG"; + switch (type) { + case SOF_IPC_TRACE_DMA_PARAMS: + str2 = "DMA_PARAMS"; break; + case SOF_IPC_TRACE_DMA_POSITION: + if (!sof_debug_check_flag(SOF_DBG_PRINT_DMA_POSITION_UPDATE_LOGS)) + return; + str2 = "DMA_POSITION"; break; + case SOF_IPC_TRACE_DMA_PARAMS_EXT: + str2 = "DMA_PARAMS_EXT"; break; + case SOF_IPC_TRACE_FILTER_UPDATE: + str2 = "FILTER_UPDATE"; break; + case SOF_IPC_TRACE_DMA_FREE: + str2 = "DMA_FREE"; break; + default: + str2 = "unknown type"; break; + } + break; + case SOF_IPC_GLB_TEST_MSG: + str = "GLB_TEST_MSG"; + switch (type) { + case SOF_IPC_TEST_IPC_FLOOD: + str2 = "IPC_FLOOD"; break; + default: + str2 = "unknown type"; break; + } + break; + case SOF_IPC_GLB_DEBUG: + str = "GLB_DEBUG"; + switch (type) { + case SOF_IPC_DEBUG_MEM_USAGE: + str2 = "MEM_USAGE"; break; + default: + str2 = "unknown type"; break; + } + break; + case SOF_IPC_GLB_PROBE: + str = "GLB_PROBE"; + switch (type) { + case SOF_IPC_PROBE_INIT: + str2 = "INIT"; break; + case SOF_IPC_PROBE_DEINIT: + str2 = "DEINIT"; break; + case SOF_IPC_PROBE_DMA_ADD: + str2 = "DMA_ADD"; break; + case SOF_IPC_PROBE_DMA_INFO: + str2 = "DMA_INFO"; break; + case SOF_IPC_PROBE_DMA_REMOVE: + str2 = "DMA_REMOVE"; break; + case SOF_IPC_PROBE_POINT_ADD: + str2 = "POINT_ADD"; break; + case SOF_IPC_PROBE_POINT_INFO: + str2 = "POINT_INFO"; break; + case SOF_IPC_PROBE_POINT_REMOVE: + str2 = "POINT_REMOVE"; break; + default: + str2 = "unknown type"; break; + } + break; + default: + str = "unknown GLB command"; break; + } + + if (str2) { + if (is_sof_ipc_stream_position) + trace_sof_stream_position_ipc_rx(dev); + else + dev_dbg(dev, "%s: 0x%x: %s: %s\n", text, cmd, str, str2); + } else { + dev_dbg(dev, "%s: 0x%x: %s\n", text, cmd, str); + } +} +#else +static inline void ipc3_log_header(struct device *dev, u8 *text, u32 cmd) +{ + if ((cmd & SOF_GLB_TYPE_MASK) != SOF_IPC_GLB_TRACE_MSG) + dev_dbg(dev, "%s: 0x%x\n", text, cmd); +} +#endif + +static int sof_ipc3_get_reply(struct snd_sof_dev *sdev) +{ + struct snd_sof_ipc_msg *msg = sdev->msg; + struct sof_ipc_reply *reply; + int ret = 0; + + /* get the generic reply */ + reply = msg->reply_data; + snd_sof_dsp_mailbox_read(sdev, sdev->host_box.offset, reply, sizeof(*reply)); + + if (reply->error < 0) + return reply->error; + + if (!reply->hdr.size) { + /* Reply should always be >= sizeof(struct sof_ipc_reply) */ + if (msg->reply_size) + dev_err(sdev->dev, + "empty reply received, expected %zu bytes\n", + msg->reply_size); + else + dev_err(sdev->dev, "empty reply received\n"); + + return -EINVAL; + } + + if (msg->reply_size > 0) { + if (reply->hdr.size == msg->reply_size) { + ret = 0; + } else if (reply->hdr.size < msg->reply_size) { + dev_dbg(sdev->dev, + "reply size (%u) is less than expected (%zu)\n", + reply->hdr.size, msg->reply_size); + + msg->reply_size = reply->hdr.size; + ret = 0; + } else { + dev_err(sdev->dev, + "reply size (%u) exceeds the buffer size (%zu)\n", + reply->hdr.size, msg->reply_size); + ret = -EINVAL; + } + + /* + * get the full message if reply->hdr.size <= msg->reply_size + * and the reply->hdr.size > sizeof(struct sof_ipc_reply) + */ + if (!ret && msg->reply_size > sizeof(*reply)) + snd_sof_dsp_mailbox_read(sdev, sdev->host_box.offset, + msg->reply_data, msg->reply_size); + } + + return ret; +} + +/* wait for IPC message reply */ +static int ipc3_wait_tx_done(struct snd_sof_ipc *ipc, void *reply_data) +{ + struct snd_sof_ipc_msg *msg = &ipc->msg; + struct sof_ipc_cmd_hdr *hdr = msg->msg_data; + struct snd_sof_dev *sdev = ipc->sdev; + int ret; + + /* wait for DSP IPC completion */ + ret = wait_event_timeout(msg->waitq, msg->ipc_complete, + msecs_to_jiffies(sdev->ipc_timeout)); + + if (ret == 0) { + dev_err(sdev->dev, + "ipc tx timed out for %#x (msg/reply size: %d/%zu)\n", + hdr->cmd, hdr->size, msg->reply_size); + snd_sof_handle_fw_exception(ipc->sdev, "IPC timeout"); + ret = -ETIMEDOUT; + } else { + ret = msg->reply_error; + if (ret < 0) { + dev_err(sdev->dev, + "ipc tx error for %#x (msg/reply size: %d/%zu): %d\n", + hdr->cmd, hdr->size, msg->reply_size, ret); + } else { + if (sof_debug_check_flag(SOF_DBG_PRINT_IPC_SUCCESS_LOGS)) + ipc3_log_header(sdev->dev, "ipc tx succeeded", hdr->cmd); + if (msg->reply_size) + /* copy the data returned from DSP */ + memcpy(reply_data, msg->reply_data, + msg->reply_size); + } + + /* re-enable dumps after successful IPC tx */ + if (sdev->ipc_dump_printed) { + sdev->dbg_dump_printed = false; + sdev->ipc_dump_printed = false; + } + } + + return ret; +} + +/* send IPC message from host to DSP */ +static int ipc3_tx_msg_unlocked(struct snd_sof_ipc *ipc, + void *msg_data, size_t msg_bytes, + void *reply_data, size_t reply_bytes) +{ + struct sof_ipc_cmd_hdr *hdr = msg_data; + struct snd_sof_dev *sdev = ipc->sdev; + int ret; + + ret = sof_ipc_send_msg(sdev, msg_data, msg_bytes, reply_bytes); + + if (ret) { + dev_err_ratelimited(sdev->dev, + "%s: ipc message send for %#x failed: %d\n", + __func__, hdr->cmd, ret); + return ret; + } + + ipc3_log_header(sdev->dev, "ipc tx", hdr->cmd); + + /* now wait for completion */ + return ipc3_wait_tx_done(ipc, reply_data); +} + +static int sof_ipc3_tx_msg(struct snd_sof_dev *sdev, void *msg_data, size_t msg_bytes, + void *reply_data, size_t reply_bytes, bool no_pm) +{ + struct snd_sof_ipc *ipc = sdev->ipc; + int ret; + + if (!msg_data || msg_bytes < sizeof(struct sof_ipc_cmd_hdr)) { + dev_err_ratelimited(sdev->dev, "No IPC message to send\n"); + return -EINVAL; + } + + if (!no_pm) { + const struct sof_dsp_power_state target_state = { + .state = SOF_DSP_PM_D0, + }; + + /* ensure the DSP is in D0 before sending a new IPC */ + ret = snd_sof_dsp_set_power_state(sdev, &target_state); + if (ret < 0) { + dev_err(sdev->dev, "%s: resuming DSP failed: %d\n", + __func__, ret); + return ret; + } + } + + /* Serialise IPC TX */ + mutex_lock(&ipc->tx_mutex); + + ret = ipc3_tx_msg_unlocked(ipc, msg_data, msg_bytes, reply_data, reply_bytes); + + mutex_unlock(&ipc->tx_mutex); + + return ret; +} + +static int sof_ipc3_set_get_data(struct snd_sof_dev *sdev, void *data, size_t data_bytes, + bool set) +{ + size_t msg_bytes, hdr_bytes, payload_size, send_bytes; + struct sof_ipc_ctrl_data *cdata = data; + struct sof_ipc_ctrl_data *cdata_chunk; + struct snd_sof_ipc *ipc = sdev->ipc; + size_t offset = 0; + u8 *src, *dst; + u32 num_msg; + int ret = 0; + int i; + + if (!cdata || data_bytes < sizeof(*cdata)) + return -EINVAL; + + if ((cdata->rhdr.hdr.cmd & SOF_GLB_TYPE_MASK) != SOF_IPC_GLB_COMP_MSG) { + dev_err(sdev->dev, "%s: Not supported message type of %#x\n", + __func__, cdata->rhdr.hdr.cmd); + return -EINVAL; + } + + /* send normal size ipc in one part */ + if (cdata->rhdr.hdr.size <= ipc->max_payload_size) + return sof_ipc3_tx_msg(sdev, cdata, cdata->rhdr.hdr.size, + cdata, cdata->rhdr.hdr.size, false); + + cdata_chunk = kzalloc(ipc->max_payload_size, GFP_KERNEL); + if (!cdata_chunk) + return -ENOMEM; + + switch (cdata->type) { + case SOF_CTRL_TYPE_VALUE_CHAN_GET: + case SOF_CTRL_TYPE_VALUE_CHAN_SET: + hdr_bytes = sizeof(struct sof_ipc_ctrl_data); + if (set) { + src = (u8 *)cdata->chanv; + dst = (u8 *)cdata_chunk->chanv; + } else { + src = (u8 *)cdata_chunk->chanv; + dst = (u8 *)cdata->chanv; + } + break; + case SOF_CTRL_TYPE_DATA_GET: + case SOF_CTRL_TYPE_DATA_SET: + hdr_bytes = sizeof(struct sof_ipc_ctrl_data) + sizeof(struct sof_abi_hdr); + if (set) { + src = (u8 *)cdata->data->data; + dst = (u8 *)cdata_chunk->data->data; + } else { + src = (u8 *)cdata_chunk->data->data; + dst = (u8 *)cdata->data->data; + } + break; + default: + kfree(cdata_chunk); + return -EINVAL; + } + + msg_bytes = cdata->rhdr.hdr.size - hdr_bytes; + payload_size = ipc->max_payload_size - hdr_bytes; + num_msg = DIV_ROUND_UP(msg_bytes, payload_size); + + /* copy the header data */ + memcpy(cdata_chunk, cdata, hdr_bytes); + + /* Serialise IPC TX */ + mutex_lock(&sdev->ipc->tx_mutex); + + /* copy the payload data in a loop */ + for (i = 0; i < num_msg; i++) { + send_bytes = min(msg_bytes, payload_size); + cdata_chunk->num_elems = send_bytes; + cdata_chunk->rhdr.hdr.size = hdr_bytes + send_bytes; + cdata_chunk->msg_index = i; + msg_bytes -= send_bytes; + cdata_chunk->elems_remaining = msg_bytes; + + if (set) + memcpy(dst, src + offset, send_bytes); + + ret = ipc3_tx_msg_unlocked(sdev->ipc, + cdata_chunk, cdata_chunk->rhdr.hdr.size, + cdata_chunk, cdata_chunk->rhdr.hdr.size); + if (ret < 0) + break; + + if (!set) + memcpy(dst + offset, src, send_bytes); + + offset += payload_size; + } + + mutex_unlock(&sdev->ipc->tx_mutex); + + kfree(cdata_chunk); + + return ret; +} + +int sof_ipc3_get_ext_windows(struct snd_sof_dev *sdev, + const struct sof_ipc_ext_data_hdr *ext_hdr) +{ + const struct sof_ipc_window *w = + container_of(ext_hdr, struct sof_ipc_window, ext_hdr); + + if (w->num_windows == 0 || w->num_windows > SOF_IPC_MAX_ELEMS) + return -EINVAL; + + if (sdev->info_window) { + if (memcmp(sdev->info_window, w, ext_hdr->hdr.size)) { + dev_err(sdev->dev, "mismatch between window descriptor from extended manifest and mailbox"); + return -EINVAL; + } + return 0; + } + + /* keep a local copy of the data */ + sdev->info_window = devm_kmemdup(sdev->dev, w, ext_hdr->hdr.size, GFP_KERNEL); + if (!sdev->info_window) + return -ENOMEM; + + return 0; +} + +int sof_ipc3_get_cc_info(struct snd_sof_dev *sdev, + const struct sof_ipc_ext_data_hdr *ext_hdr) +{ + int ret; + + const struct sof_ipc_cc_version *cc = + container_of(ext_hdr, struct sof_ipc_cc_version, ext_hdr); + + if (sdev->cc_version) { + if (memcmp(sdev->cc_version, cc, cc->ext_hdr.hdr.size)) { + dev_err(sdev->dev, + "Receive diverged cc_version descriptions"); + return -EINVAL; + } + return 0; + } + + dev_dbg(sdev->dev, + "Firmware info: used compiler %s %d:%d:%d%s used optimization flags %s\n", + cc->name, cc->major, cc->minor, cc->micro, cc->desc, cc->optim); + + /* create read-only cc_version debugfs to store compiler version info */ + /* use local copy of the cc_version to prevent data corruption */ + if (sdev->first_boot) { + sdev->cc_version = devm_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, "snd_sof_debugfs_buf_item failed\n"); + return ret; + } + } + + return 0; +} + +/* parse the extended FW boot data structures from FW boot message */ +static int ipc3_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 offset) +{ + struct sof_ipc_ext_data_hdr *ext_hdr; + void *ext_data; + int ret = 0; + + ext_data = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!ext_data) + return -ENOMEM; + + /* get first header */ + snd_sof_dsp_block_read(sdev, SOF_FW_BLK_TYPE_SRAM, offset, ext_data, + sizeof(*ext_hdr)); + ext_hdr = ext_data; + + while (ext_hdr->hdr.cmd == SOF_IPC_FW_READY) { + /* read in ext structure */ + snd_sof_dsp_block_read(sdev, SOF_FW_BLK_TYPE_SRAM, + offset + sizeof(*ext_hdr), + (void *)((u8 *)ext_data + sizeof(*ext_hdr)), + ext_hdr->hdr.size - sizeof(*ext_hdr)); + + dev_dbg(sdev->dev, "found ext header type %d size 0x%x\n", + ext_hdr->type, ext_hdr->hdr.size); + + /* process structure data */ + switch (ext_hdr->type) { + case SOF_IPC_EXT_WINDOW: + ret = sof_ipc3_get_ext_windows(sdev, ext_hdr); + break; + case SOF_IPC_EXT_CC_INFO: + ret = sof_ipc3_get_cc_info(sdev, ext_hdr); + break; + case SOF_IPC_EXT_UNUSED: + case SOF_IPC_EXT_PROBE_INFO: + case SOF_IPC_EXT_USER_ABI_INFO: + /* They are supported but we don't do anything here */ + break; + default: + dev_info(sdev->dev, "unknown ext header type %d size 0x%x\n", + ext_hdr->type, ext_hdr->hdr.size); + ret = 0; + break; + } + + if (ret < 0) { + dev_err(sdev->dev, "Failed to parse ext data type %d\n", + ext_hdr->type); + break; + } + + /* move to next header */ + offset += ext_hdr->hdr.size; + snd_sof_dsp_block_read(sdev, SOF_FW_BLK_TYPE_SRAM, offset, ext_data, + sizeof(*ext_hdr)); + ext_hdr = ext_data; + } + + kfree(ext_data); + return ret; +} + +static void ipc3_get_windows(struct snd_sof_dev *sdev) +{ + struct sof_ipc_window_elem *elem; + u32 outbox_offset = 0; + u32 stream_offset = 0; + u32 inbox_offset = 0; + u32 outbox_size = 0; + u32 stream_size = 0; + u32 inbox_size = 0; + u32 debug_size = 0; + u32 debug_offset = 0; + int window_offset; + int i; + + if (!sdev->info_window) { + dev_err(sdev->dev, "%s: No window info present\n", __func__); + return; + } + + for (i = 0; i < sdev->info_window->num_windows; i++) { + elem = &sdev->info_window->window[i]; + + window_offset = snd_sof_dsp_get_window_offset(sdev, elem->id); + if (window_offset < 0) { + dev_warn(sdev->dev, "No offset for window %d\n", elem->id); + continue; + } + + switch (elem->type) { + case SOF_IPC_REGION_UPBOX: + inbox_offset = window_offset + elem->offset; + inbox_size = elem->size; + snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, + inbox_offset, + elem->size, "inbox", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_DOWNBOX: + outbox_offset = window_offset + elem->offset; + outbox_size = elem->size; + snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, + outbox_offset, + elem->size, "outbox", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_TRACE: + snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, + window_offset + elem->offset, + elem->size, "etrace", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_DEBUG: + debug_offset = window_offset + elem->offset; + debug_size = elem->size; + snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, + window_offset + elem->offset, + elem->size, "debug", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_STREAM: + stream_offset = window_offset + elem->offset; + stream_size = elem->size; + snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, + stream_offset, + elem->size, "stream", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_REGS: + snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, + window_offset + elem->offset, + elem->size, "regs", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + case SOF_IPC_REGION_EXCEPTION: + sdev->dsp_oops_offset = window_offset + elem->offset; + snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, + window_offset + elem->offset, + elem->size, "exception", + SOF_DEBUGFS_ACCESS_D0_ONLY); + break; + default: + dev_err(sdev->dev, "%s: Illegal window info: %u\n", + __func__, elem->type); + return; + } + } + + if (outbox_size == 0 || inbox_size == 0) { + dev_err(sdev->dev, "%s: Illegal mailbox window\n", __func__); + return; + } + + sdev->dsp_box.offset = inbox_offset; + sdev->dsp_box.size = inbox_size; + + sdev->host_box.offset = outbox_offset; + sdev->host_box.size = outbox_size; + + sdev->stream_box.offset = stream_offset; + sdev->stream_box.size = stream_size; + + sdev->debug_box.offset = debug_offset; + sdev->debug_box.size = debug_size; + + dev_dbg(sdev->dev, " mailbox upstream 0x%x - size 0x%x\n", + inbox_offset, inbox_size); + dev_dbg(sdev->dev, " mailbox downstream 0x%x - size 0x%x\n", + outbox_offset, outbox_size); + dev_dbg(sdev->dev, " stream region 0x%x - size 0x%x\n", + stream_offset, stream_size); + dev_dbg(sdev->dev, " debug region 0x%x - size 0x%x\n", + debug_offset, debug_size); +} + +static int ipc3_init_reply_data_buffer(struct snd_sof_dev *sdev) +{ + struct snd_sof_ipc_msg *msg = &sdev->ipc->msg; + + msg->reply_data = devm_kzalloc(sdev->dev, SOF_IPC_MSG_MAX_SIZE, GFP_KERNEL); + if (!msg->reply_data) + return -ENOMEM; + + sdev->ipc->max_payload_size = SOF_IPC_MSG_MAX_SIZE; + + return 0; +} + +int sof_ipc3_validate_fw_version(struct snd_sof_dev *sdev) +{ + struct sof_ipc_fw_ready *ready = &sdev->fw_ready; + struct sof_ipc_fw_version *v = &ready->version; + + dev_info(sdev->dev, + "Firmware info: version %d:%d:%d-%s\n", v->major, v->minor, + v->micro, v->tag); + dev_info(sdev->dev, + "Firmware: ABI %d:%d:%d Kernel ABI %d:%d:%d\n", + SOF_ABI_VERSION_MAJOR(v->abi_version), + SOF_ABI_VERSION_MINOR(v->abi_version), + SOF_ABI_VERSION_PATCH(v->abi_version), + SOF_ABI_MAJOR, SOF_ABI_MINOR, SOF_ABI_PATCH); + + if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, v->abi_version)) { + dev_err(sdev->dev, "incompatible FW ABI version\n"); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_SND_SOC_SOF_STRICT_ABI_CHECKS) && + SOF_ABI_VERSION_MINOR(v->abi_version) > SOF_ABI_MINOR) { + dev_err(sdev->dev, "FW ABI is more recent than kernel\n"); + return -EINVAL; + } + + if (ready->flags & SOF_IPC_INFO_BUILD) { + dev_info(sdev->dev, + "Firmware debug build %d on %s-%s - options:\n" + " GDB: %s\n" + " lock debug: %s\n" + " lock vdebug: %s\n", + v->build, v->date, v->time, + (ready->flags & SOF_IPC_INFO_GDB) ? + "enabled" : "disabled", + (ready->flags & SOF_IPC_INFO_LOCKS) ? + "enabled" : "disabled", + (ready->flags & SOF_IPC_INFO_LOCKSV) ? + "enabled" : "disabled"); + } + + /* copy the fw_version into debugfs at first boot */ + memcpy(&sdev->fw_version, v, sizeof(*v)); + + return 0; +} + +static int ipc3_fw_ready(struct snd_sof_dev *sdev, u32 cmd) +{ + struct sof_ipc_fw_ready *fw_ready = &sdev->fw_ready; + int offset; + int ret; + + /* mailbox must be on 4k boundary */ + offset = snd_sof_dsp_get_mailbox_offset(sdev); + if (offset < 0) { + dev_err(sdev->dev, "%s: no mailbox offset\n", __func__); + return offset; + } + + dev_dbg(sdev->dev, "DSP is ready 0x%8.8x offset 0x%x\n", cmd, offset); + + /* no need to re-check version/ABI for subsequent boots */ + if (!sdev->first_boot) + return 0; + + /* + * copy data from the DSP FW ready offset + * Subsequent error handling is not needed for BLK_TYPE_SRAM + */ + ret = snd_sof_dsp_block_read(sdev, SOF_FW_BLK_TYPE_SRAM, offset, fw_ready, + sizeof(*fw_ready)); + if (ret) { + dev_err(sdev->dev, + "Unable to read fw_ready, read from TYPE_SRAM failed\n"); + return ret; + } + + /* make sure ABI version is compatible */ + ret = sof_ipc3_validate_fw_version(sdev); + if (ret < 0) + return ret; + + /* now check for extended data */ + ipc3_fw_parse_ext_data(sdev, offset + sizeof(struct sof_ipc_fw_ready)); + + ipc3_get_windows(sdev); + + return ipc3_init_reply_data_buffer(sdev); +} + +/* IPC stream position. */ +static void ipc3_period_elapsed(struct snd_sof_dev *sdev, u32 msg_id) +{ + struct snd_soc_component *scomp = sdev->component; + struct snd_sof_pcm_stream *stream; + struct sof_ipc_stream_posn posn; + struct snd_sof_pcm *spcm; + int direction, ret; + + spcm = snd_sof_find_spcm_comp(scomp, msg_id, &direction); + if (!spcm) { + dev_err(sdev->dev, "period elapsed for unknown stream, msg_id %d\n", + msg_id); + return; + } + + stream = &spcm->stream[direction]; + ret = snd_sof_ipc_msg_data(sdev, stream->substream, &posn, sizeof(posn)); + if (ret < 0) { + dev_warn(sdev->dev, "failed to read stream position: %d\n", ret); + return; + } + + trace_sof_ipc3_period_elapsed_position(sdev, &posn); + + memcpy(&stream->posn, &posn, sizeof(posn)); + + if (spcm->pcm.compress) + snd_sof_compr_fragment_elapsed(stream->cstream); + else if (stream->substream->runtime && + !stream->substream->runtime->no_period_wakeup) + /* only inform ALSA for period_wakeup mode */ + snd_sof_pcm_period_elapsed(stream->substream); +} + +/* DSP notifies host of an XRUN within FW */ +static void ipc3_xrun(struct snd_sof_dev *sdev, u32 msg_id) +{ + struct snd_soc_component *scomp = sdev->component; + struct snd_sof_pcm_stream *stream; + struct sof_ipc_stream_posn posn; + struct snd_sof_pcm *spcm; + int direction, ret; + + spcm = snd_sof_find_spcm_comp(scomp, msg_id, &direction); + if (!spcm) { + dev_err(sdev->dev, "XRUN for unknown stream, msg_id %d\n", + msg_id); + return; + } + + stream = &spcm->stream[direction]; + ret = snd_sof_ipc_msg_data(sdev, stream->substream, &posn, sizeof(posn)); + if (ret < 0) { + dev_warn(sdev->dev, "failed to read overrun position: %d\n", ret); + return; + } + + dev_dbg(sdev->dev, "posn XRUN: host %llx comp %d size %d\n", + posn.host_posn, posn.xrun_comp_id, posn.xrun_size); + +#if defined(CONFIG_SND_SOC_SOF_DEBUG_XRUN_STOP) + /* stop PCM on XRUN - used for pipeline debug */ + memcpy(&stream->posn, &posn, sizeof(posn)); + snd_pcm_stop_xrun(stream->substream); +#endif +} + +/* stream notifications from firmware */ +static void ipc3_stream_message(struct snd_sof_dev *sdev, void *msg_buf) +{ + struct sof_ipc_cmd_hdr *hdr = msg_buf; + u32 msg_type = hdr->cmd & SOF_CMD_TYPE_MASK; + u32 msg_id = SOF_IPC_MESSAGE_ID(hdr->cmd); + + switch (msg_type) { + case SOF_IPC_STREAM_POSITION: + ipc3_period_elapsed(sdev, msg_id); + break; + case SOF_IPC_STREAM_TRIG_XRUN: + ipc3_xrun(sdev, msg_id); + break; + default: + dev_err(sdev->dev, "unhandled stream message %#x\n", + msg_id); + break; + } +} + +/* component notifications from firmware */ +static void ipc3_comp_notification(struct snd_sof_dev *sdev, void *msg_buf) +{ + const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; + struct sof_ipc_cmd_hdr *hdr = msg_buf; + u32 msg_type = hdr->cmd & SOF_CMD_TYPE_MASK; + + switch (msg_type) { + case SOF_IPC_COMP_GET_VALUE: + case SOF_IPC_COMP_GET_DATA: + break; + default: + dev_err(sdev->dev, "unhandled component message %#x\n", msg_type); + return; + } + + if (tplg_ops->control->update) + tplg_ops->control->update(sdev, msg_buf); +} + +static void ipc3_trace_message(struct snd_sof_dev *sdev, void *msg_buf) +{ + struct sof_ipc_cmd_hdr *hdr = msg_buf; + u32 msg_type = hdr->cmd & SOF_CMD_TYPE_MASK; + + switch (msg_type) { + case SOF_IPC_TRACE_DMA_POSITION: + ipc3_dtrace_posn_update(sdev, msg_buf); + break; + default: + dev_err(sdev->dev, "unhandled trace message %#x\n", msg_type); + break; + } +} + +/* DSP firmware has sent host a message */ +static void sof_ipc3_rx_msg(struct snd_sof_dev *sdev) +{ + ipc3_rx_callback rx_callback = NULL; + struct sof_ipc_cmd_hdr hdr; + void *msg_buf; + u32 cmd; + int err; + + /* read back header */ + err = snd_sof_ipc_msg_data(sdev, NULL, &hdr, sizeof(hdr)); + if (err < 0) { + dev_warn(sdev->dev, "failed to read IPC header: %d\n", err); + return; + } + + if (hdr.size < sizeof(hdr)) { + dev_err(sdev->dev, "The received message size is invalid\n"); + return; + } + + ipc3_log_header(sdev->dev, "ipc rx", hdr.cmd); + + cmd = hdr.cmd & SOF_GLB_TYPE_MASK; + + /* check message type */ + switch (cmd) { + case SOF_IPC_GLB_REPLY: + dev_err(sdev->dev, "ipc reply unknown\n"); + break; + case SOF_IPC_FW_READY: + /* check for FW boot completion */ + if (sdev->fw_state == SOF_FW_BOOT_IN_PROGRESS) { + err = ipc3_fw_ready(sdev, cmd); + if (err < 0) + sof_set_fw_state(sdev, SOF_FW_BOOT_READY_FAILED); + else + sof_set_fw_state(sdev, SOF_FW_BOOT_READY_OK); + + /* wake up firmware loader */ + wake_up(&sdev->boot_wait); + } + break; + case SOF_IPC_GLB_COMPOUND: + case SOF_IPC_GLB_TPLG_MSG: + case SOF_IPC_GLB_PM_MSG: + break; + case SOF_IPC_GLB_COMP_MSG: + rx_callback = ipc3_comp_notification; + break; + case SOF_IPC_GLB_STREAM_MSG: + rx_callback = ipc3_stream_message; + break; + case SOF_IPC_GLB_TRACE_MSG: + rx_callback = ipc3_trace_message; + break; + default: + dev_err(sdev->dev, "%s: Unknown DSP message: 0x%x\n", __func__, cmd); + break; + } + + /* read the full message */ + msg_buf = kmalloc(hdr.size, GFP_KERNEL); + if (!msg_buf) + return; + + err = snd_sof_ipc_msg_data(sdev, NULL, msg_buf, hdr.size); + if (err < 0) { + dev_err(sdev->dev, "%s: Failed to read message: %d\n", __func__, err); + } else { + /* Call local handler for the message */ + if (rx_callback) + rx_callback(sdev, msg_buf); + + /* Notify registered clients */ + sof_client_ipc_rx_dispatcher(sdev, msg_buf); + } + + kfree(msg_buf); + + ipc3_log_header(sdev->dev, "ipc rx done", hdr.cmd); +} + +static int sof_ipc3_set_core_state(struct snd_sof_dev *sdev, int core_idx, bool on) +{ + struct sof_ipc_pm_core_config core_cfg = { + .hdr.size = sizeof(core_cfg), + .hdr.cmd = SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CORE_ENABLE, + }; + struct sof_ipc_reply reply; + + if (on) + core_cfg.enable_mask = sdev->enabled_cores_mask | BIT(core_idx); + else + core_cfg.enable_mask = sdev->enabled_cores_mask & ~BIT(core_idx); + + return sof_ipc3_tx_msg(sdev, &core_cfg, sizeof(core_cfg), + &reply, sizeof(reply), false); +} + +static int sof_ipc3_ctx_ipc(struct snd_sof_dev *sdev, int cmd) +{ + struct sof_ipc_pm_ctx pm_ctx = { + .hdr.size = sizeof(pm_ctx), + .hdr.cmd = SOF_IPC_GLB_PM_MSG | cmd, + }; + struct sof_ipc_reply reply; + + /* send ctx save ipc to dsp */ + return sof_ipc3_tx_msg(sdev, &pm_ctx, sizeof(pm_ctx), + &reply, sizeof(reply), false); +} + +static int sof_ipc3_ctx_save(struct snd_sof_dev *sdev) +{ + return sof_ipc3_ctx_ipc(sdev, SOF_IPC_PM_CTX_SAVE); +} + +static int sof_ipc3_ctx_restore(struct snd_sof_dev *sdev) +{ + return sof_ipc3_ctx_ipc(sdev, SOF_IPC_PM_CTX_RESTORE); +} + +static const struct sof_ipc_pm_ops ipc3_pm_ops = { + .ctx_save = sof_ipc3_ctx_save, + .ctx_restore = sof_ipc3_ctx_restore, + .set_core_state = sof_ipc3_set_core_state, +}; + +const struct sof_ipc_ops ipc3_ops = { + .tplg = &ipc3_tplg_ops, + .pm = &ipc3_pm_ops, + .pcm = &ipc3_pcm_ops, + .fw_loader = &ipc3_loader_ops, + .fw_tracing = &ipc3_dtrace_ops, + + .tx_msg = sof_ipc3_tx_msg, + .rx_msg = sof_ipc3_rx_msg, + .set_get_data = sof_ipc3_set_get_data, + .get_reply = sof_ipc3_get_reply, +}; diff --git a/sound/soc/sof/ipc4-control.c b/sound/soc/sof/ipc4-control.c new file mode 100644 index 000000000000..0d5a578c3496 --- /dev/null +++ b/sound/soc/sof/ipc4-control.c @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// + +#include "sof-priv.h" +#include "sof-audio.h" +#include "ipc4-priv.h" +#include "ipc4-topology.h" + +static int sof_ipc4_set_get_kcontrol_data(struct snd_sof_control *scontrol, bool set) +{ + struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data; + struct snd_soc_component *scomp = scontrol->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + const struct sof_ipc_ops *iops = sdev->ipc->ops; + struct sof_ipc4_msg *msg = &cdata->msg; + struct snd_sof_widget *swidget; + bool widget_found = false; + + /* find widget associated with the control */ + list_for_each_entry(swidget, &sdev->widget_list, list) { + if (swidget->comp_id == scontrol->comp_id) { + widget_found = true; + break; + } + } + + if (!widget_found) { + dev_err(scomp->dev, "Failed to find widget for kcontrol %s\n", scontrol->name); + return -ENOENT; + } + + /* + * Volatile controls should always be part of static pipelines and the widget use_count + * would always be > 0 in this case. For the others, just return the cached value if the + * widget is not set up. + */ + if (!swidget->use_count) + return 0; + + msg->primary &= ~SOF_IPC4_MOD_INSTANCE_MASK; + msg->primary |= SOF_IPC4_MOD_INSTANCE(swidget->instance_id); + + return iops->set_get_data(sdev, msg, msg->data_size, set); +} + +static int +sof_ipc4_set_volume_data(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget, + struct snd_sof_control *scontrol) +{ + struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data; + struct sof_ipc4_gain *gain = swidget->private; + struct sof_ipc4_msg *msg = &cdata->msg; + struct sof_ipc4_gain_data data; + bool all_channels_equal = true; + u32 value; + int ret, i; + + /* check if all channel values are equal */ + value = cdata->chanv[0].value; + for (i = 1; i < scontrol->num_channels; i++) { + if (cdata->chanv[i].value != value) { + all_channels_equal = false; + break; + } + } + + /* + * notify DSP with a single IPC message if all channel values are equal. Otherwise send + * a separate IPC for each channel. + */ + for (i = 0; i < scontrol->num_channels; i++) { + if (all_channels_equal) { + data.channels = SOF_IPC4_GAIN_ALL_CHANNELS_MASK; + data.init_val = cdata->chanv[0].value; + } else { + data.channels = cdata->chanv[i].channel; + data.init_val = cdata->chanv[i].value; + } + + /* set curve type and duration from topology */ + data.curve_duration = gain->data.curve_duration; + data.curve_type = gain->data.curve_type; + + msg->data_ptr = &data; + msg->data_size = sizeof(data); + + ret = sof_ipc4_set_get_kcontrol_data(scontrol, true); + msg->data_ptr = NULL; + msg->data_size = 0; + if (ret < 0) { + dev_err(sdev->dev, "Failed to set volume update for %s\n", + scontrol->name); + return ret; + } + + if (all_channels_equal) + break; + } + + return 0; +} + +static bool sof_ipc4_volume_put(struct snd_sof_control *scontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data; + struct snd_soc_component *scomp = scontrol->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + unsigned int channels = scontrol->num_channels; + struct snd_sof_widget *swidget; + bool widget_found = false; + bool change = false; + unsigned int i; + int ret; + + /* update each channel */ + for (i = 0; i < channels; i++) { + u32 value = mixer_to_ipc(ucontrol->value.integer.value[i], + scontrol->volume_table, scontrol->max + 1); + + change = change || (value != cdata->chanv[i].value); + cdata->chanv[i].channel = i; + cdata->chanv[i].value = value; + } + + if (!pm_runtime_active(scomp->dev)) + return change; + + /* find widget associated with the control */ + list_for_each_entry(swidget, &sdev->widget_list, list) { + if (swidget->comp_id == scontrol->comp_id) { + widget_found = true; + break; + } + } + + if (!widget_found) { + dev_err(scomp->dev, "Failed to find widget for kcontrol %s\n", scontrol->name); + return false; + } + + ret = sof_ipc4_set_volume_data(sdev, swidget, scontrol); + if (ret < 0) + return false; + + return change; +} + +static int sof_ipc4_volume_get(struct snd_sof_control *scontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data; + unsigned int channels = scontrol->num_channels; + unsigned int i; + + for (i = 0; i < channels; i++) + ucontrol->value.integer.value[i] = ipc_to_mixer(cdata->chanv[i].value, + scontrol->volume_table, + scontrol->max + 1); + + return 0; +} + +/* set up all controls for the widget */ +static int sof_ipc4_widget_kcontrol_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) +{ + struct snd_sof_control *scontrol; + int ret; + + list_for_each_entry(scontrol, &sdev->kcontrol_list, list) + if (scontrol->comp_id == swidget->comp_id) { + ret = sof_ipc4_set_volume_data(sdev, swidget, scontrol); + if (ret < 0) { + dev_err(sdev->dev, "%s: kcontrol %d set up failed for widget %s\n", + __func__, scontrol->comp_id, swidget->widget->name); + return ret; + } + } + + return 0; +} + +static int +sof_ipc4_set_up_volume_table(struct snd_sof_control *scontrol, int tlv[SOF_TLV_ITEMS], int size) +{ + int i; + + /* init the volume table */ + scontrol->volume_table = kcalloc(size, sizeof(u32), GFP_KERNEL); + if (!scontrol->volume_table) + return -ENOMEM; + + /* populate the volume table */ + for (i = 0; i < size ; i++) { + u32 val = vol_compute_gain(i, tlv); + u64 q31val = ((u64)val) << 15; /* Can be over Q1.31, need to saturate */ + + scontrol->volume_table[i] = q31val > SOF_IPC4_VOL_ZERO_DB ? + SOF_IPC4_VOL_ZERO_DB : q31val; + } + + return 0; +} + +const struct sof_ipc_tplg_control_ops tplg_ipc4_control_ops = { + .volume_put = sof_ipc4_volume_put, + .volume_get = sof_ipc4_volume_get, + .widget_kcontrol_setup = sof_ipc4_widget_kcontrol_setup, + .set_up_volume_table = sof_ipc4_set_up_volume_table, +}; diff --git a/sound/soc/sof/ipc4-loader.c b/sound/soc/sof/ipc4-loader.c new file mode 100644 index 000000000000..e635ae515fa9 --- /dev/null +++ b/sound/soc/sof/ipc4-loader.c @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. + +#include <linux/firmware.h> +#include <sound/sof/ext_manifest4.h> +#include <sound/sof/ipc4/header.h> +#include <trace/events/sof.h> +#include "ipc4-priv.h" +#include "sof-audio.h" +#include "sof-priv.h" +#include "ops.h" + +static size_t sof_ipc4_fw_parse_ext_man(struct snd_sof_dev *sdev) +{ + struct sof_ipc4_fw_data *ipc4_data = sdev->private; + struct snd_sof_pdata *plat_data = sdev->pdata; + struct sof_man4_fw_binary_header *fw_header; + const struct firmware *fw = plat_data->fw; + struct sof_ext_manifest4_hdr *ext_man_hdr; + struct sof_man4_module_config *fm_config; + struct sof_ipc4_fw_module *fw_module; + struct sof_man4_module *fm_entry; + ssize_t remaining; + u32 fw_hdr_offset; + int i; + + if (!ipc4_data) { + dev_err(sdev->dev, "%s: ipc4_data is not available\n", __func__); + return -EINVAL; + } + + remaining = fw->size; + if (remaining <= sizeof(*ext_man_hdr)) { + dev_err(sdev->dev, "Firmware size is too small: %zu\n", remaining); + return -EINVAL; + } + + ext_man_hdr = (struct sof_ext_manifest4_hdr *)fw->data; + + /* + * At the start of the firmware image we must have an extended manifest. + * Verify that the magic number is correct. + */ + if (ext_man_hdr->id != SOF_EXT_MAN4_MAGIC_NUMBER) { + dev_err(sdev->dev, + "Unexpected extended manifest magic number: %#x\n", + ext_man_hdr->id); + return -EINVAL; + } + + fw_hdr_offset = ipc4_data->manifest_fw_hdr_offset; + if (!fw_hdr_offset) + return -EINVAL; + + if (remaining <= ext_man_hdr->len + fw_hdr_offset + sizeof(*fw_header)) { + dev_err(sdev->dev, "Invalid firmware size %zu, should be at least %zu\n", + remaining, ext_man_hdr->len + fw_hdr_offset + sizeof(*fw_header)); + return -EINVAL; + } + + fw_header = (struct sof_man4_fw_binary_header *) + (fw->data + ext_man_hdr->len + fw_hdr_offset); + remaining -= (ext_man_hdr->len + fw_hdr_offset); + + if (remaining <= fw_header->len) { + dev_err(sdev->dev, "Invalid fw_header->len %u\n", fw_header->len); + return -EINVAL; + } + + dev_info(sdev->dev, "Loaded firmware version: %u.%u.%u.%u\n", + fw_header->major_version, fw_header->minor_version, + fw_header->hotfix_version, fw_header->build_version); + dev_dbg(sdev->dev, "Firmware name: %s, header length: %u, module count: %u\n", + fw_header->name, fw_header->len, fw_header->num_module_entries); + + ipc4_data->fw_modules = devm_kmalloc_array(sdev->dev, + fw_header->num_module_entries, + sizeof(*fw_module), GFP_KERNEL); + if (!ipc4_data->fw_modules) + return -ENOMEM; + + ipc4_data->num_fw_modules = fw_header->num_module_entries; + fw_module = ipc4_data->fw_modules; + + fm_entry = (struct sof_man4_module *)((u8 *)fw_header + fw_header->len); + remaining -= fw_header->len; + + if (remaining < fw_header->num_module_entries * sizeof(*fm_entry)) { + dev_err(sdev->dev, "Invalid num_module_entries %u\n", + fw_header->num_module_entries); + return -EINVAL; + } + + fm_config = (struct sof_man4_module_config *) + (fm_entry + fw_header->num_module_entries); + remaining -= (fw_header->num_module_entries * sizeof(*fm_entry)); + for (i = 0; i < fw_header->num_module_entries; i++) { + memcpy(&fw_module->man4_module_entry, fm_entry, sizeof(*fm_entry)); + + if (fm_entry->cfg_count) { + if (remaining < (fm_entry->cfg_offset + fm_entry->cfg_count) * + sizeof(*fm_config)) { + dev_err(sdev->dev, "Invalid module cfg_offset %u\n", + fm_entry->cfg_offset); + return -EINVAL; + } + + /* a module's config is always the same size */ + fw_module->bss_size = fm_config[fm_entry->cfg_offset].is_bytes; + + dev_dbg(sdev->dev, + "module %s: UUID %pUL cfg_count: %u, bss_size: %#x\n", + fm_entry->name, &fm_entry->uuid, fm_entry->cfg_count, + fw_module->bss_size); + } else { + fw_module->bss_size = 0; + + dev_dbg(sdev->dev, "module %s: UUID %pUL\n", fm_entry->name, + &fm_entry->uuid); + } + + fw_module->man4_module_entry.id = i; + ida_init(&fw_module->m_ida); + fw_module->private = NULL; + + fw_module++; + fm_entry++; + } + + return ext_man_hdr->len; +} + +static int sof_ipc4_validate_firmware(struct snd_sof_dev *sdev) +{ + struct sof_ipc4_fw_data *ipc4_data = sdev->private; + u32 fw_hdr_offset = ipc4_data->manifest_fw_hdr_offset; + struct snd_sof_pdata *plat_data = sdev->pdata; + struct sof_man4_fw_binary_header *fw_header; + const struct firmware *fw = plat_data->fw; + struct sof_ext_manifest4_hdr *ext_man_hdr; + + ext_man_hdr = (struct sof_ext_manifest4_hdr *)fw->data; + fw_header = (struct sof_man4_fw_binary_header *) + (fw->data + ext_man_hdr->len + fw_hdr_offset); + + /* TODO: Add firmware verification code here */ + + dev_dbg(sdev->dev, "Validated firmware version: %u.%u.%u.%u\n", + fw_header->major_version, fw_header->minor_version, + fw_header->hotfix_version, fw_header->build_version); + + return 0; +} + +static int sof_ipc4_query_fw_configuration(struct snd_sof_dev *sdev) +{ + struct sof_ipc4_fw_data *ipc4_data = sdev->private; + const struct sof_ipc_ops *iops = sdev->ipc->ops; + struct sof_ipc4_fw_version *fw_ver; + struct sof_ipc4_tuple *tuple; + struct sof_ipc4_msg msg; + size_t offset = 0; + int ret; + + /* Get the firmware configuration */ + msg.primary = SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG); + msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST); + msg.primary |= SOF_IPC4_MOD_ID(SOF_IPC4_MOD_INIT_BASEFW_MOD_ID); + msg.primary |= SOF_IPC4_MOD_INSTANCE(SOF_IPC4_MOD_INIT_BASEFW_INSTANCE_ID); + msg.extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(SOF_IPC4_FW_PARAM_FW_CONFIG); + + msg.data_size = sdev->ipc->max_payload_size; + msg.data_ptr = kzalloc(msg.data_size, GFP_KERNEL); + if (!msg.data_ptr) + return -ENOMEM; + + ret = iops->set_get_data(sdev, &msg, msg.data_size, false); + if (ret) + goto out; + + while (offset < msg.data_size) { + tuple = (struct sof_ipc4_tuple *)((u8 *)msg.data_ptr + offset); + + switch (tuple->type) { + case SOF_IPC4_FW_CFG_FW_VERSION: + fw_ver = (struct sof_ipc4_fw_version *)tuple->value; + + dev_info(sdev->dev, + "Booted firmware version: %u.%u.%u.%u\n", + fw_ver->major, fw_ver->minor, fw_ver->hotfix, + fw_ver->build); + break; + case SOF_IPC4_FW_CFG_DL_MAILBOX_BYTES: + trace_sof_ipc4_fw_config(sdev, "DL mailbox size", *tuple->value); + break; + case SOF_IPC4_FW_CFG_UL_MAILBOX_BYTES: + trace_sof_ipc4_fw_config(sdev, "UL mailbox size", *tuple->value); + break; + case SOF_IPC4_FW_CFG_TRACE_LOG_BYTES: + trace_sof_ipc4_fw_config(sdev, "Trace log size", *tuple->value); + ipc4_data->mtrace_log_bytes = *tuple->value; + break; + default: + break; + } + + offset += sizeof(*tuple) + tuple->size; + } + +out: + kfree(msg.data_ptr); + + return ret; +} + +const struct sof_ipc_fw_loader_ops ipc4_loader_ops = { + .validate = sof_ipc4_validate_firmware, + .parse_ext_manifest = sof_ipc4_fw_parse_ext_man, + .query_fw_configuration = sof_ipc4_query_fw_configuration, +}; diff --git a/sound/soc/sof/ipc4-mtrace.c b/sound/soc/sof/ipc4-mtrace.c new file mode 100644 index 000000000000..70dea8ae706e --- /dev/null +++ b/sound/soc/sof/ipc4-mtrace.c @@ -0,0 +1,659 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. + +#include <linux/debugfs.h> +#include <linux/sched/signal.h> +#include <sound/sof/ipc4/header.h> +#include "sof-priv.h" +#include "ipc4-priv.h" + +/* + * debug info window is organized in 16 (equal sized) pages: + * + * ------------------------ + * | Page0 - descriptors | + * ------------------------ + * | Page1 - slot0 | + * ------------------------ + * | Page2 - slot1 | + * ------------------------ + * | ... | + * ------------------------ + * | Page14 - slot13 | + * ------------------------ + * | Page15 - slot14 | + * ------------------------ + * + * The slot size == page size + * + * The first page contains descriptors for the remaining 15 cores + * The slot descriptor is: + * u32 res_id; + * u32 type; + * u32 vma; + * + * Log buffer slots have the following layout: + * u32 host_read_ptr; + * u32 dsp_write_ptr; + * u8 buffer[]; + * + * The two pointers are offsets within the buffer. + */ + +#define SOF_MTRACE_DESCRIPTOR_SIZE 12 /* 3 x u32 */ + +#define FW_EPOCH_DELTA 11644473600LL + +#define INVALID_SLOT_OFFSET 0xffffffff +#define MAX_ALLOWED_LIBRARIES 16 +#define MAX_MTRACE_SLOTS 15 + +#define SOF_MTRACE_PAGE_SIZE 0x1000 +#define SOF_MTRACE_SLOT_SIZE SOF_MTRACE_PAGE_SIZE + +/* debug log slot types */ +#define SOF_MTRACE_SLOT_UNUSED 0x00000000 +#define SOF_MTRACE_SLOT_CRITICAL_LOG 0x54524300 /* byte 0: core ID */ +#define SOF_MTRACE_SLOT_DEBUG_LOG 0x474f4c00 /* byte 0: core ID */ +#define SOF_MTRACE_SLOT_GDB_STUB 0x42444700 +#define SOF_MTRACE_SLOT_TELEMETRY 0x4c455400 +#define SOF_MTRACE_SLOT_BROKEN 0x44414544 + /* for debug and critical types */ +#define SOF_MTRACE_SLOT_CORE_MASK GENMASK(7, 0) +#define SOF_MTRACE_SLOT_TYPE_MASK GENMASK(31, 8) + +#define DEFAULT_AGING_TIMER_PERIOD_MS 0x100 +#define DEFAULT_FIFO_FULL_TIMER_PERIOD_MS 0x1000 + +/* ipc4 log level and source definitions for logs_priorities_mask */ +#define SOF_MTRACE_LOG_LEVEL_CRITICAL BIT(0) +#define SOF_MTRACE_LOG_LEVEL_ERROR BIT(1) +#define SOF_MTRACE_LOG_LEVEL_WARNING BIT(2) +#define SOF_MTRACE_LOG_LEVEL_INFO BIT(3) +#define SOF_MTRACE_LOG_LEVEL_VERBOSE BIT(4) +#define SOF_MTRACE_LOG_SOURCE_INFRA BIT(5) /* log source 0 */ +#define SOF_MTRACE_LOG_SOURCE_HAL BIT(6) +#define SOF_MTRACE_LOG_SOURCE_MODULE BIT(7) +#define SOF_MTRACE_LOG_SOURCE_AUDIO BIT(8) +#define SOF_MTRACE_LOG_SOURCE_SCHEDULER BIT(9) +#define SOF_MTRACE_LOG_SOURCE_ULP_INFRA BIT(10) +#define SOF_MTRACE_LOG_SOURCE_ULP_MODULE BIT(11) +#define SOF_MTRACE_LOG_SOURCE_VISION BIT(12) /* log source 7 */ +#define DEFAULT_LOGS_PRIORITIES_MASK (SOF_MTRACE_LOG_LEVEL_CRITICAL | \ + SOF_MTRACE_LOG_LEVEL_ERROR | \ + SOF_MTRACE_LOG_LEVEL_WARNING | \ + SOF_MTRACE_LOG_LEVEL_INFO | \ + SOF_MTRACE_LOG_SOURCE_INFRA | \ + SOF_MTRACE_LOG_SOURCE_HAL | \ + SOF_MTRACE_LOG_SOURCE_MODULE | \ + SOF_MTRACE_LOG_SOURCE_AUDIO) + +struct sof_log_state_info { + u32 aging_timer_period; + u32 fifo_full_timer_period; + u32 enable; + u32 logs_priorities_mask[MAX_ALLOWED_LIBRARIES]; +} __packed; + +enum sof_mtrace_state { + SOF_MTRACE_DISABLED, + SOF_MTRACE_INITIALIZING, + SOF_MTRACE_ENABLED, +}; + +struct sof_mtrace_core_data { + struct snd_sof_dev *sdev; + + int id; + u32 slot_offset; + void *log_buffer; + struct mutex buffer_lock; /* for log_buffer alloc/free */ + u32 host_read_ptr; + u32 dsp_write_ptr; + /* pos update IPC arrived before the slot offset is known, queried */ + bool delayed_pos_update; + wait_queue_head_t trace_sleep; +}; + +struct sof_mtrace_priv { + struct snd_sof_dev *sdev; + enum sof_mtrace_state mtrace_state; + struct sof_log_state_info state_info; + + struct sof_mtrace_core_data cores[]; +}; + +static int sof_ipc4_mtrace_dfs_open(struct inode *inode, struct file *file) +{ + struct sof_mtrace_core_data *core_data = inode->i_private; + int ret; + + mutex_lock(&core_data->buffer_lock); + + if (core_data->log_buffer) { + ret = -EBUSY; + goto out; + } + + ret = debugfs_file_get(file->f_path.dentry); + if (unlikely(ret)) + goto out; + + core_data->log_buffer = kmalloc(SOF_MTRACE_SLOT_SIZE, GFP_KERNEL); + if (!core_data->log_buffer) { + debugfs_file_put(file->f_path.dentry); + ret = -ENOMEM; + goto out; + } + + ret = simple_open(inode, file); + if (ret) { + kfree(core_data->log_buffer); + debugfs_file_put(file->f_path.dentry); + } + +out: + mutex_unlock(&core_data->buffer_lock); + + return ret; +} + +static bool sof_wait_mtrace_avail(struct sof_mtrace_core_data *core_data) +{ + wait_queue_entry_t wait; + + /* data immediately available */ + if (core_data->host_read_ptr != core_data->dsp_write_ptr) + return true; + + /* wait for available trace data from FW */ + init_waitqueue_entry(&wait, current); + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&core_data->trace_sleep, &wait); + + if (!signal_pending(current)) { + /* set timeout to max value, no error code */ + schedule_timeout(MAX_SCHEDULE_TIMEOUT); + } + remove_wait_queue(&core_data->trace_sleep, &wait); + + if (core_data->host_read_ptr != core_data->dsp_write_ptr) + return true; + + return false; +} + +static ssize_t sof_ipc4_mtrace_dfs_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct sof_mtrace_core_data *core_data = file->private_data; + u32 log_buffer_offset, log_buffer_size, read_ptr, write_ptr; + struct snd_sof_dev *sdev = core_data->sdev; + struct sof_mtrace_priv *priv = sdev->fw_trace_data; + void *log_buffer = core_data->log_buffer; + loff_t lpos = *ppos; + u32 avail; + int ret; + + /* check pos and count */ + if (lpos < 0) + return -EINVAL; + if (!count || count < sizeof(avail)) + return 0; + + /* get available count based on current host offset */ + if (!sof_wait_mtrace_avail(core_data)) { + /* No data available */ + avail = 0; + if (copy_to_user(buffer, &avail, sizeof(avail))) + return -EFAULT; + + return 0; + } + + if (core_data->slot_offset == INVALID_SLOT_OFFSET) + return 0; + + /* The log data buffer starts after the two pointer in the slot */ + log_buffer_offset = core_data->slot_offset + (sizeof(u32) * 2); + /* The log data size excludes the pointers */ + log_buffer_size = SOF_MTRACE_SLOT_SIZE - (sizeof(u32) * 2); + + read_ptr = core_data->host_read_ptr; + write_ptr = core_data->dsp_write_ptr; + + if (read_ptr < write_ptr) + avail = write_ptr - read_ptr; + else + avail = log_buffer_size - read_ptr + write_ptr; + + if (!avail) + return 0; + + if (avail > log_buffer_size) + avail = log_buffer_size; + + /* Need space for the initial u32 of the avail */ + if (avail > count - sizeof(avail)) + avail = count - sizeof(avail); + + if (sof_debug_check_flag(SOF_DBG_PRINT_DMA_POSITION_UPDATE_LOGS)) + dev_dbg(sdev->dev, + "core%d, host read: %#x, dsp write: %#x, avail: %#x\n", + core_data->id, read_ptr, write_ptr, avail); + + if (read_ptr < write_ptr) { + /* Read data between read pointer and write pointer */ + sof_mailbox_read(sdev, log_buffer_offset + read_ptr, log_buffer, avail); + } else { + /* read from read pointer to end of the slot */ + sof_mailbox_read(sdev, log_buffer_offset + read_ptr, log_buffer, + avail - write_ptr); + /* read from slot start to write pointer */ + if (write_ptr) + sof_mailbox_read(sdev, log_buffer_offset, + (u8 *)(log_buffer) + avail - write_ptr, + write_ptr); + } + + /* first write the number of bytes we have gathered */ + ret = copy_to_user(buffer, &avail, sizeof(avail)); + if (ret) + return -EFAULT; + + /* Followed by the data itself */ + ret = copy_to_user(buffer + sizeof(avail), log_buffer, avail); + if (ret) + return -EFAULT; + + /* Update the host_read_ptr in the slot for this core */ + read_ptr += avail; + if (read_ptr >= log_buffer_size) + read_ptr -= log_buffer_size; + sof_mailbox_write(sdev, core_data->slot_offset, &read_ptr, sizeof(read_ptr)); + + /* Only update the host_read_ptr if mtrace is enabled */ + if (priv->mtrace_state != SOF_MTRACE_DISABLED) + core_data->host_read_ptr = read_ptr; + + /* + * Ask for a new buffer from user space for the next chunk, not + * streaming due to the heading number of bytes value. + */ + *ppos += count; + + return count; +} + +static int sof_ipc4_mtrace_dfs_release(struct inode *inode, struct file *file) +{ + struct sof_mtrace_core_data *core_data = inode->i_private; + + debugfs_file_put(file->f_path.dentry); + + mutex_lock(&core_data->buffer_lock); + kfree(core_data->log_buffer); + core_data->log_buffer = NULL; + mutex_unlock(&core_data->buffer_lock); + + return 0; +} + +static const struct file_operations sof_dfs_mtrace_fops = { + .open = sof_ipc4_mtrace_dfs_open, + .read = sof_ipc4_mtrace_dfs_read, + .llseek = default_llseek, + .release = sof_ipc4_mtrace_dfs_release, + + .owner = THIS_MODULE, +}; + +static ssize_t sof_ipc4_priority_mask_dfs_read(struct file *file, char __user *to, + size_t count, loff_t *ppos) +{ + struct sof_mtrace_priv *priv = file->private_data; + int i, ret, offset, remaining; + char *buf; + + /* + * one entry (14 char + new line = 15): + * " 0: 000001ef" + * + * 16 * 15 + 1 = 241 + */ + buf = kzalloc(241, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (i = 0; i < MAX_ALLOWED_LIBRARIES; i++) { + offset = strlen(buf); + remaining = 241 - offset; + snprintf(buf + offset, remaining, "%2d: 0x%08x\n", i, + priv->state_info.logs_priorities_mask[i]); + } + + ret = simple_read_from_buffer(to, count, ppos, buf, strlen(buf)); + + kfree(buf); + return ret; +} + +static ssize_t sof_ipc4_priority_mask_dfs_write(struct file *file, + const char __user *from, + size_t count, loff_t *ppos) +{ + struct sof_mtrace_priv *priv = file->private_data; + int id, ret; + char *buf; + u32 mask; + + /* + * To update Nth mask entry, write: + * "N,0x1234" or "N,1234" to the debugfs file + * The mask will be interpreted as hexadecimal number + */ + buf = memdup_user_nul(from, count); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + ret = sscanf(buf, "%d,0x%x", &id, &mask); + if (ret != 2) { + ret = sscanf(buf, "%d,%x", &id, &mask); + if (ret != 2) { + ret = -EINVAL; + goto out; + } + } + + if (id >= MAX_ALLOWED_LIBRARIES) { + ret = -EINVAL; + goto out; + } + + priv->state_info.logs_priorities_mask[id] = mask; + ret = count; + +out: + kfree(buf); + return ret; +} + +static const struct file_operations sof_dfs_priority_mask_fops = { + .open = simple_open, + .read = sof_ipc4_priority_mask_dfs_read, + .write = sof_ipc4_priority_mask_dfs_write, + .llseek = default_llseek, + + .owner = THIS_MODULE, +}; + +static int mtrace_debugfs_create(struct snd_sof_dev *sdev) +{ + struct sof_mtrace_priv *priv = sdev->fw_trace_data; + struct dentry *dfs_root; + char dfs_name[100]; + int i; + + dfs_root = debugfs_create_dir("mtrace", sdev->debugfs_root); + if (IS_ERR_OR_NULL(dfs_root)) + return 0; + + /* Create files for the logging parameters */ + debugfs_create_u32("aging_timer_period", 0644, dfs_root, + &priv->state_info.aging_timer_period); + debugfs_create_u32("fifo_full_timer_period", 0644, dfs_root, + &priv->state_info.fifo_full_timer_period); + debugfs_create_file("logs_priorities_mask", 0644, dfs_root, priv, + &sof_dfs_priority_mask_fops); + + /* Separate log files per core */ + for (i = 0; i < sdev->num_cores; i++) { + snprintf(dfs_name, sizeof(dfs_name), "core%d", i); + debugfs_create_file(dfs_name, 0444, dfs_root, &priv->cores[i], + &sof_dfs_mtrace_fops); + } + + return 0; +} + +static int ipc4_mtrace_enable(struct snd_sof_dev *sdev) +{ + struct sof_mtrace_priv *priv = sdev->fw_trace_data; + const struct sof_ipc_ops *iops = sdev->ipc->ops; + struct sof_ipc4_msg msg; + u64 system_time; + ktime_t kt; + int ret; + + if (priv->mtrace_state != SOF_MTRACE_DISABLED) + return 0; + + msg.primary = SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG); + msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST); + msg.primary |= SOF_IPC4_MOD_ID(SOF_IPC4_MOD_INIT_BASEFW_MOD_ID); + msg.primary |= SOF_IPC4_MOD_INSTANCE(SOF_IPC4_MOD_INIT_BASEFW_INSTANCE_ID); + msg.extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(SOF_IPC4_FW_PARAM_SYSTEM_TIME); + + /* The system time is in usec, UTC, epoch is 1601-01-01 00:00:00 */ + kt = ktime_add_us(ktime_get_real(), FW_EPOCH_DELTA * USEC_PER_SEC); + system_time = ktime_to_us(kt); + msg.data_size = sizeof(system_time); + msg.data_ptr = &system_time; + ret = iops->set_get_data(sdev, &msg, msg.data_size, true); + if (ret) + return ret; + + msg.extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(SOF_IPC4_FW_PARAM_ENABLE_LOGS); + + priv->state_info.enable = 1; + + msg.data_size = sizeof(priv->state_info); + msg.data_ptr = &priv->state_info; + + priv->mtrace_state = SOF_MTRACE_INITIALIZING; + ret = iops->set_get_data(sdev, &msg, msg.data_size, true); + if (ret) { + priv->mtrace_state = SOF_MTRACE_DISABLED; + return ret; + } + + priv->mtrace_state = SOF_MTRACE_ENABLED; + + return 0; +} + +static void ipc4_mtrace_disable(struct snd_sof_dev *sdev) +{ + struct sof_mtrace_priv *priv = sdev->fw_trace_data; + const struct sof_ipc_ops *iops = sdev->ipc->ops; + struct sof_ipc4_msg msg; + int i; + + if (priv->mtrace_state == SOF_MTRACE_DISABLED) + return; + + msg.primary = SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG); + msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST); + msg.primary |= SOF_IPC4_MOD_ID(SOF_IPC4_MOD_INIT_BASEFW_MOD_ID); + msg.primary |= SOF_IPC4_MOD_INSTANCE(SOF_IPC4_MOD_INIT_BASEFW_INSTANCE_ID); + msg.extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(SOF_IPC4_FW_PARAM_ENABLE_LOGS); + + priv->state_info.enable = 0; + + msg.data_size = sizeof(priv->state_info); + msg.data_ptr = &priv->state_info; + iops->set_get_data(sdev, &msg, msg.data_size, true); + + priv->mtrace_state = SOF_MTRACE_DISABLED; + + for (i = 0; i < sdev->num_cores; i++) { + struct sof_mtrace_core_data *core_data = &priv->cores[i]; + + core_data->host_read_ptr = 0; + core_data->dsp_write_ptr = 0; + wake_up(&core_data->trace_sleep); + } +} + +/* + * Each DSP core logs to a dedicated slot. + * Parse the slot descriptors at debug_box offset to find the debug log slots + * and map them to cores. + * There are 15 slots and therefore 15 descriptors to check (MAX_MTRACE_SLOTS) + */ +static void sof_mtrace_find_core_slots(struct snd_sof_dev *sdev) +{ + struct sof_mtrace_priv *priv = sdev->fw_trace_data; + struct sof_mtrace_core_data *core_data; + u32 slot_desc_type_offset, type, core; + int i; + + for (i = 0; i < MAX_MTRACE_SLOTS; i++) { + /* The type is the second u32 in the slot descriptor */ + slot_desc_type_offset = sdev->debug_box.offset; + slot_desc_type_offset += SOF_MTRACE_DESCRIPTOR_SIZE * i + sizeof(u32); + sof_mailbox_read(sdev, slot_desc_type_offset, &type, sizeof(type)); + + if ((type & SOF_MTRACE_SLOT_TYPE_MASK) == SOF_MTRACE_SLOT_DEBUG_LOG) { + core = type & SOF_MTRACE_SLOT_CORE_MASK; + + if (core >= sdev->num_cores) { + dev_dbg(sdev->dev, "core%u is invalid for slot%d\n", + core, i); + continue; + } + + core_data = &priv->cores[core]; + /* + * The area reserved for descriptors have the same size + * as a slot. + * In other words: slot0 starts at + * debug_box + SOF_MTRACE_SLOT_SIZE offset + */ + core_data->slot_offset = sdev->debug_box.offset; + core_data->slot_offset += SOF_MTRACE_SLOT_SIZE * (i + 1); + dev_dbg(sdev->dev, "slot%d is used for core%u\n", i, core); + if (core_data->delayed_pos_update) { + sof_ipc4_mtrace_update_pos(sdev, core); + core_data->delayed_pos_update = false; + } + } else if (type) { + dev_dbg(sdev->dev, "slot%d is not a log slot (%#x)\n", i, type); + } + } +} + +static int ipc4_mtrace_init(struct snd_sof_dev *sdev) +{ + struct sof_ipc4_fw_data *ipc4_data = sdev->private; + struct sof_mtrace_priv *priv; + int i, ret; + + if (sdev->fw_trace_data) { + dev_err(sdev->dev, "fw_trace_data has been already allocated\n"); + return -EBUSY; + } + + if (!ipc4_data->mtrace_log_bytes || + ipc4_data->mtrace_type != SOF_IPC4_MTRACE_INTEL_CAVS_2) { + sdev->fw_trace_is_supported = false; + return 0; + } + + priv = devm_kzalloc(sdev->dev, struct_size(priv, cores, sdev->num_cores), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + sdev->fw_trace_data = priv; + + /* Set initial values for mtrace parameters */ + priv->state_info.aging_timer_period = DEFAULT_AGING_TIMER_PERIOD_MS; + priv->state_info.fifo_full_timer_period = DEFAULT_FIFO_FULL_TIMER_PERIOD_MS; + /* Only enable basefw logs initially (index 0 is always basefw) */ + priv->state_info.logs_priorities_mask[0] = DEFAULT_LOGS_PRIORITIES_MASK; + + for (i = 0; i < sdev->num_cores; i++) { + struct sof_mtrace_core_data *core_data = &priv->cores[i]; + + init_waitqueue_head(&core_data->trace_sleep); + mutex_init(&core_data->buffer_lock); + core_data->sdev = sdev; + core_data->id = i; + } + + ret = ipc4_mtrace_enable(sdev); + if (ret) { + /* + * Mark firmware tracing as not supported and return 0 to not + * block the whole audio stack + */ + sdev->fw_trace_is_supported = false; + dev_dbg(sdev->dev, "initialization failed, fw tracing is disabled\n"); + return 0; + } + + sof_mtrace_find_core_slots(sdev); + + ret = mtrace_debugfs_create(sdev); + if (ret) + ipc4_mtrace_disable(sdev); + + return ret; +} + +static void ipc4_mtrace_free(struct snd_sof_dev *sdev) +{ + ipc4_mtrace_disable(sdev); +} + +int sof_ipc4_mtrace_update_pos(struct snd_sof_dev *sdev, int core) +{ + struct sof_mtrace_priv *priv = sdev->fw_trace_data; + struct sof_mtrace_core_data *core_data; + + if (!sdev->fw_trace_is_supported || + priv->mtrace_state == SOF_MTRACE_DISABLED) + return 0; + + if (core >= sdev->num_cores) + return -EINVAL; + + core_data = &priv->cores[core]; + + if (core_data->slot_offset == INVALID_SLOT_OFFSET) { + core_data->delayed_pos_update = true; + return 0; + } + + /* Read out the dsp_write_ptr from the slot for this core */ + sof_mailbox_read(sdev, core_data->slot_offset + sizeof(u32), + &core_data->dsp_write_ptr, 4); + core_data->dsp_write_ptr -= core_data->dsp_write_ptr % 4; + + if (sof_debug_check_flag(SOF_DBG_PRINT_DMA_POSITION_UPDATE_LOGS)) + dev_dbg(sdev->dev, "core%d, host read: %#x, dsp write: %#x", + core, core_data->host_read_ptr, core_data->dsp_write_ptr); + + wake_up(&core_data->trace_sleep); + + return 0; +} + +static int ipc4_mtrace_resume(struct snd_sof_dev *sdev) +{ + return ipc4_mtrace_enable(sdev); +} + +static void ipc4_mtrace_suspend(struct snd_sof_dev *sdev, pm_message_t pm_state) +{ + ipc4_mtrace_disable(sdev); +} + +const struct sof_ipc_fw_tracing_ops ipc4_mtrace_ops = { + .init = ipc4_mtrace_init, + .free = ipc4_mtrace_free, + .suspend = ipc4_mtrace_suspend, + .resume = ipc4_mtrace_resume, +}; diff --git a/sound/soc/sof/ipc4-pcm.c b/sound/soc/sof/ipc4-pcm.c new file mode 100644 index 000000000000..732872395980 --- /dev/null +++ b/sound/soc/sof/ipc4-pcm.c @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// + +#include <sound/pcm_params.h> +#include <sound/sof/ipc4/header.h> +#include "sof-audio.h" +#include "sof-priv.h" +#include "ipc4-priv.h" +#include "ipc4-topology.h" + +int sof_ipc4_set_pipeline_state(struct snd_sof_dev *sdev, u32 id, u32 state) +{ + struct sof_ipc4_msg msg = {{ 0 }}; + u32 primary; + + dev_dbg(sdev->dev, "ipc4 set pipeline %d state %d", id, state); + + primary = state; + primary |= SOF_IPC4_GLB_PIPE_STATE_ID(id); + primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_SET_PIPELINE_STATE); + primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST); + primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG); + + msg.primary = primary; + + return sof_ipc_tx_message(sdev->ipc, &msg, 0, NULL, 0); +} +EXPORT_SYMBOL(sof_ipc4_set_pipeline_state); + +static int sof_ipc4_trigger_pipelines(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int state) +{ + 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_widget *pipeline_widget; + struct snd_soc_dapm_widget_list *list; + struct snd_soc_dapm_widget *widget; + struct sof_ipc4_pipeline *pipeline; + struct snd_sof_widget *swidget; + struct snd_sof_pcm *spcm; + int ret = 0; + int num_widgets; + + spcm = snd_sof_find_spcm_dai(component, rtd); + if (!spcm) + return -EINVAL; + + list = spcm->stream[substream->stream].list; + + for_each_dapm_widgets(list, num_widgets, widget) { + swidget = widget->dobj.private; + + if (!swidget) + continue; + + /* + * set pipeline state for both FE and BE pipelines for RUNNING state. + * For PAUSE/RESET, set the pipeline state only for the FE pipeline. + */ + switch (state) { + case SOF_IPC4_PIPE_PAUSED: + case SOF_IPC4_PIPE_RESET: + if (!WIDGET_IS_AIF(swidget->id)) + continue; + break; + default: + break; + } + + /* find pipeline widget for the pipeline that this widget belongs to */ + pipeline_widget = swidget->pipe_widget; + pipeline = (struct sof_ipc4_pipeline *)pipeline_widget->private; + + if (pipeline->state == state) + continue; + + /* first set the pipeline to PAUSED state */ + if (pipeline->state != SOF_IPC4_PIPE_PAUSED) { + ret = sof_ipc4_set_pipeline_state(sdev, swidget->pipeline_id, + SOF_IPC4_PIPE_PAUSED); + if (ret < 0) { + dev_err(sdev->dev, "failed to pause pipeline %d\n", + swidget->pipeline_id); + return ret; + } + } + + pipeline->state = SOF_IPC4_PIPE_PAUSED; + + if (pipeline->state == state) + continue; + + /* then set the final state */ + ret = sof_ipc4_set_pipeline_state(sdev, swidget->pipeline_id, state); + if (ret < 0) { + dev_err(sdev->dev, "failed to set state %d for pipeline %d\n", + state, swidget->pipeline_id); + break; + } + + pipeline->state = state; + } + + return ret; +} + +static int sof_ipc4_pcm_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int cmd) +{ + int state; + + /* determine the pipeline state */ + switch (cmd) { + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + state = SOF_IPC4_PIPE_PAUSED; + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_START: + state = SOF_IPC4_PIPE_RUNNING; + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + state = SOF_IPC4_PIPE_PAUSED; + break; + default: + dev_err(component->dev, "%s: unhandled trigger cmd %d\n", __func__, cmd); + return -EINVAL; + } + + /* set the pipeline state */ + return sof_ipc4_trigger_pipelines(component, substream, state); +} + +static int sof_ipc4_pcm_hw_free(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + return sof_ipc4_trigger_pipelines(component, substream, SOF_IPC4_PIPE_RESET); +} + +static void ipc4_ssp_dai_config_pcm_params_match(struct snd_sof_dev *sdev, const char *link_name, + struct snd_pcm_hw_params *params) +{ + struct snd_sof_dai_link *slink; + struct snd_sof_dai *dai; + bool dai_link_found = false; + int i; + + list_for_each_entry(slink, &sdev->dai_link_list, list) { + if (!strcmp(slink->link->name, link_name)) { + dai_link_found = true; + break; + } + } + + if (!dai_link_found) + return; + + for (i = 0; i < slink->num_hw_configs; i++) { + struct snd_soc_tplg_hw_config *hw_config = &slink->hw_configs[i]; + + if (params_rate(params) == le32_to_cpu(hw_config->fsync_rate)) { + /* set current config for all DAI's with matching name */ + list_for_each_entry(dai, &sdev->dai_list, list) + if (!strcmp(slink->link->name, dai->name)) + dai->current_config = le32_to_cpu(hw_config->id); + break; + } + } +} + +static int sof_ipc4_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME); + struct snd_sof_dai *dai = snd_sof_find_dai(component, rtd->dai_link->name); + struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + struct sof_ipc4_copier *ipc4_copier; + struct snd_soc_dpcm *dpcm; + + if (!dai) { + dev_err(component->dev, "%s: No DAI found with name %s\n", __func__, + rtd->dai_link->name); + return -EINVAL; + } + + ipc4_copier = dai->private; + if (!ipc4_copier) { + dev_err(component->dev, "%s: No private data found for DAI %s\n", + __func__, rtd->dai_link->name); + return -EINVAL; + } + + /* always set BE format to 32-bits for both playback and capture */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE); + + rate->min = ipc4_copier->available_fmt.base_config->audio_fmt.sampling_frequency; + rate->max = rate->min; + + /* + * Set trigger order for capture to SND_SOC_DPCM_TRIGGER_PRE. This is required + * to ensure that the BE DAI pipeline gets stopped/suspended before the FE DAI + * pipeline gets triggered and the pipeline widgets are freed. + */ + for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_CAPTURE, dpcm) { + struct snd_soc_pcm_runtime *fe = dpcm->fe; + + fe->dai_link->trigger[SNDRV_PCM_STREAM_CAPTURE] = SND_SOC_DPCM_TRIGGER_PRE; + } + + switch (ipc4_copier->dai_type) { + case SOF_DAI_INTEL_SSP: + ipc4_ssp_dai_config_pcm_params_match(sdev, (char *)rtd->dai_link->name, params); + break; + default: + break; + } + + return 0; +} + +const struct sof_ipc_pcm_ops ipc4_pcm_ops = { + .trigger = sof_ipc4_pcm_trigger, + .hw_free = sof_ipc4_pcm_hw_free, + .dai_link_fixup = sof_ipc4_pcm_dai_link_fixup, +}; diff --git a/sound/soc/sof/ipc4-priv.h b/sound/soc/sof/ipc4-priv.h new file mode 100644 index 000000000000..e3b8484a2f1f --- /dev/null +++ b/sound/soc/sof/ipc4-priv.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright(c) 2022 Intel Corporation. All rights reserved. + */ + +#ifndef __SOUND_SOC_SOF_IPC4_PRIV_H +#define __SOUND_SOC_SOF_IPC4_PRIV_H + +#include <linux/idr.h> +#include <sound/sof/ext_manifest4.h> +#include "sof-priv.h" + +/* The DSP window indices are fixed */ +#define SOF_IPC4_OUTBOX_WINDOW_IDX 1 +#define SOF_IPC4_DEBUG_WINDOW_IDX 2 + +enum sof_ipc4_mtrace_type { + SOF_IPC4_MTRACE_NOT_AVAILABLE = 0, + SOF_IPC4_MTRACE_INTEL_CAVS_1_5, + SOF_IPC4_MTRACE_INTEL_CAVS_1_8, + SOF_IPC4_MTRACE_INTEL_CAVS_2, +}; + +/** + * struct sof_ipc4_fw_data - IPC4-specific data + * @manifest_fw_hdr_offset: FW header offset in the manifest + * @num_fw_modules : Number of modules in base FW + * @fw_modules: Array of base FW modules + * @nhlt: NHLT table either from the BIOS or the topology manifest + * @mtrace_type: mtrace type supported on the booted platform + * @mtrace_log_bytes: log bytes as reported by the firmware via fw_config reply + */ +struct sof_ipc4_fw_data { + u32 manifest_fw_hdr_offset; + int num_fw_modules; + void *fw_modules; + void *nhlt; + enum sof_ipc4_mtrace_type mtrace_type; + u32 mtrace_log_bytes; +}; + +/** + * struct sof_ipc4_fw_module - IPC4 module info + * @sof_man4_module : Module info + * @m_ida: Module instance identifier + * @bss_size: Module object size + * @private: Module private data + */ +struct sof_ipc4_fw_module { + struct sof_man4_module man4_module_entry; + struct ida m_ida; + u32 bss_size; + void *private; +}; + +extern const struct sof_ipc_fw_loader_ops ipc4_loader_ops; +extern const struct sof_ipc_tplg_ops ipc4_tplg_ops; +extern const struct sof_ipc_tplg_control_ops tplg_ipc4_control_ops; +extern const struct sof_ipc_pcm_ops ipc4_pcm_ops; +extern const struct sof_ipc_fw_tracing_ops ipc4_mtrace_ops; + +int sof_ipc4_set_pipeline_state(struct snd_sof_dev *sdev, u32 id, u32 state); +int sof_ipc4_mtrace_update_pos(struct snd_sof_dev *sdev, int core); +#endif diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c new file mode 100644 index 000000000000..a81af5f73a4b --- /dev/null +++ b/sound/soc/sof/ipc4-topology.c @@ -0,0 +1,1927 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// +#include <uapi/sound/sof/tokens.h> +#include <sound/pcm_params.h> +#include <sound/sof/ext_manifest4.h> +#include <sound/intel-nhlt.h> +#include "sof-priv.h" +#include "sof-audio.h" +#include "ipc4-priv.h" +#include "ipc4-topology.h" +#include "ops.h" + +#define SOF_IPC4_GAIN_PARAM_ID 0 +#define SOF_IPC4_TPLG_ABI_SIZE 6 + +static DEFINE_IDA(alh_group_ida); + +static const struct sof_topology_token ipc4_sched_tokens[] = { + {SOF_TKN_SCHED_LP_MODE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc4_pipeline, lp_mode)} +}; + +static const struct sof_topology_token pipeline_tokens[] = { + {SOF_TKN_SCHED_DYNAMIC_PIPELINE, SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16, + offsetof(struct snd_sof_widget, dynamic_pipeline_widget)}, +}; + +static const struct sof_topology_token ipc4_comp_tokens[] = { + {SOF_TKN_COMP_CPC, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc4_base_module_cfg, cpc)}, + {SOF_TKN_COMP_IS_PAGES, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc4_base_module_cfg, is_pages)}, +}; + +static const struct sof_topology_token ipc4_audio_format_buffer_size_tokens[] = { + {SOF_TKN_CAVS_AUDIO_FORMAT_IBS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc4_base_module_cfg, ibs)}, + {SOF_TKN_CAVS_AUDIO_FORMAT_OBS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc4_base_module_cfg, obs)}, +}; + +static const struct sof_topology_token ipc4_in_audio_format_tokens[] = { + {SOF_TKN_CAVS_AUDIO_FORMAT_IN_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc4_audio_format, sampling_frequency)}, + {SOF_TKN_CAVS_AUDIO_FORMAT_IN_BIT_DEPTH, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc4_audio_format, bit_depth)}, + {SOF_TKN_CAVS_AUDIO_FORMAT_IN_CH_MAP, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc4_audio_format, ch_map)}, + {SOF_TKN_CAVS_AUDIO_FORMAT_IN_CH_CFG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc4_audio_format, ch_cfg)}, + {SOF_TKN_CAVS_AUDIO_FORMAT_IN_INTERLEAVING_STYLE, SND_SOC_TPLG_TUPLE_TYPE_WORD, + get_token_u32, offsetof(struct sof_ipc4_audio_format, interleaving_style)}, + {SOF_TKN_CAVS_AUDIO_FORMAT_IN_FMT_CFG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc4_audio_format, fmt_cfg)}, +}; + +static const struct sof_topology_token ipc4_out_audio_format_tokens[] = { + {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc4_audio_format, sampling_frequency)}, + {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_BIT_DEPTH, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc4_audio_format, bit_depth)}, + {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_CH_MAP, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc4_audio_format, ch_map)}, + {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_CH_CFG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc4_audio_format, ch_cfg)}, + {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_INTERLEAVING_STYLE, SND_SOC_TPLG_TUPLE_TYPE_WORD, + get_token_u32, offsetof(struct sof_ipc4_audio_format, interleaving_style)}, + {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_FMT_CFG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc4_audio_format, fmt_cfg)}, +}; + +static const struct sof_topology_token ipc4_copier_gateway_cfg_tokens[] = { + {SOF_TKN_CAVS_AUDIO_FORMAT_DMA_BUFFER_SIZE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, 0}, +}; + +static const struct sof_topology_token ipc4_copier_tokens[] = { + {SOF_TKN_INTEL_COPIER_NODE_TYPE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, 0}, +}; + +static const struct sof_topology_token ipc4_audio_fmt_num_tokens[] = { + {SOF_TKN_COMP_NUM_AUDIO_FORMATS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + 0}, +}; + +static const struct sof_topology_token dai_tokens[] = { + {SOF_TKN_DAI_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_dai_type, + offsetof(struct sof_ipc4_copier, dai_type)}, + {SOF_TKN_DAI_INDEX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc4_copier, dai_index)}, +}; + +/* Component extended tokens */ +static const struct sof_topology_token comp_ext_tokens[] = { + {SOF_TKN_COMP_UUID, SND_SOC_TPLG_TUPLE_TYPE_UUID, get_token_uuid, + offsetof(struct snd_sof_widget, uuid)}, +}; + +static const struct sof_topology_token gain_tokens[] = { + {SOF_TKN_GAIN_RAMP_TYPE, SND_SOC_TPLG_TUPLE_TYPE_WORD, + get_token_u32, offsetof(struct sof_ipc4_gain_data, curve_type)}, + {SOF_TKN_GAIN_RAMP_DURATION, + SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc4_gain_data, curve_duration)}, + {SOF_TKN_GAIN_VAL, SND_SOC_TPLG_TUPLE_TYPE_WORD, + get_token_u32, offsetof(struct sof_ipc4_gain_data, init_val)}, +}; + +/* SRC */ +static const struct sof_topology_token src_tokens[] = { + {SOF_TKN_SRC_RATE_OUT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc4_src, sink_rate)}, +}; + +static const struct sof_token_info ipc4_token_list[SOF_TOKEN_COUNT] = { + [SOF_DAI_TOKENS] = {"DAI tokens", dai_tokens, ARRAY_SIZE(dai_tokens)}, + [SOF_PIPELINE_TOKENS] = {"Pipeline tokens", pipeline_tokens, ARRAY_SIZE(pipeline_tokens)}, + [SOF_SCHED_TOKENS] = {"Scheduler tokens", ipc4_sched_tokens, + ARRAY_SIZE(ipc4_sched_tokens)}, + [SOF_COMP_EXT_TOKENS] = {"Comp extended tokens", comp_ext_tokens, + ARRAY_SIZE(comp_ext_tokens)}, + [SOF_COMP_TOKENS] = {"IPC4 Component tokens", + ipc4_comp_tokens, ARRAY_SIZE(ipc4_comp_tokens)}, + [SOF_IN_AUDIO_FORMAT_TOKENS] = {"IPC4 Input Audio format tokens", + ipc4_in_audio_format_tokens, ARRAY_SIZE(ipc4_in_audio_format_tokens)}, + [SOF_OUT_AUDIO_FORMAT_TOKENS] = {"IPC4 Output Audio format tokens", + ipc4_out_audio_format_tokens, ARRAY_SIZE(ipc4_out_audio_format_tokens)}, + [SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS] = {"IPC4 Audio format buffer size tokens", + ipc4_audio_format_buffer_size_tokens, + ARRAY_SIZE(ipc4_audio_format_buffer_size_tokens)}, + [SOF_COPIER_GATEWAY_CFG_TOKENS] = {"IPC4 Copier gateway config tokens", + ipc4_copier_gateway_cfg_tokens, ARRAY_SIZE(ipc4_copier_gateway_cfg_tokens)}, + [SOF_COPIER_TOKENS] = {"IPC4 Copier tokens", ipc4_copier_tokens, + ARRAY_SIZE(ipc4_copier_tokens)}, + [SOF_AUDIO_FMT_NUM_TOKENS] = {"IPC4 Audio format number tokens", + ipc4_audio_fmt_num_tokens, ARRAY_SIZE(ipc4_audio_fmt_num_tokens)}, + [SOF_GAIN_TOKENS] = {"Gain tokens", gain_tokens, ARRAY_SIZE(gain_tokens)}, + [SOF_SRC_TOKENS] = {"SRC tokens", src_tokens, ARRAY_SIZE(src_tokens)}, +}; + +static void sof_ipc4_dbg_audio_format(struct device *dev, + struct sof_ipc4_audio_format *format, + size_t object_size, int num_format) +{ + struct sof_ipc4_audio_format *fmt; + void *ptr = format; + int i; + + for (i = 0; i < num_format; i++, ptr = (u8 *)ptr + object_size) { + fmt = ptr; + dev_dbg(dev, + " #%d: %uKHz, %ubit (ch_map %#x ch_cfg %u interleaving_style %u fmt_cfg %#x)\n", + i, fmt->sampling_frequency, fmt->bit_depth, fmt->ch_map, + fmt->ch_cfg, fmt->interleaving_style, fmt->fmt_cfg); + } +} + +/** + * sof_ipc4_get_audio_fmt - get available audio formats from swidget->tuples + * @scomp: pointer to pointer to SOC component + * @swidget: pointer to struct snd_sof_widget containing tuples + * @available_fmt: pointer to struct sof_ipc4_available_audio_format being filling in + * @has_out_format: true if available_fmt contains output format + * + * Return: 0 if successful + */ +static int sof_ipc4_get_audio_fmt(struct snd_soc_component *scomp, + struct snd_sof_widget *swidget, + struct sof_ipc4_available_audio_format *available_fmt, + bool has_out_format) +{ + struct sof_ipc4_base_module_cfg *base_config; + struct sof_ipc4_audio_format *out_format; + int audio_fmt_num = 0; + int ret, i; + + ret = sof_update_ipc_object(scomp, &audio_fmt_num, + SOF_AUDIO_FMT_NUM_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(audio_fmt_num), 1); + if (ret || audio_fmt_num <= 0) { + dev_err(scomp->dev, "Invalid number of audio formats: %d\n", audio_fmt_num); + return -EINVAL; + } + available_fmt->audio_fmt_num = audio_fmt_num; + + dev_dbg(scomp->dev, "Number of audio formats: %d\n", available_fmt->audio_fmt_num); + + base_config = kcalloc(available_fmt->audio_fmt_num, sizeof(*base_config), GFP_KERNEL); + if (!base_config) + return -ENOMEM; + + /* set cpc and is_pages for all base_cfg */ + for (i = 0; i < available_fmt->audio_fmt_num; i++) { + ret = sof_update_ipc_object(scomp, &base_config[i], + SOF_COMP_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(*base_config), 1); + if (ret) { + dev_err(scomp->dev, "parse comp tokens failed %d\n", ret); + goto err_in; + } + } + + /* copy the ibs/obs for each base_cfg */ + ret = sof_update_ipc_object(scomp, base_config, + SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(*base_config), + available_fmt->audio_fmt_num); + if (ret) { + dev_err(scomp->dev, "parse buffer size tokens failed %d\n", ret); + goto err_in; + } + + for (i = 0; i < available_fmt->audio_fmt_num; i++) + dev_dbg(scomp->dev, "%d: ibs: %d obs: %d cpc: %d is_pages: %d\n", i, + base_config[i].ibs, base_config[i].obs, + base_config[i].cpc, base_config[i].is_pages); + + ret = sof_update_ipc_object(scomp, &base_config->audio_fmt, + SOF_IN_AUDIO_FORMAT_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(*base_config), + available_fmt->audio_fmt_num); + if (ret) { + dev_err(scomp->dev, "parse base_config audio_fmt tokens failed %d\n", ret); + goto err_in; + } + + dev_dbg(scomp->dev, "Get input audio formats for %s\n", swidget->widget->name); + sof_ipc4_dbg_audio_format(scomp->dev, &base_config->audio_fmt, + sizeof(*base_config), + available_fmt->audio_fmt_num); + + available_fmt->base_config = base_config; + + if (!has_out_format) + return 0; + + out_format = kcalloc(available_fmt->audio_fmt_num, sizeof(*out_format), GFP_KERNEL); + if (!out_format) { + ret = -ENOMEM; + goto err_in; + } + + ret = sof_update_ipc_object(scomp, out_format, + SOF_OUT_AUDIO_FORMAT_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(*out_format), + available_fmt->audio_fmt_num); + + if (ret) { + dev_err(scomp->dev, "parse output audio_fmt tokens failed\n"); + goto err_out; + } + + available_fmt->out_audio_fmt = out_format; + dev_dbg(scomp->dev, "Get output audio formats for %s\n", swidget->widget->name); + sof_ipc4_dbg_audio_format(scomp->dev, out_format, sizeof(*out_format), + available_fmt->audio_fmt_num); + + return 0; + +err_out: + kfree(out_format); +err_in: + kfree(base_config); + + return ret; +} + +/* release the memory allocated in sof_ipc4_get_audio_fmt */ +static void sof_ipc4_free_audio_fmt(struct sof_ipc4_available_audio_format *available_fmt) + +{ + kfree(available_fmt->base_config); + available_fmt->base_config = NULL; + kfree(available_fmt->out_audio_fmt); + available_fmt->out_audio_fmt = NULL; +} + +static void sof_ipc4_widget_free_comp(struct snd_sof_widget *swidget) +{ + kfree(swidget->private); +} + +static int sof_ipc4_widget_set_module_info(struct snd_sof_widget *swidget) +{ + struct snd_soc_component *scomp = swidget->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_ipc4_fw_data *ipc4_data = sdev->private; + struct sof_ipc4_fw_module *fw_modules = ipc4_data->fw_modules; + int i; + + if (!fw_modules) { + dev_err(sdev->dev, "no fw_module information\n"); + return -EINVAL; + } + + /* set module info */ + for (i = 0; i < ipc4_data->num_fw_modules; i++) { + if (guid_equal(&swidget->uuid, &fw_modules[i].man4_module_entry.uuid)) { + swidget->module_info = &fw_modules[i]; + return 0; + } + } + + dev_err(sdev->dev, "failed to find module info for widget %s with UUID %pUL\n", + swidget->widget->name, &swidget->uuid); + return -EINVAL; +} + +static int sof_ipc4_widget_setup_msg(struct snd_sof_widget *swidget, struct sof_ipc4_msg *msg) +{ + struct sof_ipc4_fw_module *fw_module; + uint32_t type; + int ret; + + ret = sof_ipc4_widget_set_module_info(swidget); + if (ret) + return ret; + + fw_module = swidget->module_info; + + msg->primary = fw_module->man4_module_entry.id; + msg->primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_INIT_INSTANCE); + msg->primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST); + msg->primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG); + + msg->extension = SOF_IPC4_MOD_EXT_PPL_ID(swidget->pipeline_id); + msg->extension |= SOF_IPC4_MOD_EXT_CORE_ID(swidget->core); + + type = (fw_module->man4_module_entry.type & SOF_IPC4_MODULE_DP) ? 1 : 0; + msg->extension |= SOF_IPC4_MOD_EXT_DOMAIN(type); + + return 0; +} + +static int sof_ipc4_widget_setup_pcm(struct snd_sof_widget *swidget) +{ + struct sof_ipc4_available_audio_format *available_fmt; + struct snd_soc_component *scomp = swidget->scomp; + struct sof_ipc4_copier *ipc4_copier; + int node_type = 0; + int ret, i; + + ipc4_copier = kzalloc(sizeof(*ipc4_copier), GFP_KERNEL); + if (!ipc4_copier) + return -ENOMEM; + + swidget->private = ipc4_copier; + available_fmt = &ipc4_copier->available_fmt; + + dev_dbg(scomp->dev, "Updating IPC structure for %s\n", swidget->widget->name); + + ret = sof_ipc4_get_audio_fmt(scomp, swidget, available_fmt, true); + if (ret) + goto free_copier; + + available_fmt->dma_buffer_size = kcalloc(available_fmt->audio_fmt_num, sizeof(u32), + GFP_KERNEL); + if (!available_fmt->dma_buffer_size) { + ret = -ENOMEM; + goto free_available_fmt; + } + + ret = sof_update_ipc_object(scomp, available_fmt->dma_buffer_size, + SOF_COPIER_GATEWAY_CFG_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(u32), + available_fmt->audio_fmt_num); + if (ret) { + dev_err(scomp->dev, "Failed to parse dma buffer size in audio format for %s\n", + swidget->widget->name); + goto err; + } + + dev_dbg(scomp->dev, "dma buffer size:\n"); + for (i = 0; i < available_fmt->audio_fmt_num; i++) + dev_dbg(scomp->dev, "%d: %u\n", i, + available_fmt->dma_buffer_size[i]); + + ret = sof_update_ipc_object(scomp, &node_type, + SOF_COPIER_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(node_type), 1); + + if (ret) { + dev_err(scomp->dev, "parse host copier node type token failed %d\n", + ret); + goto err; + } + dev_dbg(scomp->dev, "host copier '%s' node_type %u\n", swidget->widget->name, node_type); + + ipc4_copier->data.gtw_cfg.node_id = SOF_IPC4_NODE_TYPE(node_type); + ipc4_copier->gtw_attr = kzalloc(sizeof(*ipc4_copier->gtw_attr), GFP_KERNEL); + if (!ipc4_copier->gtw_attr) { + ret = -ENOMEM; + goto err; + } + + ipc4_copier->copier_config = (uint32_t *)ipc4_copier->gtw_attr; + ipc4_copier->data.gtw_cfg.config_length = + sizeof(struct sof_ipc4_gtw_attributes) >> 2; + + /* set up module info and message header */ + ret = sof_ipc4_widget_setup_msg(swidget, &ipc4_copier->msg); + if (ret) + goto free_gtw_attr; + + return 0; + +free_gtw_attr: + kfree(ipc4_copier->gtw_attr); +err: + kfree(available_fmt->dma_buffer_size); +free_available_fmt: + sof_ipc4_free_audio_fmt(available_fmt); +free_copier: + kfree(ipc4_copier); + swidget->private = NULL; + return ret; +} + +static void sof_ipc4_widget_free_comp_pcm(struct snd_sof_widget *swidget) +{ + struct sof_ipc4_copier *ipc4_copier = swidget->private; + struct sof_ipc4_available_audio_format *available_fmt; + + if (!ipc4_copier) + return; + + available_fmt = &ipc4_copier->available_fmt; + kfree(available_fmt->dma_buffer_size); + kfree(available_fmt->base_config); + kfree(available_fmt->out_audio_fmt); + kfree(ipc4_copier->gtw_attr); + kfree(ipc4_copier); + swidget->private = NULL; +} + +static int sof_ipc4_widget_setup_comp_dai(struct snd_sof_widget *swidget) +{ + struct sof_ipc4_available_audio_format *available_fmt; + struct snd_soc_component *scomp = swidget->scomp; + struct snd_sof_dai *dai = swidget->private; + struct sof_ipc4_copier *ipc4_copier; + int node_type = 0; + int ret, i; + + ipc4_copier = kzalloc(sizeof(*ipc4_copier), GFP_KERNEL); + if (!ipc4_copier) + return -ENOMEM; + + available_fmt = &ipc4_copier->available_fmt; + + dev_dbg(scomp->dev, "Updating IPC structure for %s\n", swidget->widget->name); + + ret = sof_ipc4_get_audio_fmt(scomp, swidget, available_fmt, true); + if (ret) + goto free_copier; + + available_fmt->dma_buffer_size = kcalloc(available_fmt->audio_fmt_num, sizeof(u32), + GFP_KERNEL); + if (!available_fmt->dma_buffer_size) { + ret = -ENOMEM; + goto free_available_fmt; + } + + ret = sof_update_ipc_object(scomp, available_fmt->dma_buffer_size, + SOF_COPIER_GATEWAY_CFG_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(u32), + available_fmt->audio_fmt_num); + if (ret) { + dev_err(scomp->dev, "Failed to parse dma buffer size in audio format for %s\n", + swidget->widget->name); + goto err; + } + + for (i = 0; i < available_fmt->audio_fmt_num; i++) + dev_dbg(scomp->dev, "%d: dma buffer size: %u\n", i, + available_fmt->dma_buffer_size[i]); + + ret = sof_update_ipc_object(scomp, &node_type, + SOF_COPIER_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(node_type), 1); + if (ret) { + dev_err(scomp->dev, "parse dai node type failed %d\n", ret); + goto err; + } + + ret = sof_update_ipc_object(scomp, ipc4_copier, + SOF_DAI_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(u32), 1); + if (ret) { + dev_err(scomp->dev, "parse dai copier node token failed %d\n", ret); + goto err; + } + + dev_dbg(scomp->dev, "dai %s node_type %u dai_type %u dai_index %d\n", swidget->widget->name, + node_type, ipc4_copier->dai_type, ipc4_copier->dai_index); + + ipc4_copier->data.gtw_cfg.node_id = SOF_IPC4_NODE_TYPE(node_type); + + switch (ipc4_copier->dai_type) { + case SOF_DAI_INTEL_ALH: + { + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_ipc4_alh_configuration_blob *blob; + struct snd_sof_widget *w; + + blob = kzalloc(sizeof(*blob), GFP_KERNEL); + if (!blob) { + ret = -ENOMEM; + goto err; + } + + list_for_each_entry(w, &sdev->widget_list, list) { + if (w->widget->sname && + strcmp(w->widget->sname, swidget->widget->sname)) + continue; + + blob->alh_cfg.count++; + } + + ipc4_copier->copier_config = (uint32_t *)blob; + ipc4_copier->data.gtw_cfg.config_length = sizeof(*blob) >> 2; + break; + } + case SOF_DAI_INTEL_SSP: + /* set SSP DAI index as the node_id */ + ipc4_copier->data.gtw_cfg.node_id |= + SOF_IPC4_NODE_INDEX_INTEL_SSP(ipc4_copier->dai_index); + break; + case SOF_DAI_INTEL_DMIC: + /* set DMIC DAI index as the node_id */ + ipc4_copier->data.gtw_cfg.node_id |= + SOF_IPC4_NODE_INDEX_INTEL_DMIC(ipc4_copier->dai_index); + break; + default: + ipc4_copier->gtw_attr = kzalloc(sizeof(*ipc4_copier->gtw_attr), GFP_KERNEL); + if (!ipc4_copier->gtw_attr) { + ret = -ENOMEM; + goto err; + } + + ipc4_copier->copier_config = (uint32_t *)ipc4_copier->gtw_attr; + ipc4_copier->data.gtw_cfg.config_length = + sizeof(struct sof_ipc4_gtw_attributes) >> 2; + break; + } + + dai->scomp = scomp; + dai->private = ipc4_copier; + + /* set up module info and message header */ + ret = sof_ipc4_widget_setup_msg(swidget, &ipc4_copier->msg); + if (ret) + goto free_copier_config; + + return 0; + +free_copier_config: + kfree(ipc4_copier->copier_config); +err: + kfree(available_fmt->dma_buffer_size); +free_available_fmt: + sof_ipc4_free_audio_fmt(available_fmt); +free_copier: + kfree(ipc4_copier); + dai->private = NULL; + dai->scomp = NULL; + return ret; +} + +static void sof_ipc4_widget_free_comp_dai(struct snd_sof_widget *swidget) +{ + struct sof_ipc4_available_audio_format *available_fmt; + struct snd_sof_dai *dai = swidget->private; + struct sof_ipc4_copier *ipc4_copier; + + if (!dai) + return; + + if (!dai->private) { + kfree(dai); + swidget->private = NULL; + return; + } + + ipc4_copier = dai->private; + available_fmt = &ipc4_copier->available_fmt; + + kfree(available_fmt->dma_buffer_size); + kfree(available_fmt->base_config); + kfree(available_fmt->out_audio_fmt); + if (ipc4_copier->dai_type != SOF_DAI_INTEL_SSP && + ipc4_copier->dai_type != SOF_DAI_INTEL_DMIC) + kfree(ipc4_copier->copier_config); + kfree(dai->private); + kfree(dai); + swidget->private = NULL; +} + +static int sof_ipc4_widget_setup_comp_pipeline(struct snd_sof_widget *swidget) +{ + struct snd_soc_component *scomp = swidget->scomp; + struct sof_ipc4_pipeline *pipeline; + int ret; + + pipeline = kzalloc(sizeof(*pipeline), GFP_KERNEL); + if (!pipeline) + return -ENOMEM; + + ret = sof_update_ipc_object(scomp, pipeline, SOF_SCHED_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(*pipeline), 1); + if (ret) { + dev_err(scomp->dev, "parsing scheduler tokens failed\n"); + goto err; + } + + /* parse one set of pipeline tokens */ + ret = sof_update_ipc_object(scomp, swidget, SOF_PIPELINE_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(*swidget), 1); + if (ret) { + dev_err(scomp->dev, "parsing pipeline tokens failed\n"); + goto err; + } + + /* TODO: Get priority from topology */ + pipeline->priority = 0; + + dev_dbg(scomp->dev, "pipeline '%s': id %d pri %d lp mode %d\n", + swidget->widget->name, swidget->pipeline_id, + pipeline->priority, pipeline->lp_mode); + + swidget->private = pipeline; + + pipeline->msg.primary = SOF_IPC4_GLB_PIPE_PRIORITY(pipeline->priority); + pipeline->msg.primary |= SOF_IPC4_GLB_PIPE_INSTANCE_ID(swidget->pipeline_id); + pipeline->msg.primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_CREATE_PIPELINE); + pipeline->msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST); + pipeline->msg.primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG); + + pipeline->msg.extension = pipeline->lp_mode; + pipeline->state = SOF_IPC4_PIPE_UNINITIALIZED; + + return 0; +err: + kfree(pipeline); + return ret; +} + +static int sof_ipc4_widget_setup_comp_pga(struct snd_sof_widget *swidget) +{ + struct snd_soc_component *scomp = swidget->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_ipc4_fw_module *fw_module; + struct snd_sof_control *scontrol; + struct sof_ipc4_gain *gain; + int ret; + + gain = kzalloc(sizeof(*gain), GFP_KERNEL); + if (!gain) + return -ENOMEM; + + swidget->private = gain; + + gain->data.channels = SOF_IPC4_GAIN_ALL_CHANNELS_MASK; + gain->data.init_val = SOF_IPC4_VOL_ZERO_DB; + + /* The out_audio_fmt in topology is ignored as it is not required to be sent to the FW */ + ret = sof_ipc4_get_audio_fmt(scomp, swidget, &gain->available_fmt, false); + if (ret) + goto err; + + ret = sof_update_ipc_object(scomp, &gain->data, SOF_GAIN_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(gain->data), 1); + if (ret) { + dev_err(scomp->dev, "Parsing gain tokens failed\n"); + goto err; + } + + dev_dbg(scomp->dev, + "pga widget %s: ramp type: %d, ramp duration %d, initial gain value: %#x, cpc %d\n", + swidget->widget->name, gain->data.curve_type, gain->data.curve_duration, + gain->data.init_val, gain->base_config.cpc); + + ret = sof_ipc4_widget_setup_msg(swidget, &gain->msg); + if (ret) + goto err; + + fw_module = swidget->module_info; + + /* update module ID for all kcontrols for this widget */ + list_for_each_entry(scontrol, &sdev->kcontrol_list, list) + if (scontrol->comp_id == swidget->comp_id) { + struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data; + struct sof_ipc4_msg *msg = &cdata->msg; + + msg->primary |= fw_module->man4_module_entry.id; + } + + return 0; +err: + sof_ipc4_free_audio_fmt(&gain->available_fmt); + kfree(gain); + swidget->private = NULL; + return ret; +} + +static void sof_ipc4_widget_free_comp_pga(struct snd_sof_widget *swidget) +{ + struct sof_ipc4_gain *gain = swidget->private; + + if (!gain) + return; + + sof_ipc4_free_audio_fmt(&gain->available_fmt); + kfree(swidget->private); + swidget->private = NULL; +} + +static int sof_ipc4_widget_setup_comp_mixer(struct snd_sof_widget *swidget) +{ + struct snd_soc_component *scomp = swidget->scomp; + struct sof_ipc4_mixer *mixer; + int ret; + + dev_dbg(scomp->dev, "Updating IPC structure for %s\n", swidget->widget->name); + + mixer = kzalloc(sizeof(*mixer), GFP_KERNEL); + if (!mixer) + return -ENOMEM; + + swidget->private = mixer; + + /* The out_audio_fmt in topology is ignored as it is not required to be sent to the FW */ + ret = sof_ipc4_get_audio_fmt(scomp, swidget, &mixer->available_fmt, false); + if (ret) + goto err; + + ret = sof_ipc4_widget_setup_msg(swidget, &mixer->msg); + if (ret) + goto err; + + return 0; +err: + sof_ipc4_free_audio_fmt(&mixer->available_fmt); + kfree(mixer); + swidget->private = NULL; + return ret; +} + +static int sof_ipc4_widget_setup_comp_src(struct snd_sof_widget *swidget) +{ + struct snd_soc_component *scomp = swidget->scomp; + struct sof_ipc4_src *src; + int ret; + + dev_dbg(scomp->dev, "Updating IPC structure for %s\n", swidget->widget->name); + + src = kzalloc(sizeof(*src), GFP_KERNEL); + if (!src) + return -ENOMEM; + + swidget->private = src; + + /* The out_audio_fmt in topology is ignored as it is not required by SRC */ + ret = sof_ipc4_get_audio_fmt(scomp, swidget, &src->available_fmt, false); + if (ret) + goto err; + + ret = sof_update_ipc_object(scomp, src, SOF_SRC_TOKENS, swidget->tuples, + swidget->num_tuples, sizeof(*src), 1); + if (ret) { + dev_err(scomp->dev, "Parsing SRC tokens failed\n"); + goto err; + } + + dev_dbg(scomp->dev, "SRC sink rate %d\n", src->sink_rate); + + ret = sof_ipc4_widget_setup_msg(swidget, &src->msg); + if (ret) + goto err; + + return 0; +err: + sof_ipc4_free_audio_fmt(&src->available_fmt); + kfree(src); + swidget->private = NULL; + return ret; +} + +static void sof_ipc4_widget_free_comp_src(struct snd_sof_widget *swidget) +{ + struct sof_ipc4_src *src = swidget->private; + + if (!src) + return; + + sof_ipc4_free_audio_fmt(&src->available_fmt); + kfree(swidget->private); + swidget->private = NULL; +} + +static void sof_ipc4_widget_free_comp_mixer(struct snd_sof_widget *swidget) +{ + struct sof_ipc4_mixer *mixer = swidget->private; + + if (!mixer) + return; + + sof_ipc4_free_audio_fmt(&mixer->available_fmt); + kfree(swidget->private); + swidget->private = NULL; +} + +static void +sof_ipc4_update_pipeline_mem_usage(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget, + struct sof_ipc4_base_module_cfg *base_config) +{ + struct sof_ipc4_fw_module *fw_module = swidget->module_info; + struct snd_sof_widget *pipe_widget; + struct sof_ipc4_pipeline *pipeline; + int task_mem, queue_mem; + int ibs, bss, total; + + ibs = base_config->ibs; + bss = base_config->is_pages; + + task_mem = SOF_IPC4_PIPELINE_OBJECT_SIZE; + task_mem += SOF_IPC4_MODULE_INSTANCE_LIST_ITEM_SIZE + bss; + + if (fw_module->man4_module_entry.type & SOF_IPC4_MODULE_LL) { + task_mem += SOF_IPC4_FW_ROUNDUP(SOF_IPC4_LL_TASK_OBJECT_SIZE); + task_mem += SOF_IPC4_FW_MAX_QUEUE_COUNT * SOF_IPC4_MODULE_INSTANCE_LIST_ITEM_SIZE; + task_mem += SOF_IPC4_LL_TASK_LIST_ITEM_SIZE; + } else { + task_mem += SOF_IPC4_FW_ROUNDUP(SOF_IPC4_DP_TASK_OBJECT_SIZE); + task_mem += SOF_IPC4_DP_TASK_LIST_SIZE; + } + + ibs = SOF_IPC4_FW_ROUNDUP(ibs); + queue_mem = SOF_IPC4_FW_MAX_QUEUE_COUNT * (SOF_IPC4_DATA_QUEUE_OBJECT_SIZE + ibs); + + total = SOF_IPC4_FW_PAGE(task_mem + queue_mem); + + pipe_widget = swidget->pipe_widget; + pipeline = pipe_widget->private; + pipeline->mem_usage += total; +} + +static int sof_ipc4_widget_assign_instance_id(struct snd_sof_dev *sdev, + struct snd_sof_widget *swidget) +{ + struct sof_ipc4_fw_module *fw_module = swidget->module_info; + int max_instances = fw_module->man4_module_entry.instance_max_count; + + swidget->instance_id = ida_alloc_max(&fw_module->m_ida, max_instances, GFP_KERNEL); + if (swidget->instance_id < 0) { + dev_err(sdev->dev, "failed to assign instance id for widget %s", + swidget->widget->name); + return swidget->instance_id; + } + + return 0; +} + +static int sof_ipc4_init_audio_fmt(struct snd_sof_dev *sdev, + struct snd_sof_widget *swidget, + struct sof_ipc4_base_module_cfg *base_config, + struct sof_ipc4_audio_format *out_format, + struct snd_pcm_hw_params *params, + struct sof_ipc4_available_audio_format *available_fmt, + size_t object_offset) +{ + void *ptr = available_fmt->ref_audio_fmt; + u32 valid_bits; + u32 channels; + u32 rate; + int sample_valid_bits; + int i; + + if (!ptr) { + dev_err(sdev->dev, "no reference formats for %s\n", swidget->widget->name); + return -EINVAL; + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + sample_valid_bits = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + sample_valid_bits = 24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + sample_valid_bits = 32; + break; + default: + dev_err(sdev->dev, "invalid pcm frame format %d\n", params_format(params)); + return -EINVAL; + } + + if (!available_fmt->audio_fmt_num) { + dev_err(sdev->dev, "no formats available for %s\n", swidget->widget->name); + return -EINVAL; + } + + /* + * Search supported audio formats to match rate, channels ,and + * sample_valid_bytes from runtime params + */ + for (i = 0; i < available_fmt->audio_fmt_num; i++, ptr = (u8 *)ptr + object_offset) { + struct sof_ipc4_audio_format *fmt = ptr; + + rate = fmt->sampling_frequency; + channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg); + valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg); + if (params_rate(params) == rate && params_channels(params) == channels && + sample_valid_bits == valid_bits) { + dev_dbg(sdev->dev, "matching audio format index for %uHz, %ubit, %u channels: %d\n", + rate, valid_bits, channels, i); + + /* copy ibs/obs and input format */ + memcpy(base_config, &available_fmt->base_config[i], + sizeof(struct sof_ipc4_base_module_cfg)); + + /* copy output format */ + if (out_format) + memcpy(out_format, &available_fmt->out_audio_fmt[i], + sizeof(struct sof_ipc4_audio_format)); + break; + } + } + + if (i == available_fmt->audio_fmt_num) { + dev_err(sdev->dev, "%s: Unsupported audio format: %uHz, %ubit, %u channels\n", + __func__, params_rate(params), sample_valid_bits, params_channels(params)); + return -EINVAL; + } + + dev_dbg(sdev->dev, "Init input audio formats for %s\n", swidget->widget->name); + sof_ipc4_dbg_audio_format(sdev->dev, &base_config->audio_fmt, + sizeof(*base_config), 1); + if (out_format) { + dev_dbg(sdev->dev, "Init output audio formats for %s\n", swidget->widget->name); + sof_ipc4_dbg_audio_format(sdev->dev, out_format, + sizeof(*out_format), 1); + } + + /* Return the index of the matched format */ + return i; +} + +static void sof_ipc4_unprepare_copier_module(struct snd_sof_widget *swidget) +{ + struct sof_ipc4_copier *ipc4_copier = NULL; + struct snd_sof_widget *pipe_widget; + struct sof_ipc4_pipeline *pipeline; + + /* reset pipeline memory usage */ + pipe_widget = swidget->pipe_widget; + pipeline = pipe_widget->private; + pipeline->mem_usage = 0; + + if (WIDGET_IS_AIF(swidget->id)) { + ipc4_copier = swidget->private; + } else if (WIDGET_IS_DAI(swidget->id)) { + struct snd_sof_dai *dai = swidget->private; + + ipc4_copier = dai->private; + if (ipc4_copier->dai_type == SOF_DAI_INTEL_ALH) { + struct sof_ipc4_alh_configuration_blob *blob; + unsigned int group_id; + + blob = (struct sof_ipc4_alh_configuration_blob *)ipc4_copier->copier_config; + if (blob->alh_cfg.count > 1) { + group_id = SOF_IPC4_NODE_INDEX(ipc4_copier->data.gtw_cfg.node_id) - + ALH_MULTI_GTW_BASE; + ida_free(&alh_group_ida, group_id); + } + } + } + + if (ipc4_copier) { + kfree(ipc4_copier->ipc_config_data); + ipc4_copier->ipc_config_data = NULL; + ipc4_copier->ipc_config_size = 0; + } +} + +#if IS_ENABLED(CONFIG_ACPI) && IS_ENABLED(CONFIG_SND_INTEL_NHLT) +static int snd_sof_get_hw_config_params(struct snd_sof_dev *sdev, struct snd_sof_dai *dai, + int *sample_rate, int *channel_count, int *bit_depth) +{ + struct snd_soc_tplg_hw_config *hw_config; + struct snd_sof_dai_link *slink; + bool dai_link_found = false; + bool hw_cfg_found = false; + int i; + + /* get current hw_config from link */ + list_for_each_entry(slink, &sdev->dai_link_list, list) { + if (!strcmp(slink->link->name, dai->name)) { + dai_link_found = true; + break; + } + } + + if (!dai_link_found) { + dev_err(sdev->dev, "%s: no DAI link found for DAI %s\n", __func__, dai->name); + return -EINVAL; + } + + for (i = 0; i < slink->num_hw_configs; i++) { + hw_config = &slink->hw_configs[i]; + if (dai->current_config == le32_to_cpu(hw_config->id)) { + hw_cfg_found = true; + break; + } + } + + if (!hw_cfg_found) { + dev_err(sdev->dev, "%s: no matching hw_config found for DAI %s\n", __func__, + dai->name); + return -EINVAL; + } + + *bit_depth = le32_to_cpu(hw_config->tdm_slot_width); + *channel_count = le32_to_cpu(hw_config->tdm_slots); + *sample_rate = le32_to_cpu(hw_config->fsync_rate); + + dev_dbg(sdev->dev, "sample rate: %d sample width: %d channels: %d\n", + *sample_rate, *bit_depth, *channel_count); + + return 0; +} + +static int snd_sof_get_nhlt_endpoint_data(struct snd_sof_dev *sdev, struct snd_sof_dai *dai, + struct snd_pcm_hw_params *params, u32 dai_index, + u32 linktype, u8 dir, u32 **dst, u32 *len) +{ + struct sof_ipc4_fw_data *ipc4_data = sdev->private; + struct nhlt_specific_cfg *cfg; + int sample_rate, channel_count; + int bit_depth, ret; + u32 nhlt_type; + + /* convert to NHLT type */ + switch (linktype) { + case SOF_DAI_INTEL_DMIC: + nhlt_type = NHLT_LINK_DMIC; + bit_depth = params_width(params); + channel_count = params_channels(params); + sample_rate = params_rate(params); + break; + case SOF_DAI_INTEL_SSP: + nhlt_type = NHLT_LINK_SSP; + ret = snd_sof_get_hw_config_params(sdev, dai, &sample_rate, &channel_count, + &bit_depth); + if (ret < 0) + return ret; + break; + default: + return 0; + } + + dev_dbg(sdev->dev, "dai index %d nhlt type %d direction %d\n", + dai_index, nhlt_type, dir); + + /* find NHLT blob with matching params */ + cfg = intel_nhlt_get_endpoint_blob(sdev->dev, ipc4_data->nhlt, dai_index, nhlt_type, + bit_depth, bit_depth, channel_count, sample_rate, + dir, 0); + + if (!cfg) { + dev_err(sdev->dev, + "no matching blob for sample rate: %d sample width: %d channels: %d\n", + sample_rate, bit_depth, channel_count); + return -EINVAL; + } + + /* config length should be in dwords */ + *len = cfg->size >> 2; + *dst = (u32 *)cfg->caps; + + return 0; +} +#else +static int snd_sof_get_nhlt_endpoint_data(struct snd_sof_dev *sdev, struct snd_sof_dai *dai, + struct snd_pcm_hw_params *params, u32 dai_index, + u32 linktype, u8 dir, u32 **dst, u32 *len) +{ + return 0; +} +#endif + +static int +sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, + struct snd_pcm_hw_params *fe_params, + struct snd_sof_platform_stream_params *platform_params, + struct snd_pcm_hw_params *pipeline_params, int dir) +{ + struct sof_ipc4_available_audio_format *available_fmt; + struct snd_soc_component *scomp = swidget->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_ipc4_copier_data *copier_data; + struct snd_pcm_hw_params *ref_params; + struct sof_ipc4_copier *ipc4_copier; + struct snd_sof_dai *dai; + struct snd_mask *fmt; + int out_sample_valid_bits; + size_t ref_audio_fmt_size; + void **ipc_config_data; + int *ipc_config_size; + u32 **data; + int ipc_size, ret; + + dev_dbg(sdev->dev, "copier %s, type %d", swidget->widget->name, swidget->id); + + switch (swidget->id) { + case snd_soc_dapm_aif_in: + case snd_soc_dapm_aif_out: + { + struct sof_ipc4_gtw_attributes *gtw_attr; + struct snd_sof_widget *pipe_widget; + struct sof_ipc4_pipeline *pipeline; + + pipe_widget = swidget->pipe_widget; + pipeline = pipe_widget->private; + ipc4_copier = (struct sof_ipc4_copier *)swidget->private; + gtw_attr = ipc4_copier->gtw_attr; + copier_data = &ipc4_copier->data; + available_fmt = &ipc4_copier->available_fmt; + + /* + * base_config->audio_fmt and out_audio_fmt represent the input and output audio + * formats. Use the input format as the reference to match pcm params for playback + * and the output format as reference for capture. + */ + if (dir == SNDRV_PCM_STREAM_PLAYBACK) { + available_fmt->ref_audio_fmt = &available_fmt->base_config->audio_fmt; + ref_audio_fmt_size = sizeof(struct sof_ipc4_base_module_cfg); + } else { + available_fmt->ref_audio_fmt = available_fmt->out_audio_fmt; + ref_audio_fmt_size = sizeof(struct sof_ipc4_audio_format); + } + copier_data->gtw_cfg.node_id &= ~SOF_IPC4_NODE_INDEX_MASK; + copier_data->gtw_cfg.node_id |= + SOF_IPC4_NODE_INDEX(platform_params->stream_tag - 1); + + /* set gateway attributes */ + gtw_attr->lp_buffer_alloc = pipeline->lp_mode; + ref_params = fe_params; + break; + } + case snd_soc_dapm_dai_in: + case snd_soc_dapm_dai_out: + { + dai = swidget->private; + + ipc4_copier = (struct sof_ipc4_copier *)dai->private; + copier_data = &ipc4_copier->data; + available_fmt = &ipc4_copier->available_fmt; + if (dir == SNDRV_PCM_STREAM_CAPTURE) { + available_fmt->ref_audio_fmt = available_fmt->out_audio_fmt; + ref_audio_fmt_size = sizeof(struct sof_ipc4_audio_format); + + /* + * modify the input params for the dai copier as it only supports + * 32-bit always + */ + fmt = hw_param_mask(pipeline_params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE); + } else { + available_fmt->ref_audio_fmt = &available_fmt->base_config->audio_fmt; + ref_audio_fmt_size = sizeof(struct sof_ipc4_base_module_cfg); + } + + ref_params = pipeline_params; + + ret = snd_sof_get_nhlt_endpoint_data(sdev, dai, fe_params, ipc4_copier->dai_index, + ipc4_copier->dai_type, dir, + &ipc4_copier->copier_config, + &copier_data->gtw_cfg.config_length); + if (ret < 0) + return ret; + + break; + } + default: + dev_err(sdev->dev, "unsupported type %d for copier %s", + swidget->id, swidget->widget->name); + return -EINVAL; + } + + /* set input and output audio formats */ + ret = sof_ipc4_init_audio_fmt(sdev, swidget, &copier_data->base_config, + &copier_data->out_format, ref_params, + available_fmt, ref_audio_fmt_size); + if (ret < 0) + return ret; + + switch (swidget->id) { + case snd_soc_dapm_dai_in: + case snd_soc_dapm_dai_out: + { + /* + * Only SOF_DAI_INTEL_ALH needs copier_data to set blob. + * That's why only ALH dai's blob is set after sof_ipc4_init_audio_fmt + */ + if (ipc4_copier->dai_type == SOF_DAI_INTEL_ALH) { + struct sof_ipc4_alh_configuration_blob *blob; + struct sof_ipc4_copier_data *alh_data; + struct sof_ipc4_copier *alh_copier; + struct snd_sof_widget *w; + u32 ch_mask = 0; + u32 ch_map; + int i; + + blob = (struct sof_ipc4_alh_configuration_blob *)ipc4_copier->copier_config; + + blob->gw_attr.lp_buffer_alloc = 0; + + /* Get channel_mask from ch_map */ + ch_map = copier_data->base_config.audio_fmt.ch_map; + for (i = 0; ch_map; i++) { + if ((ch_map & 0xf) != 0xf) + ch_mask |= BIT(i); + ch_map >>= 4; + } + + /* + * Set each gtw_cfg.node_id to blob->alh_cfg.mapping[] + * for all widgets with the same stream name + */ + i = 0; + list_for_each_entry(w, &sdev->widget_list, list) { + if (w->widget->sname && + strcmp(w->widget->sname, swidget->widget->sname)) + continue; + + dai = w->private; + alh_copier = (struct sof_ipc4_copier *)dai->private; + alh_data = &alh_copier->data; + blob->alh_cfg.mapping[i].alh_id = alh_data->gtw_cfg.node_id; + blob->alh_cfg.mapping[i].channel_mask = ch_mask; + i++; + } + if (blob->alh_cfg.count > 1) { + int group_id; + + group_id = ida_alloc_max(&alh_group_ida, ALH_MULTI_GTW_COUNT - 1, + GFP_KERNEL); + + if (group_id < 0) + return group_id; + + /* add multi-gateway base */ + group_id += ALH_MULTI_GTW_BASE; + copier_data->gtw_cfg.node_id &= ~SOF_IPC4_NODE_INDEX_MASK; + copier_data->gtw_cfg.node_id |= SOF_IPC4_NODE_INDEX(group_id); + } + } + } + } + + /* modify the input params for the next widget */ + fmt = hw_param_mask(pipeline_params, SNDRV_PCM_HW_PARAM_FORMAT); + out_sample_valid_bits = + SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(copier_data->out_format.fmt_cfg); + snd_mask_none(fmt); + switch (out_sample_valid_bits) { + case 16: + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); + break; + case 24: + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + break; + case 32: + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE); + break; + default: + dev_err(sdev->dev, "invalid sample frame format %d\n", + params_format(pipeline_params)); + return -EINVAL; + } + + /* set the gateway dma_buffer_size using the matched ID returned above */ + copier_data->gtw_cfg.dma_buffer_size = available_fmt->dma_buffer_size[ret]; + + data = &ipc4_copier->copier_config; + ipc_config_size = &ipc4_copier->ipc_config_size; + ipc_config_data = &ipc4_copier->ipc_config_data; + + /* config_length is DWORD based */ + ipc_size = sizeof(*copier_data) + copier_data->gtw_cfg.config_length * 4; + + dev_dbg(sdev->dev, "copier %s, IPC size is %d", swidget->widget->name, ipc_size); + + *ipc_config_data = kzalloc(ipc_size, GFP_KERNEL); + if (!*ipc_config_data) + return -ENOMEM; + + *ipc_config_size = ipc_size; + + /* copy IPC data */ + memcpy(*ipc_config_data, (void *)copier_data, sizeof(*copier_data)); + if (copier_data->gtw_cfg.config_length) + memcpy(*ipc_config_data + sizeof(*copier_data), + *data, copier_data->gtw_cfg.config_length * 4); + + /* update pipeline memory usage */ + sof_ipc4_update_pipeline_mem_usage(sdev, swidget, &copier_data->base_config); + + return 0; +} + +static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, + struct snd_pcm_hw_params *fe_params, + struct snd_sof_platform_stream_params *platform_params, + struct snd_pcm_hw_params *pipeline_params, int dir) +{ + struct snd_soc_component *scomp = swidget->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_ipc4_gain *gain = swidget->private; + int ret; + + gain->available_fmt.ref_audio_fmt = &gain->available_fmt.base_config->audio_fmt; + + /* output format is not required to be sent to the FW for gain */ + ret = sof_ipc4_init_audio_fmt(sdev, swidget, &gain->base_config, + NULL, pipeline_params, &gain->available_fmt, + sizeof(gain->base_config)); + if (ret < 0) + return ret; + + /* update pipeline memory usage */ + sof_ipc4_update_pipeline_mem_usage(sdev, swidget, &gain->base_config); + + return 0; +} + +static int sof_ipc4_prepare_mixer_module(struct snd_sof_widget *swidget, + struct snd_pcm_hw_params *fe_params, + struct snd_sof_platform_stream_params *platform_params, + struct snd_pcm_hw_params *pipeline_params, int dir) +{ + struct snd_soc_component *scomp = swidget->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_ipc4_mixer *mixer = swidget->private; + int ret; + + /* only 32bit is supported by mixer */ + mixer->available_fmt.ref_audio_fmt = &mixer->available_fmt.base_config->audio_fmt; + + /* output format is not required to be sent to the FW for mixer */ + ret = sof_ipc4_init_audio_fmt(sdev, swidget, &mixer->base_config, + NULL, pipeline_params, &mixer->available_fmt, + sizeof(mixer->base_config)); + if (ret < 0) + return ret; + + /* update pipeline memory usage */ + sof_ipc4_update_pipeline_mem_usage(sdev, swidget, &mixer->base_config); + + return 0; +} + +static int sof_ipc4_prepare_src_module(struct snd_sof_widget *swidget, + struct snd_pcm_hw_params *fe_params, + struct snd_sof_platform_stream_params *platform_params, + struct snd_pcm_hw_params *pipeline_params, int dir) +{ + struct snd_soc_component *scomp = swidget->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_ipc4_src *src = swidget->private; + struct snd_interval *rate; + int ret; + + src->available_fmt.ref_audio_fmt = &src->available_fmt.base_config->audio_fmt; + + /* output format is not required to be sent to the FW for SRC */ + ret = sof_ipc4_init_audio_fmt(sdev, swidget, &src->base_config, + NULL, pipeline_params, &src->available_fmt, + sizeof(src->base_config)); + if (ret < 0) + return ret; + + /* update pipeline memory usage */ + sof_ipc4_update_pipeline_mem_usage(sdev, swidget, &src->base_config); + + /* update pipeline_params for sink widgets */ + rate = hw_param_interval(pipeline_params, SNDRV_PCM_HW_PARAM_RATE); + rate->min = src->sink_rate; + rate->max = rate->min; + + return 0; +} + +static int sof_ipc4_control_load_volume(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol) +{ + struct sof_ipc4_control_data *control_data; + struct sof_ipc4_msg *msg; + int i; + + scontrol->size = struct_size(control_data, chanv, scontrol->num_channels); + + /* scontrol->ipc_control_data will be freed in sof_control_unload */ + scontrol->ipc_control_data = kzalloc(scontrol->size, GFP_KERNEL); + if (!scontrol->ipc_control_data) + return -ENOMEM; + + control_data = scontrol->ipc_control_data; + control_data->index = scontrol->index; + + msg = &control_data->msg; + msg->primary = SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_LARGE_CONFIG_SET); + msg->primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST); + msg->primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG); + + msg->extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(SOF_IPC4_GAIN_PARAM_ID); + + /* set default volume values to 0dB in control */ + for (i = 0; i < scontrol->num_channels; i++) { + control_data->chanv[i].channel = i; + control_data->chanv[i].value = SOF_IPC4_VOL_ZERO_DB; + } + + return 0; +} + +static int sof_ipc4_control_setup(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol) +{ + switch (scontrol->info_type) { + case SND_SOC_TPLG_CTL_VOLSW: + case SND_SOC_TPLG_CTL_VOLSW_SX: + case SND_SOC_TPLG_CTL_VOLSW_XR_SX: + return sof_ipc4_control_load_volume(sdev, scontrol); + default: + break; + } + + return 0; +} + +static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) +{ + struct sof_ipc4_pipeline *pipeline; + struct sof_ipc4_msg *msg; + void *ipc_data = NULL; + u32 ipc_size = 0; + int ret; + + switch (swidget->id) { + case snd_soc_dapm_scheduler: + pipeline = swidget->private; + + dev_dbg(sdev->dev, "pipeline: %d memory pages: %d\n", swidget->pipeline_id, + pipeline->mem_usage); + + msg = &pipeline->msg; + msg->primary |= pipeline->mem_usage; + break; + case snd_soc_dapm_aif_in: + case snd_soc_dapm_aif_out: + { + struct sof_ipc4_copier *ipc4_copier = swidget->private; + + ipc_size = ipc4_copier->ipc_config_size; + ipc_data = ipc4_copier->ipc_config_data; + + msg = &ipc4_copier->msg; + break; + } + case snd_soc_dapm_dai_in: + case snd_soc_dapm_dai_out: + { + struct snd_sof_dai *dai = swidget->private; + struct sof_ipc4_copier *ipc4_copier = dai->private; + + ipc_size = ipc4_copier->ipc_config_size; + ipc_data = ipc4_copier->ipc_config_data; + + msg = &ipc4_copier->msg; + break; + } + case snd_soc_dapm_pga: + { + struct sof_ipc4_gain *gain = swidget->private; + + ipc_size = sizeof(struct sof_ipc4_base_module_cfg) + + sizeof(struct sof_ipc4_gain_data); + ipc_data = gain; + + msg = &gain->msg; + break; + } + case snd_soc_dapm_mixer: + { + struct sof_ipc4_mixer *mixer = swidget->private; + + ipc_size = sizeof(mixer->base_config); + ipc_data = &mixer->base_config; + + msg = &mixer->msg; + break; + } + case snd_soc_dapm_src: + { + struct sof_ipc4_src *src = swidget->private; + + ipc_size = sizeof(struct sof_ipc4_base_module_cfg) + sizeof(src->sink_rate); + ipc_data = src; + + msg = &src->msg; + break; + } + default: + dev_err(sdev->dev, "widget type %d not supported", swidget->id); + return -EINVAL; + } + + if (swidget->id != snd_soc_dapm_scheduler) { + ret = sof_ipc4_widget_assign_instance_id(sdev, swidget); + if (ret < 0) { + dev_err(sdev->dev, "failed to assign instance id for %s\n", + swidget->widget->name); + return ret; + } + + msg->primary &= ~SOF_IPC4_MOD_INSTANCE_MASK; + msg->primary |= SOF_IPC4_MOD_INSTANCE(swidget->instance_id); + + msg->extension &= ~SOF_IPC4_MOD_EXT_PARAM_SIZE_MASK; + msg->extension |= ipc_size >> 2; + } + dev_dbg(sdev->dev, "Create widget %s instance %d - pipe %d - core %d\n", + swidget->widget->name, swidget->instance_id, swidget->pipeline_id, swidget->core); + + msg->data_size = ipc_size; + msg->data_ptr = ipc_data; + + ret = sof_ipc_tx_message(sdev->ipc, msg, ipc_size, NULL, 0); + if (ret < 0) { + dev_err(sdev->dev, "failed to create module %s\n", swidget->widget->name); + + if (swidget->id != snd_soc_dapm_scheduler) { + struct sof_ipc4_fw_module *fw_module = swidget->module_info; + + ida_free(&fw_module->m_ida, swidget->instance_id); + } + } + + return ret; +} + +static int sof_ipc4_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) +{ + struct sof_ipc4_fw_module *fw_module = swidget->module_info; + int ret = 0; + + /* freeing a pipeline frees all the widgets associated with it */ + if (swidget->id == snd_soc_dapm_scheduler) { + struct sof_ipc4_pipeline *pipeline = swidget->private; + struct sof_ipc4_msg msg = {{ 0 }}; + u32 header; + + header = SOF_IPC4_GLB_PIPE_INSTANCE_ID(swidget->pipeline_id); + header |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_DELETE_PIPELINE); + header |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST); + header |= SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG); + + msg.primary = header; + + ret = sof_ipc_tx_message(sdev->ipc, &msg, 0, NULL, 0); + if (ret < 0) + dev_err(sdev->dev, "failed to free pipeline widget %s\n", + swidget->widget->name); + + pipeline->mem_usage = 0; + pipeline->state = SOF_IPC4_PIPE_UNINITIALIZED; + } else { + ida_free(&fw_module->m_ida, swidget->instance_id); + } + + return ret; +} + +static int sof_ipc4_route_setup(struct snd_sof_dev *sdev, struct snd_sof_route *sroute) +{ + struct snd_sof_widget *src_widget = sroute->src_widget; + struct snd_sof_widget *sink_widget = sroute->sink_widget; + struct sof_ipc4_fw_module *src_fw_module = src_widget->module_info; + struct sof_ipc4_fw_module *sink_fw_module = sink_widget->module_info; + struct sof_ipc4_msg msg = {{ 0 }}; + u32 header, extension; + int src_queue = 0; + int dst_queue = 0; + int ret; + + dev_dbg(sdev->dev, "bind %s -> %s\n", + src_widget->widget->name, sink_widget->widget->name); + + header = src_fw_module->man4_module_entry.id; + header |= SOF_IPC4_MOD_INSTANCE(src_widget->instance_id); + header |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_BIND); + header |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST); + header |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG); + + extension = sink_fw_module->man4_module_entry.id; + extension |= SOF_IPC4_MOD_EXT_DST_MOD_INSTANCE(sink_widget->instance_id); + extension |= SOF_IPC4_MOD_EXT_DST_MOD_QUEUE_ID(dst_queue); + extension |= SOF_IPC4_MOD_EXT_SRC_MOD_QUEUE_ID(src_queue); + + msg.primary = header; + msg.extension = extension; + + ret = sof_ipc_tx_message(sdev->ipc, &msg, 0, NULL, 0); + if (ret < 0) + dev_err(sdev->dev, "%s: failed to bind modules %s -> %s\n", + __func__, src_widget->widget->name, sink_widget->widget->name); + + return ret; +} + +static int sof_ipc4_route_free(struct snd_sof_dev *sdev, struct snd_sof_route *sroute) +{ + struct snd_sof_widget *src_widget = sroute->src_widget; + struct snd_sof_widget *sink_widget = sroute->sink_widget; + struct sof_ipc4_fw_module *src_fw_module = src_widget->module_info; + struct sof_ipc4_fw_module *sink_fw_module = sink_widget->module_info; + struct sof_ipc4_msg msg = {{ 0 }}; + u32 header, extension; + int src_queue = 0; + int dst_queue = 0; + int ret; + + dev_dbg(sdev->dev, "unbind modules %s -> %s\n", + src_widget->widget->name, sink_widget->widget->name); + + header = src_fw_module->man4_module_entry.id; + header |= SOF_IPC4_MOD_INSTANCE(src_widget->instance_id); + header |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_UNBIND); + header |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST); + header |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG); + + extension = sink_fw_module->man4_module_entry.id; + extension |= SOF_IPC4_MOD_EXT_DST_MOD_INSTANCE(sink_widget->instance_id); + extension |= SOF_IPC4_MOD_EXT_DST_MOD_QUEUE_ID(dst_queue); + extension |= SOF_IPC4_MOD_EXT_SRC_MOD_QUEUE_ID(src_queue); + + msg.primary = header; + msg.extension = extension; + + ret = sof_ipc_tx_message(sdev->ipc, &msg, 0, NULL, 0); + if (ret < 0) + dev_err(sdev->dev, "failed to unbind modules %s -> %s\n", + src_widget->widget->name, sink_widget->widget->name); + + return ret; +} + +static int sof_ipc4_dai_config(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget, + unsigned int flags, struct snd_sof_dai_config_data *data) +{ + struct snd_sof_widget *pipe_widget = swidget->pipe_widget; + struct sof_ipc4_pipeline *pipeline = pipe_widget->private; + struct snd_sof_dai *dai = swidget->private; + struct sof_ipc4_gtw_attributes *gtw_attr; + struct sof_ipc4_copier_data *copier_data; + struct sof_ipc4_copier *ipc4_copier; + + if (!dai || !dai->private) { + dev_err(sdev->dev, "Invalid DAI or DAI private data for %s\n", + swidget->widget->name); + return -EINVAL; + } + + ipc4_copier = (struct sof_ipc4_copier *)dai->private; + copier_data = &ipc4_copier->data; + + if (!data) + return 0; + + switch (ipc4_copier->dai_type) { + case SOF_DAI_INTEL_HDA: + gtw_attr = ipc4_copier->gtw_attr; + gtw_attr->lp_buffer_alloc = pipeline->lp_mode; + fallthrough; + case SOF_DAI_INTEL_ALH: + copier_data->gtw_cfg.node_id &= ~SOF_IPC4_NODE_INDEX_MASK; + copier_data->gtw_cfg.node_id |= SOF_IPC4_NODE_INDEX(data->dai_data); + break; + case SOF_DAI_INTEL_DMIC: + case SOF_DAI_INTEL_SSP: + /* nothing to do for SSP/DMIC */ + break; + default: + dev_err(sdev->dev, "%s: unsupported dai type %d\n", __func__, + ipc4_copier->dai_type); + return -EINVAL; + } + + return 0; +} + +static int sof_ipc4_parse_manifest(struct snd_soc_component *scomp, int index, + struct snd_soc_tplg_manifest *man) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct sof_ipc4_fw_data *ipc4_data = sdev->private; + struct sof_manifest_tlv *manifest_tlv; + struct sof_manifest *manifest; + u32 size = le32_to_cpu(man->priv.size); + u8 *man_ptr = man->priv.data; + u32 len_check; + int i; + + if (!size || size < SOF_IPC4_TPLG_ABI_SIZE) { + dev_err(scomp->dev, "%s: Invalid topology ABI size: %u\n", + __func__, size); + return -EINVAL; + } + + manifest = (struct sof_manifest *)man_ptr; + + dev_info(scomp->dev, + "Topology: ABI %d:%d:%d Kernel ABI %u:%u:%u\n", + le16_to_cpu(manifest->abi_major), le16_to_cpu(manifest->abi_minor), + le16_to_cpu(manifest->abi_patch), + SOF_ABI_MAJOR, SOF_ABI_MINOR, SOF_ABI_PATCH); + + /* TODO: Add ABI compatibility check */ + + /* no more data after the ABI version */ + if (size <= SOF_IPC4_TPLG_ABI_SIZE) + return 0; + + manifest_tlv = manifest->items; + len_check = sizeof(struct sof_manifest); + for (i = 0; i < le16_to_cpu(manifest->count); i++) { + len_check += sizeof(struct sof_manifest_tlv) + le32_to_cpu(manifest_tlv->size); + if (len_check > size) + return -EINVAL; + + switch (le32_to_cpu(manifest_tlv->type)) { + case SOF_MANIFEST_DATA_TYPE_NHLT: + /* no NHLT in BIOS, so use the one from topology manifest */ + if (ipc4_data->nhlt) + break; + ipc4_data->nhlt = devm_kmemdup(sdev->dev, manifest_tlv->data, + le32_to_cpu(manifest_tlv->size), GFP_KERNEL); + if (!ipc4_data->nhlt) + return -ENOMEM; + break; + default: + dev_warn(scomp->dev, "Skipping unknown manifest data type %d\n", + manifest_tlv->type); + break; + } + man_ptr += sizeof(struct sof_manifest_tlv) + le32_to_cpu(manifest_tlv->size); + manifest_tlv = (struct sof_manifest_tlv *)man_ptr; + } + + return 0; +} + +static int sof_ipc4_dai_get_clk(struct snd_sof_dev *sdev, struct snd_sof_dai *dai, int clk_type) +{ + struct sof_ipc4_copier *ipc4_copier = dai->private; + struct snd_soc_tplg_hw_config *hw_config; + struct snd_sof_dai_link *slink; + bool dai_link_found = false; + bool hw_cfg_found = false; + int i; + + if (!ipc4_copier) + return 0; + + list_for_each_entry(slink, &sdev->dai_link_list, list) { + if (!strcmp(slink->link->name, dai->name)) { + dai_link_found = true; + break; + } + } + + if (!dai_link_found) { + dev_err(sdev->dev, "no DAI link found for DAI %s\n", dai->name); + return -EINVAL; + } + + for (i = 0; i < slink->num_hw_configs; i++) { + hw_config = &slink->hw_configs[i]; + if (dai->current_config == le32_to_cpu(hw_config->id)) { + hw_cfg_found = true; + break; + } + } + + if (!hw_cfg_found) { + dev_err(sdev->dev, "no matching hw_config found for DAI %s\n", dai->name); + return -EINVAL; + } + + switch (ipc4_copier->dai_type) { + case SOF_DAI_INTEL_SSP: + switch (clk_type) { + case SOF_DAI_CLK_INTEL_SSP_MCLK: + return le32_to_cpu(hw_config->mclk_rate); + case SOF_DAI_CLK_INTEL_SSP_BCLK: + return le32_to_cpu(hw_config->bclk_rate); + default: + dev_err(sdev->dev, "Invalid clk type for SSP %d\n", clk_type); + break; + } + break; + default: + dev_err(sdev->dev, "DAI type %d not supported yet!\n", ipc4_copier->dai_type); + break; + } + + return -EINVAL; +} + +static enum sof_tokens host_token_list[] = { + SOF_COMP_TOKENS, + SOF_AUDIO_FMT_NUM_TOKENS, + SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS, + SOF_IN_AUDIO_FORMAT_TOKENS, + SOF_OUT_AUDIO_FORMAT_TOKENS, + SOF_COPIER_GATEWAY_CFG_TOKENS, + SOF_COPIER_TOKENS, + SOF_COMP_EXT_TOKENS, +}; + +static enum sof_tokens pipeline_token_list[] = { + SOF_SCHED_TOKENS, + SOF_PIPELINE_TOKENS, +}; + +static enum sof_tokens dai_token_list[] = { + SOF_COMP_TOKENS, + SOF_AUDIO_FMT_NUM_TOKENS, + SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS, + SOF_IN_AUDIO_FORMAT_TOKENS, + SOF_OUT_AUDIO_FORMAT_TOKENS, + SOF_COPIER_GATEWAY_CFG_TOKENS, + SOF_COPIER_TOKENS, + SOF_DAI_TOKENS, + SOF_COMP_EXT_TOKENS, +}; + +static enum sof_tokens pga_token_list[] = { + SOF_COMP_TOKENS, + SOF_GAIN_TOKENS, + SOF_AUDIO_FMT_NUM_TOKENS, + SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS, + SOF_IN_AUDIO_FORMAT_TOKENS, + SOF_COMP_EXT_TOKENS, +}; + +static enum sof_tokens mixer_token_list[] = { + SOF_COMP_TOKENS, + SOF_AUDIO_FMT_NUM_TOKENS, + SOF_IN_AUDIO_FORMAT_TOKENS, + SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS, + SOF_COMP_EXT_TOKENS, +}; + +static enum sof_tokens src_token_list[] = { + SOF_COMP_TOKENS, + SOF_SRC_TOKENS, + SOF_AUDIO_FMT_NUM_TOKENS, + SOF_IN_AUDIO_FORMAT_TOKENS, + SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS, + SOF_COMP_EXT_TOKENS, +}; + +static const struct sof_ipc_tplg_widget_ops tplg_ipc4_widget_ops[SND_SOC_DAPM_TYPE_COUNT] = { + [snd_soc_dapm_aif_in] = {sof_ipc4_widget_setup_pcm, sof_ipc4_widget_free_comp_pcm, + host_token_list, ARRAY_SIZE(host_token_list), NULL, + sof_ipc4_prepare_copier_module, + sof_ipc4_unprepare_copier_module}, + [snd_soc_dapm_aif_out] = {sof_ipc4_widget_setup_pcm, sof_ipc4_widget_free_comp_pcm, + host_token_list, ARRAY_SIZE(host_token_list), NULL, + sof_ipc4_prepare_copier_module, + sof_ipc4_unprepare_copier_module}, + [snd_soc_dapm_dai_in] = {sof_ipc4_widget_setup_comp_dai, sof_ipc4_widget_free_comp_dai, + dai_token_list, ARRAY_SIZE(dai_token_list), NULL, + sof_ipc4_prepare_copier_module, + sof_ipc4_unprepare_copier_module}, + [snd_soc_dapm_dai_out] = {sof_ipc4_widget_setup_comp_dai, sof_ipc4_widget_free_comp_dai, + dai_token_list, ARRAY_SIZE(dai_token_list), NULL, + sof_ipc4_prepare_copier_module, + sof_ipc4_unprepare_copier_module}, + [snd_soc_dapm_scheduler] = {sof_ipc4_widget_setup_comp_pipeline, sof_ipc4_widget_free_comp, + pipeline_token_list, ARRAY_SIZE(pipeline_token_list), NULL, + NULL, NULL}, + [snd_soc_dapm_pga] = {sof_ipc4_widget_setup_comp_pga, sof_ipc4_widget_free_comp_pga, + pga_token_list, ARRAY_SIZE(pga_token_list), NULL, + sof_ipc4_prepare_gain_module, + NULL}, + [snd_soc_dapm_mixer] = {sof_ipc4_widget_setup_comp_mixer, sof_ipc4_widget_free_comp_mixer, + mixer_token_list, ARRAY_SIZE(mixer_token_list), + NULL, sof_ipc4_prepare_mixer_module, + NULL}, + [snd_soc_dapm_src] = {sof_ipc4_widget_setup_comp_src, sof_ipc4_widget_free_comp_src, + src_token_list, ARRAY_SIZE(src_token_list), + NULL, sof_ipc4_prepare_src_module, + NULL}, +}; + +const struct sof_ipc_tplg_ops ipc4_tplg_ops = { + .widget = tplg_ipc4_widget_ops, + .token_list = ipc4_token_list, + .control_setup = sof_ipc4_control_setup, + .control = &tplg_ipc4_control_ops, + .widget_setup = sof_ipc4_widget_setup, + .widget_free = sof_ipc4_widget_free, + .route_setup = sof_ipc4_route_setup, + .route_free = sof_ipc4_route_free, + .dai_config = sof_ipc4_dai_config, + .parse_manifest = sof_ipc4_parse_manifest, + .dai_get_clk = sof_ipc4_dai_get_clk, +}; diff --git a/sound/soc/sof/ipc4-topology.h b/sound/soc/sof/ipc4-topology.h new file mode 100644 index 000000000000..0aa87a8add5d --- /dev/null +++ b/sound/soc/sof/ipc4-topology.h @@ -0,0 +1,270 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright(c) 2022 Intel Corporation. All rights reserved. + */ + +#ifndef __INCLUDE_SOUND_SOF_IPC4_TOPOLOGY_H__ +#define __INCLUDE_SOUND_SOF_IPC4_TOPOLOGY_H__ + +#include <sound/sof/ipc4/header.h> + +#define SOF_IPC4_FW_PAGE_SIZE BIT(12) +#define SOF_IPC4_FW_PAGE(x) ((((x) + BIT(12) - 1) & ~(BIT(12) - 1)) >> 12) +#define SOF_IPC4_FW_ROUNDUP(x) (((x) + BIT(6) - 1) & (~(BIT(6) - 1))) + +#define SOF_IPC4_MODULE_LOAD_TYPE GENMASK(3, 0) +#define SOF_IPC4_MODULE_AUTO_START BIT(4) +/* + * Two module schedule domains in fw : + * LL domain - Low latency domain + * DP domain - Data processing domain + * The LL setting should be equal to !DP setting + */ +#define SOF_IPC4_MODULE_LL BIT(5) +#define SOF_IPC4_MODULE_DP BIT(6) +#define SOF_IPC4_MODULE_LIB_CODE BIT(7) + +#define SOF_IPC4_MODULE_INSTANCE_LIST_ITEM_SIZE 12 +#define SOF_IPC4_PIPELINE_OBJECT_SIZE 448 +#define SOF_IPC4_DATA_QUEUE_OBJECT_SIZE 128 +#define SOF_IPC4_LL_TASK_OBJECT_SIZE 72 +#define SOF_IPC4_DP_TASK_OBJECT_SIZE 104 +#define SOF_IPC4_DP_TASK_LIST_SIZE (12 + 8) +#define SOF_IPC4_LL_TASK_LIST_ITEM_SIZE 12 +#define SOF_IPC4_FW_MAX_PAGE_COUNT 20 +#define SOF_IPC4_FW_MAX_QUEUE_COUNT 8 + +/* Node index and mask applicable for host copier and ALH/HDA type DAI copiers */ +#define SOF_IPC4_NODE_INDEX_MASK 0xFF +#define SOF_IPC4_NODE_INDEX(x) ((x) & SOF_IPC4_NODE_INDEX_MASK) +#define SOF_IPC4_NODE_TYPE(x) ((x) << 8) + +/* Node ID for SSP type DAI copiers */ +#define SOF_IPC4_NODE_INDEX_INTEL_SSP(x) (((x) & 0xf) << 4) + +/* Node ID for DMIC type DAI copiers */ +#define SOF_IPC4_NODE_INDEX_INTEL_DMIC(x) (((x) & 0x7) << 5) + +#define SOF_IPC4_GAIN_ALL_CHANNELS_MASK 0xffffffff +#define SOF_IPC4_VOL_ZERO_DB 0x7fffffff + +#define ALH_MAX_NUMBER_OF_GTW 16 + +/* + * The base of multi-gateways. Multi-gateways addressing starts from + * ALH_MULTI_GTW_BASE and there are ALH_MULTI_GTW_COUNT multi-sources + * and ALH_MULTI_GTW_COUNT multi-sinks available. + * Addressing is continuous from ALH_MULTI_GTW_BASE to + * ALH_MULTI_GTW_BASE + ALH_MULTI_GTW_COUNT - 1. + */ +#define ALH_MULTI_GTW_BASE 0x50 +/* A magic number from FW */ +#define ALH_MULTI_GTW_COUNT 8 + +/** + * struct sof_ipc4_pipeline - pipeline config data + * @priority: Priority of this pipeline + * @lp_mode: Low power mode + * @mem_usage: Memory usage + * @state: Pipeline state + * @msg: message structure for pipeline + */ +struct sof_ipc4_pipeline { + uint32_t priority; + uint32_t lp_mode; + uint32_t mem_usage; + int state; + struct sof_ipc4_msg msg; +}; + +/** + * struct sof_ipc4_available_audio_format - Available audio formats + * @base_config: Available base config + * @out_audio_fmt: Available output audio format + * @ref_audio_fmt: Reference audio format to match runtime audio format + * @dma_buffer_size: Available Gateway DMA buffer size (in bytes) + * @audio_fmt_num: Number of available audio formats + */ +struct sof_ipc4_available_audio_format { + struct sof_ipc4_base_module_cfg *base_config; + struct sof_ipc4_audio_format *out_audio_fmt; + struct sof_ipc4_audio_format *ref_audio_fmt; + u32 *dma_buffer_size; + int audio_fmt_num; +}; + +/** + * struct sof_copier_gateway_cfg - IPC gateway configuration + * @node_id: ID of Gateway Node + * @dma_buffer_size: Preferred Gateway DMA buffer size (in bytes) + * @config_length: Length of gateway node configuration blob specified in #config_data + * config_data: Gateway node configuration blob + */ +struct sof_copier_gateway_cfg { + uint32_t node_id; + uint32_t dma_buffer_size; + uint32_t config_length; + uint32_t config_data[]; +}; + +/** + * struct sof_ipc4_copier_data - IPC data for copier + * @base_config: Base configuration including input audio format + * @out_format: Output audio format + * @copier_feature_mask: Copier feature mask + * @gtw_cfg: Gateway configuration + */ +struct sof_ipc4_copier_data { + struct sof_ipc4_base_module_cfg base_config; + struct sof_ipc4_audio_format out_format; + uint32_t copier_feature_mask; + struct sof_copier_gateway_cfg gtw_cfg; +}; + +/** + * struct sof_ipc4_gtw_attributes: Gateway attributes + * @lp_buffer_alloc: Gateway data requested in low power memory + * @alloc_from_reg_file: Gateway data requested in register file memory + * @rsvd: reserved for future use + */ +struct sof_ipc4_gtw_attributes { + uint32_t lp_buffer_alloc : 1; + uint32_t alloc_from_reg_file : 1; + uint32_t rsvd : 30; +}; + +/** struct sof_ipc4_alh_multi_gtw_cfg: ALH gateway cfg data + * @count: Number of streams (valid items in mapping array) + * @alh_id: ALH stream id of a single ALH stream aggregated + * @channel_mask: Channel mask + * @mapping: ALH streams + */ +struct sof_ipc4_alh_multi_gtw_cfg { + uint32_t count; + struct { + uint32_t alh_id; + uint32_t channel_mask; + } mapping[ALH_MAX_NUMBER_OF_GTW]; +} __packed; + +/** struct sof_ipc4_alh_configuration_blob: ALH blob + * @gw_attr: Gateway attributes + * @alh_cfg: ALH configuration data + */ +struct sof_ipc4_alh_configuration_blob { + struct sof_ipc4_gtw_attributes gw_attr; + struct sof_ipc4_alh_multi_gtw_cfg alh_cfg; +}; + +/** + * struct sof_ipc4_copier - copier config data + * @data: IPC copier data + * @copier_config: Copier + blob + * @ipc_config_size: Size of copier_config + * @available_fmt: Available audio format + * @frame_fmt: frame format + * @msg: message structure for copier + * @gtw_attr: Gateway attributes for copier blob + * @dai_type: DAI type + * @dai_index: DAI index + */ +struct sof_ipc4_copier { + struct sof_ipc4_copier_data data; + u32 *copier_config; + uint32_t ipc_config_size; + void *ipc_config_data; + struct sof_ipc4_available_audio_format available_fmt; + u32 frame_fmt; + struct sof_ipc4_msg msg; + struct sof_ipc4_gtw_attributes *gtw_attr; + u32 dai_type; + int dai_index; +}; + +/** + * struct sof_ipc4_ctrl_value_chan: generic channel mapped value data + * @channel: Channel ID + * @value: gain value + */ +struct sof_ipc4_ctrl_value_chan { + u32 channel; + u32 value; +}; + +/** + * struct sof_ipc4_control_data - IPC data for kcontrol IO + * @msg: message structure for kcontrol IO + * @index: pipeline ID + * @chanv: channel ID and value array used by volume type controls + * @data: data for binary kcontrols + */ +struct sof_ipc4_control_data { + struct sof_ipc4_msg msg; + int index; + + union { + struct sof_ipc4_ctrl_value_chan chanv[0]; + struct sof_abi_hdr data[0]; + }; +}; + +/** + * struct sof_ipc4_gain_data - IPC gain blob + * @channels: Channels + * @init_val: Initial value + * @curve_type: Curve type + * @reserved: reserved for future use + * @curve_duration: Curve duration + */ +struct sof_ipc4_gain_data { + uint32_t channels; + uint32_t init_val; + uint32_t curve_type; + uint32_t reserved; + uint32_t curve_duration; +} __aligned(8); + +/** + * struct sof_ipc4_gain - gain config data + * @base_config: IPC base config data + * @data: IPC gain blob + * @available_fmt: Available audio format + * @msg: message structure for gain + */ +struct sof_ipc4_gain { + struct sof_ipc4_base_module_cfg base_config; + struct sof_ipc4_gain_data data; + struct sof_ipc4_available_audio_format available_fmt; + struct sof_ipc4_msg msg; +}; + +/** + * struct sof_ipc4_mixer - mixer config data + * @base_config: IPC base config data + * @available_fmt: Available audio format + * @msg: IPC4 message struct containing header and data info + */ +struct sof_ipc4_mixer { + struct sof_ipc4_base_module_cfg base_config; + struct sof_ipc4_available_audio_format available_fmt; + struct sof_ipc4_msg msg; +}; + +/** + * struct sof_ipc4_src SRC config data + * @base_config: IPC base config data + * @sink_rate: Output rate for sink module + * @available_fmt: Available audio format + * @msg: IPC4 message struct containing header and data info + */ +struct sof_ipc4_src { + struct sof_ipc4_base_module_cfg base_config; + uint32_t sink_rate; + struct sof_ipc4_available_audio_format available_fmt; + struct sof_ipc4_msg msg; +}; + +#endif diff --git a/sound/soc/sof/ipc4.c b/sound/soc/sof/ipc4.c new file mode 100644 index 000000000000..6eaa18e27e5a --- /dev/null +++ b/sound/soc/sof/ipc4.c @@ -0,0 +1,670 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// Authors: Rander Wang <rander.wang@linux.intel.com> +// Peter Ujfalusi <peter.ujfalusi@linux.intel.com> +// +#include <sound/sof/header.h> +#include <sound/sof/ipc4/header.h> +#include "sof-priv.h" +#include "sof-audio.h" +#include "ipc4-priv.h" +#include "ops.h" + +#ifdef DEBUG_VERBOSE +#define sof_ipc4_dump_payload(sdev, ipc_data, size) \ + print_hex_dump_debug("Message payload: ", \ + DUMP_PREFIX_OFFSET, \ + 16, 4, ipc_data, size, false) +#else +#define sof_ipc4_dump_payload(sdev, ipc_data, size) do { } while (0) +#endif + +static const struct sof_ipc4_fw_status { + int status; + char *msg; +} ipc4_status[] = { + {0, "The operation was successful"}, + {1, "Invalid parameter specified"}, + {2, "Unknown message type specified"}, + {3, "Not enough space in the IPC reply buffer to complete the request"}, + {4, "The system or resource is busy"}, + {5, "Replaced ADSP IPC PENDING (unused)"}, + {6, "Unknown error while processing the request"}, + {7, "Unsupported operation requested"}, + {8, "Reserved (ADSP_STAGE_UNINITIALIZED removed)"}, + {9, "Specified resource not found"}, + {10, "A resource's ID requested to be created is already assigned"}, + {11, "Reserved (ADSP_IPC_OUT_OF_MIPS removed)"}, + {12, "Required resource is in invalid state"}, + {13, "Requested power transition failed to complete"}, + {14, "Manifest of the library being loaded is invalid"}, + {15, "Requested service or data is unavailable on the target platform"}, + {42, "Library target address is out of storage memory range"}, + {43, "Reserved"}, + {44, "Image verification by CSE failed"}, + {100, "General module management error"}, + {101, "Module loading failed"}, + {102, "Integrity check of the loaded module content failed"}, + {103, "Attempt to unload code of the module in use"}, + {104, "Other failure of module instance initialization request"}, + {105, "Reserved (ADSP_IPC_OUT_OF_MIPS removed)"}, + {106, "Reserved (ADSP_IPC_CONFIG_GET_ERROR removed)"}, + {107, "Reserved (ADSP_IPC_CONFIG_SET_ERROR removed)"}, + {108, "Reserved (ADSP_IPC_LARGE_CONFIG_GET_ERROR removed)"}, + {109, "Reserved (ADSP_IPC_LARGE_CONFIG_SET_ERROR removed)"}, + {110, "Invalid (out of range) module ID provided"}, + {111, "Invalid module instance ID provided"}, + {112, "Invalid queue (pin) ID provided"}, + {113, "Invalid destination queue (pin) ID provided"}, + {114, "Reserved (ADSP_IPC_BIND_UNBIND_DST_SINK_UNSUPPORTED removed)"}, + {115, "Reserved (ADSP_IPC_UNLOAD_INST_EXISTS removed)"}, + {116, "Invalid target code ID provided"}, + {117, "Injection DMA buffer is too small for probing the input pin"}, + {118, "Extraction DMA buffer is too small for probing the output pin"}, + {120, "Invalid ID of configuration item provided in TLV list"}, + {121, "Invalid length of configuration item provided in TLV list"}, + {122, "Invalid structure of configuration item provided"}, + {140, "Initialization of DMA Gateway failed"}, + {141, "Invalid ID of gateway provided"}, + {142, "Setting state of DMA Gateway failed"}, + {143, "DMA_CONTROL message targeting gateway not allocated yet"}, + {150, "Attempt to configure SCLK while I2S port is running"}, + {151, "Attempt to configure MCLK while I2S port is running"}, + {152, "Attempt to stop SCLK that is not running"}, + {153, "Attempt to stop MCLK that is not running"}, + {160, "Reserved (ADSP_IPC_PIPELINE_NOT_INITIALIZED removed)"}, + {161, "Reserved (ADSP_IPC_PIPELINE_NOT_EXIST removed)"}, + {162, "Reserved (ADSP_IPC_PIPELINE_SAVE_FAILED removed)"}, + {163, "Reserved (ADSP_IPC_PIPELINE_RESTORE_FAILED removed)"}, + {165, "Reserved (ADSP_IPC_PIPELINE_ALREADY_EXISTS removed)"}, +}; + +static int sof_ipc4_check_reply_status(struct snd_sof_dev *sdev, u32 status) +{ + int i, ret; + + status &= SOF_IPC4_REPLY_STATUS; + + if (!status) + return 0; + + for (i = 0; i < ARRAY_SIZE(ipc4_status); i++) { + if (ipc4_status[i].status == status) { + dev_err(sdev->dev, "FW reported error: %u - %s\n", + status, ipc4_status[i].msg); + goto to_errno; + } + } + + if (i == ARRAY_SIZE(ipc4_status)) + dev_err(sdev->dev, "FW reported error: %u - Unknown\n", status); + +to_errno: + switch (status) { + case 8: + case 11: + case 105 ... 109: + case 114 ... 115: + case 160 ... 163: + case 165: + ret = -ENOENT; + break; + case 4: + case 150: + case 151: + ret = -EBUSY; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_VERBOSE_IPC) +#define DBG_IPC4_MSG_TYPE_ENTRY(type) [SOF_IPC4_##type] = #type +static const char * const ipc4_dbg_mod_msg_type[] = { + DBG_IPC4_MSG_TYPE_ENTRY(MOD_INIT_INSTANCE), + DBG_IPC4_MSG_TYPE_ENTRY(MOD_CONFIG_GET), + DBG_IPC4_MSG_TYPE_ENTRY(MOD_CONFIG_SET), + DBG_IPC4_MSG_TYPE_ENTRY(MOD_LARGE_CONFIG_GET), + DBG_IPC4_MSG_TYPE_ENTRY(MOD_LARGE_CONFIG_SET), + DBG_IPC4_MSG_TYPE_ENTRY(MOD_BIND), + DBG_IPC4_MSG_TYPE_ENTRY(MOD_UNBIND), + DBG_IPC4_MSG_TYPE_ENTRY(MOD_SET_DX), + DBG_IPC4_MSG_TYPE_ENTRY(MOD_SET_D0IX), + DBG_IPC4_MSG_TYPE_ENTRY(MOD_ENTER_MODULE_RESTORE), + DBG_IPC4_MSG_TYPE_ENTRY(MOD_EXIT_MODULE_RESTORE), + DBG_IPC4_MSG_TYPE_ENTRY(MOD_DELETE_INSTANCE), +}; + +static const char * const ipc4_dbg_glb_msg_type[] = { + DBG_IPC4_MSG_TYPE_ENTRY(GLB_BOOT_CONFIG), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_ROM_CONTROL), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_IPCGATEWAY_CMD), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_PERF_MEASUREMENTS_CMD), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_CHAIN_DMA), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_LOAD_MULTIPLE_MODULES), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_UNLOAD_MULTIPLE_MODULES), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_CREATE_PIPELINE), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_DELETE_PIPELINE), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_SET_PIPELINE_STATE), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_GET_PIPELINE_STATE), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_GET_PIPELINE_CONTEXT_SIZE), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_SAVE_PIPELINE), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_RESTORE_PIPELINE), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_LOAD_LIBRARY), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_INTERNAL_MESSAGE), + DBG_IPC4_MSG_TYPE_ENTRY(GLB_NOTIFICATION), +}; + +#define DBG_IPC4_NOTIFICATION_TYPE_ENTRY(type) [SOF_IPC4_NOTIFY_##type] = #type +static const char * const ipc4_dbg_notification_type[] = { + DBG_IPC4_NOTIFICATION_TYPE_ENTRY(PHRASE_DETECTED), + DBG_IPC4_NOTIFICATION_TYPE_ENTRY(RESOURCE_EVENT), + DBG_IPC4_NOTIFICATION_TYPE_ENTRY(LOG_BUFFER_STATUS), + DBG_IPC4_NOTIFICATION_TYPE_ENTRY(TIMESTAMP_CAPTURED), + DBG_IPC4_NOTIFICATION_TYPE_ENTRY(FW_READY), + DBG_IPC4_NOTIFICATION_TYPE_ENTRY(FW_AUD_CLASS_RESULT), + DBG_IPC4_NOTIFICATION_TYPE_ENTRY(EXCEPTION_CAUGHT), + DBG_IPC4_NOTIFICATION_TYPE_ENTRY(MODULE_NOTIFICATION), + DBG_IPC4_NOTIFICATION_TYPE_ENTRY(PROBE_DATA_AVAILABLE), + DBG_IPC4_NOTIFICATION_TYPE_ENTRY(ASYNC_MSG_SRVC_MESSAGE), +}; + +static void sof_ipc4_log_header(struct device *dev, u8 *text, struct sof_ipc4_msg *msg, + bool data_size_valid) +{ + u32 val, type; + const u8 *str2 = NULL; + const u8 *str = NULL; + + val = msg->primary & SOF_IPC4_MSG_TARGET_MASK; + type = SOF_IPC4_MSG_TYPE_GET(msg->primary); + + if (val == SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG)) { + /* Module message */ + if (type < SOF_IPC4_MOD_TYPE_LAST) + str = ipc4_dbg_mod_msg_type[type]; + if (!str) + str = "Unknown Module message type"; + } else { + /* Global FW message */ + if (type < SOF_IPC4_GLB_TYPE_LAST) + str = ipc4_dbg_glb_msg_type[type]; + if (!str) + str = "Unknown Global message type"; + + if (type == SOF_IPC4_GLB_NOTIFICATION) { + /* Notification message */ + u32 notif = SOF_IPC4_NOTIFICATION_TYPE_GET(msg->primary); + + /* Do not print log buffer notification if not desired */ + if (notif == SOF_IPC4_NOTIFY_LOG_BUFFER_STATUS && + !sof_debug_check_flag(SOF_DBG_PRINT_DMA_POSITION_UPDATE_LOGS)) + return; + + if (notif < SOF_IPC4_NOTIFY_TYPE_LAST) + str2 = ipc4_dbg_notification_type[notif]; + if (!str2) + str2 = "Unknown Global notification"; + } + } + + if (str2) { + if (data_size_valid && msg->data_size) + dev_dbg(dev, "%s: %#x|%#x: %s|%s [data size: %zu]\n", + text, msg->primary, msg->extension, str, str2, + msg->data_size); + else + dev_dbg(dev, "%s: %#x|%#x: %s|%s\n", text, msg->primary, + msg->extension, str, str2); + } else { + if (data_size_valid && msg->data_size) + dev_dbg(dev, "%s: %#x|%#x: %s [data size: %zu]\n", + text, msg->primary, msg->extension, str, + msg->data_size); + else + dev_dbg(dev, "%s: %#x|%#x: %s\n", text, msg->primary, + msg->extension, str); + } +} +#else /* CONFIG_SND_SOC_SOF_DEBUG_VERBOSE_IPC */ +static void sof_ipc4_log_header(struct device *dev, u8 *text, struct sof_ipc4_msg *msg, + bool data_size_valid) +{ + /* Do not print log buffer notification if not desired */ + if (!sof_debug_check_flag(SOF_DBG_PRINT_DMA_POSITION_UPDATE_LOGS) && + !SOF_IPC4_MSG_IS_MODULE_MSG(msg->primary) && + SOF_IPC4_MSG_TYPE_GET(msg->primary) == SOF_IPC4_GLB_NOTIFICATION && + SOF_IPC4_NOTIFICATION_TYPE_GET(msg->primary) == SOF_IPC4_NOTIFY_LOG_BUFFER_STATUS) + return; + + if (data_size_valid && msg->data_size) + dev_dbg(dev, "%s: %#x|%#x [data size: %zu]\n", text, + msg->primary, msg->extension, msg->data_size); + else + dev_dbg(dev, "%s: %#x|%#x\n", text, msg->primary, msg->extension); +} +#endif + +static int sof_ipc4_get_reply(struct snd_sof_dev *sdev) +{ + struct snd_sof_ipc_msg *msg = sdev->msg; + struct sof_ipc4_msg *ipc4_reply; + int ret; + + /* get the generic reply */ + ipc4_reply = msg->reply_data; + + sof_ipc4_log_header(sdev->dev, "ipc tx reply", ipc4_reply, false); + + ret = sof_ipc4_check_reply_status(sdev, ipc4_reply->primary); + if (ret) + return ret; + + /* No other information is expected for non large config get replies */ + if (!msg->reply_size || !SOF_IPC4_MSG_IS_MODULE_MSG(ipc4_reply->primary) || + (SOF_IPC4_MSG_TYPE_GET(ipc4_reply->primary) != SOF_IPC4_MOD_LARGE_CONFIG_GET)) + return 0; + + /* Read the requested payload */ + snd_sof_dsp_mailbox_read(sdev, sdev->dsp_box.offset, ipc4_reply->data_ptr, + msg->reply_size); + + return 0; +} + +/* wait for IPC message reply */ +static int ipc4_wait_tx_done(struct snd_sof_ipc *ipc, void *reply_data) +{ + struct snd_sof_ipc_msg *msg = &ipc->msg; + struct sof_ipc4_msg *ipc4_msg = msg->msg_data; + struct snd_sof_dev *sdev = ipc->sdev; + int ret; + + /* wait for DSP IPC completion */ + ret = wait_event_timeout(msg->waitq, msg->ipc_complete, + msecs_to_jiffies(sdev->ipc_timeout)); + if (ret == 0) { + dev_err(sdev->dev, "ipc timed out for %#x|%#x\n", + ipc4_msg->primary, ipc4_msg->extension); + snd_sof_handle_fw_exception(ipc->sdev, "IPC timeout"); + return -ETIMEDOUT; + } + + if (msg->reply_error) { + dev_err(sdev->dev, "ipc error for msg %#x|%#x\n", + ipc4_msg->primary, ipc4_msg->extension); + ret = msg->reply_error; + } else { + if (reply_data) { + struct sof_ipc4_msg *ipc4_reply = msg->reply_data; + struct sof_ipc4_msg *ipc4_reply_data = reply_data; + + /* Copy the header */ + ipc4_reply_data->header_u64 = ipc4_reply->header_u64; + if (msg->reply_size && ipc4_reply_data->data_ptr) { + /* copy the payload returned from DSP */ + memcpy(ipc4_reply_data->data_ptr, ipc4_reply->data_ptr, + msg->reply_size); + ipc4_reply_data->data_size = msg->reply_size; + } + } + + ret = 0; + sof_ipc4_log_header(sdev->dev, "ipc tx done ", ipc4_msg, true); + } + + /* re-enable dumps after successful IPC tx */ + if (sdev->ipc_dump_printed) { + sdev->dbg_dump_printed = false; + sdev->ipc_dump_printed = false; + } + + return ret; +} + +static int ipc4_tx_msg_unlocked(struct snd_sof_ipc *ipc, + void *msg_data, size_t msg_bytes, + void *reply_data, size_t reply_bytes) +{ + struct sof_ipc4_msg *ipc4_msg = msg_data; + struct snd_sof_dev *sdev = ipc->sdev; + int ret; + + if (msg_bytes > ipc->max_payload_size || reply_bytes > ipc->max_payload_size) + return -EINVAL; + + ret = sof_ipc_send_msg(sdev, msg_data, msg_bytes, reply_bytes); + if (ret) { + dev_err_ratelimited(sdev->dev, + "%s: ipc message send for %#x|%#x failed: %d\n", + __func__, ipc4_msg->primary, ipc4_msg->extension, ret); + return ret; + } + + sof_ipc4_log_header(sdev->dev, "ipc tx ", msg_data, true); + + /* now wait for completion */ + return ipc4_wait_tx_done(ipc, reply_data); +} + +static int sof_ipc4_tx_msg(struct snd_sof_dev *sdev, void *msg_data, size_t msg_bytes, + void *reply_data, size_t reply_bytes, bool no_pm) +{ + struct snd_sof_ipc *ipc = sdev->ipc; +#ifdef DEBUG_VERBOSE + struct sof_ipc4_msg *msg = NULL; +#endif + int ret; + + if (!msg_data) + return -EINVAL; + + /* Serialise IPC TX */ + mutex_lock(&ipc->tx_mutex); + + ret = ipc4_tx_msg_unlocked(ipc, msg_data, msg_bytes, reply_data, reply_bytes); + + mutex_unlock(&ipc->tx_mutex); + +#ifdef DEBUG_VERBOSE + /* payload is indicated by non zero msg/reply_bytes */ + if (msg_bytes) + msg = msg_data; + else if (reply_bytes) + msg = reply_data; + + if (msg) + sof_ipc4_dump_payload(sdev, msg->data_ptr, msg->data_size); +#endif + + return ret; +} + +static int sof_ipc4_set_get_data(struct snd_sof_dev *sdev, void *data, + size_t payload_bytes, bool set) +{ + size_t payload_limit = sdev->ipc->max_payload_size; + struct sof_ipc4_msg *ipc4_msg = data; + struct sof_ipc4_msg tx = {{ 0 }}; + struct sof_ipc4_msg rx = {{ 0 }}; + size_t remaining = payload_bytes; + size_t offset = 0; + size_t chunk_size; + int ret; + + if (!data) + return -EINVAL; + + if ((ipc4_msg->primary & SOF_IPC4_MSG_TARGET_MASK) != + SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG)) + return -EINVAL; + + ipc4_msg->primary &= ~SOF_IPC4_MSG_TYPE_MASK; + tx.primary = ipc4_msg->primary; + tx.extension = ipc4_msg->extension; + + if (set) + tx.primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_LARGE_CONFIG_SET); + else + tx.primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_LARGE_CONFIG_GET); + + tx.extension &= ~SOF_IPC4_MOD_EXT_MSG_SIZE_MASK; + tx.extension |= SOF_IPC4_MOD_EXT_MSG_SIZE(payload_bytes); + + tx.extension |= SOF_IPC4_MOD_EXT_MSG_FIRST_BLOCK(1); + + /* Serialise IPC TX */ + mutex_lock(&sdev->ipc->tx_mutex); + + do { + size_t tx_size, rx_size; + + if (remaining > payload_limit) { + chunk_size = payload_limit; + } else { + chunk_size = remaining; + if (set) + tx.extension |= SOF_IPC4_MOD_EXT_MSG_LAST_BLOCK(1); + } + + if (offset) { + tx.extension &= ~SOF_IPC4_MOD_EXT_MSG_FIRST_BLOCK_MASK; + tx.extension &= ~SOF_IPC4_MOD_EXT_MSG_SIZE_MASK; + tx.extension |= SOF_IPC4_MOD_EXT_MSG_SIZE(offset); + } + + if (set) { + tx.data_size = chunk_size; + tx.data_ptr = ipc4_msg->data_ptr + offset; + + tx_size = chunk_size; + rx_size = 0; + } else { + rx.primary = 0; + rx.extension = 0; + rx.data_size = chunk_size; + rx.data_ptr = ipc4_msg->data_ptr + offset; + + tx_size = 0; + rx_size = chunk_size; + } + + /* Send the message for the current chunk */ + ret = ipc4_tx_msg_unlocked(sdev->ipc, &tx, tx_size, &rx, rx_size); + if (ret < 0) { + dev_err(sdev->dev, + "%s: large config %s failed at offset %zu: %d\n", + __func__, set ? "set" : "get", offset, ret); + goto out; + } + + if (!set && rx.extension & SOF_IPC4_MOD_EXT_MSG_FIRST_BLOCK_MASK) { + /* Verify the firmware reported total payload size */ + rx_size = rx.extension & SOF_IPC4_MOD_EXT_MSG_SIZE_MASK; + + if (rx_size > payload_bytes) { + dev_err(sdev->dev, + "%s: Receive buffer (%zu) is too small for %zu\n", + __func__, payload_bytes, rx_size); + ret = -ENOMEM; + goto out; + } + + if (rx_size < chunk_size) { + chunk_size = rx_size; + remaining = rx_size; + } else if (rx_size < payload_bytes) { + remaining = rx_size; + } + } + + offset += chunk_size; + remaining -= chunk_size; + } while (remaining); + + /* Adjust the received data size if needed */ + if (!set && payload_bytes != offset) + ipc4_msg->data_size = offset; + + sof_ipc4_dump_payload(sdev, ipc4_msg->data_ptr, ipc4_msg->data_size); + +out: + mutex_unlock(&sdev->ipc->tx_mutex); + + return ret; +} + +static int sof_ipc4_init_msg_memory(struct snd_sof_dev *sdev) +{ + struct sof_ipc4_msg *ipc4_msg; + struct snd_sof_ipc_msg *msg = &sdev->ipc->msg; + + /* TODO: get max_payload_size from firmware */ + sdev->ipc->max_payload_size = SOF_IPC4_MSG_MAX_SIZE; + + /* Allocate memory for the ipc4 container and the maximum payload */ + msg->reply_data = devm_kzalloc(sdev->dev, sdev->ipc->max_payload_size + + sizeof(struct sof_ipc4_msg), GFP_KERNEL); + if (!msg->reply_data) + return -ENOMEM; + + ipc4_msg = msg->reply_data; + ipc4_msg->data_ptr = msg->reply_data + sizeof(struct sof_ipc4_msg); + + return 0; +} + +static int ipc4_fw_ready(struct snd_sof_dev *sdev, struct sof_ipc4_msg *ipc4_msg) +{ + int inbox_offset, inbox_size, outbox_offset, outbox_size; + + /* no need to re-check version/ABI for subsequent boots */ + if (!sdev->first_boot) + return 0; + + /* Set up the windows for IPC communication */ + inbox_offset = snd_sof_dsp_get_mailbox_offset(sdev); + if (inbox_offset < 0) { + dev_err(sdev->dev, "%s: No mailbox offset\n", __func__); + return inbox_offset; + } + inbox_size = SOF_IPC4_MSG_MAX_SIZE; + outbox_offset = snd_sof_dsp_get_window_offset(sdev, SOF_IPC4_OUTBOX_WINDOW_IDX); + outbox_size = SOF_IPC4_MSG_MAX_SIZE; + + sdev->dsp_box.offset = inbox_offset; + sdev->dsp_box.size = inbox_size; + sdev->host_box.offset = outbox_offset; + sdev->host_box.size = outbox_size; + + sdev->debug_box.offset = snd_sof_dsp_get_window_offset(sdev, + SOF_IPC4_DEBUG_WINDOW_IDX); + + 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, "debug box 0x%x\n", sdev->debug_box.offset); + + return sof_ipc4_init_msg_memory(sdev); +} + +static void sof_ipc4_rx_msg(struct snd_sof_dev *sdev) +{ + struct sof_ipc4_msg *ipc4_msg = sdev->ipc->msg.rx_data; + size_t data_size = 0; + int err; + + if (!ipc4_msg || !SOF_IPC4_MSG_IS_NOTIFICATION(ipc4_msg->primary)) + return; + + ipc4_msg->data_ptr = NULL; + ipc4_msg->data_size = 0; + + sof_ipc4_log_header(sdev->dev, "ipc rx ", ipc4_msg, false); + + switch (SOF_IPC4_NOTIFICATION_TYPE_GET(ipc4_msg->primary)) { + case SOF_IPC4_NOTIFY_FW_READY: + /* check for FW boot completion */ + if (sdev->fw_state == SOF_FW_BOOT_IN_PROGRESS) { + err = ipc4_fw_ready(sdev, ipc4_msg); + if (err < 0) + sof_set_fw_state(sdev, SOF_FW_BOOT_READY_FAILED); + else + sof_set_fw_state(sdev, SOF_FW_BOOT_READY_OK); + + /* wake up firmware loader */ + wake_up(&sdev->boot_wait); + } + + break; + case SOF_IPC4_NOTIFY_RESOURCE_EVENT: + data_size = sizeof(struct sof_ipc4_notify_resource_data); + break; + case SOF_IPC4_NOTIFY_LOG_BUFFER_STATUS: + sof_ipc4_mtrace_update_pos(sdev, SOF_IPC4_LOG_CORE_GET(ipc4_msg->primary)); + break; + default: + dev_dbg(sdev->dev, "Unhandled DSP message: %#x|%#x\n", + ipc4_msg->primary, ipc4_msg->extension); + break; + } + + if (data_size) { + ipc4_msg->data_ptr = kmalloc(data_size, GFP_KERNEL); + if (!ipc4_msg->data_ptr) + return; + + ipc4_msg->data_size = data_size; + snd_sof_ipc_msg_data(sdev, NULL, ipc4_msg->data_ptr, ipc4_msg->data_size); + } + + sof_ipc4_log_header(sdev->dev, "ipc rx done ", ipc4_msg, true); + + if (data_size) { + kfree(ipc4_msg->data_ptr); + ipc4_msg->data_ptr = NULL; + ipc4_msg->data_size = 0; + } +} + +static int sof_ipc4_set_core_state(struct snd_sof_dev *sdev, int core_idx, bool on) +{ + struct sof_ipc4_dx_state_info dx_state; + struct sof_ipc4_msg msg; + + dx_state.core_mask = BIT(core_idx); + if (on) + dx_state.dx_mask = BIT(core_idx); + else + dx_state.dx_mask = 0; + + msg.primary = SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_SET_DX); + msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST); + msg.primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG); + msg.extension = 0; + msg.data_ptr = &dx_state; + msg.data_size = sizeof(dx_state); + + return sof_ipc4_tx_msg(sdev, &msg, msg.data_size, NULL, 0, false); +} + +/* + * The context save callback is used to send a message to the firmware notifying + * it that the primary core is going to be turned off, which is used as an + * indication to prepare for a full power down, thus preparing for IMR boot + * (when supported) + * + * Note: in IPC4 there is no message used to restore context, thus no context + * restore callback is implemented + */ +static int sof_ipc4_ctx_save(struct snd_sof_dev *sdev) +{ + return sof_ipc4_set_core_state(sdev, SOF_DSP_PRIMARY_CORE, false); +} + +static const struct sof_ipc_pm_ops ipc4_pm_ops = { + .ctx_save = sof_ipc4_ctx_save, + .set_core_state = sof_ipc4_set_core_state, +}; + +const struct sof_ipc_ops ipc4_ops = { + .tx_msg = sof_ipc4_tx_msg, + .rx_msg = sof_ipc4_rx_msg, + .set_get_data = sof_ipc4_set_get_data, + .get_reply = sof_ipc4_get_reply, + .pm = &ipc4_pm_ops, + .fw_loader = &ipc4_loader_ops, + .tplg = &ipc4_tplg_ops, + .pcm = &ipc4_pcm_ops, + .fw_tracing = &ipc4_mtrace_ops, +}; diff --git a/sound/soc/sof/loader.c b/sound/soc/sof/loader.c index c04646647637..5f51d936b306 100644 --- a/sound/soc/sof/loader.c +++ b/sound/soc/sof/loader.c @@ -11,690 +11,9 @@ // #include <linux/firmware.h> -#include <sound/sof.h> -#include <sound/sof/ext_manifest.h> #include "sof-priv.h" #include "ops.h" -static int get_ext_windows(struct snd_sof_dev *sdev, - const struct sof_ipc_ext_data_hdr *ext_hdr) -{ - const struct sof_ipc_window *w = - container_of(ext_hdr, struct sof_ipc_window, ext_hdr); - - 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 = 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 */ -static int snd_sof_fw_parse_ext_data(struct snd_sof_dev *sdev, u32 offset) -{ - struct sof_ipc_ext_data_hdr *ext_hdr; - void *ext_data; - int ret = 0; - - ext_data = kzalloc(PAGE_SIZE, GFP_KERNEL); - if (!ext_data) - return -ENOMEM; - - /* get first header */ - snd_sof_dsp_block_read(sdev, SOF_FW_BLK_TYPE_SRAM, offset, ext_data, - sizeof(*ext_hdr)); - ext_hdr = ext_data; - - while (ext_hdr->hdr.cmd == SOF_IPC_FW_READY) { - /* read in ext structure */ - snd_sof_dsp_block_read(sdev, SOF_FW_BLK_TYPE_SRAM, - offset + sizeof(*ext_hdr), - (void *)((u8 *)ext_data + sizeof(*ext_hdr)), - ext_hdr->hdr.size - sizeof(*ext_hdr)); - - dev_dbg(sdev->dev, "found ext header type %d size 0x%x\n", - ext_hdr->type, ext_hdr->hdr.size); - - /* process structure data */ - switch (ext_hdr->type) { - case SOF_IPC_EXT_WINDOW: - ret = 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_info(sdev->dev, "unknown ext header type %d size 0x%x\n", - ext_hdr->type, ext_hdr->hdr.size); - ret = 0; - break; - } - - if (ret < 0) { - dev_err(sdev->dev, "error: failed to parse ext data type %d\n", - ext_hdr->type); - break; - } - - /* move to next header */ - offset += ext_hdr->hdr.size; - snd_sof_dsp_block_read(sdev, SOF_FW_BLK_TYPE_SRAM, offset, ext_data, - sizeof(*ext_hdr)); - ext_hdr = ext_data; - } - - kfree(ext_data); - return ret; -} - -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 int ext_man_get_config_data(struct snd_sof_dev *sdev, - const struct sof_ext_man_elem_header *hdr) -{ - const struct sof_ext_man_config_data *config = - container_of(hdr, struct sof_ext_man_config_data, hdr); - const struct sof_config_elem *elem; - int elems_counter; - int elems_size; - int ret = 0; - int i; - - /* calculate elements counter */ - elems_size = config->hdr.size - sizeof(struct sof_ext_man_elem_header); - elems_counter = elems_size / sizeof(struct sof_config_elem); - - dev_dbg(sdev->dev, "%s can hold up to %d config elements\n", - __func__, elems_counter); - - for (i = 0; i < elems_counter; ++i) { - elem = &config->elems[i]; - dev_dbg(sdev->dev, "%s get index %d token %d val %d\n", - __func__, i, elem->token, elem->value); - switch (elem->token) { - case SOF_EXT_MAN_CONFIG_EMPTY: - /* unused memory space is zero filled - mapped to EMPTY elements */ - break; - case SOF_EXT_MAN_CONFIG_IPC_MSG_SIZE: - /* TODO: use ipc msg size from config data */ - break; - case SOF_EXT_MAN_CONFIG_MEMORY_USAGE_SCAN: - if (sdev->first_boot && elem->value) - ret = snd_sof_dbg_memory_info_init(sdev); - break; - default: - dev_info(sdev->dev, "Unknown firmware configuration token %d value %d", - elem->token, elem->value); - break; - } - if (ret < 0) { - dev_err(sdev->dev, "error: processing sof_ext_man_config_data failed for token %d value 0x%x, %d\n", - elem->token, elem->value, ret); - return ret; - } - } - - 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; - case SOF_EXT_MAN_ELEM_CONFIG_DATA: - ret = ext_man_get_config_data(sdev, elem_hdr); - break; - case SOF_EXT_MAN_ELEM_PLATFORM_CONFIG_DATA: - ret = snd_sof_dsp_parse_platform_ext_manifest(sdev, elem_hdr); - break; - default: - dev_info(sdev->dev, "unknown sof_ext_man header type %d size 0x%X\n", - elem_hdr->type, elem_hdr->size); - break; - } - - if (ret < 0) { - dev_err(sdev->dev, "error: failed to parse sof_ext_man header type %d size 0x%X\n", - elem_hdr->type, elem_hdr->size); - return ret; - } - - remaining -= elem_hdr->size; - iptr += elem_hdr->size; - } - - if (remaining) { - dev_err(sdev->dev, "error: sof_ext_man header is inconsistent\n"); - return -EINVAL; - } - - return ext_man_size; -} - -/* - * IPC Firmware ready. - */ -static void sof_get_windows(struct snd_sof_dev *sdev) -{ - struct sof_ipc_window_elem *elem; - u32 outbox_offset = 0; - u32 stream_offset = 0; - u32 inbox_offset = 0; - u32 outbox_size = 0; - u32 stream_size = 0; - u32 inbox_size = 0; - u32 debug_size = 0; - u32 debug_offset = 0; - int window_offset; - int i; - - if (!sdev->info_window) { - dev_err(sdev->dev, "error: have no window info\n"); - return; - } - - for (i = 0; i < sdev->info_window->num_windows; i++) { - elem = &sdev->info_window->window[i]; - - window_offset = snd_sof_dsp_get_window_offset(sdev, elem->id); - if (window_offset < 0) { - dev_warn(sdev->dev, "warn: no offset for window %d\n", - elem->id); - continue; - } - - switch (elem->type) { - case SOF_IPC_REGION_UPBOX: - inbox_offset = window_offset + elem->offset; - inbox_size = elem->size; - snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, - inbox_offset, - elem->size, "inbox", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_DOWNBOX: - outbox_offset = window_offset + elem->offset; - outbox_size = elem->size; - snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, - outbox_offset, - elem->size, "outbox", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_TRACE: - snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, - window_offset + elem->offset, - elem->size, "etrace", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_DEBUG: - debug_offset = window_offset + elem->offset; - debug_size = elem->size; - snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, - window_offset + elem->offset, - elem->size, "debug", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_STREAM: - stream_offset = window_offset + elem->offset; - stream_size = elem->size; - snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, - stream_offset, - elem->size, "stream", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_REGS: - snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, - window_offset + elem->offset, - elem->size, "regs", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - case SOF_IPC_REGION_EXCEPTION: - sdev->dsp_oops_offset = window_offset + elem->offset; - snd_sof_debugfs_add_region_item(sdev, SOF_FW_BLK_TYPE_SRAM, - window_offset + elem->offset, - elem->size, "exception", - SOF_DEBUGFS_ACCESS_D0_ONLY); - break; - default: - dev_err(sdev->dev, "error: get illegal window info\n"); - return; - } - } - - if (outbox_size == 0 || inbox_size == 0) { - dev_err(sdev->dev, "error: get illegal mailbox window\n"); - return; - } - - sdev->dsp_box.offset = inbox_offset; - sdev->dsp_box.size = inbox_size; - - sdev->host_box.offset = outbox_offset; - sdev->host_box.size = outbox_size; - - sdev->stream_box.offset = stream_offset; - sdev->stream_box.size = stream_size; - - sdev->debug_box.offset = debug_offset; - sdev->debug_box.size = debug_size; - - dev_dbg(sdev->dev, " mailbox upstream 0x%x - size 0x%x\n", - inbox_offset, inbox_size); - dev_dbg(sdev->dev, " mailbox downstream 0x%x - size 0x%x\n", - outbox_offset, outbox_size); - dev_dbg(sdev->dev, " stream region 0x%x - size 0x%x\n", - stream_offset, stream_size); - dev_dbg(sdev->dev, " debug region 0x%x - size 0x%x\n", - debug_offset, debug_size); -} - -/* check for ABI compatibility and create memory windows on first boot */ -int sof_fw_ready(struct snd_sof_dev *sdev, u32 msg_id) -{ - struct sof_ipc_fw_ready *fw_ready = &sdev->fw_ready; - int offset; - int ret; - - /* mailbox must be on 4k boundary */ - offset = snd_sof_dsp_get_mailbox_offset(sdev); - if (offset < 0) { - dev_err(sdev->dev, "error: have no mailbox offset\n"); - return offset; - } - - dev_dbg(sdev->dev, "ipc: DSP is ready 0x%8.8x offset 0x%x\n", - msg_id, offset); - - /* no need to re-check version/ABI for subsequent boots */ - if (!sdev->first_boot) - return 0; - - /* - * copy data from the DSP FW ready offset - * Subsequent error handling is not needed for BLK_TYPE_SRAM - */ - ret = snd_sof_dsp_block_read(sdev, SOF_FW_BLK_TYPE_SRAM, offset, fw_ready, - sizeof(*fw_ready)); - if (ret) { - dev_err(sdev->dev, - "error: unable to read fw_ready, read from TYPE_SRAM failed\n"); - return ret; - } - - /* make sure ABI version is compatible */ - ret = snd_sof_ipc_valid(sdev); - if (ret < 0) - return ret; - - /* now check for extended data */ - snd_sof_fw_parse_ext_data(sdev, offset + sizeof(struct sof_ipc_fw_ready)); - - sof_get_windows(sdev); - - return sof_ipc_init_msg_memory(sdev); -} -EXPORT_SYMBOL(sof_fw_ready); - -/* generic module parser for mmaped DSPs */ -int snd_sof_parse_module_memcpy(struct snd_sof_dev *sdev, - struct snd_sof_mod_hdr *module) -{ - struct snd_sof_blk_hdr *block; - int count, ret; - u32 offset; - size_t remaining; - - dev_dbg(sdev->dev, "new module size 0x%x blocks 0x%x type 0x%x\n", - module->size, module->num_blocks, module->type); - - block = (struct snd_sof_blk_hdr *)((u8 *)module + sizeof(*module)); - - /* module->size doesn't include header size */ - remaining = module->size; - for (count = 0; count < module->num_blocks; count++) { - /* check for wrap */ - if (remaining < sizeof(*block)) { - dev_err(sdev->dev, "error: not enough data remaining\n"); - return -EINVAL; - } - - /* minus header size of block */ - remaining -= sizeof(*block); - - if (block->size == 0) { - dev_warn(sdev->dev, - "warning: block %d size zero\n", count); - dev_warn(sdev->dev, " type 0x%x offset 0x%x\n", - block->type, block->offset); - continue; - } - - switch (block->type) { - case SOF_FW_BLK_TYPE_RSRVD0: - case SOF_FW_BLK_TYPE_ROM...SOF_FW_BLK_TYPE_RSRVD14: - continue; /* not handled atm */ - case SOF_FW_BLK_TYPE_IRAM: - case SOF_FW_BLK_TYPE_DRAM: - case SOF_FW_BLK_TYPE_SRAM: - offset = block->offset; - break; - default: - dev_err(sdev->dev, "error: bad type 0x%x for block 0x%x\n", - block->type, count); - return -EINVAL; - } - - dev_dbg(sdev->dev, - "block %d type 0x%x size 0x%x ==> offset 0x%x\n", - count, block->type, block->size, offset); - - /* checking block->size to avoid unaligned access */ - if (block->size % sizeof(u32)) { - dev_err(sdev->dev, "error: invalid block size 0x%x\n", - block->size); - return -EINVAL; - } - ret = snd_sof_dsp_block_write(sdev, block->type, offset, - block + 1, block->size); - if (ret < 0) { - dev_err(sdev->dev, "error: write to block type 0x%x failed\n", - block->type); - return ret; - } - - if (remaining < block->size) { - dev_err(sdev->dev, "error: not enough data remaining\n"); - return -EINVAL; - } - - /* minus body size of block */ - remaining -= block->size; - /* next block */ - block = (struct snd_sof_blk_hdr *)((u8 *)block + sizeof(*block) - + block->size); - } - - return 0; -} -EXPORT_SYMBOL(snd_sof_parse_module_memcpy); - -static int check_header(struct snd_sof_dev *sdev, const struct firmware *fw, - size_t fw_offset) -{ - struct snd_sof_fw_header *header; - size_t fw_size = fw->size - fw_offset; - - if (fw->size <= fw_offset) { - dev_err(sdev->dev, "error: firmware size must be greater than firmware offset\n"); - return -EINVAL; - } - - /* Read the header information from the data pointer */ - header = (struct snd_sof_fw_header *)(fw->data + fw_offset); - - /* verify FW sig */ - if (strncmp(header->sig, SND_SOF_FW_SIG, SND_SOF_FW_SIG_SIZE) != 0) { - dev_err(sdev->dev, "error: invalid firmware signature\n"); - return -EINVAL; - } - - /* check size is valid */ - if (fw_size != header->file_size + sizeof(*header)) { - dev_err(sdev->dev, "error: invalid filesize mismatch got 0x%zx expected 0x%zx\n", - fw_size, header->file_size + sizeof(*header)); - return -EINVAL; - } - - dev_dbg(sdev->dev, "header size=0x%x modules=0x%x abi=0x%x size=%zu\n", - header->file_size, header->num_modules, - header->abi, sizeof(*header)); - - return 0; -} - -static int load_modules(struct snd_sof_dev *sdev, const struct firmware *fw, - size_t fw_offset) -{ - struct snd_sof_fw_header *header; - struct snd_sof_mod_hdr *module; - int (*load_module)(struct snd_sof_dev *sof_dev, - struct snd_sof_mod_hdr *hdr); - int ret, count; - size_t remaining; - - header = (struct snd_sof_fw_header *)(fw->data + fw_offset); - load_module = sof_ops(sdev)->load_module; - if (!load_module) - return -EINVAL; - - /* parse each module */ - module = (struct snd_sof_mod_hdr *)(fw->data + fw_offset + - sizeof(*header)); - remaining = fw->size - sizeof(*header) - fw_offset; - /* check for wrap */ - if (remaining > fw->size) { - dev_err(sdev->dev, "error: fw size smaller than header size\n"); - return -EINVAL; - } - - for (count = 0; count < header->num_modules; count++) { - /* check for wrap */ - if (remaining < sizeof(*module)) { - dev_err(sdev->dev, "error: not enough data remaining\n"); - return -EINVAL; - } - - /* minus header size of module */ - remaining -= sizeof(*module); - - /* module */ - ret = load_module(sdev, module); - if (ret < 0) { - dev_err(sdev->dev, "error: invalid module %d\n", count); - return ret; - } - - if (remaining < module->size) { - dev_err(sdev->dev, "error: not enough data remaining\n"); - return -EINVAL; - } - - /* minus body size of module */ - remaining -= module->size; - module = (struct snd_sof_mod_hdr *)((u8 *)module - + sizeof(*module) + module->size); - } - - return 0; -} - int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev) { struct snd_sof_pdata *plat_data = sdev->pdata; @@ -726,7 +45,7 @@ int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev) } /* check for extended manifest */ - ext_man_size = snd_sof_fw_ext_man_parse(sdev, plat_data->fw); + ext_man_size = sdev->ipc->ops->fw_loader->parse_ext_manifest(sdev); if (ext_man_size > 0) { /* when no error occurred, drop extended manifest */ plat_data->fw_offset = ext_man_size; @@ -756,7 +75,7 @@ int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev) return ret; /* make sure the FW header and file is valid */ - ret = check_header(sdev, plat_data->fw, plat_data->fw_offset); + ret = sdev->ipc->ops->fw_loader->validate(sdev); if (ret < 0) { dev_err(sdev->dev, "error: invalid FW header\n"); goto error; @@ -770,10 +89,12 @@ int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev) } /* parse and load firmware modules to DSP */ - ret = load_modules(sdev, plat_data->fw, plat_data->fw_offset); - if (ret < 0) { - dev_err(sdev->dev, "error: invalid FW modules\n"); - goto error; + if (sdev->ipc->ops->fw_loader->load_fw_to_dsp) { + ret = sdev->ipc->ops->fw_loader->load_fw_to_dsp(sdev); + if (ret < 0) { + dev_err(sdev->dev, "Firmware loading failed\n"); + goto error; + } } return 0; @@ -820,8 +141,8 @@ int snd_sof_run_firmware(struct snd_sof_dev *sdev) /* boot the firmware on the DSP */ ret = snd_sof_dsp_run(sdev); if (ret < 0) { - dev_err(sdev->dev, "error: failed to start DSP\n"); - snd_sof_dsp_dbg_dump(sdev, SOF_DBG_DUMP_MBOX | SOF_DBG_DUMP_PCI); + snd_sof_dsp_dbg_dump(sdev, "Failed to start DSP", + SOF_DBG_DUMP_MBOX | SOF_DBG_DUMP_PCI); return ret; } @@ -835,16 +156,13 @@ int snd_sof_run_firmware(struct snd_sof_dev *sdev) sdev->fw_state > SOF_FW_BOOT_IN_PROGRESS, msecs_to_jiffies(sdev->boot_timeout)); if (ret == 0) { - dev_err(sdev->dev, "error: firmware boot failure\n"); - snd_sof_dsp_dbg_dump(sdev, SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX | + snd_sof_dsp_dbg_dump(sdev, "Firmware boot failure due to timeout", + SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX | SOF_DBG_DUMP_TEXT | SOF_DBG_DUMP_PCI); - sof_set_fw_state(sdev, SOF_FW_BOOT_FAILED); return -EIO; } - if (sdev->fw_state == SOF_FW_BOOT_COMPLETE) - dev_dbg(sdev->dev, "firmware boot complete\n"); - else + if (sdev->fw_state == SOF_FW_BOOT_READY_FAILED) return -EIO; /* FW boots but fw_ready op failed */ /* perform post fw run operations */ @@ -854,6 +172,12 @@ int snd_sof_run_firmware(struct snd_sof_dev *sdev) return ret; } + dev_dbg(sdev->dev, "firmware boot complete\n"); + sof_set_fw_state(sdev, SOF_FW_BOOT_COMPLETE); + + if (sdev->first_boot && sdev->ipc->ops->fw_loader->query_fw_configuration) + return sdev->ipc->ops->fw_loader->query_fw_configuration(sdev); + return 0; } EXPORT_SYMBOL(snd_sof_run_firmware); diff --git a/sound/soc/sof/mediatek/Kconfig b/sound/soc/sof/mediatek/Kconfig new file mode 100644 index 000000000000..4a2eddf6009a --- /dev/null +++ b/sound/soc/sof/mediatek/Kconfig @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) + +config SND_SOC_SOF_MTK_TOPLEVEL + bool "SOF support for MTK audio DSPs" + depends on ARM64 || COMPILE_TEST + depends on SND_SOC_SOF_OF + help + This adds support for Sound Open Firmware for Mediatek platforms. + It is top level for all mediatek platforms. + Say Y if you have such a device. + If unsure select "N". + +if SND_SOC_SOF_MTK_TOPLEVEL +config SND_SOC_SOF_MTK_COMMON + tristate + select SND_SOC_SOF_OF_DEV + select SND_SOC_SOF + select SND_SOC_SOF_IPC3 + select SND_SOC_SOF_XTENSA + select SND_SOC_SOF_COMPRESS + help + This option is not user-selectable but automagically handled by + 'select' statements at a higher level + +config SND_SOC_SOF_MT8186 + tristate "SOF support for MT8186 audio DSP" + select SND_SOC_SOF_MTK_COMMON + depends on MTK_ADSP_IPC + help + This adds support for Sound Open Firmware for Mediatek platforms + using the mt8186 processors. + Say Y if you have such a device. + If unsure select "N". + +config SND_SOC_SOF_MT8195 + tristate "SOF support for MT8195 audio DSP" + select SND_SOC_SOF_MTK_COMMON + depends on MTK_ADSP_IPC + help + This adds support for Sound Open Firmware for Mediatek platforms + using the mt8195 processors. + Say Y if you have such a device. + If unsure select "N". + +endif ## SND_SOC_SOF_MTK_TOPLEVEL diff --git a/sound/soc/sof/mediatek/Makefile b/sound/soc/sof/mediatek/Makefile new file mode 100644 index 000000000000..29c5afb2f3d6 --- /dev/null +++ b/sound/soc/sof/mediatek/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +obj-$(CONFIG_SND_SOC_SOF_MTK_COMMON) += mtk-adsp-common.o +obj-$(CONFIG_SND_SOC_SOF_MT8195) += mt8195/ +obj-$(CONFIG_SND_SOC_SOF_MT8186) += mt8186/ diff --git a/sound/soc/sof/mediatek/adsp_helper.h b/sound/soc/sof/mediatek/adsp_helper.h new file mode 100644 index 000000000000..d41e904e6614 --- /dev/null +++ b/sound/soc/sof/mediatek/adsp_helper.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * Copyright (c) 2021 MediaTek Corporation. All rights reserved. + */ + +#ifndef __MTK_ADSP_HELPER_H__ +#define __MTK_ADSP_HELPER_H__ + +#include <linux/firmware/mediatek/mtk-adsp-ipc.h> + +/* + * Global important adsp data structure. + */ +struct mtk_adsp_chip_info { + phys_addr_t pa_sram; + phys_addr_t pa_dram; /* adsp dram physical base */ + phys_addr_t pa_shared_dram; /* adsp dram physical base */ + phys_addr_t pa_cfgreg; + u32 sramsize; + u32 dramsize; + u32 cfgregsize; + u32 shared_size; + void __iomem *va_sram; /* corresponding to pa_sram */ + void __iomem *va_dram; /* corresponding to pa_dram */ + void __iomem *va_cfgreg; + void __iomem *shared_sram; /* part of va_sram */ + void __iomem *shared_dram; /* part of va_dram */ + phys_addr_t adsp_bootup_addr; + int dram_offset; /*dram offset between system and dsp view*/ + + phys_addr_t pa_secreg; + u32 secregsize; + void __iomem *va_secreg; + + phys_addr_t pa_busreg; + u32 busregsize; + void __iomem *va_busreg; +}; + +struct adsp_priv { + struct device *dev; + struct snd_sof_dev *sdev; + struct mtk_adsp_ipc *dsp_ipc; + struct platform_device *ipc_dev; + struct mtk_adsp_chip_info *adsp; + struct clk **clk; + u32 (*ap2adsp_addr)(u32 addr, void *data); + u32 (*adsp2ap_addr)(u32 addr, void *data); + + void *private_data; +}; + +#endif diff --git a/sound/soc/sof/mediatek/mt8186/Makefile b/sound/soc/sof/mediatek/mt8186/Makefile new file mode 100644 index 000000000000..c1f5fc4e2495 --- /dev/null +++ b/sound/soc/sof/mediatek/mt8186/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +snd-sof-mt8186-objs := mt8186.o mt8186-clk.o mt8186-loader.o +obj-$(CONFIG_SND_SOC_SOF_MT8186) += snd-sof-mt8186.o + diff --git a/sound/soc/sof/mediatek/mt8186/mt8186-clk.c b/sound/soc/sof/mediatek/mt8186/mt8186-clk.c new file mode 100644 index 000000000000..2df3b7ae1c6f --- /dev/null +++ b/sound/soc/sof/mediatek/mt8186/mt8186-clk.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// Copyright(c) 2022 Mediatek Corporation. All rights reserved. +// +// Author: Allen-KH Cheng <allen-kh.cheng@mediatek.com> +// Tinghan Shen <tinghan.shen@mediatek.com> +// +// Hardware interface for mt8186 DSP clock + +#include <linux/clk.h> +#include <linux/pm_runtime.h> +#include <linux/io.h> + +#include "../../sof-audio.h" +#include "../../ops.h" +#include "../adsp_helper.h" +#include "mt8186.h" +#include "mt8186-clk.h" + +static const char *adsp_clks[ADSP_CLK_MAX] = { + [CLK_TOP_AUDIODSP] = "audiodsp", + [CLK_TOP_ADSP_BUS] = "adsp_bus", +}; + +int mt8186_adsp_init_clock(struct snd_sof_dev *sdev) +{ + struct adsp_priv *priv = sdev->pdata->hw_pdata; + struct device *dev = sdev->dev; + int i; + + priv->clk = devm_kcalloc(dev, ADSP_CLK_MAX, sizeof(*priv->clk), GFP_KERNEL); + if (!priv->clk) + return -ENOMEM; + + for (i = 0; i < ADSP_CLK_MAX; i++) { + priv->clk[i] = devm_clk_get(dev, adsp_clks[i]); + + if (IS_ERR(priv->clk[i])) + return PTR_ERR(priv->clk[i]); + } + + return 0; +} + +static int adsp_enable_all_clock(struct snd_sof_dev *sdev) +{ + struct adsp_priv *priv = sdev->pdata->hw_pdata; + struct device *dev = sdev->dev; + int ret; + + ret = clk_prepare_enable(priv->clk[CLK_TOP_AUDIODSP]); + if (ret) { + dev_err(dev, "%s clk_prepare_enable(audiodsp) fail %d\n", + __func__, ret); + return ret; + } + + ret = clk_prepare_enable(priv->clk[CLK_TOP_ADSP_BUS]); + if (ret) { + dev_err(dev, "%s clk_prepare_enable(adsp_bus) fail %d\n", + __func__, ret); + clk_disable_unprepare(priv->clk[CLK_TOP_AUDIODSP]); + return ret; + } + + return 0; +} + +static void adsp_disable_all_clock(struct snd_sof_dev *sdev) +{ + struct adsp_priv *priv = sdev->pdata->hw_pdata; + + clk_disable_unprepare(priv->clk[CLK_TOP_ADSP_BUS]); + clk_disable_unprepare(priv->clk[CLK_TOP_AUDIODSP]); +} + +int mt8186_adsp_clock_on(struct snd_sof_dev *sdev) +{ + struct device *dev = sdev->dev; + int ret; + + ret = adsp_enable_all_clock(sdev); + if (ret) { + dev_err(dev, "failed to adsp_enable_clock: %d\n", ret); + return ret; + } + snd_sof_dsp_write(sdev, DSP_REG_BAR, ADSP_CK_EN, + UART_EN | DMA_EN | TIMER_EN | COREDBG_EN | CORE_CLK_EN); + snd_sof_dsp_write(sdev, DSP_REG_BAR, ADSP_UART_CTRL, + UART_BCLK_CG | UART_RSTN); + + return 0; +} + +void mt8186_adsp_clock_off(struct snd_sof_dev *sdev) +{ + snd_sof_dsp_write(sdev, DSP_REG_BAR, ADSP_CK_EN, 0); + snd_sof_dsp_write(sdev, DSP_REG_BAR, ADSP_UART_CTRL, 0); + adsp_disable_all_clock(sdev); +} + diff --git a/sound/soc/sof/mediatek/mt8186/mt8186-clk.h b/sound/soc/sof/mediatek/mt8186/mt8186-clk.h new file mode 100644 index 000000000000..89c23caf0fee --- /dev/null +++ b/sound/soc/sof/mediatek/mt8186/mt8186-clk.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ + +/* + * Copyright (c) 2022 MediaTek Corporation. All rights reserved. + * + * Header file for the mt8186 DSP clock definition + */ + +#ifndef __MT8186_CLK_H +#define __MT8186_CLK_H + +struct snd_sof_dev; + +/* DSP clock */ +enum adsp_clk_id { + CLK_TOP_AUDIODSP, + CLK_TOP_ADSP_BUS, + ADSP_CLK_MAX +}; + +int mt8186_adsp_init_clock(struct snd_sof_dev *sdev); +int mt8186_adsp_clock_on(struct snd_sof_dev *sdev); +void mt8186_adsp_clock_off(struct snd_sof_dev *sdev); +#endif diff --git a/sound/soc/sof/mediatek/mt8186/mt8186-loader.c b/sound/soc/sof/mediatek/mt8186/mt8186-loader.c new file mode 100644 index 000000000000..946e6c43204f --- /dev/null +++ b/sound/soc/sof/mediatek/mt8186/mt8186-loader.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// Copyright (c) 2022 Mediatek Corporation. All rights reserved. +// +// Author: Allen-KH Cheng <allen-kh.cheng@mediatek.com> +// Tinghan Shen <tinghan.shen@mediatek.com> +// +// Hardware interface for mt8186 DSP code loader + +#include <sound/sof.h> +#include "mt8186.h" +#include "../../ops.h" + +void mt8186_sof_hifixdsp_boot_sequence(struct snd_sof_dev *sdev, u32 boot_addr) +{ + /* set RUNSTALL to stop core */ + snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, ADSP_HIFI_IO_CONFIG, + RUNSTALL, RUNSTALL); + + /* enable mbox 0 & 1 IRQ */ + snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, ADSP_MBOX_IRQ_EN, + DSP_MBOX0_IRQ_EN | DSP_MBOX1_IRQ_EN, + DSP_MBOX0_IRQ_EN | DSP_MBOX1_IRQ_EN); + + /* set core boot address */ + snd_sof_dsp_write(sdev, DSP_SECREG_BAR, ADSP_ALTVEC_C0, boot_addr); + snd_sof_dsp_write(sdev, DSP_SECREG_BAR, ADSP_ALTVECSEL, ADSP_ALTVECSEL_C0); + + /* assert core reset */ + snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, ADSP_CFGREG_SW_RSTN, + SW_RSTN_C0 | SW_DBG_RSTN_C0, + SW_RSTN_C0 | SW_DBG_RSTN_C0); + + /* hardware requirement */ + udelay(1); + + /* release core reset */ + snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, ADSP_CFGREG_SW_RSTN, + SW_RSTN_C0 | SW_DBG_RSTN_C0, + 0); + + /* clear RUNSTALL (bit31) to start core */ + snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, ADSP_HIFI_IO_CONFIG, + RUNSTALL, 0); +} + +void mt8186_sof_hifixdsp_shutdown(struct snd_sof_dev *sdev) +{ + /* set RUNSTALL to stop core */ + snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, ADSP_HIFI_IO_CONFIG, + RUNSTALL, RUNSTALL); + + /* assert core reset */ + snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, ADSP_CFGREG_SW_RSTN, + SW_RSTN_C0 | SW_DBG_RSTN_C0, + SW_RSTN_C0 | SW_DBG_RSTN_C0); +} + diff --git a/sound/soc/sof/mediatek/mt8186/mt8186.c b/sound/soc/sof/mediatek/mt8186/mt8186.c new file mode 100644 index 000000000000..181189e00e02 --- /dev/null +++ b/sound/soc/sof/mediatek/mt8186/mt8186.c @@ -0,0 +1,642 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// Copyright(c) 2022 Mediatek Inc. All rights reserved. +// +// Author: Allen-KH Cheng <allen-kh.cheng@mediatek.com> +// Tinghan Shen <tinghan.shen@mediatek.com> + +/* + * Hardware interface for audio DSP on mt8186 + */ + +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/io.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/of_reserved_mem.h> +#include <linux/module.h> + +#include <sound/sof.h> +#include <sound/sof/xtensa.h> +#include "../../ops.h" +#include "../../sof-of-dev.h" +#include "../../sof-audio.h" +#include "../adsp_helper.h" +#include "mt8186.h" +#include "mt8186-clk.h" + +static int mt8186_get_mailbox_offset(struct snd_sof_dev *sdev) +{ + return MBOX_OFFSET; +} + +static int mt8186_get_window_offset(struct snd_sof_dev *sdev, u32 id) +{ + return MBOX_OFFSET; +} + +static int mt8186_send_msg(struct snd_sof_dev *sdev, + struct snd_sof_ipc_msg *msg) +{ + struct adsp_priv *priv = sdev->pdata->hw_pdata; + + sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data, + msg->msg_size); + + return mtk_adsp_ipc_send(priv->dsp_ipc, MTK_ADSP_IPC_REQ, MTK_ADSP_IPC_OP_REQ); +} + +static void mt8186_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 void mt8186_dsp_handle_reply(struct mtk_adsp_ipc *ipc) +{ + struct adsp_priv *priv = mtk_adsp_ipc_get_data(ipc); + unsigned long flags; + + spin_lock_irqsave(&priv->sdev->ipc_lock, flags); + mt8186_get_reply(priv->sdev); + snd_sof_ipc_reply(priv->sdev, 0); + spin_unlock_irqrestore(&priv->sdev->ipc_lock, flags); +} + +static void mt8186_dsp_handle_request(struct mtk_adsp_ipc *ipc) +{ + struct adsp_priv *priv = mtk_adsp_ipc_get_data(ipc); + u32 p; /* panic code */ + int ret; + + /* Read the message from the debug box. */ + sof_mailbox_read(priv->sdev, priv->sdev->debug_box.offset + 4, + &p, sizeof(p)); + + /* Check to see if the message is a panic code 0x0dead*** */ + if ((p & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) { + snd_sof_dsp_panic(priv->sdev, p, true); + } else { + snd_sof_ipc_msgs_rx(priv->sdev); + + /* tell DSP cmd is done */ + ret = mtk_adsp_ipc_send(priv->dsp_ipc, MTK_ADSP_IPC_RSP, MTK_ADSP_IPC_OP_RSP); + if (ret) + dev_err(priv->dev, "request send ipc failed"); + } +} + +static struct mtk_adsp_ipc_ops dsp_ops = { + .handle_reply = mt8186_dsp_handle_reply, + .handle_request = mt8186_dsp_handle_request, +}; + +static int platform_parse_resource(struct platform_device *pdev, void *data) +{ + struct resource *mmio; + struct resource res; + struct device_node *mem_region; + struct device *dev = &pdev->dev; + struct mtk_adsp_chip_info *adsp = data; + int ret; + + mem_region = of_parse_phandle(dev->of_node, "memory-region", 0); + if (!mem_region) { + dev_err(dev, "no dma memory-region phandle\n"); + return -ENODEV; + } + + ret = of_address_to_resource(mem_region, 0, &res); + of_node_put(mem_region); + if (ret) { + dev_err(dev, "of_address_to_resource dma failed\n"); + return ret; + } + + dev_dbg(dev, "DMA %pR\n", &res); + + ret = of_reserved_mem_device_init(dev); + if (ret) { + dev_err(dev, "of_reserved_mem_device_init failed\n"); + return ret; + } + + mem_region = of_parse_phandle(dev->of_node, "memory-region", 1); + if (!mem_region) { + dev_err(dev, "no memory-region sysmem phandle\n"); + return -ENODEV; + } + + ret = of_address_to_resource(mem_region, 0, &res); + of_node_put(mem_region); + if (ret) { + dev_err(dev, "of_address_to_resource sysmem failed\n"); + return ret; + } + + adsp->pa_dram = (phys_addr_t)res.start; + if (adsp->pa_dram & DRAM_REMAP_MASK) { + dev_err(dev, "adsp memory(%#x) is not 4K-aligned\n", + (u32)adsp->pa_dram); + return -EINVAL; + } + + adsp->dramsize = resource_size(&res); + if (adsp->dramsize < TOTAL_SIZE_SHARED_DRAM_FROM_TAIL) { + dev_err(dev, "adsp memory(%#x) is not enough for share\n", + adsp->dramsize); + return -EINVAL; + } + + dev_dbg(dev, "dram pbase=%pa size=%#x\n", &adsp->pa_dram, adsp->dramsize); + + mmio = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cfg"); + if (!mmio) { + dev_err(dev, "no ADSP-CFG register resource\n"); + return -ENXIO; + } + + adsp->va_cfgreg = devm_ioremap_resource(dev, mmio); + if (IS_ERR(adsp->va_cfgreg)) + return PTR_ERR(adsp->va_cfgreg); + + adsp->pa_cfgreg = (phys_addr_t)mmio->start; + adsp->cfgregsize = resource_size(mmio); + + dev_dbg(dev, "cfgreg pbase=%pa size=%#x\n", &adsp->pa_cfgreg, adsp->cfgregsize); + + mmio = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sram"); + if (!mmio) { + dev_err(dev, "no SRAM resource\n"); + return -ENXIO; + } + + adsp->pa_sram = (phys_addr_t)mmio->start; + adsp->sramsize = resource_size(mmio); + + dev_dbg(dev, "sram pbase=%pa size=%#x\n", &adsp->pa_sram, adsp->sramsize); + + mmio = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sec"); + if (!mmio) { + dev_err(dev, "no SEC register resource\n"); + return -ENXIO; + } + + adsp->va_secreg = devm_ioremap_resource(dev, mmio); + if (IS_ERR(adsp->va_secreg)) + return PTR_ERR(adsp->va_secreg); + + adsp->pa_secreg = (phys_addr_t)mmio->start; + adsp->secregsize = resource_size(mmio); + + dev_dbg(dev, "secreg pbase=%pa size=%#x\n", &adsp->pa_secreg, adsp->secregsize); + + mmio = platform_get_resource_byname(pdev, IORESOURCE_MEM, "bus"); + if (!mmio) { + dev_err(dev, "no BUS register resource\n"); + return -ENXIO; + } + + adsp->va_busreg = devm_ioremap_resource(dev, mmio); + if (IS_ERR(adsp->va_busreg)) + return PTR_ERR(adsp->va_busreg); + + adsp->pa_busreg = (phys_addr_t)mmio->start; + adsp->busregsize = resource_size(mmio); + + dev_dbg(dev, "busreg pbase=%pa size=%#x\n", &adsp->pa_busreg, adsp->busregsize); + + return 0; +} + +static void adsp_sram_power_on(struct snd_sof_dev *sdev) +{ + snd_sof_dsp_update_bits(sdev, DSP_BUSREG_BAR, ADSP_SRAM_POOL_CON, + DSP_SRAM_POOL_PD_MASK, 0); +} + +static void adsp_sram_power_off(struct snd_sof_dev *sdev) +{ + snd_sof_dsp_update_bits(sdev, DSP_BUSREG_BAR, ADSP_SRAM_POOL_CON, + DSP_SRAM_POOL_PD_MASK, DSP_SRAM_POOL_PD_MASK); +} + +/* Init the basic DSP DRAM address */ +static int adsp_memory_remap_init(struct snd_sof_dev *sdev, struct mtk_adsp_chip_info *adsp) +{ + u32 offset; + + offset = adsp->pa_dram - DRAM_PHYS_BASE_FROM_DSP_VIEW; + adsp->dram_offset = offset; + offset >>= DRAM_REMAP_SHIFT; + + dev_dbg(sdev->dev, "adsp->pa_dram %pa, offset %#x\n", &adsp->pa_dram, offset); + + snd_sof_dsp_write(sdev, DSP_BUSREG_BAR, DSP_C0_EMI_MAP_ADDR, offset); + snd_sof_dsp_write(sdev, DSP_BUSREG_BAR, DSP_C0_DMAEMI_MAP_ADDR, offset); + + if (offset != snd_sof_dsp_read(sdev, DSP_BUSREG_BAR, DSP_C0_EMI_MAP_ADDR) || + offset != snd_sof_dsp_read(sdev, DSP_BUSREG_BAR, DSP_C0_DMAEMI_MAP_ADDR)) { + dev_err(sdev->dev, "emi remap fail\n"); + return -EIO; + } + + return 0; +} + +static int adsp_shared_base_ioremap(struct platform_device *pdev, void *data) +{ + struct device *dev = &pdev->dev; + struct mtk_adsp_chip_info *adsp = data; + u32 shared_size; + + /* remap shared-dram base to be non-cachable */ + shared_size = TOTAL_SIZE_SHARED_DRAM_FROM_TAIL; + adsp->pa_shared_dram = adsp->pa_dram + adsp->dramsize - shared_size; + if (adsp->va_dram) { + adsp->shared_dram = adsp->va_dram + DSP_DRAM_SIZE - shared_size; + } else { + adsp->shared_dram = devm_ioremap(dev, adsp->pa_shared_dram, + shared_size); + if (!adsp->shared_dram) { + dev_err(dev, "ioremap failed for shared DRAM\n"); + return -ENOMEM; + } + } + dev_dbg(dev, "shared-dram vbase=%p, phy addr :%pa, size=%#x\n", + adsp->shared_dram, &adsp->pa_shared_dram, shared_size); + + return 0; +} + +static int mt8186_run(struct snd_sof_dev *sdev) +{ + u32 adsp_bootup_addr; + + adsp_bootup_addr = SRAM_PHYS_BASE_FROM_DSP_VIEW; + dev_dbg(sdev->dev, "HIFIxDSP boot from base : 0x%08X\n", adsp_bootup_addr); + mt8186_sof_hifixdsp_boot_sequence(sdev, adsp_bootup_addr); + + return 0; +} + +static int mt8186_dsp_probe(struct snd_sof_dev *sdev) +{ + struct platform_device *pdev = container_of(sdev->dev, struct platform_device, dev); + struct adsp_priv *priv; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + sdev->pdata->hw_pdata = priv; + priv->dev = sdev->dev; + priv->sdev = sdev; + + priv->adsp = devm_kzalloc(&pdev->dev, sizeof(struct mtk_adsp_chip_info), GFP_KERNEL); + if (!priv->adsp) + return -ENOMEM; + + ret = platform_parse_resource(pdev, priv->adsp); + if (ret) + return ret; + + sdev->bar[SOF_FW_BLK_TYPE_IRAM] = devm_ioremap(sdev->dev, + priv->adsp->pa_sram, + priv->adsp->sramsize); + if (!sdev->bar[SOF_FW_BLK_TYPE_IRAM]) { + dev_err(sdev->dev, "failed to ioremap base %pa size %#x\n", + &priv->adsp->pa_sram, priv->adsp->sramsize); + return -ENOMEM; + } + + sdev->bar[SOF_FW_BLK_TYPE_SRAM] = devm_ioremap_wc(sdev->dev, + priv->adsp->pa_dram, + priv->adsp->dramsize); + if (!sdev->bar[SOF_FW_BLK_TYPE_SRAM]) { + dev_err(sdev->dev, "failed to ioremap base %pa size %#x\n", + &priv->adsp->pa_dram, priv->adsp->dramsize); + return -ENOMEM; + } + + priv->adsp->va_dram = sdev->bar[SOF_FW_BLK_TYPE_SRAM]; + + ret = adsp_shared_base_ioremap(pdev, priv->adsp); + if (ret) { + dev_err(sdev->dev, "adsp_shared_base_ioremap fail!\n"); + return ret; + } + + sdev->bar[DSP_REG_BAR] = priv->adsp->va_cfgreg; + sdev->bar[DSP_SECREG_BAR] = priv->adsp->va_secreg; + sdev->bar[DSP_BUSREG_BAR] = priv->adsp->va_busreg; + + sdev->mmio_bar = SOF_FW_BLK_TYPE_SRAM; + sdev->mailbox_bar = SOF_FW_BLK_TYPE_SRAM; + + /* set default mailbox offset for FW ready message */ + sdev->dsp_box.offset = mt8186_get_mailbox_offset(sdev); + + ret = adsp_memory_remap_init(sdev, priv->adsp); + if (ret) { + dev_err(sdev->dev, "adsp_memory_remap_init fail!\n"); + return ret; + } + + /* enable adsp clock before touching registers */ + ret = mt8186_adsp_init_clock(sdev); + if (ret) { + dev_err(sdev->dev, "mt8186_adsp_init_clock failed\n"); + return ret; + } + + ret = mt8186_adsp_clock_on(sdev); + if (ret) { + dev_err(sdev->dev, "mt8186_adsp_clock_on fail!\n"); + return ret; + } + + adsp_sram_power_on(sdev); + + priv->ipc_dev = platform_device_register_data(&pdev->dev, "mtk-adsp-ipc", + PLATFORM_DEVID_NONE, + pdev, sizeof(*pdev)); + if (IS_ERR(priv->ipc_dev)) { + ret = PTR_ERR(priv->ipc_dev); + dev_err(sdev->dev, "failed to create mtk-adsp-ipc device\n"); + goto err_adsp_off; + } + + priv->dsp_ipc = dev_get_drvdata(&priv->ipc_dev->dev); + if (!priv->dsp_ipc) { + ret = -EPROBE_DEFER; + dev_err(sdev->dev, "failed to get drvdata\n"); + goto exit_pdev_unregister; + } + + mtk_adsp_ipc_set_data(priv->dsp_ipc, priv); + priv->dsp_ipc->ops = &dsp_ops; + + return 0; + +exit_pdev_unregister: + platform_device_unregister(priv->ipc_dev); +err_adsp_off: + adsp_sram_power_off(sdev); + mt8186_adsp_clock_off(sdev); + + return ret; +} + +static int mt8186_dsp_remove(struct snd_sof_dev *sdev) +{ + struct adsp_priv *priv = sdev->pdata->hw_pdata; + + platform_device_unregister(priv->ipc_dev); + mt8186_sof_hifixdsp_shutdown(sdev); + adsp_sram_power_off(sdev); + mt8186_adsp_clock_off(sdev); + + return 0; +} + +static int mt8186_dsp_suspend(struct snd_sof_dev *sdev, u32 target_state) +{ + mt8186_sof_hifixdsp_shutdown(sdev); + adsp_sram_power_off(sdev); + mt8186_adsp_clock_off(sdev); + + return 0; +} + +static int mt8186_dsp_resume(struct snd_sof_dev *sdev) +{ + int ret; + + ret = mt8186_adsp_clock_on(sdev); + if (ret) { + dev_err(sdev->dev, "mt8186_adsp_clock_on fail!\n"); + return ret; + } + + adsp_sram_power_on(sdev); + + return ret; +} + +/* on mt8186 there is 1 to 1 match between type and BAR idx */ +static int mt8186_get_bar_index(struct snd_sof_dev *sdev, u32 type) +{ + return type; +} + +static int mt8186_pcm_hw_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_sof_platform_stream_params *platform_params) +{ + platform_params->cont_update_posn = 1; + + return 0; +} + +static snd_pcm_uframes_t mt8186_pcm_pointer(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + int ret; + snd_pcm_uframes_t pos; + struct snd_sof_pcm *spcm; + struct sof_ipc_stream_posn posn; + struct snd_sof_pcm_stream *stream; + struct snd_soc_component *scomp = sdev->component; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + + spcm = snd_sof_find_spcm_dai(scomp, rtd); + if (!spcm) { + dev_warn_ratelimited(sdev->dev, "warn: can't find PCM with DAI ID %d\n", + rtd->dai_link->id); + return 0; + } + + stream = &spcm->stream[substream->stream]; + ret = snd_sof_ipc_msg_data(sdev, stream->substream, &posn, sizeof(posn)); + if (ret < 0) { + dev_warn(sdev->dev, "failed to read stream position: %d\n", ret); + return 0; + } + + memcpy(&stream->posn, &posn, sizeof(posn)); + pos = spcm->stream[substream->stream].posn.host_posn; + pos = bytes_to_frames(substream->runtime, pos); + + return pos; +} + +static struct snd_soc_dai_driver mt8186_dai[] = { +{ + .name = "SOF_DL1", + .playback = { + .channels_min = 1, + .channels_max = 2, + }, +}, +{ + .name = "SOF_DL2", + .playback = { + .channels_min = 1, + .channels_max = 2, + }, +}, +{ + .name = "SOF_UL1", + .capture = { + .channels_min = 1, + .channels_max = 2, + }, +}, +{ + .name = "SOF_UL2", + .capture = { + .channels_min = 1, + .channels_max = 2, + }, +}, +}; + +/* mt8186 ops */ +static struct snd_sof_dsp_ops sof_mt8186_ops = { + /* probe and remove */ + .probe = mt8186_dsp_probe, + .remove = mt8186_dsp_remove, + + /* DSP core boot */ + .run = mt8186_run, + + /* Block IO */ + .block_read = sof_block_read, + .block_write = sof_block_write, + + /* Mailbox IO */ + .mailbox_read = sof_mailbox_read, + .mailbox_write = sof_mailbox_write, + + /* Register IO */ + .write = sof_io_write, + .read = sof_io_read, + .write64 = sof_io_write64, + .read64 = sof_io_read64, + + /* ipc */ + .send_msg = mt8186_send_msg, + .get_mailbox_offset = mt8186_get_mailbox_offset, + .get_window_offset = mt8186_get_window_offset, + .ipc_msg_data = sof_ipc_msg_data, + .set_stream_data_offset = sof_set_stream_data_offset, + + /* misc */ + .get_bar_index = mt8186_get_bar_index, + + /* stream callbacks */ + .pcm_open = sof_stream_pcm_open, + .pcm_hw_params = mt8186_pcm_hw_params, + .pcm_pointer = mt8186_pcm_pointer, + .pcm_close = sof_stream_pcm_close, + + /* firmware loading */ + .load_firmware = snd_sof_load_firmware_memcpy, + + /* Firmware ops */ + .dsp_arch_ops = &sof_xtensa_arch_ops, + + /* DAI drivers */ + .drv = mt8186_dai, + .num_drv = ARRAY_SIZE(mt8186_dai), + + /* PM */ + .suspend = mt8186_dsp_suspend, + .resume = mt8186_dsp_resume, + + /* ALSA HW info flags */ + .hw_info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, +}; + +static struct snd_sof_of_mach sof_mt8186_machs[] = { + { + .compatible = "mediatek,mt8186", + .sof_tplg_filename = "sof-mt8186.tplg", + }, + {} +}; + +static const struct sof_dev_desc sof_of_mt8186_desc = { + .of_machines = sof_mt8186_machs, + .ipc_supported_mask = BIT(SOF_IPC), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "mediatek/sof", + }, + .default_tplg_path = { + [SOF_IPC] = "mediatek/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-mt8186.ri", + }, + .nocodec_tplg_filename = "sof-mt8186-nocodec.tplg", + .ops = &sof_mt8186_ops, +}; + +static const struct of_device_id sof_of_mt8186_ids[] = { + { .compatible = "mediatek,mt8186-dsp", .data = &sof_of_mt8186_desc}, + { } +}; +MODULE_DEVICE_TABLE(of, sof_of_mt8186_ids); + +/* DT driver definition */ +static struct platform_driver snd_sof_of_mt8186_driver = { + .probe = sof_of_probe, + .remove = sof_of_remove, + .driver = { + .name = "sof-audio-of-mt8186", + .pm = &sof_of_pm, + .of_match_table = sof_of_mt8186_ids, + }, +}; +module_platform_driver(snd_sof_of_mt8186_driver); + +MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA); +MODULE_IMPORT_NS(SND_SOC_SOF_MTK_COMMON); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/mediatek/mt8186/mt8186.h b/sound/soc/sof/mediatek/mt8186/mt8186.h new file mode 100644 index 000000000000..98b2965e5ba6 --- /dev/null +++ b/sound/soc/sof/mediatek/mt8186/mt8186.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ + +/* + * Copyright (c) 2022 MediaTek Corporation. All rights reserved. + * + * Header file for the mt8186 DSP register definition + */ + +#ifndef __MT8186_H +#define __MT8186_H + +struct mtk_adsp_chip_info; +struct snd_sof_dev; + +#define DSP_REG_BAR 4 +#define DSP_SECREG_BAR 5 +#define DSP_BUSREG_BAR 6 + +/***************************************************************************** + * R E G I S T E R TABLE + *****************************************************************************/ +/* dsp cfg */ +#define ADSP_CFGREG_SW_RSTN 0x0000 +#define SW_DBG_RSTN_C0 BIT(0) +#define SW_RSTN_C0 BIT(4) +#define ADSP_HIFI_IO_CONFIG 0x000C +#define TRACEMEMREADY BIT(15) +#define RUNSTALL BIT(31) +#define ADSP_IRQ_MASK 0x0030 +#define ADSP_DVFSRC_REQ 0x0040 +#define ADSP_DDREN_REQ_0 0x0044 +#define ADSP_SEMAPHORE 0x0064 +#define ADSP_WDT_CON_C0 0x007C +#define ADSP_MBOX_IRQ_EN 0x009C +#define DSP_MBOX0_IRQ_EN BIT(0) +#define DSP_MBOX1_IRQ_EN BIT(1) +#define DSP_MBOX2_IRQ_EN BIT(2) +#define DSP_MBOX3_IRQ_EN BIT(3) +#define DSP_MBOX4_IRQ_EN BIT(4) +#define DSP_PDEBUGPC 0x013C +#define ADSP_CK_EN 0x1000 +#define CORE_CLK_EN BIT(0) +#define COREDBG_EN BIT(1) +#define TIMER_EN BIT(3) +#define DMA_EN BIT(4) +#define UART_EN BIT(5) +#define ADSP_UART_CTRL 0x1010 +#define UART_BCLK_CG BIT(0) +#define UART_RSTN BIT(3) + +/* dsp sec */ +#define ADSP_PRID 0x0 +#define ADSP_ALTVEC_C0 0x04 +#define ADSP_ALTVECSEL 0x0C +#define ADSP_ALTVECSEL_C0 BIT(1) + +/* dsp bus */ +#define ADSP_SRAM_POOL_CON 0x190 +#define DSP_SRAM_POOL_PD_MASK 0xF00F /* [0:3] and [12:15] */ +#define DSP_C0_EMI_MAP_ADDR 0xA00 /* ADSP Core0 To EMI Address Remap */ +#define DSP_C0_DMAEMI_MAP_ADDR 0xA08 /* DMA0 To EMI Address Remap */ + +/* DSP memories */ +#define MBOX_OFFSET 0x500000 /* DRAM */ +#define MBOX_SIZE 0x1000 /* consistent with which in memory.h of sof fw */ +#define DSP_DRAM_SIZE 0xA00000 /* 16M */ + +/*remap dram between AP and DSP view, 4KB aligned*/ +#define SRAM_PHYS_BASE_FROM_DSP_VIEW 0x4E100000 /* MT8186 DSP view */ +#define DRAM_PHYS_BASE_FROM_DSP_VIEW 0x60000000 /* MT8186 DSP view */ +#define DRAM_REMAP_SHIFT 12 +#define DRAM_REMAP_MASK 0xFFF + +#define SIZE_SHARED_DRAM_DL 0x40000 /*Shared buffer for Downlink*/ +#define SIZE_SHARED_DRAM_UL 0x40000 /*Shared buffer for Uplink*/ +#define TOTAL_SIZE_SHARED_DRAM_FROM_TAIL (SIZE_SHARED_DRAM_DL + SIZE_SHARED_DRAM_UL) + +void mt8186_sof_hifixdsp_boot_sequence(struct snd_sof_dev *sdev, u32 boot_addr); +void mt8186_sof_hifixdsp_shutdown(struct snd_sof_dev *sdev); +#endif diff --git a/sound/soc/sof/mediatek/mt8195/Makefile b/sound/soc/sof/mediatek/mt8195/Makefile new file mode 100644 index 000000000000..afc4f21fccc5 --- /dev/null +++ b/sound/soc/sof/mediatek/mt8195/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +snd-sof-mt8195-objs := mt8195.o mt8195-clk.o mt8195-loader.o +obj-$(CONFIG_SND_SOC_SOF_MT8195) += snd-sof-mt8195.o diff --git a/sound/soc/sof/mediatek/mt8195/mt8195-clk.c b/sound/soc/sof/mediatek/mt8195/mt8195-clk.c new file mode 100644 index 000000000000..9ef08e43aa38 --- /dev/null +++ b/sound/soc/sof/mediatek/mt8195/mt8195-clk.c @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// Copyright(c) 2021 Mediatek Corporation. All rights reserved. +// +// Author: YC Hung <yc.hung@mediatek.com> +// +// Hardware interface for mt8195 DSP clock + +#include <linux/clk.h> +#include <linux/pm_runtime.h> +#include <linux/io.h> +#include "mt8195.h" +#include "mt8195-clk.h" +#include "../adsp_helper.h" +#include "../../sof-audio.h" + +static const char *adsp_clks[ADSP_CLK_MAX] = { + [CLK_TOP_ADSP] = "adsp_sel", + [CLK_TOP_CLK26M] = "clk26m_ck", + [CLK_TOP_AUDIO_LOCAL_BUS] = "audio_local_bus", + [CLK_TOP_MAINPLL_D7_D2] = "mainpll_d7_d2", + [CLK_SCP_ADSP_AUDIODSP] = "scp_adsp_audiodsp", + [CLK_TOP_AUDIO_H] = "audio_h", +}; + +int mt8195_adsp_init_clock(struct snd_sof_dev *sdev) +{ + struct device *dev = sdev->dev; + struct adsp_priv *priv = sdev->pdata->hw_pdata; + int i; + + priv->clk = devm_kcalloc(dev, ADSP_CLK_MAX, sizeof(*priv->clk), GFP_KERNEL); + + if (!priv->clk) + return -ENOMEM; + + for (i = 0; i < ADSP_CLK_MAX; i++) { + priv->clk[i] = devm_clk_get(dev, adsp_clks[i]); + if (IS_ERR(priv->clk[i])) + return PTR_ERR(priv->clk[i]); + } + + return 0; +} + +static int adsp_enable_all_clock(struct snd_sof_dev *sdev) +{ + struct device *dev = sdev->dev; + struct adsp_priv *priv = sdev->pdata->hw_pdata; + int ret; + + ret = clk_prepare_enable(priv->clk[CLK_TOP_MAINPLL_D7_D2]); + if (ret) { + dev_err(dev, "%s clk_prepare_enable(mainpll_d7_d2) fail %d\n", + __func__, ret); + return ret; + } + + ret = clk_prepare_enable(priv->clk[CLK_TOP_ADSP]); + if (ret) { + dev_err(dev, "%s clk_prepare_enable(adsp_sel) fail %d\n", + __func__, ret); + goto disable_mainpll_d7_d2_clk; + } + + ret = clk_prepare_enable(priv->clk[CLK_TOP_AUDIO_LOCAL_BUS]); + if (ret) { + dev_err(dev, "%s clk_prepare_enable(audio_local_bus) fail %d\n", + __func__, ret); + goto disable_dsp_sel_clk; + } + + ret = clk_prepare_enable(priv->clk[CLK_SCP_ADSP_AUDIODSP]); + if (ret) { + dev_err(dev, "%s clk_prepare_enable(scp_adsp_audiodsp) fail %d\n", + __func__, ret); + goto disable_audio_local_bus_clk; + } + + ret = clk_prepare_enable(priv->clk[CLK_TOP_AUDIO_H]); + if (ret) { + dev_err(dev, "%s clk_prepare_enable(audio_h) fail %d\n", + __func__, ret); + goto disable_scp_adsp_audiodsp_clk; + } + + return 0; + +disable_scp_adsp_audiodsp_clk: + clk_disable_unprepare(priv->clk[CLK_SCP_ADSP_AUDIODSP]); +disable_audio_local_bus_clk: + clk_disable_unprepare(priv->clk[CLK_TOP_AUDIO_LOCAL_BUS]); +disable_dsp_sel_clk: + clk_disable_unprepare(priv->clk[CLK_TOP_ADSP]); +disable_mainpll_d7_d2_clk: + clk_disable_unprepare(priv->clk[CLK_TOP_MAINPLL_D7_D2]); + + return ret; +} + +static void adsp_disable_all_clock(struct snd_sof_dev *sdev) +{ + struct adsp_priv *priv = sdev->pdata->hw_pdata; + + clk_disable_unprepare(priv->clk[CLK_TOP_AUDIO_H]); + clk_disable_unprepare(priv->clk[CLK_SCP_ADSP_AUDIODSP]); + clk_disable_unprepare(priv->clk[CLK_TOP_AUDIO_LOCAL_BUS]); + clk_disable_unprepare(priv->clk[CLK_TOP_ADSP]); + clk_disable_unprepare(priv->clk[CLK_TOP_MAINPLL_D7_D2]); +} + +static int adsp_default_clk_init(struct snd_sof_dev *sdev, bool enable) +{ + struct device *dev = sdev->dev; + struct adsp_priv *priv = sdev->pdata->hw_pdata; + int ret; + + dev_dbg(dev, "%s: %s\n", __func__, enable ? "on" : "off"); + + if (enable) { + ret = clk_set_parent(priv->clk[CLK_TOP_ADSP], + priv->clk[CLK_TOP_CLK26M]); + if (ret) { + dev_err(dev, "failed to set dsp_sel to clk26m: %d\n", ret); + return ret; + } + + ret = clk_set_parent(priv->clk[CLK_TOP_AUDIO_LOCAL_BUS], + priv->clk[CLK_TOP_MAINPLL_D7_D2]); + if (ret) { + dev_err(dev, "set audio_local_bus failed %d\n", ret); + return ret; + } + + ret = clk_set_parent(priv->clk[CLK_TOP_AUDIO_H], + priv->clk[CLK_TOP_CLK26M]); + if (ret) { + dev_err(dev, "set audio_h_sel failed %d\n", ret); + return ret; + } + + ret = adsp_enable_all_clock(sdev); + if (ret) { + dev_err(dev, "failed to adsp_enable_clock: %d\n", ret); + return ret; + } + } else { + adsp_disable_all_clock(sdev); + } + + return 0; +} + +int adsp_clock_on(struct snd_sof_dev *sdev) +{ + /* Open ADSP clock */ + return adsp_default_clk_init(sdev, 1); +} + +int adsp_clock_off(struct snd_sof_dev *sdev) +{ + /* Close ADSP clock */ + return adsp_default_clk_init(sdev, 0); +} + diff --git a/sound/soc/sof/mediatek/mt8195/mt8195-clk.h b/sound/soc/sof/mediatek/mt8195/mt8195-clk.h new file mode 100644 index 000000000000..9cc0573d5cd2 --- /dev/null +++ b/sound/soc/sof/mediatek/mt8195/mt8195-clk.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * Copyright (c) 2021 MediaTek Corporation. All rights reserved. + * + * Header file for the mt8195 DSP clock definition + */ + +#ifndef __MT8195_CLK_H +#define __MT8195_CLK_H + +struct snd_sof_dev; + +/*DSP clock*/ +enum adsp_clk_id { + CLK_TOP_ADSP, + CLK_TOP_CLK26M, + CLK_TOP_AUDIO_LOCAL_BUS, + CLK_TOP_MAINPLL_D7_D2, + CLK_SCP_ADSP_AUDIODSP, + CLK_TOP_AUDIO_H, + ADSP_CLK_MAX +}; + +int mt8195_adsp_init_clock(struct snd_sof_dev *sdev); +int adsp_clock_on(struct snd_sof_dev *sdev); +int adsp_clock_off(struct snd_sof_dev *sdev); +#endif diff --git a/sound/soc/sof/mediatek/mt8195/mt8195-loader.c b/sound/soc/sof/mediatek/mt8195/mt8195-loader.c new file mode 100644 index 000000000000..4be99ff4ebd3 --- /dev/null +++ b/sound/soc/sof/mediatek/mt8195/mt8195-loader.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// Copyright (c) 2021 Mediatek Corporation. All rights reserved. +// +// Author: YC Hung <yc.hung@mediatek.com> +// +// Hardware interface for mt8195 DSP code loader + +#include <sound/sof.h> +#include "mt8195.h" +#include "../../ops.h" + +void sof_hifixdsp_boot_sequence(struct snd_sof_dev *sdev, u32 boot_addr) +{ + /* ADSP bootup base */ + snd_sof_dsp_write(sdev, DSP_REG_BAR, DSP_ALTRESETVEC, boot_addr); + + /* pull high RunStall (set bit3 to 1) */ + snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, DSP_RESET_SW, + ADSP_RUNSTALL, ADSP_RUNSTALL); + + /* pull high StatVectorSel to use AltResetVec (set bit4 to 1) */ + snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, DSP_RESET_SW, + STATVECTOR_SEL, STATVECTOR_SEL); + + /* toggle DReset & BReset */ + /* pull high DReset & BReset */ + snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, DSP_RESET_SW, + ADSP_BRESET_SW | ADSP_DRESET_SW, + ADSP_BRESET_SW | ADSP_DRESET_SW); + + /* delay 10 DSP cycles at 26M about 1us by IP vendor's suggestion */ + udelay(1); + + /* pull low DReset & BReset */ + snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, DSP_RESET_SW, + ADSP_BRESET_SW | ADSP_DRESET_SW, + 0); + + /* Enable PDebug */ + snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, DSP_PDEBUGBUS0, + PDEBUG_ENABLE, + PDEBUG_ENABLE); + + /* release RunStall (set bit3 to 0) */ + snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, DSP_RESET_SW, + ADSP_RUNSTALL, 0); +} + +void sof_hifixdsp_shutdown(struct snd_sof_dev *sdev) +{ + /* RUN_STALL pull high again to reset */ + snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, DSP_RESET_SW, + ADSP_RUNSTALL, ADSP_RUNSTALL); + + /* pull high DReset & BReset */ + snd_sof_dsp_update_bits(sdev, DSP_REG_BAR, DSP_RESET_SW, + ADSP_BRESET_SW | ADSP_DRESET_SW, + ADSP_BRESET_SW | ADSP_DRESET_SW); +} + diff --git a/sound/soc/sof/mediatek/mt8195/mt8195.c b/sound/soc/sof/mediatek/mt8195/mt8195.c new file mode 100644 index 000000000000..3c81e84fcecf --- /dev/null +++ b/sound/soc/sof/mediatek/mt8195/mt8195.c @@ -0,0 +1,713 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// Copyright(c) 2021 Mediatek Inc. All rights reserved. +// +// Author: YC Hung <yc.hung@mediatek.com> +// + +/* + * Hardware interface for audio DSP on mt8195 + */ + +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/io.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/of_reserved_mem.h> +#include <linux/module.h> + +#include <sound/sof.h> +#include <sound/sof/xtensa.h> +#include "../../ops.h" +#include "../../sof-of-dev.h" +#include "../../sof-audio.h" +#include "../adsp_helper.h" +#include "../mtk-adsp-common.h" +#include "mt8195.h" +#include "mt8195-clk.h" + +static int mt8195_get_mailbox_offset(struct snd_sof_dev *sdev) +{ + return MBOX_OFFSET; +} + +static int mt8195_get_window_offset(struct snd_sof_dev *sdev, u32 id) +{ + return MBOX_OFFSET; +} + +static int mt8195_send_msg(struct snd_sof_dev *sdev, + struct snd_sof_ipc_msg *msg) +{ + struct adsp_priv *priv = sdev->pdata->hw_pdata; + + sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data, + msg->msg_size); + + return mtk_adsp_ipc_send(priv->dsp_ipc, MTK_ADSP_IPC_REQ, MTK_ADSP_IPC_OP_REQ); +} + +static void mt8195_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 void mt8195_dsp_handle_reply(struct mtk_adsp_ipc *ipc) +{ + struct adsp_priv *priv = mtk_adsp_ipc_get_data(ipc); + unsigned long flags; + + spin_lock_irqsave(&priv->sdev->ipc_lock, flags); + mt8195_get_reply(priv->sdev); + snd_sof_ipc_reply(priv->sdev, 0); + spin_unlock_irqrestore(&priv->sdev->ipc_lock, flags); +} + +static void mt8195_dsp_handle_request(struct mtk_adsp_ipc *ipc) +{ + struct adsp_priv *priv = mtk_adsp_ipc_get_data(ipc); + u32 p; /* panic code */ + int ret; + + /* Read the message from the debug box. */ + sof_mailbox_read(priv->sdev, priv->sdev->debug_box.offset + 4, + &p, sizeof(p)); + + /* Check to see if the message is a panic code 0x0dead*** */ + if ((p & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) { + snd_sof_dsp_panic(priv->sdev, p, true); + } else { + snd_sof_ipc_msgs_rx(priv->sdev); + + /* tell DSP cmd is done */ + ret = mtk_adsp_ipc_send(priv->dsp_ipc, MTK_ADSP_IPC_RSP, MTK_ADSP_IPC_OP_RSP); + if (ret) + dev_err(priv->dev, "request send ipc failed"); + } +} + +static struct mtk_adsp_ipc_ops dsp_ops = { + .handle_reply = mt8195_dsp_handle_reply, + .handle_request = mt8195_dsp_handle_request, +}; + +static int platform_parse_resource(struct platform_device *pdev, void *data) +{ + struct resource *mmio; + struct resource res; + struct device_node *mem_region; + struct device *dev = &pdev->dev; + struct mtk_adsp_chip_info *adsp = data; + int ret; + + mem_region = of_parse_phandle(dev->of_node, "memory-region", 0); + if (!mem_region) { + dev_err(dev, "no dma memory-region phandle\n"); + return -ENODEV; + } + + ret = of_address_to_resource(mem_region, 0, &res); + of_node_put(mem_region); + if (ret) { + dev_err(dev, "of_address_to_resource dma failed\n"); + return ret; + } + + dev_dbg(dev, "DMA %pR\n", &res); + + adsp->pa_shared_dram = (phys_addr_t)res.start; + adsp->shared_size = resource_size(&res); + if (adsp->pa_shared_dram & DRAM_REMAP_MASK) { + dev_err(dev, "adsp shared dma memory(%#x) is not 4K-aligned\n", + (u32)adsp->pa_shared_dram); + return -EINVAL; + } + + ret = of_reserved_mem_device_init(dev); + if (ret) { + dev_err(dev, "of_reserved_mem_device_init failed\n"); + return ret; + } + + mem_region = of_parse_phandle(dev->of_node, "memory-region", 1); + if (!mem_region) { + dev_err(dev, "no memory-region sysmem phandle\n"); + return -ENODEV; + } + + ret = of_address_to_resource(mem_region, 0, &res); + of_node_put(mem_region); + if (ret) { + dev_err(dev, "of_address_to_resource sysmem failed\n"); + return ret; + } + + adsp->pa_dram = (phys_addr_t)res.start; + adsp->dramsize = resource_size(&res); + if (adsp->pa_dram & DRAM_REMAP_MASK) { + dev_err(dev, "adsp memory(%#x) is not 4K-aligned\n", + (u32)adsp->pa_dram); + return -EINVAL; + } + + if (adsp->dramsize < TOTAL_SIZE_SHARED_DRAM_FROM_TAIL) { + dev_err(dev, "adsp memory(%#x) is not enough for share\n", + adsp->dramsize); + return -EINVAL; + } + + dev_dbg(dev, "dram pbase=%pa, dramsize=%#x\n", + &adsp->pa_dram, adsp->dramsize); + + /* Parse CFG base */ + mmio = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cfg"); + if (!mmio) { + dev_err(dev, "no ADSP-CFG register resource\n"); + return -ENXIO; + } + /* remap for DSP register accessing */ + adsp->va_cfgreg = devm_ioremap_resource(dev, mmio); + if (IS_ERR(adsp->va_cfgreg)) + return PTR_ERR(adsp->va_cfgreg); + + adsp->pa_cfgreg = (phys_addr_t)mmio->start; + adsp->cfgregsize = resource_size(mmio); + + dev_dbg(dev, "cfgreg-vbase=%p, cfgregsize=%#x\n", + adsp->va_cfgreg, adsp->cfgregsize); + + /* Parse SRAM */ + mmio = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sram"); + if (!mmio) { + dev_err(dev, "no SRAM resource\n"); + return -ENXIO; + } + + adsp->pa_sram = (phys_addr_t)mmio->start; + adsp->sramsize = resource_size(mmio); + if (adsp->sramsize < TOTAL_SIZE_SHARED_SRAM_FROM_TAIL) { + dev_err(dev, "adsp SRAM(%#x) is not enough for share\n", + adsp->sramsize); + return -EINVAL; + } + + dev_dbg(dev, "sram pbase=%pa,%#x\n", &adsp->pa_sram, adsp->sramsize); + + return ret; +} + +static int adsp_sram_power_on(struct device *dev, bool on) +{ + void __iomem *va_dspsysreg; + u32 srampool_con; + + va_dspsysreg = ioremap(ADSP_SRAM_POOL_CON, 0x4); + if (!va_dspsysreg) { + dev_err(dev, "failed to ioremap sram pool base %#x\n", + ADSP_SRAM_POOL_CON); + return -ENOMEM; + } + + srampool_con = readl(va_dspsysreg); + if (on) + writel(srampool_con & ~DSP_SRAM_POOL_PD_MASK, va_dspsysreg); + else + writel(srampool_con | DSP_SRAM_POOL_PD_MASK, va_dspsysreg); + + iounmap(va_dspsysreg); + return 0; +} + +/* Init the basic DSP DRAM address */ +static int adsp_memory_remap_init(struct device *dev, struct mtk_adsp_chip_info *adsp) +{ + void __iomem *vaddr_emi_map; + int offset; + + if (!adsp) + return -ENXIO; + + vaddr_emi_map = devm_ioremap(dev, DSP_EMI_MAP_ADDR, 0x4); + if (!vaddr_emi_map) { + dev_err(dev, "failed to ioremap emi map base %#x\n", + DSP_EMI_MAP_ADDR); + return -ENOMEM; + } + + offset = adsp->pa_dram - DRAM_PHYS_BASE_FROM_DSP_VIEW; + adsp->dram_offset = offset; + offset >>= DRAM_REMAP_SHIFT; + dev_dbg(dev, "adsp->pa_dram %pa, offset %#x\n", &adsp->pa_dram, offset); + writel(offset, vaddr_emi_map); + if (offset != readl(vaddr_emi_map)) { + dev_err(dev, "write emi map fail : %#x\n", readl(vaddr_emi_map)); + return -EIO; + } + + return 0; +} + +static int adsp_shared_base_ioremap(struct platform_device *pdev, void *data) +{ + struct device *dev = &pdev->dev; + struct mtk_adsp_chip_info *adsp = data; + + /* remap shared-dram base to be non-cachable */ + adsp->shared_dram = devm_ioremap(dev, adsp->pa_shared_dram, + adsp->shared_size); + if (!adsp->shared_dram) { + dev_err(dev, "failed to ioremap base %pa size %#x\n", + adsp->shared_dram, adsp->shared_size); + return -ENOMEM; + } + + dev_dbg(dev, "shared-dram vbase=%p, phy addr :%pa, size=%#x\n", + adsp->shared_dram, &adsp->pa_shared_dram, adsp->shared_size); + + return 0; +} + +static int mt8195_run(struct snd_sof_dev *sdev) +{ + u32 adsp_bootup_addr; + + adsp_bootup_addr = SRAM_PHYS_BASE_FROM_DSP_VIEW; + dev_dbg(sdev->dev, "HIFIxDSP boot from base : 0x%08X\n", adsp_bootup_addr); + sof_hifixdsp_boot_sequence(sdev, adsp_bootup_addr); + + return 0; +} + +static int mt8195_dsp_probe(struct snd_sof_dev *sdev) +{ + struct platform_device *pdev = container_of(sdev->dev, struct platform_device, dev); + struct adsp_priv *priv; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + sdev->pdata->hw_pdata = priv; + priv->dev = sdev->dev; + priv->sdev = sdev; + + priv->adsp = devm_kzalloc(&pdev->dev, sizeof(struct mtk_adsp_chip_info), GFP_KERNEL); + if (!priv->adsp) + return -ENOMEM; + + ret = platform_parse_resource(pdev, priv->adsp); + if (ret) + return ret; + + ret = mt8195_adsp_init_clock(sdev); + if (ret) { + dev_err(sdev->dev, "mt8195_adsp_init_clock failed\n"); + return -EINVAL; + } + + ret = adsp_clock_on(sdev); + if (ret) { + dev_err(sdev->dev, "adsp_clock_on fail!\n"); + return -EINVAL; + } + + ret = adsp_sram_power_on(sdev->dev, true); + if (ret) { + dev_err(sdev->dev, "adsp_sram_power_on fail!\n"); + goto exit_clk_disable; + } + + ret = adsp_memory_remap_init(&pdev->dev, priv->adsp); + if (ret) { + dev_err(sdev->dev, "adsp_memory_remap_init fail!\n"); + goto err_adsp_sram_power_off; + } + + sdev->bar[SOF_FW_BLK_TYPE_IRAM] = devm_ioremap(sdev->dev, + priv->adsp->pa_sram, + priv->adsp->sramsize); + if (!sdev->bar[SOF_FW_BLK_TYPE_IRAM]) { + dev_err(sdev->dev, "failed to ioremap base %pa size %#x\n", + &priv->adsp->pa_sram, priv->adsp->sramsize); + ret = -EINVAL; + goto err_adsp_sram_power_off; + } + + priv->adsp->va_sram = sdev->bar[SOF_FW_BLK_TYPE_IRAM]; + + sdev->bar[SOF_FW_BLK_TYPE_SRAM] = devm_ioremap(sdev->dev, + priv->adsp->pa_dram, + priv->adsp->dramsize); + if (!sdev->bar[SOF_FW_BLK_TYPE_SRAM]) { + dev_err(sdev->dev, "failed to ioremap base %pa size %#x\n", + &priv->adsp->pa_dram, priv->adsp->dramsize); + ret = -EINVAL; + goto err_adsp_sram_power_off; + } + priv->adsp->va_dram = sdev->bar[SOF_FW_BLK_TYPE_SRAM]; + + ret = adsp_shared_base_ioremap(pdev, priv->adsp); + if (ret) { + dev_err(sdev->dev, "adsp_shared_base_ioremap fail!\n"); + goto err_adsp_sram_power_off; + } + + sdev->bar[DSP_REG_BAR] = priv->adsp->va_cfgreg; + + sdev->mmio_bar = SOF_FW_BLK_TYPE_SRAM; + sdev->mailbox_bar = SOF_FW_BLK_TYPE_SRAM; + + /* set default mailbox offset for FW ready message */ + sdev->dsp_box.offset = mt8195_get_mailbox_offset(sdev); + + priv->ipc_dev = platform_device_register_data(&pdev->dev, "mtk-adsp-ipc", + PLATFORM_DEVID_NONE, + pdev, sizeof(*pdev)); + if (IS_ERR(priv->ipc_dev)) { + ret = PTR_ERR(priv->ipc_dev); + dev_err(sdev->dev, "failed to register mtk-adsp-ipc device\n"); + goto err_adsp_sram_power_off; + } + + priv->dsp_ipc = dev_get_drvdata(&priv->ipc_dev->dev); + if (!priv->dsp_ipc) { + ret = -EPROBE_DEFER; + dev_err(sdev->dev, "failed to get drvdata\n"); + goto exit_pdev_unregister; + } + + mtk_adsp_ipc_set_data(priv->dsp_ipc, priv); + priv->dsp_ipc->ops = &dsp_ops; + + return 0; + +exit_pdev_unregister: + platform_device_unregister(priv->ipc_dev); +err_adsp_sram_power_off: + adsp_sram_power_on(&pdev->dev, false); +exit_clk_disable: + adsp_clock_off(sdev); + + return ret; +} + +static int mt8195_dsp_shutdown(struct snd_sof_dev *sdev) +{ + return snd_sof_suspend(sdev->dev); +} + +static int mt8195_dsp_remove(struct snd_sof_dev *sdev) +{ + struct platform_device *pdev = container_of(sdev->dev, struct platform_device, dev); + struct adsp_priv *priv = sdev->pdata->hw_pdata; + + platform_device_unregister(priv->ipc_dev); + adsp_sram_power_on(&pdev->dev, false); + adsp_clock_off(sdev); + + return 0; +} + +static int mt8195_dsp_suspend(struct snd_sof_dev *sdev, u32 target_state) +{ + struct platform_device *pdev = container_of(sdev->dev, struct platform_device, dev); + int ret; + u32 reset_sw, dbg_pc; + + /* wait dsp enter idle, timeout is 1 second */ + ret = snd_sof_dsp_read_poll_timeout(sdev, DSP_REG_BAR, + DSP_RESET_SW, reset_sw, + ((reset_sw & ADSP_PWAIT) == ADSP_PWAIT), + SUSPEND_DSP_IDLE_POLL_INTERVAL_US, + SUSPEND_DSP_IDLE_TIMEOUT_US); + if (ret < 0) { + dbg_pc = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGPC); + dev_warn(sdev->dev, "dsp not idle, powering off anyway : swrest %#x, pc %#x, ret %d\n", + reset_sw, dbg_pc, ret); + } + + /* stall and reset dsp */ + sof_hifixdsp_shutdown(sdev); + + /* power down adsp sram */ + ret = adsp_sram_power_on(&pdev->dev, false); + if (ret) { + dev_err(sdev->dev, "adsp_sram_power_off fail!\n"); + return ret; + } + + /* turn off adsp clock */ + return adsp_clock_off(sdev); +} + +static int mt8195_dsp_resume(struct snd_sof_dev *sdev) +{ + int ret; + + /* turn on adsp clock */ + ret = adsp_clock_on(sdev); + if (ret) { + dev_err(sdev->dev, "adsp_clock_on fail!\n"); + return ret; + } + + /* power on adsp sram */ + ret = adsp_sram_power_on(sdev->dev, true); + if (ret) + dev_err(sdev->dev, "adsp_sram_power_on fail!\n"); + + return ret; +} + +/* on mt8195 there is 1 to 1 match between type and BAR idx */ +static int mt8195_get_bar_index(struct snd_sof_dev *sdev, u32 type) +{ + return type; +} + +static int mt8195_pcm_hw_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_sof_platform_stream_params *platform_params) +{ + platform_params->cont_update_posn = 1; + + return 0; +} + +static snd_pcm_uframes_t mt8195_pcm_pointer(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + int ret; + snd_pcm_uframes_t pos; + struct snd_sof_pcm *spcm; + struct sof_ipc_stream_posn posn; + struct snd_sof_pcm_stream *stream; + struct snd_soc_component *scomp = sdev->component; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + + spcm = snd_sof_find_spcm_dai(scomp, rtd); + if (!spcm) { + dev_warn_ratelimited(sdev->dev, "warn: can't find PCM with DAI ID %d\n", + rtd->dai_link->id); + return 0; + } + + stream = &spcm->stream[substream->stream]; + ret = snd_sof_ipc_msg_data(sdev, stream->substream, &posn, sizeof(posn)); + if (ret < 0) { + dev_warn(sdev->dev, "failed to read stream position: %d\n", ret); + return 0; + } + + memcpy(&stream->posn, &posn, sizeof(posn)); + pos = spcm->stream[substream->stream].posn.host_posn; + pos = bytes_to_frames(substream->runtime, pos); + + return pos; +} + +static void mt8195_adsp_dump(struct snd_sof_dev *sdev, u32 flags) +{ + u32 dbg_pc, dbg_data, dbg_bus0, dbg_bus1, dbg_inst; + u32 dbg_ls0stat, dbg_ls1stat, faultbus, faultinfo, swrest; + + /* dump debug registers */ + dbg_pc = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGPC); + dbg_data = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGDATA); + dbg_bus0 = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGBUS0); + dbg_bus1 = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGBUS1); + dbg_inst = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGINST); + dbg_ls0stat = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGLS0STAT); + dbg_ls1stat = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGLS1STAT); + faultbus = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PFAULTBUS); + faultinfo = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PFAULTINFO); + swrest = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_RESET_SW); + + dev_info(sdev->dev, "adsp dump : pc %#x, data %#x, bus0 %#x, bus1 %#x, swrest %#x", + dbg_pc, dbg_data, dbg_bus0, dbg_bus1, swrest); + dev_info(sdev->dev, "dbg_inst %#x, ls0stat %#x, ls1stat %#x, faultbus %#x, faultinfo %#x", + dbg_inst, dbg_ls0stat, dbg_ls1stat, faultbus, faultinfo); + + mtk_adsp_dump(sdev, flags); +} + +static struct snd_soc_dai_driver mt8195_dai[] = { +{ + .name = "SOF_DL2", + .playback = { + .channels_min = 1, + .channels_max = 2, + }, +}, +{ + .name = "SOF_DL3", + .playback = { + .channels_min = 1, + .channels_max = 2, + }, +}, +{ + .name = "SOF_UL4", + .capture = { + .channels_min = 1, + .channels_max = 2, + }, +}, +{ + .name = "SOF_UL5", + .capture = { + .channels_min = 1, + .channels_max = 2, + }, +}, +}; + +/* mt8195 ops */ +static struct snd_sof_dsp_ops sof_mt8195_ops = { + /* probe and remove */ + .probe = mt8195_dsp_probe, + .remove = mt8195_dsp_remove, + .shutdown = mt8195_dsp_shutdown, + + /* DSP core boot */ + .run = mt8195_run, + + /* Block IO */ + .block_read = sof_block_read, + .block_write = sof_block_write, + + /* Mailbox IO */ + .mailbox_read = sof_mailbox_read, + .mailbox_write = sof_mailbox_write, + + /* Register IO */ + .write = sof_io_write, + .read = sof_io_read, + .write64 = sof_io_write64, + .read64 = sof_io_read64, + + /* ipc */ + .send_msg = mt8195_send_msg, + .get_mailbox_offset = mt8195_get_mailbox_offset, + .get_window_offset = mt8195_get_window_offset, + .ipc_msg_data = sof_ipc_msg_data, + .set_stream_data_offset = sof_set_stream_data_offset, + + /* misc */ + .get_bar_index = mt8195_get_bar_index, + + /* stream callbacks */ + .pcm_open = sof_stream_pcm_open, + .pcm_hw_params = mt8195_pcm_hw_params, + .pcm_pointer = mt8195_pcm_pointer, + .pcm_close = sof_stream_pcm_close, + + /* firmware loading */ + .load_firmware = snd_sof_load_firmware_memcpy, + + /* Firmware ops */ + .dsp_arch_ops = &sof_xtensa_arch_ops, + + /* Debug information */ + .dbg_dump = mt8195_adsp_dump, + + /* DAI drivers */ + .drv = mt8195_dai, + .num_drv = ARRAY_SIZE(mt8195_dai), + + /* PM */ + .suspend = mt8195_dsp_suspend, + .resume = mt8195_dsp_resume, + + /* ALSA HW info flags */ + .hw_info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, +}; + +static struct snd_sof_of_mach sof_mt8195_machs[] = { + { + .compatible = "google,tomato", + .sof_tplg_filename = "sof-mt8195-mt6359-rt1019-rt5682-dts.tplg" + }, { + .compatible = "mediatek,mt8195", + .sof_tplg_filename = "sof-mt8195.tplg" + }, { + /* sentinel */ + } +}; + +static const struct sof_dev_desc sof_of_mt8195_desc = { + .of_machines = sof_mt8195_machs, + .ipc_supported_mask = BIT(SOF_IPC), + .ipc_default = SOF_IPC, + .default_fw_path = { + [SOF_IPC] = "mediatek/sof", + }, + .default_tplg_path = { + [SOF_IPC] = "mediatek/sof-tplg", + }, + .default_fw_filename = { + [SOF_IPC] = "sof-mt8195.ri", + }, + .nocodec_tplg_filename = "sof-mt8195-nocodec.tplg", + .ops = &sof_mt8195_ops, + .ipc_timeout = 1000, +}; + +static const struct of_device_id sof_of_mt8195_ids[] = { + { .compatible = "mediatek,mt8195-dsp", .data = &sof_of_mt8195_desc}, + { } +}; +MODULE_DEVICE_TABLE(of, sof_of_mt8195_ids); + +/* DT driver definition */ +static struct platform_driver snd_sof_of_mt8195_driver = { + .probe = sof_of_probe, + .remove = sof_of_remove, + .shutdown = sof_of_shutdown, + .driver = { + .name = "sof-audio-of-mt8195", + .pm = &sof_of_pm, + .of_match_table = sof_of_mt8195_ids, + }, +}; +module_platform_driver(snd_sof_of_mt8195_driver); + +MODULE_IMPORT_NS(SND_SOC_SOF_XTENSA); +MODULE_IMPORT_NS(SND_SOC_SOF_MTK_COMMON); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/mediatek/mt8195/mt8195.h b/sound/soc/sof/mediatek/mt8195/mt8195.h new file mode 100644 index 000000000000..7ffd523f936c --- /dev/null +++ b/sound/soc/sof/mediatek/mt8195/mt8195.h @@ -0,0 +1,163 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * Copyright (c) 2021 MediaTek Corporation. All rights reserved. + * + * Header file for the mt8195 DSP register definition + */ + +#ifndef __MT8195_H +#define __MT8195_H + +struct mtk_adsp_chip_info; +struct snd_sof_dev; + +#define DSP_REG_BASE 0x10803000 +#define SCP_CFGREG_BASE 0x10724000 +#define DSP_SYSAO_BASE 0x1080C000 + +/***************************************************************************** + * R E G I S T E R TABLE + *****************************************************************************/ +#define DSP_JTAGMUX 0x0000 +#define DSP_ALTRESETVEC 0x0004 +#define DSP_PDEBUGDATA 0x0008 +#define DSP_PDEBUGBUS0 0x000c +#define PDEBUG_ENABLE BIT(0) +#define DSP_PDEBUGBUS1 0x0010 +#define DSP_PDEBUGINST 0x0014 +#define DSP_PDEBUGLS0STAT 0x0018 +#define DSP_PDEBUGLS1STAT 0x001c +#define DSP_PDEBUGPC 0x0020 +#define DSP_RESET_SW 0x0024 /*reset sw*/ +#define ADSP_BRESET_SW BIT(0) +#define ADSP_DRESET_SW BIT(1) +#define ADSP_RUNSTALL BIT(3) +#define STATVECTOR_SEL BIT(4) +#define ADSP_PWAIT BIT(16) +#define DSP_PFAULTBUS 0x0028 +#define DSP_PFAULTINFO 0x002c +#define DSP_GPR00 0x0030 +#define DSP_GPR01 0x0034 +#define DSP_GPR02 0x0038 +#define DSP_GPR03 0x003c +#define DSP_GPR04 0x0040 +#define DSP_GPR05 0x0044 +#define DSP_GPR06 0x0048 +#define DSP_GPR07 0x004c +#define DSP_GPR08 0x0050 +#define DSP_GPR09 0x0054 +#define DSP_GPR0A 0x0058 +#define DSP_GPR0B 0x005c +#define DSP_GPR0C 0x0060 +#define DSP_GPR0D 0x0064 +#define DSP_GPR0E 0x0068 +#define DSP_GPR0F 0x006c +#define DSP_GPR10 0x0070 +#define DSP_GPR11 0x0074 +#define DSP_GPR12 0x0078 +#define DSP_GPR13 0x007c +#define DSP_GPR14 0x0080 +#define DSP_GPR15 0x0084 +#define DSP_GPR16 0x0088 +#define DSP_GPR17 0x008c +#define DSP_GPR18 0x0090 +#define DSP_GPR19 0x0094 +#define DSP_GPR1A 0x0098 +#define DSP_GPR1B 0x009c +#define DSP_GPR1C 0x00a0 +#define DSP_GPR1D 0x00a4 +#define DSP_GPR1E 0x00a8 +#define DSP_GPR1F 0x00ac +#define DSP_TCM_OFFSET 0x00b0 /* not used */ +#define DSP_DDR_OFFSET 0x00b4 /* not used */ +#define DSP_INTFDSP 0x00d0 +#define DSP_INTFDSP_CLR 0x00d4 +#define DSP_SRAM_PD_SW1 0x00d8 +#define DSP_SRAM_PD_SW2 0x00dc +#define DSP_OCD 0x00e0 +#define DSP_RG_DSP_IRQ_POL 0x00f0 /* not used */ +#define DSP_DSP_IRQ_EN 0x00f4 /* not used */ +#define DSP_DSP_IRQ_LEVEL 0x00f8 /* not used */ +#define DSP_DSP_IRQ_STATUS 0x00fc /* not used */ +#define DSP_RG_INT2CIRQ 0x0114 +#define DSP_RG_INT_POL_CTL0 0x0120 +#define DSP_RG_INT_EN_CTL0 0x0130 +#define DSP_RG_INT_LV_CTL0 0x0140 +#define DSP_RG_INT_STATUS0 0x0150 +#define DSP_PDEBUGSTATUS0 0x0200 +#define DSP_PDEBUGSTATUS1 0x0204 +#define DSP_PDEBUGSTATUS2 0x0208 +#define DSP_PDEBUGSTATUS3 0x020c +#define DSP_PDEBUGSTATUS4 0x0210 +#define DSP_PDEBUGSTATUS5 0x0214 +#define DSP_PDEBUGSTATUS6 0x0218 +#define DSP_PDEBUGSTATUS7 0x021c +#define DSP_DSP2PSRAM_PRIORITY 0x0220 /* not used */ +#define DSP_AUDIO_DSP2SPM_INT 0x0224 +#define DSP_AUDIO_DSP2SPM_INT_ACK 0x0228 +#define DSP_AUDIO_DSP_DEBUG_SEL 0x022C +#define DSP_AUDIO_DSP_EMI_BASE_ADDR 0x02E0 /* not used */ +#define DSP_AUDIO_DSP_SHARED_IRAM 0x02E4 +#define DSP_AUDIO_DSP_CKCTRL_P2P_CK_CON 0x02F0 +#define DSP_RG_SEMAPHORE00 0x0300 +#define DSP_RG_SEMAPHORE01 0x0304 +#define DSP_RG_SEMAPHORE02 0x0308 +#define DSP_RG_SEMAPHORE03 0x030C +#define DSP_RG_SEMAPHORE04 0x0310 +#define DSP_RG_SEMAPHORE05 0x0314 +#define DSP_RG_SEMAPHORE06 0x0318 +#define DSP_RG_SEMAPHORE07 0x031C +#define DSP_RESERVED_0 0x03F0 +#define DSP_RESERVED_1 0x03F4 + +/* dsp wdt */ +#define DSP_WDT_MODE 0x0400 + +/* dsp mbox */ +#define DSP_MBOX_IN_CMD 0x00 +#define DSP_MBOX_IN_CMD_CLR 0x04 +#define DSP_MBOX_OUT_CMD 0x1c +#define DSP_MBOX_OUT_CMD_CLR 0x20 +#define DSP_MBOX_IN_MSG0 0x08 +#define DSP_MBOX_IN_MSG1 0x0C +#define DSP_MBOX_OUT_MSG0 0x24 +#define DSP_MBOX_OUT_MSG1 0x28 + +/*dsp sys ao*/ +#define ADSP_SRAM_POOL_CON (DSP_SYSAO_BASE + 0x30) +#define DSP_SRAM_POOL_PD_MASK 0xf +#define DSP_EMI_MAP_ADDR (DSP_SYSAO_BASE + 0x81c) + +/* DSP memories */ +#define MBOX_OFFSET 0x800000 /* DRAM */ +#define MBOX_SIZE 0x1000 /* consistent with which in memory.h of sof fw */ +#define DSP_DRAM_SIZE 0x1000000 /* 16M */ + +#define DSP_REG_BAR 4 +#define DSP_MBOX0_BAR 5 +#define DSP_MBOX1_BAR 6 +#define DSP_MBOX2_BAR 7 + +#define TOTAL_SIZE_SHARED_SRAM_FROM_TAIL 0x0 + +#define SIZE_SHARED_DRAM_DL 0x40000 /*Shared buffer for Downlink*/ +#define SIZE_SHARED_DRAM_UL 0x40000 /*Shared buffer for Uplink*/ + +#define TOTAL_SIZE_SHARED_DRAM_FROM_TAIL \ + (SIZE_SHARED_DRAM_DL + SIZE_SHARED_DRAM_UL) + +#define SRAM_PHYS_BASE_FROM_DSP_VIEW 0x40000000 /* MT8195 DSP view */ +#define DRAM_PHYS_BASE_FROM_DSP_VIEW 0x60000000 /* MT8195 DSP view */ + +/*remap dram between AP and DSP view, 4KB aligned*/ +#define DRAM_REMAP_SHIFT 12 +#define DRAM_REMAP_MASK (BIT(DRAM_REMAP_SHIFT) - 1) + +/* suspend dsp idle check interval and timeout */ +#define SUSPEND_DSP_IDLE_TIMEOUT_US 1000000 /* timeout to wait dsp idle, 1 sec */ +#define SUSPEND_DSP_IDLE_POLL_INTERVAL_US 500 /* 0.5 msec */ + +void sof_hifixdsp_boot_sequence(struct snd_sof_dev *sdev, u32 boot_addr); +void sof_hifixdsp_shutdown(struct snd_sof_dev *sdev); +#endif diff --git a/sound/soc/sof/mediatek/mtk-adsp-common.c b/sound/soc/sof/mediatek/mtk-adsp-common.c new file mode 100644 index 000000000000..1e0769c668a7 --- /dev/null +++ b/sound/soc/sof/mediatek/mtk-adsp-common.c @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2022 MediaTek Inc. All rights reserved. +// +// Author: YC Hung <yc.hung@mediatek.com> + +/* + * Common helpers for the audio DSP on MediaTek platforms + */ + +#include <linux/module.h> +#include <sound/sof/xtensa.h> +#include "../ops.h" +#include "mtk-adsp-common.h" + +/** + * mtk_adsp_get_registers() - This function is called in case of DSP oops + * in order to gather information about the registers, filename and + * linenumber and stack. + * @sdev: SOF device + * @xoops: Stores information about registers. + * @panic_info: Stores information about filename and line number. + * @stack: Stores the stack dump. + * @stack_words: Size of the stack dump. + */ +static void mtk_adsp_get_registers(struct snd_sof_dev *sdev, + struct sof_ipc_dsp_oops_xtensa *xoops, + struct sof_ipc_panic_info *panic_info, + u32 *stack, size_t stack_words) +{ + u32 offset = sdev->dsp_oops_offset; + + /* first read registers */ + sof_mailbox_read(sdev, offset, xoops, sizeof(*xoops)); + + /* then get panic info */ + if (xoops->arch_hdr.totalsize > EXCEPT_MAX_HDR_SIZE) { + dev_err(sdev->dev, "invalid header size 0x%x\n", + xoops->arch_hdr.totalsize); + return; + } + offset += xoops->arch_hdr.totalsize; + sof_mailbox_read(sdev, offset, panic_info, sizeof(*panic_info)); + + /* then get the stack */ + offset += sizeof(*panic_info); + sof_mailbox_read(sdev, offset, stack, stack_words * sizeof(u32)); +} + +/** + * mtk_adsp_dump() - This function is called when a panic message is + * received from the firmware. + * @sdev: SOF device + * @flags: parameter not used but required by ops prototype + */ +void mtk_adsp_dump(struct snd_sof_dev *sdev, u32 flags) +{ + char *level = (flags & SOF_DBG_DUMP_OPTIONAL) ? KERN_DEBUG : KERN_ERR; + struct sof_ipc_dsp_oops_xtensa xoops; + struct sof_ipc_panic_info panic_info; + u32 stack[MTK_ADSP_STACK_DUMP_SIZE]; + u32 status; + + /* Get information about the panic status from the debug box area. + * Compute the trace point based on the status. + */ + sof_mailbox_read(sdev, sdev->debug_box.offset + 0x4, &status, 4); + + /* Get information about the registers, the filename and line + * number and the stack. + */ + mtk_adsp_get_registers(sdev, &xoops, &panic_info, stack, + MTK_ADSP_STACK_DUMP_SIZE); + + /* Print the information to the console */ + sof_print_oops_and_stack(sdev, level, status, status, &xoops, &panic_info, + stack, MTK_ADSP_STACK_DUMP_SIZE); +} +EXPORT_SYMBOL(mtk_adsp_dump); + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/mediatek/mtk-adsp-common.h b/sound/soc/sof/mediatek/mtk-adsp-common.h new file mode 100644 index 000000000000..612cff1f38f7 --- /dev/null +++ b/sound/soc/sof/mediatek/mtk-adsp-common.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ + +#ifndef __MEDIATEK_ADSP_COMMON_H__ +#define __MEDIATEK_ADSP_COMMON_H__ + +#define EXCEPT_MAX_HDR_SIZE 0x400 +#define MTK_ADSP_STACK_DUMP_SIZE 32 + +void mtk_adsp_dump(struct snd_sof_dev *sdev, u32 flags); +#endif diff --git a/sound/soc/sof/nocodec.c b/sound/soc/sof/nocodec.c index 356497fe4f4c..3537805070ad 100644 --- a/sound/soc/sof/nocodec.c +++ b/sound/soc/sof/nocodec.c @@ -32,7 +32,7 @@ static int sof_nocodec_bes_setup(struct device *dev, /* set up BE dai_links */ for (i = 0; i < link_num; i++) { - dlc = devm_kzalloc(dev, 3 * sizeof(*dlc), GFP_KERNEL); + dlc = devm_kcalloc(dev, 3, sizeof(*dlc), GFP_KERNEL); if (!dlc) return -ENOMEM; @@ -78,7 +78,7 @@ static int sof_nocodec_setup(struct device *dev, struct snd_soc_dai_link *links; /* create dummy BE dai_links */ - links = devm_kzalloc(dev, sizeof(struct snd_soc_dai_link) * num_dai_drivers, GFP_KERNEL); + links = devm_kcalloc(dev, num_dai_drivers, sizeof(struct snd_soc_dai_link), GFP_KERNEL); if (!links) return -ENOMEM; diff --git a/sound/soc/sof/ops.c b/sound/soc/sof/ops.c index 160b88a2d59f..ff066de4ceb9 100644 --- a/sound/soc/sof/ops.c +++ b/sound/soc/sof/ops.c @@ -142,25 +142,46 @@ void snd_sof_dsp_update_bits_forced(struct snd_sof_dev *sdev, u32 bar, } EXPORT_SYMBOL(snd_sof_dsp_update_bits_forced); -void snd_sof_dsp_panic(struct snd_sof_dev *sdev, u32 offset) +/** + * snd_sof_dsp_panic - handle a received DSP panic message + * @sdev: Pointer to the device's sdev + * @offset: offset of panic information + * @non_recoverable: the panic is fatal, no recovery will be done by the caller + */ +void snd_sof_dsp_panic(struct snd_sof_dev *sdev, u32 offset, bool non_recoverable) { - dev_err(sdev->dev, "error : DSP panic!\n"); - /* - * check if DSP is not ready and did not set the dsp_oops_offset. - * if the dsp_oops_offset is not set, set it from the panic message. - * Also add a check to memory window setting with panic message. + * if DSP is not ready and the dsp_oops_offset is not yet set, use the + * offset from the panic message. */ if (!sdev->dsp_oops_offset) sdev->dsp_oops_offset = offset; - else - dev_dbg(sdev->dev, "panic: dsp_oops_offset %zu offset %d\n", - sdev->dsp_oops_offset, offset); - /* We want to see the DSP panic! */ - sdev->dbg_dump_printed = false; + /* + * Print warning if the offset from the panic message differs from + * dsp_oops_offset + */ + if (sdev->dsp_oops_offset != offset) + dev_warn(sdev->dev, + "%s: dsp_oops_offset %zu differs from panic offset %u\n", + __func__, sdev->dsp_oops_offset, offset); - snd_sof_dsp_dbg_dump(sdev, SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX); - snd_sof_trace_notify_for_error(sdev); + /* + * Set the fw_state to crashed only in case of non recoverable DSP panic + * event. + * Use different message within the snd_sof_dsp_dbg_dump() depending on + * the non_recoverable flag. + */ + sdev->dbg_dump_printed = false; + if (non_recoverable) { + snd_sof_dsp_dbg_dump(sdev, "DSP panic!", + SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX); + sof_set_fw_state(sdev, SOF_FW_CRASHED); + sof_fw_trace_fw_crashed(sdev); + } else { + snd_sof_dsp_dbg_dump(sdev, + "DSP panic (recovery will be attempted)", + SOF_DBG_DUMP_REGS | SOF_DBG_DUMP_MBOX); + } } EXPORT_SYMBOL(snd_sof_dsp_panic); diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h index 09bf38fdfb8a..55d43adb6a29 100644 --- a/sound/soc/sof/ops.h +++ b/sound/soc/sof/ops.h @@ -21,6 +21,20 @@ #define sof_ops(sdev) \ ((sdev)->pdata->desc->ops) +static inline int sof_ops_init(struct snd_sof_dev *sdev) +{ + if (sdev->pdata->desc->ops_init) + return sdev->pdata->desc->ops_init(sdev); + + return 0; +} + +static inline void sof_ops_free(struct snd_sof_dev *sdev) +{ + if (sdev->pdata->desc->ops_free) + sdev->pdata->desc->ops_free(sdev); +} + /* Mandatory operations are verified during probing */ /* init */ @@ -72,35 +86,68 @@ static inline int snd_sof_dsp_reset(struct snd_sof_dev *sdev) return 0; } -/* dsp core power up/power down */ -static inline int snd_sof_dsp_core_power_up(struct snd_sof_dev *sdev, - unsigned int core_mask) +/* dsp core get/put */ +static inline int snd_sof_dsp_core_get(struct snd_sof_dev *sdev, int core) { - int ret = 0; + if (core > sdev->num_cores - 1) { + dev_err(sdev->dev, "invalid core id: %d for num_cores: %d\n", core, + sdev->num_cores); + return -EINVAL; + } + + if (sof_ops(sdev)->core_get) { + int ret; + + /* if current ref_count is > 0, increment it and return */ + if (sdev->dsp_core_ref_count[core] > 0) { + sdev->dsp_core_ref_count[core]++; + return 0; + } - core_mask &= ~sdev->enabled_cores_mask; - if (sof_ops(sdev)->core_power_up && core_mask) { - ret = sof_ops(sdev)->core_power_up(sdev, core_mask); - if (!ret) - sdev->enabled_cores_mask |= core_mask; + /* power up the core */ + ret = sof_ops(sdev)->core_get(sdev, core); + if (ret < 0) + return ret; + + /* increment ref_count */ + sdev->dsp_core_ref_count[core]++; + + /* and update enabled_cores_mask */ + sdev->enabled_cores_mask |= BIT(core); + + dev_dbg(sdev->dev, "Core %d powered up\n", core); } - return ret; + return 0; } -static inline int snd_sof_dsp_core_power_down(struct snd_sof_dev *sdev, - unsigned int core_mask) +static inline int snd_sof_dsp_core_put(struct snd_sof_dev *sdev, int core) { - int ret = 0; + if (core > sdev->num_cores - 1) { + dev_err(sdev->dev, "invalid core id: %d for num_cores: %d\n", core, + sdev->num_cores); + return -EINVAL; + } + + if (sof_ops(sdev)->core_put) { + int ret; - core_mask &= sdev->enabled_cores_mask; - if (sof_ops(sdev)->core_power_down && core_mask) { - ret = sof_ops(sdev)->core_power_down(sdev, core_mask); - if (!ret) - sdev->enabled_cores_mask &= ~core_mask; + /* decrement ref_count and return if it is > 0 */ + if (--(sdev->dsp_core_ref_count[core]) > 0) + return 0; + + /* power down the core */ + ret = sof_ops(sdev)->core_put(sdev, core); + if (ret < 0) + return ret; + + /* and update enabled_cores_mask */ + sdev->enabled_cores_mask &= ~BIT(core); + + dev_dbg(sdev->dev, "Core %d powered down\n", core); } - return ret; + return 0; } /* pre/post fw load */ @@ -241,7 +288,7 @@ snd_sof_dsp_set_power_state(struct snd_sof_dev *sdev, } /* debug */ -void snd_sof_dsp_dbg_dump(struct snd_sof_dev *sdev, u32 flags); +void snd_sof_dsp_dbg_dump(struct snd_sof_dev *sdev, const char *msg, u32 flags); static inline int snd_sof_debugfs_add_region_item(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type, u32 offset, size_t size, @@ -334,32 +381,6 @@ static inline int snd_sof_dsp_send_msg(struct snd_sof_dev *sdev, return sof_ops(sdev)->send_msg(sdev, msg); } -/* host DMA trace */ -static inline int snd_sof_dma_trace_init(struct snd_sof_dev *sdev, - u32 *stream_tag) -{ - if (sof_ops(sdev)->trace_init) - return sof_ops(sdev)->trace_init(sdev, stream_tag); - - return 0; -} - -static inline int snd_sof_dma_trace_release(struct snd_sof_dev *sdev) -{ - if (sof_ops(sdev)->trace_release) - return sof_ops(sdev)->trace_release(sdev); - - return 0; -} - -static inline int snd_sof_dma_trace_trigger(struct snd_sof_dev *sdev, int cmd) -{ - if (sof_ops(sdev)->trace_trigger) - return sof_ops(sdev)->trace_trigger(sdev, cmd); - - return 0; -} - /* host PCM ops */ static inline int snd_sof_pcm_platform_open(struct snd_sof_dev *sdev, @@ -387,11 +408,11 @@ static inline int snd_sof_pcm_platform_hw_params(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, - struct sof_ipc_stream_params *ipc_params) + struct snd_sof_platform_stream_params *platform_params) { if (sof_ops(sdev) && sof_ops(sdev)->pcm_hw_params) - return sof_ops(sdev)->pcm_hw_params(sdev, substream, - params, ipc_params); + return sof_ops(sdev)->pcm_hw_params(sdev, substream, params, + platform_params); return 0; } @@ -433,14 +454,17 @@ static inline int snd_sof_ipc_msg_data(struct snd_sof_dev *sdev, { return sof_ops(sdev)->ipc_msg_data(sdev, substream, p, sz); } - -/* host configure DSP HW parameters */ +/* host side configuration of the stream's data offset in stream mailbox area */ static inline int -snd_sof_ipc_pcm_params(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream, - const struct sof_ipc_pcm_params_reply *reply) +snd_sof_set_stream_data_offset(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + size_t posn_offset) { - return sof_ops(sdev)->ipc_pcm_params(sdev, substream, reply); + if (sof_ops(sdev) && sof_ops(sdev)->set_stream_data_offset) + return sof_ops(sdev)->set_stream_data_offset(sdev, substream, + posn_offset); + + return 0; } /* host stream pointer */ @@ -454,48 +478,15 @@ snd_sof_pcm_platform_pointer(struct snd_sof_dev *sdev, return 0; } -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) -static inline int -snd_sof_probe_compr_assign(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, struct snd_soc_dai *dai) -{ - return sof_ops(sdev)->probe_assign(sdev, cstream, dai); -} - -static inline int -snd_sof_probe_compr_free(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, struct snd_soc_dai *dai) -{ - return sof_ops(sdev)->probe_free(sdev, cstream, dai); -} - -static inline int -snd_sof_probe_compr_set_params(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_compr_params *params, struct snd_soc_dai *dai) -{ - 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) +/* pcm ack */ +static inline int snd_sof_pcm_platform_ack(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) { - if (sof_ops(sdev) && sof_ops(sdev)->probe_pointer) - return sof_ops(sdev)->probe_pointer(sdev, cstream, tstamp, dai); + if (sof_ops(sdev) && sof_ops(sdev)->pcm_ack) + return sof_ops(sdev)->pcm_ack(sdev, substream); return 0; } -#endif /* machine driver */ static inline int @@ -514,15 +505,17 @@ snd_sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata) sof_ops(sdev)->machine_unregister(sdev, pdata); } -static inline void +static inline struct snd_soc_acpi_mach * snd_sof_machine_select(struct snd_sof_dev *sdev) { if (sof_ops(sdev) && sof_ops(sdev)->machine_select) - sof_ops(sdev)->machine_select(sdev); + return sof_ops(sdev)->machine_select(sdev); + + return NULL; } static inline void -snd_sof_set_mach_params(const struct snd_soc_acpi_mach *mach, +snd_sof_set_mach_params(struct snd_soc_acpi_mach *mach, struct snd_sof_dev *sdev) { if (sof_ops(sdev) && sof_ops(sdev)->set_mach_params) @@ -598,5 +591,5 @@ int snd_sof_dsp_register_poll(struct snd_sof_dev *sdev, u32 bar, u32 offset, u32 mask, u32 target, u32 timeout_ms, u32 interval_us); -void snd_sof_dsp_panic(struct snd_sof_dev *sdev, u32 offset); +void snd_sof_dsp_panic(struct snd_sof_dev *sdev, u32 offset, bool non_recoverable); #endif diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index fa0bfcd2474e..14571b821eca 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -13,12 +13,12 @@ #include <linux/pm_runtime.h> #include <sound/pcm_params.h> #include <sound/sof.h> +#include <trace/events/sof.h> +#include "sof-of-dev.h" #include "sof-priv.h" #include "sof-audio.h" +#include "sof-utils.h" #include "ops.h" -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) -#include "sof-probes.h" -#endif /* Create DMA buffer page table for DSP */ static int create_page_table(struct snd_soc_component *component, @@ -38,22 +38,6 @@ static int create_page_table(struct snd_soc_component *component, spcm->stream[stream].page_table.area, size); } -static int sof_pcm_dsp_params(struct snd_sof_pcm *spcm, struct snd_pcm_substream *substream, - const struct sof_ipc_pcm_params_reply *reply) -{ - struct snd_soc_component *scomp = spcm->scomp; - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - - /* validate offset */ - int ret = snd_sof_ipc_pcm_params(sdev, substream, reply); - - if (ret < 0) - dev_err(scomp->dev, "error: got wrong reply for PCM %d\n", - spcm->pcm.pcm_id); - - return ret; -} - /* * sof pcm period elapse work */ @@ -100,30 +84,10 @@ void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream) } EXPORT_SYMBOL(snd_sof_pcm_period_elapsed); -static int sof_pcm_dsp_pcm_free(struct snd_pcm_substream *substream, - struct snd_sof_dev *sdev, - struct snd_sof_pcm *spcm) -{ - 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_setup_connected_widgets(struct snd_sof_dev *sdev, - struct snd_soc_pcm_runtime *rtd, - struct snd_sof_pcm *spcm, int dir) +static int +sof_pcm_setup_connected_widgets(struct snd_sof_dev *sdev, struct snd_soc_pcm_runtime *rtd, + struct snd_sof_pcm *spcm, struct snd_pcm_hw_params *params, + struct snd_sof_platform_stream_params *platform_params, int dir) { struct snd_soc_dai *dai; int ret, j; @@ -142,7 +106,7 @@ static int sof_pcm_setup_connected_widgets(struct snd_sof_dev *sdev, spcm->stream[dir].list = list; - ret = sof_widget_list_setup(sdev, spcm, dir); + ret = sof_widget_list_setup(sdev, spcm, params, platform_params, dir); if (ret < 0) { dev_err(sdev->dev, "error: failed widget list set up for pcm %d dir %d\n", spcm->pcm.pcm_id, dir); @@ -159,12 +123,12 @@ static int sof_pcm_hw_params(struct snd_soc_component *component, struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { + 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_platform_stream_params platform_params = { 0 }; + const struct sof_ipc_pcm_ops *pcm_ops = sdev->ipc->ops->pcm; struct snd_pcm_runtime *runtime = substream->runtime; - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_sof_pcm *spcm; - struct sof_ipc_pcm_params pcm; - struct sof_ipc_pcm_params_reply ipc_params_reply; int ret; /* nothing to do for BE */ @@ -179,105 +143,52 @@ static int sof_pcm_hw_params(struct snd_soc_component *component, * Handle repeated calls to hw_params() without free_pcm() in * between. At least ALSA OSS emulation depends on this. */ - if (spcm->prepared[substream->stream]) { - ret = sof_pcm_dsp_pcm_free(substream, sdev, spcm); + if (pcm_ops->hw_free && spcm->prepared[substream->stream]) { + ret = pcm_ops->hw_free(component, substream); if (ret < 0) return ret; + + spcm->prepared[substream->stream] = false; } dev_dbg(component->dev, "pcm: hw params stream %d dir %d\n", spcm->pcm.pcm_id, substream->stream); - memset(&pcm, 0, sizeof(pcm)); - - /* create compressed page table for audio firmware */ - if (runtime->buffer_changed) { - ret = create_page_table(component, substream, runtime->dma_area, - runtime->dma_bytes); - if (ret < 0) - return ret; - } - - /* number of pages should be rounded up */ - pcm.params.buffer.pages = PFN_UP(runtime->dma_bytes); - - /* set IPC PCM parameters */ - pcm.hdr.size = sizeof(pcm); - pcm.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS; - pcm.comp_id = spcm->stream[substream->stream].comp_id; - pcm.params.hdr.size = sizeof(pcm.params); - pcm.params.buffer.phy_addr = - spcm->stream[substream->stream].page_table.addr; - pcm.params.buffer.size = runtime->dma_bytes; - pcm.params.direction = substream->stream; - pcm.params.sample_valid_bytes = params_width(params) >> 3; - pcm.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED; - pcm.params.rate = params_rate(params); - pcm.params.channels = params_channels(params); - pcm.params.host_period_bytes = params_period_bytes(params); - - /* container size */ - ret = snd_pcm_format_physical_width(params_format(params)); - if (ret < 0) - return ret; - pcm.params.sample_container_bytes = ret >> 3; - - /* format */ - switch (params_format(params)) { - case SNDRV_PCM_FORMAT_S16: - pcm.params.frame_fmt = SOF_IPC_FRAME_S16_LE; - break; - case SNDRV_PCM_FORMAT_S24: - pcm.params.frame_fmt = SOF_IPC_FRAME_S24_4LE; - break; - case SNDRV_PCM_FORMAT_S32: - pcm.params.frame_fmt = SOF_IPC_FRAME_S32_LE; - break; - case SNDRV_PCM_FORMAT_FLOAT: - pcm.params.frame_fmt = SOF_IPC_FRAME_FLOAT; - break; - default: - return -EINVAL; - } - - /* firmware already configured host stream */ - ret = snd_sof_pcm_platform_hw_params(sdev, - substream, - params, - &pcm.params); + ret = snd_sof_pcm_platform_hw_params(sdev, substream, params, &platform_params); if (ret < 0) { - dev_err(component->dev, "error: platform hw params failed\n"); + dev_err(component->dev, "platform hw params failed\n"); return ret; } - dev_dbg(component->dev, "stream_tag %d", pcm.params.stream_tag); - /* if this is a repeated hw_params without hw_free, skip setting up widgets */ if (!spcm->stream[substream->stream].list) { - ret = sof_pcm_setup_connected_widgets(sdev, rtd, spcm, substream->stream); + ret = sof_pcm_setup_connected_widgets(sdev, rtd, spcm, params, &platform_params, + substream->stream); if (ret < 0) return ret; } - /* send hw_params IPC to the DSP */ - ret = sof_ipc_tx_message(sdev->ipc, pcm.hdr.cmd, &pcm, sizeof(pcm), - &ipc_params_reply, sizeof(ipc_params_reply)); - if (ret < 0) { - dev_err(component->dev, "error: hw params ipc failed for stream %d\n", - pcm.params.stream_tag); - return ret; + /* 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; } - ret = sof_pcm_dsp_params(spcm, substream, &ipc_params_reply); - if (ret < 0) - return ret; + if (pcm_ops->hw_params) { + ret = pcm_ops->hw_params(component, substream, params, &platform_params); + if (ret < 0) + return ret; + } spcm->prepared[substream->stream] = true; /* save pcm hw_params */ memcpy(&spcm->params[substream->stream], params, sizeof(*params)); - return ret; + return 0; } static int sof_pcm_hw_free(struct snd_soc_component *component, @@ -285,6 +196,7 @@ static int sof_pcm_hw_free(struct snd_soc_component *component, { struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + const struct sof_ipc_pcm_ops *pcm_ops = sdev->ipc->ops->pcm; struct snd_sof_pcm *spcm; int ret, err = 0; @@ -299,24 +211,29 @@ static int sof_pcm_hw_free(struct snd_soc_component *component, 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); + /* free PCM in the DSP */ + if (pcm_ops->hw_free && spcm->prepared[substream->stream]) { + ret = pcm_ops->hw_free(component, substream); if (ret < 0) err = ret; - } - ret = sof_widget_list_free(sdev, spcm, substream->stream); - if (ret < 0) - err = ret; - - cancel_work_sync(&spcm->stream[substream->stream].period_elapsed_work); + spcm->prepared[substream->stream] = false; + } + /* stop DMA */ ret = snd_sof_pcm_platform_hw_free(sdev, substream); if (ret < 0) { dev_err(component->dev, "error: platform hw free failed\n"); err = ret; } + /* free the DAPM widget list */ + ret = sof_widget_list_free(sdev, spcm, substream->stream); + if (ret < 0) + err = ret; + + cancel_work_sync(&spcm->stream[substream->stream].period_elapsed_work); + return err; } @@ -362,13 +279,12 @@ static int sof_pcm_trigger(struct snd_soc_component *component, { struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + const struct sof_ipc_pcm_ops *pcm_ops = sdev->ipc->ops->pcm; struct snd_sof_pcm *spcm; - struct sof_ipc_stream stream; - struct sof_ipc_reply reply; bool reset_hw_params = false; bool free_widget_list = false; bool ipc_first = false; - int ret; + int ret = 0; /* nothing to do for BE */ if (rtd->dai_link->no_pcm) @@ -381,38 +297,12 @@ static int sof_pcm_trigger(struct snd_soc_component *component, dev_dbg(component->dev, "pcm: trigger stream %d dir %d cmd %d\n", spcm->pcm.pcm_id, substream->stream, cmd); - stream.hdr.size = sizeof(stream); - stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG; - stream.comp_id = spcm->stream[substream->stream].comp_id; - switch (cmd) { case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_PAUSE; ipc_first = true; break; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_RELEASE; break; - case SNDRV_PCM_TRIGGER_RESUME: - if (spcm->stream[substream->stream].suspend_ignored) { - /* - * this case will be triggered when INFO_RESUME is - * supported, no need to resume streams that remained - * enabled in D0ix. - */ - spcm->stream[substream->stream].suspend_ignored = false; - return 0; - } - - /* set up hw_params */ - ret = sof_pcm_prepare(component, substream); - if (ret < 0) { - dev_err(component->dev, - "error: failed to set up hw_params upon resume\n"); - return ret; - } - - fallthrough; case SNDRV_PCM_TRIGGER_START: if (spcm->stream[substream->stream].suspend_ignored) { /* @@ -423,7 +313,6 @@ static int sof_pcm_trigger(struct snd_soc_component *component, spcm->stream[substream->stream].suspend_ignored = false; return 0; } - stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_START; break; case SNDRV_PCM_TRIGGER_SUSPEND: if (sdev->system_suspend_target == SOF_SUSPEND_S0IX && @@ -440,13 +329,11 @@ static int sof_pcm_trigger(struct snd_soc_component *component, free_widget_list = true; 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(component->dev, "error: unhandled trigger cmd %d\n", - cmd); + dev_err(component->dev, "Unhandled trigger cmd %d\n", cmd); return -EINVAL; } @@ -457,24 +344,17 @@ static int sof_pcm_trigger(struct snd_soc_component *component, if (!ipc_first) snd_sof_pcm_platform_trigger(sdev, substream, cmd); - /* send IPC to the DSP */ - ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream, - sizeof(stream), &reply, sizeof(reply)); + if (pcm_ops->trigger) + ret = pcm_ops->trigger(component, substream, cmd); - /* need to STOP DMA even if STOP IPC failed */ + /* need to STOP DMA even if trigger IPC failed */ if (ipc_first) snd_sof_pcm_platform_trigger(sdev, substream, cmd); /* free PCM if reset_hw_params is set and the STOP IPC is successful */ - if (!ret && reset_hw_params) { - ret = sof_pcm_dsp_pcm_free(substream, sdev, spcm); - if (ret < 0) - return ret; - - /* free widget list only for SUSPEND trigger */ - if (free_widget_list) - ret = sof_widget_list_free(sdev, spcm, substream->stream); - } + if (!ret && reset_hw_params) + ret = sof_pcm_stream_free(sdev, substream, spcm, substream->stream, + free_widget_list); return ret; } @@ -505,9 +385,7 @@ static snd_pcm_uframes_t sof_pcm_pointer(struct snd_soc_component *component, dai = bytes_to_frames(substream->runtime, spcm->stream[substream->stream].posn.dai_posn); - dev_vdbg(component->dev, - "PCM: stream %d dir %d DMA position %lu DAI position %lu\n", - spcm->pcm.pcm_id, substream->stream, host, dai); + trace_sof_pcm_pointer_position(sdev, spcm, substream, host, dai); return host; } @@ -518,7 +396,7 @@ static int sof_pcm_open(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_pcm_runtime *runtime = substream->runtime; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); - const struct snd_sof_dsp_ops *ops = sof_ops(sdev); + struct snd_sof_dsp_ops *ops = sof_ops(sdev); struct snd_sof_pcm *spcm; struct snd_soc_tplg_stream_caps *caps; int ret; @@ -678,31 +556,6 @@ capture: return 0; } -static void ssp_dai_config_pcm_params_match(struct snd_sof_dev *sdev, const char *link_name, - struct snd_pcm_hw_params *params) -{ - struct sof_ipc_dai_config *config; - struct snd_sof_dai *dai; - int i; - - /* - * Search for all matching DAIs as we can have both playback and capture DAI - * associated with the same link. - */ - list_for_each_entry(dai, &sdev->dai_list, list) { - if (!dai->name || strcmp(link_name, dai->name)) - continue; - for (i = 0; i < dai->number_configs; i++) { - config = &dai->dai_config[i]; - if (config->ssp.fsync_rate == params_rate(params)) { - dev_dbg(sdev->dev, "DAI config %d matches pcm hw params\n", i); - dai->current_config = i; - break; - } - } - } -} - /* fixup the BE DAI link to match any values from topology */ int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params) { @@ -716,7 +569,7 @@ int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_pa struct snd_sof_dai *dai = snd_sof_find_dai(component, (char *)rtd->dai_link->name); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); - struct snd_soc_dpcm *dpcm; + const struct sof_ipc_pcm_ops *pcm_ops = sdev->ipc->ops->pcm; /* no topology exists for this BE, try a common configuration */ if (!dai) { @@ -737,100 +590,8 @@ int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_pa return 0; } - /* read format from topology */ - snd_mask_none(fmt); - - switch (dai->comp_dai.config.frame_fmt) { - case SOF_IPC_FRAME_S16_LE: - snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); - break; - case SOF_IPC_FRAME_S24_4LE: - snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); - break; - case SOF_IPC_FRAME_S32_LE: - snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE); - break; - default: - dev_err(component->dev, "error: No available DAI format!\n"); - return -EINVAL; - } - - /* read rate and channels from topology */ - switch (dai->dai_config->type) { - case SOF_DAI_INTEL_SSP: - /* search for config to pcm params match, if not found use default */ - ssp_dai_config_pcm_params_match(sdev, (char *)rtd->dai_link->name, params); - - rate->min = dai->dai_config[dai->current_config].ssp.fsync_rate; - rate->max = dai->dai_config[dai->current_config].ssp.fsync_rate; - channels->min = dai->dai_config[dai->current_config].ssp.tdm_slots; - channels->max = dai->dai_config[dai->current_config].ssp.tdm_slots; - - dev_dbg(component->dev, - "rate_min: %d rate_max: %d\n", rate->min, rate->max); - dev_dbg(component->dev, - "channels_min: %d channels_max: %d\n", - channels->min, channels->max); - - break; - case SOF_DAI_INTEL_DMIC: - /* DMIC only supports 16 or 32 bit formats */ - if (dai->comp_dai.config.frame_fmt == SOF_IPC_FRAME_S24_4LE) { - dev_err(component->dev, - "error: invalid fmt %d for DAI type %d\n", - dai->comp_dai.config.frame_fmt, - dai->dai_config->type); - } - break; - case SOF_DAI_INTEL_HDA: - /* - * HDAudio does not follow the default trigger - * sequence due to firmware implementation - */ - for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_PLAYBACK, dpcm) { - struct snd_soc_pcm_runtime *fe = dpcm->fe; - - fe->dai_link->trigger[SNDRV_PCM_STREAM_PLAYBACK] = - SND_SOC_DPCM_TRIGGER_POST; - } - break; - case SOF_DAI_INTEL_ALH: - /* - * Dai could run with different channel count compared with - * front end, so get dai channel count from topology - */ - channels->min = dai->dai_config->alh.channels; - channels->max = dai->dai_config->alh.channels; - break; - case SOF_DAI_IMX_ESAI: - rate->min = dai->dai_config->esai.fsync_rate; - rate->max = dai->dai_config->esai.fsync_rate; - channels->min = dai->dai_config->esai.tdm_slots; - channels->max = dai->dai_config->esai.tdm_slots; - - dev_dbg(component->dev, - "rate_min: %d rate_max: %d\n", rate->min, rate->max); - dev_dbg(component->dev, - "channels_min: %d channels_max: %d\n", - channels->min, channels->max); - break; - case SOF_DAI_IMX_SAI: - rate->min = dai->dai_config->sai.fsync_rate; - rate->max = dai->dai_config->sai.fsync_rate; - channels->min = dai->dai_config->sai.tdm_slots; - channels->max = dai->dai_config->sai.tdm_slots; - - dev_dbg(component->dev, - "rate_min: %d rate_max: %d\n", rate->min, rate->max); - dev_dbg(component->dev, - "channels_min: %d channels_max: %d\n", - channels->min, channels->max); - break; - default: - dev_err(component->dev, "error: invalid DAI type %d\n", - dai->dai_config->type); - break; - } + if (pcm_ops->dai_link_fixup) + return pcm_ops->dai_link_fixup(rtd, params); return 0; } @@ -843,6 +604,14 @@ static int sof_pcm_probe(struct snd_soc_component *component) const char *tplg_filename; int ret; + /* + * make sure the device is pm_runtime_active before loading the + * topology and initiating IPC or bus transactions + */ + ret = pm_runtime_resume_and_get(component->dev); + if (ret < 0 && ret != -EACCES) + return ret; + /* load the default topology */ sdev->component = component; @@ -860,6 +629,9 @@ static int sof_pcm_probe(struct snd_soc_component *component) return ret; } + pm_runtime_mark_last_busy(component->dev); + pm_runtime_put_autosuspend(component->dev); + return ret; } @@ -869,13 +641,26 @@ static void sof_pcm_remove(struct snd_soc_component *component) snd_soc_tplg_component_remove(component); } +static int sof_pcm_ack(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + + return snd_sof_pcm_platform_ack(sdev, substream); +} + void snd_sof_new_platform_drv(struct snd_sof_dev *sdev) { struct snd_soc_component_driver *pd = &sdev->plat_drv; struct snd_sof_pdata *plat_data = sdev->pdata; const char *drv_name; - drv_name = plat_data->machine->drv_name; + if (plat_data->machine) + drv_name = plat_data->machine->drv_name; + else if (plat_data->of_machine) + drv_name = plat_data->of_machine->drv_name; + else + drv_name = NULL; pd->name = "sof-audio-component"; pd->probe = sof_pcm_probe; @@ -887,10 +672,12 @@ void snd_sof_new_platform_drv(struct snd_sof_dev *sdev) pd->hw_free = sof_pcm_hw_free; pd->trigger = sof_pcm_trigger; pd->pointer = sof_pcm_pointer; + pd->ack = sof_pcm_ack; -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) - pd->compress_ops = &sof_probe_compressed_ops; +#if IS_ENABLED(CONFIG_SND_SOC_SOF_COMPRESS) + pd->compress_ops = &sof_compressed_ops; #endif + pd->pcm_construct = sof_pcm_new; pd->ignore_machine = drv_name; pd->be_hw_params_fixup = sof_pcm_dai_link_fixup; @@ -900,4 +687,6 @@ void snd_sof_new_platform_drv(struct snd_sof_dev *sdev) /* increment module refcount when a pcm is opened */ pd->module_get_upon_open = 1; + + pd->legacy_dai_naming = 1; } diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c index ac8ae6e422a7..df740be645e8 100644 --- a/sound/soc/sof/pm.c +++ b/sound/soc/sof/pm.c @@ -23,6 +23,9 @@ static u32 snd_sof_dsp_power_target(struct snd_sof_dev *sdev) u32 target_dsp_state; switch (sdev->system_suspend_target) { + case SOF_SUSPEND_S5: + case SOF_SUSPEND_S4: + /* DSP should be in D3 if the system is suspending to S3+ */ case SOF_SUSPEND_S3: /* DSP should be in D3 if the system is suspending to S3 */ target_dsp_state = SOF_DSP_PM_D3; @@ -48,22 +51,6 @@ static u32 snd_sof_dsp_power_target(struct snd_sof_dev *sdev) return target_dsp_state; } -static int sof_send_pm_ctx_ipc(struct snd_sof_dev *sdev, int cmd) -{ - struct sof_ipc_pm_ctx pm_ctx; - struct sof_ipc_reply reply; - - memset(&pm_ctx, 0, sizeof(pm_ctx)); - - /* configure ctx save ipc message */ - pm_ctx.hdr.size = sizeof(pm_ctx); - pm_ctx.hdr.cmd = SOF_IPC_GLB_PM_MSG | cmd; - - /* send ctx save ipc to dsp */ - return sof_ipc_tx_message(sdev->ipc, pm_ctx.hdr.cmd, &pm_ctx, - sizeof(pm_ctx), &reply, sizeof(reply)); -} - #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) static void sof_cache_debugfs(struct snd_sof_dev *sdev) { @@ -86,6 +73,8 @@ static void sof_cache_debugfs(struct snd_sof_dev *sdev) static int sof_resume(struct device *dev, bool runtime_resume) { struct snd_sof_dev *sdev = dev_get_drvdata(dev); + const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm; + const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; u32 old_state = sdev->dsp_power_state.state; int ret; @@ -116,11 +105,18 @@ static int sof_resume(struct device *dev, bool runtime_resume) /* * Nothing further to be done for platforms that support the low power - * D0 substate. + * D0 substate. Resume trace and return when resuming from + * low-power D0 substate */ if (!runtime_resume && sof_ops(sdev)->set_power_state && - old_state == SOF_DSP_PM_D0) + old_state == SOF_DSP_PM_D0) { + ret = sof_fw_trace_resume(sdev); + if (ret < 0) + /* non fatal */ + dev_warn(sdev->dev, + "failed to enable trace after resume %d\n", ret); return 0; + } sof_set_fw_state(sdev, SOF_FW_BOOT_PREPARE); @@ -130,6 +126,7 @@ static int sof_resume(struct device *dev, bool runtime_resume) dev_err(sdev->dev, "error: failed to load DSP firmware after resume %d\n", ret); + sof_set_fw_state(sdev, SOF_FW_BOOT_FAILED); return ret; } @@ -144,11 +141,12 @@ static int sof_resume(struct device *dev, bool runtime_resume) dev_err(sdev->dev, "error: failed to boot DSP firmware after resume %d\n", ret); + sof_set_fw_state(sdev, SOF_FW_BOOT_FAILED); return ret; } - /* resume DMA trace, only need send ipc */ - ret = snd_sof_init_trace_ipc(sdev); + /* resume DMA trace */ + ret = sof_fw_trace_resume(sdev); if (ret < 0) { /* non fatal */ dev_warn(sdev->dev, @@ -157,20 +155,23 @@ static int sof_resume(struct device *dev, bool runtime_resume) } /* restore pipelines */ - ret = sof_set_up_pipelines(sdev, false); - if (ret < 0) { - dev_err(sdev->dev, - "error: failed to restore pipeline after resume %d\n", - ret); - return ret; + if (tplg_ops->set_up_all_pipelines) { + ret = tplg_ops->set_up_all_pipelines(sdev, false); + if (ret < 0) { + dev_err(sdev->dev, "Failed to restore pipeline after resume %d\n", ret); + return ret; + } } + /* Notify clients not managed by pm framework about core resume */ + sof_resume_clients(sdev); + /* notify DSP of system resume */ - ret = sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_RESTORE); - if (ret < 0) - dev_err(sdev->dev, - "error: ctx_restore ipc error during resume %d\n", - ret); + if (pm_ops && pm_ops->ctx_restore) { + ret = pm_ops->ctx_restore(sdev); + if (ret < 0) + dev_err(sdev->dev, "ctx_restore IPC error during resume: %d\n", ret); + } return ret; } @@ -178,6 +179,9 @@ static int sof_resume(struct device *dev, bool runtime_resume) static int sof_suspend(struct device *dev, bool runtime_suspend) { struct snd_sof_dev *sdev = dev_get_drvdata(dev); + const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm; + const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; + pm_message_t pm_state; u32 target_state = 0; int ret; @@ -193,7 +197,7 @@ static int sof_suspend(struct device *dev, bool runtime_suspend) /* prepare for streams to be resumed properly upon resume */ if (!runtime_suspend) { - ret = sof_set_hw_params_upon_resume(sdev->dev); + ret = snd_sof_dsp_hw_params_upon_resume(sdev); if (ret < 0) { dev_err(sdev->dev, "error: setting hw_params flag during suspend %d\n", @@ -203,15 +207,24 @@ static int sof_suspend(struct device *dev, bool runtime_suspend) } target_state = snd_sof_dsp_power_target(sdev); + pm_state.event = target_state; /* Skip to platform-specific suspend if DSP is entering D0 */ - if (target_state == SOF_DSP_PM_D0) + if (target_state == SOF_DSP_PM_D0) { + sof_fw_trace_suspend(sdev, pm_state); + /* Notify clients not managed by pm framework about core suspend */ + sof_suspend_clients(sdev, pm_state); goto suspend; + } + + if (tplg_ops->tear_down_all_pipelines) + tplg_ops->tear_down_all_pipelines(sdev, false); - sof_tear_down_pipelines(sdev, false); + /* suspend DMA trace */ + sof_fw_trace_suspend(sdev, pm_state); - /* release trace */ - snd_sof_release_trace(sdev); + /* Notify clients not managed by pm framework about core suspend */ + sof_suspend_clients(sdev, pm_state); #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) /* cache debugfs contents during runtime suspend */ @@ -219,21 +232,20 @@ static int sof_suspend(struct device *dev, bool runtime_suspend) sof_cache_debugfs(sdev); #endif /* notify DSP of upcoming power down */ - ret = sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_SAVE); - if (ret == -EBUSY || ret == -EAGAIN) { - /* - * runtime PM has logic to handle -EBUSY/-EAGAIN so - * pass these errors up - */ - dev_err(sdev->dev, - "error: ctx_save ipc error during suspend %d\n", - ret); - return ret; - } else if (ret < 0) { - /* FW in unexpected state, continue to power down */ - dev_warn(sdev->dev, - "ctx_save ipc error %d, proceeding with suspend\n", - ret); + if (pm_ops && pm_ops->ctx_save) { + ret = pm_ops->ctx_save(sdev); + if (ret == -EBUSY || ret == -EAGAIN) { + /* + * runtime PM has logic to handle -EBUSY/-EAGAIN so + * pass these errors up + */ + dev_err(sdev->dev, "ctx_save IPC error during suspend: %d\n", ret); + return ret; + } else if (ret < 0) { + /* FW in unexpected state, continue to power down */ + dev_warn(sdev->dev, "ctx_save IPC error: %d, proceeding with suspend\n", + ret); + } } suspend: @@ -265,9 +277,11 @@ suspend: int snd_sof_dsp_power_down_notify(struct snd_sof_dev *sdev) { + const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm; + /* Notify DSP of upcoming power down */ - if (sof_ops(sdev)->remove) - return sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_SAVE); + if (sof_ops(sdev)->remove && pm_ops && pm_ops->ctx_save) + return pm_ops->ctx_save(sdev); return 0; } @@ -312,12 +326,36 @@ int snd_sof_prepare(struct device *dev) /* will suspend to S3 by default */ sdev->system_suspend_target = SOF_SUSPEND_S3; + /* + * if the firmware is crashed or boot failed then we try to aim for S3 + * to reboot the firmware + */ + if (sdev->fw_state == SOF_FW_CRASHED || + sdev->fw_state == SOF_FW_BOOT_FAILED) + return 0; + if (!desc->use_acpi_target_states) return 0; #if defined(CONFIG_ACPI) - if (acpi_target_system_state() == ACPI_STATE_S0) + switch (acpi_target_system_state()) { + case ACPI_STATE_S0: sdev->system_suspend_target = SOF_SUSPEND_S0IX; + break; + case ACPI_STATE_S1: + case ACPI_STATE_S2: + case ACPI_STATE_S3: + sdev->system_suspend_target = SOF_SUSPEND_S3; + break; + case ACPI_STATE_S4: + sdev->system_suspend_target = SOF_SUSPEND_S4; + break; + case ACPI_STATE_S5: + sdev->system_suspend_target = SOF_SUSPEND_S5; + break; + default: + break; + } #endif return 0; diff --git a/sound/soc/sof/sof-acpi-dev.c b/sound/soc/sof/sof-acpi-dev.c index 74982c04497b..1b04dcb33293 100644 --- a/sound/soc/sof/sof-acpi-dev.c +++ b/sound/soc/sof/sof-acpi-dev.c @@ -74,20 +74,20 @@ int sof_acpi_probe(struct platform_device *pdev, const struct sof_dev_desc *desc sof_pdata->desc = desc; sof_pdata->dev = &pdev->dev; - sof_pdata->fw_filename = desc->default_fw_filename; + sof_pdata->fw_filename = desc->default_fw_filename[SOF_IPC]; /* alternate fw and tplg filenames ? */ if (fw_path) sof_pdata->fw_filename_prefix = fw_path; else sof_pdata->fw_filename_prefix = - sof_pdata->desc->default_fw_path; + sof_pdata->desc->default_fw_path[SOF_IPC]; if (tplg_path) sof_pdata->tplg_filename_prefix = tplg_path; else sof_pdata->tplg_filename_prefix = - sof_pdata->desc->default_tplg_path; + sof_pdata->desc->default_tplg_path[SOF_IPC]; /* set callback to be called on successful device probe to enable runtime_pm */ sof_pdata->sof_probe_complete = sof_acpi_probe_complete; diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index 7cbe757c1fe2..62092e2d609c 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -9,274 +9,172 @@ // #include <linux/bitfield.h> +#include <trace/events/sof.h> #include "sof-audio.h" +#include "sof-of-dev.h" #include "ops.h" -static int sof_kcontrol_setup(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol) -{ - int ipc_cmd, ctrl_type; - int ret; - - /* reset readback offset for scontrol */ - 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; - break; - case SOF_CTRL_CMD_BINARY: - ipc_cmd = SOF_IPC_COMP_SET_DATA; - ctrl_type = SOF_CTRL_TYPE_DATA_SET; - break; - default: - return 0; - } - - ret = snd_sof_ipc_set_get_comp_data(scontrol, ipc_cmd, ctrl_type, scontrol->cmd, true); - if (ret < 0) - dev_err(sdev->dev, "error: failed kcontrol value set for widget: %d\n", - scontrol->comp_id); - - return ret; -} - -static int sof_dai_config_setup(struct snd_sof_dev *sdev, struct snd_sof_dai *dai) -{ - struct sof_ipc_dai_config *config; - struct sof_ipc_reply reply; - int ret; - - config = &dai->dai_config[dai->current_config]; - if (!config) { - dev_err(sdev->dev, "error: no config for DAI %s\n", dai->name); - return -EINVAL; - } - - /* set NONE flag to clear all previous settings */ - config->flags = FIELD_PREP(SOF_DAI_CONFIG_FLAGS_MASK, SOF_DAI_CONFIG_FLAGS_NONE); - - 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; -} - -static int sof_widget_kcontrol_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) -{ - struct snd_sof_control *scontrol; - int ret; - - /* set up all controls for the widget */ - list_for_each_entry(scontrol, &sdev->kcontrol_list, list) - if (scontrol->comp_id == swidget->comp_id) { - ret = sof_kcontrol_setup(sdev, scontrol); - if (ret < 0) { - dev_err(sdev->dev, "error: fail to set up kcontrols for widget %s\n", - swidget->widget->name); - return ret; - } - } - - return 0; -} - static void sof_reset_route_setup_status(struct snd_sof_dev *sdev, struct snd_sof_widget *widget) { + const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; struct snd_sof_route *sroute; list_for_each_entry(sroute, &sdev->route_list, list) - if (sroute->src_widget == widget || sroute->sink_widget == widget) + if (sroute->src_widget == widget || sroute->sink_widget == widget) { + if (sroute->setup && tplg_ops->route_free) + tplg_ops->route_free(sdev, sroute); + sroute->setup = false; + } } int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) { - struct sof_ipc_free ipc_free = { - .hdr = { - .size = sizeof(ipc_free), - .cmd = SOF_IPC_GLB_TPLG_MSG, - }, - .id = swidget->comp_id, - }; - struct sof_ipc_reply reply; + const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; + int err = 0; int ret; if (!swidget->private) return 0; + trace_sof_widget_free(swidget); + /* only free when use_count is 0 */ if (--swidget->use_count) return 0; - switch (swidget->id) { - case snd_soc_dapm_scheduler: - ipc_free.hdr.cmd |= SOF_IPC_TPLG_PIPE_FREE; - break; - case snd_soc_dapm_buffer: - ipc_free.hdr.cmd |= SOF_IPC_TPLG_BUFFER_FREE; - break; - default: - ipc_free.hdr.cmd |= SOF_IPC_TPLG_COMP_FREE; - break; - } + /* reset route setup status for all routes that contain this widget */ + sof_reset_route_setup_status(sdev, swidget); - ret = sof_ipc_tx_message(sdev->ipc, ipc_free.hdr.cmd, &ipc_free, sizeof(ipc_free), - &reply, sizeof(reply)); + /* continue to disable core even if IPC fails */ + if (tplg_ops->widget_free) + err = tplg_ops->widget_free(sdev, swidget); + + /* + * disable widget core. continue to route setup status and complete flag + * even if this fails and return the appropriate error + */ + ret = snd_sof_dsp_core_put(sdev, swidget->core); if (ret < 0) { - dev_err(sdev->dev, "error: failed to free widget %s\n", swidget->widget->name); - swidget->use_count++; - return ret; + dev_err(sdev->dev, "error: failed to disable target core: %d for widget %s\n", + swidget->core, swidget->widget->name); + if (!err) + err = ret; } - /* reset route setup status for all routes that contain this widget */ - sof_reset_route_setup_status(sdev, swidget); - swidget->complete = 0; - dev_dbg(sdev->dev, "widget %s freed\n", swidget->widget->name); + /* + * free the scheduler widget (same as pipe_widget) associated with the current swidget. + * skip for static pipelines + */ + if (swidget->dynamic_pipeline_widget && swidget->id != snd_soc_dapm_scheduler) { + ret = sof_widget_free(sdev, swidget->pipe_widget); + if (ret < 0 && !err) + err = ret; + swidget->pipe_widget->complete = 0; + } - return 0; + if (!err) + dev_dbg(sdev->dev, "widget %s freed\n", swidget->widget->name); + + return err; } EXPORT_SYMBOL(sof_widget_free); int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) { - struct sof_ipc_pipe_new *pipeline; - struct sof_ipc_comp_reply r; - struct sof_ipc_cmd_hdr *hdr; - struct sof_ipc_comp *comp; - struct snd_sof_dai *dai; - size_t ipc_size; + const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; int ret; /* skip if there is no private data */ if (!swidget->private) return 0; + trace_sof_widget_setup(swidget); + /* widget already set up */ if (++swidget->use_count > 1) return 0; - ret = sof_pipeline_core_enable(sdev, swidget); - if (ret < 0) { - dev_err(sdev->dev, "error: failed to enable target core: %d for widget %s\n", - ret, swidget->widget->name); - goto use_count_dec; - } - - 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; - dai->configured = false; - 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); - if (ret < 0) { - dev_err(sdev->dev, "error: failed to load widget %s\n", + /* + * The scheduler widget for a pipeline is not part of the connected DAPM + * widget list and it needs to be set up before the widgets in the pipeline + * are set up. The use_count for the scheduler widget is incremented for every + * widget in a given pipeline to ensure that it is freed only after the last + * widget in the pipeline is freed. Skip setting up scheduler widget for static pipelines. + */ + if (swidget->dynamic_pipeline_widget && swidget->id != snd_soc_dapm_scheduler) { + if (!swidget->pipe_widget) { + dev_err(sdev->dev, "No scheduler widget set for %s\n", swidget->widget->name); + ret = -EINVAL; goto use_count_dec; } - ret = sof_dai_config_setup(sdev, dai); - if (ret < 0) { - dev_err(sdev->dev, "error: failed to load dai config for DAI %s\n", - swidget->widget->name); - sof_widget_free(sdev, swidget); - return ret; - } - break; - case snd_soc_dapm_scheduler: - 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; + ret = sof_widget_setup(sdev, swidget->pipe_widget); + if (ret < 0) + goto use_count_dec; } + + /* enable widget core */ + ret = snd_sof_dsp_core_get(sdev, swidget->core); if (ret < 0) { - dev_err(sdev->dev, "error: failed to load widget %s\n", swidget->widget->name); - goto use_count_dec; + dev_err(sdev->dev, "error: failed to enable target core for widget %s\n", + swidget->widget->name); + goto pipe_widget_free; + } + + /* setup widget in the DSP */ + if (tplg_ops->widget_setup) { + ret = tplg_ops->widget_setup(sdev, swidget); + if (ret < 0) + goto core_put; + } + + /* send config for DAI components */ + if (WIDGET_IS_DAI(swidget->id)) { + unsigned int flags = SOF_DAI_CONFIG_FLAGS_NONE; + + if (tplg_ops->dai_config) { + ret = tplg_ops->dai_config(sdev, swidget, flags, NULL); + if (ret < 0) + goto widget_free; + } } /* restore kcontrols for widget */ - ret = sof_widget_kcontrol_setup(sdev, swidget); - if (ret < 0) { - dev_err(sdev->dev, "error: failed to restore kcontrols for widget %s\n", - swidget->widget->name); - sof_widget_free(sdev, swidget); - return ret; + if (tplg_ops->control->widget_kcontrol_setup) { + ret = tplg_ops->control->widget_kcontrol_setup(sdev, swidget); + if (ret < 0) + goto widget_free; } dev_dbg(sdev->dev, "widget %s setup complete\n", swidget->widget->name); return 0; +widget_free: + /* widget use_count and core ref_count will both be decremented by sof_widget_free() */ + sof_widget_free(sdev, swidget); +core_put: + snd_sof_dsp_core_put(sdev, swidget->core); +pipe_widget_free: + if (swidget->id != snd_soc_dapm_scheduler) + sof_widget_free(sdev, swidget->pipe_widget); use_count_dec: swidget->use_count--; return ret; } EXPORT_SYMBOL(sof_widget_setup); -static int sof_route_setup_ipc(struct snd_sof_dev *sdev, struct snd_sof_route *sroute) -{ - struct sof_ipc_pipe_comp_connect *connect; - struct sof_ipc_reply reply; - int ret; - - /* skip if there's no private data */ - if (!sroute->private) - return 0; - - /* nothing to do if route is already set up */ - if (sroute->setup) - return 0; - - connect = sroute->private; - - dev_dbg(sdev->dev, "setting up route %s -> %s\n", - sroute->src_widget->widget->name, - sroute->sink_widget->widget->name); - - /* send ipc */ - ret = sof_ipc_tx_message(sdev->ipc, - connect->hdr.cmd, - connect, sizeof(*connect), - &reply, sizeof(reply)); - if (ret < 0) { - dev_err(sdev->dev, "%s: route setup failed %d\n", __func__, ret); - return ret; - } - - sroute->setup = true; - - return 0; -} - -static int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsource, - struct snd_soc_dapm_widget *wsink) +int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsource, + struct snd_soc_dapm_widget *wsink) { + const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg; struct snd_sof_widget *src_widget = wsource->dobj.private; struct snd_sof_widget *sink_widget = wsink->dobj.private; struct snd_sof_route *sroute; bool route_found = false; + int ret; /* ignore routes involving virtual widgets in topology */ switch (src_widget->id) { @@ -310,7 +208,16 @@ static int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget return -EINVAL; } - return sof_route_setup_ipc(sdev, sroute); + /* nothing to do if route is already set up */ + if (sroute->setup) + return 0; + + ret = ipc_tplg_ops->route_setup(sdev, sroute); + if (ret < 0) + return ret; + + sroute->setup = true; + return 0; } static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev, @@ -356,51 +263,253 @@ static int sof_setup_pipeline_connections(struct snd_sof_dev *sdev, return 0; } -int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir) +static void +sof_unprepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget) { - struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list; - struct snd_soc_dapm_widget *widget; - int i, ret, num_widgets; + const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg; + const struct sof_ipc_tplg_widget_ops *widget_ops = ipc_tplg_ops->widget; + struct snd_sof_widget *swidget = widget->dobj.private; + struct snd_soc_dapm_path *p; - /* nothing to set up */ - if (!list) - return 0; + /* return if the widget is in use or if it is already unprepared */ + if (!swidget->prepared || swidget->use_count > 1) + return; - /* set up widgets in the list */ - for_each_dapm_widgets(list, num_widgets, widget) { - struct snd_sof_widget *swidget = widget->dobj.private; - struct snd_sof_widget *pipe_widget; + if (widget_ops[widget->id].ipc_unprepare) + /* unprepare the source widget */ + widget_ops[widget->id].ipc_unprepare(swidget); - if (!swidget) - continue; + swidget->prepared = false; - /* - * The scheduler widget for a pipeline is not part of the connected DAPM - * widget list and it needs to be set up before the widgets in the pipeline - * are set up. The use_count for the scheduler widget is incremented for every - * widget in a given pipeline to ensure that it is freed only after the last - * widget in the pipeline is freed. - */ - pipe_widget = swidget->pipe_widget; - if (!pipe_widget) { - dev_err(sdev->dev, "error: no pipeline widget found for %s\n", - swidget->widget->name); - ret = -EINVAL; - goto widget_free; + /* unprepare all widgets in the sink paths */ + snd_soc_dapm_widget_for_each_sink_path(widget, p) { + if (!p->walking && p->sink->dobj.private) { + p->walking = true; + sof_unprepare_widgets_in_path(sdev, p->sink); + p->walking = false; } + } +} - ret = sof_widget_setup(sdev, pipe_widget); - if (ret < 0) - goto widget_free; +static int +sof_prepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget, + struct snd_pcm_hw_params *fe_params, + struct snd_sof_platform_stream_params *platform_params, + struct snd_pcm_hw_params *pipeline_params, int dir) +{ + const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg; + const struct sof_ipc_tplg_widget_ops *widget_ops = ipc_tplg_ops->widget; + struct snd_sof_widget *swidget = widget->dobj.private; + struct snd_soc_dapm_path *p; + int ret; + + if (!widget_ops[widget->id].ipc_prepare || swidget->prepared) + goto sink_prepare; + + /* prepare the source widget */ + ret = widget_ops[widget->id].ipc_prepare(swidget, fe_params, platform_params, + pipeline_params, dir); + if (ret < 0) { + dev_err(sdev->dev, "failed to prepare widget %s\n", widget->name); + return ret; + } + + swidget->prepared = true; + +sink_prepare: + /* prepare all widgets in the sink paths */ + snd_soc_dapm_widget_for_each_sink_path(widget, p) { + if (!p->walking && p->sink->dobj.private) { + p->walking = true; + ret = sof_prepare_widgets_in_path(sdev, p->sink, fe_params, + platform_params, pipeline_params, dir); + p->walking = false; + if (ret < 0) { + /* unprepare the source widget */ + if (widget_ops[widget->id].ipc_unprepare && swidget->prepared) { + widget_ops[widget->id].ipc_unprepare(swidget); + swidget->prepared = false; + } + return ret; + } + } + } + + return 0; +} + +/* + * free all widgets in the sink path starting from the source widget + * (DAI type for capture, AIF type for playback) + */ +static int sof_free_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget, + int dir) +{ + struct snd_soc_dapm_path *p; + int err; + int ret = 0; + + /* free all widgets even in case of error to keep use counts balanced */ + snd_soc_dapm_widget_for_each_sink_path(widget, p) { + if (!p->walking && p->sink->dobj.private && widget->dobj.private) { + p->walking = true; + if (WIDGET_IS_AIF_OR_DAI(widget->id)) { + err = sof_widget_free(sdev, widget->dobj.private); + if (err < 0) + ret = err; + } + + err = sof_widget_free(sdev, p->sink->dobj.private); + if (err < 0) + ret = err; + + err = sof_free_widgets_in_path(sdev, p->sink, dir); + if (err < 0) + ret = err; + p->walking = false; + } + } + + return ret; +} + +/* + * set up all widgets in the sink path starting from the source widget + * (DAI type for capture, AIF type for playback). + * The error path in this function ensures that all successfully set up widgets getting freed. + */ +static int sof_set_up_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget, + int dir) +{ + struct snd_soc_dapm_path *p; + int ret; + + snd_soc_dapm_widget_for_each_sink_path(widget, p) { + if (!p->walking && p->sink->dobj.private && widget->dobj.private) { + p->walking = true; + if (WIDGET_IS_AIF_OR_DAI(widget->id)) { + ret = sof_widget_setup(sdev, widget->dobj.private); + if (ret < 0) + goto out; + } + + ret = sof_widget_setup(sdev, p->sink->dobj.private); + if (ret < 0) { + if (WIDGET_IS_AIF_OR_DAI(widget->id)) + sof_widget_free(sdev, widget->dobj.private); + goto out; + } + + ret = sof_set_up_widgets_in_path(sdev, p->sink, dir); + if (ret < 0) { + if (WIDGET_IS_AIF_OR_DAI(widget->id)) + sof_widget_free(sdev, widget->dobj.private); + sof_widget_free(sdev, p->sink->dobj.private); + } +out: + p->walking = false; + if (ret < 0) + return ret; + } + } - /* set up the widget */ - ret = sof_widget_setup(sdev, swidget); + return 0; +} + +static int +sof_walk_widgets_in_order(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget_list *list, + struct snd_pcm_hw_params *fe_params, + struct snd_sof_platform_stream_params *platform_params, int dir, + enum sof_widget_op op) +{ + struct snd_soc_dapm_widget *widget; + char *str; + int ret = 0; + int i; + + for_each_dapm_widgets(list, i, widget) { + /* starting widget for playback is AIF type */ + if (dir == SNDRV_PCM_STREAM_PLAYBACK && !WIDGET_IS_AIF(widget->id)) + continue; + + /* starting widget for capture is DAI type */ + if (dir == SNDRV_PCM_STREAM_CAPTURE && !WIDGET_IS_DAI(widget->id)) + continue; + + switch (op) { + case SOF_WIDGET_SETUP: + ret = sof_set_up_widgets_in_path(sdev, widget, dir); + str = "set up"; + break; + case SOF_WIDGET_FREE: + ret = sof_free_widgets_in_path(sdev, widget, dir); + str = "free"; + break; + case SOF_WIDGET_PREPARE: + { + struct snd_pcm_hw_params pipeline_params; + + str = "prepare"; + /* + * When walking the list of connected widgets, the pipeline_params for each + * widget is modified by the source widget in the path. Use a local + * copy of the runtime params as the pipeline_params so that the runtime + * params does not get overwritten. + */ + memcpy(&pipeline_params, fe_params, sizeof(*fe_params)); + + ret = sof_prepare_widgets_in_path(sdev, widget, fe_params, + platform_params, &pipeline_params, dir); + break; + } + case SOF_WIDGET_UNPREPARE: + sof_unprepare_widgets_in_path(sdev, widget); + break; + default: + dev_err(sdev->dev, "Invalid widget op %d\n", op); + return -EINVAL; + } if (ret < 0) { - sof_widget_free(sdev, pipe_widget); - goto widget_free; + dev_err(sdev->dev, "Failed to %s connected widgets\n", str); + return ret; } } + return 0; +} + +int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, + struct snd_pcm_hw_params *fe_params, + struct snd_sof_platform_stream_params *platform_params, + int dir) +{ + const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg; + struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list; + struct snd_soc_dapm_widget *widget; + int i, ret; + + /* nothing to set up */ + if (!list) + return 0; + + /* + * Prepare widgets for set up. The prepare step is used to allocate memory, assign + * instance ID and pick the widget configuration based on the runtime PCM params. + */ + ret = sof_walk_widgets_in_order(sdev, list, fe_params, platform_params, + dir, SOF_WIDGET_PREPARE); + if (ret < 0) + return ret; + + /* Set up is used to send the IPC to the DSP to create the widget */ + ret = sof_walk_widgets_in_order(sdev, list, fe_params, platform_params, + dir, SOF_WIDGET_SETUP); + if (ret < 0) { + ret = sof_walk_widgets_in_order(sdev, list, fe_params, platform_params, + dir, SOF_WIDGET_UNPREPARE); + return ret; + } + /* * error in setting pipeline connections will result in route status being reset for * routes that were successfully set up when the widgets are freed. @@ -428,29 +537,21 @@ int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, in if (pipe_widget->complete) continue; - pipe_widget->complete = snd_sof_complete_pipeline(sdev, pipe_widget); - if (pipe_widget->complete < 0) { - ret = pipe_widget->complete; - goto widget_free; + if (ipc_tplg_ops->pipeline_complete) { + pipe_widget->complete = ipc_tplg_ops->pipeline_complete(sdev, pipe_widget); + if (pipe_widget->complete < 0) { + ret = pipe_widget->complete; + goto widget_free; + } } } return 0; widget_free: - /* free all widgets that have been set up successfully */ - for_each_dapm_widgets(list, i, widget) { - struct snd_sof_widget *swidget = widget->dobj.private; - - if (!swidget) - continue; - - if (!num_widgets--) - break; - - sof_widget_free(sdev, swidget); - sof_widget_free(sdev, swidget->pipe_widget); - } + sof_walk_widgets_in_order(sdev, list, fe_params, platform_params, dir, + SOF_WIDGET_FREE); + sof_walk_widgets_in_order(sdev, list, NULL, NULL, dir, SOF_WIDGET_UNPREPARE); return ret; } @@ -458,41 +559,22 @@ widget_free: int sof_widget_list_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir) { struct snd_soc_dapm_widget_list *list = spcm->stream[dir].list; - struct snd_soc_dapm_widget *widget; - int i, ret; - int ret1 = 0; + int ret; /* nothing to free */ if (!list) return 0; - /* - * Free widgets in the list. This can fail but continue freeing other widgets to keep - * use_counts balanced. - */ - for_each_dapm_widgets(list, i, widget) { - struct snd_sof_widget *swidget = widget->dobj.private; - - if (!swidget) - continue; - - /* - * free widget and its pipe_widget. Either of these can fail, but free as many as - * possible before freeing the list and returning the error. - */ - ret = sof_widget_free(sdev, swidget); - if (ret < 0) - ret1 = ret; + /* send IPC to free widget in the DSP */ + ret = sof_walk_widgets_in_order(sdev, list, NULL, NULL, dir, SOF_WIDGET_FREE); - ret = sof_widget_free(sdev, swidget->pipe_widget); - if (ret < 0) - ret1 = ret; - } + /* unprepare the widget */ + sof_walk_widgets_in_order(sdev, list, NULL, NULL, dir, SOF_WIDGET_UNPREPARE); snd_soc_dapm_dai_free_widgets(&list); spcm->stream[dir].list = NULL; - return ret1; + return ret; } /* @@ -541,159 +623,34 @@ bool snd_sof_stream_suspend_ignored(struct snd_sof_dev *sdev) return false; } -int sof_set_hw_params_upon_resume(struct device *dev) +int sof_pcm_stream_free(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, + struct snd_sof_pcm *spcm, int dir, bool free_widget_list) { - 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); -} - -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_set_up_pipelines(struct snd_sof_dev *sdev, bool verify) -{ - struct snd_sof_widget *swidget; - struct snd_sof_route *sroute; + const struct sof_ipc_pcm_ops *pcm_ops = sdev->ipc->ops->pcm; int ret; - /* restore pipeline components */ - list_for_each_entry_reverse(swidget, &sdev->widget_list, list) { - /* only set up the widgets belonging to static pipelines */ - if (!verify && swidget->dynamic_pipeline_widget) - continue; - - /* update DAI config. The IPC will be sent in sof_widget_setup() */ - if (WIDGET_IS_DAI(swidget->id)) { - struct snd_sof_dai *dai = swidget->private; - struct sof_ipc_dai_config *config; - - if (!dai || !dai->dai_config) - continue; - - config = dai->dai_config; - /* - * The link DMA channel would be invalidated for running - * streams but not for streams that were in the PAUSED - * state during suspend. So invalidate it here before setting - * the dai config in the DSP. - */ - if (config->type == SOF_DAI_INTEL_HDA) - config->hda.link_dma_ch = DMA_CHAN_INVALID; - } - - ret = sof_widget_setup(sdev, swidget); + /* Send PCM_FREE IPC to reset pipeline */ + if (pcm_ops->hw_free && spcm->prepared[substream->stream]) { + ret = pcm_ops->hw_free(sdev->component, substream); if (ret < 0) return ret; } - /* restore pipeline connections */ - list_for_each_entry(sroute, &sdev->route_list, list) { - - /* only set up routes belonging to static pipelines */ - if (!verify && (sroute->src_widget->dynamic_pipeline_widget || - sroute->sink_widget->dynamic_pipeline_widget)) - continue; - - ret = sof_route_setup_ipc(sdev, sroute); - if (ret < 0) { - dev_err(sdev->dev, "%s: restore pipeline connections failed\n", __func__); - return ret; - } - } - - /* complete pipeline */ - list_for_each_entry(swidget, &sdev->widget_list, list) { - switch (swidget->id) { - case snd_soc_dapm_scheduler: - /* only complete static pipelines */ - if (!verify && swidget->dynamic_pipeline_widget) - continue; - - swidget->complete = - snd_sof_complete_pipeline(sdev, swidget); - break; - default: - break; - } - } - - return 0; -} - -/* - * This function doesn't free widgets during suspend. It only resets the set up status for all - * routes and use_count for all widgets. - */ -int sof_tear_down_pipelines(struct snd_sof_dev *sdev, bool verify) -{ - struct snd_sof_widget *swidget; - struct snd_sof_route *sroute; - int ret; + spcm->prepared[substream->stream] = false; - /* - * This function is called during suspend and for one-time topology verification during - * first boot. In both cases, there is no need to protect swidget->use_count and - * sroute->setup because during suspend all streams are suspended and during topology - * loading the sound card unavailable to open PCMs. - */ - list_for_each_entry_reverse(swidget, &sdev->widget_list, list) { - if (!verify) { - swidget->use_count = 0; - continue; - } + /* stop the DMA */ + ret = snd_sof_pcm_platform_hw_free(sdev, substream); + if (ret < 0) + return ret; - ret = sof_widget_free(sdev, swidget); + /* free widget list */ + if (free_widget_list) { + ret = sof_widget_list_free(sdev, spcm, dir); if (ret < 0) - return ret; + dev_err(sdev->dev, "failed to free widgets during suspend\n"); } - list_for_each_entry(sroute, &sdev->route_list, list) - sroute->setup = false; - - return 0; + return ret; } /* @@ -745,20 +702,6 @@ struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_soc_component *scomp, return NULL; } -struct snd_sof_pcm *snd_sof_find_spcm_pcm_id(struct snd_soc_component *scomp, - unsigned int pcm_id) -{ - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - struct snd_sof_pcm *spcm; - - list_for_each_entry(spcm, &sdev->pcm_list, list) { - if (le32_to_cpu(spcm->pcm.pcm_id) == pcm_id) - return spcm; - } - - return NULL; -} - struct snd_sof_widget *snd_sof_find_swidget(struct snd_soc_component *scomp, const char *name) { @@ -810,39 +753,23 @@ struct snd_sof_dai *snd_sof_find_dai(struct snd_soc_component *scomp, return NULL; } -#define SOF_DAI_CLK_INTEL_SSP_MCLK 0 -#define SOF_DAI_CLK_INTEL_SSP_BCLK 1 - static int sof_dai_get_clk(struct snd_soc_pcm_runtime *rtd, int clk_type) { struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME); struct snd_sof_dai *dai = snd_sof_find_dai(component, (char *)rtd->dai_link->name); + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); + const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; /* use the tplg configured mclk if existed */ - if (!dai || !dai->dai_config) + if (!dai) return 0; - switch (dai->dai_config->type) { - case SOF_DAI_INTEL_SSP: - switch (clk_type) { - case SOF_DAI_CLK_INTEL_SSP_MCLK: - return dai->dai_config->ssp.mclk_rate; - case SOF_DAI_CLK_INTEL_SSP_BCLK: - return dai->dai_config->ssp.bclk_rate; - default: - dev_err(rtd->dev, "fail to get SSP clk %d rate\n", - clk_type); - return -EINVAL; - } - break; - default: - /* not yet implemented for platforms other than the above */ - dev_err(rtd->dev, "DAI type %d not supported yet!\n", - dai->dai_config->type); - return -EINVAL; - } + if (tplg_ops->dai_get_clk) + return tplg_ops->dai_get_clk(sdev, dai, clk_type); + + return 0; } /* @@ -865,6 +792,28 @@ int sof_dai_get_bclk(struct snd_soc_pcm_runtime *rtd) } EXPORT_SYMBOL(sof_dai_get_bclk); +static struct snd_sof_of_mach *sof_of_machine_select(struct snd_sof_dev *sdev) +{ + struct snd_sof_pdata *sof_pdata = sdev->pdata; + const struct sof_dev_desc *desc = sof_pdata->desc; + struct snd_sof_of_mach *mach = desc->of_machines; + + if (!mach) + return NULL; + + for (; mach->compatible; mach++) { + if (of_machine_is_compatible(mach->compatible)) { + sof_pdata->tplg_filename = mach->sof_tplg_filename; + if (mach->fw_filename) + sof_pdata->fw_filename = mach->fw_filename; + + return mach; + } + } + + return NULL; +} + /* * SOF Driver enumeration. */ @@ -875,11 +824,19 @@ int sof_machine_check(struct snd_sof_dev *sdev) struct snd_soc_acpi_mach *mach; if (!IS_ENABLED(CONFIG_SND_SOC_SOF_FORCE_NOCODEC_MODE)) { + const struct snd_sof_of_mach *of_mach; /* find machine */ - snd_sof_machine_select(sdev); - if (sof_pdata->machine) { - snd_sof_set_mach_params(sof_pdata->machine, sdev); + mach = snd_sof_machine_select(sdev); + if (mach) { + sof_pdata->machine = mach; + snd_sof_set_mach_params(mach, sdev); + return 0; + } + + of_mach = sof_of_machine_select(sdev); + if (of_mach) { + sof_pdata->of_machine = of_mach; return 0; } @@ -898,10 +855,11 @@ int sof_machine_check(struct snd_sof_dev *sdev) return -ENOMEM; mach->drv_name = "sof-nocodec"; - sof_pdata->tplg_filename = desc->nocodec_tplg_filename; + if (!sof_pdata->tplg_filename) + sof_pdata->tplg_filename = desc->nocodec_tplg_filename; sof_pdata->machine = mach; - snd_sof_set_mach_params(sof_pdata->machine, sdev); + snd_sof_set_mach_params(mach, sdev); return 0; } diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index 05e98e231b85..4284ea2f3a1f 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -29,6 +29,238 @@ #define DMA_CHAN_INVALID 0xFFFFFFFF #define WIDGET_IS_DAI(id) ((id) == snd_soc_dapm_dai_in || (id) == snd_soc_dapm_dai_out) +#define WIDGET_IS_AIF(id) ((id) == snd_soc_dapm_aif_in || (id) == snd_soc_dapm_aif_out) +#define WIDGET_IS_AIF_OR_DAI(id) (WIDGET_IS_DAI(id) || WIDGET_IS_AIF(id)) + +#define SOF_DAI_CLK_INTEL_SSP_MCLK 0 +#define SOF_DAI_CLK_INTEL_SSP_BCLK 1 + +enum sof_widget_op { + SOF_WIDGET_PREPARE, + SOF_WIDGET_SETUP, + SOF_WIDGET_FREE, + SOF_WIDGET_UNPREPARE, +}; + +/* + * Volume fractional word length define to 16 sets + * the volume linear gain value to use Qx.16 format + */ +#define VOLUME_FWL 16 + +#define SOF_TLV_ITEMS 3 + +static inline u32 mixer_to_ipc(unsigned int value, u32 *volume_map, int size) +{ + if (value >= size) + return volume_map[size - 1]; + + return volume_map[value]; +} + +static inline u32 ipc_to_mixer(u32 value, u32 *volume_map, int size) +{ + int i; + + for (i = 0; i < size; i++) { + if (volume_map[i] >= value) + return i; + } + + return i - 1; +} + +struct snd_sof_widget; +struct snd_sof_route; +struct snd_sof_control; +struct snd_sof_dai; + +struct snd_sof_dai_config_data { + int dai_index; + int dai_data; /* contains DAI-specific information */ +}; + +/** + * struct sof_ipc_pcm_ops - IPC-specific PCM ops + * @hw_params: Function pointer for hw_params + * @hw_free: Function pointer for hw_free + * @trigger: Function pointer for trigger + * @dai_link_fixup: Function pointer for DAI link fixup + */ +struct sof_ipc_pcm_ops { + int (*hw_params)(struct snd_soc_component *component, struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_sof_platform_stream_params *platform_params); + int (*hw_free)(struct snd_soc_component *component, struct snd_pcm_substream *substream); + int (*trigger)(struct snd_soc_component *component, struct snd_pcm_substream *substream, + int cmd); + int (*dai_link_fixup)(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params); +}; + +/** + * struct sof_ipc_tplg_control_ops - IPC-specific ops for topology kcontrol IO + */ +struct sof_ipc_tplg_control_ops { + bool (*volume_put)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol); + int (*volume_get)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol); + bool (*switch_put)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol); + int (*switch_get)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol); + bool (*enum_put)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol); + int (*enum_get)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol); + int (*bytes_put)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol); + int (*bytes_get)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol); + int (*bytes_ext_get)(struct snd_sof_control *scontrol, + const unsigned int __user *binary_data, unsigned int size); + int (*bytes_ext_volatile_get)(struct snd_sof_control *scontrol, + const unsigned int __user *binary_data, unsigned int size); + int (*bytes_ext_put)(struct snd_sof_control *scontrol, + const unsigned int __user *binary_data, unsigned int size); + /* update control data based on notification from the DSP */ + void (*update)(struct snd_sof_dev *sdev, void *ipc_control_message); + /* Optional callback to setup kcontrols associated with an swidget */ + int (*widget_kcontrol_setup)(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget); + /* mandatory callback to set up volume table for volume kcontrols */ + int (*set_up_volume_table)(struct snd_sof_control *scontrol, int tlv[SOF_TLV_ITEMS], + int size); +}; + +/** + * struct sof_ipc_tplg_widget_ops - IPC-specific ops for topology widgets + * @ipc_setup: Function pointer for setting up widget IPC params + * @ipc_free: Function pointer for freeing widget IPC params + * @token_list: List of token ID's that should be parsed for the widget + * @token_list_size: number of elements in token_list + * @bind_event: Function pointer for binding events to the widget + * @ipc_prepare: Optional op for preparing a widget for set up + * @ipc_unprepare: Optional op for unpreparing a widget + */ +struct sof_ipc_tplg_widget_ops { + int (*ipc_setup)(struct snd_sof_widget *swidget); + void (*ipc_free)(struct snd_sof_widget *swidget); + enum sof_tokens *token_list; + int token_list_size; + int (*bind_event)(struct snd_soc_component *scomp, struct snd_sof_widget *swidget, + u16 event_type); + int (*ipc_prepare)(struct snd_sof_widget *swidget, + struct snd_pcm_hw_params *fe_params, + struct snd_sof_platform_stream_params *platform_params, + struct snd_pcm_hw_params *source_params, int dir); + void (*ipc_unprepare)(struct snd_sof_widget *swidget); +}; + +/** + * struct sof_ipc_tplg_ops - IPC-specific topology ops + * @widget: Array of pointers to IPC-specific ops for widgets. This should always be of size + * SND_SOF_DAPM_TYPE_COUNT i.e one per widget type. Unsupported widget types will be + * initialized to 0. + * @control: Pointer to the IPC-specific ops for topology kcontrol IO + * @route_setup: Function pointer for setting up pipeline connections + * @route_free: Optional op for freeing pipeline connections. + * @token_list: List of all tokens supported by the IPC version. The size of the token_list + * array should be SOF_TOKEN_COUNT. The unused elements in the array will be + * initialized to 0. + * @control_setup: Function pointer for setting up kcontrol IPC-specific data + * @control_free: Function pointer for freeing kcontrol IPC-specific data + * @pipeline_complete: Function pointer for pipeline complete IPC + * @widget_setup: Function pointer for setting up setup in the DSP + * @widget_free: Function pointer for freeing widget in the DSP + * @dai_config: Function pointer for sending DAI config IPC to the DSP + * @dai_get_clk: Function pointer for getting the DAI clock setting + * @set_up_all_pipelines: Function pointer for setting up all topology pipelines + * @tear_down_all_pipelines: Function pointer for tearing down all topology pipelines + * @parse_manifest: Optional function pointer for ipc4 specific parsing of topology manifest + */ +struct sof_ipc_tplg_ops { + const struct sof_ipc_tplg_widget_ops *widget; + const struct sof_ipc_tplg_control_ops *control; + int (*route_setup)(struct snd_sof_dev *sdev, struct snd_sof_route *sroute); + int (*route_free)(struct snd_sof_dev *sdev, struct snd_sof_route *sroute); + const struct sof_token_info *token_list; + int (*control_setup)(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol); + int (*control_free)(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol); + int (*pipeline_complete)(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget); + int (*widget_setup)(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget); + int (*widget_free)(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget); + int (*dai_config)(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget, + unsigned int flags, struct snd_sof_dai_config_data *data); + int (*dai_get_clk)(struct snd_sof_dev *sdev, struct snd_sof_dai *dai, int clk_type); + int (*set_up_all_pipelines)(struct snd_sof_dev *sdev, bool verify); + int (*tear_down_all_pipelines)(struct snd_sof_dev *sdev, bool verify); + int (*parse_manifest)(struct snd_soc_component *scomp, int index, + struct snd_soc_tplg_manifest *man); +}; + +/** struct snd_sof_tuple - Tuple info + * @token: Token ID + * @value: union of a string or a u32 values + */ +struct snd_sof_tuple { + u32 token; + union { + u32 v; + const char *s; + } value; +}; + +/* + * List of SOF token ID's. The order of ID's does not matter as token arrays are looked up based on + * the ID. + */ +enum sof_tokens { + SOF_PCM_TOKENS, + SOF_PIPELINE_TOKENS, + SOF_SCHED_TOKENS, + SOF_ASRC_TOKENS, + SOF_SRC_TOKENS, + SOF_COMP_TOKENS, + SOF_BUFFER_TOKENS, + SOF_VOLUME_TOKENS, + SOF_PROCESS_TOKENS, + SOF_DAI_TOKENS, + SOF_DAI_LINK_TOKENS, + SOF_HDA_TOKENS, + SOF_SSP_TOKENS, + SOF_ALH_TOKENS, + SOF_DMIC_TOKENS, + SOF_DMIC_PDM_TOKENS, + SOF_ESAI_TOKENS, + SOF_SAI_TOKENS, + SOF_AFE_TOKENS, + SOF_CORE_TOKENS, + SOF_COMP_EXT_TOKENS, + SOF_IN_AUDIO_FORMAT_TOKENS, + SOF_OUT_AUDIO_FORMAT_TOKENS, + SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS, + SOF_COPIER_GATEWAY_CFG_TOKENS, + SOF_COPIER_TOKENS, + SOF_AUDIO_FMT_NUM_TOKENS, + SOF_COPIER_FORMAT_TOKENS, + SOF_GAIN_TOKENS, + SOF_ACPDMIC_TOKENS, + + /* this should be the last */ + SOF_TOKEN_COUNT, +}; + +/** + * struct sof_topology_token - SOF topology token definition + * @token: Token number + * @type: Token type + * @get_token: Function pointer to parse the token value and save it in a object + * @offset: Offset within an object to save the token value into + */ +struct sof_topology_token { + u32 token; + u32 type; + int (*get_token)(void *elem, void *object, u32 offset); + u32 offset; +}; + +struct sof_token_info { + const char *name; + const struct sof_topology_token *tokens; + int count; +}; /* PCM stream, mapped to FW component */ struct snd_sof_pcm_stream { @@ -66,15 +298,20 @@ struct snd_sof_led_control { /* ALSA SOF Kcontrol device */ struct snd_sof_control { struct snd_soc_component *scomp; + const char *name; int comp_id; int min_volume_step; /* min volume step for volume_table */ int max_volume_step; /* max volume step for volume_table */ int num_channels; unsigned int access; - u32 readback_offset; /* offset to mmapped data if used */ - struct sof_ipc_ctrl_data *control_data; + int info_type; + int index; /* pipeline ID */ + void *priv; /* private data copied from topology */ + size_t priv_size; /* size of private data */ + size_t max_size; + void *ipc_control_data; + int max; /* applicable to volume controls */ u32 size; /* cdata size */ - enum sof_ipc_ctrl_cmd cmd; u32 *volume_table; /* volume table computed from tlv data*/ struct list_head list; /* list in sdev control list */ @@ -85,17 +322,50 @@ struct snd_sof_control { bool comp_data_dirty; }; -struct snd_sof_widget; +/** struct snd_sof_dai_link - DAI link info + * @tuples: array of parsed tuples + * @num_tuples: number of tuples in the tuples array + * @link: Pointer to snd_soc_dai_link + * @hw_configs: Pointer to hw configs in topology + * @num_hw_configs: Number of hw configs in topology + * @default_hw_cfg_id: Default hw config ID + * @type: DAI type + * @list: item in snd_sof_dev dai_link list + */ +struct snd_sof_dai_link { + struct snd_sof_tuple *tuples; + int num_tuples; + struct snd_soc_dai_link *link; + struct snd_soc_tplg_hw_config *hw_configs; + int num_hw_configs; + int default_hw_cfg_id; + int type; + struct list_head list; +}; /* ASoC SOF DAPM widget */ struct snd_sof_widget { struct snd_soc_component *scomp; int comp_id; int pipeline_id; + /* + * complete flag is used to indicate that pipeline set up is complete for scheduler type + * widgets. It is unused for all other widget types. + */ int complete; + /* + * the prepared flag is used to indicate that a widget has been prepared for getting set + * up in the DSP. + */ + bool prepared; int use_count; /* use_count will be protected by the PCM mutex held by the core */ int core; - int id; + int id; /* id is the DAPM widget type */ + /* + * Instance ID is set dynamically when the widget gets set up in the FW. It should be + * unique for each module type across all pipelines. This will not be used in SOF_IPC. + */ + int instance_id; /* * Flag indicating if the widget should be set up dynamically when a PCM is opened. @@ -110,9 +380,12 @@ struct snd_sof_widget { struct snd_soc_dapm_widget *widget; struct list_head list; /* list in sdev widget list */ struct snd_sof_widget *pipe_widget; + void *module_info; - /* extended data for UUID components */ - struct sof_ipc_comp_ext comp_ext; + const guid_t uuid; + + int num_tuples; + struct snd_sof_tuple *tuples; void *private; /* core does not touch this */ }; @@ -135,12 +408,10 @@ struct snd_sof_dai { struct snd_soc_component *scomp; const char *name; - struct sof_ipc_comp_dai comp_dai; int number_configs; int current_config; - bool configured; /* DAI configured during BE hw_params */ - struct sof_ipc_dai_config *dai_config; struct list_head list; /* list in sdev dai list */ + void *private; }; /* @@ -182,14 +453,6 @@ void snd_sof_control_notify(struct snd_sof_dev *sdev, * 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 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_pipeline_core_enable(struct snd_sof_dev *sdev, - const struct snd_sof_widget *swidget); /* * Stream IPC @@ -211,8 +474,7 @@ struct snd_sof_pcm *snd_sof_find_spcm_dai(struct snd_soc_component *scomp, struct snd_soc_pcm_runtime *rtd) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - - struct snd_sof_pcm *spcm = NULL; + struct snd_sof_pcm *spcm; list_for_each_entry(spcm, &sdev->pcm_list, list) { if (le32_to_cpu(spcm->pcm.dai_id) == rtd->dai_link->id) @@ -227,10 +489,6 @@ struct snd_sof_pcm *snd_sof_find_spcm_name(struct snd_soc_component *scomp, struct snd_sof_pcm *snd_sof_find_spcm_comp(struct snd_soc_component *scomp, unsigned int comp_id, int *direction); -struct snd_sof_pcm *snd_sof_find_spcm_pcm_id(struct snd_soc_component *scomp, - unsigned int pcm_id); -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_init_elapsed_work(struct work_struct *work); @@ -242,22 +500,10 @@ static inline void snd_sof_compr_fragment_elapsed(struct snd_compr_stream *cstre static inline void snd_sof_compr_init_elapsed_work(struct work_struct *work) { } #endif -/* - * Mixer IPC - */ -int snd_sof_ipc_set_get_comp_data(struct snd_sof_control *scontrol, - u32 ipc_cmd, - enum sof_ipc_ctrl_type ctrl_type, - enum sof_ipc_ctrl_cmd ctrl_cmd, - bool send); - /* DAI link fixup */ int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params); /* PM */ -int sof_set_up_pipelines(struct snd_sof_dev *sdev, bool verify); -int sof_tear_down_pipelines(struct snd_sof_dev *sdev, bool verify); -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); @@ -267,8 +513,26 @@ void sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata); int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget); int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget); +int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsource, + struct snd_soc_dapm_widget *wsink); /* PCM */ -int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir); +int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, + struct snd_pcm_hw_params *fe_params, + struct snd_sof_platform_stream_params *platform_params, + int dir); int sof_widget_list_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir); +int sof_pcm_dsp_pcm_free(struct snd_pcm_substream *substream, struct snd_sof_dev *sdev, + struct snd_sof_pcm *spcm); +int sof_pcm_stream_free(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, + struct snd_sof_pcm *spcm, int dir, bool free_widget_list); +int get_token_u32(void *elem, void *object, u32 offset); +int get_token_u16(void *elem, void *object, u32 offset); +int get_token_comp_format(void *elem, void *object, u32 offset); +int get_token_dai_type(void *elem, void *object, u32 offset); +int get_token_uuid(void *elem, void *object, u32 offset); +int sof_update_ipc_object(struct snd_soc_component *scomp, void *object, enum sof_tokens token_id, + struct snd_sof_tuple *tuples, int num_tuples, + size_t object_size, int token_instance_num); +u32 vol_compute_gain(u32 value, int *tlv); #endif diff --git a/sound/soc/sof/sof-client-ipc-flood-test.c b/sound/soc/sof/sof-client-ipc-flood-test.c new file mode 100644 index 000000000000..4bdecd80248a --- /dev/null +++ b/sound/soc/sof/sof-client-ipc-flood-test.c @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// Authors: Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// Peter Ujfalusi <peter.ujfalusi@linux.intel.com> +// + +#include <linux/auxiliary_bus.h> +#include <linux/completion.h> +#include <linux/debugfs.h> +#include <linux/ktime.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <sound/sof/header.h> + +#include "sof-client.h" + +#define MAX_IPC_FLOOD_DURATION_MS 1000 +#define MAX_IPC_FLOOD_COUNT 10000 +#define IPC_FLOOD_TEST_RESULT_LEN 512 +#define SOF_IPC_CLIENT_SUSPEND_DELAY_MS 3000 + +#define DEBUGFS_IPC_FLOOD_COUNT "ipc_flood_count" +#define DEBUGFS_IPC_FLOOD_DURATION "ipc_flood_duration_ms" + +struct sof_ipc_flood_priv { + struct dentry *dfs_root; + struct dentry *dfs_link[2]; + char *buf; +}; + +static int sof_ipc_flood_dfs_open(struct inode *inode, struct file *file) +{ + struct sof_client_dev *cdev = inode->i_private; + int ret; + + if (sof_client_get_fw_state(cdev) == SOF_FW_CRASHED) + return -ENODEV; + + ret = debugfs_file_get(file->f_path.dentry); + if (unlikely(ret)) + return ret; + + ret = simple_open(inode, file); + if (ret) + debugfs_file_put(file->f_path.dentry); + + return ret; +} + +/* + * helper function to perform the flood test. Only one of the two params, ipc_duration_ms + * or ipc_count, will be non-zero and will determine the type of test + */ +static int sof_debug_ipc_flood_test(struct sof_client_dev *cdev, + bool flood_duration_test, + unsigned long ipc_duration_ms, + unsigned long ipc_count) +{ + struct sof_ipc_flood_priv *priv = cdev->data; + struct device *dev = &cdev->auxdev.dev; + struct sof_ipc_cmd_hdr hdr; + struct sof_ipc_reply reply; + u64 min_response_time = U64_MAX; + ktime_t start, end, test_end; + u64 avg_response_time = 0; + u64 max_response_time = 0; + u64 ipc_response_time; + int i = 0; + int ret; + + /* configure test IPC */ + hdr.cmd = SOF_IPC_GLB_TEST_MSG | SOF_IPC_TEST_IPC_FLOOD; + hdr.size = sizeof(hdr); + + /* set test end time for duration flood test */ + if (flood_duration_test) + test_end = ktime_get_ns() + ipc_duration_ms * NSEC_PER_MSEC; + + /* send test IPC's */ + while (1) { + start = ktime_get(); + ret = sof_client_ipc_tx_message(cdev, &hdr, &reply, sizeof(reply)); + end = ktime_get(); + + if (ret < 0) + break; + + /* compute min and max response times */ + ipc_response_time = ktime_to_ns(ktime_sub(end, start)); + min_response_time = min(min_response_time, ipc_response_time); + max_response_time = max(max_response_time, ipc_response_time); + + /* sum up response times */ + avg_response_time += ipc_response_time; + i++; + + /* test complete? */ + if (flood_duration_test) { + if (ktime_to_ns(end) >= test_end) + break; + } else { + if (i == ipc_count) + break; + } + } + + if (ret < 0) + dev_err(dev, "ipc flood test failed at %d iterations\n", i); + + /* return if the first IPC fails */ + if (!i) + return ret; + + /* compute average response time */ + do_div(avg_response_time, i); + + /* clear previous test output */ + memset(priv->buf, 0, IPC_FLOOD_TEST_RESULT_LEN); + + if (!ipc_count) { + dev_dbg(dev, "IPC Flood test duration: %lums\n", ipc_duration_ms); + snprintf(priv->buf, IPC_FLOOD_TEST_RESULT_LEN, + "IPC Flood test duration: %lums\n", ipc_duration_ms); + } + + dev_dbg(dev, "IPC Flood count: %d, Avg response time: %lluns\n", + i, avg_response_time); + dev_dbg(dev, "Max response time: %lluns\n", max_response_time); + dev_dbg(dev, "Min response time: %lluns\n", min_response_time); + + /* format output string and save test results */ + snprintf(priv->buf + strlen(priv->buf), + IPC_FLOOD_TEST_RESULT_LEN - strlen(priv->buf), + "IPC Flood count: %d\nAvg response time: %lluns\n", + i, avg_response_time); + + snprintf(priv->buf + strlen(priv->buf), + IPC_FLOOD_TEST_RESULT_LEN - strlen(priv->buf), + "Max response time: %lluns\nMin response time: %lluns\n", + max_response_time, min_response_time); + + return ret; +} + +/* + * Writing to the debugfs entry initiates the IPC flood test based on + * the IPC count or the duration specified by the user. + */ +static ssize_t sof_ipc_flood_dfs_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct device *dev = &cdev->auxdev.dev; + unsigned long ipc_duration_ms = 0; + bool flood_duration_test = false; + unsigned long ipc_count = 0; + struct dentry *dentry; + int err; + size_t size; + char *string; + int ret; + + string = kzalloc(count + 1, GFP_KERNEL); + if (!string) + return -ENOMEM; + + size = simple_write_to_buffer(string, count, ppos, buffer, count); + + /* + * write op is only supported for ipc_flood_count or + * ipc_flood_duration_ms debugfs entries atm. + * ipc_flood_count floods the DSP with the number of IPC's specified. + * ipc_duration_ms test floods the DSP for the time specified + * in the debugfs entry. + */ + dentry = file->f_path.dentry; + if (strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_COUNT) && + strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_DURATION)) { + ret = -EINVAL; + goto out; + } + + if (!strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_DURATION)) + flood_duration_test = true; + + /* test completion criterion */ + if (flood_duration_test) + ret = kstrtoul(string, 0, &ipc_duration_ms); + else + ret = kstrtoul(string, 0, &ipc_count); + if (ret < 0) + goto out; + + /* limit max duration/ipc count for flood test */ + if (flood_duration_test) { + if (!ipc_duration_ms) { + ret = size; + goto out; + } + + /* find the minimum. min() is not used to avoid warnings */ + if (ipc_duration_ms > MAX_IPC_FLOOD_DURATION_MS) + ipc_duration_ms = MAX_IPC_FLOOD_DURATION_MS; + } else { + if (!ipc_count) { + ret = size; + goto out; + } + + /* find the minimum. min() is not used to avoid warnings */ + if (ipc_count > MAX_IPC_FLOOD_COUNT) + ipc_count = MAX_IPC_FLOOD_COUNT; + } + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0 && ret != -EACCES) { + dev_err_ratelimited(dev, "debugfs write failed to resume %d\n", ret); + goto out; + } + + /* flood test */ + ret = sof_debug_ipc_flood_test(cdev, flood_duration_test, + ipc_duration_ms, ipc_count); + + pm_runtime_mark_last_busy(dev); + err = pm_runtime_put_autosuspend(dev); + if (err < 0) + dev_err_ratelimited(dev, "debugfs write failed to idle %d\n", err); + + /* return size if test is successful */ + if (ret >= 0) + ret = size; +out: + kfree(string); + return ret; +} + +/* return the result of the last IPC flood test */ +static ssize_t sof_ipc_flood_dfs_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_ipc_flood_priv *priv = cdev->data; + size_t size_ret; + + struct dentry *dentry; + + dentry = file->f_path.dentry; + if (!strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_COUNT) || + !strcmp(dentry->d_name.name, DEBUGFS_IPC_FLOOD_DURATION)) { + if (*ppos) + return 0; + + count = min_t(size_t, count, strlen(priv->buf)); + size_ret = copy_to_user(buffer, priv->buf, count); + if (size_ret) + return -EFAULT; + + *ppos += count; + return count; + } + return count; +} + +static int sof_ipc_flood_dfs_release(struct inode *inode, struct file *file) +{ + debugfs_file_put(file->f_path.dentry); + + return 0; +} + +static const struct file_operations sof_ipc_flood_fops = { + .open = sof_ipc_flood_dfs_open, + .read = sof_ipc_flood_dfs_read, + .llseek = default_llseek, + .write = sof_ipc_flood_dfs_write, + .release = sof_ipc_flood_dfs_release, + + .owner = THIS_MODULE, +}; + +/* + * The IPC test client creates a couple of debugfs entries that will be used + * flood tests. Users can write to these entries to execute the IPC flood test + * by specifying either the number of IPCs to flood the DSP with or the duration + * (in ms) for which the DSP should be flooded with test IPCs. At the + * end of each test, the average, min and max response times are reported back. + * The results of the last flood test can be accessed by reading the debugfs + * entries. + */ +static int sof_ipc_flood_probe(struct auxiliary_device *auxdev, + const struct auxiliary_device_id *id) +{ + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + struct dentry *debugfs_root = sof_client_get_debugfs_root(cdev); + struct device *dev = &auxdev->dev; + struct sof_ipc_flood_priv *priv; + + /* allocate memory for client data */ + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->buf = devm_kmalloc(dev, IPC_FLOOD_TEST_RESULT_LEN, GFP_KERNEL); + if (!priv->buf) + return -ENOMEM; + + cdev->data = priv; + + /* create debugfs root folder with device name under parent SOF dir */ + priv->dfs_root = debugfs_create_dir(dev_name(dev), debugfs_root); + if (!IS_ERR_OR_NULL(priv->dfs_root)) { + /* create read-write ipc_flood_count debugfs entry */ + debugfs_create_file(DEBUGFS_IPC_FLOOD_COUNT, 0644, priv->dfs_root, + cdev, &sof_ipc_flood_fops); + + /* create read-write ipc_flood_duration_ms debugfs entry */ + debugfs_create_file(DEBUGFS_IPC_FLOOD_DURATION, 0644, + priv->dfs_root, cdev, &sof_ipc_flood_fops); + + if (auxdev->id == 0) { + /* + * Create symlinks for backwards compatibility to the + * first IPC flood test instance + */ + char target[100]; + + snprintf(target, 100, "%s/" DEBUGFS_IPC_FLOOD_COUNT, + dev_name(dev)); + priv->dfs_link[0] = + debugfs_create_symlink(DEBUGFS_IPC_FLOOD_COUNT, + debugfs_root, target); + + snprintf(target, 100, "%s/" DEBUGFS_IPC_FLOOD_DURATION, + dev_name(dev)); + priv->dfs_link[1] = + debugfs_create_symlink(DEBUGFS_IPC_FLOOD_DURATION, + debugfs_root, target); + } + } + + /* enable runtime PM */ + pm_runtime_set_autosuspend_delay(dev, SOF_IPC_CLIENT_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_idle(dev); + + return 0; +} + +static void sof_ipc_flood_remove(struct auxiliary_device *auxdev) +{ + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + struct sof_ipc_flood_priv *priv = cdev->data; + + pm_runtime_disable(&auxdev->dev); + + if (auxdev->id == 0) { + debugfs_remove(priv->dfs_link[0]); + debugfs_remove(priv->dfs_link[1]); + } + + debugfs_remove_recursive(priv->dfs_root); +} + +static const struct auxiliary_device_id sof_ipc_flood_client_id_table[] = { + { .name = "snd_sof.ipc_flood" }, + {}, +}; +MODULE_DEVICE_TABLE(auxiliary, sof_ipc_flood_client_id_table); + +/* + * No need for driver pm_ops as the generic pm callbacks in the auxiliary bus + * type are enough to ensure that the parent SOF device resumes to bring the DSP + * back to D0. + * Driver name will be set based on KBUILD_MODNAME. + */ +static struct auxiliary_driver sof_ipc_flood_client_drv = { + .probe = sof_ipc_flood_probe, + .remove = sof_ipc_flood_remove, + + .id_table = sof_ipc_flood_client_id_table, +}; + +module_auxiliary_driver(sof_ipc_flood_client_drv); + +MODULE_DESCRIPTION("SOF IPC Flood Test Client Driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); diff --git a/sound/soc/sof/sof-client-ipc-msg-injector.c b/sound/soc/sof/sof-client-ipc-msg-injector.c new file mode 100644 index 000000000000..752d5320680f --- /dev/null +++ b/sound/soc/sof/sof-client-ipc-msg-injector.c @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// Author: Peter Ujfalusi <peter.ujfalusi@linux.intel.com> +// + +#include <linux/auxiliary_bus.h> +#include <linux/completion.h> +#include <linux/debugfs.h> +#include <linux/ktime.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <sound/sof/header.h> +#include <sound/sof/ipc4/header.h> + +#include "sof-client.h" + +#define SOF_IPC_CLIENT_SUSPEND_DELAY_MS 3000 + +struct sof_msg_inject_priv { + struct dentry *dfs_file; + size_t max_msg_size; + enum sof_ipc_type ipc_type; + + void *tx_buffer; + void *rx_buffer; +}; + +static int sof_msg_inject_dfs_open(struct inode *inode, struct file *file) +{ + struct sof_client_dev *cdev = inode->i_private; + int ret; + + if (sof_client_get_fw_state(cdev) == SOF_FW_CRASHED) + return -ENODEV; + + ret = debugfs_file_get(file->f_path.dentry); + if (unlikely(ret)) + return ret; + + ret = simple_open(inode, file); + if (ret) + debugfs_file_put(file->f_path.dentry); + + return ret; +} + +static ssize_t sof_msg_inject_dfs_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_msg_inject_priv *priv = cdev->data; + struct sof_ipc_reply *rhdr = priv->rx_buffer; + + if (!rhdr->hdr.size || !count || *ppos) + return 0; + + if (count > rhdr->hdr.size) + count = rhdr->hdr.size; + + if (copy_to_user(buffer, priv->rx_buffer, count)) + return -EFAULT; + + *ppos += count; + return count; +} + +static ssize_t sof_msg_inject_ipc4_dfs_read(struct file *file, + char __user *buffer, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_msg_inject_priv *priv = cdev->data; + struct sof_ipc4_msg *ipc4_msg = priv->rx_buffer; + size_t header_size = sizeof(ipc4_msg->header_u64); + size_t remaining; + + if (!ipc4_msg->header_u64 || !count || *ppos) + return 0; + + /* we need space for the header at minimum (u64) */ + if (count < header_size) + return -ENOSPC; + + remaining = header_size; + + /* Only get large config have payload */ + if (SOF_IPC4_MSG_IS_MODULE_MSG(ipc4_msg->primary) && + (SOF_IPC4_MSG_TYPE_GET(ipc4_msg->primary) == SOF_IPC4_MOD_LARGE_CONFIG_GET)) + remaining += ipc4_msg->data_size; + + if (count > remaining) + count = remaining; + else if (count < remaining) + remaining = count; + + /* copy the header first */ + if (copy_to_user(buffer, &ipc4_msg->header_u64, header_size)) + return -EFAULT; + + *ppos += header_size; + remaining -= header_size; + + if (!remaining) + return count; + + if (remaining > ipc4_msg->data_size) + remaining = ipc4_msg->data_size; + + /* Copy the payload */ + if (copy_to_user(buffer + *ppos, ipc4_msg->data_ptr, remaining)) + return -EFAULT; + + *ppos += remaining; + return count; +} + +static int sof_msg_inject_send_message(struct sof_client_dev *cdev) +{ + struct sof_msg_inject_priv *priv = cdev->data; + struct device *dev = &cdev->auxdev.dev; + int ret, err; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0 && ret != -EACCES) { + dev_err_ratelimited(dev, "debugfs write failed to resume %d\n", ret); + return ret; + } + + /* send the message */ + ret = sof_client_ipc_tx_message(cdev, priv->tx_buffer, priv->rx_buffer, + priv->max_msg_size); + if (ret) + dev_err(dev, "IPC message send failed: %d\n", ret); + + pm_runtime_mark_last_busy(dev); + err = pm_runtime_put_autosuspend(dev); + if (err < 0) + dev_err_ratelimited(dev, "debugfs write failed to idle %d\n", err); + + return ret; +} + +static ssize_t sof_msg_inject_dfs_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_msg_inject_priv *priv = cdev->data; + ssize_t size; + int ret; + + if (*ppos) + return 0; + + size = simple_write_to_buffer(priv->tx_buffer, priv->max_msg_size, + ppos, buffer, count); + if (size < 0) + return size; + if (size != count) + return -EFAULT; + + memset(priv->rx_buffer, 0, priv->max_msg_size); + + ret = sof_msg_inject_send_message(cdev); + + /* return the error code if test failed */ + if (ret < 0) + size = ret; + + return size; +}; + +static ssize_t sof_msg_inject_ipc4_dfs_write(struct file *file, + const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_msg_inject_priv *priv = cdev->data; + struct sof_ipc4_msg *ipc4_msg = priv->tx_buffer; + size_t data_size; + int ret; + + if (*ppos) + return 0; + + if (count < sizeof(ipc4_msg->header_u64)) + return -EINVAL; + + /* copy the header first */ + if (copy_from_user(&ipc4_msg->header_u64, buffer, + sizeof(ipc4_msg->header_u64))) + return -EFAULT; + + data_size = count - sizeof(ipc4_msg->header_u64); + if (data_size > priv->max_msg_size) + return -EINVAL; + + /* Copy the payload */ + if (copy_from_user(ipc4_msg->data_ptr, + buffer + sizeof(ipc4_msg->header_u64), data_size)) + return -EFAULT; + + ipc4_msg->data_size = data_size; + + /* Initialize the reply storage */ + ipc4_msg = priv->rx_buffer; + ipc4_msg->header_u64 = 0; + ipc4_msg->data_size = priv->max_msg_size; + memset(ipc4_msg->data_ptr, 0, priv->max_msg_size); + + ret = sof_msg_inject_send_message(cdev); + + /* return the error code if test failed */ + if (ret < 0) + return ret; + + return count; +}; + +static int sof_msg_inject_dfs_release(struct inode *inode, struct file *file) +{ + debugfs_file_put(file->f_path.dentry); + + return 0; +} + +static const struct file_operations sof_msg_inject_fops = { + .open = sof_msg_inject_dfs_open, + .read = sof_msg_inject_dfs_read, + .write = sof_msg_inject_dfs_write, + .llseek = default_llseek, + .release = sof_msg_inject_dfs_release, + + .owner = THIS_MODULE, +}; + +static const struct file_operations sof_msg_inject_ipc4_fops = { + .open = sof_msg_inject_dfs_open, + .read = sof_msg_inject_ipc4_dfs_read, + .write = sof_msg_inject_ipc4_dfs_write, + .llseek = default_llseek, + .release = sof_msg_inject_dfs_release, + + .owner = THIS_MODULE, +}; + +static int sof_msg_inject_probe(struct auxiliary_device *auxdev, + const struct auxiliary_device_id *id) +{ + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + struct dentry *debugfs_root = sof_client_get_debugfs_root(cdev); + static const struct file_operations *fops; + struct device *dev = &auxdev->dev; + struct sof_msg_inject_priv *priv; + size_t alloc_size; + + /* allocate memory for client data */ + priv = devm_kzalloc(&auxdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->ipc_type = sof_client_get_ipc_type(cdev); + priv->max_msg_size = sof_client_get_ipc_max_payload_size(cdev); + alloc_size = priv->max_msg_size; + + if (priv->ipc_type == SOF_INTEL_IPC4) + alloc_size += sizeof(struct sof_ipc4_msg); + + priv->tx_buffer = devm_kmalloc(dev, alloc_size, GFP_KERNEL); + priv->rx_buffer = devm_kzalloc(dev, alloc_size, GFP_KERNEL); + if (!priv->tx_buffer || !priv->rx_buffer) + return -ENOMEM; + + if (priv->ipc_type == SOF_INTEL_IPC4) { + struct sof_ipc4_msg *ipc4_msg; + + ipc4_msg = priv->tx_buffer; + ipc4_msg->data_ptr = priv->tx_buffer + sizeof(struct sof_ipc4_msg); + + ipc4_msg = priv->rx_buffer; + ipc4_msg->data_ptr = priv->rx_buffer + sizeof(struct sof_ipc4_msg); + + fops = &sof_msg_inject_ipc4_fops; + } else { + fops = &sof_msg_inject_fops; + } + + cdev->data = priv; + + priv->dfs_file = debugfs_create_file("ipc_msg_inject", 0644, debugfs_root, + cdev, fops); + + /* enable runtime PM */ + pm_runtime_set_autosuspend_delay(dev, SOF_IPC_CLIENT_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_idle(dev); + + return 0; +} + +static void sof_msg_inject_remove(struct auxiliary_device *auxdev) +{ + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + struct sof_msg_inject_priv *priv = cdev->data; + + pm_runtime_disable(&auxdev->dev); + + debugfs_remove(priv->dfs_file); +} + +static const struct auxiliary_device_id sof_msg_inject_client_id_table[] = { + { .name = "snd_sof.msg_injector" }, + {}, +}; +MODULE_DEVICE_TABLE(auxiliary, sof_msg_inject_client_id_table); + +/* + * No need for driver pm_ops as the generic pm callbacks in the auxiliary bus + * type are enough to ensure that the parent SOF device resumes to bring the DSP + * back to D0. + * Driver name will be set based on KBUILD_MODNAME. + */ +static struct auxiliary_driver sof_msg_inject_client_drv = { + .probe = sof_msg_inject_probe, + .remove = sof_msg_inject_remove, + + .id_table = sof_msg_inject_client_id_table, +}; + +module_auxiliary_driver(sof_msg_inject_client_drv); + +MODULE_DESCRIPTION("SOF IPC Message Injector Client Driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); diff --git a/sound/soc/sof/sof-client-probes.c b/sound/soc/sof/sof-client-probes.c new file mode 100644 index 000000000000..ddeabbb5580e --- /dev/null +++ b/sound/soc/sof/sof-client-probes.c @@ -0,0 +1,750 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2019-2022 Intel Corporation. All rights reserved. +// +// Author: Cezary Rojewski <cezary.rojewski@intel.com> +// +// SOF client support: +// Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// Peter Ujfalusi <peter.ujfalusi@linux.intel.com> +// + +#include <linux/debugfs.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/string_helpers.h> + +#include <sound/soc.h> +#include <sound/sof/header.h> +#include "sof-client.h" +#include "sof-client-probes.h" + +#define SOF_PROBES_SUSPEND_DELAY_MS 3000 +/* only extraction supported for now */ +#define SOF_PROBES_NUM_DAI_LINKS 1 + +#define SOF_PROBES_INVALID_NODE_ID UINT_MAX + +static bool __read_mostly sof_probes_enabled; +module_param_named(enable, sof_probes_enabled, bool, 0444); +MODULE_PARM_DESC(enable, "Enable SOF probes support"); + +struct sof_probes_priv { + struct dentry *dfs_points; + struct dentry *dfs_points_remove; + u32 extractor_stream_tag; + struct snd_soc_card card; + + const struct sof_probes_host_ops *host_ops; +}; + +struct sof_probe_point_desc { + unsigned int buffer_id; + unsigned int purpose; + unsigned int stream_tag; +} __packed; + +struct sof_probe_dma { + unsigned int stream_tag; + unsigned int dma_buffer_size; +} __packed; + +struct sof_ipc_probe_dma_add_params { + struct sof_ipc_cmd_hdr hdr; + unsigned int num_elems; + struct sof_probe_dma dma[]; +} __packed; + +struct sof_ipc_probe_info_params { + struct sof_ipc_reply rhdr; + unsigned int num_elems; + union { + struct sof_probe_dma dma[0]; + struct sof_probe_point_desc desc[0]; + }; +} __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; + +/** + * sof_probes_init - initialize data probing + * @cdev: SOF client device + * @stream_tag: Extractor stream tag + * @buffer_size: DMA buffer size to set for extractor + * + * Host chooses whether extraction is supported or not by providing + * valid stream tag to DSP. Once specified, stream described by that + * tag will be tied to DSP for extraction for the entire lifetime of + * probe. + * + * Probing is initialized only once and each INIT request must be + * matched by DEINIT call. + */ +static int sof_probes_init(struct sof_client_dev *cdev, u32 stream_tag, + size_t buffer_size) +{ + struct sof_ipc_probe_dma_add_params *msg; + size_t size = struct_size(msg, dma, 1); + struct sof_ipc_reply reply; + int ret; + + msg = kmalloc(size, GFP_KERNEL); + if (!msg) + return -ENOMEM; + msg->hdr.size = size; + msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_INIT; + msg->num_elems = 1; + msg->dma[0].stream_tag = stream_tag; + msg->dma[0].dma_buffer_size = buffer_size; + + ret = sof_client_ipc_tx_message(cdev, msg, &reply, sizeof(reply)); + kfree(msg); + return ret; +} + +/** + * sof_probes_deinit - cleanup after data probing + * @cdev: SOF client device + * + * Host sends DEINIT request to free previously initialized probe + * on DSP side once it is no longer needed. DEINIT only when there + * are no probes connected and with all injectors detached. + */ +static int sof_probes_deinit(struct sof_client_dev *cdev) +{ + struct sof_ipc_cmd_hdr msg; + struct sof_ipc_reply reply; + + msg.size = sizeof(msg); + msg.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_DEINIT; + + return sof_client_ipc_tx_message(cdev, &msg, &reply, sizeof(reply)); +} + +static int sof_probes_info(struct sof_client_dev *cdev, unsigned int cmd, + void **params, size_t *num_params) +{ + size_t max_msg_size = sof_client_get_ipc_max_payload_size(cdev); + struct sof_ipc_probe_info_params msg = {{{0}}}; + struct sof_ipc_probe_info_params *reply; + size_t bytes; + int ret; + + *params = NULL; + *num_params = 0; + + reply = kzalloc(max_msg_size, GFP_KERNEL); + if (!reply) + return -ENOMEM; + msg.rhdr.hdr.size = sizeof(msg); + msg.rhdr.hdr.cmd = SOF_IPC_GLB_PROBE | cmd; + + ret = sof_client_ipc_tx_message(cdev, &msg, reply, max_msg_size); + if (ret < 0 || reply->rhdr.error < 0) + goto exit; + + if (!reply->num_elems) + goto exit; + + if (cmd == SOF_IPC_PROBE_DMA_INFO) + bytes = sizeof(reply->dma[0]); + else + bytes = sizeof(reply->desc[0]); + bytes *= reply->num_elems; + *params = kmemdup(&reply->dma[0], bytes, GFP_KERNEL); + if (!*params) { + ret = -ENOMEM; + goto exit; + } + *num_params = reply->num_elems; + +exit: + kfree(reply); + return ret; +} + +/** + * sof_probes_points_info - retrieve list of active probe points + * @cdev: SOF client device + * @desc: Returned list of active probes + * @num_desc: Returned count of active probes + * + * Host sends PROBE_POINT_INFO request to obtain list of active probe + * points, valid for disconnection when given probe is no longer + * required. + */ +static int sof_probes_points_info(struct sof_client_dev *cdev, + struct sof_probe_point_desc **desc, + size_t *num_desc) +{ + return sof_probes_info(cdev, SOF_IPC_PROBE_POINT_INFO, + (void **)desc, num_desc); +} + +/** + * sof_probes_points_add - connect specified probes + * @cdev: SOF client device + * @desc: List of probe points to connect + * @num_desc: Number of elements in @desc + * + * Dynamically connects to provided set of endpoints. Immediately + * after connection is established, host must be prepared to + * transfer data from or to target stream given the probing purpose. + * + * Each probe point should be removed using PROBE_POINT_REMOVE + * request when no longer needed. + */ +static int sof_probes_points_add(struct sof_client_dev *cdev, + struct sof_probe_point_desc *desc, + size_t num_desc) +{ + struct sof_ipc_probe_point_add_params *msg; + size_t size = struct_size(msg, desc, num_desc); + struct sof_ipc_reply reply; + int ret; + + msg = kmalloc(size, GFP_KERNEL); + if (!msg) + return -ENOMEM; + msg->hdr.size = size; + msg->num_elems = num_desc; + msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_POINT_ADD; + memcpy(&msg->desc[0], desc, size - sizeof(*msg)); + + ret = sof_client_ipc_tx_message(cdev, msg, &reply, sizeof(reply)); + kfree(msg); + return ret; +} + +/** + * sof_probes_points_remove - disconnect specified probes + * @cdev: SOF client device + * @buffer_id: List of probe points to disconnect + * @num_buffer_id: Number of elements in @desc + * + * Removes previously connected probes from list of active probe + * points and frees all resources on DSP side. + */ +static int sof_probes_points_remove(struct sof_client_dev *cdev, + unsigned int *buffer_id, size_t num_buffer_id) +{ + struct sof_ipc_probe_point_remove_params *msg; + size_t size = struct_size(msg, buffer_id, num_buffer_id); + struct sof_ipc_reply reply; + int ret; + + msg = kmalloc(size, GFP_KERNEL); + if (!msg) + return -ENOMEM; + msg->hdr.size = size; + msg->num_elems = num_buffer_id; + msg->hdr.cmd = SOF_IPC_GLB_PROBE | SOF_IPC_PROBE_POINT_REMOVE; + memcpy(&msg->buffer_id[0], buffer_id, size - sizeof(*msg)); + + ret = sof_client_ipc_tx_message(cdev, msg, &reply, sizeof(reply)); + kfree(msg); + return ret; +} + +static int sof_probes_compr_startup(struct snd_compr_stream *cstream, + struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = snd_soc_component_get_drvdata(dai->component); + struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card); + struct sof_probes_priv *priv = cdev->data; + const struct sof_probes_host_ops *ops = priv->host_ops; + int ret; + + if (sof_client_get_fw_state(cdev) == SOF_FW_CRASHED) + return -ENODEV; + + ret = sof_client_core_module_get(cdev); + if (ret) + return ret; + + ret = ops->startup(cdev, cstream, dai, &priv->extractor_stream_tag); + if (ret) { + dev_err(dai->dev, "Failed to startup probe stream: %d\n", ret); + priv->extractor_stream_tag = SOF_PROBES_INVALID_NODE_ID; + sof_client_core_module_put(cdev); + } + + return ret; +} + +static int sof_probes_compr_shutdown(struct snd_compr_stream *cstream, + struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = snd_soc_component_get_drvdata(dai->component); + struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card); + struct sof_probes_priv *priv = cdev->data; + const struct sof_probes_host_ops *ops = priv->host_ops; + struct sof_probe_point_desc *desc; + size_t num_desc; + int i, ret; + + /* disconnect all probe points */ + ret = sof_probes_points_info(cdev, &desc, &num_desc); + if (ret < 0) { + dev_err(dai->dev, "Failed to get probe points: %d\n", ret); + goto exit; + } + + for (i = 0; i < num_desc; i++) + sof_probes_points_remove(cdev, &desc[i].buffer_id, 1); + kfree(desc); + +exit: + ret = sof_probes_deinit(cdev); + if (ret < 0) + dev_err(dai->dev, "Failed to deinit probe: %d\n", ret); + + priv->extractor_stream_tag = SOF_PROBES_INVALID_NODE_ID; + snd_compr_free_pages(cstream); + + ret = ops->shutdown(cdev, cstream, dai); + + sof_client_core_module_put(cdev); + + return ret; +} + +static int sof_probes_compr_set_params(struct snd_compr_stream *cstream, + struct snd_compr_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = snd_soc_component_get_drvdata(dai->component); + struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card); + struct snd_compr_runtime *rtd = cstream->runtime; + struct sof_probes_priv *priv = cdev->data; + const struct sof_probes_host_ops *ops = priv->host_ops; + int ret; + + cstream->dma_buffer.dev.type = SNDRV_DMA_TYPE_DEV_SG; + cstream->dma_buffer.dev.dev = sof_client_get_dma_dev(cdev); + ret = snd_compr_malloc_pages(cstream, rtd->buffer_size); + if (ret < 0) + return ret; + + ret = ops->set_params(cdev, cstream, params, dai); + if (ret) + return ret; + + ret = sof_probes_init(cdev, priv->extractor_stream_tag, rtd->dma_bytes); + if (ret < 0) { + dev_err(dai->dev, "Failed to init probe: %d\n", ret); + return ret; + } + + return 0; +} + +static int sof_probes_compr_trigger(struct snd_compr_stream *cstream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = snd_soc_component_get_drvdata(dai->component); + struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card); + struct sof_probes_priv *priv = cdev->data; + const struct sof_probes_host_ops *ops = priv->host_ops; + + return ops->trigger(cdev, cstream, cmd, dai); +} + +static int sof_probes_compr_pointer(struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp, + struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = snd_soc_component_get_drvdata(dai->component); + struct sof_client_dev *cdev = snd_soc_card_get_drvdata(card); + struct sof_probes_priv *priv = cdev->data; + const struct sof_probes_host_ops *ops = priv->host_ops; + + return ops->pointer(cdev, cstream, tstamp, dai); +} + +static const struct snd_soc_cdai_ops sof_probes_compr_ops = { + .startup = sof_probes_compr_startup, + .shutdown = sof_probes_compr_shutdown, + .set_params = sof_probes_compr_set_params, + .trigger = sof_probes_compr_trigger, + .pointer = sof_probes_compr_pointer, +}; + +static int sof_probes_compr_copy(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + char __user *buf, size_t count) +{ + struct snd_compr_runtime *rtd = cstream->runtime; + unsigned int offset, n; + void *ptr; + int ret; + + if (count > rtd->buffer_size) + count = rtd->buffer_size; + + div_u64_rem(rtd->total_bytes_transferred, rtd->buffer_size, &offset); + ptr = rtd->dma_area + offset; + n = rtd->buffer_size - offset; + + if (count < n) { + ret = copy_to_user(buf, ptr, count); + } else { + ret = copy_to_user(buf, ptr, n); + ret += copy_to_user(buf + n, rtd->dma_area, count - n); + } + + if (ret) + return count - ret; + return count; +} + +static const struct snd_compress_ops sof_probes_compressed_ops = { + .copy = sof_probes_compr_copy, +}; + +static ssize_t sof_probes_dfs_points_read(struct file *file, char __user *to, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_probes_priv *priv = cdev->data; + struct device *dev = &cdev->auxdev.dev; + struct sof_probe_point_desc *desc; + int remaining, offset; + size_t num_desc; + char *buf; + int i, ret, err; + + if (priv->extractor_stream_tag == SOF_PROBES_INVALID_NODE_ID) { + dev_warn(dev, "no extractor stream running\n"); + return -ENOENT; + } + + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0 && ret != -EACCES) { + dev_err_ratelimited(dev, "debugfs read failed to resume %d\n", ret); + goto exit; + } + + ret = sof_probes_points_info(cdev, &desc, &num_desc); + if (ret < 0) + goto exit; + + pm_runtime_mark_last_busy(dev); + err = pm_runtime_put_autosuspend(dev); + if (err < 0) + dev_err_ratelimited(dev, "debugfs read failed to idle %d\n", err); + + for (i = 0; i < num_desc; i++) { + offset = strlen(buf); + remaining = PAGE_SIZE - offset; + ret = snprintf(buf + offset, remaining, + "Id: %#010x Purpose: %u Node id: %#x\n", + desc[i].buffer_id, desc[i].purpose, desc[i].stream_tag); + if (ret < 0 || ret >= remaining) { + /* truncate the output buffer at the last full line */ + buf[offset] = '\0'; + break; + } + } + + ret = simple_read_from_buffer(to, count, ppos, buf, strlen(buf)); + + kfree(desc); +exit: + kfree(buf); + return ret; +} + +static ssize_t +sof_probes_dfs_points_write(struct file *file, const char __user *from, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_probes_priv *priv = cdev->data; + struct device *dev = &cdev->auxdev.dev; + struct sof_probe_point_desc *desc; + u32 num_elems, *array; + size_t bytes; + int ret, err; + + if (priv->extractor_stream_tag == SOF_PROBES_INVALID_NODE_ID) { + dev_warn(dev, "no extractor stream running\n"); + return -ENOENT; + } + + ret = parse_int_array_user(from, count, (int **)&array); + if (ret < 0) + return ret; + + num_elems = *array; + bytes = sizeof(*array) * num_elems; + if (bytes % sizeof(*desc)) { + ret = -EINVAL; + goto exit; + } + + desc = (struct sof_probe_point_desc *)&array[1]; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0 && ret != -EACCES) { + dev_err_ratelimited(dev, "debugfs write failed to resume %d\n", ret); + goto exit; + } + + ret = sof_probes_points_add(cdev, desc, bytes / sizeof(*desc)); + if (!ret) + ret = count; + + pm_runtime_mark_last_busy(dev); + err = pm_runtime_put_autosuspend(dev); + if (err < 0) + dev_err_ratelimited(dev, "debugfs write failed to idle %d\n", err); +exit: + kfree(array); + return ret; +} + +static const struct file_operations sof_probes_points_fops = { + .open = simple_open, + .read = sof_probes_dfs_points_read, + .write = sof_probes_dfs_points_write, + .llseek = default_llseek, + + .owner = THIS_MODULE, +}; + +static ssize_t +sof_probes_dfs_points_remove_write(struct file *file, const char __user *from, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_probes_priv *priv = cdev->data; + struct device *dev = &cdev->auxdev.dev; + int ret, err; + u32 *array; + + if (priv->extractor_stream_tag == SOF_PROBES_INVALID_NODE_ID) { + dev_warn(dev, "no extractor stream running\n"); + return -ENOENT; + } + + ret = parse_int_array_user(from, count, (int **)&array); + if (ret < 0) + return ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + dev_err_ratelimited(dev, "debugfs write failed to resume %d\n", ret); + goto exit; + } + + ret = sof_probes_points_remove(cdev, &array[1], array[0]); + if (!ret) + ret = count; + + pm_runtime_mark_last_busy(dev); + err = pm_runtime_put_autosuspend(dev); + if (err < 0) + dev_err_ratelimited(dev, "debugfs write failed to idle %d\n", err); +exit: + kfree(array); + return ret; +} + +static const struct file_operations sof_probes_points_remove_fops = { + .open = simple_open, + .write = sof_probes_dfs_points_remove_write, + .llseek = default_llseek, + + .owner = THIS_MODULE, +}; + +static struct snd_soc_dai_driver sof_probes_dai_drv[] = { +{ + .name = "Probe Extraction CPU DAI", + .compress_new = snd_soc_new_compress, + .cops = &sof_probes_compr_ops, + .capture = { + .stream_name = "Probe Extraction", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + }, +}, +}; + +static const struct snd_soc_component_driver sof_probes_component = { + .name = "sof-probes-component", + .compress_ops = &sof_probes_compressed_ops, + .module_get_upon_open = 1, + .legacy_dai_naming = 1, +}; + +SND_SOC_DAILINK_DEF(dummy, DAILINK_COMP_ARRAY(COMP_DUMMY())); + +static int sof_probes_client_probe(struct auxiliary_device *auxdev, + const struct auxiliary_device_id *id) +{ + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + struct dentry *dfsroot = sof_client_get_debugfs_root(cdev); + struct device *dev = &auxdev->dev; + struct snd_soc_dai_link_component platform_component[] = { + { + .name = dev_name(dev), + } + }; + struct snd_soc_card *card; + struct sof_probes_priv *priv; + struct snd_soc_dai_link_component *cpus; + struct sof_probes_host_ops *ops; + struct snd_soc_dai_link *links; + int ret; + + /* do not set up the probes support if it is not enabled */ + if (!sof_probes_enabled) + return -ENXIO; + + /* only ipc3 is supported */ + if (sof_client_get_ipc_type(cdev) != SOF_IPC) + return -ENXIO; + + if (!dev->platform_data) { + dev_err(dev, "missing platform data\n"); + return -ENODEV; + } + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + ops = dev->platform_data; + + if (!ops->startup || !ops->shutdown || !ops->set_params || !ops->trigger || + !ops->pointer) { + dev_err(dev, "missing platform callback(s)\n"); + return -ENODEV; + } + + priv->host_ops = ops; + cdev->data = priv; + + /* register probes component driver and dai */ + ret = devm_snd_soc_register_component(dev, &sof_probes_component, + sof_probes_dai_drv, + ARRAY_SIZE(sof_probes_dai_drv)); + if (ret < 0) { + dev_err(dev, "failed to register SOF probes DAI driver %d\n", ret); + return ret; + } + + /* set client data */ + priv->extractor_stream_tag = SOF_PROBES_INVALID_NODE_ID; + + /* create read-write probes_points debugfs entry */ + priv->dfs_points = debugfs_create_file("probe_points", 0644, dfsroot, + cdev, &sof_probes_points_fops); + + /* create read-write probe_points_remove debugfs entry */ + priv->dfs_points_remove = debugfs_create_file("probe_points_remove", 0644, + dfsroot, cdev, + &sof_probes_points_remove_fops); + + links = devm_kcalloc(dev, SOF_PROBES_NUM_DAI_LINKS, sizeof(*links), GFP_KERNEL); + cpus = devm_kcalloc(dev, SOF_PROBES_NUM_DAI_LINKS, sizeof(*cpus), GFP_KERNEL); + if (!links || !cpus) { + debugfs_remove(priv->dfs_points); + debugfs_remove(priv->dfs_points_remove); + return -ENOMEM; + } + + /* extraction DAI link */ + links[0].name = "Compress Probe Capture"; + links[0].id = 0; + links[0].cpus = &cpus[0]; + links[0].num_cpus = 1; + links[0].cpus->dai_name = "Probe Extraction CPU DAI"; + links[0].codecs = dummy; + links[0].num_codecs = 1; + links[0].platforms = platform_component; + links[0].num_platforms = ARRAY_SIZE(platform_component); + links[0].nonatomic = 1; + + card = &priv->card; + + card->dev = dev; + card->name = "sof-probes"; + card->owner = THIS_MODULE; + card->num_links = SOF_PROBES_NUM_DAI_LINKS; + card->dai_link = links; + + /* set idle_bias_off to prevent the core from resuming the card->dev */ + card->dapm.idle_bias_off = true; + + snd_soc_card_set_drvdata(card, cdev); + + ret = devm_snd_soc_register_card(dev, card); + if (ret < 0) { + debugfs_remove(priv->dfs_points); + debugfs_remove(priv->dfs_points_remove); + dev_err(dev, "Probes card register failed %d\n", ret); + return ret; + } + + /* enable runtime PM */ + pm_runtime_set_autosuspend_delay(dev, SOF_PROBES_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_idle(dev); + + return 0; +} + +static void sof_probes_client_remove(struct auxiliary_device *auxdev) +{ + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + struct sof_probes_priv *priv = cdev->data; + + if (!sof_probes_enabled) + return; + + pm_runtime_disable(&auxdev->dev); + debugfs_remove(priv->dfs_points); + debugfs_remove(priv->dfs_points_remove); +} + +static const struct auxiliary_device_id sof_probes_client_id_table[] = { + { .name = "snd_sof.hda-probes", }, + {}, +}; +MODULE_DEVICE_TABLE(auxiliary, sof_probes_client_id_table); + +/* driver name will be set based on KBUILD_MODNAME */ +static struct auxiliary_driver sof_probes_client_drv = { + .probe = sof_probes_client_probe, + .remove = sof_probes_client_remove, + + .id_table = sof_probes_client_id_table, +}; + +module_auxiliary_driver(sof_probes_client_drv); + +MODULE_DESCRIPTION("SOF Probes Client Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); diff --git a/sound/soc/sof/sof-client-probes.h b/sound/soc/sof/sof-client-probes.h new file mode 100644 index 000000000000..9e43f3c444f8 --- /dev/null +++ b/sound/soc/sof/sof-client-probes.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __SOF_CLIENT_PROBES_H +#define __SOF_CLIENT_PROBES_H + +struct snd_compr_stream; +struct snd_compr_tstamp; +struct snd_compr_params; +struct sof_client_dev; +struct snd_soc_dai; + +/* + * Callbacks used on platforms where the control for audio is split between + * DSP and host, like HDA. + */ +struct sof_probes_host_ops { + int (*startup)(struct sof_client_dev *cdev, struct snd_compr_stream *cstream, + struct snd_soc_dai *dai, u32 *stream_id); + int (*shutdown)(struct sof_client_dev *cdev, struct snd_compr_stream *cstream, + struct snd_soc_dai *dai); + int (*set_params)(struct sof_client_dev *cdev, struct snd_compr_stream *cstream, + struct snd_compr_params *params, + struct snd_soc_dai *dai); + int (*trigger)(struct sof_client_dev *cdev, struct snd_compr_stream *cstream, + int cmd, struct snd_soc_dai *dai); + int (*pointer)(struct sof_client_dev *cdev, struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp, + struct snd_soc_dai *dai); +}; + +#endif diff --git a/sound/soc/sof/sof-client.c b/sound/soc/sof/sof-client.c new file mode 100644 index 000000000000..125aa2137195 --- /dev/null +++ b/sound/soc/sof/sof-client.c @@ -0,0 +1,523 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// Authors: Ranjani Sridharan <ranjani.sridharan@linux.intel.com> +// Peter Ujfalusi <peter.ujfalusi@linux.intel.com> +// + +#include <linux/debugfs.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <sound/sof/ipc4/header.h> +#include "ops.h" +#include "sof-client.h" +#include "sof-priv.h" + +/** + * struct sof_ipc_event_entry - IPC client event description + * @ipc_msg_type: IPC msg type of the event the client is interested + * @cdev: sof_client_dev of the requesting client + * @callback: Callback function of the client + * @list: item in SOF core client event list + */ +struct sof_ipc_event_entry { + u32 ipc_msg_type; + struct sof_client_dev *cdev; + sof_client_event_callback callback; + struct list_head list; +}; + +/** + * struct sof_state_event_entry - DSP panic event subscription entry + * @cdev: sof_client_dev of the requesting client + * @callback: Callback function of the client + * @list: item in SOF core client event list + */ +struct sof_state_event_entry { + struct sof_client_dev *cdev; + sof_client_fw_state_callback callback; + struct list_head list; +}; + +static void sof_client_auxdev_release(struct device *dev) +{ + struct auxiliary_device *auxdev = to_auxiliary_dev(dev); + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + + kfree(cdev->auxdev.dev.platform_data); + kfree(cdev); +} + +static int sof_client_dev_add_data(struct sof_client_dev *cdev, const void *data, + size_t size) +{ + void *d = NULL; + + if (data) { + d = kmemdup(data, size, GFP_KERNEL); + if (!d) + return -ENOMEM; + } + + cdev->auxdev.dev.platform_data = d; + return 0; +} + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) +static int sof_register_ipc_flood_test(struct snd_sof_dev *sdev) +{ + int ret = 0; + int i; + + if (sdev->pdata->ipc_type != SOF_IPC) + return 0; + + for (i = 0; i < CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST_NUM; i++) { + ret = sof_client_dev_register(sdev, "ipc_flood", i, NULL, 0); + if (ret < 0) + break; + } + + if (ret) { + for (; i >= 0; --i) + sof_client_dev_unregister(sdev, "ipc_flood", i); + } + + return ret; +} + +static void sof_unregister_ipc_flood_test(struct snd_sof_dev *sdev) +{ + int i; + + for (i = 0; i < CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST_NUM; i++) + sof_client_dev_unregister(sdev, "ipc_flood", i); +} +#else +static inline int sof_register_ipc_flood_test(struct snd_sof_dev *sdev) +{ + return 0; +} + +static inline void sof_unregister_ipc_flood_test(struct snd_sof_dev *sdev) {} +#endif /* CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST */ + +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR) +static int sof_register_ipc_msg_injector(struct snd_sof_dev *sdev) +{ + return sof_client_dev_register(sdev, "msg_injector", 0, NULL, 0); +} + +static void sof_unregister_ipc_msg_injector(struct snd_sof_dev *sdev) +{ + sof_client_dev_unregister(sdev, "msg_injector", 0); +} +#else +static inline int sof_register_ipc_msg_injector(struct snd_sof_dev *sdev) +{ + return 0; +} + +static inline void sof_unregister_ipc_msg_injector(struct snd_sof_dev *sdev) {} +#endif /* CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR */ + +int sof_register_clients(struct snd_sof_dev *sdev) +{ + int ret; + + /* Register platform independent client devices */ + ret = sof_register_ipc_flood_test(sdev); + if (ret) { + dev_err(sdev->dev, "IPC flood test client registration failed\n"); + return ret; + } + + ret = sof_register_ipc_msg_injector(sdev); + if (ret) { + dev_err(sdev->dev, "IPC message injector client registration failed\n"); + goto err_msg_injector; + } + + /* Platform depndent client device registration */ + + if (sof_ops(sdev) && sof_ops(sdev)->register_ipc_clients) + ret = sof_ops(sdev)->register_ipc_clients(sdev); + + if (!ret) + return 0; + + sof_unregister_ipc_msg_injector(sdev); + +err_msg_injector: + sof_unregister_ipc_flood_test(sdev); + + return ret; +} + +void sof_unregister_clients(struct snd_sof_dev *sdev) +{ + if (sof_ops(sdev) && sof_ops(sdev)->unregister_ipc_clients) + sof_ops(sdev)->unregister_ipc_clients(sdev); + + sof_unregister_ipc_msg_injector(sdev); + sof_unregister_ipc_flood_test(sdev); +} + +int sof_client_dev_register(struct snd_sof_dev *sdev, const char *name, u32 id, + const void *data, size_t size) +{ + struct auxiliary_device *auxdev; + struct sof_client_dev *cdev; + int ret; + + cdev = kzalloc(sizeof(*cdev), GFP_KERNEL); + if (!cdev) + return -ENOMEM; + + cdev->sdev = sdev; + auxdev = &cdev->auxdev; + auxdev->name = name; + auxdev->dev.parent = sdev->dev; + auxdev->dev.release = sof_client_auxdev_release; + auxdev->id = id; + + ret = sof_client_dev_add_data(cdev, data, size); + if (ret < 0) + goto err_dev_add_data; + + ret = auxiliary_device_init(auxdev); + if (ret < 0) { + dev_err(sdev->dev, "failed to initialize client dev %s.%d\n", name, id); + goto err_dev_init; + } + + ret = auxiliary_device_add(&cdev->auxdev); + if (ret < 0) { + dev_err(sdev->dev, "failed to add client dev %s.%d\n", name, id); + /* + * sof_client_auxdev_release() will be invoked to free up memory + * allocations through put_device() + */ + auxiliary_device_uninit(&cdev->auxdev); + return ret; + } + + /* add to list of SOF client devices */ + mutex_lock(&sdev->ipc_client_mutex); + list_add(&cdev->list, &sdev->ipc_client_list); + mutex_unlock(&sdev->ipc_client_mutex); + + return 0; + +err_dev_init: + kfree(cdev->auxdev.dev.platform_data); + +err_dev_add_data: + kfree(cdev); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(sof_client_dev_register, SND_SOC_SOF_CLIENT); + +void sof_client_dev_unregister(struct snd_sof_dev *sdev, const char *name, u32 id) +{ + struct sof_client_dev *cdev; + + mutex_lock(&sdev->ipc_client_mutex); + + /* + * sof_client_auxdev_release() will be invoked to free up memory + * allocations through put_device() + */ + list_for_each_entry(cdev, &sdev->ipc_client_list, list) { + if (!strcmp(cdev->auxdev.name, name) && cdev->auxdev.id == id) { + list_del(&cdev->list); + auxiliary_device_delete(&cdev->auxdev); + auxiliary_device_uninit(&cdev->auxdev); + break; + } + } + + mutex_unlock(&sdev->ipc_client_mutex); +} +EXPORT_SYMBOL_NS_GPL(sof_client_dev_unregister, SND_SOC_SOF_CLIENT); + +int sof_client_ipc_tx_message(struct sof_client_dev *cdev, void *ipc_msg, + void *reply_data, size_t reply_bytes) +{ + if (cdev->sdev->pdata->ipc_type == SOF_IPC) { + struct sof_ipc_cmd_hdr *hdr = ipc_msg; + + return sof_ipc_tx_message(cdev->sdev->ipc, ipc_msg, hdr->size, + reply_data, reply_bytes); + } else if (cdev->sdev->pdata->ipc_type == SOF_INTEL_IPC4) { + struct sof_ipc4_msg *msg = ipc_msg; + + return sof_ipc_tx_message(cdev->sdev->ipc, ipc_msg, msg->data_size, + reply_data, reply_bytes); + } + + return -EINVAL; +} +EXPORT_SYMBOL_NS_GPL(sof_client_ipc_tx_message, SND_SOC_SOF_CLIENT); + +int sof_suspend_clients(struct snd_sof_dev *sdev, pm_message_t state) +{ + struct auxiliary_driver *adrv; + struct sof_client_dev *cdev; + + mutex_lock(&sdev->ipc_client_mutex); + + list_for_each_entry(cdev, &sdev->ipc_client_list, list) { + /* Skip devices without loaded driver */ + if (!cdev->auxdev.dev.driver) + continue; + + adrv = to_auxiliary_drv(cdev->auxdev.dev.driver); + if (adrv->suspend) + adrv->suspend(&cdev->auxdev, state); + } + + mutex_unlock(&sdev->ipc_client_mutex); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(sof_suspend_clients, SND_SOC_SOF_CLIENT); + +int sof_resume_clients(struct snd_sof_dev *sdev) +{ + struct auxiliary_driver *adrv; + struct sof_client_dev *cdev; + + mutex_lock(&sdev->ipc_client_mutex); + + list_for_each_entry(cdev, &sdev->ipc_client_list, list) { + /* Skip devices without loaded driver */ + if (!cdev->auxdev.dev.driver) + continue; + + adrv = to_auxiliary_drv(cdev->auxdev.dev.driver); + if (adrv->resume) + adrv->resume(&cdev->auxdev); + } + + mutex_unlock(&sdev->ipc_client_mutex); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(sof_resume_clients, SND_SOC_SOF_CLIENT); + +struct dentry *sof_client_get_debugfs_root(struct sof_client_dev *cdev) +{ + return cdev->sdev->debugfs_root; +} +EXPORT_SYMBOL_NS_GPL(sof_client_get_debugfs_root, SND_SOC_SOF_CLIENT); + +/* DMA buffer allocation in client drivers must use the core SOF device */ +struct device *sof_client_get_dma_dev(struct sof_client_dev *cdev) +{ + return cdev->sdev->dev; +} +EXPORT_SYMBOL_NS_GPL(sof_client_get_dma_dev, SND_SOC_SOF_CLIENT); + +const struct sof_ipc_fw_version *sof_client_get_fw_version(struct sof_client_dev *cdev) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + + return &sdev->fw_ready.version; +} +EXPORT_SYMBOL_NS_GPL(sof_client_get_fw_version, SND_SOC_SOF_CLIENT); + +size_t sof_client_get_ipc_max_payload_size(struct sof_client_dev *cdev) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + + return sdev->ipc->max_payload_size; +} +EXPORT_SYMBOL_NS_GPL(sof_client_get_ipc_max_payload_size, SND_SOC_SOF_CLIENT); + +enum sof_ipc_type sof_client_get_ipc_type(struct sof_client_dev *cdev) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + + return sdev->pdata->ipc_type; +} +EXPORT_SYMBOL_NS_GPL(sof_client_get_ipc_type, SND_SOC_SOF_CLIENT); + +/* module refcount management of SOF core */ +int sof_client_core_module_get(struct sof_client_dev *cdev) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + + if (!try_module_get(sdev->dev->driver->owner)) + return -ENODEV; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(sof_client_core_module_get, SND_SOC_SOF_CLIENT); + +void sof_client_core_module_put(struct sof_client_dev *cdev) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + + module_put(sdev->dev->driver->owner); +} +EXPORT_SYMBOL_NS_GPL(sof_client_core_module_put, SND_SOC_SOF_CLIENT); + +/* IPC event handling */ +void sof_client_ipc_rx_dispatcher(struct snd_sof_dev *sdev, void *msg_buf) +{ + struct sof_ipc_event_entry *event; + u32 msg_type; + + if (sdev->pdata->ipc_type == SOF_IPC) { + struct sof_ipc_cmd_hdr *hdr = msg_buf; + + msg_type = hdr->cmd & SOF_GLB_TYPE_MASK; + } else if (sdev->pdata->ipc_type == SOF_INTEL_IPC4) { + struct sof_ipc4_msg *msg = msg_buf; + + msg_type = SOF_IPC4_NOTIFICATION_TYPE_GET(msg->primary); + } else { + dev_dbg_once(sdev->dev, "Not supported IPC version: %d\n", + sdev->pdata->ipc_type); + return; + } + + mutex_lock(&sdev->client_event_handler_mutex); + + list_for_each_entry(event, &sdev->ipc_rx_handler_list, list) { + if (event->ipc_msg_type == msg_type) + event->callback(event->cdev, msg_buf); + } + + mutex_unlock(&sdev->client_event_handler_mutex); +} + +int sof_client_register_ipc_rx_handler(struct sof_client_dev *cdev, + u32 ipc_msg_type, + sof_client_event_callback callback) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + struct sof_ipc_event_entry *event; + + if (!callback) + return -EINVAL; + + if (cdev->sdev->pdata->ipc_type == SOF_IPC) { + if (!(ipc_msg_type & SOF_GLB_TYPE_MASK)) + return -EINVAL; + } else if (cdev->sdev->pdata->ipc_type == SOF_INTEL_IPC4) { + if (!(ipc_msg_type & SOF_IPC4_NOTIFICATION_TYPE_MASK)) + return -EINVAL; + } else { + dev_warn(sdev->dev, "%s: Not supported IPC version: %d\n", + __func__, sdev->pdata->ipc_type); + return -EINVAL; + } + + event = kmalloc(sizeof(*event), GFP_KERNEL); + if (!event) + return -ENOMEM; + + event->ipc_msg_type = ipc_msg_type; + event->cdev = cdev; + event->callback = callback; + + /* add to list of SOF client devices */ + mutex_lock(&sdev->client_event_handler_mutex); + list_add(&event->list, &sdev->ipc_rx_handler_list); + mutex_unlock(&sdev->client_event_handler_mutex); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(sof_client_register_ipc_rx_handler, SND_SOC_SOF_CLIENT); + +void sof_client_unregister_ipc_rx_handler(struct sof_client_dev *cdev, + u32 ipc_msg_type) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + struct sof_ipc_event_entry *event; + + mutex_lock(&sdev->client_event_handler_mutex); + + list_for_each_entry(event, &sdev->ipc_rx_handler_list, list) { + if (event->cdev == cdev && event->ipc_msg_type == ipc_msg_type) { + list_del(&event->list); + kfree(event); + break; + } + } + + mutex_unlock(&sdev->client_event_handler_mutex); +} +EXPORT_SYMBOL_NS_GPL(sof_client_unregister_ipc_rx_handler, SND_SOC_SOF_CLIENT); + +/*DSP state notification and query */ +void sof_client_fw_state_dispatcher(struct snd_sof_dev *sdev) +{ + struct sof_state_event_entry *event; + + mutex_lock(&sdev->client_event_handler_mutex); + + list_for_each_entry(event, &sdev->fw_state_handler_list, list) + event->callback(event->cdev, sdev->fw_state); + + mutex_unlock(&sdev->client_event_handler_mutex); +} + +int sof_client_register_fw_state_handler(struct sof_client_dev *cdev, + sof_client_fw_state_callback callback) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + struct sof_state_event_entry *event; + + if (!callback) + return -EINVAL; + + event = kmalloc(sizeof(*event), GFP_KERNEL); + if (!event) + return -ENOMEM; + + event->cdev = cdev; + event->callback = callback; + + /* add to list of SOF client devices */ + mutex_lock(&sdev->client_event_handler_mutex); + list_add(&event->list, &sdev->fw_state_handler_list); + mutex_unlock(&sdev->client_event_handler_mutex); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(sof_client_register_fw_state_handler, SND_SOC_SOF_CLIENT); + +void sof_client_unregister_fw_state_handler(struct sof_client_dev *cdev) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + struct sof_state_event_entry *event; + + mutex_lock(&sdev->client_event_handler_mutex); + + list_for_each_entry(event, &sdev->fw_state_handler_list, list) { + if (event->cdev == cdev) { + list_del(&event->list); + kfree(event); + break; + } + } + + mutex_unlock(&sdev->client_event_handler_mutex); +} +EXPORT_SYMBOL_NS_GPL(sof_client_unregister_fw_state_handler, SND_SOC_SOF_CLIENT); + +enum sof_fw_state sof_client_get_fw_state(struct sof_client_dev *cdev) +{ + struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); + + return sdev->fw_state; +} +EXPORT_SYMBOL_NS_GPL(sof_client_get_fw_state, SND_SOC_SOF_CLIENT); diff --git a/sound/soc/sof/sof-client.h b/sound/soc/sof/sof-client.h new file mode 100644 index 000000000000..46b215d9200f --- /dev/null +++ b/sound/soc/sof/sof-client.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __SOC_SOF_CLIENT_H +#define __SOC_SOF_CLIENT_H + +#include <linux/auxiliary_bus.h> +#include <linux/device.h> +#include <linux/list.h> +#include <sound/sof.h> + +struct sof_ipc_fw_version; +struct sof_ipc_cmd_hdr; +struct snd_sof_dev; +struct dentry; + +/** + * struct sof_client_dev - SOF client device + * @auxdev: auxiliary device + * @sdev: pointer to SOF core device struct + * @list: item in SOF core client dev list + * @data: device specific data + */ +struct sof_client_dev { + struct auxiliary_device auxdev; + struct snd_sof_dev *sdev; + struct list_head list; + void *data; +}; + +#define sof_client_dev_to_sof_dev(cdev) ((cdev)->sdev) + +#define auxiliary_dev_to_sof_client_dev(auxiliary_dev) \ + container_of(auxiliary_dev, struct sof_client_dev, auxdev) + +#define dev_to_sof_client_dev(dev) \ + container_of(to_auxiliary_dev(dev), struct sof_client_dev, auxdev) + +int sof_client_ipc_tx_message(struct sof_client_dev *cdev, void *ipc_msg, + void *reply_data, size_t reply_bytes); + +struct dentry *sof_client_get_debugfs_root(struct sof_client_dev *cdev); +struct device *sof_client_get_dma_dev(struct sof_client_dev *cdev); +const struct sof_ipc_fw_version *sof_client_get_fw_version(struct sof_client_dev *cdev); +size_t sof_client_get_ipc_max_payload_size(struct sof_client_dev *cdev); +enum sof_ipc_type sof_client_get_ipc_type(struct sof_client_dev *cdev); + +/* module refcount management of SOF core */ +int sof_client_core_module_get(struct sof_client_dev *cdev); +void sof_client_core_module_put(struct sof_client_dev *cdev); + +/* IPC notification */ +typedef void (*sof_client_event_callback)(struct sof_client_dev *cdev, void *msg_buf); + +int sof_client_register_ipc_rx_handler(struct sof_client_dev *cdev, + u32 ipc_msg_type, + sof_client_event_callback callback); +void sof_client_unregister_ipc_rx_handler(struct sof_client_dev *cdev, + u32 ipc_msg_type); + +/* DSP state notification and query */ +typedef void (*sof_client_fw_state_callback)(struct sof_client_dev *cdev, + enum sof_fw_state state); + +int sof_client_register_fw_state_handler(struct sof_client_dev *cdev, + sof_client_fw_state_callback callback); +void sof_client_unregister_fw_state_handler(struct sof_client_dev *cdev); +enum sof_fw_state sof_client_get_fw_state(struct sof_client_dev *cdev); + +#endif /* __SOC_SOF_CLIENT_H */ diff --git a/sound/soc/sof/sof-of-dev.c b/sound/soc/sof/sof-of-dev.c index 885430a42226..53faeccedd4f 100644 --- a/sound/soc/sof/sof-of-dev.c +++ b/sound/soc/sof/sof-of-dev.c @@ -11,8 +11,8 @@ #include <linux/pm_runtime.h> #include <sound/sof.h> +#include "sof-of-dev.h" #include "ops.h" -#include "imx/imx-ops.h" static char *fw_path; module_param(fw_path, charp, 0444); @@ -22,56 +22,26 @@ static char *tplg_path; module_param(tplg_path, charp, 0444); MODULE_PARM_DESC(tplg_path, "alternate path for SOF topology."); -/* platform specific devices */ -#if IS_ENABLED(CONFIG_SND_SOC_SOF_IMX8) -static struct sof_dev_desc sof_of_imx8qxp_desc = { - .default_fw_path = "imx/sof", - .default_tplg_path = "imx/sof-tplg", - .default_fw_filename = "sof-imx8x.ri", - .nocodec_tplg_filename = "sof-imx8-nocodec.tplg", - .ops = &sof_imx8x_ops, -}; - -static 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 = { +const struct dev_pm_ops sof_of_pm = { .prepare = snd_sof_prepare, .complete = snd_sof_complete, SET_SYSTEM_SLEEP_PM_OPS(snd_sof_suspend, snd_sof_resume) SET_RUNTIME_PM_OPS(snd_sof_runtime_suspend, snd_sof_runtime_resume, NULL) }; +EXPORT_SYMBOL(sof_of_pm); static void sof_of_probe_complete(struct device *dev) { /* allow runtime_pm */ pm_runtime_set_autosuspend_delay(dev, SND_SOF_SUSPEND_DELAY_MS); pm_runtime_use_autosuspend(dev); + pm_runtime_mark_last_busy(dev); pm_runtime_set_active(dev); pm_runtime_enable(dev); - - pm_runtime_mark_last_busy(dev); - pm_runtime_put_autosuspend(dev); } -static int sof_of_probe(struct platform_device *pdev) +int sof_of_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; const struct sof_dev_desc *desc; @@ -94,17 +64,17 @@ static int sof_of_probe(struct platform_device *pdev) sof_pdata->desc = desc; sof_pdata->dev = &pdev->dev; - sof_pdata->fw_filename = desc->default_fw_filename; + sof_pdata->fw_filename = desc->default_fw_filename[SOF_IPC]; if (fw_path) sof_pdata->fw_filename_prefix = fw_path; else - sof_pdata->fw_filename_prefix = sof_pdata->desc->default_fw_path; + sof_pdata->fw_filename_prefix = sof_pdata->desc->default_fw_path[SOF_IPC]; if (tplg_path) sof_pdata->tplg_filename_prefix = tplg_path; else - sof_pdata->tplg_filename_prefix = sof_pdata->desc->default_tplg_path; + sof_pdata->tplg_filename_prefix = sof_pdata->desc->default_tplg_path[SOF_IPC]; /* set callback to be called on successful device probe to enable runtime_pm */ sof_pdata->sof_probe_complete = sof_of_probe_complete; @@ -112,8 +82,9 @@ static int sof_of_probe(struct platform_device *pdev) /* call sof helper for DSP hardware probe */ return snd_sof_device_probe(dev, sof_pdata); } +EXPORT_SYMBOL(sof_of_probe); -static int sof_of_remove(struct platform_device *pdev) +int sof_of_remove(struct platform_device *pdev) { pm_runtime_disable(&pdev->dev); @@ -122,29 +93,12 @@ static int sof_of_remove(struct platform_device *pdev) return 0; } +EXPORT_SYMBOL(sof_of_remove); -static const struct of_device_id sof_of_ids[] = { -#if IS_ENABLED(CONFIG_SND_SOC_SOF_IMX8) - { .compatible = "fsl,imx8qxp-dsp", .data = &sof_of_imx8qxp_desc}, - { .compatible = "fsl,imx8qm-dsp", .data = &sof_of_imx8qm_desc}, -#endif -#if IS_ENABLED(CONFIG_SND_SOC_SOF_IMX8M) - { .compatible = "fsl,imx8mp-dsp", .data = &sof_of_imx8mp_desc}, -#endif - { } -}; -MODULE_DEVICE_TABLE(of, sof_of_ids); - -/* DT driver definition */ -static struct platform_driver snd_sof_of_driver = { - .probe = sof_of_probe, - .remove = sof_of_remove, - .driver = { - .name = "sof-audio-of", - .pm = &sof_of_pm, - .of_match_table = sof_of_ids, - }, -}; -module_platform_driver(snd_sof_of_driver); +void sof_of_shutdown(struct platform_device *pdev) +{ + snd_sof_device_shutdown(&pdev->dev); +} +EXPORT_SYMBOL(sof_of_shutdown); MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/sof-of-dev.h b/sound/soc/sof/sof-of-dev.h new file mode 100644 index 000000000000..2948b3a0d9fe --- /dev/null +++ b/sound/soc/sof/sof-of-dev.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright 2021 NXP + */ + +#ifndef __SOUND_SOC_SOF_OF_H +#define __SOUND_SOC_SOF_OF_H + +struct snd_sof_of_mach { + const char *compatible; + const char *drv_name; + const char *fw_filename; + const char *sof_tplg_filename; +}; + +extern const struct dev_pm_ops sof_of_pm; + +int sof_of_probe(struct platform_device *pdev); +int sof_of_remove(struct platform_device *pdev); +void sof_of_shutdown(struct platform_device *pdev); + +#endif diff --git a/sound/soc/sof/sof-pci-dev.c b/sound/soc/sof/sof-pci-dev.c index bc9e70765678..643fd1036d60 100644 --- a/sound/soc/sof/sof-pci-dev.c +++ b/sound/soc/sof/sof-pci-dev.c @@ -12,6 +12,7 @@ #include <linux/dmi.h> #include <linux/module.h> #include <linux/pci.h> +#include <linux/platform_data/x86/soc.h> #include <linux/pm_runtime.h> #include <sound/soc-acpi.h> #include <sound/soc-acpi-intel-match.h> @@ -23,21 +24,34 @@ static char *fw_path; module_param(fw_path, charp, 0444); MODULE_PARM_DESC(fw_path, "alternate path for SOF firmware."); +static char *fw_filename; +module_param(fw_filename, charp, 0444); +MODULE_PARM_DESC(fw_filename, "alternate filename for SOF firmware."); + static char *tplg_path; module_param(tplg_path, charp, 0444); MODULE_PARM_DESC(tplg_path, "alternate path for SOF topology."); +static char *tplg_filename; +module_param(tplg_filename, charp, 0444); +MODULE_PARM_DESC(tplg_filename, "alternate filename for SOF topology."); + static int sof_pci_debug; module_param_named(sof_pci_debug, sof_pci_debug, int, 0444); MODULE_PARM_DESC(sof_pci_debug, "SOF PCI debug options (0x0 all off)"); -static const char *sof_override_tplg_name; +static int sof_pci_ipc_type = -1; +module_param_named(ipc_type, sof_pci_ipc_type, int, 0444); +MODULE_PARM_DESC(ipc_type, "SOF IPC type (0): SOF, (1) Intel CAVS"); + +static const char *sof_dmi_override_tplg_name; +static bool sof_dmi_use_community_key; #define SOF_PCI_DISABLE_PM_RUNTIME BIT(0) static int sof_tplg_cb(const struct dmi_system_id *id) { - sof_override_tplg_name = id->driver_data; + sof_dmi_override_tplg_name = id->driver_data; return 1; } @@ -59,28 +73,72 @@ static const struct dmi_system_id sof_tplg_table[] = { }, .driver_data = "sof-adl-rt5682-ssp0-max98373-ssp2.tplg", }, + { + .callback = sof_tplg_cb, + .matches = { + DMI_MATCH(DMI_PRODUCT_FAMILY, "Google_Brya"), + DMI_MATCH(DMI_OEM_STRING, "AUDIO-MAX98390_ALC5682I_I2S"), + }, + .driver_data = "sof-adl-max98390-ssp2-rt5682-ssp0.tplg", + }, + { + .callback = sof_tplg_cb, + .matches = { + DMI_MATCH(DMI_PRODUCT_FAMILY, "Google_Brya"), + DMI_MATCH(DMI_OEM_STRING, "AUDIO_AMP-MAX98360_ALC5682VS_I2S_2WAY"), + }, + .driver_data = "sof-adl-max98360a-rt5682-2way.tplg", + }, + { + .callback = sof_tplg_cb, + .matches = { + DMI_MATCH(DMI_PRODUCT_FAMILY, "Google_Brya"), + DMI_MATCH(DMI_OEM_STRING, "AUDIO-AUDIO_MAX98357_ALC5682I_I2S_2WAY"), + }, + .driver_data = "sof-adl-max98357a-rt5682-2way.tplg", + }, + { + .callback = sof_tplg_cb, + .matches = { + DMI_MATCH(DMI_PRODUCT_FAMILY, "Google_Brya"), + DMI_MATCH(DMI_OEM_STRING, "AUDIO-MAX98360_ALC5682I_I2S_AMP_SSP2"), + }, + .driver_data = "sof-adl-max98357a-rt5682.tplg", + }, {} }; +/* all Up boards use the community key */ +static int up_use_community_key(const struct dmi_system_id *id) +{ + sof_dmi_use_community_key = true; + return 1; +} + +/* + * For ApolloLake Chromebooks we want to force the use of the Intel production key. + * All newer platforms use the community key + */ +static int chromebook_use_community_key(const struct dmi_system_id *id) +{ + if (!soc_intel_is_apl()) + sof_dmi_use_community_key = true; + return 1; +} + static const struct dmi_system_id community_key_platforms[] = { { - .ident = "Up Squared", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "AAEON"), - DMI_MATCH(DMI_BOARD_NAME, "UP-APL01"), - } - }, - { - .ident = "Up Extreme", + .ident = "Up boards", + .callback = up_use_community_key, .matches = { DMI_MATCH(DMI_SYS_VENDOR, "AAEON"), - DMI_MATCH(DMI_BOARD_NAME, "UP-WHL01"), } }, { .ident = "Google Chromebooks", + .callback = chromebook_use_community_key, .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Google"), + DMI_MATCH(DMI_PRODUCT_FAMILY, "Google"), } }, {}, @@ -129,6 +187,11 @@ int sof_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) dev_dbg(&pci->dev, "PCI DSP detected"); + if (!desc) { + dev_err(dev, "error: no matching PCI descriptor\n"); + return -ENODEV; + } + if (!desc->ops) { dev_err(dev, "error: no matching PCI descriptor ops\n"); return -ENODEV; @@ -149,7 +212,36 @@ int sof_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) sof_pdata->name = pci_name(pci); sof_pdata->desc = desc; sof_pdata->dev = dev; - sof_pdata->fw_filename = desc->default_fw_filename; + + sof_pdata->ipc_type = desc->ipc_default; + + if (sof_pci_ipc_type < 0) { + sof_pdata->ipc_type = desc->ipc_default; + } else { + dev_info(dev, "overriding default IPC %d to requested %d\n", + desc->ipc_default, sof_pci_ipc_type); + if (sof_pci_ipc_type >= SOF_IPC_TYPE_COUNT) { + dev_err(dev, "invalid request value %d\n", sof_pci_ipc_type); + ret = -EINVAL; + goto out; + } + if (!(BIT(sof_pci_ipc_type) & desc->ipc_supported_mask)) { + dev_err(dev, "invalid request value %d, supported mask is %#x\n", + sof_pci_ipc_type, desc->ipc_supported_mask); + ret = -EINVAL; + goto out; + } + sof_pdata->ipc_type = sof_pci_ipc_type; + } + + if (fw_filename) { + sof_pdata->fw_filename = fw_filename; + + dev_dbg(dev, "Module parameter used, changed fw filename to %s\n", + sof_pdata->fw_filename); + } else { + sof_pdata->fw_filename = desc->default_fw_filename[sof_pdata->ipc_type]; + } /* * for platforms using the SOF community key, change the @@ -166,10 +258,10 @@ int sof_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) "Module parameter used, changed fw path to %s\n", sof_pdata->fw_filename_prefix); - } else if (dmi_check_system(community_key_platforms)) { + } else if (dmi_check_system(community_key_platforms) && sof_dmi_use_community_key) { sof_pdata->fw_filename_prefix = devm_kasprintf(dev, GFP_KERNEL, "%s/%s", - sof_pdata->desc->default_fw_path, + sof_pdata->desc->default_fw_path[sof_pdata->ipc_type], "community"); dev_dbg(dev, @@ -177,24 +269,37 @@ int sof_pci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) sof_pdata->fw_filename_prefix); } else { sof_pdata->fw_filename_prefix = - sof_pdata->desc->default_fw_path; + sof_pdata->desc->default_fw_path[sof_pdata->ipc_type]; } if (tplg_path) sof_pdata->tplg_filename_prefix = tplg_path; else sof_pdata->tplg_filename_prefix = - sof_pdata->desc->default_tplg_path; + sof_pdata->desc->default_tplg_path[sof_pdata->ipc_type]; - dmi_check_system(sof_tplg_table); - if (sof_override_tplg_name) - sof_pdata->tplg_filename = sof_override_tplg_name; + /* + * the topology filename will be provided in the machine descriptor, unless + * it is overridden by a module parameter or DMI quirk. + */ + if (tplg_filename) { + sof_pdata->tplg_filename = tplg_filename; + + dev_dbg(dev, "Module parameter used, changed tplg filename to %s\n", + sof_pdata->tplg_filename); + } else { + dmi_check_system(sof_tplg_table); + if (sof_dmi_override_tplg_name) + sof_pdata->tplg_filename = sof_dmi_override_tplg_name; + } /* set callback to be called on successful device probe to enable runtime_pm */ sof_pdata->sof_probe_complete = sof_pci_probe_complete; /* call sof helper for DSP hardware probe */ ret = snd_sof_device_probe(dev, sof_pdata); + +out: if (ret) pci_release_regions(pci); diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index ba341b1bda0c..de08825915b3 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -20,7 +20,7 @@ #include <uapi/sound/sof/fw.h> #include <sound/sof/ext_manifest.h> -/* debug flags */ +/* Flag definitions used in sof_core_debug (sof_debug module parameter) */ #define SOF_DBG_ENABLE_TRACE BIT(0) #define SOF_DBG_RETAIN_CTX BIT(1) /* prevent DSP D3 on FW exception */ #define SOF_DBG_VERIFY_TPLG BIT(2) /* verify topology during load */ @@ -34,15 +34,26 @@ * on primary core */ #define SOF_DBG_PRINT_ALL_DUMPS BIT(6) /* Print all ipc and dsp dumps */ +#define SOF_DBG_IGNORE_D3_PERSISTENT BIT(7) /* ignore the DSP D3 persistent capability + * and always download firmware upon D3 exit + */ +#define SOF_DBG_PRINT_DMA_POSITION_UPDATE_LOGS BIT(8) /* print DMA position updates + * in dmesg logs + */ +#define SOF_DBG_PRINT_IPC_SUCCESS_LOGS BIT(9) /* print IPC success + * in dmesg logs + */ +/* Flag definitions used for controlling the DSP dump behavior */ #define SOF_DBG_DUMP_REGS BIT(0) #define SOF_DBG_DUMP_MBOX BIT(1) #define SOF_DBG_DUMP_TEXT BIT(2) #define SOF_DBG_DUMP_PCI BIT(3) -#define SOF_DBG_DUMP_OPTIONAL BIT(4) /* only dump if SOF_DBG_PRINT_ALL_DUMPS is set */ +/* Output this dump (at the DEBUG level) only when SOF_DBG_PRINT_ALL_DUMPS is set */ +#define SOF_DBG_DUMP_OPTIONAL BIT(4) /* global debug state set by SOF_DBG_ flags */ -extern int sof_core_debug; +bool sof_debug_check_flag(int mask); /* max BARs mmaped devices can use */ #define SND_SOF_BARS 8 @@ -64,22 +75,11 @@ extern int sof_core_debug; #define SOF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \ SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_FLOAT) -#define ENABLE_DEBUGFS_CACHEBUF \ - (IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) || \ - IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST)) - /* 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, -}; +/* max number of DSP cores */ +#define SOF_MAX_DSP_NUM_CORES 8 struct sof_dsp_power_state { u32 state; @@ -91,6 +91,8 @@ enum sof_system_suspend_state { SOF_SUSPEND_NONE = 0, SOF_SUSPEND_S0IX, SOF_SUSPEND_S3, + SOF_SUSPEND_S4, + SOF_SUSPEND_S5, }; enum sof_dfsentry_type { @@ -103,6 +105,13 @@ enum sof_debugfs_access_type { SOF_DEBUGFS_ACCESS_D0_ONLY, }; +struct sof_compr_stream { + u64 copied_total; + u32 sampling_rate; + u16 channels; + u16 sample_container_bytes; +}; + struct snd_sof_dev; struct snd_sof_ipc_msg; struct snd_sof_ipc; @@ -111,6 +120,22 @@ struct snd_soc_tplg_ops; struct snd_soc_component; struct snd_sof_pdata; +/** + * struct snd_sof_platform_stream_params - platform dependent stream parameters + * @stream_tag: Stream tag to use + * @use_phy_addr: Use the provided @phy_addr for configuration + * @phy_addr: Platform dependent address to be used, if @use_phy_addr + * is true + * @no_ipc_position: Disable position update IPC from firmware + */ +struct snd_sof_platform_stream_params { + u16 stream_tag; + bool use_phy_address; + u32 phy_addr; + bool no_ipc_position; + bool cont_update_posn; +}; + /* * SOF DSP HW abstraction operations. * Used to abstract DSP HW architecture and any IO busses between host CPU @@ -127,10 +152,8 @@ struct snd_sof_dsp_ops { int (*run)(struct snd_sof_dev *sof_dev); /* mandatory */ int (*stall)(struct snd_sof_dev *sof_dev, unsigned int core_mask); /* optional */ int (*reset)(struct snd_sof_dev *sof_dev); /* optional */ - int (*core_power_up)(struct snd_sof_dev *sof_dev, - unsigned int core_mask); /* optional */ - int (*core_power_down)(struct snd_sof_dev *sof_dev, - unsigned int core_mask); /* optional */ + int (*core_get)(struct snd_sof_dev *sof_dev, int core); /* optional */ + int (*core_put)(struct snd_sof_dev *sof_dev, int core); /* optional */ /* * Register IO: only used by respective drivers themselves, @@ -174,11 +197,6 @@ struct snd_sof_dsp_ops { int (*load_firmware)(struct snd_sof_dev *sof_dev); /* mandatory */ int (*load_module)(struct snd_sof_dev *sof_dev, struct snd_sof_mod_hdr *hdr); /* optional */ - /* - * FW ready checks for ABI compatibility and creates - * memory windows at first boot - */ - int (*fw_ready)(struct snd_sof_dev *sdev, u32 msg_id); /* mandatory */ /* connect pcm substream to a host stream */ int (*pcm_open)(struct snd_sof_dev *sdev, @@ -191,7 +209,7 @@ struct snd_sof_dsp_ops { int (*pcm_hw_params)(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, - struct sof_ipc_stream_params *ipc_params); /* optional */ + struct snd_sof_platform_stream_params *platform_params); /* optional */ /* host stream hw_free */ int (*pcm_hw_free)(struct snd_sof_dev *sdev, @@ -206,36 +224,18 @@ struct snd_sof_dsp_ops { snd_pcm_uframes_t (*pcm_pointer)(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream); /* optional */ -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) - /* Except for probe_pointer, all probe ops are mandatory */ - int (*probe_assign)(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_soc_dai *dai); /* mandatory */ - int (*probe_free)(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_soc_dai *dai); /* mandatory */ - int (*probe_set_params)(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_compr_params *params, - struct snd_soc_dai *dai); /* mandatory */ - int (*probe_trigger)(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, int cmd, - struct snd_soc_dai *dai); /* mandatory */ - int (*probe_pointer)(struct snd_sof_dev *sdev, - struct snd_compr_stream *cstream, - struct snd_compr_tstamp *tstamp, - struct snd_soc_dai *dai); /* optional */ -#endif + /* pcm ack */ + int (*pcm_ack)(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream); /* optional */ /* host read DSP stream data */ int (*ipc_msg_data)(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, void *p, size_t sz); /* mandatory */ - /* host configure DSP HW parameters */ - int (*ipc_pcm_params)(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream, - const struct sof_ipc_pcm_params_reply *reply); /* mandatory */ + /* host side configuration of the stream's data offset in stream mailbox area */ + int (*set_stream_data_offset)(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + size_t posn_offset); /* optional */ /* pre/post firmware run */ int (*pre_fw_run)(struct snd_sof_dev *sof_dev); /* optional */ @@ -270,9 +270,10 @@ struct snd_sof_dsp_ops { size_t size, const char *name, enum sof_debugfs_access_type access_type); /* optional */ - /* host DMA trace initialization */ + /* host DMA trace (IPC3) */ int (*trace_init)(struct snd_sof_dev *sdev, - u32 *stream_tag); /* optional */ + struct snd_dma_buffer *dmatb, + struct sof_ipc_dma_trace_params_ext *dtrace_params); /* optional */ int (*trace_release)(struct snd_sof_dev *sdev); /* optional */ int (*trace_trigger)(struct snd_sof_dev *sdev, int cmd); /* optional */ @@ -289,10 +290,14 @@ struct snd_sof_dsp_ops { void *pdata); /* optional */ void (*machine_unregister)(struct snd_sof_dev *sdev, void *pdata); /* optional */ - void (*machine_select)(struct snd_sof_dev *sdev); /* optional */ - void (*set_mach_params)(const struct snd_soc_acpi_mach *mach, + struct snd_soc_acpi_mach * (*machine_select)(struct snd_sof_dev *sdev); /* optional */ + void (*set_mach_params)(struct snd_soc_acpi_mach *mach, struct snd_sof_dev *sdev); /* optional */ + /* IPC client ops */ + int (*register_ipc_clients)(struct snd_sof_dev *sdev); /* optional */ + void (*unregister_ipc_clients)(struct snd_sof_dev *sdev); /* optional */ + /* DAI ops */ struct snd_soc_dai_driver *drv; int num_drv; @@ -305,8 +310,8 @@ struct snd_sof_dsp_ops { /* DSP architecture specific callbacks for oops and stack dumps */ struct dsp_arch_ops { - void (*dsp_oops)(struct snd_sof_dev *sdev, void *oops); - void (*dsp_stack)(struct snd_sof_dev *sdev, void *oops, + void (*dsp_oops)(struct snd_sof_dev *sdev, const char *level, void *oops); + void (*dsp_stack)(struct snd_sof_dev *sdev, const char *level, void *oops, u32 *stack, u32 stack_words); }; @@ -323,7 +328,7 @@ struct snd_sof_dfsentry { * or if it is accessible only when the DSP is in D0. */ enum sof_debugfs_access_type access_type; -#if ENABLE_DEBUGFS_CACHEBUF +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) char *cache_buf; /* buffer to cache the contents of debugfs memory */ #endif struct snd_sof_dev *sdev; @@ -356,24 +361,122 @@ struct snd_sof_mailbox { /* IPC message descriptor for host <-> DSP IO */ struct snd_sof_ipc_msg { /* message data */ - u32 header; void *msg_data; void *reply_data; size_t msg_size; size_t reply_size; int reply_error; + /* notification, firmware initiated messages */ + void *rx_data; + wait_queue_head_t waitq; bool ipc_complete; }; -enum snd_sof_fw_state { - SOF_FW_BOOT_NOT_STARTED = 0, - SOF_FW_BOOT_PREPARE, - SOF_FW_BOOT_IN_PROGRESS, - SOF_FW_BOOT_FAILED, - SOF_FW_BOOT_READY_FAILED, /* firmware booted but fw_ready op failed */ - SOF_FW_BOOT_COMPLETE, +/** + * struct sof_ipc_fw_tracing_ops - IPC-specific firmware tracing ops + * @init: Function pointer for initialization of the tracing + * @free: Optional function pointer for freeing of the tracing + * @fw_crashed: Optional function pointer to notify the tracing of a firmware crash + * @suspend: Function pointer for system/runtime suspend + * @resume: Function pointer for system/runtime resume + */ +struct sof_ipc_fw_tracing_ops { + int (*init)(struct snd_sof_dev *sdev); + void (*free)(struct snd_sof_dev *sdev); + void (*fw_crashed)(struct snd_sof_dev *sdev); + void (*suspend)(struct snd_sof_dev *sdev, pm_message_t pm_state); + int (*resume)(struct snd_sof_dev *sdev); +}; + +/** + * struct sof_ipc_pm_ops - IPC-specific PM ops + * @ctx_save: Optional function pointer for context save + * @ctx_restore: Optional function pointer for context restore + * @set_core_state: Optional function pointer for turning on/off a DSP core + */ +struct sof_ipc_pm_ops { + int (*ctx_save)(struct snd_sof_dev *sdev); + int (*ctx_restore)(struct snd_sof_dev *sdev); + int (*set_core_state)(struct snd_sof_dev *sdev, int core_idx, bool on); +}; + +/** + * struct sof_ipc_fw_loader_ops - IPC/FW-specific loader ops + * @validate: Function pointer for validating the firmware image + * @parse_ext_manifest: Function pointer for parsing the manifest of the firmware + * @load_fw_to_dsp: Optional function pointer for loading the firmware to the + * DSP. + * The function implements generic, hardware independent way + * of loading the initial firmware and its modules (if any). + * @query_fw_configuration: Optional function pointer to query information and + * configuration from the booted firmware. + * Executed after the first successful firmware boot. + */ +struct sof_ipc_fw_loader_ops { + int (*validate)(struct snd_sof_dev *sdev); + size_t (*parse_ext_manifest)(struct snd_sof_dev *sdev); + int (*load_fw_to_dsp)(struct snd_sof_dev *sdev); + int (*query_fw_configuration)(struct snd_sof_dev *sdev); +}; + +struct sof_ipc_tplg_ops; +struct sof_ipc_pcm_ops; + +/** + * struct sof_ipc_ops - IPC-specific ops + * @tplg: Pointer to IPC-specific topology ops + * @pm: Pointer to PM ops + * @pcm: Pointer to PCM ops + * @fw_loader: Pointer to Firmware Loader ops + * @fw_tracing: Pointer to Firmware tracing ops + * + * @tx_msg: Function pointer for sending a 'short' IPC message + * @set_get_data: Function pointer for set/get data ('large' IPC message). This + * function may split up the 'large' message and use the @tx_msg + * path to transfer individual chunks, or use other means to transfer + * the message. + * @get_reply: Function pointer for fetching the reply to + * sdev->ipc->msg.reply_data + * @rx_msg: Function pointer for handling a received message + * + * Note: both @tx_msg and @set_get_data considered as TX functions and they are + * serialized for the duration of the instructed transfer. A large message sent + * via @set_get_data is a single transfer even if at the hardware level it is + * handled with multiple chunks. + */ +struct sof_ipc_ops { + const struct sof_ipc_tplg_ops *tplg; + const struct sof_ipc_pm_ops *pm; + const struct sof_ipc_pcm_ops *pcm; + const struct sof_ipc_fw_loader_ops *fw_loader; + const struct sof_ipc_fw_tracing_ops *fw_tracing; + + int (*tx_msg)(struct snd_sof_dev *sdev, void *msg_data, size_t msg_bytes, + void *reply_data, size_t reply_bytes, bool no_pm); + int (*set_get_data)(struct snd_sof_dev *sdev, void *data, size_t data_bytes, + bool set); + int (*get_reply)(struct snd_sof_dev *sdev); + void (*rx_msg)(struct snd_sof_dev *sdev); +}; + +/* SOF generic IPC data */ +struct snd_sof_ipc { + struct snd_sof_dev *sdev; + + /* protects messages and the disable flag */ + struct mutex tx_mutex; + /* disables further sending of ipc's */ + bool disable_ipc_tx; + + /* Maximum allowed size of a single IPC message/reply */ + size_t max_payload_size; + + struct snd_sof_ipc_msg msg; + + /* IPC ops based on version */ + const struct sof_ipc_ops *ops; }; /* @@ -400,7 +503,7 @@ struct snd_sof_dev { /* DSP firmware boot */ wait_queue_head_t boot_wait; - enum snd_sof_fw_state fw_state; + enum sof_fw_state fw_state; bool first_boot; /* work queue in case the probe is implemented in two steps */ @@ -433,8 +536,6 @@ struct snd_sof_dev { bool ipc_dump_printed; /* firmware loader */ - struct snd_dma_buffer dmab; - struct snd_dma_buffer dmab_bdl; struct sof_ipc_fw_ready fw_ready; struct sof_ipc_fw_version fw_version; struct sof_ipc_cc_version *cc_version; @@ -445,9 +546,11 @@ struct snd_sof_dev { struct list_head kcontrol_list; struct list_head widget_list; struct list_head dai_list; + struct list_head dai_link_list; struct list_head route_list; struct snd_soc_component *component; u32 enabled_cores_mask; /* keep track of enabled cores */ + bool led_present; /* FW configuration */ struct sof_ipc_window *info_window; @@ -456,23 +559,52 @@ struct snd_sof_dev { int ipc_timeout; int boot_timeout; -#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) - unsigned int extractor_stream_tag; -#endif - - /* DMA for Trace */ - struct snd_dma_buffer dmatb; - struct snd_dma_buffer dmatp; - int dma_trace_pages; - wait_queue_head_t trace_sleep; - u32 host_offset; - bool dtrace_is_supported; /* set with Kconfig or module parameter */ - bool dtrace_is_enabled; - bool dtrace_error; - bool dtrace_draining; + /* firmwre tracing */ + bool fw_trace_is_supported; /* set with Kconfig or module parameter */ + void *fw_trace_data; /* private data used by firmware tracing implementation */ bool msi_enabled; + /* DSP core context */ + u32 num_cores; + + /* + * ref count per core that will be modified during system suspend/resume and during pcm + * hw_params/hw_free. This doesn't need to be protected with a mutex because pcm + * hw_params/hw_free are already protected by the PCM mutex in the ALSA framework in + * sound/core/ when streams are active and during system suspend/resume, streams are + * already suspended. + */ + int dsp_core_ref_count[SOF_MAX_DSP_NUM_CORES]; + + /* + * Used to keep track of registered IPC client devices so that they can + * be removed when the parent SOF module is removed. + */ + struct list_head ipc_client_list; + + /* mutex to protect client list */ + struct mutex ipc_client_mutex; + + /* + * Used for tracking the IPC client's RX registration for DSP initiated + * message handling. + */ + struct list_head ipc_rx_handler_list; + + /* + * Used for tracking the IPC client's registration for DSP state change + * notification + */ + struct list_head fw_state_handler_list; + + /* to protect the ipc_rx_handler_list and dsp_state_handler_list list */ + struct mutex client_event_handler_mutex; + + /* quirks to override topology values */ + bool mclk_id_override; + u16 mclk_id_quirk; /* same size as in IPC3 definitions */ + void *private; /* core does not touch this */ }; @@ -496,9 +628,10 @@ void snd_sof_complete(struct device *dev); void snd_sof_new_platform_drv(struct snd_sof_dev *sdev); -int snd_sof_create_page_table(struct device *dev, - struct snd_dma_buffer *dmab, - unsigned char *page_table, size_t size); +/* + * Compress support + */ +extern struct snd_compress_ops sof_compressed_ops; /* * Firmware loading. @@ -506,8 +639,6 @@ int snd_sof_create_page_table(struct device *dev, int snd_sof_load_firmware_raw(struct snd_sof_dev *sdev); int snd_sof_load_firmware_memcpy(struct snd_sof_dev *sdev); int snd_sof_run_firmware(struct snd_sof_dev *sdev); -int snd_sof_parse_module_memcpy(struct snd_sof_dev *sdev, - struct snd_sof_mod_hdr *module); void snd_sof_fw_unload(struct snd_sof_dev *sdev); /* @@ -515,57 +646,63 @@ void snd_sof_fw_unload(struct snd_sof_dev *sdev); */ struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev); void snd_sof_ipc_free(struct snd_sof_dev *sdev); +void snd_sof_ipc_get_reply(struct snd_sof_dev *sdev); void snd_sof_ipc_reply(struct snd_sof_dev *sdev, u32 msg_id); -void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev); -int snd_sof_ipc_stream_pcm_params(struct snd_sof_dev *sdev, - struct sof_ipc_pcm_params *params); -int snd_sof_ipc_valid(struct snd_sof_dev *sdev); -int sof_ipc_tx_message(struct snd_sof_ipc *ipc, u32 header, - void *msg_data, size_t msg_bytes, void *reply_data, - size_t reply_bytes); -int sof_ipc_tx_message_no_pm(struct snd_sof_ipc *ipc, u32 header, - void *msg_data, size_t msg_bytes, +static inline void snd_sof_ipc_msgs_rx(struct snd_sof_dev *sdev) +{ + sdev->ipc->ops->rx_msg(sdev); +} +int sof_ipc_tx_message(struct snd_sof_ipc *ipc, void *msg_data, size_t msg_bytes, + void *reply_data, size_t reply_bytes); +int sof_ipc_tx_message_no_pm(struct snd_sof_ipc *ipc, void *msg_data, size_t msg_bytes, void *reply_data, size_t reply_bytes); -int sof_ipc_init_msg_memory(struct snd_sof_dev *sdev); +int sof_ipc_send_msg(struct snd_sof_dev *sdev, void *msg_data, size_t msg_bytes, + size_t reply_bytes); + +static inline void snd_sof_ipc_process_reply(struct snd_sof_dev *sdev, u32 msg_id) +{ + snd_sof_ipc_get_reply(sdev); + snd_sof_ipc_reply(sdev, msg_id); +} /* * Trace/debug */ -int snd_sof_init_trace(struct snd_sof_dev *sdev); -void snd_sof_release_trace(struct snd_sof_dev *sdev); -void snd_sof_free_trace(struct snd_sof_dev *sdev); int snd_sof_dbg_init(struct snd_sof_dev *sdev); void snd_sof_free_debug(struct snd_sof_dev *sdev); int snd_sof_debugfs_buf_item(struct snd_sof_dev *sdev, void *base, size_t size, const char *name, mode_t mode); -int snd_sof_trace_update_pos(struct snd_sof_dev *sdev, - struct sof_ipc_dma_trace_posn *posn); -void snd_sof_trace_notify_for_error(struct snd_sof_dev *sdev); -void snd_sof_get_status(struct snd_sof_dev *sdev, u32 panic_code, - u32 tracep_code, void *oops, - struct sof_ipc_panic_info *panic_info, - void *stack, size_t stack_words); -int snd_sof_init_trace_ipc(struct snd_sof_dev *sdev); -void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev); +void sof_print_oops_and_stack(struct snd_sof_dev *sdev, const char *level, + u32 panic_code, u32 tracep_code, void *oops, + struct sof_ipc_panic_info *panic_info, + void *stack, size_t stack_words); +void snd_sof_handle_fw_exception(struct snd_sof_dev *sdev, const char *msg); int snd_sof_dbg_memory_info_init(struct snd_sof_dev *sdev); int snd_sof_debugfs_add_region_item_iomem(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type, u32 offset, size_t size, const char *name, enum sof_debugfs_access_type access_type); +/* Firmware tracing */ +int sof_fw_trace_init(struct snd_sof_dev *sdev); +void sof_fw_trace_free(struct snd_sof_dev *sdev); +void sof_fw_trace_fw_crashed(struct snd_sof_dev *sdev); +void sof_fw_trace_suspend(struct snd_sof_dev *sdev, pm_message_t pm_state); +int sof_fw_trace_resume(struct snd_sof_dev *sdev); /* * DSP Architectures. */ -static inline void sof_stack(struct snd_sof_dev *sdev, void *oops, u32 *stack, - u32 stack_words) +static inline void sof_stack(struct snd_sof_dev *sdev, const char *level, + void *oops, u32 *stack, u32 stack_words) { - sof_dsp_arch_ops(sdev)->dsp_stack(sdev, oops, stack, stack_words); + sof_dsp_arch_ops(sdev)->dsp_stack(sdev, level, oops, stack, + stack_words); } -static inline void sof_oops(struct snd_sof_dev *sdev, void *oops) +static inline void sof_oops(struct snd_sof_dev *sdev, const char *level, void *oops) { if (sof_dsp_arch_ops(sdev)->dsp_oops) - sof_dsp_arch_ops(sdev)->dsp_oops(sdev, oops); + sof_dsp_arch_ops(sdev)->dsp_oops(sdev, level, oops); } extern const struct dsp_arch_ops sof_xtensa_arch_ops; @@ -573,15 +710,7 @@ extern const struct dsp_arch_ops sof_xtensa_arch_ops; /* * Firmware state tracking */ -static inline void sof_set_fw_state(struct snd_sof_dev *sdev, - enum snd_sof_fw_state new_state) -{ - if (sdev->fw_state == new_state) - return; - - dev_dbg(sdev->dev, "fw_state change: %d -> %d\n", sdev->fw_state, new_state); - sdev->fw_state = new_state; -} +void sof_set_fw_state(struct snd_sof_dev *sdev, enum sof_fw_state new_state); /* * Utilities @@ -599,14 +728,12 @@ int sof_block_write(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type, int sof_block_read(struct snd_sof_dev *sdev, enum snd_sof_fw_blk_type blk_type, u32 offset, void *dest, size_t size); -int sof_fw_ready(struct snd_sof_dev *sdev, u32 msg_id); - int sof_ipc_msg_data(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, void *p, size_t sz); -int sof_ipc_pcm_params(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream, - const struct sof_ipc_pcm_params_reply *reply); +int sof_set_stream_data_offset(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + size_t posn_offset); int sof_stream_pcm_open(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream); @@ -614,4 +741,60 @@ int sof_stream_pcm_close(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream); int sof_machine_check(struct snd_sof_dev *sdev); + +/* SOF client support */ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_CLIENT) +int sof_client_dev_register(struct snd_sof_dev *sdev, const char *name, u32 id, + const void *data, size_t size); +void sof_client_dev_unregister(struct snd_sof_dev *sdev, const char *name, u32 id); +int sof_register_clients(struct snd_sof_dev *sdev); +void sof_unregister_clients(struct snd_sof_dev *sdev); +void sof_client_ipc_rx_dispatcher(struct snd_sof_dev *sdev, void *msg_buf); +void sof_client_fw_state_dispatcher(struct snd_sof_dev *sdev); +int sof_suspend_clients(struct snd_sof_dev *sdev, pm_message_t state); +int sof_resume_clients(struct snd_sof_dev *sdev); +#else /* CONFIG_SND_SOC_SOF_CLIENT */ +static inline int sof_client_dev_register(struct snd_sof_dev *sdev, const char *name, + u32 id, const void *data, size_t size) +{ + return 0; +} + +static inline void sof_client_dev_unregister(struct snd_sof_dev *sdev, + const char *name, u32 id) +{ +} + +static inline int sof_register_clients(struct snd_sof_dev *sdev) +{ + return 0; +} + +static inline void sof_unregister_clients(struct snd_sof_dev *sdev) +{ +} + +static inline void sof_client_ipc_rx_dispatcher(struct snd_sof_dev *sdev, void *msg_buf) +{ +} + +static inline void sof_client_fw_state_dispatcher(struct snd_sof_dev *sdev) +{ +} + +static inline int sof_suspend_clients(struct snd_sof_dev *sdev, pm_message_t state) +{ + return 0; +} + +static inline int sof_resume_clients(struct snd_sof_dev *sdev) +{ + return 0; +} +#endif /* CONFIG_SND_SOC_SOF_CLIENT */ + +/* Main ops for IPC implementations */ +extern const struct sof_ipc_ops ipc3_ops; +extern const struct sof_ipc_ops ipc4_ops; + #endif diff --git a/sound/soc/sof/sof-probes.c b/sound/soc/sof/sof-probes.c deleted file mode 100644 index 5586af9f1a25..000000000000 --- a/sound/soc/sof/sof-probes.c +++ /dev/null @@ -1,364 +0,0 @@ -// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) -// -// This file is provided under a dual BSD/GPLv2 license. When using or -// redistributing this file, you may do so under either license. -// -// Copyright(c) 2019-2021 Intel Corporation. All rights reserved. -// Author: Cezary Rojewski <cezary.rojewski@intel.com> -// - -#include <sound/soc.h> -#include "ops.h" -#include "sof-priv.h" -#include "sof-probes.h" - -struct sof_probe_dma { - unsigned int stream_tag; - unsigned int dma_buffer_size; -} __packed; - -struct sof_ipc_probe_dma_add_params { - struct sof_ipc_cmd_hdr hdr; - unsigned int num_elems; - struct sof_probe_dma dma[]; -} __packed; - -struct sof_ipc_probe_info_params { - struct sof_ipc_reply rhdr; - unsigned int num_elems; - union { - struct sof_probe_dma dma[0]; - struct sof_probe_point_desc desc[0]; - }; -} __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; - -/** - * 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. - */ -static 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; -} - -/** - * 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. - */ -static 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)); -} - -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_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); - -static int sof_probe_compr_startup(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; -} - -static int sof_probe_compr_shutdown(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); -} - -static int sof_probe_compr_set_params(struct snd_compr_stream *cstream, - struct snd_compr_params *params, - struct snd_soc_dai *dai) -{ - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); - struct snd_compr_runtime *rtd = cstream->runtime; - 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; -} - -static 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); -} - -static 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); -} - -struct snd_soc_cdai_ops sof_probe_compr_ops = { - .startup = sof_probe_compr_startup, - .shutdown = sof_probe_compr_shutdown, - .set_params = sof_probe_compr_set_params, - .trigger = sof_probe_compr_trigger, - .pointer = sof_probe_compr_pointer, -}; -EXPORT_SYMBOL(sof_probe_compr_ops); - -static 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; -} - -const struct snd_compress_ops sof_probe_compressed_ops = { - .copy = sof_probe_compr_copy, -}; -EXPORT_SYMBOL(sof_probe_compressed_ops); diff --git a/sound/soc/sof/sof-probes.h b/sound/soc/sof/sof-probes.h deleted file mode 100644 index 35e1dd8d9e03..000000000000 --- a/sound/soc/sof/sof-probes.h +++ /dev/null @@ -1,38 +0,0 @@ -/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ -/* - * This file is provided under a dual BSD/GPLv2 license. When using or - * redistributing this file, you may do so under either license. - * - * Copyright(c) 2019-2021 Intel Corporation. All rights reserved. - * Author: Cezary Rojewski <cezary.rojewski@intel.com> - */ - -#ifndef __SOF_PROBES_H -#define __SOF_PROBES_H - -#include <sound/compress_driver.h> -#include <sound/sof/header.h> - -struct snd_sof_dev; - -#define SOF_PROBE_INVALID_NODE_ID UINT_MAX - -struct sof_probe_point_desc { - unsigned int buffer_id; - unsigned int purpose; - unsigned int stream_tag; -} __packed; - -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); - -extern struct snd_soc_cdai_ops sof_probe_compr_ops; -extern const struct snd_compress_ops sof_probe_compressed_ops; - -#endif diff --git a/sound/soc/sof/sof-utils.c b/sound/soc/sof/sof-utils.c new file mode 100644 index 000000000000..b6345a7345af --- /dev/null +++ b/sound/soc/sof/sof-utils.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2018-2022 Intel Corporation. All rights reserved. +// +// Author: Keyon Jie <yang.jie@linux.intel.com> +// + +#include <asm/unaligned.h> +#include <linux/io-64-nonatomic-lo-hi.h> +#include <linux/device.h> +#include <sound/memalloc.h> +#include <linux/module.h> +#include "sof-utils.h" + +/* + * Generic buffer page table creation. + * Take the each physical page address and drop the least significant unused + * bits from each (based on PAGE_SIZE). Then pack valid page address bits + * into compressed page table. + */ + +int snd_sof_create_page_table(struct device *dev, + struct snd_dma_buffer *dmab, + unsigned char *page_table, size_t size) +{ + int i, pages; + + pages = snd_sgbuf_aligned_pages(size); + + dev_dbg(dev, "generating page table for %p size 0x%zx pages %d\n", + dmab->area, size, pages); + + for (i = 0; i < pages; i++) { + /* + * The number of valid address bits for each page is 20. + * idx determines the byte position within page_table + * where the current page's address is stored + * in the compressed page_table. + * This can be calculated by multiplying the page number by 2.5. + */ + u32 idx = (5 * i) >> 1; + u32 pfn = snd_sgbuf_get_addr(dmab, i * PAGE_SIZE) >> PAGE_SHIFT; + u8 *pg_table; + + pg_table = (u8 *)(page_table + idx); + + /* + * pagetable compression: + * byte 0 byte 1 byte 2 byte 3 byte 4 byte 5 + * ___________pfn 0__________ __________pfn 1___________ _pfn 2... + * .... .... .... .... .... .... .... .... .... .... .... + * It is created by: + * 1. set current location to 0, PFN index i to 0 + * 2. put pfn[i] at current location in Little Endian byte order + * 3. calculate an intermediate value as + * x = (pfn[i+1] << 4) | (pfn[i] & 0xf) + * 4. put x at offset (current location + 2) in LE byte order + * 5. increment current location by 5 bytes, increment i by 2 + * 6. continue to (2) + */ + if (i & 1) + put_unaligned_le32((pg_table[0] & 0xf) | pfn << 4, + pg_table); + else + put_unaligned_le32(pfn, pg_table); + } + + return pages; +} +EXPORT_SYMBOL(snd_sof_create_page_table); + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/sof-utils.h b/sound/soc/sof/sof-utils.h new file mode 100644 index 000000000000..6f902893807e --- /dev/null +++ b/sound/soc/sof/sof-utils.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * Copyright(c) 2022 Intel Corporation. All rights reserved. + */ + +#ifndef __SOC_SOF_UTILS_H +#define __SOC_SOF_UTILS_H + +struct snd_dma_buffer; +struct device; + +int snd_sof_create_page_table(struct device *dev, + struct snd_dma_buffer *dmab, + unsigned char *page_table, size_t size); + +#endif diff --git a/sound/soc/sof/stream-ipc.c b/sound/soc/sof/stream-ipc.c index 15a55851faeb..5f1ceeea893a 100644 --- a/sound/soc/sof/stream-ipc.c +++ b/sound/soc/sof/stream-ipc.c @@ -45,12 +45,11 @@ int sof_ipc_msg_data(struct snd_sof_dev *sdev, } EXPORT_SYMBOL(sof_ipc_msg_data); -int sof_ipc_pcm_params(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream, - const struct sof_ipc_pcm_params_reply *reply) +int sof_set_stream_data_offset(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + size_t posn_offset) { struct sof_stream *stream = substream->runtime->private_data; - size_t posn_offset = reply->posn_offset; /* check if offset is overflow or it is not aligned */ if (posn_offset > sdev->stream_box.size || @@ -64,7 +63,7 @@ int sof_ipc_pcm_params(struct snd_sof_dev *sdev, return 0; } -EXPORT_SYMBOL(sof_ipc_pcm_params); +EXPORT_SYMBOL(sof_set_stream_data_offset); int sof_stream_pcm_open(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream) diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index bb9e62bbe5db..38855dd60617 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -14,7 +14,6 @@ #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" @@ -28,198 +27,110 @@ #define VOL_TWENTIETH_ROOT_OF_TEN 73533 /* 40th root of 10 in Q1.16 fixed-point notation*/ #define VOL_FORTIETH_ROOT_OF_TEN 69419 -/* - * Volume fractional word length define to 16 sets - * the volume linear gain value to use Qx.16 format - */ -#define VOLUME_FWL 16 + /* 0.5 dB step value in topology TLV */ #define VOL_HALF_DB_STEP 50 -/* Full volume for default values */ -#define VOL_ZERO_DB BIT(VOLUME_FWL) /* TLV data items */ -#define TLV_ITEMS 3 #define TLV_MIN 0 #define TLV_STEP 1 #define TLV_MUTE 2 -/* size of tplg abi in byte */ -#define SOF_TPLG_ABI_SIZE 3 - -struct sof_widget_data { - int ctrl_type; - int ipc_cmd; - struct sof_abi_hdr *pdata; - struct snd_sof_control *control; -}; - -/* send pcm params ipc */ -static int ipc_pcm_params(struct snd_sof_widget *swidget, int dir) +/** + * sof_update_ipc_object - Parse multiple sets of tokens within the token array associated with the + * token ID. + * @scomp: pointer to SOC component + * @object: target IPC struct to save the parsed values + * @token_id: token ID for the token array to be searched + * @tuples: pointer to the tuples array + * @num_tuples: number of tuples in the tuples array + * @object_size: size of the object + * @token_instance_num: number of times the same @token_id needs to be parsed i.e. the function + * looks for @token_instance_num of each token in the token array associated + * with the @token_id + */ +int sof_update_ipc_object(struct snd_soc_component *scomp, void *object, enum sof_tokens token_id, + struct snd_sof_tuple *tuples, int num_tuples, + size_t object_size, int token_instance_num) { - struct sof_ipc_pcm_params_reply ipc_params_reply; - struct snd_soc_component *scomp = swidget->scomp; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - struct sof_ipc_pcm_params pcm; - struct snd_pcm_hw_params *params; - struct snd_sof_pcm *spcm; - int ret; - - memset(&pcm, 0, sizeof(pcm)); + const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg; + const struct sof_token_info *token_list = ipc_tplg_ops->token_list; + const struct sof_topology_token *tokens; + int i, j; - /* get runtime PCM params using widget's stream name */ - spcm = snd_sof_find_spcm_name(scomp, swidget->widget->sname); - if (!spcm) { - dev_err(scomp->dev, "error: cannot find PCM for %s\n", - swidget->widget->name); + if (token_list[token_id].count < 0) { + dev_err(scomp->dev, "Invalid token count for token ID: %d\n", token_id); return -EINVAL; } - params = &spcm->params[dir]; - - /* set IPC PCM params */ - pcm.hdr.size = sizeof(pcm); - pcm.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS; - pcm.comp_id = swidget->comp_id; - pcm.params.hdr.size = sizeof(pcm.params); - pcm.params.direction = dir; - pcm.params.sample_valid_bytes = params_width(params) >> 3; - pcm.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED; - pcm.params.rate = params_rate(params); - pcm.params.channels = params_channels(params); - pcm.params.host_period_bytes = params_period_bytes(params); - - /* set format */ - switch (params_format(params)) { - case SNDRV_PCM_FORMAT_S16: - pcm.params.frame_fmt = SOF_IPC_FRAME_S16_LE; - break; - case SNDRV_PCM_FORMAT_S24: - pcm.params.frame_fmt = SOF_IPC_FRAME_S24_4LE; - break; - case SNDRV_PCM_FORMAT_S32: - pcm.params.frame_fmt = SOF_IPC_FRAME_S32_LE; - break; - default: + /* No tokens to match */ + if (!token_list[token_id].count) + return 0; + + tokens = token_list[token_id].tokens; + if (!tokens) { + dev_err(scomp->dev, "Invalid tokens for token id: %d\n", token_id); return -EINVAL; } - /* send IPC to the DSP */ - ret = sof_ipc_tx_message(sdev->ipc, pcm.hdr.cmd, &pcm, sizeof(pcm), - &ipc_params_reply, sizeof(ipc_params_reply)); - if (ret < 0) - dev_err(scomp->dev, "error: pcm params failed for %s\n", - swidget->widget->name); - - return ret; -} - - /* send stream trigger ipc */ -static int ipc_trigger(struct snd_sof_widget *swidget, int cmd) -{ - struct snd_soc_component *scomp = swidget->scomp; - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - struct sof_ipc_stream stream; - struct sof_ipc_reply reply; - int ret; - - /* set IPC stream params */ - stream.hdr.size = sizeof(stream); - stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | cmd; - stream.comp_id = swidget->comp_id; - - /* send IPC to the DSP */ - ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream, - sizeof(stream), &reply, sizeof(reply)); - if (ret < 0) - dev_err(scomp->dev, "error: failed to trigger %s\n", - swidget->widget->name); - - return ret; -} - -static int sof_keyword_dapm_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *k, int event) -{ - struct snd_sof_widget *swidget = w->dobj.private; - struct snd_soc_component *scomp; - int stream = SNDRV_PCM_STREAM_CAPTURE; - struct snd_sof_pcm *spcm; - int ret = 0; - - if (!swidget) - return 0; + for (i = 0; i < token_list[token_id].count; i++) { + int offset = 0; + int num_tokens_matched = 0; - scomp = swidget->scomp; + for (j = 0; j < num_tuples; j++) { + if (tokens[i].token == tuples[j].token) { + switch (tokens[i].type) { + case SND_SOC_TPLG_TUPLE_TYPE_WORD: + { + u32 *val = (u32 *)((u8 *)object + tokens[i].offset + + offset); - dev_dbg(scomp->dev, "received event %d for widget %s\n", - event, w->name); + *val = tuples[j].value.v; + break; + } + case SND_SOC_TPLG_TUPLE_TYPE_SHORT: + case SND_SOC_TPLG_TUPLE_TYPE_BOOL: + { + u16 *val = (u16 *)((u8 *)object + tokens[i].offset + + offset); - /* get runtime PCM params using widget's stream name */ - spcm = snd_sof_find_spcm_name(scomp, swidget->widget->sname); - if (!spcm) { - dev_err(scomp->dev, "error: cannot find PCM for %s\n", - swidget->widget->name); - return -EINVAL; - } + *val = (u16)tuples[j].value.v; + break; + } + case SND_SOC_TPLG_TUPLE_TYPE_STRING: + { + if (!tokens[i].get_token) { + dev_err(scomp->dev, + "get_token not defined for token %d in %s\n", + tokens[i].token, token_list[token_id].name); + return -EINVAL; + } + + tokens[i].get_token((void *)tuples[j].value.s, object, + tokens[i].offset + offset); + break; + } + default: + break; + } - /* process events */ - switch (event) { - case SND_SOC_DAPM_PRE_PMU: - if (spcm->stream[stream].suspend_ignored) { - dev_dbg(scomp->dev, "PRE_PMU event ignored, KWD pipeline is already RUNNING\n"); - return 0; - } + num_tokens_matched++; - /* set pcm params */ - ret = ipc_pcm_params(swidget, stream); - if (ret < 0) { - dev_err(scomp->dev, - "error: failed to set pcm params for widget %s\n", - swidget->widget->name); - break; - } + /* found all required sets of current token. Move to the next one */ + if (!(num_tokens_matched % token_instance_num)) + break; - /* start trigger */ - ret = ipc_trigger(swidget, SOF_IPC_STREAM_TRIG_START); - if (ret < 0) - dev_err(scomp->dev, - "error: failed to trigger widget %s\n", - swidget->widget->name); - break; - case SND_SOC_DAPM_POST_PMD: - if (spcm->stream[stream].suspend_ignored) { - dev_dbg(scomp->dev, "POST_PMD even ignored, KWD pipeline will remain RUNNING\n"); - return 0; + /* move to the next object */ + offset += object_size; + } } - - /* stop trigger */ - ret = ipc_trigger(swidget, SOF_IPC_STREAM_TRIG_STOP); - if (ret < 0) - dev_err(scomp->dev, - "error: failed to trigger widget %s\n", - swidget->widget->name); - - /* pcm free */ - ret = ipc_trigger(swidget, SOF_IPC_STREAM_PCM_FREE); - if (ret < 0) - dev_err(scomp->dev, - "error: failed to trigger widget %s\n", - swidget->widget->name); - break; - default: - break; } - return ret; + return 0; } -/* event handlers for keyword detect component */ -static const struct snd_soc_tplg_widget_events sof_kwd_events[] = { - {SOF_KEYWORD_DETECT_DAPM_EVENT, sof_keyword_dapm_event}, -}; - -static inline int get_tlv_data(const int *p, int tlv[TLV_ITEMS]) +static inline int get_tlv_data(const int *p, int tlv[SOF_TLV_ITEMS]) { /* we only support dB scale TLV type at the moment */ if ((int)p[SNDRV_CTL_TLVO_TYPE] != SNDRV_CTL_TLVT_DB_SCALE) @@ -309,7 +220,7 @@ static u32 vol_pow32(u32 a, int exp, u32 fwl) * Function to calculate volume gain from TLV data. * This function can only handle gain steps that are multiples of 0.5 dB */ -static u32 vol_compute_gain(u32 value, int *tlv) +u32 vol_compute_gain(u32 value, int *tlv) { int dB_gain; u32 linear_gain; @@ -348,20 +259,17 @@ static u32 vol_compute_gain(u32 value, int *tlv) * "size" specifies the number of entries in the table */ static int set_up_volume_table(struct snd_sof_control *scontrol, - int tlv[TLV_ITEMS], int size) + int tlv[SOF_TLV_ITEMS], int size) { - int j; - - /* init the volume table */ - scontrol->volume_table = kcalloc(size, sizeof(u32), GFP_KERNEL); - if (!scontrol->volume_table) - return -ENOMEM; + struct snd_soc_component *scomp = scontrol->scomp; + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg; - /* populate the volume table */ - for (j = 0; j < size ; j++) - scontrol->volume_table[j] = vol_compute_gain(j, tlv); + if (tplg_ops->control->set_up_volume_table) + return tplg_ops->control->set_up_volume_table(scontrol, tlv, size); - return 0; + dev_err(scomp->dev, "Mandatory op %s not set\n", __func__); + return -EINVAL; } struct sof_dai_types { @@ -376,6 +284,11 @@ static const struct sof_dai_types sof_dais[] = { {"ALH", SOF_DAI_INTEL_ALH}, {"SAI", SOF_DAI_IMX_SAI}, {"ESAI", SOF_DAI_IMX_ESAI}, + {"ACP", SOF_DAI_AMD_BT}, + {"ACPSP", SOF_DAI_AMD_SP}, + {"ACPDMIC", SOF_DAI_AMD_DMIC}, + {"ACPHS", SOF_DAI_AMD_HS}, + {"AFE", SOF_DAI_MEDIATEK_AFE}, }; static enum sof_ipc_dai_type find_dai(const char *name) @@ -419,62 +332,7 @@ static enum sof_ipc_frame find_format(const char *name) return SOF_IPC_FRAME_S32_LE; } -struct sof_process_types { - const char *name; - enum sof_ipc_process_type type; - enum sof_comp_type comp_type; -}; - -static const struct sof_process_types sof_process[] = { - {"EQFIR", SOF_PROCESS_EQFIR, SOF_COMP_EQ_FIR}, - {"EQIIR", SOF_PROCESS_EQIIR, SOF_COMP_EQ_IIR}, - {"KEYWORD_DETECT", SOF_PROCESS_KEYWORD_DETECT, SOF_COMP_KEYWORD_DETECT}, - {"KPB", SOF_PROCESS_KPB, SOF_COMP_KPB}, - {"CHAN_SELECTOR", SOF_PROCESS_CHAN_SELECTOR, SOF_COMP_SELECTOR}, - {"MUX", SOF_PROCESS_MUX, SOF_COMP_MUX}, - {"DEMUX", SOF_PROCESS_DEMUX, SOF_COMP_DEMUX}, - {"DCBLOCK", SOF_PROCESS_DCBLOCK, SOF_COMP_DCBLOCK}, - {"SMART_AMP", SOF_PROCESS_SMART_AMP, SOF_COMP_SMART_AMP}, -}; - -static enum sof_ipc_process_type find_process(const char *name) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(sof_process); i++) { - if (strcmp(name, sof_process[i].name) == 0) - return sof_process[i].type; - } - - return SOF_PROCESS_NONE; -} - -static enum sof_comp_type find_process_comp_type(enum sof_ipc_process_type type) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(sof_process); i++) { - if (sof_process[i].type == type) - return sof_process[i].comp_type; - } - - return SOF_COMP_NONE; -} - -/* - * Topology Token Parsing. - * New tokens should be added to headers and parsing tables below. - */ - -struct sof_topology_token { - u32 token; - u32 type; - int (*get_token)(void *elem, void *object, u32 offset, u32 size); - u32 offset; - u32 size; -}; - -static int get_token_u32(void *elem, void *object, u32 offset, u32 size) +int get_token_u32(void *elem, void *object, u32 offset) { struct snd_soc_tplg_vendor_value_elem *velem = elem; u32 *val = (u32 *)((u8 *)object + offset); @@ -483,7 +341,7 @@ static int get_token_u32(void *elem, void *object, u32 offset, u32 size) return 0; } -static int get_token_u16(void *elem, void *object, u32 offset, u32 size) +int get_token_u16(void *elem, void *object, u32 offset) { struct snd_soc_tplg_vendor_value_elem *velem = elem; u16 *val = (u16 *)((u8 *)object + offset); @@ -492,7 +350,7 @@ static int get_token_u16(void *elem, void *object, u32 offset, u32 size) return 0; } -static int get_token_uuid(void *elem, void *object, u32 offset, u32 size) +int get_token_uuid(void *elem, void *object, u32 offset) { struct snd_soc_tplg_vendor_uuid_elem *velem = elem; u8 *dst = (u8 *)object + offset; @@ -502,313 +360,53 @@ static int get_token_uuid(void *elem, void *object, u32 offset, u32 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; - u32 *val = (u32 *)((u8 *)object + offset); - - *val = find_format(velem->string); - return 0; -} - -static int get_token_dai_type(void *elem, void *object, u32 offset, u32 size) +int get_token_comp_format(void *elem, void *object, u32 offset) { - struct snd_soc_tplg_vendor_string_elem *velem = elem; u32 *val = (u32 *)((u8 *)object + offset); - *val = find_dai(velem->string); + *val = find_format((const char *)elem); return 0; } -static int get_token_process_type(void *elem, void *object, u32 offset, - u32 size) +int get_token_dai_type(void *elem, void *object, u32 offset) { - struct snd_soc_tplg_vendor_string_elem *velem = elem; u32 *val = (u32 *)((u8 *)object + offset); - *val = find_process(velem->string); + *val = find_dai((const char *)elem); return 0; } -/* Buffers */ -static const struct sof_topology_token buffer_tokens[] = { - {SOF_TKN_BUF_SIZE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_buffer, size), 0}, - {SOF_TKN_BUF_CAPS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_buffer, caps), 0}, -}; - -/* DAI */ -static const struct sof_topology_token dai_tokens[] = { - {SOF_TKN_DAI_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_dai_type, - offsetof(struct sof_ipc_comp_dai, type), 0}, - {SOF_TKN_DAI_INDEX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_comp_dai, dai_index), 0}, - {SOF_TKN_DAI_DIRECTION, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_comp_dai, direction), 0}, -}; - -/* BE DAI link */ -static const struct sof_topology_token dai_link_tokens[] = { - {SOF_TKN_DAI_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_dai_type, - offsetof(struct sof_ipc_dai_config, type), 0}, - {SOF_TKN_DAI_INDEX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_dai_config, dai_index), 0}, -}; - -/* scheduling */ -static const struct sof_topology_token sched_tokens[] = { - {SOF_TKN_SCHED_PERIOD, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_pipe_new, period), 0}, - {SOF_TKN_SCHED_PRIORITY, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_pipe_new, priority), 0}, - {SOF_TKN_SCHED_MIPS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_pipe_new, period_mips), 0}, - {SOF_TKN_SCHED_CORE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_pipe_new, core), 0}, - {SOF_TKN_SCHED_FRAMES, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_pipe_new, frames_per_sched), 0}, - {SOF_TKN_SCHED_TIME_DOMAIN, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_pipe_new, time_domain), 0}, -}; - -static const struct sof_topology_token pipeline_tokens[] = { - {SOF_TKN_SCHED_DYNAMIC_PIPELINE, SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16, - offsetof(struct snd_sof_widget, dynamic_pipeline_widget), 0}, - -}; - -/* volume */ -static const struct sof_topology_token volume_tokens[] = { - {SOF_TKN_VOLUME_RAMP_STEP_TYPE, SND_SOC_TPLG_TUPLE_TYPE_WORD, - get_token_u32, offsetof(struct sof_ipc_comp_volume, ramp), 0}, - {SOF_TKN_VOLUME_RAMP_STEP_MS, - SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_comp_volume, initial_ramp), 0}, -}; - -/* SRC */ -static const struct sof_topology_token src_tokens[] = { - {SOF_TKN_SRC_RATE_IN, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_comp_src, source_rate), 0}, - {SOF_TKN_SRC_RATE_OUT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_comp_src, sink_rate), 0}, -}; - -/* ASRC */ -static const struct sof_topology_token asrc_tokens[] = { - {SOF_TKN_ASRC_RATE_IN, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_comp_asrc, source_rate), 0}, - {SOF_TKN_ASRC_RATE_OUT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_comp_asrc, sink_rate), 0}, - {SOF_TKN_ASRC_ASYNCHRONOUS_MODE, SND_SOC_TPLG_TUPLE_TYPE_WORD, - get_token_u32, - offsetof(struct sof_ipc_comp_asrc, asynchronous_mode), 0}, - {SOF_TKN_ASRC_OPERATION_MODE, SND_SOC_TPLG_TUPLE_TYPE_WORD, - get_token_u32, - offsetof(struct sof_ipc_comp_asrc, operation_mode), 0}, -}; - -/* Tone */ -static const struct sof_topology_token tone_tokens[] = { -}; - -/* EFFECT */ -static const struct sof_topology_token process_tokens[] = { - {SOF_TKN_PROCESS_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, - get_token_process_type, - offsetof(struct sof_ipc_comp_process, type), 0}, -}; - -/* PCM */ -static const struct sof_topology_token pcm_tokens[] = { - {SOF_TKN_PCM_DMAC_CONFIG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_comp_host, dmac_config), 0}, -}; - /* PCM */ static const struct sof_topology_token stream_tokens[] = { - {SOF_TKN_STREAM_PLAYBACK_COMPATIBLE_D0I3, - SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16, - offsetof(struct snd_sof_pcm, stream[0].d0i3_compatible), 0}, - {SOF_TKN_STREAM_CAPTURE_COMPATIBLE_D0I3, - SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16, - offsetof(struct snd_sof_pcm, stream[1].d0i3_compatible), 0}, -}; - -/* Generic components */ -static const struct sof_topology_token comp_tokens[] = { - {SOF_TKN_COMP_PERIOD_SINK_COUNT, - SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_comp_config, periods_sink), 0}, - {SOF_TKN_COMP_PERIOD_SOURCE_COUNT, - SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_comp_config, periods_source), 0}, - {SOF_TKN_COMP_FORMAT, - SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_comp_format, - offsetof(struct sof_ipc_comp_config, frame_fmt), 0}, -}; - -/* SSP */ -static const struct sof_topology_token ssp_tokens[] = { - {SOF_TKN_INTEL_SSP_CLKS_CONTROL, - SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_dai_ssp_params, clks_control), 0}, - {SOF_TKN_INTEL_SSP_MCLK_ID, - SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, - offsetof(struct sof_ipc_dai_ssp_params, mclk_id), 0}, - {SOF_TKN_INTEL_SSP_SAMPLE_BITS, SND_SOC_TPLG_TUPLE_TYPE_WORD, - get_token_u32, - offsetof(struct sof_ipc_dai_ssp_params, sample_valid_bits), 0}, - {SOF_TKN_INTEL_SSP_FRAME_PULSE_WIDTH, SND_SOC_TPLG_TUPLE_TYPE_SHORT, - get_token_u16, - offsetof(struct sof_ipc_dai_ssp_params, frame_pulse_width), 0}, - {SOF_TKN_INTEL_SSP_QUIRKS, SND_SOC_TPLG_TUPLE_TYPE_WORD, - get_token_u32, - offsetof(struct sof_ipc_dai_ssp_params, quirks), 0}, - {SOF_TKN_INTEL_SSP_TDM_PADDING_PER_SLOT, SND_SOC_TPLG_TUPLE_TYPE_BOOL, - get_token_u16, - offsetof(struct sof_ipc_dai_ssp_params, - tdm_per_slot_padding_flag), 0}, - {SOF_TKN_INTEL_SSP_BCLK_DELAY, SND_SOC_TPLG_TUPLE_TYPE_WORD, - get_token_u32, - offsetof(struct sof_ipc_dai_ssp_params, bclk_delay), 0}, - -}; - -/* ALH */ -static const struct sof_topology_token alh_tokens[] = { - {SOF_TKN_INTEL_ALH_RATE, - SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_dai_alh_params, rate), 0}, - {SOF_TKN_INTEL_ALH_CH, - SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_dai_alh_params, channels), 0}, -}; - -/* DMIC */ -static const struct sof_topology_token dmic_tokens[] = { - {SOF_TKN_INTEL_DMIC_DRIVER_VERSION, - SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_dai_dmic_params, driver_ipc_version), - 0}, - {SOF_TKN_INTEL_DMIC_CLK_MIN, - SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_dai_dmic_params, pdmclk_min), 0}, - {SOF_TKN_INTEL_DMIC_CLK_MAX, - SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_dai_dmic_params, pdmclk_max), 0}, - {SOF_TKN_INTEL_DMIC_SAMPLE_RATE, - SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_dai_dmic_params, fifo_fs), 0}, - {SOF_TKN_INTEL_DMIC_DUTY_MIN, - SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, - offsetof(struct sof_ipc_dai_dmic_params, duty_min), 0}, - {SOF_TKN_INTEL_DMIC_DUTY_MAX, - SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, - offsetof(struct sof_ipc_dai_dmic_params, duty_max), 0}, - {SOF_TKN_INTEL_DMIC_NUM_PDM_ACTIVE, - SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_dai_dmic_params, - num_pdm_active), 0}, - {SOF_TKN_INTEL_DMIC_FIFO_WORD_LENGTH, - SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, - offsetof(struct sof_ipc_dai_dmic_params, fifo_bits), 0}, - {SOF_TKN_INTEL_DMIC_UNMUTE_RAMP_TIME_MS, - SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_dai_dmic_params, unmute_ramp_time), 0}, - -}; - -/* 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 - * as it increments the index while parsing the array of pdm tokens - * and determines the correct offset - */ -static const struct sof_topology_token dmic_pdm_tokens[] = { - {SOF_TKN_INTEL_DMIC_PDM_CTRL_ID, - SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, - offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, id), - 0}, - {SOF_TKN_INTEL_DMIC_PDM_MIC_A_Enable, - SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, - offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, enable_mic_a), - 0}, - {SOF_TKN_INTEL_DMIC_PDM_MIC_B_Enable, - SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, - offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, enable_mic_b), - 0}, - {SOF_TKN_INTEL_DMIC_PDM_POLARITY_A, - SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, - offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, polarity_mic_a), - 0}, - {SOF_TKN_INTEL_DMIC_PDM_POLARITY_B, - SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, - offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, polarity_mic_b), - 0}, - {SOF_TKN_INTEL_DMIC_PDM_CLK_EDGE, - SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, - offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, clk_edge), - 0}, - {SOF_TKN_INTEL_DMIC_PDM_SKEW, - SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16, - offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, skew), - 0}, -}; - -/* HDA */ -static const struct sof_topology_token hda_tokens[] = { - {SOF_TKN_INTEL_HDA_RATE, - SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_dai_hda_params, rate), 0}, - {SOF_TKN_INTEL_HDA_CH, - SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc_dai_hda_params, channels), 0}, + {SOF_TKN_STREAM_PLAYBACK_COMPATIBLE_D0I3, SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16, + offsetof(struct snd_sof_pcm, stream[0].d0i3_compatible)}, + {SOF_TKN_STREAM_CAPTURE_COMPATIBLE_D0I3, SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16, + offsetof(struct snd_sof_pcm, stream[1].d0i3_compatible)}, }; /* 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}, + offsetof(struct snd_sof_led_control, use_led)}, + {SOF_TKN_MUTE_LED_DIRECTION, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct snd_sof_led_control, direction)}, }; +/** + * sof_parse_uuid_tokens - Parse multiple sets of UUID tokens + * @scomp: pointer to soc component + * @object: target ipc struct for parsed values + * @offset: offset within the object pointer + * @tokens: array of struct sof_topology_token containing the tokens to be matched + * @num_tokens: number of tokens in tokens array + * @array: source pointer to consecutive vendor arrays in topology + * + * This function parses multiple sets of string type tokens in vendor arrays + */ static int sof_parse_uuid_tokens(struct snd_soc_component *scomp, - void *object, - const struct sof_topology_token *tokens, - int count, - struct snd_soc_tplg_vendor_array *array, - size_t offset) + void *object, size_t offset, + const struct sof_topology_token *tokens, int num_tokens, + struct snd_soc_tplg_vendor_array *array) { struct snd_soc_tplg_vendor_uuid_elem *elem; int found = 0; @@ -819,7 +417,7 @@ static int sof_parse_uuid_tokens(struct snd_soc_component *scomp, elem = &array->uuid[i]; /* search for token */ - for (j = 0; j < count; j++) { + for (j = 0; j < num_tokens; j++) { /* match token type */ if (tokens[j].type != SND_SOC_TPLG_TUPLE_TYPE_UUID) continue; @@ -830,8 +428,7 @@ static int sof_parse_uuid_tokens(struct snd_soc_component *scomp, /* matched - now load token */ tokens[j].get_token(elem, object, - offset + tokens[j].offset, - tokens[j].size); + offset + tokens[j].offset); found++; } @@ -840,12 +437,138 @@ static int sof_parse_uuid_tokens(struct snd_soc_component *scomp, return found; } +/** + * sof_copy_tuples - Parse tokens and copy them to the @tuples array + * @sdev: pointer to struct snd_sof_dev + * @array: source pointer to consecutive vendor arrays in topology + * @array_size: size of @array + * @token_id: Token ID associated with a token array + * @token_instance_num: number of times the same @token_id needs to be parsed i.e. the function + * looks for @token_instance_num of each token in the token array associated + * with the @token_id + * @tuples: tuples array to copy the matched tuples to + * @tuples_size: size of @tuples + * @num_copied_tuples: pointer to the number of copied tuples in the tuples array + * + */ +static int sof_copy_tuples(struct snd_sof_dev *sdev, struct snd_soc_tplg_vendor_array *array, + int array_size, u32 token_id, int token_instance_num, + struct snd_sof_tuple *tuples, int tuples_size, int *num_copied_tuples) +{ + const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg; + const struct sof_token_info *token_list = ipc_tplg_ops->token_list; + const struct sof_topology_token *tokens; + int found = 0; + int num_tokens, asize; + int i, j; + + /* nothing to do if token_list is NULL */ + if (!token_list) + return 0; + + if (!tuples || !num_copied_tuples) { + dev_err(sdev->dev, "Invalid tuples array\n"); + return -EINVAL; + } + + tokens = token_list[token_id].tokens; + num_tokens = token_list[token_id].count; + + if (!tokens) { + dev_err(sdev->dev, "No token array defined for token ID: %d\n", token_id); + return -EINVAL; + } + + /* check if there's space in the tuples array for new tokens */ + if (*num_copied_tuples >= tuples_size) { + dev_err(sdev->dev, "No space in tuples array for new tokens from %s", + token_list[token_id].name); + return -EINVAL; + } + + while (array_size > 0 && found < num_tokens * token_instance_num) { + asize = le32_to_cpu(array->size); + + /* validate asize */ + if (asize < 0) { + dev_err(sdev->dev, "Invalid array size 0x%x\n", asize); + return -EINVAL; + } + + /* make sure there is enough data before parsing */ + array_size -= asize; + if (array_size < 0) { + dev_err(sdev->dev, "Invalid array size 0x%x\n", asize); + return -EINVAL; + } + + /* parse element by element */ + for (i = 0; i < le32_to_cpu(array->num_elems); i++) { + /* search for token */ + for (j = 0; j < num_tokens; j++) { + /* match token type */ + if (!(tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_WORD || + tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_SHORT || + tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_BYTE || + tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_BOOL || + tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_STRING)) + continue; + + if (tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_STRING) { + struct snd_soc_tplg_vendor_string_elem *elem; + + elem = &array->string[i]; + + /* match token id */ + if (tokens[j].token != le32_to_cpu(elem->token)) + continue; + + tuples[*num_copied_tuples].token = tokens[j].token; + tuples[*num_copied_tuples].value.s = elem->string; + } else { + struct snd_soc_tplg_vendor_value_elem *elem; + + elem = &array->value[i]; + + /* match token id */ + if (tokens[j].token != le32_to_cpu(elem->token)) + continue; + + tuples[*num_copied_tuples].token = tokens[j].token; + tuples[*num_copied_tuples].value.v = + le32_to_cpu(elem->value); + } + found++; + (*num_copied_tuples)++; + + /* stop if there's no space for any more new tuples */ + if (*num_copied_tuples == tuples_size) + return 0; + } + } + + /* next array */ + array = (struct snd_soc_tplg_vendor_array *)((u8 *)array + asize); + } + + return 0; +} + +/** + * sof_parse_string_tokens - Parse multiple sets of tokens + * @scomp: pointer to soc component + * @object: target ipc struct for parsed values + * @offset: offset within the object pointer + * @tokens: array of struct sof_topology_token containing the tokens to be matched + * @num_tokens: number of tokens in tokens array + * @array: source pointer to consecutive vendor arrays in topology + * + * This function parses multiple sets of string type tokens in vendor arrays + */ static int sof_parse_string_tokens(struct snd_soc_component *scomp, - void *object, - const struct sof_topology_token *tokens, - int count, - struct snd_soc_tplg_vendor_array *array, - size_t offset) + void *object, int offset, + const struct sof_topology_token *tokens, int num_tokens, + struct snd_soc_tplg_vendor_array *array) { struct snd_soc_tplg_vendor_string_elem *elem; int found = 0; @@ -856,7 +579,7 @@ static int sof_parse_string_tokens(struct snd_soc_component *scomp, elem = &array->string[i]; /* search for token */ - for (j = 0; j < count; j++) { + for (j = 0; j < num_tokens; j++) { /* match token type */ if (tokens[j].type != SND_SOC_TPLG_TUPLE_TYPE_STRING) continue; @@ -866,9 +589,7 @@ static int sof_parse_string_tokens(struct snd_soc_component *scomp, continue; /* matched - now load token */ - tokens[j].get_token(elem, object, - offset + tokens[j].offset, - tokens[j].size); + tokens[j].get_token(elem->string, object, offset + tokens[j].offset); found++; } @@ -877,12 +598,21 @@ static int sof_parse_string_tokens(struct snd_soc_component *scomp, return found; } +/** + * sof_parse_word_tokens - Parse multiple sets of tokens + * @scomp: pointer to soc component + * @object: target ipc struct for parsed values + * @offset: offset within the object pointer + * @tokens: array of struct sof_topology_token containing the tokens to be matched + * @num_tokens: number of tokens in tokens array + * @array: source pointer to consecutive vendor arrays in topology + * + * This function parses multiple sets of word type tokens in vendor arrays + */ static int sof_parse_word_tokens(struct snd_soc_component *scomp, - void *object, - const struct sof_topology_token *tokens, - int count, - struct snd_soc_tplg_vendor_array *array, - size_t offset) + void *object, int offset, + const struct sof_topology_token *tokens, int num_tokens, + struct snd_soc_tplg_vendor_array *array) { struct snd_soc_tplg_vendor_value_elem *elem; int found = 0; @@ -893,7 +623,7 @@ static int sof_parse_word_tokens(struct snd_soc_component *scomp, elem = &array->value[i]; /* search for token */ - for (j = 0; j < count; j++) { + for (j = 0; j < num_tokens; j++) { /* match token type */ if (!(tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_WORD || tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_SHORT || @@ -906,9 +636,7 @@ static int sof_parse_word_tokens(struct snd_soc_component *scomp, continue; /* load token */ - tokens[j].get_token(elem, object, - offset + tokens[j].offset, - tokens[j].size); + tokens[j].get_token(elem, object, offset + tokens[j].offset); found++; } @@ -923,27 +651,26 @@ static int sof_parse_word_tokens(struct snd_soc_component *scomp, * @object: target ipc struct for parsed values * @tokens: token definition array describing what tokens to parse * @count: number of tokens in definition array - * @array: source pointer to consecutive vendor arrays to be parsed - * @priv_size: total size of the consecutive source arrays - * @sets: number of similar token sets to be parsed, 1 set has count elements + * @array: source pointer to consecutive vendor arrays in topology + * @array_size: total size of @array + * @token_instance_num: number of times the same tokens needs to be parsed i.e. the function + * looks for @token_instance_num of each token in the @tokens * @object_size: offset to next target ipc struct with multiple sets * * This function parses multiple sets of tokens in vendor arrays into * consecutive ipc structs. */ static int sof_parse_token_sets(struct snd_soc_component *scomp, - void *object, - const struct sof_topology_token *tokens, - int count, - struct snd_soc_tplg_vendor_array *array, - int priv_size, int sets, size_t object_size) + void *object, const struct sof_topology_token *tokens, + int count, struct snd_soc_tplg_vendor_array *array, + int array_size, int token_instance_num, size_t object_size) { size_t offset = 0; int found = 0; int total = 0; int asize; - while (priv_size > 0 && total < count * sets) { + while (array_size > 0 && total < count * token_instance_num) { asize = le32_to_cpu(array->size); /* validate asize */ @@ -954,8 +681,8 @@ static int sof_parse_token_sets(struct snd_soc_component *scomp, } /* make sure there is enough data before parsing */ - priv_size -= asize; - if (priv_size < 0) { + array_size -= asize; + if (array_size < 0) { dev_err(scomp->dev, "error: invalid array size 0x%x\n", asize); return -EINVAL; @@ -964,19 +691,19 @@ static int sof_parse_token_sets(struct snd_soc_component *scomp, /* call correct parser depending on type */ switch (le32_to_cpu(array->type)) { case SND_SOC_TPLG_TUPLE_TYPE_UUID: - found += sof_parse_uuid_tokens(scomp, object, tokens, - count, array, offset); + found += sof_parse_uuid_tokens(scomp, object, offset, tokens, count, + array); break; case SND_SOC_TPLG_TUPLE_TYPE_STRING: - found += sof_parse_string_tokens(scomp, object, tokens, - count, array, offset); + found += sof_parse_string_tokens(scomp, object, offset, 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: - found += sof_parse_word_tokens(scomp, object, tokens, - count, array, offset); + found += sof_parse_word_tokens(scomp, object, offset, tokens, count, + array); break; default: dev_err(scomp->dev, "error: unknown token type %d\n", @@ -999,12 +726,23 @@ static int sof_parse_token_sets(struct snd_soc_component *scomp, return 0; } -static int sof_parse_tokens(struct snd_soc_component *scomp, - void *object, - const struct sof_topology_token *tokens, - int count, +/** + * sof_parse_tokens - Parse one set of tokens + * @scomp: pointer to soc component + * @object: target ipc struct for parsed values + * @tokens: token definition array describing what tokens to parse + * @num_tokens: number of tokens in definition array + * @array: source pointer to consecutive vendor arrays in topology + * @array_size: total size of @array + * + * This function parses a single set of tokens in vendor arrays into + * consecutive ipc structs. + */ +static int sof_parse_tokens(struct snd_soc_component *scomp, void *object, + const struct sof_topology_token *tokens, int num_tokens, struct snd_soc_tplg_vendor_array *array, - int priv_size) + int array_size) + { /* * sof_parse_tokens is used when topology contains only a single set of @@ -1012,16 +750,8 @@ static int sof_parse_tokens(struct snd_soc_component *scomp, * sof_parse_token_sets are sets = 1 (only 1 set) and * object_size = 0 (irrelevant). */ - return sof_parse_token_sets(scomp, object, tokens, count, array, - priv_size, 1, 0); -} - -static void sof_dbg_comp_config(struct snd_soc_component *scomp, - struct sof_ipc_comp_config *config) -{ - dev_dbg(scomp->dev, " config: periods snk %d src %d fmt %d\n", - config->periods_sink, config->periods_source, - config->frame_fmt); + return sof_parse_token_sets(scomp, object, tokens, num_tokens, array, + array_size, 1, 0); } /* @@ -1036,16 +766,13 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct snd_soc_tplg_mixer_control *mc = container_of(hdr, struct snd_soc_tplg_mixer_control, hdr); - struct sof_ipc_ctrl_data *cdata; - int tlv[TLV_ITEMS]; - unsigned int i; + int tlv[SOF_TLV_ITEMS]; + unsigned int mask; int ret; /* validate topology data */ - if (le32_to_cpu(mc->num_channels) > SND_SOC_TPLG_MAX_CHAN) { - ret = -EINVAL; - goto out; - } + if (le32_to_cpu(mc->num_channels) > SND_SOC_TPLG_MAX_CHAN) + return -EINVAL; /* * If control has more than 2 channels we need to override the info. This is because even if @@ -1056,48 +783,26 @@ static int sof_control_load_volume(struct snd_soc_component *scomp, if (le32_to_cpu(mc->num_channels) > 2) kc->info = snd_sof_volume_info; - /* 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); - scontrol->control_data->index = kc->index; - /* set cmd for mixer control */ - if (le32_to_cpu(mc->max) == 1) { - scontrol->cmd = SOF_CTRL_CMD_SWITCH; + scontrol->max = le32_to_cpu(mc->max); + if (le32_to_cpu(mc->max) == 1) goto skip; - } - - scontrol->cmd = SOF_CTRL_CMD_VOLUME; /* extract tlv data */ if (!kc->tlv.p || get_tlv_data(kc->tlv.p, tlv) < 0) { dev_err(scomp->dev, "error: invalid TLV data\n"); - ret = -EINVAL; - goto out_free; + return -EINVAL; } /* set up volume table */ ret = set_up_volume_table(scontrol, tlv, le32_to_cpu(mc->max) + 1); if (ret < 0) { dev_err(scomp->dev, "error: setting up volume table\n"); - goto out_free; - } - - /* set default volume values to 0dB in control */ - cdata = scontrol->control_data; - for (i = 0; i < scontrol->num_channels; i++) { - cdata->chanv[i].channel = i; - cdata->chanv[i].value = VOL_ZERO_DB; + return ret; } skip: @@ -1108,7 +813,17 @@ skip: if (ret != 0) { dev_err(scomp->dev, "error: parse led tokens failed %d\n", le32_to_cpu(mc->priv.size)); - goto out_free_table; + goto err; + } + + if (scontrol->led_ctl.use_led) { + mask = scontrol->led_ctl.direction ? SNDRV_CTL_ELEM_ACCESS_MIC_LED : + SNDRV_CTL_ELEM_ACCESS_SPK_LED; + scontrol->access &= ~SNDRV_CTL_ELEM_ACCESS_LED_MASK; + scontrol->access |= mask; + kc->access &= ~SNDRV_CTL_ELEM_ACCESS_LED_MASK; + kc->access |= mask; + sdev->led_present = true; } dev_dbg(scomp->dev, "tplg: load kcontrol index %d chans %d\n", @@ -1116,12 +831,10 @@ skip: return 0; -out_free_table: +err: if (le32_to_cpu(mc->max) > 1) kfree(scontrol->volume_table); -out_free: - kfree(scontrol->control_data); -out: + return ret; } @@ -1138,17 +851,8 @@ static int sof_control_load_enum(struct snd_soc_component *scomp, if (le32_to_cpu(ec->num_channels) > SND_SOC_TPLG_MAX_CHAN) return -EINVAL; - /* init the enum get/put data */ - scontrol->size = struct_size(scontrol->control_data, chanv, - le32_to_cpu(ec->num_channels)); - scontrol->control_data = kzalloc(scontrol->size, GFP_KERNEL); - if (!scontrol->control_data) - return -ENOMEM; - scontrol->comp_id = sdev->next_comp_id; scontrol->num_channels = le32_to_cpu(ec->num_channels); - scontrol->control_data->index = kc->index; - 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); @@ -1162,77 +866,26 @@ static int sof_control_load_bytes(struct snd_soc_component *scomp, struct snd_soc_tplg_ctl_hdr *hdr) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - struct sof_ipc_ctrl_data *cdata; struct snd_soc_tplg_bytes_control *control = container_of(hdr, struct snd_soc_tplg_bytes_control, hdr); struct soc_bytes_ext *sbe = (struct soc_bytes_ext *)kc->private_value; - 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->max_size = sbe->max; scontrol->comp_id = sdev->next_comp_id; - scontrol->cmd = SOF_CTRL_CMD_BINARY; - scontrol->control_data->index = kc->index; - dev_dbg(scomp->dev, "tplg: load kcontrol index %d chans %d\n", - scontrol->comp_id, scontrol->num_channels); + dev_dbg(scomp->dev, "tplg: load kcontrol index %d\n", scontrol->comp_id); - if (le32_to_cpu(control->priv.size) > 0) { - memcpy(cdata->data, control->priv.data, - le32_to_cpu(control->priv.size)); + /* copy the private data */ + if (priv_size > 0) { + scontrol->priv = kmemdup(control->priv.data, priv_size, GFP_KERNEL); + if (!scontrol->priv) + return -ENOMEM; - if (cdata->data->magic != SOF_ABI_MAGIC) { - dev_err(scomp->dev, "error: Wrong ABI magic 0x%08x.\n", - cdata->data->magic); - ret = -EINVAL; - goto out_free; - } - if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, - cdata->data->abi)) { - dev_err(scomp->dev, - "error: Incompatible ABI version 0x%08x.\n", - cdata->data->abi); - ret = -EINVAL; - goto out_free; - } - if (cdata->data->size + sizeof(struct sof_abi_hdr) != - le32_to_cpu(control->priv.size)) { - dev_err(scomp->dev, - "error: Conflict in bytes vs. priv size.\n"); - ret = -EINVAL; - goto out_free; - } + scontrol->priv_size = priv_size; } return 0; - -out_free: - kfree(scontrol->control_data); -out: - return ret; } /* external kcontrol init - used for any driver specific init */ @@ -1255,8 +908,16 @@ static int sof_control_load(struct snd_soc_component *scomp, int index, if (!scontrol) return -ENOMEM; + scontrol->name = kstrdup(hdr->name, GFP_KERNEL); + if (!scontrol->name) { + kfree(scontrol); + return -ENOMEM; + } + scontrol->scomp = scomp; scontrol->access = kc->access; + scontrol->info_type = le32_to_cpu(hdr->ops.info); + scontrol->index = kc->index; switch (le32_to_cpu(hdr->ops.info)) { case SND_SOC_TPLG_CTL_VOLSW: @@ -1287,11 +948,13 @@ static int sof_control_load(struct snd_soc_component *scomp, int index, default: dev_warn(scomp->dev, "control type not supported %d:%d:%d\n", hdr->ops.get, hdr->ops.put, hdr->ops.info); + kfree(scontrol->name); kfree(scontrol); return 0; } if (ret < 0) { + kfree(scontrol->name); kfree(scontrol); return ret; } @@ -1307,91 +970,32 @@ static int sof_control_unload(struct snd_soc_component *scomp, struct snd_soc_dobj *dobj) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - struct sof_ipc_free fcomp; + const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg; struct snd_sof_control *scontrol = dobj->private; + int ret = 0; - dev_dbg(scomp->dev, "tplg: unload control name : %s\n", scomp->name); + dev_dbg(scomp->dev, "tplg: unload control name : %s\n", scontrol->name); - fcomp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_FREE; - fcomp.hdr.size = sizeof(fcomp); - fcomp.id = scontrol->comp_id; + if (ipc_tplg_ops->control_free) { + ret = ipc_tplg_ops->control_free(sdev, scontrol); + if (ret < 0) + dev_err(scomp->dev, "failed to free control: %s\n", scontrol->name); + } - kfree(scontrol->control_data); + /* free all data before returning in case of error too */ + kfree(scontrol->ipc_control_data); + kfree(scontrol->priv); + kfree(scontrol->name); list_del(&scontrol->list); kfree(scontrol); - /* send IPC to the DSP */ - return sof_ipc_tx_message(sdev->ipc, - fcomp.hdr.cmd, &fcomp, sizeof(fcomp), - NULL, 0); + + return ret; } /* * 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; - } - 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, @@ -1402,15 +1006,15 @@ static int sof_connect_dai_widget(struct snd_soc_component *scomp, struct snd_soc_dai *cpu_dai; int i; - list_for_each_entry(rtd, &card->rtd_list, list) { - dev_vdbg(scomp->dev, "tplg: check widget: %s stream: %s dai stream: %s\n", - w->name, w->sname, rtd->dai_link->stream_name); - - if (!w->sname || !rtd->dai_link->stream_name) - continue; + if (!w->sname) { + dev_err(scomp->dev, "Widget %s does not have stream\n", w->name); + return -EINVAL; + } + list_for_each_entry(rtd, &card->rtd_list, list) { /* does stream match DAI link ? */ - if (strcmp(w->sname, rtd->dai_link->stream_name)) + if (!rtd->dai_link->stream_name || + strcmp(w->sname, rtd->dai_link->stream_name)) continue; switch (w->id) { @@ -1426,7 +1030,7 @@ static int sof_connect_dai_widget(struct snd_soc_component *scomp, break; } } - if (i == rtd->num_cpus) { + if (i == rtd->dai_link->num_cpus) { dev_err(scomp->dev, "error: can't find BE for DAI %s\n", w->name); @@ -1448,7 +1052,7 @@ static int sof_connect_dai_widget(struct snd_soc_component *scomp, break; } } - if (i == rtd->num_cpus) { + if (i == rtd->dai_link->num_cpus) { dev_err(scomp->dev, "error: can't find BE for DAI %s\n", w->name); @@ -1473,146 +1077,44 @@ static int sof_connect_dai_widget(struct snd_soc_component *scomp, 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) +static void sof_disconnect_dai_widget(struct snd_soc_component *scomp, + struct snd_soc_dapm_widget *w) { - 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, - struct snd_sof_dai *dai) -{ - struct snd_soc_tplg_private *private = &tw->priv; - struct sof_ipc_comp_dai *comp_dai; - size_t ipc_size = sizeof(*comp_dai); - int ret; - - comp_dai = (struct sof_ipc_comp_dai *) - sof_comp_alloc(swidget, &ipc_size, index); - if (!comp_dai) - return -ENOMEM; - - /* 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(scomp->dev, "error: parse dai tokens failed %d\n", - le32_to_cpu(private->size)); - goto finish; - } - - ret = sof_parse_tokens(scomp, &comp_dai->config, comp_tokens, - ARRAY_SIZE(comp_tokens), private->array, - le32_to_cpu(private->size)); - if (ret != 0) { - dev_err(scomp->dev, "error: parse dai.cfg tokens failed %d\n", - private->size); - goto finish; - } - - 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); - - if (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; -} - -/* - * Buffer topology - */ - -static int sof_widget_load_buffer(struct snd_soc_component *scomp, int index, - struct snd_sof_widget *swidget, - struct snd_soc_tplg_dapm_widget *tw) -{ - struct snd_soc_tplg_private *private = &tw->priv; - struct sof_ipc_buffer *buffer; - int ret; + struct snd_soc_card *card = scomp->card; + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_dai *cpu_dai; + int i; - buffer = kzalloc(sizeof(*buffer), GFP_KERNEL); - if (!buffer) - return -ENOMEM; + if (!w->sname) + return; - /* configure dai IPC message */ - buffer->comp.hdr.size = sizeof(*buffer); - buffer->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_BUFFER_NEW; - buffer->comp.id = swidget->comp_id; - buffer->comp.type = SOF_COMP_BUFFER; - buffer->comp.pipeline_id = index; - buffer->comp.core = swidget->core; + list_for_each_entry(rtd, &card->rtd_list, list) { + /* does stream match DAI link ? */ + if (!rtd->dai_link->stream_name || + strcmp(w->sname, rtd->dai_link->stream_name)) + continue; - ret = sof_parse_tokens(scomp, buffer, buffer_tokens, - ARRAY_SIZE(buffer_tokens), private->array, - le32_to_cpu(private->size)); - if (ret != 0) { - dev_err(scomp->dev, "error: parse buffer tokens failed %d\n", - private->size); - kfree(buffer); - return ret; + switch (w->id) { + case snd_soc_dapm_dai_out: + for_each_rtd_cpu_dais(rtd, i, cpu_dai) { + if (cpu_dai->capture_widget == w) { + cpu_dai->capture_widget = NULL; + break; + } + } + break; + case snd_soc_dapm_dai_in: + for_each_rtd_cpu_dais(rtd, i, cpu_dai) { + if (cpu_dai->playback_widget == w) { + cpu_dai->playback_widget = NULL; + break; + } + } + break; + default: + break; + } } - - dev_dbg(scomp->dev, "buffer %s: size %d caps 0x%x\n", - swidget->widget->name, buffer->size, buffer->caps); - - swidget->private = buffer; - - return 0; } /* bind PCM ID to host component ID */ @@ -1634,701 +1136,133 @@ static int spcm_bind(struct snd_soc_component *scomp, struct snd_sof_pcm *spcm, return 0; } -/* - * PCM Topology - */ - -static int sof_widget_load_pcm(struct snd_soc_component *scomp, int index, - struct snd_sof_widget *swidget, - enum sof_ipc_stream_direction dir, - struct snd_soc_tplg_dapm_widget *tw) -{ - struct snd_soc_tplg_private *private = &tw->priv; - struct sof_ipc_comp_host *host; - size_t ipc_size = sizeof(*host); - int ret; - - host = (struct sof_ipc_comp_host *) - sof_comp_alloc(swidget, &ipc_size, index); - if (!host) - return -ENOMEM; - - /* configure host comp IPC message */ - host->comp.type = SOF_COMP_HOST; - host->direction = dir; - host->config.hdr.size = sizeof(host->config); - - ret = sof_parse_tokens(scomp, host, pcm_tokens, - ARRAY_SIZE(pcm_tokens), private->array, - le32_to_cpu(private->size)); - if (ret != 0) { - dev_err(scomp->dev, "error: parse host tokens failed %d\n", - private->size); - goto err; - } - - ret = sof_parse_tokens(scomp, &host->config, comp_tokens, - ARRAY_SIZE(comp_tokens), private->array, - le32_to_cpu(private->size)); - if (ret != 0) { - dev_err(scomp->dev, "error: parse host.cfg tokens failed %d\n", - le32_to_cpu(private->size)); - goto err; - } - - dev_dbg(scomp->dev, "loaded host %s\n", swidget->widget->name); - sof_dbg_comp_config(scomp, &host->config); - - swidget->private = host; - - return 0; -err: - kfree(host); - return ret; -} - -/* - * Pipeline Topology - */ -int sof_load_pipeline_ipc(struct snd_sof_dev *sdev, - struct sof_ipc_pipe_new *pipeline, - struct sof_ipc_comp_reply *r) -{ - 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; -} - -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 snd_soc_tplg_private *private = &tw->priv; - struct sof_ipc_pipe_new *pipeline; - struct snd_sof_widget *comp_swidget; - int ret; - - pipeline = kzalloc(sizeof(*pipeline), GFP_KERNEL); - if (!pipeline) - return -ENOMEM; - - /* configure dai IPC message */ - pipeline->hdr.size = sizeof(*pipeline); - pipeline->hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_PIPE_NEW; - pipeline->pipeline_id = index; - pipeline->comp_id = swidget->comp_id; - - /* component at start of pipeline is our stream id */ - comp_swidget = snd_sof_find_swidget(scomp, tw->sname); - if (!comp_swidget) { - dev_err(scomp->dev, "error: widget %s refers to non existent widget %s\n", - tw->name, tw->sname); - ret = -EINVAL; - goto err; - } - - pipeline->sched_id = comp_swidget->comp_id; - - dev_dbg(scomp->dev, "tplg: pipeline id %d comp %d scheduling comp id %d\n", - pipeline->pipeline_id, pipeline->comp_id, pipeline->sched_id); - - ret = sof_parse_tokens(scomp, pipeline, sched_tokens, - ARRAY_SIZE(sched_tokens), private->array, - le32_to_cpu(private->size)); - if (ret != 0) { - dev_err(scomp->dev, "error: parse pipeline tokens failed %d\n", - private->size); - goto err; - } - - ret = sof_parse_tokens(scomp, swidget, pipeline_tokens, - ARRAY_SIZE(pipeline_tokens), private->array, - le32_to_cpu(private->size)); - if (ret != 0) { - dev_err(scomp->dev, "error: parse dynamic pipeline token failed %d\n", - private->size); - goto err; - } - - if (sof_core_debug & SOF_DBG_DISABLE_MULTICORE) - pipeline->core = SOF_DSP_PRIMARY_CORE; - - if (sof_core_debug & SOF_DBG_DYNAMIC_PIPELINES_OVERRIDE) - swidget->dynamic_pipeline_widget = sof_core_debug & - SOF_DBG_DYNAMIC_PIPELINES_ENABLE; - - dev_dbg(scomp->dev, "pipeline %s: period %d pri %d mips %d core %d frames %d dynamic %d\n", - swidget->widget->name, pipeline->period, pipeline->priority, - pipeline->period_mips, pipeline->core, pipeline->frames_per_sched, - swidget->dynamic_pipeline_widget); - - swidget->private = pipeline; - - return 0; -err: - kfree(pipeline); - return ret; -} - -/* - * Mixer topology - */ - -static int sof_widget_load_mixer(struct snd_soc_component *scomp, int index, - struct snd_sof_widget *swidget, - struct snd_soc_tplg_dapm_widget *tw) +static int sof_get_token_value(u32 token_id, struct snd_sof_tuple *tuples, int num_tuples) { - struct snd_soc_tplg_private *private = &tw->priv; - struct sof_ipc_comp_mixer *mixer; - size_t ipc_size = sizeof(*mixer); - int ret; - - mixer = (struct sof_ipc_comp_mixer *) - sof_comp_alloc(swidget, &ipc_size, index); - if (!mixer) - return -ENOMEM; - - /* configure mixer IPC message */ - mixer->comp.type = SOF_COMP_MIXER; - 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(scomp->dev, "error: parse mixer.cfg tokens failed %d\n", - private->size); - kfree(mixer); - return ret; - } - - sof_dbg_comp_config(scomp, &mixer->config); - - swidget->private = mixer; - - return 0; -} - -/* - * Mux topology - */ -static int sof_widget_load_mux(struct snd_soc_component *scomp, int index, - struct snd_sof_widget *swidget, - struct snd_soc_tplg_dapm_widget *tw) -{ - struct snd_soc_tplg_private *private = &tw->priv; - struct sof_ipc_comp_mux *mux; - size_t ipc_size = sizeof(*mux); - int ret; - - mux = (struct sof_ipc_comp_mux *) - sof_comp_alloc(swidget, &ipc_size, index); - if (!mux) - return -ENOMEM; + int i; - /* configure mux IPC message */ - mux->comp.type = SOF_COMP_MUX; - mux->config.hdr.size = sizeof(mux->config); + if (!tuples) + return -EINVAL; - ret = sof_parse_tokens(scomp, &mux->config, comp_tokens, - ARRAY_SIZE(comp_tokens), private->array, - le32_to_cpu(private->size)); - if (ret != 0) { - dev_err(scomp->dev, "error: parse mux.cfg tokens failed %d\n", - private->size); - kfree(mux); - return ret; + for (i = 0; i < num_tuples; i++) { + if (tuples[i].token == token_id) + return tuples[i].value.v; } - sof_dbg_comp_config(scomp, &mux->config); - - swidget->private = mux; - - return 0; + return -EINVAL; } -/* - * PGA Topology - */ - -static int sof_widget_load_pga(struct snd_soc_component *scomp, int index, - struct snd_sof_widget *swidget, - struct snd_soc_tplg_dapm_widget *tw) +static int sof_widget_parse_tokens(struct snd_soc_component *scomp, struct snd_sof_widget *swidget, + struct snd_soc_tplg_dapm_widget *tw, + enum sof_tokens *object_token_list, int count) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg; + const struct sof_token_info *token_list = ipc_tplg_ops->token_list; 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 = (struct sof_ipc_comp_volume *) - sof_comp_alloc(swidget, &ipc_size, index); - if (!volume) - return -ENOMEM; - - if (!le32_to_cpu(tw->num_kcontrols)) { - dev_err(scomp->dev, "error: invalid kcontrol count %d for volume\n", - tw->num_kcontrols); - ret = -EINVAL; - goto err; - } - - /* configure volume IPC message */ - volume->comp.type = SOF_COMP_VOLUME; - 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(scomp->dev, "error: parse volume tokens failed %d\n", - private->size); - goto err; - } - ret = sof_parse_tokens(scomp, &volume->config, comp_tokens, - ARRAY_SIZE(comp_tokens), private->array, - le32_to_cpu(private->size)); - if (ret != 0) { - dev_err(scomp->dev, "error: parse volume.cfg tokens failed %d\n", - le32_to_cpu(private->size)); - goto err; - } - - sof_dbg_comp_config(scomp, &volume->config); - - swidget->private = volume; - - list_for_each_entry(scontrol, &sdev->kcontrol_list, list) { - if (scontrol->comp_id == swidget->comp_id && - scontrol->volume_table) { - min_step = scontrol->min_volume_step; - max_step = scontrol->max_volume_step; - volume->min_value = scontrol->volume_table[min_step]; - volume->max_value = scontrol->volume_table[max_step]; - volume->channels = scontrol->num_channels; - break; - } - } - - return 0; -err: - kfree(volume); - return ret; -} - -/* - * SRC Topology - */ - -static int sof_widget_load_src(struct snd_soc_component *scomp, int index, - struct snd_sof_widget *swidget, - struct snd_soc_tplg_dapm_widget *tw) -{ - struct snd_soc_tplg_private *private = &tw->priv; - struct sof_ipc_comp_src *src; - size_t ipc_size = sizeof(*src); - int ret; - - src = (struct sof_ipc_comp_src *) - sof_comp_alloc(swidget, &ipc_size, index); - if (!src) - return -ENOMEM; - - /* configure src IPC message */ - src->comp.type = SOF_COMP_SRC; - src->config.hdr.size = sizeof(src->config); - - ret = sof_parse_tokens(scomp, src, src_tokens, - ARRAY_SIZE(src_tokens), private->array, - le32_to_cpu(private->size)); - if (ret != 0) { - dev_err(scomp->dev, "error: parse src tokens failed %d\n", - private->size); - goto err; - } - - ret = sof_parse_tokens(scomp, &src->config, comp_tokens, - ARRAY_SIZE(comp_tokens), private->array, - le32_to_cpu(private->size)); - if (ret != 0) { - dev_err(scomp->dev, "error: parse src.cfg tokens failed %d\n", - le32_to_cpu(private->size)); - goto err; - } - - dev_dbg(scomp->dev, "src %s: source rate %d sink rate %d\n", - swidget->widget->name, src->source_rate, src->sink_rate); - sof_dbg_comp_config(scomp, &src->config); - - swidget->private = src; - - return 0; -err: - kfree(src); - return ret; -} - -/* - * ASRC Topology - */ - -static int sof_widget_load_asrc(struct snd_soc_component *scomp, int index, - struct snd_sof_widget *swidget, - struct snd_soc_tplg_dapm_widget *tw) -{ - struct 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; - } + int num_tuples = 0; + int ret, i; - 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; + if (count > 0 && !object_token_list) { + dev_err(scomp->dev, "No token list for widget %s\n", swidget->widget->name); + return -EINVAL; } - 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; + /* calculate max size of tuples array */ + for (i = 0; i < count; i++) + num_tuples += token_list[object_token_list[i]].count; - return 0; -err: - kfree(asrc); - return ret; -} - -/* - * Signal Generator Topology - */ - -static int sof_widget_load_siggen(struct snd_soc_component *scomp, int index, - struct snd_sof_widget *swidget, - struct snd_soc_tplg_dapm_widget *tw) -{ - struct snd_soc_tplg_private *private = &tw->priv; - struct sof_ipc_comp_tone *tone; - size_t ipc_size = sizeof(*tone); - int ret; - - tone = (struct sof_ipc_comp_tone *) - sof_comp_alloc(swidget, &ipc_size, index); - if (!tone) + /* allocate memory for tuples array */ + swidget->tuples = kcalloc(num_tuples, sizeof(*swidget->tuples), GFP_KERNEL); + if (!swidget->tuples) return -ENOMEM; - /* configure siggen IPC message */ - tone->comp.type = SOF_COMP_TONE; - tone->config.hdr.size = sizeof(tone->config); - - ret = sof_parse_tokens(scomp, tone, tone_tokens, - ARRAY_SIZE(tone_tokens), private->array, - le32_to_cpu(private->size)); - if (ret != 0) { - dev_err(scomp->dev, "error: parse tone tokens failed %d\n", - le32_to_cpu(private->size)); - goto err; - } - - ret = sof_parse_tokens(scomp, &tone->config, comp_tokens, - ARRAY_SIZE(comp_tokens), private->array, - le32_to_cpu(private->size)); - if (ret != 0) { - dev_err(scomp->dev, "error: parse tone.cfg tokens failed %d\n", - le32_to_cpu(private->size)); - goto err; - } - - dev_dbg(scomp->dev, "tone %s: frequency %d amplitude %d\n", - swidget->widget->name, tone->frequency, tone->amplitude); - sof_dbg_comp_config(scomp, &tone->config); - - swidget->private = tone; + /* parse token list for widget */ + for (i = 0; i < count; i++) { + int num_sets = 1; - return 0; -err: - kfree(tone); - return ret; -} - -static int sof_get_control_data(struct snd_soc_component *scomp, - struct snd_soc_dapm_widget *widget, - struct sof_widget_data *wdata, - size_t *size) -{ - const struct snd_kcontrol_new *kc; - struct soc_mixer_control *sm; - struct soc_bytes_ext *sbe; - struct soc_enum *se; - int i; - - *size = 0; - - for (i = 0; i < widget->num_kcontrols; i++) { - kc = &widget->kcontrol_news[i]; - - switch (widget->dobj.widget.kcontrol_type[i]) { - case SND_SOC_TPLG_TYPE_MIXER: - sm = (struct soc_mixer_control *)kc->private_value; - wdata[i].control = sm->dobj.private; - break; - case SND_SOC_TPLG_TYPE_BYTES: - sbe = (struct soc_bytes_ext *)kc->private_value; - wdata[i].control = sbe->dobj.private; - break; - case SND_SOC_TPLG_TYPE_ENUM: - se = (struct soc_enum *)kc->private_value; - wdata[i].control = se->dobj.private; - break; - default: - dev_err(scomp->dev, "error: unknown kcontrol type %u in widget %s\n", - widget->dobj.widget.kcontrol_type[i], - widget->name); - return -EINVAL; + if (object_token_list[i] >= SOF_TOKEN_COUNT) { + dev_err(scomp->dev, "Invalid token id %d for widget %s\n", + object_token_list[i], swidget->widget->name); + ret = -EINVAL; + goto err; } - if (!wdata[i].control) { - dev_err(scomp->dev, "error: no scontrol for widget %s\n", - widget->name); - return -EINVAL; - } + switch (object_token_list[i]) { + case SOF_COMP_EXT_TOKENS: + /* parse and save UUID in swidget */ + ret = sof_parse_tokens(scomp, swidget, + token_list[object_token_list[i]].tokens, + token_list[object_token_list[i]].count, + private->array, le32_to_cpu(private->size)); + if (ret < 0) { + dev_err(scomp->dev, "Failed parsing %s for widget %s\n", + token_list[object_token_list[i]].name, + swidget->widget->name); + goto err; + } - wdata[i].pdata = wdata[i].control->control_data->data; - if (!wdata[i].pdata) - return -EINVAL; + continue; + case SOF_IN_AUDIO_FORMAT_TOKENS: + case SOF_OUT_AUDIO_FORMAT_TOKENS: + case SOF_COPIER_GATEWAY_CFG_TOKENS: + case SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS: + num_sets = sof_get_token_value(SOF_TKN_COMP_NUM_AUDIO_FORMATS, + swidget->tuples, swidget->num_tuples); + + if (num_sets < 0) { + dev_err(sdev->dev, "Invalid audio format count for %s\n", + swidget->widget->name); + ret = num_sets; + goto err; + } - /* make sure data is valid - data can be updated at runtime */ - if (widget->dobj.widget.kcontrol_type[i] == SND_SOC_TPLG_TYPE_BYTES && - wdata[i].pdata->magic != SOF_ABI_MAGIC) - return -EINVAL; + if (num_sets > 1) { + struct snd_sof_tuple *new_tuples; - *size += wdata[i].pdata->size; + num_tuples += token_list[object_token_list[i]].count * num_sets; + new_tuples = krealloc(swidget->tuples, + sizeof(*new_tuples) * num_tuples, GFP_KERNEL); + if (!new_tuples) { + ret = -ENOMEM; + goto err; + } - /* get data type */ - switch (wdata[i].control->cmd) { - case SOF_CTRL_CMD_VOLUME: - case SOF_CTRL_CMD_ENUM: - case SOF_CTRL_CMD_SWITCH: - wdata[i].ipc_cmd = SOF_IPC_COMP_SET_VALUE; - wdata[i].ctrl_type = SOF_CTRL_TYPE_VALUE_CHAN_SET; - break; - case SOF_CTRL_CMD_BINARY: - wdata[i].ipc_cmd = SOF_IPC_COMP_SET_DATA; - wdata[i].ctrl_type = SOF_CTRL_TYPE_DATA_SET; + swidget->tuples = new_tuples; + } break; default: break; } - } - - return 0; -} - -static int sof_process_load(struct snd_soc_component *scomp, int index, - struct snd_sof_widget *swidget, - struct snd_soc_tplg_dapm_widget *tw, - int type) -{ - struct snd_soc_dapm_widget *widget = swidget->widget; - struct snd_soc_tplg_private *private = &tw->priv; - struct sof_ipc_comp_process *process; - struct sof_widget_data *wdata = NULL; - size_t ipc_data_size = 0; - size_t ipc_size; - int offset = 0; - int ret; - int i; - /* allocate struct for widget control data sizes and types */ - if (widget->num_kcontrols) { - wdata = kcalloc(widget->num_kcontrols, - sizeof(*wdata), - GFP_KERNEL); - - if (!wdata) - return -ENOMEM; - - /* get possible component controls and get size of all pdata */ - ret = sof_get_control_data(scomp, widget, wdata, - &ipc_data_size); - - if (ret < 0) - goto out; - } - - ipc_size = sizeof(struct sof_ipc_comp_process) + ipc_data_size; - - /* we are exceeding max ipc size, config needs to be sent separately */ - if (ipc_size > SOF_IPC_MSG_MAX_SIZE) { - ipc_size -= ipc_data_size; - ipc_data_size = 0; - } - - process = (struct sof_ipc_comp_process *) - sof_comp_alloc(swidget, &ipc_size, index); - if (!process) { - ret = -ENOMEM; - goto out; - } - - /* configure iir IPC message */ - process->comp.type = type; - process->config.hdr.size = sizeof(process->config); - - ret = sof_parse_tokens(scomp, &process->config, comp_tokens, - ARRAY_SIZE(comp_tokens), private->array, - le32_to_cpu(private->size)); - if (ret != 0) { - dev_err(scomp->dev, "error: parse process.cfg tokens failed %d\n", - le32_to_cpu(private->size)); - goto err; - } - - sof_dbg_comp_config(scomp, &process->config); - - /* - * found private data in control, so copy it. - * get possible component controls - get size of all pdata, - * then memcpy with headers - */ - if (ipc_data_size) { - for (i = 0; i < widget->num_kcontrols; i++) { - memcpy(&process->data + offset, - wdata[i].pdata->data, - wdata[i].pdata->size); - offset += wdata[i].pdata->size; + /* copy one set of tuples per token ID into swidget->tuples */ + ret = sof_copy_tuples(sdev, private->array, le32_to_cpu(private->size), + object_token_list[i], num_sets, swidget->tuples, + num_tuples, &swidget->num_tuples); + if (ret < 0) { + dev_err(scomp->dev, "Failed parsing %s for widget %s err: %d\n", + token_list[object_token_list[i]].name, swidget->widget->name, ret); + goto err; } } - process->size = ipc_data_size; - swidget->private = process; + return 0; err: - if (ret < 0) - kfree(process); -out: - kfree(wdata); + kfree(swidget->tuples); return ret; } -/* - * Processing Component Topology - can be "effect", "codec", or general - * "processing". - */ - -static int sof_widget_load_process(struct snd_soc_component *scomp, int index, - struct snd_sof_widget *swidget, - struct snd_soc_tplg_dapm_widget *tw) -{ - struct snd_soc_tplg_private *private = &tw->priv; - struct sof_ipc_comp_process config; - int ret; - - /* check we have some tokens - we need at least process type */ - if (le32_to_cpu(private->size) == 0) { - dev_err(scomp->dev, "error: process tokens not found\n"); - return -EINVAL; - } - - memset(&config, 0, sizeof(config)); - 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(scomp->dev, "error: parse process tokens failed %d\n", - le32_to_cpu(private->size)); - return ret; - } - - /* now load process specific data and send IPC */ - ret = sof_process_load(scomp, index, swidget, tw, find_process_comp_type(config.type)); - if (ret < 0) { - dev_err(scomp->dev, "error: process loading failed\n"); - return ret; - } - - return 0; -} - -static int sof_widget_bind_event(struct snd_soc_component *scomp, - struct snd_sof_widget *swidget, - u16 event_type) -{ - struct sof_ipc_comp *ipc_comp; - - /* validate widget event type */ - switch (event_type) { - case SOF_KEYWORD_DETECT_DAPM_EVENT: - /* only KEYWORD_DETECT comps should handle this */ - if (swidget->id != snd_soc_dapm_effect) - break; - - ipc_comp = swidget->private; - if (ipc_comp && ipc_comp->type != SOF_COMP_KEYWORD_DETECT) - break; - - /* bind event to keyword detect comp */ - return snd_soc_tplg_widget_bind_event(swidget->widget, - sof_kwd_events, - ARRAY_SIZE(sof_kwd_events), - event_type); - default: - break; - } - - dev_err(scomp->dev, - "error: invalid event type %d for widget %s\n", - event_type, swidget->widget->name); - return -EINVAL; -} - /* external widget init - used for any driver specific init */ static int sof_widget_ready(struct snd_soc_component *scomp, int index, struct snd_soc_dapm_widget *w, struct snd_soc_tplg_dapm_widget *tw) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg; + const struct sof_ipc_tplg_widget_ops *widget_ops = ipc_tplg_ops->widget; struct snd_sof_widget *swidget; struct snd_sof_dai *dai; - struct sof_ipc_comp comp = { - .core = SOF_DSP_PRIMARY_CORE, - }; + enum sof_tokens *token_list; + int token_list_size; int ret = 0; swidget = kzalloc(sizeof(*swidget), GFP_KERNEL); @@ -2348,30 +1282,8 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index, 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; - } - - if (sof_core_debug & SOF_DBG_DISABLE_MULTICORE) - comp.core = SOF_DSP_PRIMARY_CORE; - - swidget->core = comp.core; - - 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; - } + token_list = widget_ops[w->id].token_list; + token_list_size = widget_ops[w->id].token_list_size; /* handle any special case widgets */ switch (w->id) { @@ -2381,9 +1293,10 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index, if (!dai) { kfree(swidget); return -ENOMEM; + } - ret = sof_widget_load_dai(scomp, index, swidget, tw, dai); + ret = sof_widget_parse_tokens(scomp, swidget, tw, token_list, token_list_size); if (!ret) ret = sof_connect_dai_widget(scomp, w, tw, dai); if (ret < 0) { @@ -2393,41 +1306,35 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index, list_add(&dai->list, &sdev->dai_list); swidget->private = dai; break; - case snd_soc_dapm_mixer: - ret = sof_widget_load_mixer(scomp, index, swidget, tw); + case snd_soc_dapm_effect: + /* check we have some tokens - we need at least process type */ + if (le32_to_cpu(tw->priv.size) == 0) { + dev_err(scomp->dev, "error: process tokens not found\n"); + ret = -EINVAL; + break; + } + ret = sof_widget_parse_tokens(scomp, swidget, tw, token_list, token_list_size); break; case snd_soc_dapm_pga: - ret = sof_widget_load_pga(scomp, index, swidget, tw); - break; + if (!le32_to_cpu(tw->num_kcontrols)) { + dev_err(scomp->dev, "invalid kcontrol count %d for volume\n", + tw->num_kcontrols); + ret = -EINVAL; + break; + } + + fallthrough; + case snd_soc_dapm_mixer: case snd_soc_dapm_buffer: - ret = sof_widget_load_buffer(scomp, index, swidget, tw); - break; case snd_soc_dapm_scheduler: - ret = sof_widget_load_pipeline(scomp, index, swidget, tw); - break; case snd_soc_dapm_aif_out: - ret = sof_widget_load_pcm(scomp, index, swidget, - SOF_IPC_STREAM_CAPTURE, tw); - break; case snd_soc_dapm_aif_in: - ret = sof_widget_load_pcm(scomp, index, swidget, - SOF_IPC_STREAM_PLAYBACK, tw); - break; case snd_soc_dapm_src: - ret = sof_widget_load_src(scomp, index, swidget, tw); - break; case snd_soc_dapm_asrc: - ret = sof_widget_load_asrc(scomp, index, swidget, tw); - break; case snd_soc_dapm_siggen: - ret = sof_widget_load_siggen(scomp, index, swidget, tw); - break; - case snd_soc_dapm_effect: - ret = sof_widget_load_process(scomp, index, swidget, tw); - break; case snd_soc_dapm_mux: case snd_soc_dapm_demux: - ret = sof_widget_load_mux(scomp, index, swidget, tw); + ret = sof_widget_parse_tokens(scomp, swidget, tw, token_list, token_list_size); break; case snd_soc_dapm_switch: case snd_soc_dapm_dai_link: @@ -2437,7 +1344,17 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index, break; } - /* check IPC reply */ + if (sof_debug_check_flag(SOF_DBG_DISABLE_MULTICORE)) { + swidget->core = SOF_DSP_PRIMARY_CORE; + } else { + int core = sof_get_token_value(SOF_TKN_COMP_CORE_ID, swidget->tuples, + swidget->num_tuples); + + if (core >= 0) + swidget->core = core; + } + + /* check token parsing reply */ if (ret < 0) { dev_err(scomp->dev, "error: failed to add widget id %d type %d name : %s stream %s\n", @@ -2450,13 +1367,17 @@ static int sof_widget_ready(struct snd_soc_component *scomp, int index, /* bind widget to external event */ if (tw->event_type) { - ret = sof_widget_bind_event(scomp, swidget, - le16_to_cpu(tw->event_type)); - if (ret) { - dev_err(scomp->dev, "error: widget event binding failed\n"); - kfree(swidget->private); - kfree(swidget); - return ret; + if (widget_ops[w->id].bind_event) { + ret = widget_ops[w->id].bind_event(scomp, swidget, + le16_to_cpu(tw->event_type)); + if (ret) { + dev_err(scomp->dev, "widget event binding failed for %s\n", + swidget->widget->name); + kfree(swidget->private); + kfree(swidget->tuples); + kfree(swidget); + return ret; + } } } @@ -2486,16 +1407,16 @@ static int sof_widget_unload(struct snd_soc_component *scomp, struct snd_soc_dobj *dobj) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg; + const struct sof_ipc_tplg_widget_ops *widget_ops = ipc_tplg_ops->widget; const struct snd_kcontrol_new *kc; struct snd_soc_dapm_widget *widget; - struct sof_ipc_pipe_new *pipeline; struct snd_sof_control *scontrol; struct snd_sof_widget *swidget; struct soc_mixer_control *sm; struct soc_bytes_ext *sbe; struct snd_sof_dai *dai; struct soc_enum *se; - int ret = 0; int i; swidget = dobj->private; @@ -2509,29 +1430,11 @@ static int sof_widget_unload(struct snd_soc_component *scomp, case snd_soc_dapm_dai_out: dai = swidget->private; - if (dai) { - /* free dai config */ - kfree(dai->dai_config); + if (dai) list_del(&dai->list); - } - break; - case snd_soc_dapm_scheduler: - - /* power down the pipeline schedule core */ - pipeline = swidget->private; - /* - * 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; + sof_disconnect_dai_widget(scomp, widget); - ret = snd_sof_dsp_core_power_down(sdev, 1 << pipeline->core); - if (ret < 0) - dev_err(scomp->dev, "error: powering down pipeline schedule core %d\n", - pipeline->core); break; default: break; @@ -2557,20 +1460,24 @@ static int sof_widget_unload(struct snd_soc_component *scomp, dev_warn(scomp->dev, "unsupported kcontrol_type\n"); goto out; } - kfree(scontrol->control_data); + kfree(scontrol->ipc_control_data); list_del(&scontrol->list); + kfree(scontrol->name); kfree(scontrol); } out: - /* free private value */ - kfree(swidget->private); + /* free IPC related data */ + if (widget_ops[swidget->id].ipc_free) + widget_ops[swidget->id].ipc_free(swidget); + + kfree(swidget->tuples); /* remove and free swidget object */ list_del(&swidget->list); kfree(swidget); - return ret; + return 0; } /* @@ -2628,9 +1535,6 @@ static int sof_dai_load(struct snd_soc_component *scomp, int index, stream = SNDRV_PCM_STREAM_PLAYBACK; - dev_vdbg(scomp->dev, "tplg: pcm %s stream tokens: playback d0i3:%d\n", - spcm->pcm.pcm_name, spcm->stream[stream].d0i3_compatible); - caps = &spcm->pcm.caps[stream]; /* allocate playback page table buffer */ @@ -2658,9 +1562,6 @@ capture: if (!spcm->pcm.capture) return ret; - dev_vdbg(scomp->dev, "tplg: pcm %s stream tokens: capture d0i3:%d\n", - spcm->pcm.pcm_name, spcm->stream[stream].d0i3_compatible); - caps = &spcm->pcm.caps[stream]; /* allocate capture page table buffer */ @@ -2709,464 +1610,23 @@ static int sof_dai_unload(struct snd_soc_component *scomp, return 0; } -static void sof_dai_set_format(struct snd_soc_tplg_hw_config *hw_config, - struct sof_ipc_dai_config *config) -{ - /* clock directions wrt codec */ - if (hw_config->bclk_provider == SND_SOC_TPLG_BCLK_CP) { - /* codec is bclk provider */ - if (hw_config->fsync_provider == SND_SOC_TPLG_FSYNC_CP) - config->format |= SOF_DAI_FMT_CBP_CFP; - else - config->format |= SOF_DAI_FMT_CBP_CFC; - } else { - /* codec is bclk consumer */ - if (hw_config->fsync_provider == SND_SOC_TPLG_FSYNC_CP) - config->format |= SOF_DAI_FMT_CBC_CFP; - else - config->format |= SOF_DAI_FMT_CBC_CFC; - } - - /* inverted clocks ? */ - if (hw_config->invert_bclk) { - if (hw_config->invert_fsync) - config->format |= SOF_DAI_FMT_IB_IF; - else - config->format |= SOF_DAI_FMT_IB_NF; - } else { - if (hw_config->invert_fsync) - config->format |= SOF_DAI_FMT_NB_IF; - else - config->format |= SOF_DAI_FMT_NB_NF; - } -} - -/* - * Send IPC and set the same config for all DAIs with name matching the link - * name. Note that the function can only be used for the case that all DAIs - * have a common DAI config for now. - */ -static int sof_set_dai_config_multi(struct snd_sof_dev *sdev, u32 size, - struct snd_soc_dai_link *link, - struct sof_ipc_dai_config *config, - int num_conf, int curr_conf) -{ - struct snd_sof_dai *dai; - int found = 0; - int i; - - list_for_each_entry(dai, &sdev->dai_list, list) { - if (!dai->name) - continue; - - if (strcmp(link->name, dai->name) == 0) { - /* - * 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. - */ - for (i = 0; i < num_conf; i++) - config[i].dai_index = dai->comp_dai.dai_index; - - dev_dbg(sdev->dev, "set DAI config for %s index %d\n", - dai->name, config[curr_conf].dai_index); - - dai->number_configs = num_conf; - dai->current_config = curr_conf; - dai->dai_config = kmemdup(config, size * num_conf, GFP_KERNEL); - if (!dai->dai_config) - return -ENOMEM; - - found = 1; - } - } - - /* - * machine driver may define a dai link with playback and capture - * dai enabled, but the dai link in topology would support both, one - * or none of them. Here print a warning message to notify user - */ - if (!found) { - dev_warn(sdev->dev, "warning: failed to find dai for dai link %s", - link->name); - } - - return 0; -} - -static int sof_set_dai_config(struct snd_sof_dev *sdev, u32 size, - struct snd_soc_dai_link *link, - struct sof_ipc_dai_config *config) -{ - return sof_set_dai_config_multi(sdev, size, link, config, 1, 0); -} - -static int sof_link_ssp_load(struct snd_soc_component *scomp, int index, - struct snd_soc_dai_link *link, - struct snd_soc_tplg_link_config *cfg, - struct snd_soc_tplg_hw_config *hw_config, - struct sof_ipc_dai_config *config, int curr_conf) -{ - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - struct snd_soc_tplg_private *private = &cfg->priv; - int num_conf = le32_to_cpu(cfg->num_hw_configs); - u32 size = sizeof(*config); - int ret; - int i; - - /* - * Parse common data, we should have 1 common data per hw_config. - */ - ret = sof_parse_token_sets(scomp, &config->ssp, ssp_tokens, - ARRAY_SIZE(ssp_tokens), private->array, - le32_to_cpu(private->size), - num_conf, size); - - if (ret != 0) { - dev_err(scomp->dev, "error: parse ssp tokens failed %d\n", - le32_to_cpu(private->size)); - return ret; - } - - /* process all possible hw configs */ - for (i = 0; i < num_conf; i++) { - - /* handle master/slave and inverted clocks */ - sof_dai_set_format(&hw_config[i], &config[i]); - - config[i].hdr.size = size; - - /* copy differentiating hw configs to ipc structs */ - config[i].ssp.mclk_rate = le32_to_cpu(hw_config[i].mclk_rate); - config[i].ssp.bclk_rate = le32_to_cpu(hw_config[i].bclk_rate); - config[i].ssp.fsync_rate = le32_to_cpu(hw_config[i].fsync_rate); - config[i].ssp.tdm_slots = le32_to_cpu(hw_config[i].tdm_slots); - config[i].ssp.tdm_slot_width = le32_to_cpu(hw_config[i].tdm_slot_width); - config[i].ssp.mclk_direction = hw_config[i].mclk_direction; - config[i].ssp.rx_slots = le32_to_cpu(hw_config[i].rx_slots); - config[i].ssp.tx_slots = le32_to_cpu(hw_config[i].tx_slots); - - dev_dbg(scomp->dev, "tplg: config SSP%d fmt %#x mclk %d bclk %d fclk %d width (%d)%d slots %d mclk id %d quirks %d clks_control %#x\n", - config[i].dai_index, config[i].format, - config[i].ssp.mclk_rate, config[i].ssp.bclk_rate, - config[i].ssp.fsync_rate, config[i].ssp.sample_valid_bits, - config[i].ssp.tdm_slot_width, config[i].ssp.tdm_slots, - config[i].ssp.mclk_id, config[i].ssp.quirks, config[i].ssp.clks_control); - - /* validate SSP fsync rate and channel count */ - if (config[i].ssp.fsync_rate < 8000 || config[i].ssp.fsync_rate > 192000) { - dev_err(scomp->dev, "error: invalid fsync rate for SSP%d\n", - config[i].dai_index); - return -EINVAL; - } - - if (config[i].ssp.tdm_slots < 1 || config[i].ssp.tdm_slots > 8) { - dev_err(scomp->dev, "error: invalid channel count for SSP%d\n", - config[i].dai_index); - return -EINVAL; - } - } - - /* set config for all DAI's with name matching the link name */ - ret = sof_set_dai_config_multi(sdev, size, link, config, num_conf, curr_conf); - if (ret < 0) - dev_err(scomp->dev, "error: failed to save DAI config for SSP%d\n", - config->dai_index); - - return ret; -} - -static int sof_link_sai_load(struct snd_soc_component *scomp, int index, - struct snd_soc_dai_link *link, - struct snd_soc_tplg_link_config *cfg, - struct snd_soc_tplg_hw_config *hw_config, - struct sof_ipc_dai_config *config) -{ - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - struct snd_soc_tplg_private *private = &cfg->priv; - u32 size = sizeof(*config); - int ret; - - /* handle master/slave and inverted clocks */ - sof_dai_set_format(hw_config, config); - - /* init IPC */ - memset(&config->sai, 0, sizeof(struct sof_ipc_dai_sai_params)); - config->hdr.size = size; - - ret = sof_parse_tokens(scomp, &config->sai, sai_tokens, - ARRAY_SIZE(sai_tokens), private->array, - le32_to_cpu(private->size)); - if (ret != 0) { - dev_err(scomp->dev, "error: parse sai tokens failed %d\n", - le32_to_cpu(private->size)); - return ret; - } - - config->sai.mclk_rate = le32_to_cpu(hw_config->mclk_rate); - config->sai.bclk_rate = le32_to_cpu(hw_config->bclk_rate); - config->sai.fsync_rate = le32_to_cpu(hw_config->fsync_rate); - config->sai.mclk_direction = hw_config->mclk_direction; - - config->sai.tdm_slots = le32_to_cpu(hw_config->tdm_slots); - config->sai.tdm_slot_width = le32_to_cpu(hw_config->tdm_slot_width); - config->sai.rx_slots = le32_to_cpu(hw_config->rx_slots); - config->sai.tx_slots = le32_to_cpu(hw_config->tx_slots); - - dev_info(scomp->dev, - "tplg: config SAI%d fmt 0x%x mclk %d width %d slots %d mclk id %d\n", - config->dai_index, config->format, - config->sai.mclk_rate, config->sai.tdm_slot_width, - config->sai.tdm_slots, config->sai.mclk_id); - - if (config->sai.tdm_slots < 1 || config->sai.tdm_slots > 8) { - dev_err(scomp->dev, "error: invalid channel count for SAI%d\n", - config->dai_index); - return -EINVAL; - } - - /* set config for all DAI's with name matching the link name */ - ret = sof_set_dai_config(sdev, size, link, config); - if (ret < 0) - dev_err(scomp->dev, "error: failed to save DAI config for SAI%d\n", - config->dai_index); - - return ret; -} - -static int sof_link_esai_load(struct snd_soc_component *scomp, int index, - struct snd_soc_dai_link *link, - struct snd_soc_tplg_link_config *cfg, - struct snd_soc_tplg_hw_config *hw_config, - struct sof_ipc_dai_config *config) -{ - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - struct snd_soc_tplg_private *private = &cfg->priv; - u32 size = sizeof(*config); - int ret; - - /* handle master/slave and inverted clocks */ - sof_dai_set_format(hw_config, config); - - /* init IPC */ - memset(&config->esai, 0, sizeof(struct sof_ipc_dai_esai_params)); - config->hdr.size = size; - - ret = sof_parse_tokens(scomp, &config->esai, esai_tokens, - ARRAY_SIZE(esai_tokens), private->array, - le32_to_cpu(private->size)); - if (ret != 0) { - dev_err(scomp->dev, "error: parse esai tokens failed %d\n", - le32_to_cpu(private->size)); - return ret; - } - - config->esai.mclk_rate = le32_to_cpu(hw_config->mclk_rate); - config->esai.bclk_rate = le32_to_cpu(hw_config->bclk_rate); - config->esai.fsync_rate = le32_to_cpu(hw_config->fsync_rate); - config->esai.mclk_direction = hw_config->mclk_direction; - config->esai.tdm_slots = le32_to_cpu(hw_config->tdm_slots); - config->esai.tdm_slot_width = le32_to_cpu(hw_config->tdm_slot_width); - config->esai.rx_slots = le32_to_cpu(hw_config->rx_slots); - config->esai.tx_slots = le32_to_cpu(hw_config->tx_slots); - - dev_info(scomp->dev, - "tplg: config ESAI%d fmt 0x%x mclk %d width %d slots %d mclk id %d\n", - config->dai_index, config->format, - config->esai.mclk_rate, config->esai.tdm_slot_width, - config->esai.tdm_slots, config->esai.mclk_id); - - if (config->esai.tdm_slots < 1 || config->esai.tdm_slots > 8) { - dev_err(scomp->dev, "error: invalid channel count for ESAI%d\n", - config->dai_index); - return -EINVAL; - } - - /* set config for all DAI's with name matching the link name */ - ret = sof_set_dai_config(sdev, size, link, config); - if (ret < 0) - dev_err(scomp->dev, "error: failed to save DAI config for ESAI%d\n", - config->dai_index); - - return ret; -} - -static int sof_link_dmic_load(struct snd_soc_component *scomp, int index, - struct snd_soc_dai_link *link, - struct snd_soc_tplg_link_config *cfg, - struct snd_soc_tplg_hw_config *hw_config, - struct sof_ipc_dai_config *config) -{ - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - struct snd_soc_tplg_private *private = &cfg->priv; - struct sof_ipc_fw_ready *ready = &sdev->fw_ready; - struct sof_ipc_fw_version *v = &ready->version; - size_t size = sizeof(*config); - int ret, j; - - /* Ensure the entire DMIC config struct is zeros */ - memset(&config->dmic, 0, sizeof(struct sof_ipc_dai_dmic_params)); - - /* get DMIC tokens */ - ret = sof_parse_tokens(scomp, &config->dmic, dmic_tokens, - ARRAY_SIZE(dmic_tokens), private->array, - le32_to_cpu(private->size)); - if (ret != 0) { - dev_err(scomp->dev, "error: parse dmic tokens failed %d\n", - le32_to_cpu(private->size)); - return ret; - } - - /* get DMIC PDM tokens */ - ret = sof_parse_token_sets(scomp, &config->dmic.pdm[0], dmic_pdm_tokens, - ARRAY_SIZE(dmic_pdm_tokens), private->array, - le32_to_cpu(private->size), - config->dmic.num_pdm_active, - sizeof(struct sof_ipc_dai_dmic_pdm_ctrl)); - - if (ret != 0) { - dev_err(scomp->dev, "error: parse dmic pdm tokens failed %d\n", - le32_to_cpu(private->size)); - return ret; - } - - /* set IPC header size */ - config->hdr.size = size; - - /* debug messages */ - dev_dbg(scomp->dev, "tplg: config DMIC%d driver version %d\n", - config->dai_index, config->dmic.driver_ipc_version); - dev_dbg(scomp->dev, "pdmclk_min %d pdm_clkmax %d duty_min %hd\n", - config->dmic.pdmclk_min, config->dmic.pdmclk_max, - config->dmic.duty_min); - dev_dbg(scomp->dev, "duty_max %hd fifo_fs %d num_pdms active %d\n", - config->dmic.duty_max, config->dmic.fifo_fs, - config->dmic.num_pdm_active); - dev_dbg(scomp->dev, "fifo word length %hd\n", config->dmic.fifo_bits); - - for (j = 0; j < config->dmic.num_pdm_active; j++) { - dev_dbg(scomp->dev, "pdm %hd mic a %hd mic b %hd\n", - config->dmic.pdm[j].id, - config->dmic.pdm[j].enable_mic_a, - config->dmic.pdm[j].enable_mic_b); - dev_dbg(scomp->dev, "pdm %hd polarity a %hd polarity b %hd\n", - config->dmic.pdm[j].id, - config->dmic.pdm[j].polarity_mic_a, - config->dmic.pdm[j].polarity_mic_b); - dev_dbg(scomp->dev, "pdm %hd clk_edge %hd skew %hd\n", - config->dmic.pdm[j].id, - config->dmic.pdm[j].clk_edge, - config->dmic.pdm[j].skew); - } - - /* - * this takes care of backwards compatible handling of fifo_bits_b. - * It is deprecated since firmware ABI version 3.0.1. - */ - if (SOF_ABI_VER(v->major, v->minor, v->micro) < SOF_ABI_VER(3, 0, 1)) - config->dmic.fifo_bits_b = config->dmic.fifo_bits; - - /* set config for all DAI's with name matching the link name */ - ret = sof_set_dai_config(sdev, size, link, config); - if (ret < 0) - dev_err(scomp->dev, "error: failed to save DAI config for DMIC%d\n", - config->dai_index); - - return ret; -} - -static int sof_link_hda_load(struct snd_soc_component *scomp, int index, - struct snd_soc_dai_link *link, - struct snd_soc_tplg_link_config *cfg, - struct snd_soc_tplg_hw_config *hw_config, - struct sof_ipc_dai_config *config) -{ - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - struct snd_soc_tplg_private *private = &cfg->priv; - struct snd_soc_dai *dai; - u32 size = sizeof(*config); - int ret; - - /* init IPC */ - memset(&config->hda, 0, sizeof(struct sof_ipc_dai_hda_params)); - config->hdr.size = size; - - /* get any bespoke DAI tokens */ - ret = sof_parse_tokens(scomp, &config->hda, hda_tokens, - ARRAY_SIZE(hda_tokens), private->array, - le32_to_cpu(private->size)); - if (ret != 0) { - dev_err(scomp->dev, "error: parse hda tokens failed %d\n", - le32_to_cpu(private->size)); - return ret; - } - - dev_dbg(scomp->dev, "HDA config rate %d channels %d\n", - config->hda.rate, config->hda.channels); - - dai = snd_soc_find_dai(link->cpus); - if (!dai) { - dev_err(scomp->dev, "error: failed to find dai %s in %s", - link->cpus->dai_name, __func__); - return -EINVAL; - } - - config->hda.link_dma_ch = DMA_CHAN_INVALID; - - ret = sof_set_dai_config(sdev, size, link, config); - if (ret < 0) - dev_err(scomp->dev, "error: failed to process hda dai link %s", - link->name); - - return ret; -} - -static int sof_link_alh_load(struct snd_soc_component *scomp, int index, - struct snd_soc_dai_link *link, - struct snd_soc_tplg_link_config *cfg, - struct snd_soc_tplg_hw_config *hw_config, - struct sof_ipc_dai_config *config) -{ - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - struct snd_soc_tplg_private *private = &cfg->priv; - u32 size = sizeof(*config); - int ret; - - ret = sof_parse_tokens(scomp, &config->alh, alh_tokens, - ARRAY_SIZE(alh_tokens), private->array, - le32_to_cpu(private->size)); - if (ret != 0) { - dev_err(scomp->dev, "error: parse alh tokens failed %d\n", - le32_to_cpu(private->size)); - return ret; - } - - /* init IPC */ - config->hdr.size = size; - - /* set config for all DAI's with name matching the link name */ - ret = sof_set_dai_config(sdev, size, link, config); - if (ret < 0) - dev_err(scomp->dev, "error: failed to save DAI config for ALH %d\n", - config->dai_index); - - return ret; -} +static const struct sof_topology_token common_dai_link_tokens[] = { + {SOF_TKN_DAI_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_dai_type, + offsetof(struct snd_sof_dai_link, type)}, +}; /* DAI link - used for any driver specific init */ -static int sof_link_load(struct snd_soc_component *scomp, int index, - struct snd_soc_dai_link *link, +static int sof_link_load(struct snd_soc_component *scomp, int index, struct snd_soc_dai_link *link, struct snd_soc_tplg_link_config *cfg) { + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg; + const struct sof_token_info *token_list = ipc_tplg_ops->token_list; struct snd_soc_tplg_private *private = &cfg->priv; - struct snd_soc_tplg_hw_config *hw_config; - struct sof_ipc_dai_config common_config; - struct sof_ipc_dai_config *config; - int curr_conf; - int num_conf; - int ret; - int i; + struct snd_sof_dai_link *slink; + u32 token_id = 0; + int num_tuples = 0; + int ret, num_sets; if (!link->platforms) { dev_err(scomp->dev, "error: no platforms\n"); @@ -3203,97 +1663,169 @@ static int sof_link_load(struct snd_soc_component *scomp, int index, return -EINVAL; } - memset(&common_config, 0, sizeof(common_config)); + slink = kzalloc(sizeof(*slink), GFP_KERNEL); + if (!slink) + return -ENOMEM; - /* get any common DAI tokens */ - ret = sof_parse_tokens(scomp, &common_config, dai_link_tokens, ARRAY_SIZE(dai_link_tokens), - private->array, le32_to_cpu(private->size)); - if (ret != 0) { - dev_err(scomp->dev, "error: parse link tokens failed %d\n", - le32_to_cpu(private->size)); - return ret; + slink->num_hw_configs = le32_to_cpu(cfg->num_hw_configs); + slink->hw_configs = kmemdup(cfg->hw_config, + sizeof(*slink->hw_configs) * slink->num_hw_configs, + GFP_KERNEL); + if (!slink->hw_configs) { + kfree(slink); + return -ENOMEM; } - /* - * DAI links are expected to have at least 1 hw_config. - * But some older topologies might have no hw_config for HDA dai links. - */ - hw_config = cfg->hw_config; - num_conf = le32_to_cpu(cfg->num_hw_configs); - if (!num_conf) { - if (common_config.type != SOF_DAI_INTEL_HDA) { - dev_err(scomp->dev, "error: unexpected DAI config count %d!\n", - le32_to_cpu(cfg->num_hw_configs)); - return -EINVAL; - } - num_conf = 1; - curr_conf = 0; - } else { - dev_dbg(scomp->dev, "tplg: %d hw_configs found, default id: %d!\n", - cfg->num_hw_configs, le32_to_cpu(cfg->default_hw_config_id)); + slink->default_hw_cfg_id = le32_to_cpu(cfg->default_hw_config_id); + slink->link = link; - for (curr_conf = 0; curr_conf < num_conf; curr_conf++) { - if (hw_config[curr_conf].id == cfg->default_hw_config_id) - break; - } + dev_dbg(scomp->dev, "tplg: %d hw_configs found, default id: %d for dai link %s!\n", + slink->num_hw_configs, slink->default_hw_cfg_id, link->name); - if (curr_conf == num_conf) { - dev_err(scomp->dev, "error: default hw_config id: %d not found!\n", - le32_to_cpu(cfg->default_hw_config_id)); - return -EINVAL; - } + ret = sof_parse_tokens(scomp, slink, common_dai_link_tokens, + ARRAY_SIZE(common_dai_link_tokens), + private->array, le32_to_cpu(private->size)); + if (ret < 0) { + dev_err(scomp->dev, "Failed tp parse common DAI link tokens\n"); + kfree(slink->hw_configs); + kfree(slink); + return ret; } - /* Reserve memory for all hw configs, eventually freed by widget */ - config = kcalloc(num_conf, sizeof(*config), GFP_KERNEL); - if (!config) - return -ENOMEM; - - /* Copy common data to all config ipc structs */ - for (i = 0; i < num_conf; i++) { - config[i].hdr.cmd = SOF_IPC_GLB_DAI_MSG | SOF_IPC_DAI_CONFIG; - config[i].format = le32_to_cpu(hw_config[i].fmt); - config[i].type = common_config.type; - config[i].dai_index = common_config.dai_index; - } + if (!token_list) + goto out; - /* now load DAI specific data and send IPC - type comes from token */ - switch (common_config.type) { + /* calculate size of tuples array */ + num_tuples += token_list[SOF_DAI_LINK_TOKENS].count; + num_sets = slink->num_hw_configs; + switch (slink->type) { case SOF_DAI_INTEL_SSP: - ret = sof_link_ssp_load(scomp, index, link, cfg, hw_config, config, curr_conf); + token_id = SOF_SSP_TOKENS; + num_tuples += token_list[SOF_SSP_TOKENS].count * slink->num_hw_configs; break; case SOF_DAI_INTEL_DMIC: - ret = sof_link_dmic_load(scomp, index, link, cfg, hw_config + curr_conf, config); + token_id = SOF_DMIC_TOKENS; + num_tuples += token_list[SOF_DMIC_TOKENS].count; + + /* Allocate memory for max PDM controllers */ + num_tuples += token_list[SOF_DMIC_PDM_TOKENS].count * SOF_DAI_INTEL_DMIC_NUM_CTRL; break; case SOF_DAI_INTEL_HDA: - ret = sof_link_hda_load(scomp, index, link, cfg, hw_config + curr_conf, config); + token_id = SOF_HDA_TOKENS; + num_tuples += token_list[SOF_HDA_TOKENS].count; break; case SOF_DAI_INTEL_ALH: - ret = sof_link_alh_load(scomp, index, link, cfg, hw_config + curr_conf, config); + token_id = SOF_ALH_TOKENS; + num_tuples += token_list[SOF_ALH_TOKENS].count; break; case SOF_DAI_IMX_SAI: - ret = sof_link_sai_load(scomp, index, link, cfg, hw_config + curr_conf, config); + token_id = SOF_SAI_TOKENS; + num_tuples += token_list[SOF_SAI_TOKENS].count; break; case SOF_DAI_IMX_ESAI: - ret = sof_link_esai_load(scomp, index, link, cfg, hw_config + curr_conf, config); + token_id = SOF_ESAI_TOKENS; + num_tuples += token_list[SOF_ESAI_TOKENS].count; + break; + case SOF_DAI_MEDIATEK_AFE: + token_id = SOF_AFE_TOKENS; + num_tuples += token_list[SOF_AFE_TOKENS].count; + break; + case SOF_DAI_AMD_DMIC: + token_id = SOF_ACPDMIC_TOKENS; + num_tuples += token_list[SOF_ACPDMIC_TOKENS].count; break; default: - dev_err(scomp->dev, "error: invalid DAI type %d\n", common_config.type); - ret = -EINVAL; break; } - kfree(config); + /* allocate memory for tuples array */ + slink->tuples = kcalloc(num_tuples, sizeof(*slink->tuples), GFP_KERNEL); + if (!slink->tuples) { + kfree(slink->hw_configs); + kfree(slink); + return -ENOMEM; + } + + if (token_list[SOF_DAI_LINK_TOKENS].tokens) { + /* parse one set of DAI link tokens */ + ret = sof_copy_tuples(sdev, private->array, le32_to_cpu(private->size), + SOF_DAI_LINK_TOKENS, 1, slink->tuples, + num_tuples, &slink->num_tuples); + if (ret < 0) { + dev_err(scomp->dev, "failed to parse %s for dai link %s\n", + token_list[SOF_DAI_LINK_TOKENS].name, link->name); + goto err; + } + } + + /* nothing more to do if there are no DAI type-specific tokens defined */ + if (!token_id || !token_list[token_id].tokens) + goto out; + + /* parse "num_sets" sets of DAI-specific tokens */ + ret = sof_copy_tuples(sdev, private->array, le32_to_cpu(private->size), + token_id, num_sets, slink->tuples, num_tuples, &slink->num_tuples); + if (ret < 0) { + dev_err(scomp->dev, "failed to parse %s for dai link %s\n", + token_list[token_id].name, link->name); + goto err; + } + + /* for DMIC, also parse all sets of DMIC PDM tokens based on active PDM count */ + if (token_id == SOF_DMIC_TOKENS) { + num_sets = sof_get_token_value(SOF_TKN_INTEL_DMIC_NUM_PDM_ACTIVE, + slink->tuples, slink->num_tuples); + + if (num_sets < 0) { + dev_err(sdev->dev, "Invalid active PDM count for %s\n", link->name); + ret = num_sets; + goto err; + } + + ret = sof_copy_tuples(sdev, private->array, le32_to_cpu(private->size), + SOF_DMIC_PDM_TOKENS, num_sets, slink->tuples, + num_tuples, &slink->num_tuples); + if (ret < 0) { + dev_err(scomp->dev, "failed to parse %s for dai link %s\n", + token_list[SOF_DMIC_PDM_TOKENS].name, link->name); + goto err; + } + } +out: + link->dobj.private = slink; + list_add(&slink->list, &sdev->dai_link_list); + + return 0; + +err: + kfree(slink->tuples); + kfree(slink->hw_configs); + kfree(slink); return ret; } +static int sof_link_unload(struct snd_soc_component *scomp, struct snd_soc_dobj *dobj) +{ + struct snd_sof_dai_link *slink = dobj->private; + + if (!slink) + return 0; + + kfree(slink->tuples); + list_del(&slink->list); + kfree(slink->hw_configs); + kfree(slink); + dobj->private = NULL; + + return 0; +} + /* DAI link - used for any driver specific init */ static int sof_route_load(struct snd_soc_component *scomp, int index, struct snd_soc_dapm_route *route) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - struct sof_ipc_pipe_comp_connect *connect; struct snd_sof_widget *source_swidget, *sink_swidget; struct snd_soc_dobj *dobj = &route->dobj; struct snd_sof_route *sroute; @@ -3305,16 +1837,6 @@ static int sof_route_load(struct snd_soc_component *scomp, int index, return -ENOMEM; sroute->scomp = scomp; - - connect = kzalloc(sizeof(*connect), GFP_KERNEL); - if (!connect) { - kfree(sroute); - return -ENOMEM; - } - - connect->hdr.size = sizeof(*connect); - connect->hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_CONNECT; - dev_dbg(scomp->dev, "sink %s control %s source %s\n", route->sink, route->control ? route->control : "none", route->source); @@ -3338,8 +1860,6 @@ static int sof_route_load(struct snd_soc_component *scomp, int index, source_swidget->id == snd_soc_dapm_output) goto err; - connect->source_id = source_swidget->comp_id; - /* sink component */ sink_swidget = snd_sof_find_swidget(scomp, (char *)route->sink); if (!sink_swidget) { @@ -3357,61 +1877,20 @@ static int sof_route_load(struct snd_soc_component *scomp, int index, sink_swidget->id == snd_soc_dapm_output) goto err; - connect->sink_id = sink_swidget->comp_id; - - /* - * For virtual routes, both sink and source are not - * buffer. Since only buffer linked to component is supported by - * FW, others are reported as error, add check in route function, - * do not send it to FW when both source and sink are not buffer - */ - if (source_swidget->id != snd_soc_dapm_buffer && - sink_swidget->id != snd_soc_dapm_buffer) { - dev_dbg(scomp->dev, "warning: neither Linked source component %s nor sink component %s is of buffer type, ignoring link\n", - route->source, route->sink); - goto err; - } else { - sroute->route = route; - dobj->private = sroute; - sroute->private = connect; - sroute->src_widget = source_swidget; - sroute->sink_widget = sink_swidget; + sroute->route = route; + dobj->private = sroute; + sroute->src_widget = source_swidget; + sroute->sink_widget = sink_swidget; - /* add route to route list */ - list_add(&sroute->list, &sdev->route_list); - - return 0; - } + /* add route to route list */ + list_add(&sroute->list, &sdev->route_list); + return 0; err: - kfree(connect); kfree(sroute); return ret; } -int snd_sof_complete_pipeline(struct snd_sof_dev *sdev, - struct snd_sof_widget *swidget) -{ - struct sof_ipc_pipe_ready ready; - struct sof_ipc_reply reply; - int ret; - - dev_dbg(sdev->dev, "tplg: complete pipeline %s id %d\n", - swidget->widget->name, swidget->comp_id); - - memset(&ready, 0, sizeof(ready)); - ready.hdr.size = sizeof(ready); - ready.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_PIPE_COMPLETE; - ready.comp_id = swidget->comp_id; - - ret = sof_ipc_tx_message(sdev->ipc, - ready.hdr.cmd, &ready, sizeof(ready), &reply, - sizeof(reply)); - if (ret < 0) - return ret; - return 1; -} - /** * sof_set_pipe_widget - Set pipe_widget for a component * @sdev: pointer to struct snd_sof_dev @@ -3451,8 +1930,38 @@ static int sof_complete(struct snd_soc_component *scomp) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct snd_sof_widget *swidget, *comp_swidget; + const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg; + const struct sof_ipc_tplg_widget_ops *widget_ops = ipc_tplg_ops->widget; + struct snd_sof_control *scontrol; int ret; + /* first update all control IPC structures based on the IPC version */ + if (ipc_tplg_ops->control_setup) + list_for_each_entry(scontrol, &sdev->kcontrol_list, list) { + ret = ipc_tplg_ops->control_setup(sdev, scontrol); + if (ret < 0) { + dev_err(sdev->dev, "failed updating IPC struct for control %s\n", + scontrol->name); + return ret; + } + } + + /* + * then update all widget IPC structures. If any of the ipc_setup callbacks fail, the + * topology will be removed and all widgets will be unloaded resulting in freeing all + * associated memories. + */ + list_for_each_entry(swidget, &sdev->widget_list, list) { + if (widget_ops[swidget->id].ipc_setup) { + ret = widget_ops[swidget->id].ipc_setup(swidget); + if (ret < 0) { + dev_err(sdev->dev, "failed updating IPC struct for %s\n", + swidget->widget->name); + return ret; + } + } + } + /* set the pipe_widget and apply the dynamic_pipeline_widget_flag */ list_for_each_entry(swidget, &sdev->widget_list, list) { switch (swidget->id) { @@ -3461,7 +1970,7 @@ static int sof_complete(struct snd_soc_component *scomp) * Apply the dynamic_pipeline_widget flag and set the pipe_widget field * for all widgets that have the same pipeline ID as the scheduler widget */ - list_for_each_entry_reverse(comp_swidget, &sdev->widget_list, list) + list_for_each_entry(comp_swidget, &sdev->widget_list, list) if (comp_swidget->pipeline_id == swidget->pipeline_id) { ret = sof_set_pipe_widget(sdev, swidget, comp_swidget); if (ret < 0) @@ -3474,67 +1983,40 @@ static int sof_complete(struct snd_soc_component *scomp) } /* verify topology components loading including dynamic pipelines */ - if (sof_core_debug & SOF_DBG_VERIFY_TPLG) { - ret = sof_set_up_pipelines(sdev, true); - if (ret < 0) { - dev_err(sdev->dev, "error: topology verification failed %d\n", ret); - return ret; - } + if (sof_debug_check_flag(SOF_DBG_VERIFY_TPLG)) { + if (ipc_tplg_ops->set_up_all_pipelines && ipc_tplg_ops->tear_down_all_pipelines) { + ret = ipc_tplg_ops->set_up_all_pipelines(sdev, true); + if (ret < 0) { + dev_err(sdev->dev, "Failed to set up all topology pipelines: %d\n", + ret); + return ret; + } - ret = sof_tear_down_pipelines(sdev, true); - if (ret < 0) { - dev_err(sdev->dev, "error: topology tear down pipelines failed %d\n", ret); - return ret; + ret = ipc_tplg_ops->tear_down_all_pipelines(sdev, true); + if (ret < 0) { + dev_err(sdev->dev, "Failed to tear down topology pipelines: %d\n", + ret); + return ret; + } } } /* set up static pipelines */ - return sof_set_up_pipelines(sdev, false); + if (ipc_tplg_ops->set_up_all_pipelines) + return ipc_tplg_ops->set_up_all_pipelines(sdev, false); + + return 0; } /* manifest - optional to inform component of manifest */ static int sof_manifest(struct snd_soc_component *scomp, int index, struct snd_soc_tplg_manifest *man) { - u32 size; - u32 abi_version; - - size = le32_to_cpu(man->priv.size); - - /* backward compatible with tplg without ABI info */ - if (!size) { - dev_dbg(scomp->dev, "No topology ABI info\n"); - return 0; - } - - if (size != SOF_TPLG_ABI_SIZE) { - dev_err(scomp->dev, "error: invalid topology ABI size\n"); - return -EINVAL; - } - - dev_info(scomp->dev, - "Topology: ABI %d:%d:%d Kernel ABI %d:%d:%d\n", - man->priv.data[0], man->priv.data[1], - man->priv.data[2], SOF_ABI_MAJOR, SOF_ABI_MINOR, - SOF_ABI_PATCH); - - abi_version = SOF_ABI_VER(man->priv.data[0], - man->priv.data[1], - man->priv.data[2]); - - if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, abi_version)) { - dev_err(scomp->dev, "error: incompatible topology ABI version\n"); - return -EINVAL; - } + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg; - if (SOF_ABI_VERSION_MINOR(abi_version) > SOF_ABI_MINOR) { - if (!IS_ENABLED(CONFIG_SND_SOC_SOF_STRICT_ABI_CHECKS)) { - dev_warn(scomp->dev, "warn: topology ABI is more recent than kernel\n"); - } else { - dev_err(scomp->dev, "error: topology ABI is more recent than kernel\n"); - return -EINVAL; - } - } + if (ipc_tplg_ops->parse_manifest) + return ipc_tplg_ops->parse_manifest(scomp, index, man); return 0; } @@ -3573,6 +2055,7 @@ static struct snd_soc_tplg_ops sof_tplg_ops = { /* DAI link - used for any driver specific init */ .link_load = sof_link_load, + .link_unload = sof_link_unload, /* completion - called at completion of firmware loading */ .complete = sof_complete, @@ -3591,6 +2074,7 @@ static struct snd_soc_tplg_ops sof_tplg_ops = { int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) { + struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); const struct firmware *fw; int ret; @@ -3613,6 +2097,10 @@ int snd_sof_load_topology(struct snd_soc_component *scomp, const char *file) } release_firmware(fw); + + if (ret >= 0 && sdev->led_present) + ret = snd_ctl_led_request(); + return ret; } EXPORT_SYMBOL(snd_sof_load_topology); diff --git a/sound/soc/sof/trace.c b/sound/soc/sof/trace.c index e3afc3dac7d1..6f662642d611 100644 --- a/sound/soc/sof/trace.c +++ b/sound/soc/sof/trace.c @@ -1,576 +1,51 @@ -// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) -// -// This file is provided under a dual BSD/GPLv2 license. When using or -// redistributing this file, you may do so under either license. -// -// Copyright(c) 2018 Intel Corporation. All rights reserved. -// -// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> +// SPDX-License-Identifier: GPL-2.0-only // +// Copyright(c) 2022 Intel Corporation. All rights reserved. -#include <linux/debugfs.h> -#include <linux/sched/signal.h> #include "sof-priv.h" -#include "ops.h" - -#define TRACE_FILTER_ELEMENTS_PER_ENTRY 4 -#define TRACE_FILTER_MAX_CONFIG_STRING_LENGTH 1024 - -static int trace_filter_append_elem(struct snd_sof_dev *sdev, uint32_t key, uint32_t value, - struct sof_ipc_trace_filter_elem *elem_list, - int capacity, int *counter) -{ - if (*counter >= capacity) - return -ENOMEM; - - elem_list[*counter].key = key; - elem_list[*counter].value = value; - ++*counter; - - return 0; -} - -static int trace_filter_parse_entry(struct snd_sof_dev *sdev, const char *line, - struct sof_ipc_trace_filter_elem *elem, - int capacity, int *counter) -{ - int len = strlen(line); - int cnt = *counter; - uint32_t uuid_id; - int log_level; - int pipe_id; - int comp_id; - int read; - int ret; - - /* ignore empty content */ - ret = sscanf(line, " %n", &read); - if (!ret && read == len) - return len; - - ret = sscanf(line, " %d %x %d %d %n", &log_level, &uuid_id, &pipe_id, &comp_id, &read); - if (ret != TRACE_FILTER_ELEMENTS_PER_ENTRY || read != len) { - dev_err(sdev->dev, "error: invalid trace filter entry '%s'\n", line); - return -EINVAL; - } - - if (uuid_id > 0) { - ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_BY_UUID, - uuid_id, elem, capacity, &cnt); - if (ret) - return ret; - } - if (pipe_id >= 0) { - ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_BY_PIPE, - pipe_id, elem, capacity, &cnt); - if (ret) - return ret; - } - if (comp_id >= 0) { - ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_BY_COMP, - comp_id, elem, capacity, &cnt); - if (ret) - return ret; - } - - ret = trace_filter_append_elem(sdev, SOF_IPC_TRACE_FILTER_ELEM_SET_LEVEL | - SOF_IPC_TRACE_FILTER_ELEM_FIN, - log_level, elem, capacity, &cnt); - if (ret) - return ret; - - /* update counter only when parsing whole entry passed */ - *counter = cnt; - - return len; -} - -static int trace_filter_parse(struct snd_sof_dev *sdev, char *string, - int *out_elem_cnt, - struct sof_ipc_trace_filter_elem **out) -{ - static const char entry_delimiter[] = ";"; - char *entry = string; - int capacity = 0; - int entry_len; - int cnt = 0; - - /* - * Each entry contains at least 1, up to TRACE_FILTER_ELEMENTS_PER_ENTRY - * IPC elements, depending on content. Calculate IPC elements capacity - * for the input string where each element is set. - */ - while (entry) { - capacity += TRACE_FILTER_ELEMENTS_PER_ENTRY; - entry = strchr(entry + 1, entry_delimiter[0]); - } - *out = kmalloc(capacity * sizeof(**out), GFP_KERNEL); - if (!*out) - return -ENOMEM; - - /* split input string by ';', and parse each entry separately in trace_filter_parse_entry */ - while ((entry = strsep(&string, entry_delimiter))) { - entry_len = trace_filter_parse_entry(sdev, entry, *out, capacity, &cnt); - if (entry_len < 0) { - dev_err(sdev->dev, "error: %s failed for '%s', %d\n", __func__, entry, - entry_len); - return -EINVAL; - } - } - - *out_elem_cnt = cnt; - - return 0; -} - -static int sof_ipc_trace_update_filter(struct snd_sof_dev *sdev, int num_elems, - struct sof_ipc_trace_filter_elem *elems) -{ - struct sof_ipc_trace_filter *msg; - struct sof_ipc_reply reply; - size_t size; - int ret; - - size = struct_size(msg, elems, num_elems); - if (size > SOF_IPC_MSG_MAX_SIZE) - return -EINVAL; - - msg = kmalloc(size, GFP_KERNEL); - if (!msg) - return -ENOMEM; - - msg->hdr.size = size; - msg->hdr.cmd = SOF_IPC_GLB_TRACE_MSG | SOF_IPC_TRACE_FILTER_UPDATE; - msg->elem_cnt = num_elems; - memcpy(&msg->elems[0], elems, num_elems * sizeof(*elems)); - - ret = pm_runtime_get_sync(sdev->dev); - if (ret < 0 && ret != -EACCES) { - pm_runtime_put_noidle(sdev->dev); - dev_err(sdev->dev, "error: enabling device failed: %d\n", ret); - goto error; - } - ret = sof_ipc_tx_message(sdev->ipc, msg->hdr.cmd, msg, msg->hdr.size, - &reply, sizeof(reply)); - pm_runtime_mark_last_busy(sdev->dev); - pm_runtime_put_autosuspend(sdev->dev); - -error: - kfree(msg); - return ret ? ret : reply.error; -} - -static ssize_t sof_dfsentry_trace_filter_write(struct file *file, const char __user *from, - size_t count, loff_t *ppos) -{ - struct snd_sof_dfsentry *dfse = file->private_data; - struct sof_ipc_trace_filter_elem *elems = NULL; - struct snd_sof_dev *sdev = dfse->sdev; - loff_t pos = 0; - int num_elems; - char *string; - int ret; - - if (count > TRACE_FILTER_MAX_CONFIG_STRING_LENGTH) { - dev_err(sdev->dev, "%s too long input, %zu > %d\n", __func__, count, - TRACE_FILTER_MAX_CONFIG_STRING_LENGTH); - return -EINVAL; - } - - string = kmalloc(count + 1, GFP_KERNEL); - if (!string) - return -ENOMEM; - - /* assert null termination */ - string[count] = 0; - ret = simple_write_to_buffer(string, count, &pos, from, count); - if (ret < 0) - goto error; - - ret = trace_filter_parse(sdev, string, &num_elems, &elems); - if (ret < 0) { - dev_err(sdev->dev, "error: fail in trace_filter_parse, %d\n", ret); - goto error; - } - - if (num_elems) { - ret = sof_ipc_trace_update_filter(sdev, num_elems, elems); - if (ret < 0) { - dev_err(sdev->dev, "error: fail in sof_ipc_trace_update_filter %d\n", ret); - goto error; - } - } - ret = count; -error: - kfree(string); - kfree(elems); - return ret; -} - -static const struct file_operations sof_dfs_trace_filter_fops = { - .open = simple_open, - .write = sof_dfsentry_trace_filter_write, - .llseek = default_llseek, -}; - -static int trace_debugfs_filter_create(struct snd_sof_dev *sdev) -{ - struct snd_sof_dfsentry *dfse; - - dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); - if (!dfse) - return -ENOMEM; - - dfse->sdev = sdev; - dfse->type = SOF_DFSENTRY_TYPE_BUF; - - debugfs_create_file("filter", 0200, sdev->debugfs_root, dfse, - &sof_dfs_trace_filter_fops); - /* add to dfsentry list */ - list_add(&dfse->list, &sdev->dfsentry_list); - - return 0; -} - -static size_t sof_trace_avail(struct snd_sof_dev *sdev, - loff_t pos, size_t buffer_size) -{ - loff_t host_offset = READ_ONCE(sdev->host_offset); - - /* - * If host offset is less than local pos, it means write pointer of - * host DMA buffer has been wrapped. We should output the trace data - * at the end of host DMA buffer at first. - */ - if (host_offset < pos) - return buffer_size - pos; - - /* If there is available trace data now, it is unnecessary to wait. */ - if (host_offset > pos) - return host_offset - pos; - - return 0; -} - -static size_t sof_wait_trace_avail(struct snd_sof_dev *sdev, - loff_t pos, size_t buffer_size) -{ - wait_queue_entry_t wait; - size_t ret = sof_trace_avail(sdev, pos, buffer_size); - - /* data immediately available */ - if (ret) - return ret; - - if (!sdev->dtrace_is_enabled && sdev->dtrace_draining) { - /* - * tracing has ended and all traces have been - * read by client, return EOF - */ - sdev->dtrace_draining = false; - return 0; - } - - /* wait for available trace data from FW */ - init_waitqueue_entry(&wait, current); - set_current_state(TASK_INTERRUPTIBLE); - add_wait_queue(&sdev->trace_sleep, &wait); - - if (!signal_pending(current)) { - /* set timeout to max value, no error code */ - schedule_timeout(MAX_SCHEDULE_TIMEOUT); - } - remove_wait_queue(&sdev->trace_sleep, &wait); - - return sof_trace_avail(sdev, pos, buffer_size); -} -static ssize_t sof_dfsentry_trace_read(struct file *file, char __user *buffer, - size_t count, loff_t *ppos) +int sof_fw_trace_init(struct snd_sof_dev *sdev) { - struct snd_sof_dfsentry *dfse = file->private_data; - struct snd_sof_dev *sdev = dfse->sdev; - unsigned long rem; - loff_t lpos = *ppos; - size_t avail, buffer_size = dfse->size; - u64 lpos_64; + if (!sdev->ipc->ops->fw_tracing) { + dev_info(sdev->dev, "Firmware tracing is not available\n"); + sdev->fw_trace_is_supported = false; - /* make sure we know about any failures on the DSP side */ - sdev->dtrace_error = false; - - /* check pos and count */ - if (lpos < 0) - return -EINVAL; - if (!count) return 0; - - /* check for buffer wrap and count overflow */ - lpos_64 = lpos; - lpos = do_div(lpos_64, buffer_size); - - if (count > buffer_size - lpos) /* min() not used to avoid sparse warnings */ - count = buffer_size - lpos; - - /* get available count based on current host offset */ - avail = sof_wait_trace_avail(sdev, lpos, buffer_size); - if (sdev->dtrace_error) { - dev_err(sdev->dev, "error: trace IO error\n"); - return -EIO; } - /* make sure count is <= avail */ - count = avail > count ? count : avail; - - /* copy available trace data to debugfs */ - rem = copy_to_user(buffer, ((u8 *)(dfse->buf) + lpos), count); - if (rem) - return -EFAULT; - - *ppos += count; - - /* move debugfs reading position */ - return count; + return sdev->ipc->ops->fw_tracing->init(sdev); } -static int sof_dfsentry_trace_release(struct inode *inode, struct file *file) +void sof_fw_trace_free(struct snd_sof_dev *sdev) { - struct snd_sof_dfsentry *dfse = inode->i_private; - struct snd_sof_dev *sdev = dfse->sdev; - - /* avoid duplicate traces at next open */ - if (!sdev->dtrace_is_enabled) - sdev->host_offset = 0; - - return 0; -} - -static const struct file_operations sof_dfs_trace_fops = { - .open = simple_open, - .read = sof_dfsentry_trace_read, - .llseek = default_llseek, - .release = sof_dfsentry_trace_release, -}; - -static int trace_debugfs_create(struct snd_sof_dev *sdev) -{ - struct snd_sof_dfsentry *dfse; - int ret; - - if (!sdev) - return -EINVAL; - - ret = trace_debugfs_filter_create(sdev); - if (ret < 0) - dev_err(sdev->dev, "error: fail in %s, %d", __func__, ret); - - dfse = devm_kzalloc(sdev->dev, sizeof(*dfse), GFP_KERNEL); - if (!dfse) - return -ENOMEM; - - dfse->type = SOF_DFSENTRY_TYPE_BUF; - dfse->buf = sdev->dmatb.area; - dfse->size = sdev->dmatb.bytes; - dfse->sdev = sdev; - - debugfs_create_file("trace", 0444, sdev->debugfs_root, dfse, - &sof_dfs_trace_fops); - - return 0; -} - -int snd_sof_init_trace_ipc(struct snd_sof_dev *sdev) -{ - struct sof_ipc_fw_ready *ready = &sdev->fw_ready; - struct sof_ipc_fw_version *v = &ready->version; - struct sof_ipc_dma_trace_params_ext params; - struct sof_ipc_reply ipc_reply; - int ret; - - if (!sdev->dtrace_is_supported) - return 0; - - if (sdev->dtrace_is_enabled || !sdev->dma_trace_pages) - return -EINVAL; - - /* set IPC parameters */ - params.hdr.cmd = SOF_IPC_GLB_TRACE_MSG; - /* PARAMS_EXT is only supported from ABI 3.7.0 onwards */ - if (v->abi_version >= SOF_ABI_VER(3, 7, 0)) { - params.hdr.size = sizeof(struct sof_ipc_dma_trace_params_ext); - params.hdr.cmd |= SOF_IPC_TRACE_DMA_PARAMS_EXT; - params.timestamp_ns = ktime_get(); /* in nanosecond */ - } else { - params.hdr.size = sizeof(struct sof_ipc_dma_trace_params); - params.hdr.cmd |= SOF_IPC_TRACE_DMA_PARAMS; - } - params.buffer.phy_addr = sdev->dmatp.addr; - params.buffer.size = sdev->dmatb.bytes; - params.buffer.pages = sdev->dma_trace_pages; - params.stream_tag = 0; - - sdev->host_offset = 0; - sdev->dtrace_draining = false; - - ret = snd_sof_dma_trace_init(sdev, ¶ms.stream_tag); - if (ret < 0) { - dev_err(sdev->dev, - "error: fail in snd_sof_dma_trace_init %d\n", ret); - return ret; - } - dev_dbg(sdev->dev, "%s: stream_tag: %d\n", __func__, params.stream_tag); - - /* send IPC to the DSP */ - ret = sof_ipc_tx_message(sdev->ipc, - params.hdr.cmd, ¶ms, sizeof(params), - &ipc_reply, sizeof(ipc_reply)); - if (ret < 0) { - dev_err(sdev->dev, - "error: can't set params for DMA for trace %d\n", ret); - goto trace_release; - } - - ret = snd_sof_dma_trace_trigger(sdev, SNDRV_PCM_TRIGGER_START); - if (ret < 0) { - dev_err(sdev->dev, - "error: snd_sof_dma_trace_trigger: start: %d\n", ret); - goto trace_release; - } - - sdev->dtrace_is_enabled = true; - - return 0; - -trace_release: - snd_sof_dma_trace_release(sdev); - return ret; -} - -int snd_sof_init_trace(struct snd_sof_dev *sdev) -{ - int ret; - - if (!sdev->dtrace_is_supported) - return 0; - - /* set false before start initialization */ - sdev->dtrace_is_enabled = false; - - /* allocate trace page table buffer */ - ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->dev, - PAGE_SIZE, &sdev->dmatp); - if (ret < 0) { - dev_err(sdev->dev, - "error: can't alloc page table for trace %d\n", ret); - return ret; - } - - /* allocate trace data buffer */ - ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV_SG, sdev->dev, - DMA_BUF_SIZE_FOR_TRACE, &sdev->dmatb); - if (ret < 0) { - dev_err(sdev->dev, - "error: can't alloc buffer for trace %d\n", ret); - goto page_err; - } - - /* create compressed page table for audio firmware */ - ret = snd_sof_create_page_table(sdev->dev, &sdev->dmatb, - sdev->dmatp.area, sdev->dmatb.bytes); - if (ret < 0) - goto table_err; - - sdev->dma_trace_pages = ret; - dev_dbg(sdev->dev, "%s: dma_trace_pages: %d\n", - __func__, sdev->dma_trace_pages); - - if (sdev->first_boot) { - ret = trace_debugfs_create(sdev); - if (ret < 0) - goto table_err; - } - - init_waitqueue_head(&sdev->trace_sleep); - - ret = snd_sof_init_trace_ipc(sdev); - if (ret < 0) - goto table_err; - - return 0; -table_err: - sdev->dma_trace_pages = 0; - snd_dma_free_pages(&sdev->dmatb); -page_err: - snd_dma_free_pages(&sdev->dmatp); - return ret; -} -EXPORT_SYMBOL(snd_sof_init_trace); - -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); - } - - if (posn->overflow != 0) - dev_err(sdev->dev, - "error: DSP trace buffer overflow %u bytes. Total messages %d\n", - posn->overflow, posn->messages); + if (!sdev->fw_trace_is_supported || !sdev->ipc->ops->fw_tracing) + return; - return 0; + if (sdev->ipc->ops->fw_tracing->free) + sdev->ipc->ops->fw_tracing->free(sdev); } -/* an error has occurred within the DSP that prevents further trace */ -void snd_sof_trace_notify_for_error(struct snd_sof_dev *sdev) +void sof_fw_trace_fw_crashed(struct snd_sof_dev *sdev) { - if (!sdev->dtrace_is_supported) + if (!sdev->fw_trace_is_supported) return; - if (sdev->dtrace_is_enabled) { - sdev->dtrace_error = true; - wake_up(&sdev->trace_sleep); - } + if (sdev->ipc->ops->fw_tracing->fw_crashed) + sdev->ipc->ops->fw_tracing->fw_crashed(sdev); } -EXPORT_SYMBOL(snd_sof_trace_notify_for_error); -void snd_sof_release_trace(struct snd_sof_dev *sdev) +void sof_fw_trace_suspend(struct snd_sof_dev *sdev, pm_message_t pm_state) { - int ret; - - if (!sdev->dtrace_is_supported || !sdev->dtrace_is_enabled) + if (!sdev->fw_trace_is_supported) return; - ret = snd_sof_dma_trace_trigger(sdev, SNDRV_PCM_TRIGGER_STOP); - if (ret < 0) - dev_err(sdev->dev, - "error: snd_sof_dma_trace_trigger: stop: %d\n", ret); - - ret = snd_sof_dma_trace_release(sdev); - if (ret < 0) - dev_err(sdev->dev, - "error: fail in snd_sof_dma_trace_release %d\n", ret); - - sdev->dtrace_is_enabled = false; - sdev->dtrace_draining = true; - wake_up(&sdev->trace_sleep); + sdev->ipc->ops->fw_tracing->suspend(sdev, pm_state); } -EXPORT_SYMBOL(snd_sof_release_trace); -void snd_sof_free_trace(struct snd_sof_dev *sdev) +int sof_fw_trace_resume(struct snd_sof_dev *sdev) { - if (!sdev->dtrace_is_supported) - return; - - snd_sof_release_trace(sdev); + if (!sdev->fw_trace_is_supported) + return 0; - if (sdev->dma_trace_pages) { - snd_dma_free_pages(&sdev->dmatb); - snd_dma_free_pages(&sdev->dmatp); - sdev->dma_trace_pages = 0; - } + return sdev->ipc->ops->fw_tracing->resume(sdev); } -EXPORT_SYMBOL(snd_sof_free_trace); diff --git a/sound/soc/sof/xtensa/core.c b/sound/soc/sof/xtensa/core.c index bd09c3825caf..bebbe3a2865c 100644 --- a/sound/soc/sof/xtensa/core.c +++ b/sound/soc/sof/xtensa/core.c @@ -81,33 +81,39 @@ static const struct xtensa_exception_cause xtensa_exception_causes[] = { }; /* only need xtensa atm */ -static void xtensa_dsp_oops(struct snd_sof_dev *sdev, void *oops) +static void xtensa_dsp_oops(struct snd_sof_dev *sdev, const char *level, void *oops) { struct sof_ipc_dsp_oops_xtensa *xoops = oops; int i; - dev_err(sdev->dev, "error: DSP Firmware Oops\n"); + dev_printk(level, sdev->dev, "error: DSP Firmware Oops\n"); for (i = 0; i < ARRAY_SIZE(xtensa_exception_causes); i++) { if (xtensa_exception_causes[i].id == xoops->exccause) { - dev_err(sdev->dev, "error: Exception Cause: %s, %s\n", - xtensa_exception_causes[i].msg, - xtensa_exception_causes[i].description); + dev_printk(level, sdev->dev, + "error: Exception Cause: %s, %s\n", + xtensa_exception_causes[i].msg, + xtensa_exception_causes[i].description); } } - dev_err(sdev->dev, "EXCCAUSE 0x%8.8x EXCVADDR 0x%8.8x PS 0x%8.8x SAR 0x%8.8x\n", - xoops->exccause, xoops->excvaddr, xoops->ps, xoops->sar); - dev_err(sdev->dev, "EPC1 0x%8.8x EPC2 0x%8.8x EPC3 0x%8.8x EPC4 0x%8.8x", - xoops->epc1, xoops->epc2, xoops->epc3, xoops->epc4); - dev_err(sdev->dev, "EPC5 0x%8.8x EPC6 0x%8.8x EPC7 0x%8.8x DEPC 0x%8.8x", - xoops->epc5, xoops->epc6, xoops->epc7, xoops->depc); - dev_err(sdev->dev, "EPS2 0x%8.8x EPS3 0x%8.8x EPS4 0x%8.8x EPS5 0x%8.8x", - xoops->eps2, xoops->eps3, xoops->eps4, xoops->eps5); - dev_err(sdev->dev, "EPS6 0x%8.8x EPS7 0x%8.8x INTENABL 0x%8.8x INTERRU 0x%8.8x", - xoops->eps6, xoops->eps7, xoops->intenable, xoops->interrupt); + dev_printk(level, sdev->dev, + "EXCCAUSE 0x%8.8x EXCVADDR 0x%8.8x PS 0x%8.8x SAR 0x%8.8x\n", + xoops->exccause, xoops->excvaddr, xoops->ps, xoops->sar); + dev_printk(level, sdev->dev, + "EPC1 0x%8.8x EPC2 0x%8.8x EPC3 0x%8.8x EPC4 0x%8.8x", + xoops->epc1, xoops->epc2, xoops->epc3, xoops->epc4); + dev_printk(level, sdev->dev, + "EPC5 0x%8.8x EPC6 0x%8.8x EPC7 0x%8.8x DEPC 0x%8.8x", + xoops->epc5, xoops->epc6, xoops->epc7, xoops->depc); + dev_printk(level, sdev->dev, + "EPS2 0x%8.8x EPS3 0x%8.8x EPS4 0x%8.8x EPS5 0x%8.8x", + xoops->eps2, xoops->eps3, xoops->eps4, xoops->eps5); + dev_printk(level, sdev->dev, + "EPS6 0x%8.8x EPS7 0x%8.8x INTENABL 0x%8.8x INTERRU 0x%8.8x", + xoops->eps6, xoops->eps7, xoops->intenable, xoops->interrupt); } -static void xtensa_stack(struct snd_sof_dev *sdev, void *oops, u32 *stack, - u32 stack_words) +static void xtensa_stack(struct snd_sof_dev *sdev, const char *level, void *oops, + u32 *stack, u32 stack_words) { struct sof_ipc_dsp_oops_xtensa *xoops = oops; u32 stack_ptr = xoops->plat_hdr.stackptr; @@ -115,7 +121,7 @@ static void xtensa_stack(struct snd_sof_dev *sdev, void *oops, u32 *stack, unsigned char buf[4 * 8 + 3 + 1]; int i; - dev_err(sdev->dev, "stack dump from 0x%8.8x\n", stack_ptr); + dev_printk(level, sdev->dev, "stack dump from 0x%8.8x\n", stack_ptr); /* * example output: @@ -124,7 +130,7 @@ static void xtensa_stack(struct snd_sof_dev *sdev, void *oops, u32 *stack, for (i = 0; i < stack_words; i += 4) { hex_dump_to_buffer(stack + i, 16, 16, 4, buf, sizeof(buf), false); - dev_err(sdev->dev, "0x%08x: %s\n", stack_ptr + i * 4, buf); + dev_printk(level, sdev->dev, "0x%08x: %s\n", stack_ptr + i * 4, buf); } } |