Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+416
View File
@@ -0,0 +1,416 @@
# SPDX-License-Identifier: GPL-2.0-only
config SND_SOC_SOF_INTEL_TOPLEVEL
bool "SOF support for Intel audio DSPs"
depends on X86 || COMPILE_TEST
help
This adds support for Sound Open Firmware for Intel(R) platforms.
Say Y if you have such a device.
If unsure select "N".
if SND_SOC_SOF_INTEL_TOPLEVEL
config SND_SOC_SOF_INTEL_HIFI_EP_IPC
tristate
help
This option is not user-selectable but automagically handled by
'select' statements at a higher level.
config SND_SOC_SOF_INTEL_ATOM_HIFI_EP
tristate
select SND_SOC_SOF_INTEL_COMMON
select SND_SOC_SOF_INTEL_HIFI_EP_IPC
help
This option is not user-selectable but automagically handled by
'select' statements at a higher level.
config SND_SOC_SOF_INTEL_COMMON
tristate
select SND_SOC_SOF
select SND_SOC_ACPI_INTEL_MATCH
select SND_SOC_SOF_XTENSA
select SND_SOC_INTEL_MACH
select SND_SOC_ACPI if ACPI
select SND_INTEL_DSP_CONFIG
help
This option is not user-selectable but automagically handled by
'select' statements at a higher level.
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
select IOSF_MBI if X86 && PCI
help
This adds support for Sound Open Firmware for Intel(R) platforms
using the Baytrail, Braswell or Cherrytrail processors.
This option can coexist in the same build with the Atom legacy
drivers, currently the default but which will be deprecated
at some point.
Existing firmware/topology binaries and UCM configurations
typically located in the root file system are already
compatible with both SOF or Atom/SST legacy drivers.
This is a recommended option for distributions.
Say Y if you want to enable SOF on Baytrail/Cherrytrail.
If unsure select "N".
config SND_SOC_SOF_BROADWELL
tristate "SOF support for Broadwell"
default SND_SOC_SOF_ACPI
select SND_SOC_SOF_IPC3
select SND_SOC_SOF_INTEL_COMMON
select SND_SOC_SOF_INTEL_HIFI_EP_IPC
select SND_SOC_SOF_ACPI_DEV
help
This adds support for Sound Open Firmware for Intel(R) platforms
using the Broadwell processors.
This option can coexist in the same build with the default 'catpt'
driver.
Existing firmware/topology binaries and UCM configurations typically
located in the root file system are already compatible with both SOF
or catpt drivers.
SOF does not fully support Broadwell and has limitations related to
DMA and suspend-resume, this is not a recommended option for
distributions.
Say Y if you want to enable SOF on Broadwell.
If unsure select "N".
endif ## SND_SOC_SOF_ACPI
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
using the Tangier/Merrifield processors.
Say Y if you have such a device.
If unsure select "N".
config SND_SOC_SOF_INTEL_SKL
tristate
select SND_SOC_SOF_HDA_GENERIC
select SND_SOC_SOF_IPC4
config SND_SOC_SOF_SKYLAKE
tristate "SOF support for SkyLake"
default SND_SOC_SOF_PCI
select SND_SOC_SOF_INTEL_SKL
help
This 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_GENERIC
select SND_SOC_SOF_IPC3
select SND_SOC_SOF_IPC4
config SND_SOC_SOF_APOLLOLAKE
tristate "SOF support for Apollolake"
default SND_SOC_SOF_PCI
select SND_SOC_SOF_INTEL_APL
help
This adds support for Sound Open Firmware for Intel(R) platforms
using the Apollolake processors.
Say Y if you have such a device.
If unsure select "N".
config SND_SOC_SOF_GEMINILAKE
tristate "SOF support for GeminiLake"
default SND_SOC_SOF_PCI
select SND_SOC_SOF_INTEL_APL
help
This adds support for Sound Open Firmware for Intel(R) platforms
using the Geminilake processors.
Say Y if you have such a device.
If unsure select "N".
config SND_SOC_SOF_INTEL_CNL
tristate
select SND_SOC_SOF_HDA_GENERIC
select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
select SND_SOC_SOF_IPC3
select SND_SOC_SOF_IPC4
config SND_SOC_SOF_CANNONLAKE
tristate "SOF support for Cannonlake"
default SND_SOC_SOF_PCI
select SND_SOC_SOF_INTEL_CNL
help
This adds support for Sound Open Firmware for Intel(R) platforms
using the Cannonlake processors.
Say Y if you have such a device.
If unsure select "N".
config SND_SOC_SOF_COFFEELAKE
tristate "SOF support for CoffeeLake"
default SND_SOC_SOF_PCI
select SND_SOC_SOF_INTEL_CNL
help
This adds support for Sound Open Firmware for Intel(R) platforms
using the Coffeelake processors.
Say Y if you have such a device.
If unsure select "N".
config SND_SOC_SOF_COMETLAKE
tristate "SOF support for CometLake"
default SND_SOC_SOF_PCI
select SND_SOC_SOF_INTEL_CNL
help
This adds support for Sound Open Firmware for Intel(R) platforms
using the Cometlake processors.
If unsure select "N".
config SND_SOC_SOF_INTEL_ICL
tristate
select SND_SOC_SOF_HDA_GENERIC
select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
select SND_SOC_SOF_IPC3
select SND_SOC_SOF_IPC4
select SND_SOC_SOF_INTEL_CNL
config SND_SOC_SOF_ICELAKE
tristate "SOF support for Icelake"
default SND_SOC_SOF_PCI
select SND_SOC_SOF_INTEL_ICL
help
This adds support for Sound Open Firmware for Intel(R) platforms
using the Icelake processors.
Say Y if you have such a device.
If unsure select "N".
config SND_SOC_SOF_JASPERLAKE
tristate "SOF support for JasperLake"
default SND_SOC_SOF_PCI
select SND_SOC_SOF_INTEL_ICL
help
This adds support for Sound Open Firmware for Intel(R) platforms
using the JasperLake processors.
Say Y if you have such a device.
If unsure select "N".
config SND_SOC_SOF_INTEL_TGL
tristate
select SND_SOC_SOF_HDA_GENERIC
select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
select SND_SOC_SOF_IPC3
select SND_SOC_SOF_IPC4
select SND_SOC_SOF_INTEL_CNL
config SND_SOC_SOF_TIGERLAKE
tristate "SOF support for Tigerlake"
default SND_SOC_SOF_PCI
select SND_SOC_SOF_INTEL_TGL
help
This adds support for Sound Open Firmware for Intel(R) platforms
using the Tigerlake processors.
Say Y if you have such a device.
If unsure select "N".
config SND_SOC_SOF_ELKHARTLAKE
tristate "SOF support for ElkhartLake"
default SND_SOC_SOF_PCI
select SND_SOC_SOF_INTEL_TGL
help
This adds support for Sound Open Firmware for Intel(R) platforms
using the ElkhartLake processors.
Say Y if you have such a device.
If unsure select "N".
config SND_SOC_SOF_ALDERLAKE
tristate "SOF support for Alderlake"
default SND_SOC_SOF_PCI
select SND_SOC_SOF_INTEL_TGL
help
This adds support for Sound Open Firmware for Intel(R) platforms
using the Alderlake processors.
Say Y if you have such a device.
If unsure select "N".
config SND_SOC_SOF_INTEL_MTL
tristate
select SND_SOC_SOF_HDA_GENERIC
select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
select SND_SOC_SOF_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_INTEL_LNL
tristate
select SND_SOC_SOF_HDA_GENERIC
select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
select SND_SOC_SOF_IPC4
select SND_SOC_SOF_INTEL_MTL
config SND_SOC_SOF_LUNARLAKE
tristate "SOF support for Lunarlake"
default SND_SOC_SOF_PCI
select SND_SOC_SOF_INTEL_LNL
help
This adds support for Sound Open Firmware for Intel(R) platforms
using the Lunarlake processors.
Say Y if you have such a device.
If unsure select "N".
config SND_SOC_SOF_INTEL_PTL
tristate
select SND_SOC_SOF_HDA_COMMON
select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
select SND_SOC_SOF_IPC4
select SND_SOC_SOF_INTEL_LNL
config SND_SOC_SOF_PANTHERLAKE
tristate "SOF support for Pantherlake"
default SND_SOC_SOF_PCI
select SND_SOC_SOF_INTEL_PTL
help
This adds support for Sound Open Firmware for Intel(R) platforms
using the Pantherlake processors.
Say Y if you have such a device.
If unsure select "N".
config SND_SOC_SOF_INTEL_NVL
tristate
select SND_SOC_SOF_HDA_COMMON
select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
select SND_SOC_SOF_IPC4
select SND_SOC_SOF_INTEL_PTL
config SND_SOC_SOF_NOVALAKE
tristate "SOF support for Novalake"
default SND_SOC_SOF_PCI
select SND_SOC_SOF_INTEL_NVL
help
This adds support for Sound Open Firmware for Intel(R) platforms
using the Novalake processors.
Say Y if you have such a device.
If unsure select "N".
config SND_SOC_SOF_HDA_COMMON
tristate
select SND_HDA_EXT_CORE
config SND_SOC_SOF_HDA_GENERIC
tristate
select SND_SOC_SOF_HDA_COMMON
select SND_SOC_SOF_INTEL_COMMON
select SND_SOC_SOF_PCI_DEV
select SND_INTEL_DSP_CONFIG
select SND_SOC_SOF_HDA_LINK_BASELINE
select SND_SOC_SOF_HDA_PROBES
select SND_SOC_SDW_UTILS if SND_SOC_SOF_INTEL_SOUNDWIRE !=n
select SND_SOC_SOF_HDA_MLINK if SND_SOC_SOF_HDA_LINK
select SND_SOF_SOF_HDA_SDW_BPT if SND_SOC_SOF_INTEL_LNL != n && \
SND_SOC_SOF_INTEL_SOUNDWIRE !=n
help
This option is not user-selectable but automagically handled by
'select' statements at a higher level.
config SND_SOC_SOF_HDA_MLINK
tristate
help
This option is not user-selectable but automagically handled by
'select' statements at a higher level.
if SND_SOC_SOF_HDA_GENERIC
config SND_SOC_SOF_HDA_LINK
bool "SOF support for HDA Links(HDA/HDMI)"
help
This adds support for HDA links(HDA/HDMI) with Sound Open Firmware
for Intel(R) platforms.
Say Y if you want to enable HDA links with SOF.
If unsure select "N".
config SND_SOC_SOF_HDA_AUDIO_CODEC
bool "SOF support for HDAudio codecs"
depends on SND_SOC_SOF_HDA_LINK
select SND_SOC_SOF_PROBE_WORK_QUEUE
help
This adds support for HDAudio codecs with Sound Open Firmware
for Intel(R) platforms.
Say Y if you want to enable HDAudio codecs with SOF.
If unsure select "N".
endif ## SND_SOC_SOF_HDA_GENERIC
config SND_SOF_SOF_HDA_SDW_BPT
tristate
select SND_HDA_EXT_CORE
help
This option is not user-selectable but automagically handled by
'select' statements at a higher level.
config SND_SOC_SOF_HDA_LINK_BASELINE
tristate
select SND_SOC_SOF_HDA if SND_SOC_SOF_HDA_LINK
help
This option is not user-selectable but automagically handled by
'select' statements at a higher level.
config SND_SOC_SOF_HDA
tristate
select SND_HDA_EXT_CORE if SND_SOC_SOF_HDA_LINK
select SND_SOC_HDAC_HDA if SND_SOC_SOF_HDA_AUDIO_CODEC
help
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 != n
select SND_INTEL_SOUNDWIRE_ACPI if SND_SOC_SOF_INTEL_SOUNDWIRE != n
config SND_SOC_SOF_INTEL_SOUNDWIRE
tristate "SOF support for SoundWire"
default SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
depends on SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
depends on ACPI && SOUNDWIRE
depends on !(SOUNDWIRE=m && SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE=y)
help
This adds support for SoundWire with Sound Open Firmware
for Intel(R) platforms.
Say Y if you want to enable SoundWire links with SOF.
If unsure select "N".
endif ## SND_SOC_SOF_PCI
endif ## SND_SOC_SOF_INTEL_TOPLEVEL
+53
View File
@@ -0,0 +1,53 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
snd-sof-acpi-intel-byt-y := byt.o
snd-sof-acpi-intel-bdw-y := bdw.o
snd-sof-intel-hda-common-y := 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-dai-ops.o hda-bus.o \
telemetry.o tracepoints.o
snd-sof-intel-hda-generic-y := hda.o hda-common-ops.o
snd-sof-intel-hda-mlink-y := hda-mlink.o
snd-sof-intel-hda-sdw-bpt-objs := hda-sdw-bpt.o
snd-sof-intel-hda-common-$(CONFIG_SND_SOC_SOF_HDA_PROBES) += hda-probes.o
snd-sof-intel-hda-y := hda-codec.o
snd-sof-intel-atom-y := atom.o
obj-$(CONFIG_SND_SOC_SOF_INTEL_ATOM_HIFI_EP) += snd-sof-intel-atom.o
obj-$(CONFIG_SND_SOC_SOF_BAYTRAIL) += snd-sof-acpi-intel-byt.o
obj-$(CONFIG_SND_SOC_SOF_BROADWELL) += snd-sof-acpi-intel-bdw.o
obj-$(CONFIG_SND_SOC_SOF_HDA_COMMON) += snd-sof-intel-hda-common.o
obj-$(CONFIG_SND_SOC_SOF_HDA_GENERIC) += snd-sof-intel-hda-generic.o
obj-$(CONFIG_SND_SOC_SOF_HDA_MLINK) += snd-sof-intel-hda-mlink.o
obj-$(CONFIG_SND_SOC_SOF_HDA) += snd-sof-intel-hda.o
obj-$(CONFIG_SND_SOF_SOF_HDA_SDW_BPT) += snd-sof-intel-hda-sdw-bpt.o
snd-sof-pci-intel-tng-y := pci-tng.o
snd-sof-pci-intel-skl-y := pci-skl.o skl.o hda-loader-skl.o
snd-sof-pci-intel-apl-y := pci-apl.o apl.o
snd-sof-pci-intel-cnl-y := pci-cnl.o cnl.o
snd-sof-pci-intel-icl-y := pci-icl.o icl.o
snd-sof-pci-intel-tgl-y := pci-tgl.o tgl.o
snd-sof-pci-intel-mtl-y := pci-mtl.o mtl.o
snd-sof-pci-intel-lnl-y := pci-lnl.o lnl.o
snd-sof-pci-intel-ptl-y := pci-ptl.o ptl.o
snd-sof-pci-intel-nvl-y := pci-nvl.o nvl.o
obj-$(CONFIG_SND_SOC_SOF_MERRIFIELD) += snd-sof-pci-intel-tng.o
obj-$(CONFIG_SND_SOC_SOF_INTEL_SKL) += snd-sof-pci-intel-skl.o
obj-$(CONFIG_SND_SOC_SOF_INTEL_APL) += snd-sof-pci-intel-apl.o
obj-$(CONFIG_SND_SOC_SOF_INTEL_CNL) += snd-sof-pci-intel-cnl.o
obj-$(CONFIG_SND_SOC_SOF_INTEL_ICL) += snd-sof-pci-intel-icl.o
obj-$(CONFIG_SND_SOC_SOF_INTEL_TGL) += snd-sof-pci-intel-tgl.o
obj-$(CONFIG_SND_SOC_SOF_INTEL_MTL) += snd-sof-pci-intel-mtl.o
obj-$(CONFIG_SND_SOC_SOF_INTEL_LNL) += snd-sof-pci-intel-lnl.o
obj-$(CONFIG_SND_SOC_SOF_INTEL_PTL) += snd-sof-pci-intel-ptl.o
obj-$(CONFIG_SND_SOC_SOF_INTEL_NVL) += snd-sof-pci-intel-nvl.o
+122
View File
@@ -0,0 +1,122 @@
// 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
//
// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com>
// Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
// Rander Wang <rander.wang@intel.com>
// Keyon Jie <yang.jie@linux.intel.com>
//
/*
* 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"
static const struct snd_sof_debugfs_map apl_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},
};
/* apollolake ops */
struct snd_sof_dsp_ops sof_apl_ops;
int sof_apl_ops_init(struct snd_sof_dev *sdev)
{
/* common defaults */
memcpy(&sof_apl_ops, &sof_hda_common_ops, sizeof(struct snd_sof_dsp_ops));
/* probe/remove/shutdown */
sof_apl_ops.shutdown = hda_dsp_shutdown;
if (sdev->pdata->ipc_type == SOF_IPC_TYPE_3) {
/* doorbell */
sof_apl_ops.irq_thread = hda_dsp_ipc_irq_thread;
/* ipc */
sof_apl_ops.send_msg = hda_dsp_ipc_send_msg;
/* debug */
sof_apl_ops.ipc_dump = hda_ipc_dump;
sof_apl_ops.set_power_state = hda_dsp_set_power_state_ipc3;
}
if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4) {
struct sof_ipc4_fw_data *ipc4_data;
sdev->private = kzalloc_obj(*ipc4_data);
if (!sdev->private)
return -ENOMEM;
ipc4_data = sdev->private;
ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET;
ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_1_5;
/* External library loading support */
ipc4_data->load_library = hda_dsp_ipc4_load_library;
/* doorbell */
sof_apl_ops.irq_thread = hda_dsp_ipc4_irq_thread;
/* ipc */
sof_apl_ops.send_msg = hda_dsp_ipc4_send_msg;
/* debug */
sof_apl_ops.ipc_dump = hda_ipc4_dump;
sof_apl_ops.set_power_state = hda_dsp_set_power_state_ipc4;
}
/* set DAI driver ops */
hda_set_dai_drv_ops(sdev, &sof_apl_ops);
/* debug */
sof_apl_ops.debug_map = apl_dsp_debugfs;
sof_apl_ops.debug_map_count = ARRAY_SIZE(apl_dsp_debugfs);
/* firmware run */
sof_apl_ops.run = hda_dsp_cl_boot_firmware;
/* pre/post fw run */
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;
};
const struct sof_intel_dsp_desc apl_chip_info = {
/* Apollolake */
.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,
.rom_init_timeout = 150,
.ssp_count = APL_SSP_COUNT,
.ssp_base_offset = APL_SSP_BASE_OFFSET,
.d0i3_offset = SOF_HDA_VS_D0I3C,
.quirks = SOF_INTEL_PROCEN_FMT_QUIRK,
.check_ipc_irq = hda_dsp_check_ipc_irq,
.cl_init = cl_dsp_init,
.power_down_dsp = hda_power_down_dsp,
.disable_interrupts = hda_dsp_disable_interrupts,
.hw_ip_version = SOF_INTEL_CAVS_1_5_PLUS,
.platform = "apl",
};
+416
View File
@@ -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) 2018-2021 Intel Corporation
//
// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
//
/*
* Hardware interface for audio DSP on Atom devices
*/
#include <linux/module.h>
#include <sound/sof.h>
#include <sound/sof/xtensa.h>
#include <sound/soc-acpi.h>
#include <sound/soc-acpi-intel-match.h>
#include <sound/intel-dsp-config.h>
#include "../ops.h"
#include "shim.h"
#include "atom.h"
#include "../sof-acpi-dev.h"
#include "../sof-audio.h"
#include "../../intel/common/soc-intel-quirks.h"
static void atom_host_done(struct snd_sof_dev *sdev);
static void atom_dsp_done(struct snd_sof_dev *sdev);
/*
* Debug
*/
static void atom_get_registers(struct snd_sof_dev *sdev,
struct sof_ipc_dsp_oops_xtensa *xoops,
struct sof_ipc_panic_info *panic_info,
u32 *stack, size_t stack_words)
{
u32 offset = sdev->dsp_oops_offset;
/* first read regsisters */
sof_mailbox_read(sdev, offset, xoops, sizeof(*xoops));
/* note: variable AR register array is not read */
/* then get panic info */
if (xoops->arch_hdr.totalsize > EXCEPT_MAX_HDR_SIZE) {
dev_err(sdev->dev, "invalid header size 0x%x. FW oops is bogus\n",
xoops->arch_hdr.totalsize);
return;
}
offset += xoops->arch_hdr.totalsize;
sof_mailbox_read(sdev, offset, panic_info, sizeof(*panic_info));
/* then get the stack */
offset += sizeof(*panic_info);
sof_mailbox_read(sdev, offset, stack, stack_words * sizeof(u32));
}
void atom_dump(struct snd_sof_dev *sdev, u32 flags)
{
struct sof_ipc_dsp_oops_xtensa xoops;
struct sof_ipc_panic_info panic_info;
u32 stack[STACK_DUMP_SIZE];
u64 status, panic, imrd, imrx;
/* now try generic SOF status messages */
status = snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_IPCD);
panic = snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_IPCX);
atom_get_registers(sdev, &xoops, &panic_info, stack,
STACK_DUMP_SIZE);
sof_print_oops_and_stack(sdev, KERN_ERR, status, panic, &xoops,
&panic_info, stack, STACK_DUMP_SIZE);
/* provide some context for firmware debug */
imrx = snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_IMRX);
imrd = snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_IMRD);
dev_err(sdev->dev,
"error: ipc host -> DSP: pending %s complete %s raw 0x%llx\n",
str_yes_no(panic & SHIM_IPCX_BUSY),
str_yes_no(panic & SHIM_IPCX_DONE), panic);
dev_err(sdev->dev,
"error: mask host: pending %s complete %s raw 0x%llx\n",
str_yes_no(imrx & SHIM_IMRX_BUSY),
str_yes_no(imrx & SHIM_IMRX_DONE), imrx);
dev_err(sdev->dev,
"error: ipc DSP -> host: pending %s complete %s raw 0x%llx\n",
str_yes_no(status & SHIM_IPCD_BUSY),
str_yes_no(status & SHIM_IPCD_DONE), status);
dev_err(sdev->dev,
"error: mask DSP: pending %s complete %s raw 0x%llx\n",
str_yes_no(imrd & SHIM_IMRD_BUSY),
str_yes_no(imrd & SHIM_IMRD_DONE), imrd);
}
EXPORT_SYMBOL_NS(atom_dump, "SND_SOC_SOF_INTEL_ATOM_HIFI_EP");
/*
* IPC Doorbell IRQ handler and thread.
*/
irqreturn_t atom_irq_handler(int irq, void *context)
{
struct snd_sof_dev *sdev = context;
u64 ipcx, ipcd;
int ret = IRQ_NONE;
ipcx = snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_IPCX);
ipcd = snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_IPCD);
if (ipcx & SHIM_BYT_IPCX_DONE) {
/* reply message from DSP, Mask Done interrupt first */
snd_sof_dsp_update_bits64_unlocked(sdev, DSP_BAR,
SHIM_IMRX,
SHIM_IMRX_DONE,
SHIM_IMRX_DONE);
ret = IRQ_WAKE_THREAD;
}
if (ipcd & SHIM_BYT_IPCD_BUSY) {
/* new message from DSP, Mask Busy interrupt first */
snd_sof_dsp_update_bits64_unlocked(sdev, DSP_BAR,
SHIM_IMRX,
SHIM_IMRX_BUSY,
SHIM_IMRX_BUSY);
ret = IRQ_WAKE_THREAD;
}
return ret;
}
EXPORT_SYMBOL_NS(atom_irq_handler, "SND_SOC_SOF_INTEL_ATOM_HIFI_EP");
irqreturn_t atom_irq_thread(int irq, void *context)
{
struct snd_sof_dev *sdev = context;
u64 ipcx, ipcd;
ipcx = snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_IPCX);
ipcd = snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_IPCD);
/* reply message from DSP */
if (ipcx & SHIM_BYT_IPCX_DONE) {
/*
* handle immediate reply from DSP core. If the msg is
* found, set done bit in cmd_done which is called at the
* end of message processing function, else set it here
* because the done bit can't be set in cmd_done function
* which is triggered by msg
*/
guard(spinlock_irq)(&sdev->ipc_lock);
snd_sof_ipc_process_reply(sdev, ipcx);
atom_dsp_done(sdev);
}
/* new message from DSP */
if (ipcd & SHIM_BYT_IPCD_BUSY) {
/* Handle messages from DSP Core */
if ((ipcd & SOF_IPC_PANIC_MAGIC_MASK) == SOF_IPC_PANIC_MAGIC) {
snd_sof_dsp_panic(sdev, PANIC_OFFSET(ipcd) + MBOX_OFFSET,
true);
} else {
snd_sof_ipc_msgs_rx(sdev);
}
atom_host_done(sdev);
}
return IRQ_HANDLED;
}
EXPORT_SYMBOL_NS(atom_irq_thread, "SND_SOC_SOF_INTEL_ATOM_HIFI_EP");
int atom_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
{
/* unmask and prepare to receive Done interrupt */
snd_sof_dsp_update_bits64_unlocked(sdev, DSP_BAR, SHIM_IMRX,
SHIM_IMRX_DONE, 0);
/* send the message */
sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data,
msg->msg_size);
snd_sof_dsp_write64(sdev, DSP_BAR, SHIM_IPCX, SHIM_BYT_IPCX_BUSY);
return 0;
}
EXPORT_SYMBOL_NS(atom_send_msg, "SND_SOC_SOF_INTEL_ATOM_HIFI_EP");
int atom_get_mailbox_offset(struct snd_sof_dev *sdev)
{
return MBOX_OFFSET;
}
EXPORT_SYMBOL_NS(atom_get_mailbox_offset, "SND_SOC_SOF_INTEL_ATOM_HIFI_EP");
int atom_get_window_offset(struct snd_sof_dev *sdev, u32 id)
{
return MBOX_OFFSET;
}
EXPORT_SYMBOL_NS(atom_get_window_offset, "SND_SOC_SOF_INTEL_ATOM_HIFI_EP");
static void atom_host_done(struct snd_sof_dev *sdev)
{
/* clear BUSY bit and set DONE bit - accept new messages */
snd_sof_dsp_update_bits64_unlocked(sdev, DSP_BAR, SHIM_IPCD,
SHIM_BYT_IPCD_BUSY |
SHIM_BYT_IPCD_DONE,
SHIM_BYT_IPCD_DONE);
/* unmask and prepare to receive next new message */
snd_sof_dsp_update_bits64_unlocked(sdev, DSP_BAR, SHIM_IMRX,
SHIM_IMRX_BUSY, 0);
}
static void atom_dsp_done(struct snd_sof_dev *sdev)
{
/* clear DONE bit - tell DSP we have completed */
snd_sof_dsp_update_bits64_unlocked(sdev, DSP_BAR, SHIM_IPCX,
SHIM_BYT_IPCX_DONE, 0);
}
/*
* DSP control.
*/
int atom_run(struct snd_sof_dev *sdev)
{
int tries = 10;
/* release stall and wait to unstall */
snd_sof_dsp_update_bits64(sdev, DSP_BAR, SHIM_CSR,
SHIM_BYT_CSR_STALL, 0x0);
while (tries--) {
if (!(snd_sof_dsp_read64(sdev, DSP_BAR, SHIM_CSR) &
SHIM_BYT_CSR_PWAITMODE))
break;
msleep(100);
}
if (tries < 0)
return -ENODEV;
/* return init core mask */
return 1;
}
EXPORT_SYMBOL_NS(atom_run, "SND_SOC_SOF_INTEL_ATOM_HIFI_EP");
int atom_reset(struct snd_sof_dev *sdev)
{
/* put DSP into reset, set reset vector and stall */
snd_sof_dsp_update_bits64(sdev, DSP_BAR, SHIM_CSR,
SHIM_BYT_CSR_RST | SHIM_BYT_CSR_VECTOR_SEL |
SHIM_BYT_CSR_STALL,
SHIM_BYT_CSR_RST | SHIM_BYT_CSR_VECTOR_SEL |
SHIM_BYT_CSR_STALL);
usleep_range(10, 15);
/* take DSP out of reset and keep stalled for FW loading */
snd_sof_dsp_update_bits64(sdev, DSP_BAR, SHIM_CSR,
SHIM_BYT_CSR_RST, 0);
return 0;
}
EXPORT_SYMBOL_NS(atom_reset, "SND_SOC_SOF_INTEL_ATOM_HIFI_EP");
static const char *fixup_tplg_name(struct snd_sof_dev *sdev,
const char *sof_tplg_filename,
const char *ssp_str)
{
const char *tplg_filename = NULL;
const char *split_ext;
char *filename, *tmp;
filename = kstrdup(sof_tplg_filename, GFP_KERNEL);
if (!filename)
return NULL;
/* this assumes a .tplg extension */
tmp = filename;
split_ext = strsep(&tmp, ".");
if (split_ext)
tplg_filename = devm_kasprintf(sdev->dev, GFP_KERNEL,
"%s-%s.tplg",
split_ext, ssp_str);
kfree(filename);
return tplg_filename;
}
struct snd_soc_acpi_mach *atom_machine_select(struct snd_sof_dev *sdev)
{
struct snd_sof_pdata *sof_pdata = sdev->pdata;
const struct sof_dev_desc *desc = sof_pdata->desc;
struct snd_soc_acpi_mach *mach;
struct platform_device *pdev;
const char *tplg_filename;
mach = snd_soc_acpi_find_machine(desc->machines);
if (!mach) {
dev_warn(sdev->dev, "warning: No matching ASoC machine driver found\n");
return NULL;
}
pdev = to_platform_device(sdev->dev);
if (soc_intel_is_byt_cr(pdev)) {
dev_dbg(sdev->dev,
"BYT-CR detected, SSP0 used instead of SSP2\n");
tplg_filename = fixup_tplg_name(sdev,
mach->sof_tplg_filename,
"ssp0");
} else {
tplg_filename = mach->sof_tplg_filename;
}
if (!tplg_filename) {
dev_dbg(sdev->dev,
"error: no topology filename\n");
return NULL;
}
sof_pdata->tplg_filename = tplg_filename;
mach->mach_params.acpi_ipc_irq_index = desc->irqindex_host_ipc;
return mach;
}
EXPORT_SYMBOL_NS(atom_machine_select, "SND_SOC_SOF_INTEL_ATOM_HIFI_EP");
/* Atom DAIs */
struct snd_soc_dai_driver atom_dai[] = {
{
.name = "ssp0-port",
.playback = {
.channels_min = 1,
.channels_max = 8,
},
.capture = {
.channels_min = 1,
.channels_max = 8,
},
},
{
.name = "ssp1-port",
.playback = {
.channels_min = 1,
.channels_max = 8,
},
.capture = {
.channels_min = 1,
.channels_max = 8,
},
},
{
.name = "ssp2-port",
.playback = {
.channels_min = 1,
.channels_max = 8,
},
.capture = {
.channels_min = 1,
.channels_max = 8,
}
},
{
.name = "ssp3-port",
.playback = {
.channels_min = 1,
.channels_max = 8,
},
.capture = {
.channels_min = 1,
.channels_max = 8,
},
},
{
.name = "ssp4-port",
.playback = {
.channels_min = 1,
.channels_max = 8,
},
.capture = {
.channels_min = 1,
.channels_max = 8,
},
},
{
.name = "ssp5-port",
.playback = {
.channels_min = 1,
.channels_max = 8,
},
.capture = {
.channels_min = 1,
.channels_max = 8,
},
},
};
EXPORT_SYMBOL_NS(atom_dai, "SND_SOC_SOF_INTEL_ATOM_HIFI_EP");
void atom_set_mach_params(struct snd_soc_acpi_mach *mach,
struct snd_sof_dev *sdev)
{
struct snd_sof_pdata *pdata = sdev->pdata;
const struct sof_dev_desc *desc = pdata->desc;
struct snd_soc_acpi_mach_params *mach_params;
mach_params = &mach->mach_params;
mach_params->platform = dev_name(sdev->dev);
mach_params->num_dai_drivers = desc->ops->num_drv;
mach_params->dai_drivers = desc->ops->drv;
}
EXPORT_SYMBOL_NS(atom_set_mach_params, "SND_SOC_SOF_INTEL_ATOM_HIFI_EP");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("SOF support for Atom platforms");
+74
View File
@@ -0,0 +1,74 @@
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
/*
* This file is provided under a dual BSD/GPLv2 license. When using or
* redistributing this file, you may do so under either license.
*
* Copyright(c) 2017-2021 Intel Corporation
*
* Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
*/
#ifndef __SOF_INTEL_ATOM_H
#define __SOF_INTEL_ATOM_H
/* DSP memories */
#define IRAM_OFFSET 0x0C0000
#define IRAM_SIZE (80 * 1024)
#define DRAM_OFFSET 0x100000
#define DRAM_SIZE (160 * 1024)
#define SHIM_OFFSET 0x140000
#define SHIM_SIZE_BYT 0x100
#define SHIM_SIZE_CHT 0x118
#define MBOX_OFFSET 0x144000
#define MBOX_SIZE 0x1000
#define EXCEPT_OFFSET 0x800
#define EXCEPT_MAX_HDR_SIZE 0x400
/* DSP peripherals */
#define DMAC0_OFFSET 0x098000
#define DMAC1_OFFSET 0x09c000
#define DMAC2_OFFSET 0x094000
#define DMAC_SIZE 0x420
#define SSP0_OFFSET 0x0a0000
#define SSP1_OFFSET 0x0a1000
#define SSP2_OFFSET 0x0a2000
#define SSP3_OFFSET 0x0a4000
#define SSP4_OFFSET 0x0a5000
#define SSP5_OFFSET 0x0a6000
#define SSP_SIZE 0x100
#define STACK_DUMP_SIZE 32
#define PCI_BAR_SIZE 0x200000
#define PANIC_OFFSET(x) (((x) & GENMASK_ULL(47, 32)) >> 32)
/*
* Debug
*/
#define MBOX_DUMP_SIZE 0x30
/* BARs */
#define DSP_BAR 0
#define PCI_BAR 1
#define IMR_BAR 2
irqreturn_t atom_irq_handler(int irq, void *context);
irqreturn_t atom_irq_thread(int irq, void *context);
int atom_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg);
int atom_get_mailbox_offset(struct snd_sof_dev *sdev);
int atom_get_window_offset(struct snd_sof_dev *sdev, u32 id);
int atom_run(struct snd_sof_dev *sdev);
int atom_reset(struct snd_sof_dev *sdev);
void atom_dump(struct snd_sof_dev *sdev, u32 flags);
struct snd_soc_acpi_mach *atom_machine_select(struct snd_sof_dev *sdev);
void atom_set_mach_params(struct snd_soc_acpi_mach *mach,
struct snd_sof_dev *sdev);
extern struct snd_soc_dai_driver atom_dai[];
#endif
+693
View File
@@ -0,0 +1,693 @@
// 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
//
// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
//
/*
* Hardware interface for audio DSP on Broadwell
*/
#include <linux/module.h>
#include <sound/sof.h>
#include <sound/sof/xtensa.h>
#include <sound/soc-acpi.h>
#include <sound/soc-acpi-intel-match.h>
#include <sound/intel-dsp-config.h>
#include "../ops.h"
#include "shim.h"
#include "../sof-acpi-dev.h"
#include "../sof-audio.h"
/* BARs */
#define BDW_DSP_BAR 0
#define BDW_PCI_BAR 1
/*
* Debug
*/
/* DSP memories for BDW */
#define IRAM_OFFSET 0xA0000
#define BDW_IRAM_SIZE (10 * 32 * 1024)
#define DRAM_OFFSET 0x00000
#define BDW_DRAM_SIZE (20 * 32 * 1024)
#define SHIM_OFFSET 0xFB000
#define SHIM_SIZE 0x100
#define MBOX_OFFSET 0x9E000
#define MBOX_SIZE 0x1000
#define MBOX_DUMP_SIZE 0x30
#define EXCEPT_OFFSET 0x800
#define EXCEPT_MAX_HDR_SIZE 0x400
/* DSP peripherals */
#define DMAC0_OFFSET 0xFE000
#define DMAC1_OFFSET 0xFF000
#define DMAC_SIZE 0x420
#define SSP0_OFFSET 0xFC000
#define SSP1_OFFSET 0xFD000
#define SSP_SIZE 0x100
#define BDW_STACK_DUMP_SIZE 32
#define BDW_PANIC_OFFSET(x) ((x) & 0xFFFF)
static const struct snd_sof_debugfs_map bdw_debugfs[] = {
{"dmac0", BDW_DSP_BAR, DMAC0_OFFSET, DMAC_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
{"dmac1", BDW_DSP_BAR, DMAC1_OFFSET, DMAC_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
{"ssp0", BDW_DSP_BAR, SSP0_OFFSET, SSP_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
{"ssp1", BDW_DSP_BAR, SSP1_OFFSET, SSP_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
{"iram", BDW_DSP_BAR, IRAM_OFFSET, BDW_IRAM_SIZE,
SOF_DEBUGFS_ACCESS_D0_ONLY},
{"dram", BDW_DSP_BAR, DRAM_OFFSET, BDW_DRAM_SIZE,
SOF_DEBUGFS_ACCESS_D0_ONLY},
{"shim", BDW_DSP_BAR, SHIM_OFFSET, SHIM_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
};
static void bdw_host_done(struct snd_sof_dev *sdev);
static void bdw_dsp_done(struct snd_sof_dev *sdev);
/*
* DSP Control.
*/
static int bdw_run(struct snd_sof_dev *sdev)
{
/* set opportunistic mode on engine 0,1 for all channels */
snd_sof_dsp_update_bits(sdev, BDW_DSP_BAR, SHIM_HMDC,
SHIM_HMDC_HDDA_E0_ALLCH |
SHIM_HMDC_HDDA_E1_ALLCH, 0);
/* set DSP to RUN */
snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_CSR,
SHIM_CSR_STALL, 0x0);
/* return init core mask */
return 1;
}
static int bdw_reset(struct snd_sof_dev *sdev)
{
/* put DSP into reset and stall */
snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_CSR,
SHIM_CSR_RST | SHIM_CSR_STALL,
SHIM_CSR_RST | SHIM_CSR_STALL);
/* keep in reset for 10ms */
mdelay(10);
/* take DSP out of reset and keep stalled for FW loading */
snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_CSR,
SHIM_CSR_RST | SHIM_CSR_STALL,
SHIM_CSR_STALL);
return 0;
}
static int bdw_set_dsp_D0(struct snd_sof_dev *sdev)
{
int tries = 10;
u32 reg;
/* Disable core clock gating (VDRTCTL2.DCLCGE = 0) */
snd_sof_dsp_update_bits_unlocked(sdev, BDW_PCI_BAR, PCI_VDRTCTL2,
PCI_VDRTCL2_DCLCGE |
PCI_VDRTCL2_DTCGE, 0);
/* Disable D3PG (VDRTCTL0.D3PGD = 1) */
snd_sof_dsp_update_bits_unlocked(sdev, BDW_PCI_BAR, PCI_VDRTCTL0,
PCI_VDRTCL0_D3PGD, PCI_VDRTCL0_D3PGD);
/* Set D0 state */
snd_sof_dsp_update_bits_unlocked(sdev, BDW_PCI_BAR, PCI_PMCS,
PCI_PMCS_PS_MASK, 0);
/* check that ADSP shim is enabled */
while (tries--) {
reg = readl(sdev->bar[BDW_PCI_BAR] + PCI_PMCS)
& PCI_PMCS_PS_MASK;
if (reg == 0)
goto finish;
msleep(20);
}
return -ENODEV;
finish:
/*
* select SSP1 19.2MHz base clock, SSP clock 0,
* turn off Low Power Clock
*/
snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_CSR,
SHIM_CSR_S1IOCS | SHIM_CSR_SBCS1 |
SHIM_CSR_LPCS, 0x0);
/* stall DSP core, set clk to 192/96Mhz */
snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR,
SHIM_CSR, SHIM_CSR_STALL |
SHIM_CSR_DCS_MASK,
SHIM_CSR_STALL |
SHIM_CSR_DCS(4));
/* Set 24MHz MCLK, prevent local clock gating, enable SSP0 clock */
snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_CLKCTL,
SHIM_CLKCTL_MASK |
SHIM_CLKCTL_DCPLCG |
SHIM_CLKCTL_SCOE0,
SHIM_CLKCTL_MASK |
SHIM_CLKCTL_DCPLCG |
SHIM_CLKCTL_SCOE0);
/* Stall and reset core, set CSR */
bdw_reset(sdev);
/* Enable core clock gating (VDRTCTL2.DCLCGE = 1), delay 50 us */
snd_sof_dsp_update_bits_unlocked(sdev, BDW_PCI_BAR, PCI_VDRTCTL2,
PCI_VDRTCL2_DCLCGE |
PCI_VDRTCL2_DTCGE,
PCI_VDRTCL2_DCLCGE |
PCI_VDRTCL2_DTCGE);
usleep_range(50, 55);
/* switch on audio PLL */
snd_sof_dsp_update_bits_unlocked(sdev, BDW_PCI_BAR, PCI_VDRTCTL2,
PCI_VDRTCL2_APLLSE_MASK, 0);
/*
* set default power gating control, enable power gating control for
* all blocks. that is, can't be accessed, please enable each block
* before accessing.
*/
snd_sof_dsp_update_bits_unlocked(sdev, BDW_PCI_BAR, PCI_VDRTCTL0,
0xfffffffC, 0x0);
/* disable DMA finish function for SSP0 & SSP1 */
snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_CSR2,
SHIM_CSR2_SDFD_SSP1,
SHIM_CSR2_SDFD_SSP1);
/* set on-demond mode on engine 0,1 for all channels */
snd_sof_dsp_update_bits(sdev, BDW_DSP_BAR, SHIM_HMDC,
SHIM_HMDC_HDDA_E0_ALLCH |
SHIM_HMDC_HDDA_E1_ALLCH,
SHIM_HMDC_HDDA_E0_ALLCH |
SHIM_HMDC_HDDA_E1_ALLCH);
/* Enable Interrupt from both sides */
snd_sof_dsp_update_bits(sdev, BDW_DSP_BAR, SHIM_IMRX,
(SHIM_IMRX_BUSY | SHIM_IMRX_DONE), 0x0);
snd_sof_dsp_update_bits(sdev, BDW_DSP_BAR, SHIM_IMRD,
(SHIM_IMRD_DONE | SHIM_IMRD_BUSY |
SHIM_IMRD_SSP0 | SHIM_IMRD_DMAC), 0x0);
/* clear IPC registers */
snd_sof_dsp_write(sdev, BDW_DSP_BAR, SHIM_IPCX, 0x0);
snd_sof_dsp_write(sdev, BDW_DSP_BAR, SHIM_IPCD, 0x0);
snd_sof_dsp_write(sdev, BDW_DSP_BAR, 0x80, 0x6);
snd_sof_dsp_write(sdev, BDW_DSP_BAR, 0xe0, 0x300a);
return 0;
}
static void bdw_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));
/* note: variable AR register array is not read */
/* then get panic info */
if (xoops->arch_hdr.totalsize > EXCEPT_MAX_HDR_SIZE) {
dev_err(sdev->dev, "invalid header size 0x%x. FW oops is bogus\n",
xoops->arch_hdr.totalsize);
return;
}
offset += xoops->arch_hdr.totalsize;
sof_mailbox_read(sdev, offset, panic_info, sizeof(*panic_info));
/* then get the stack */
offset += sizeof(*panic_info);
sof_mailbox_read(sdev, offset, stack, stack_words * sizeof(u32));
}
static void bdw_dump(struct snd_sof_dev *sdev, u32 flags)
{
struct sof_ipc_dsp_oops_xtensa xoops;
struct sof_ipc_panic_info panic_info;
u32 stack[BDW_STACK_DUMP_SIZE];
u32 status, panic, imrx, imrd;
/* now try generic SOF status messages */
status = snd_sof_dsp_read(sdev, BDW_DSP_BAR, SHIM_IPCD);
panic = snd_sof_dsp_read(sdev, BDW_DSP_BAR, SHIM_IPCX);
bdw_get_registers(sdev, &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);
imrd = snd_sof_dsp_read(sdev, BDW_DSP_BAR, SHIM_IMRD);
dev_err(sdev->dev,
"error: ipc host -> DSP: pending %s complete %s raw 0x%8.8x\n",
str_yes_no(panic & SHIM_IPCX_BUSY),
str_yes_no(panic & SHIM_IPCX_DONE), panic);
dev_err(sdev->dev,
"error: mask host: pending %s complete %s raw 0x%8.8x\n",
str_yes_no(imrx & SHIM_IMRX_BUSY),
str_yes_no(imrx & SHIM_IMRX_DONE), imrx);
dev_err(sdev->dev,
"error: ipc DSP -> host: pending %s complete %s raw 0x%8.8x\n",
str_yes_no(status & SHIM_IPCD_BUSY),
str_yes_no(status & SHIM_IPCD_DONE), status);
dev_err(sdev->dev,
"error: mask DSP: pending %s complete %s raw 0x%8.8x\n",
str_yes_no(imrd & SHIM_IMRD_BUSY),
str_yes_no(imrd & SHIM_IMRD_DONE), imrd);
}
/*
* IPC Doorbell IRQ handler and thread.
*/
static irqreturn_t bdw_irq_handler(int irq, void *context)
{
struct snd_sof_dev *sdev = context;
u32 isr;
int ret = IRQ_NONE;
/* Interrupt arrived, check src */
isr = snd_sof_dsp_read(sdev, BDW_DSP_BAR, SHIM_ISRX);
if (isr & (SHIM_ISRX_DONE | SHIM_ISRX_BUSY))
ret = IRQ_WAKE_THREAD;
return ret;
}
static irqreturn_t bdw_irq_thread(int irq, void *context)
{
struct snd_sof_dev *sdev = context;
u32 ipcx, ipcd, imrx;
imrx = snd_sof_dsp_read64(sdev, BDW_DSP_BAR, SHIM_IMRX);
ipcx = snd_sof_dsp_read(sdev, BDW_DSP_BAR, SHIM_IPCX);
/* reply message from DSP */
if (ipcx & SHIM_IPCX_DONE &&
!(imrx & SHIM_IMRX_DONE)) {
/* Mask Done interrupt before return */
snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR,
SHIM_IMRX, SHIM_IMRX_DONE,
SHIM_IMRX_DONE);
/*
* handle immediate reply from DSP core. If the msg is
* found, set done bit in cmd_done which is called at the
* end of message processing function, else set it here
* because the done bit can't be set in cmd_done function
* which is triggered by msg
*/
guard(spinlock_irq)(&sdev->ipc_lock);
snd_sof_ipc_process_reply(sdev, ipcx);
bdw_dsp_done(sdev);
}
ipcd = snd_sof_dsp_read(sdev, BDW_DSP_BAR, SHIM_IPCD);
/* new message from DSP */
if (ipcd & SHIM_IPCD_BUSY &&
!(imrx & SHIM_IMRX_BUSY)) {
/* Mask Busy interrupt before return */
snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR,
SHIM_IMRX, SHIM_IMRX_BUSY,
SHIM_IMRX_BUSY);
/* 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,
true);
} else {
snd_sof_ipc_msgs_rx(sdev);
}
bdw_host_done(sdev);
}
return IRQ_HANDLED;
}
/*
* IPC Mailbox IO
*/
static int bdw_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
{
/* send the message */
sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data,
msg->msg_size);
snd_sof_dsp_write(sdev, BDW_DSP_BAR, SHIM_IPCX, SHIM_IPCX_BUSY);
return 0;
}
static int bdw_get_mailbox_offset(struct snd_sof_dev *sdev)
{
return MBOX_OFFSET;
}
static int bdw_get_window_offset(struct snd_sof_dev *sdev, u32 id)
{
return MBOX_OFFSET;
}
static void bdw_host_done(struct snd_sof_dev *sdev)
{
/* clear BUSY bit and set DONE bit - accept new messages */
snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_IPCD,
SHIM_IPCD_BUSY | SHIM_IPCD_DONE,
SHIM_IPCD_DONE);
/* unmask busy interrupt */
snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_IMRX,
SHIM_IMRX_BUSY, 0);
}
static void bdw_dsp_done(struct snd_sof_dev *sdev)
{
/* clear DONE bit - tell DSP we have completed */
snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_IPCX,
SHIM_IPCX_DONE, 0);
/* unmask Done interrupt */
snd_sof_dsp_update_bits_unlocked(sdev, BDW_DSP_BAR, SHIM_IMRX,
SHIM_IMRX_DONE, 0);
}
/*
* Probe and remove.
*/
static int bdw_probe(struct snd_sof_dev *sdev)
{
struct snd_sof_pdata *pdata = sdev->pdata;
const struct sof_dev_desc *desc = pdata->desc;
struct platform_device *pdev = to_platform_device(sdev->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);
if (mmio) {
base = mmio->start;
size = resource_size(mmio);
} else {
dev_err(sdev->dev, "error: failed to get LPE base at idx %d\n",
desc->resindex_lpe_base);
return -EINVAL;
}
dev_dbg(sdev->dev, "LPE PHY base at 0x%x size 0x%x", base, size);
sdev->bar[BDW_DSP_BAR] = devm_ioremap(sdev->dev, base, size);
if (!sdev->bar[BDW_DSP_BAR]) {
dev_err(sdev->dev,
"error: failed to ioremap LPE base 0x%x size 0x%x\n",
base, size);
return -ENODEV;
}
dev_dbg(sdev->dev, "LPE VADDR %p\n", sdev->bar[BDW_DSP_BAR]);
/* TODO: add offsets */
sdev->mmio_bar = BDW_DSP_BAR;
sdev->mailbox_bar = BDW_DSP_BAR;
sdev->dsp_oops_offset = MBOX_OFFSET;
/* PCI base */
mmio = platform_get_resource(pdev, IORESOURCE_MEM,
desc->resindex_pcicfg_base);
if (mmio) {
base = mmio->start;
size = resource_size(mmio);
} else {
dev_err(sdev->dev, "error: failed to get PCI base at idx %d\n",
desc->resindex_pcicfg_base);
return -ENODEV;
}
dev_dbg(sdev->dev, "PCI base at 0x%x size 0x%x", base, size);
sdev->bar[BDW_PCI_BAR] = devm_ioremap(sdev->dev, base, size);
if (!sdev->bar[BDW_PCI_BAR]) {
dev_err(sdev->dev,
"error: failed to ioremap PCI base 0x%x size 0x%x\n",
base, size);
return -ENODEV;
}
dev_dbg(sdev->dev, "PCI VADDR %p\n", sdev->bar[BDW_PCI_BAR]);
/* register our IRQ */
sdev->ipc_irq = platform_get_irq(pdev, desc->irqindex_host_ipc);
if (sdev->ipc_irq < 0)
return sdev->ipc_irq;
dev_dbg(sdev->dev, "using IRQ %d\n", sdev->ipc_irq);
ret = devm_request_threaded_irq(sdev->dev, sdev->ipc_irq,
bdw_irq_handler, bdw_irq_thread,
IRQF_SHARED, "AudioDSP", sdev);
if (ret < 0) {
dev_err(sdev->dev, "error: failed to register IRQ %d\n",
sdev->ipc_irq);
return ret;
}
/* enable the DSP SHIM */
ret = bdw_set_dsp_D0(sdev);
if (ret < 0) {
dev_err(sdev->dev, "error: failed to set DSP D0\n");
return ret;
}
/* 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) {
dev_err(sdev->dev, "error: failed to set DMA mask %d\n", ret);
return ret;
}
/* set default mailbox offset for FW ready message */
sdev->dsp_box.offset = MBOX_OFFSET;
return ret;
}
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;
struct snd_soc_acpi_mach *mach;
mach = snd_soc_acpi_find_machine(desc->machines);
if (!mach) {
dev_warn(sdev->dev, "warning: No matching ASoC machine driver found\n");
return NULL;
}
sof_pdata->tplg_filename = mach->sof_tplg_filename;
mach->mach_params.acpi_ipc_irq_index = desc->irqindex_host_ipc;
return 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 = &mach->mach_params;
mach_params->platform = dev_name(sdev->dev);
mach_params->num_dai_drivers = desc->ops->num_drv;
mach_params->dai_drivers = desc->ops->drv;
}
/* Broadwell DAIs */
static struct snd_soc_dai_driver bdw_dai[] = {
{
.name = "ssp0-port",
.playback = {
.channels_min = 1,
.channels_max = 8,
},
.capture = {
.channels_min = 1,
.channels_max = 8,
},
},
{
.name = "ssp1-port",
.playback = {
.channels_min = 1,
.channels_max = 8,
},
.capture = {
.channels_min = 1,
.channels_max = 8,
},
},
};
/* broadwell ops */
static const struct snd_sof_dsp_ops sof_bdw_ops = {
/*Device init */
.probe = bdw_probe,
/* DSP Core Control */
.run = bdw_run,
.reset = bdw_reset,
/* Register IO uses direct mmio */
/* Block IO */
.block_read = sof_block_read,
.block_write = sof_block_write,
/* Mailbox IO */
.mailbox_read = sof_mailbox_read,
.mailbox_write = sof_mailbox_write,
/* ipc */
.send_msg = bdw_send_msg,
.get_mailbox_offset = bdw_get_mailbox_offset,
.get_window_offset = bdw_get_window_offset,
.ipc_msg_data = sof_ipc_msg_data,
.set_stream_data_offset = sof_set_stream_data_offset,
/* machine driver */
.machine_select = bdw_machine_select,
.machine_register = sof_machine_register,
.machine_unregister = sof_machine_unregister,
.set_mach_params = bdw_set_mach_params,
/* debug */
.debug_map = bdw_debugfs,
.debug_map_count = ARRAY_SIZE(bdw_debugfs),
.dbg_dump = bdw_dump,
.debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem,
/* stream callbacks */
.pcm_open = sof_stream_pcm_open,
.pcm_close = sof_stream_pcm_close,
/*Firmware loading */
.load_firmware = snd_sof_load_firmware_memcpy,
/* DAI drivers */
.drv = bdw_dai,
.num_drv = ARRAY_SIZE(bdw_dai),
/* ALSA HW info flags */
.hw_info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_BATCH,
.dsp_arch_ops = &sof_xtensa_arch_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 = {
.machines = snd_soc_acpi_intel_broadwell_machines,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = 1,
.resindex_imr_base = -1,
.irqindex_host_ipc = 0,
.chip_info = &bdw_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_3),
.ipc_default = SOF_IPC_TYPE_3,
.default_fw_path = {
[SOF_IPC_TYPE_3] = "intel/sof",
},
.default_tplg_path = {
[SOF_IPC_TYPE_3] = "intel/sof-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_3] = "sof-bdw.ri",
},
.nocodec_tplg_filename = "sof-bdw-nocodec.tplg",
.ops = &sof_bdw_ops,
};
static const struct acpi_device_id sof_broadwell_match[] = {
{ "INT3438", (unsigned long)&sof_acpi_broadwell_desc },
{ }
};
MODULE_DEVICE_TABLE(acpi, sof_broadwell_match);
static int sof_broadwell_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct acpi_device_id *id;
const struct sof_dev_desc *desc;
int ret;
id = acpi_match_device(dev->driver->acpi_match_table, dev);
if (!id)
return -ENODEV;
ret = snd_intel_acpi_dsp_driver_probe(dev, id->id);
if (ret != SND_INTEL_DSP_DRIVER_ANY && ret != SND_INTEL_DSP_DRIVER_SOF) {
dev_dbg(dev, "SOF ACPI driver not selected, aborting probe\n");
return -ENODEV;
}
desc = (const struct sof_dev_desc *)id->driver_data;
return sof_acpi_probe(pdev, desc);
}
/* acpi_driver definition */
static struct platform_driver snd_sof_acpi_intel_bdw_driver = {
.probe = sof_broadwell_probe,
.remove = sof_acpi_remove,
.driver = {
.name = "sof-audio-acpi-intel-bdw",
.pm = pm_ptr(&sof_acpi_pm),
.acpi_match_table = sof_broadwell_match,
},
};
module_platform_driver(snd_sof_acpi_intel_bdw_driver);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("SOF support for Broadwell platforms");
MODULE_IMPORT_NS("SND_SOC_SOF_XTENSA");
MODULE_IMPORT_NS("SND_SOC_SOF_ACPI_DEV");
+480
View File
@@ -0,0 +1,480 @@
// 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
//
// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
//
/*
* Hardware interface for audio DSP on Baytrail, Braswell and Cherrytrail.
*/
#include <linux/module.h>
#include <sound/sof.h>
#include <sound/sof/xtensa.h>
#include <sound/soc-acpi.h>
#include <sound/soc-acpi-intel-match.h>
#include <sound/intel-dsp-config.h>
#include "../ops.h"
#include "atom.h"
#include "shim.h"
#include "../sof-acpi-dev.h"
#include "../sof-audio.h"
#include "../../intel/common/soc-intel-quirks.h"
static const struct snd_sof_debugfs_map byt_debugfs[] = {
{"dmac0", DSP_BAR, DMAC0_OFFSET, DMAC_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
{"dmac1", DSP_BAR, DMAC1_OFFSET, DMAC_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
{"ssp0", DSP_BAR, SSP0_OFFSET, SSP_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
{"ssp1", DSP_BAR, SSP1_OFFSET, SSP_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
{"ssp2", DSP_BAR, SSP2_OFFSET, SSP_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
{"iram", DSP_BAR, IRAM_OFFSET, IRAM_SIZE,
SOF_DEBUGFS_ACCESS_D0_ONLY},
{"dram", DSP_BAR, DRAM_OFFSET, DRAM_SIZE,
SOF_DEBUGFS_ACCESS_D0_ONLY},
{"shim", DSP_BAR, SHIM_OFFSET, SHIM_SIZE_BYT,
SOF_DEBUGFS_ACCESS_ALWAYS},
};
static const struct snd_sof_debugfs_map cht_debugfs[] = {
{"dmac0", DSP_BAR, DMAC0_OFFSET, DMAC_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
{"dmac1", DSP_BAR, DMAC1_OFFSET, DMAC_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
{"dmac2", DSP_BAR, DMAC2_OFFSET, DMAC_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
{"ssp0", DSP_BAR, SSP0_OFFSET, SSP_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
{"ssp1", DSP_BAR, SSP1_OFFSET, SSP_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
{"ssp2", DSP_BAR, SSP2_OFFSET, SSP_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
{"ssp3", DSP_BAR, SSP3_OFFSET, SSP_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
{"ssp4", DSP_BAR, SSP4_OFFSET, SSP_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
{"ssp5", DSP_BAR, SSP5_OFFSET, SSP_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
{"iram", DSP_BAR, IRAM_OFFSET, IRAM_SIZE,
SOF_DEBUGFS_ACCESS_D0_ONLY},
{"dram", DSP_BAR, DRAM_OFFSET, DRAM_SIZE,
SOF_DEBUGFS_ACCESS_D0_ONLY},
{"shim", DSP_BAR, SHIM_OFFSET, SHIM_SIZE_CHT,
SOF_DEBUGFS_ACCESS_ALWAYS},
};
static void byt_reset_dsp_disable_int(struct snd_sof_dev *sdev)
{
/* Disable Interrupt from both sides */
snd_sof_dsp_update_bits64(sdev, DSP_BAR, SHIM_IMRX, 0x3, 0x3);
snd_sof_dsp_update_bits64(sdev, DSP_BAR, SHIM_IMRD, 0x3, 0x3);
/* Put DSP into reset, set reset vector */
snd_sof_dsp_update_bits64(sdev, DSP_BAR, SHIM_CSR,
SHIM_BYT_CSR_RST | SHIM_BYT_CSR_VECTOR_SEL,
SHIM_BYT_CSR_RST | SHIM_BYT_CSR_VECTOR_SEL);
}
static int byt_suspend(struct snd_sof_dev *sdev, u32 target_state)
{
byt_reset_dsp_disable_int(sdev);
return 0;
}
static int byt_resume(struct snd_sof_dev *sdev)
{
/* enable BUSY and disable DONE Interrupt by default */
snd_sof_dsp_update_bits64(sdev, DSP_BAR, SHIM_IMRX,
SHIM_IMRX_BUSY | SHIM_IMRX_DONE,
SHIM_IMRX_DONE);
return 0;
}
static void byt_remove(struct snd_sof_dev *sdev)
{
byt_reset_dsp_disable_int(sdev);
}
static int byt_acpi_probe(struct snd_sof_dev *sdev)
{
struct snd_sof_pdata *pdata = sdev->pdata;
const struct sof_dev_desc *desc = pdata->desc;
struct platform_device *pdev = to_platform_device(sdev->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) {
dev_err(sdev->dev, "error: failed to set DMA mask %d\n", ret);
return ret;
}
/* LPE base */
mmio = platform_get_resource(pdev, IORESOURCE_MEM,
desc->resindex_lpe_base);
if (mmio) {
base = mmio->start;
size = resource_size(mmio);
} else {
dev_err(sdev->dev, "error: failed to get LPE base at idx %d\n",
desc->resindex_lpe_base);
return -EINVAL;
}
dev_dbg(sdev->dev, "LPE PHY base at 0x%x size 0x%x", base, size);
sdev->bar[DSP_BAR] = devm_ioremap(sdev->dev, base, size);
if (!sdev->bar[DSP_BAR]) {
dev_err(sdev->dev, "error: failed to ioremap LPE base 0x%x size 0x%x\n",
base, size);
return -ENODEV;
}
dev_dbg(sdev->dev, "LPE VADDR %p\n", sdev->bar[DSP_BAR]);
/* TODO: add offsets */
sdev->mmio_bar = DSP_BAR;
sdev->mailbox_bar = DSP_BAR;
/* IMR base - optional */
if (desc->resindex_imr_base == -1)
goto irq;
mmio = platform_get_resource(pdev, IORESOURCE_MEM,
desc->resindex_imr_base);
if (mmio) {
base = mmio->start;
size = resource_size(mmio);
} else {
dev_err(sdev->dev, "error: failed to get IMR base at idx %d\n",
desc->resindex_imr_base);
return -ENODEV;
}
/* some BIOSes don't map IMR */
if (base == 0x55aa55aa || base == 0x0) {
dev_info(sdev->dev, "IMR not set by BIOS. Ignoring\n");
goto irq;
}
dev_dbg(sdev->dev, "IMR base at 0x%x size 0x%x", base, size);
sdev->bar[IMR_BAR] = devm_ioremap(sdev->dev, base, size);
if (!sdev->bar[IMR_BAR]) {
dev_err(sdev->dev, "error: failed to ioremap IMR base 0x%x size 0x%x\n",
base, size);
return -ENODEV;
}
dev_dbg(sdev->dev, "IMR VADDR %p\n", sdev->bar[IMR_BAR]);
irq:
/* register our IRQ */
sdev->ipc_irq = platform_get_irq(pdev, desc->irqindex_host_ipc);
if (sdev->ipc_irq < 0)
return sdev->ipc_irq;
dev_dbg(sdev->dev, "using IRQ %d\n", sdev->ipc_irq);
ret = devm_request_threaded_irq(sdev->dev, sdev->ipc_irq,
atom_irq_handler, atom_irq_thread,
IRQF_SHARED, "AudioDSP", sdev);
if (ret < 0) {
dev_err(sdev->dev, "error: failed to register IRQ %d\n",
sdev->ipc_irq);
return ret;
}
/* enable BUSY and disable DONE Interrupt by default */
snd_sof_dsp_update_bits64(sdev, DSP_BAR, SHIM_IMRX,
SHIM_IMRX_BUSY | SHIM_IMRX_DONE,
SHIM_IMRX_DONE);
/* set default mailbox offset for FW ready message */
sdev->dsp_box.offset = MBOX_OFFSET;
return ret;
}
/* baytrail ops */
static const struct snd_sof_dsp_ops sof_byt_ops = {
/* device init */
.probe = byt_acpi_probe,
.remove = byt_remove,
/* DSP core boot / reset */
.run = atom_run,
.reset = atom_reset,
/* Register IO uses direct mmio */
/* Block IO */
.block_read = sof_block_read,
.block_write = sof_block_write,
/* Mailbox IO */
.mailbox_read = sof_mailbox_read,
.mailbox_write = sof_mailbox_write,
/* doorbell */
.irq_handler = atom_irq_handler,
.irq_thread = atom_irq_thread,
/* ipc */
.send_msg = atom_send_msg,
.get_mailbox_offset = atom_get_mailbox_offset,
.get_window_offset = atom_get_window_offset,
.ipc_msg_data = sof_ipc_msg_data,
.set_stream_data_offset = sof_set_stream_data_offset,
/* machine driver */
.machine_select = atom_machine_select,
.machine_register = sof_machine_register,
.machine_unregister = sof_machine_unregister,
.set_mach_params = atom_set_mach_params,
/* debug */
.debug_map = byt_debugfs,
.debug_map_count = ARRAY_SIZE(byt_debugfs),
.dbg_dump = atom_dump,
.debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem,
/* stream callbacks */
.pcm_open = sof_stream_pcm_open,
.pcm_close = sof_stream_pcm_close,
/*Firmware loading */
.load_firmware = snd_sof_load_firmware_memcpy,
/* PM */
.suspend = byt_suspend,
.resume = byt_resume,
/* DAI drivers */
.drv = atom_dai,
.num_drv = 3, /* we have only 3 SSPs on byt*/
/* ALSA HW info flags */
.hw_info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_BATCH,
.dsp_arch_ops = &sof_xtensa_arch_ops,
};
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 = {
/* device init */
.probe = byt_acpi_probe,
.remove = byt_remove,
/* DSP core boot / reset */
.run = atom_run,
.reset = atom_reset,
/* Register IO uses direct mmio */
/* Block IO */
.block_read = sof_block_read,
.block_write = sof_block_write,
/* Mailbox IO */
.mailbox_read = sof_mailbox_read,
.mailbox_write = sof_mailbox_write,
/* doorbell */
.irq_handler = atom_irq_handler,
.irq_thread = atom_irq_thread,
/* ipc */
.send_msg = atom_send_msg,
.get_mailbox_offset = atom_get_mailbox_offset,
.get_window_offset = atom_get_window_offset,
.ipc_msg_data = sof_ipc_msg_data,
.set_stream_data_offset = sof_set_stream_data_offset,
/* machine driver */
.machine_select = atom_machine_select,
.machine_register = sof_machine_register,
.machine_unregister = sof_machine_unregister,
.set_mach_params = atom_set_mach_params,
/* debug */
.debug_map = cht_debugfs,
.debug_map_count = ARRAY_SIZE(cht_debugfs),
.dbg_dump = atom_dump,
.debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem,
/* stream callbacks */
.pcm_open = sof_stream_pcm_open,
.pcm_close = sof_stream_pcm_close,
/*Firmware loading */
.load_firmware = snd_sof_load_firmware_memcpy,
/* PM */
.suspend = byt_suspend,
.resume = byt_resume,
/* DAI drivers */
.drv = atom_dai,
/* all 6 SSPs may be available for cherrytrail */
.num_drv = 6,
/* ALSA HW info flags */
.hw_info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_BATCH,
.dsp_arch_ops = &sof_xtensa_arch_ops,
};
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 */
static const struct sof_dev_desc sof_acpi_baytrailcr_desc = {
.machines = snd_soc_acpi_intel_baytrail_machines,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = 1,
.resindex_imr_base = 2,
.irqindex_host_ipc = 0,
.chip_info = &byt_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_3),
.ipc_default = SOF_IPC_TYPE_3,
.default_fw_path = {
[SOF_IPC_TYPE_3] = "intel/sof",
},
.default_tplg_path = {
[SOF_IPC_TYPE_3] = "intel/sof-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_3] = "sof-byt.ri",
},
.nocodec_tplg_filename = "sof-byt-nocodec.tplg",
.ops = &sof_byt_ops,
};
static const struct sof_dev_desc sof_acpi_baytrail_desc = {
.machines = snd_soc_acpi_intel_baytrail_machines,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = 1,
.resindex_imr_base = 2,
.irqindex_host_ipc = 5,
.chip_info = &byt_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_3),
.ipc_default = SOF_IPC_TYPE_3,
.default_fw_path = {
[SOF_IPC_TYPE_3] = "intel/sof",
},
.default_tplg_path = {
[SOF_IPC_TYPE_3] = "intel/sof-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_3] = "sof-byt.ri",
},
.nocodec_tplg_filename = "sof-byt-nocodec.tplg",
.ops = &sof_byt_ops,
};
static const struct sof_dev_desc sof_acpi_cherrytrail_desc = {
.machines = snd_soc_acpi_intel_cherrytrail_machines,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = 1,
.resindex_imr_base = 2,
.irqindex_host_ipc = 5,
.chip_info = &cht_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_3),
.ipc_default = SOF_IPC_TYPE_3,
.default_fw_path = {
[SOF_IPC_TYPE_3] = "intel/sof",
},
.default_tplg_path = {
[SOF_IPC_TYPE_3] = "intel/sof-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_3] = "sof-cht.ri",
},
.nocodec_tplg_filename = "sof-cht-nocodec.tplg",
.ops = &sof_cht_ops,
};
static const struct acpi_device_id sof_baytrail_match[] = {
{ "80860F28", (unsigned long)&sof_acpi_baytrail_desc },
{ "808622A8", (unsigned long)&sof_acpi_cherrytrail_desc },
{ }
};
MODULE_DEVICE_TABLE(acpi, sof_baytrail_match);
static int sof_baytrail_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct sof_dev_desc *desc;
const struct acpi_device_id *id;
int ret;
id = acpi_match_device(dev->driver->acpi_match_table, dev);
if (!id)
return -ENODEV;
ret = snd_intel_acpi_dsp_driver_probe(dev, id->id);
if (ret != SND_INTEL_DSP_DRIVER_ANY && ret != SND_INTEL_DSP_DRIVER_SOF) {
dev_dbg(dev, "SOF ACPI driver not selected, aborting probe\n");
return -ENODEV;
}
desc = (const struct sof_dev_desc *)id->driver_data;
if (desc == &sof_acpi_baytrail_desc && soc_intel_is_byt_cr(pdev))
desc = &sof_acpi_baytrailcr_desc;
return sof_acpi_probe(pdev, desc);
}
/* acpi_driver definition */
static struct platform_driver snd_sof_acpi_intel_byt_driver = {
.probe = sof_baytrail_probe,
.remove = sof_acpi_remove,
.driver = {
.name = "sof-audio-acpi-intel-byt",
.pm = pm_ptr(&sof_acpi_pm),
.acpi_match_table = sof_baytrail_match,
},
};
module_platform_driver(snd_sof_acpi_intel_byt_driver);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("SOF support for Baytrail/Cherrytrail");
MODULE_IMPORT_NS("SND_SOC_SOF_XTENSA");
MODULE_IMPORT_NS("SND_SOC_SOF_ACPI_DEV");
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_ATOM_HIFI_EP");
+514
View File
@@ -0,0 +1,514 @@
// 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
//
// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com>
// Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
// Rander Wang <rander.wang@intel.com>
// Keyon Jie <yang.jie@linux.intel.com>
//
/*
* 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"
#include "../sof-audio.h"
static const struct snd_sof_debugfs_map cnl_dsp_debugfs[] = {
{"hda", HDA_DSP_HDA_BAR, 0, 0x4000, SOF_DEBUGFS_ACCESS_ALWAYS},
{"pp", HDA_DSP_PP_BAR, 0, 0x1000, SOF_DEBUGFS_ACCESS_ALWAYS},
{"dsp", HDA_DSP_BAR, 0, 0x10000, SOF_DEBUGFS_ACCESS_ALWAYS},
};
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 ack_received = false;
bool ipc_irq = false;
u32 hipcida, hipctdr;
hipcida = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDA);
hipctdr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCTDR);
if (hipcida & CNL_DSP_REG_HIPCIDA_DONE) {
/* DSP received the message */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
CNL_DSP_REG_HIPCCTL,
CNL_DSP_REG_HIPCCTL_DONE, 0);
cnl_ipc_dsp_done(sdev);
ipc_irq = true;
ack_received = true;
}
if (hipctdr & CNL_DSP_REG_HIPCTDR_BUSY) {
/* Message from DSP (reply or notification) */
u32 hipctdd = snd_sof_dsp_read(sdev, HDA_DSP_BAR,
CNL_DSP_REG_HIPCTDD);
u32 primary = hipctdr & CNL_DSP_REG_HIPCTDR_MSG_MASK;
u32 extension = hipctdd & CNL_DSP_REG_HIPCTDD_MSG_MASK;
if (primary & SOF_IPC4_MSG_DIR_MASK) {
/* Reply received */
if (likely(sdev->fw_state == SOF_FW_BOOT_COMPLETE)) {
struct sof_ipc4_msg *data = sdev->ipc->msg.reply_data;
data->primary = primary;
data->extension = extension;
guard(spinlock_irq)(&sdev->ipc_lock);
snd_sof_ipc_get_reply(sdev);
cnl_ipc_host_done(sdev);
snd_sof_ipc_reply(sdev, data->primary);
} else {
dev_dbg_ratelimited(sdev->dev,
"IPC reply before FW_READY: %#x|%#x\n",
primary, extension);
}
} else {
/* Notification received */
notification_data.primary = primary;
notification_data.extension = extension;
sdev->ipc->msg.rx_data = &notification_data;
snd_sof_ipc_msgs_rx(sdev);
sdev->ipc->msg.rx_data = NULL;
/* Let DSP know that we have finished processing the message */
cnl_ipc_host_done(sdev);
}
ipc_irq = true;
}
if (!ipc_irq)
/* This interrupt is not shared so no need to return IRQ_NONE. */
dev_dbg_ratelimited(sdev->dev, "nothing to do in IPC IRQ thread\n");
if (ack_received) {
struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
if (hdev->delayed_ipc_tx_msg)
cnl_ipc4_send_msg(sdev, hdev->delayed_ipc_tx_msg);
}
return IRQ_HANDLED;
}
EXPORT_SYMBOL_NS(cnl_ipc4_irq_thread, "SND_SOC_SOF_INTEL_CNL");
irqreturn_t cnl_ipc_irq_thread(int irq, void *context)
{
struct snd_sof_dev *sdev = context;
u32 hipci;
u32 hipcida;
u32 hipctdr;
u32 hipctdd;
u32 msg;
u32 msg_ext;
bool ipc_irq = false;
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);
hipci = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDR);
/* reply message from DSP */
if (hipcida & CNL_DSP_REG_HIPCIDA_DONE) {
msg_ext = hipci & CNL_DSP_REG_HIPCIDR_MSG_MASK;
msg = hipcida & CNL_DSP_REG_HIPCIDA_MSG_MASK;
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);
if (likely(sdev->fw_state == SOF_FW_BOOT_COMPLETE)) {
/* handle immediate reply from DSP core */
guard(spinlock_irq)(&sdev->ipc_lock);
hda_dsp_ipc_get_reply(sdev);
snd_sof_ipc_reply(sdev, msg);
cnl_ipc_dsp_done(sdev);
} else {
dev_dbg_ratelimited(sdev->dev, "IPC reply before FW_READY: %#x\n",
msg);
}
ipc_irq = true;
}
/* new message from DSP */
if (hipctdr & CNL_DSP_REG_HIPCTDR_BUSY) {
msg = hipctdr & CNL_DSP_REG_HIPCTDR_MSG_MASK;
msg_ext = hipctdd & CNL_DSP_REG_HIPCTDD_MSG_MASK;
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) {
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);
}
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;
}
EXPORT_SYMBOL_NS(cnl_ipc_irq_thread, "SND_SOC_SOF_INTEL_CNL");
static void cnl_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,
CNL_DSP_REG_HIPCTDR,
CNL_DSP_REG_HIPCTDR_BUSY,
CNL_DSP_REG_HIPCTDR_BUSY);
/*
* set done 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,
CNL_DSP_REG_HIPCTDA,
CNL_DSP_REG_HIPCTDA_DONE,
CNL_DSP_REG_HIPCTDA_DONE);
}
static void cnl_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,
CNL_DSP_REG_HIPCIDA,
CNL_DSP_REG_HIPCIDA_DONE,
CNL_DSP_REG_HIPCIDA_DONE);
/* unmask Done interrupt */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
CNL_DSP_REG_HIPCCTL,
CNL_DSP_REG_HIPCCTL_DONE,
CNL_DSP_REG_HIPCCTL_DONE);
}
static bool cnl_compact_ipc_compress(struct snd_sof_ipc_msg *msg,
u32 *dr, u32 *dd)
{
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;
/* send payload via the extended data register */
*dd = pm_gate->flags;
return true;
}
return false;
}
int cnl_ipc4_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
{
struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
struct sof_ipc4_msg *msg_data = msg->msg_data;
if (hda_ipc4_tx_is_busy(sdev)) {
hdev->delayed_ipc_tx_msg = msg;
return 0;
}
hdev->delayed_ipc_tx_msg = NULL;
/* send the message via mailbox */
if (msg_data->data_size)
sof_mailbox_write(sdev, sdev->host_box.offset, msg_data->data_ptr,
msg_data->data_size);
snd_sof_dsp_write(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDD, msg_data->extension);
snd_sof_dsp_write(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDR,
msg_data->primary | CNL_DSP_REG_HIPCIDR_BUSY);
hda_dsp_ipc4_schedule_d0i3_work(hdev, msg);
return 0;
}
EXPORT_SYMBOL_NS(cnl_ipc4_send_msg, "SND_SOC_SOF_INTEL_CNL");
int cnl_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
{
struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
struct sof_ipc_cmd_hdr *hdr;
u32 dr = 0;
u32 dd = 0;
/*
* Currently the only compact IPC supported is the PM_GATE
* IPC which is used for transitioning the DSP between the
* D0I0 and D0I3 states. And these are sent only during the
* set_power_state() op. Therefore, there will never be a case
* that a compact IPC results in the DSP exiting D0I3 without
* the host and FW being in sync.
*/
if (cnl_compact_ipc_compress(msg, &dr, &dd)) {
/* send the message via IPC registers */
snd_sof_dsp_write(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDD,
dd);
snd_sof_dsp_write(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDR,
CNL_DSP_REG_HIPCIDR_BUSY | dr);
return 0;
}
/* send the message via mailbox */
sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data,
msg->msg_size);
snd_sof_dsp_write(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDR,
CNL_DSP_REG_HIPCIDR_BUSY);
hdr = msg->msg_data;
/*
* Use mod_delayed_work() to schedule the delayed work
* to avoid scheduling multiple workqueue items when
* IPCs are sent at a high-rate. mod_delayed_work()
* modifies the timer if the work is pending.
* Also, a new delayed work should not be queued after the
* CTX_SAVE IPC, which is sent before the DSP enters D3.
*/
if (hdr->cmd != (SOF_IPC_GLB_PM_MSG | SOF_IPC_PM_CTX_SAVE))
mod_delayed_work(system_dfl_wq, &hdev->d0i3_work,
msecs_to_jiffies(SOF_HDA_D0I3_WORK_DELAY_MS));
return 0;
}
EXPORT_SYMBOL_NS(cnl_ipc_send_msg, "SND_SOC_SOF_INTEL_CNL");
void cnl_ipc_dump(struct snd_sof_dev *sdev)
{
u32 hipcctl;
u32 hipcida;
u32 hipctdr;
hda_ipc_irq_dump(sdev);
/* read IPC status */
hipcida = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCIDA);
hipcctl = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCCTL);
hipctdr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, CNL_DSP_REG_HIPCTDR);
/* dump the IPC regs */
/* TODO: parse the raw msg */
dev_err(sdev->dev,
"error: host status 0x%8.8x dsp status 0x%8.8x mask 0x%8.8x\n",
hipcida, hipctdr, hipcctl);
}
EXPORT_SYMBOL_NS(cnl_ipc_dump, "SND_SOC_SOF_INTEL_CNL");
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);
}
EXPORT_SYMBOL_NS(cnl_ipc4_dump, "SND_SOC_SOF_INTEL_CNL");
/* cannonlake ops */
struct snd_sof_dsp_ops sof_cnl_ops;
EXPORT_SYMBOL_NS(sof_cnl_ops, "SND_SOC_SOF_INTEL_CNL");
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 */
sof_cnl_ops.shutdown = hda_dsp_shutdown;
/* ipc */
if (sdev->pdata->ipc_type == SOF_IPC_TYPE_3) {
/* doorbell */
sof_cnl_ops.irq_thread = cnl_ipc_irq_thread;
/* ipc */
sof_cnl_ops.send_msg = cnl_ipc_send_msg;
/* debug */
sof_cnl_ops.ipc_dump = cnl_ipc_dump;
sof_cnl_ops.set_power_state = hda_dsp_set_power_state_ipc3;
}
if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4) {
struct sof_ipc4_fw_data *ipc4_data;
sdev->private = kzalloc_obj(*ipc4_data);
if (!sdev->private)
return -ENOMEM;
ipc4_data = sdev->private;
ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET;
ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_1_8;
/* External library loading support */
ipc4_data->load_library = hda_dsp_ipc4_load_library;
/* doorbell */
sof_cnl_ops.irq_thread = cnl_ipc4_irq_thread;
/* ipc */
sof_cnl_ops.send_msg = cnl_ipc4_send_msg;
/* debug */
sof_cnl_ops.ipc_dump = cnl_ipc4_dump;
sof_cnl_ops.set_power_state = hda_dsp_set_power_state_ipc4;
}
/* set DAI driver ops */
hda_set_dai_drv_ops(sdev, &sof_cnl_ops);
/* debug */
sof_cnl_ops.debug_map = cnl_dsp_debugfs;
sof_cnl_ops.debug_map_count = ARRAY_SIZE(cnl_dsp_debugfs);
/* pre/post fw run */
sof_cnl_ops.post_fw_run = hda_dsp_post_fw_run;
/* firmware run */
sof_cnl_ops.run = hda_dsp_cl_boot_firmware;
/* dsp core get/put */
sof_cnl_ops.core_get = hda_dsp_core_get;
return 0;
};
EXPORT_SYMBOL_NS(sof_cnl_ops_init, "SND_SOC_SOF_INTEL_CNL");
const struct sof_intel_dsp_desc cnl_chip_info = {
/* Cannonlake */
.cores_num = 4,
.init_core_mask = 1,
.host_managed_cores_mask = GENMASK(3, 0),
.ipc_req = CNL_DSP_REG_HIPCIDR,
.ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY,
.ipc_ack = CNL_DSP_REG_HIPCIDA,
.ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE,
.ipc_ctl = CNL_DSP_REG_HIPCCTL,
.rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS,
.rom_init_timeout = 300,
.ssp_count = CNL_SSP_COUNT,
.ssp_base_offset = CNL_SSP_BASE_OFFSET,
.sdw_shim_base = SDW_SHIM_BASE,
.sdw_alh_base = SDW_ALH_BASE,
.d0i3_offset = SOF_HDA_VS_D0I3C,
.read_sdw_lcount = hda_sdw_check_lcount_common,
.enable_sdw_irq = hda_common_enable_sdw_irq,
.check_sdw_irq = hda_common_check_sdw_irq,
.check_sdw_wakeen_irq = hda_sdw_check_wakeen_irq_common,
.sdw_process_wakeen = hda_sdw_process_wakeen_common,
.check_ipc_irq = hda_dsp_check_ipc_irq,
.cl_init = cl_dsp_init,
.power_down_dsp = hda_power_down_dsp,
.disable_interrupts = hda_dsp_disable_interrupts,
.hw_ip_version = SOF_INTEL_CAVS_1_8,
.platform = "cnl",
};
/*
* JasperLake is technically derived from IceLake, and should be in
* described in icl.c. However since JasperLake was designed with
* two cores, it cannot support the IceLake-specific power-up sequences
* which rely on core3. To simplify, JasperLake uses the CannonLake ops and
* is described in cnl.c
*/
const struct sof_intel_dsp_desc jsl_chip_info = {
/* Jasperlake */
.cores_num = 2,
.init_core_mask = 1,
.host_managed_cores_mask = GENMASK(1, 0),
.ipc_req = CNL_DSP_REG_HIPCIDR,
.ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY,
.ipc_ack = CNL_DSP_REG_HIPCIDA,
.ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE,
.ipc_ctl = CNL_DSP_REG_HIPCCTL,
.rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS,
.rom_init_timeout = 300,
.ssp_count = ICL_SSP_COUNT,
.ssp_base_offset = CNL_SSP_BASE_OFFSET,
.sdw_shim_base = SDW_SHIM_BASE,
.sdw_alh_base = SDW_ALH_BASE,
.d0i3_offset = SOF_HDA_VS_D0I3C,
.read_sdw_lcount = hda_sdw_check_lcount_common,
.enable_sdw_irq = hda_common_enable_sdw_irq,
.check_sdw_irq = hda_common_check_sdw_irq,
.check_sdw_wakeen_irq = hda_sdw_check_wakeen_irq_common,
.sdw_process_wakeen = hda_sdw_process_wakeen_common,
.check_ipc_irq = hda_dsp_check_ipc_irq,
.cl_init = cl_dsp_init,
.power_down_dsp = hda_power_down_dsp,
.disable_interrupts = hda_dsp_disable_interrupts,
.hw_ip_version = SOF_INTEL_CAVS_2_0,
.platform = "jsl",
};
EXPORT_SYMBOL_NS(jsl_chip_info, "SND_SOC_SOF_INTEL_CNL");
@@ -0,0 +1,35 @@
/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
/*
* This file is provided under a dual BSD/GPLv2 license. When using or
* redistributing this file, you may do so under either license.
*
* Copyright(c) 2020 Intel Corporation
*/
/*
* Intel extended manifest is a extra place to store Intel cavs specific
* metadata about firmware, for example LPRO/HPRO configuration is
* Intel cavs specific. This part of output binary is not signed.
*/
#ifndef __INTEL_CAVS_EXT_MANIFEST_H__
#define __INTEL_CAVS_EXT_MANIFEST_H__
#include <sound/sof/ext_manifest.h>
/* EXT_MAN_ELEM_PLATFORM_CONFIG_DATA elements identificators */
enum sof_cavs_config_elem_type {
SOF_EXT_MAN_CAVS_CONFIG_EMPTY = 0,
SOF_EXT_MAN_CAVS_CONFIG_CAVS_LPRO = 1,
SOF_EXT_MAN_CAVS_CONFIG_OUTBOX_SIZE = 2,
SOF_EXT_MAN_CAVS_CONFIG_INBOX_SIZE = 3,
};
/* EXT_MAN_ELEM_PLATFORM_CONFIG_DATA elements */
struct sof_ext_man_cavs_config_data {
struct sof_ext_man_elem_header hdr;
struct sof_config_elem elems[];
} __packed;
#endif /* __INTEL_CAVS_EXT_MANIFEST_H__ */
+112
View File
@@ -0,0 +1,112 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2018 Intel Corporation
//
// Authors: Keyon Jie <yang.jie@linux.intel.com>
#include <linux/io.h>
#include <sound/hdaudio.h>
#include <sound/hda_i915.h>
#include <sound/hda_codec.h>
#include <sound/hda_register.h>
#include "../sof-priv.h"
#include "hda.h"
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
#include "../../codecs/hdac_hda.h"
#define sof_hda_ext_ops snd_soc_hdac_hda_get_ops()
static void update_codec_wake_enable(struct hdac_bus *bus, unsigned int addr, bool link_power)
{
unsigned int mask = snd_hdac_chip_readw(bus, WAKEEN);
if (link_power)
mask &= ~BIT(addr);
else
mask |= BIT(addr);
snd_hdac_chip_updatew(bus, WAKEEN, STATESTS_INT_MASK, mask);
}
static void sof_hda_bus_link_power(struct hdac_device *codec, bool enable)
{
struct hdac_bus *bus = codec->bus;
bool oldstate = test_bit(codec->addr, &bus->codec_powered);
snd_hdac_ext_bus_link_power(codec, enable);
if (enable == oldstate)
return;
/*
* Both codec driver and controller can hold references to
* display power. To avoid unnecessary power-up/down cycles,
* controller doesn't immediately release its reference.
*
* If the codec driver powers down the link, release
* the controller reference as well.
*/
if (codec->addr == HDA_IDISP_ADDR && !enable)
snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, false);
/* WAKEEN needs to be set for disabled links */
update_codec_wake_enable(bus, codec->addr, enable);
}
static const struct hdac_bus_ops bus_core_ops = {
.command = snd_hdac_bus_send_cmd,
.get_response = snd_hdac_bus_get_response,
.link_power = sof_hda_bus_link_power,
};
#endif
/*
* This can be used for both with/without hda link support.
*/
void sof_hda_bus_init(struct snd_sof_dev *sdev, struct device *dev)
{
struct hdac_bus *bus = sof_to_bus(sdev);
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_LINK)
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
const struct sof_intel_dsp_desc *chip = get_chip_info(sdev->pdata);
snd_hdac_ext_bus_init(bus, dev, &bus_core_ops, sof_hda_ext_ops);
if (chip && chip->hw_ip_version >= SOF_INTEL_ACE_2_0)
bus->use_pio_for_commands = true;
#else
snd_hdac_ext_bus_init(bus, dev, NULL, NULL);
#endif
#else
memset(bus, 0, sizeof(*bus));
bus->dev = dev;
INIT_LIST_HEAD(&bus->stream_list);
bus->irq = -1;
/*
* There is only one HDA bus atm. keep the index as 0.
* Need to fix when there are more than one HDA bus.
*/
bus->idx = 0;
spin_lock_init(&bus->reg_lock);
#endif /* CONFIG_SND_SOC_SOF_HDA_LINK */
}
EXPORT_SYMBOL_NS(sof_hda_bus_init, "SND_SOC_SOF_INTEL_HDA_COMMON");
void sof_hda_bus_exit(struct snd_sof_dev *sdev)
{
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_LINK)
struct hdac_bus *bus = sof_to_bus(sdev);
snd_hdac_ext_bus_exit(bus);
#endif
}
EXPORT_SYMBOL_NS(sof_hda_bus_exit, "SND_SOC_SOF_INTEL_HDA_COMMON");
+457
View File
@@ -0,0 +1,457 @@
// SPDX-License-Identifier: GPL-2.0-only
//
// Copyright(c) 2018 Intel Corporation
//
// Authors: Keyon Jie <yang.jie@linux.intel.com>
//
#include <linux/module.h>
#include <sound/hdaudio_ext.h>
#include <sound/hda_register.h>
#include <sound/hda_codec.h>
#include <sound/hda_i915.h>
#include <sound/sof.h>
#include "../ops.h"
#include "hda.h"
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
#include "../../codecs/hdac_hda.h"
#define CODEC_PROBE_RETRIES 3
#define IDISP_VID_INTEL 0x80860000
static int hda_codec_mask = -1;
module_param_named(codec_mask, hda_codec_mask, int, 0444);
MODULE_PARM_DESC(codec_mask, "SOF HDA codec mask for probing");
/* load the legacy HDA codec driver */
static int request_codec_module(struct hda_codec *codec)
{
#ifdef MODULE
char alias[MODULE_NAME_LEN];
const char *mod = NULL;
switch (codec->probe_id) {
case HDA_CODEC_ID_GENERIC:
#if IS_MODULE(CONFIG_SND_HDA_GENERIC)
mod = "snd-hda-codec-generic";
#endif
break;
default:
snd_hdac_codec_modalias(&codec->core, alias, sizeof(alias));
mod = alias;
break;
}
if (mod) {
dev_dbg(&codec->core.dev, "loading codec module: %s\n", mod);
request_module(mod);
}
#endif /* MODULE */
return device_attach(hda_codec_dev(codec));
}
static int hda_codec_load_module(struct hda_codec *codec)
{
int ret;
ret = snd_hdac_device_register(&codec->core);
if (ret) {
dev_err(&codec->core.dev, "failed to register hdac device\n");
put_device(&codec->core.dev);
return ret;
}
ret = request_codec_module(codec);
if (ret <= 0) {
codec->probe_id = HDA_CODEC_ID_GENERIC;
ret = request_codec_module(codec);
}
return ret;
}
/* enable controller wake up event for all codecs with jack connectors */
void hda_codec_jack_wake_enable(struct snd_sof_dev *sdev, bool enable)
{
struct hda_bus *hbus = sof_to_hbus(sdev);
struct hdac_bus *bus = sof_to_bus(sdev);
struct hda_codec *codec;
unsigned int mask = 0;
unsigned int val = 0;
if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
return;
if (enable) {
list_for_each_codec(codec, hbus) {
/* only set WAKEEN when needed for HDaudio codecs */
mask |= BIT(codec->core.addr);
if (codec->jacktbl.used)
val |= BIT(codec->core.addr);
}
} else {
list_for_each_codec(codec, hbus) {
/* reset WAKEEN only HDaudio codecs */
mask |= BIT(codec->core.addr);
}
}
snd_hdac_chip_updatew(bus, WAKEEN, mask & STATESTS_INT_MASK, val);
}
EXPORT_SYMBOL_NS_GPL(hda_codec_jack_wake_enable, "SND_SOC_SOF_HDA_AUDIO_CODEC");
/* check jack status after resuming from suspend mode */
void hda_codec_jack_check(struct snd_sof_dev *sdev)
{
struct hda_bus *hbus = sof_to_hbus(sdev);
struct hda_codec *codec;
if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
return;
list_for_each_codec(codec, hbus)
/*
* Wake up all jack-detecting codecs regardless whether an event
* has been recorded in STATESTS
*/
if (codec->jacktbl.used)
pm_request_resume(&codec->core.dev);
}
EXPORT_SYMBOL_NS_GPL(hda_codec_jack_check, "SND_SOC_SOF_HDA_AUDIO_CODEC");
#if IS_ENABLED(CONFIG_SND_HDA_GENERIC)
#define is_generic_config(bus) \
((bus)->modelname && !strcmp((bus)->modelname, "generic"))
#else
#define is_generic_config(x) 0
#endif
static struct hda_codec *hda_codec_device_init(struct hdac_bus *bus, int addr, int type)
{
struct hda_codec *codec;
codec = snd_hda_codec_device_init(to_hda_bus(bus), addr, "ehdaudio%dD%d", bus->idx, addr);
if (IS_ERR(codec)) {
dev_err(bus->dev, "device init failed for hdac device\n");
return codec;
}
codec->core.type = type;
return codec;
}
/* probe individual codec */
static int hda_codec_probe(struct snd_sof_dev *sdev, int address)
{
struct hdac_hda_priv *hda_priv;
struct hda_bus *hbus = sof_to_hbus(sdev);
struct hda_codec *codec;
u32 hda_cmd = (address << 28) | (AC_NODE_ROOT << 20) |
(AC_VERB_PARAMETERS << 8) | AC_PAR_VENDOR_ID;
u32 resp = -1;
int ret, retry = 0;
do {
mutex_lock(&hbus->core.cmd_mutex);
snd_hdac_bus_send_cmd(&hbus->core, hda_cmd);
snd_hdac_bus_get_response(&hbus->core, address, &resp);
mutex_unlock(&hbus->core.cmd_mutex);
} while (resp == -1 && retry++ < CODEC_PROBE_RETRIES);
if (resp == -1)
return -EIO;
dev_dbg(sdev->dev, "HDA codec #%d probed OK: response: %x\n",
address, resp);
hda_priv = devm_kzalloc(sdev->dev, sizeof(*hda_priv), GFP_KERNEL);
if (!hda_priv)
return -ENOMEM;
codec = hda_codec_device_init(&hbus->core, address, HDA_DEV_LEGACY);
ret = PTR_ERR_OR_ZERO(codec);
if (ret < 0)
return ret;
hda_priv->codec = codec;
hda_priv->dev_index = address;
dev_set_drvdata(&codec->core.dev, hda_priv);
if ((resp & 0xFFFF0000) == IDISP_VID_INTEL) {
if (!hbus->core.audio_component) {
dev_dbg(sdev->dev,
"iDisp hw present but no driver\n");
ret = -ENOENT;
goto out;
}
hda_priv->need_display_power = true;
}
if (is_generic_config(hbus))
codec->probe_id = HDA_CODEC_ID_GENERIC;
else
codec->probe_id = 0;
ret = hda_codec_load_module(codec);
/*
* handle ret==0 (no driver bound) as an error, but pass
* other return codes without modification
*/
if (ret == 0)
ret = -ENOENT;
out:
if (ret < 0) {
snd_hdac_device_unregister(&codec->core);
put_device(&codec->core.dev);
}
return ret;
}
/* Codec initialization */
void hda_codec_probe_bus(struct snd_sof_dev *sdev)
{
struct hdac_bus *bus = sof_to_bus(sdev);
int i, ret;
if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
return;
/* probe codecs in avail slots */
for (i = 0; i < HDA_MAX_CODECS; i++) {
if (!(bus->codec_mask & (1 << i)))
continue;
ret = hda_codec_probe(sdev, i);
if (ret < 0) {
dev_warn(bus->dev, "codec #%d probe error, ret: %d\n",
i, ret);
bus->codec_mask &= ~BIT(i);
}
}
}
EXPORT_SYMBOL_NS_GPL(hda_codec_probe_bus, "SND_SOC_SOF_HDA_AUDIO_CODEC");
void hda_codec_check_for_state_change(struct snd_sof_dev *sdev)
{
struct hdac_bus *bus = sof_to_bus(sdev);
unsigned int codec_mask;
codec_mask = snd_hdac_chip_readw(bus, STATESTS);
if (codec_mask) {
hda_codec_jack_check(sdev);
snd_hdac_chip_writew(bus, STATESTS, codec_mask);
}
}
EXPORT_SYMBOL_NS_GPL(hda_codec_check_for_state_change, "SND_SOC_SOF_HDA_AUDIO_CODEC");
void hda_codec_detect_mask(struct snd_sof_dev *sdev)
{
struct hdac_bus *bus = sof_to_bus(sdev);
if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
return;
/* detect codecs */
if (!bus->codec_mask) {
bus->codec_mask = snd_hdac_chip_readw(bus, STATESTS);
dev_dbg(bus->dev, "codec_mask = 0x%lx\n", bus->codec_mask);
}
if (hda_codec_mask != -1) {
bus->codec_mask &= hda_codec_mask;
dev_dbg(bus->dev, "filtered codec_mask = 0x%lx\n",
bus->codec_mask);
}
}
EXPORT_SYMBOL_NS_GPL(hda_codec_detect_mask, "SND_SOC_SOF_HDA_AUDIO_CODEC");
void hda_codec_init_cmd_io(struct snd_sof_dev *sdev)
{
struct hdac_bus *bus = sof_to_bus(sdev);
if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
return;
/* initialize the codec command I/O */
snd_hdac_bus_init_cmd_io(bus);
}
EXPORT_SYMBOL_NS_GPL(hda_codec_init_cmd_io, "SND_SOC_SOF_HDA_AUDIO_CODEC");
void hda_codec_resume_cmd_io(struct snd_sof_dev *sdev)
{
struct hdac_bus *bus = sof_to_bus(sdev);
if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
return;
/* set up CORB/RIRB buffers if was on before suspend */
if (bus->cmd_dma_state)
snd_hdac_bus_init_cmd_io(bus);
}
EXPORT_SYMBOL_NS_GPL(hda_codec_resume_cmd_io, "SND_SOC_SOF_HDA_AUDIO_CODEC");
void hda_codec_stop_cmd_io(struct snd_sof_dev *sdev)
{
struct hdac_bus *bus = sof_to_bus(sdev);
if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
return;
/* initialize the codec command I/O */
snd_hdac_bus_stop_cmd_io(bus);
}
EXPORT_SYMBOL_NS_GPL(hda_codec_stop_cmd_io, "SND_SOC_SOF_HDA_AUDIO_CODEC");
void hda_codec_suspend_cmd_io(struct snd_sof_dev *sdev)
{
struct hdac_bus *bus = sof_to_bus(sdev);
if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
return;
/* stop the CORB/RIRB DMA if it is On */
if (bus->cmd_dma_state)
snd_hdac_bus_stop_cmd_io(bus);
}
EXPORT_SYMBOL_NS_GPL(hda_codec_suspend_cmd_io, "SND_SOC_SOF_HDA_AUDIO_CODEC");
void hda_codec_rirb_status_clear(struct snd_sof_dev *sdev)
{
struct hdac_bus *bus = sof_to_bus(sdev);
if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
return;
/* clear rirb status */
snd_hdac_chip_writeb(bus, RIRBSTS, RIRB_INT_MASK);
}
EXPORT_SYMBOL_NS_GPL(hda_codec_rirb_status_clear, "SND_SOC_SOF_HDA_AUDIO_CODEC");
void hda_codec_set_codec_wakeup(struct snd_sof_dev *sdev, bool status)
{
struct hdac_bus *bus = sof_to_bus(sdev);
if (sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
return;
snd_hdac_set_codec_wakeup(bus, status);
}
EXPORT_SYMBOL_NS_GPL(hda_codec_set_codec_wakeup, "SND_SOC_SOF_HDA_AUDIO_CODEC");
bool hda_codec_check_rirb_status(struct snd_sof_dev *sdev)
{
struct hdac_bus *bus = sof_to_bus(sdev);
bool active = false;
u32 rirb_status;
if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
return false;
rirb_status = snd_hdac_chip_readb(bus, RIRBSTS);
if (rirb_status & RIRB_INT_MASK) {
/*
* Clearing the interrupt status here ensures
* that no interrupt gets masked after the RIRB
* wp is read in snd_hdac_bus_update_rirb.
*/
snd_hdac_chip_writeb(bus, RIRBSTS,
RIRB_INT_MASK);
active = true;
if (rirb_status & RIRB_INT_RESPONSE)
snd_hdac_bus_update_rirb(bus);
}
return active;
}
EXPORT_SYMBOL_NS_GPL(hda_codec_check_rirb_status, "SND_SOC_SOF_HDA_AUDIO_CODEC");
void hda_codec_device_remove(struct snd_sof_dev *sdev)
{
struct hdac_bus *bus = sof_to_bus(sdev);
if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
return;
/* codec removal, invoke bus_device_remove */
snd_hdac_ext_bus_device_remove(bus);
}
EXPORT_SYMBOL_NS_GPL(hda_codec_device_remove, "SND_SOC_SOF_HDA_AUDIO_CODEC");
#endif /* CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC */
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) && IS_ENABLED(CONFIG_SND_HDA_CODEC_HDMI)
void hda_codec_i915_display_power(struct snd_sof_dev *sdev, bool enable)
{
struct hdac_bus *bus = sof_to_bus(sdev);
if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
return;
if (HDA_IDISP_CODEC(bus->codec_mask)) {
dev_dbg(bus->dev, "Turning i915 HDAC power %d\n", enable);
snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, enable);
}
}
EXPORT_SYMBOL_NS_GPL(hda_codec_i915_display_power, "SND_SOC_SOF_HDA_AUDIO_CODEC_I915");
int hda_codec_i915_init(struct snd_sof_dev *sdev)
{
struct hdac_bus *bus = sof_to_bus(sdev);
int ret;
if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
return 0;
/* i915 exposes a HDA codec for HDMI audio */
ret = snd_hdac_i915_init(bus);
if (ret < 0)
return ret;
/* codec_mask not yet known, power up for probe */
snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, true);
return 0;
}
EXPORT_SYMBOL_NS_GPL(hda_codec_i915_init, "SND_SOC_SOF_HDA_AUDIO_CODEC_I915");
int hda_codec_i915_exit(struct snd_sof_dev *sdev)
{
struct hdac_bus *bus = sof_to_bus(sdev);
if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) &&
sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC))
return 0;
if (!bus->audio_component)
return 0;
/* power down unconditionally */
snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, false);
return snd_hdac_i915_exit(bus);
}
EXPORT_SYMBOL_NS_GPL(hda_codec_i915_exit, "SND_SOC_SOF_HDA_AUDIO_CODEC_I915");
#endif
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("SOF support for HDaudio codecs");
@@ -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) 2022 Intel Corporation
//
/*
* common ops for SKL+ HDAudio platforms
*/
#include "../sof-priv.h"
#include "hda.h"
#include "../sof-audio.h"
const struct snd_sof_dsp_ops sof_hda_common_ops = {
/* probe/remove/shutdown */
.probe_early = hda_dsp_probe_early,
.probe = hda_dsp_probe,
.remove = hda_dsp_remove,
.remove_late = hda_dsp_remove_late,
/* Register IO uses direct mmio */
/* Block IO */
.block_read = sof_block_read,
.block_write = sof_block_write,
/* Mailbox IO */
.mailbox_read = sof_mailbox_read,
.mailbox_write = sof_mailbox_write,
/* ipc */
.get_mailbox_offset = hda_dsp_ipc_get_mailbox_offset,
.get_window_offset = hda_dsp_ipc_get_window_offset,
.ipc_msg_data = hda_ipc_msg_data,
.set_stream_data_offset = hda_set_stream_data_offset,
/* machine driver */
.machine_select = hda_machine_select,
.machine_register = sof_machine_register,
.machine_unregister = sof_machine_unregister,
.set_mach_params = hda_set_mach_params,
/* debug */
.dbg_dump = hda_dsp_dump,
.debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem,
/* stream callbacks */
.pcm_open = hda_dsp_pcm_open,
.pcm_close = hda_dsp_pcm_close,
.pcm_hw_params = hda_dsp_pcm_hw_params,
.pcm_hw_free = hda_dsp_stream_hw_free,
.pcm_trigger = hda_dsp_pcm_trigger,
.pcm_pointer = hda_dsp_pcm_pointer,
.pcm_ack = hda_dsp_pcm_ack,
.get_dai_frame_counter = hda_dsp_get_stream_llp,
.get_host_byte_counter = hda_dsp_get_stream_ldp,
/* firmware loading */
.load_firmware = snd_sof_load_firmware_raw,
/* pre/post fw run */
.pre_fw_run = hda_dsp_pre_fw_run,
/* firmware run */
.run = hda_dsp_cl_boot_firmware,
/* parse platform specific extended manifest */
.parse_platform_ext_manifest = hda_dsp_ext_man_get_cavs_config_data,
/* dsp core get/put */
/* trace callback */
.trace_init = hda_dsp_trace_init,
.trace_release = hda_dsp_trace_release,
.trace_trigger = hda_dsp_trace_trigger,
/* client ops */
.register_ipc_clients = hda_register_clients,
.unregister_ipc_clients = hda_unregister_clients,
/* DAI drivers */
.drv = skl_dai,
.num_drv = SOF_SKL_NUM_DAIS,
.is_chain_dma_supported = hda_is_chain_dma_supported,
/* PM */
.suspend = hda_dsp_suspend,
.resume = hda_dsp_resume,
.runtime_suspend = hda_dsp_runtime_suspend,
.runtime_resume = hda_dsp_runtime_resume,
.runtime_idle = hda_dsp_runtime_idle,
.set_hw_params_upon_resume = hda_dsp_set_hw_params_upon_resume,
/* ALSA HW info flags */
.hw_info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
.dsp_arch_ops = &sof_xtensa_arch_ops,
};
EXPORT_SYMBOL_NS(sof_hda_common_ops, "SND_SOC_SOF_INTEL_HDA_GENERIC");
+338
View File
@@ -0,0 +1,338 @@
// 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
//
// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com>
// Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
// Rander Wang <rander.wang@intel.com>
// Keyon Jie <yang.jie@linux.intel.com>
//
/*
* Hardware interface for generic Intel audio DSP HDA IP
*/
#include <linux/module.h>
#include <sound/hdaudio_ext.h>
#include <sound/hda_register.h>
#include <sound/hda_component.h>
#include <sound/hda-mlink.h>
#include "../ops.h"
#include "hda.h"
/*
* HDA Operations.
*/
int hda_dsp_ctrl_link_reset(struct snd_sof_dev *sdev, bool reset)
{
unsigned long timeout;
u32 gctl = 0;
u32 val;
/* 0 to enter reset and 1 to exit reset */
val = reset ? 0 : SOF_HDA_GCTL_RESET;
/* enter/exit HDA controller reset */
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_GCTL,
SOF_HDA_GCTL_RESET, val);
/* wait to enter/exit reset */
timeout = jiffies + msecs_to_jiffies(HDA_DSP_CTRL_RESET_TIMEOUT);
while (time_before(jiffies, timeout)) {
gctl = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_GCTL);
if ((gctl & SOF_HDA_GCTL_RESET) == val)
return 0;
usleep_range(500, 1000);
}
/* enter/exit reset failed */
dev_err(sdev->dev, "error: failed to %s HDA controller gctl 0x%x\n",
reset ? "reset" : "ready", gctl);
return -EIO;
}
int hda_dsp_ctrl_get_caps(struct snd_sof_dev *sdev)
{
struct hdac_bus *bus = sof_to_bus(sdev);
u32 cap, offset, feature;
int count = 0;
int ret;
/*
* On some devices, one reset cycle is necessary before reading
* capabilities
*/
ret = hda_dsp_ctrl_link_reset(sdev, true);
if (ret < 0)
return ret;
ret = hda_dsp_ctrl_link_reset(sdev, false);
if (ret < 0)
return ret;
offset = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_LLCH);
do {
dev_dbg(sdev->dev, "checking for capabilities at offset 0x%x\n",
offset & SOF_HDA_CAP_NEXT_MASK);
cap = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, offset);
if (cap == -1) {
dev_dbg(bus->dev, "Invalid capability reg read\n");
break;
}
feature = (cap & SOF_HDA_CAP_ID_MASK) >> SOF_HDA_CAP_ID_OFF;
switch (feature) {
case SOF_HDA_PP_CAP_ID:
dev_dbg(sdev->dev, "found DSP capability at 0x%x\n",
offset);
bus->ppcap = bus->remap_addr + offset;
sdev->bar[HDA_DSP_PP_BAR] = bus->ppcap;
break;
case SOF_HDA_SPIB_CAP_ID:
dev_dbg(sdev->dev, "found SPIB capability at 0x%x\n",
offset);
bus->spbcap = bus->remap_addr + offset;
sdev->bar[HDA_DSP_SPIB_BAR] = bus->spbcap;
break;
case SOF_HDA_DRSM_CAP_ID:
dev_dbg(sdev->dev, "found DRSM capability at 0x%x\n",
offset);
bus->drsmcap = bus->remap_addr + offset;
sdev->bar[HDA_DSP_DRSM_BAR] = bus->drsmcap;
break;
case SOF_HDA_GTS_CAP_ID:
dev_dbg(sdev->dev, "found GTS capability at 0x%x\n",
offset);
bus->gtscap = bus->remap_addr + offset;
break;
case SOF_HDA_ML_CAP_ID:
dev_dbg(sdev->dev, "found ML capability at 0x%x\n",
offset);
bus->mlcap = bus->remap_addr + offset;
break;
default:
dev_dbg(sdev->dev, "found capability %d at 0x%x\n",
feature, offset);
break;
}
offset = cap & SOF_HDA_CAP_NEXT_MASK;
} while (count++ <= SOF_HDA_MAX_CAPS && offset);
return 0;
}
EXPORT_SYMBOL_NS(hda_dsp_ctrl_get_caps, "SND_SOC_SOF_INTEL_HDA_COMMON");
void hda_dsp_ctrl_ppcap_enable(struct snd_sof_dev *sdev, bool enable)
{
u32 val = enable ? SOF_HDA_PPCTL_GPROCEN : 0;
snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL,
SOF_HDA_PPCTL_GPROCEN, val);
}
EXPORT_SYMBOL_NS(hda_dsp_ctrl_ppcap_enable, "SND_SOC_SOF_INTEL_HDA_COMMON");
void hda_dsp_ctrl_ppcap_int_enable(struct snd_sof_dev *sdev, bool enable)
{
u32 val = enable ? SOF_HDA_PPCTL_PIE : 0;
snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL,
SOF_HDA_PPCTL_PIE, val);
}
EXPORT_SYMBOL_NS(hda_dsp_ctrl_ppcap_int_enable, "SND_SOC_SOF_INTEL_HDA_COMMON");
void hda_dsp_ctrl_misc_clock_gating(struct snd_sof_dev *sdev, bool enable)
{
u32 val = enable ? PCI_CGCTL_MISCBDCGE_MASK : 0;
snd_sof_pci_update_bits(sdev, PCI_CGCTL, PCI_CGCTL_MISCBDCGE_MASK, val);
}
/*
* enable/disable audio dsp clock gating and power gating bits.
* This allows the HW to opportunistically power and clock gate
* the audio dsp when it is idle
*/
int hda_dsp_ctrl_clock_power_gating(struct snd_sof_dev *sdev, bool enable)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
u32 val;
/* enable/disable audio dsp clock gating */
val = enable ? PCI_CGCTL_ADSPDCGE : 0;
snd_sof_pci_update_bits(sdev, PCI_CGCTL, PCI_CGCTL_ADSPDCGE, val);
/* disable the DMI link when requested. But enable only if it wasn't disabled previously */
val = enable ? HDA_VS_INTEL_EM2_L1SEN : 0;
if (!enable || !hda->l1_disabled)
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_EM2,
HDA_VS_INTEL_EM2_L1SEN, val);
/* enable/disable audio dsp power gating */
val = enable ? 0 : PCI_PGCTL_ADSPPGD;
snd_sof_pci_update_bits(sdev, PCI_PGCTL, PCI_PGCTL_ADSPPGD, val);
return 0;
}
EXPORT_SYMBOL_NS(hda_dsp_ctrl_clock_power_gating, "SND_SOC_SOF_INTEL_HDA_COMMON");
int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool detect_codec)
{
struct hdac_bus *bus = sof_to_bus(sdev);
struct hdac_stream *stream;
int sd_offset, ret = 0;
u32 gctl;
if (bus->chip_init)
return 0;
hda_codec_set_codec_wakeup(sdev, true);
hda_dsp_ctrl_misc_clock_gating(sdev, false);
/* clear WAKE_STS if not in reset */
gctl = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_GCTL);
if (gctl & SOF_HDA_GCTL_RESET)
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
SOF_HDA_WAKESTS, SOF_HDA_WAKESTS_INT_MASK);
/* reset HDA controller */
ret = hda_dsp_ctrl_link_reset(sdev, true);
if (ret < 0) {
dev_err(sdev->dev, "error: failed to reset HDA controller\n");
goto err;
}
usleep_range(500, 1000);
/* exit HDA controller reset */
ret = hda_dsp_ctrl_link_reset(sdev, false);
if (ret < 0) {
dev_err(sdev->dev, "error: failed to exit HDA controller reset\n");
goto err;
}
usleep_range(1000, 1200);
/* Accept unsolicited responses */
snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_UNSOL, AZX_GCTL_UNSOL);
if (detect_codec)
hda_codec_detect_mask(sdev);
/* clear stream status */
list_for_each_entry(stream, &bus->stream_list, list) {
sd_offset = SOF_STREAM_SD_OFFSET(stream);
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
sd_offset + SOF_HDA_ADSP_REG_SD_STS,
SOF_HDA_CL_DMA_SD_INT_MASK);
}
/* clear WAKESTS */
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_WAKESTS,
bus->codec_mask);
hda_codec_rirb_status_clear(sdev);
/* clear interrupt status register */
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTSTS,
SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_ALL_STREAM);
hda_codec_init_cmd_io(sdev);
/* enable CIE and GIE interrupts */
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL,
SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN,
SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN);
/* program the position buffer */
if (bus->use_posbuf && bus->posbuf.addr) {
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_ADSP_DPLBASE,
(u32)bus->posbuf.addr);
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_ADSP_DPUBASE,
upper_32_bits(bus->posbuf.addr));
}
hda_bus_ml_reset_losidv(bus);
bus->chip_init = true;
err:
hda_dsp_ctrl_misc_clock_gating(sdev, true);
hda_codec_set_codec_wakeup(sdev, false);
return ret;
}
EXPORT_SYMBOL_NS(hda_dsp_ctrl_init_chip, "SND_SOC_SOF_INTEL_HDA_COMMON");
void hda_dsp_ctrl_stop_chip(struct snd_sof_dev *sdev)
{
struct hdac_bus *bus = sof_to_bus(sdev);
struct hdac_stream *stream;
int sd_offset;
if (!bus->chip_init)
return;
/* disable interrupts in stream descriptor */
list_for_each_entry(stream, &bus->stream_list, list) {
sd_offset = SOF_STREAM_SD_OFFSET(stream);
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
sd_offset +
SOF_HDA_ADSP_REG_SD_CTL,
SOF_HDA_CL_DMA_SD_INT_MASK,
0);
}
/* disable SIE for all streams */
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL,
SOF_HDA_INT_ALL_STREAM, 0);
/* disable controller CIE and GIE */
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL,
SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN,
0);
/* clear stream status */
list_for_each_entry(stream, &bus->stream_list, list) {
sd_offset = SOF_STREAM_SD_OFFSET(stream);
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
sd_offset + SOF_HDA_ADSP_REG_SD_STS,
SOF_HDA_CL_DMA_SD_INT_MASK);
}
/* clear WAKESTS */
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_WAKESTS,
SOF_HDA_WAKESTS_INT_MASK);
hda_codec_rirb_status_clear(sdev);
/* clear interrupt status register */
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTSTS,
SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_ALL_STREAM);
hda_codec_stop_cmd_io(sdev);
/* disable position buffer */
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,
SOF_HDA_ADSP_DPUBASE, 0);
}
bus->chip_init = false;
}
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("SOF helpers for HDaudio platforms");
MODULE_IMPORT_NS("SND_SOC_SOF_HDA_MLINK");
MODULE_IMPORT_NS("SND_SOC_SOF_HDA_AUDIO_CODEC");
MODULE_IMPORT_NS("SND_SOC_SOF_HDA_AUDIO_CODEC_I915");
@@ -0,0 +1,661 @@
// 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
#include <sound/pcm_params.h>
#include <sound/hdaudio_ext.h>
#include <sound/hda_register.h>
#include <sound/hda-mlink.h>
#include <sound/sof/ipc4/header.h>
#include <uapi/sound/sof/header.h>
#include "../ipc4-priv.h"
#include "../ipc4-topology.h"
#include "../sof-priv.h"
#include "../sof-audio.h"
#include "hda.h"
/* These ops are only applicable for the HDA DAI's in their current form */
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_LINK)
/*
* This function checks if the host dma channel corresponding
* to the link DMA stream_tag argument is assigned to one
* of the FEs connected to the BE DAI.
*/
static bool hda_check_fes(struct snd_soc_pcm_runtime *rtd,
int dir, int stream_tag)
{
struct snd_pcm_substream *fe_substream;
struct hdac_stream *fe_hstream;
struct snd_soc_dpcm *dpcm;
for_each_dpcm_fe(rtd, dir, dpcm) {
fe_substream = snd_soc_dpcm_get_substream(dpcm->fe, dir);
fe_hstream = fe_substream->runtime->private_data;
if (fe_hstream->stream_tag == stream_tag)
return true;
}
return false;
}
static struct hdac_ext_stream *
hda_link_stream_assign(struct hdac_bus *bus, struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct sof_intel_hda_stream *hda_stream;
const struct sof_intel_dsp_desc *chip;
struct snd_sof_dev *sdev;
struct hdac_ext_stream *res = NULL;
struct hdac_stream *hstream = NULL;
int stream_dir = substream->stream;
if (!bus->ppcap) {
dev_err(bus->dev, "stream type not supported\n");
return NULL;
}
guard(spinlock_irq)(&bus->reg_lock);
list_for_each_entry(hstream, &bus->stream_list, list) {
struct hdac_ext_stream *hext_stream =
stream_to_hdac_ext_stream(hstream);
if (hstream->direction != substream->stream)
continue;
hda_stream = hstream_to_sof_hda_stream(hext_stream);
sdev = hda_stream->sdev;
chip = get_chip_info(sdev->pdata);
/* check if link is available */
if (!hext_stream->link_locked) {
/*
* choose the first available link for platforms that do not have the
* PROCEN_FMT_QUIRK set.
*/
if (!(chip->quirks & SOF_INTEL_PROCEN_FMT_QUIRK)) {
res = hext_stream;
break;
}
if (hstream->opened) {
/*
* check if the stream tag matches the stream
* tag of one of the connected FEs
*/
if (hda_check_fes(rtd, stream_dir,
hstream->stream_tag)) {
res = hext_stream;
break;
}
} else {
res = hext_stream;
/*
* This must be a hostless stream.
* So reserve the host DMA channel.
*/
hda_stream->host_reserved = 1;
break;
}
}
}
if (res) {
/* Make sure that host and link DMA is decoupled. */
snd_hdac_ext_stream_decouple_locked(bus, res, true);
res->link_locked = 1;
res->link_substream = substream;
}
return res;
}
static struct hdac_ext_stream *hda_get_hext_stream(struct snd_sof_dev *sdev,
struct snd_soc_dai *cpu_dai,
struct snd_pcm_substream *substream)
{
return snd_soc_dai_get_dma_data(cpu_dai, substream);
}
static struct hdac_ext_stream *hda_ipc4_get_hext_stream(struct snd_sof_dev *sdev,
struct snd_soc_dai *cpu_dai,
struct snd_pcm_substream *substream)
{
struct snd_sof_widget *pipe_widget;
struct sof_ipc4_pipeline *pipeline;
struct snd_sof_widget *swidget;
struct snd_soc_dapm_widget *w;
w = snd_soc_dai_get_widget(cpu_dai, substream->stream);
swidget = w->dobj.private;
pipe_widget = swidget->spipe->pipe_widget;
pipeline = pipe_widget->private;
/* mark pipeline so that it can be skipped during FE trigger */
pipeline->skip_during_fe_trigger = true;
return snd_soc_dai_get_dma_data(cpu_dai, substream);
}
static struct hdac_ext_stream *hda_assign_hext_stream(struct snd_sof_dev *sdev,
struct snd_soc_dai *cpu_dai,
struct snd_pcm_substream *substream)
{
struct hdac_ext_stream *hext_stream;
hext_stream = hda_link_stream_assign(sof_to_bus(sdev), substream);
if (!hext_stream)
return NULL;
snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)hext_stream);
return hext_stream;
}
static void hda_release_hext_stream(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
struct snd_pcm_substream *substream)
{
struct hdac_ext_stream *hext_stream = hda_get_hext_stream(sdev, cpu_dai, substream);
snd_soc_dai_set_dma_data(cpu_dai, substream, NULL);
snd_hdac_ext_stream_release(hext_stream, HDAC_EXT_STREAM_TYPE_LINK);
}
static void hda_setup_hext_stream(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream,
unsigned int format_val)
{
snd_hdac_ext_stream_setup(hext_stream, format_val);
}
static void hda_reset_hext_stream(struct snd_sof_dev *sdev, struct hdac_ext_stream *hext_stream)
{
snd_hdac_ext_stream_reset(hext_stream);
}
static void hda_codec_dai_set_stream(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream,
struct hdac_stream *hstream)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
/* set the hdac_stream in the codec dai */
snd_soc_dai_set_stream(codec_dai, hstream, substream->stream);
}
static unsigned int hda_calc_stream_format(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
unsigned int link_bps;
unsigned int format_val;
unsigned int bits;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
link_bps = codec_dai->driver->playback.sig_bits;
else
link_bps = codec_dai->driver->capture.sig_bits;
bits = snd_hdac_stream_format_bits(params_format(params), SNDRV_PCM_SUBFORMAT_STD,
link_bps);
format_val = snd_hdac_stream_format(params_channels(params), bits, params_rate(params));
dev_dbg(sdev->dev, "format_val=%#x, rate=%d, ch=%d, format=%d\n", format_val,
params_rate(params), params_channels(params), params_format(params));
return format_val;
}
static struct hdac_ext_link *hda_get_hlink(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
struct hdac_bus *bus = sof_to_bus(sdev);
return snd_hdac_ext_bus_get_hlink_by_name(bus, codec_dai->component->name);
}
static unsigned int generic_calc_stream_format(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
unsigned int format_val;
unsigned int bits;
bits = snd_hdac_stream_format_bits(params_format(params), SNDRV_PCM_SUBFORMAT_STD,
params_physical_width(params));
format_val = snd_hdac_stream_format(params_channels(params), bits, params_rate(params));
dev_dbg(sdev->dev, "format_val=%#x, rate=%d, ch=%d, format=%d\n", format_val,
params_rate(params), params_channels(params), params_format(params));
return format_val;
}
static unsigned int dmic_calc_stream_format(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
unsigned int format_val;
snd_pcm_format_t format;
unsigned int channels;
unsigned int width;
unsigned int bits;
channels = params_channels(params);
format = params_format(params);
width = params_physical_width(params);
if (format == SNDRV_PCM_FORMAT_S16_LE) {
format = SNDRV_PCM_FORMAT_S32_LE;
channels /= 2;
width = 32;
}
bits = snd_hdac_stream_format_bits(format, SNDRV_PCM_SUBFORMAT_STD, width);
format_val = snd_hdac_stream_format(channels, bits, params_rate(params));
dev_dbg(sdev->dev, "format_val=%#x, rate=%d, ch=%d, format=%d\n", format_val,
params_rate(params), channels, format);
return format_val;
}
static struct hdac_ext_link *ssp_get_hlink(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream)
{
struct hdac_bus *bus = sof_to_bus(sdev);
return hdac_bus_eml_ssp_get_hlink(bus);
}
static struct hdac_ext_link *dmic_get_hlink(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream)
{
struct hdac_bus *bus = sof_to_bus(sdev);
return hdac_bus_eml_dmic_get_hlink(bus);
}
static struct hdac_ext_link *sdw_get_hlink(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream)
{
struct hdac_bus *bus = sof_to_bus(sdev);
return hdac_bus_eml_sdw_get_hlink(bus);
}
static int hda_ipc4_pre_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
struct snd_pcm_substream *substream, int cmd)
{
struct sof_ipc4_fw_data *ipc4_data = sdev->private;
struct snd_sof_widget *pipe_widget;
struct sof_ipc4_pipeline *pipeline;
struct snd_sof_widget *swidget;
struct snd_soc_dapm_widget *w;
int ret = 0;
w = snd_soc_dai_get_widget(cpu_dai, substream->stream);
swidget = w->dobj.private;
pipe_widget = swidget->spipe->pipe_widget;
pipeline = pipe_widget->private;
if (pipe_widget->instance_id < 0)
return 0;
guard(mutex)(&ipc4_data->pipeline_state_mutex);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_STOP:
ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id,
SOF_IPC4_PIPE_PAUSED);
if (ret < 0)
return ret;
pipeline->state = SOF_IPC4_PIPE_PAUSED;
break;
default:
dev_err(sdev->dev, "unknown trigger command %d\n", cmd);
ret = -EINVAL;
}
return ret;
}
static int hda_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
struct snd_pcm_substream *substream, int cmd)
{
struct hdac_ext_stream *hext_stream = snd_soc_dai_get_dma_data(cpu_dai, substream);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
snd_hdac_ext_stream_start(hext_stream);
break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
/*
* Save the LLP registers since in case of PAUSE the LLP
* register are not reset to 0, the delay calculation will use
* the saved offsets for compensating the delay calculation.
*/
hext_stream->pplcllpl = readl(hext_stream->pplc_addr + AZX_REG_PPLCLLPL);
hext_stream->pplcllpu = readl(hext_stream->pplc_addr + AZX_REG_PPLCLLPU);
snd_hdac_ext_stream_clear(hext_stream);
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_STOP:
hext_stream->pplcllpl = 0;
hext_stream->pplcllpu = 0;
snd_hdac_ext_stream_clear(hext_stream);
break;
default:
dev_err(sdev->dev, "unknown trigger command %d\n", cmd);
return -EINVAL;
}
return 0;
}
static int hda_ipc4_post_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
struct snd_pcm_substream *substream, int cmd)
{
struct sof_ipc4_fw_data *ipc4_data = sdev->private;
struct snd_sof_widget *pipe_widget;
struct sof_ipc4_pipeline *pipeline;
struct snd_sof_widget *swidget;
struct snd_soc_dapm_widget *w;
int ret = 0;
w = snd_soc_dai_get_widget(cpu_dai, substream->stream);
swidget = w->dobj.private;
pipe_widget = swidget->spipe->pipe_widget;
pipeline = pipe_widget->private;
if (pipe_widget->instance_id < 0)
return 0;
guard(mutex)(&ipc4_data->pipeline_state_mutex);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
if (pipeline->state != SOF_IPC4_PIPE_PAUSED) {
ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id,
SOF_IPC4_PIPE_PAUSED);
if (ret < 0)
return ret;
pipeline->state = SOF_IPC4_PIPE_PAUSED;
}
ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id,
SOF_IPC4_PIPE_RUNNING);
if (ret < 0)
return ret;
pipeline->state = SOF_IPC4_PIPE_RUNNING;
swidget->spipe->started_count++;
break;
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id,
SOF_IPC4_PIPE_RUNNING);
if (ret < 0)
return ret;
pipeline->state = SOF_IPC4_PIPE_RUNNING;
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_STOP:
/*
* STOP/SUSPEND trigger is invoked only once when all users of this pipeline have
* been stopped. So, clear the started_count so that the pipeline can be reset
*/
swidget->spipe->started_count = 0;
break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
break;
default:
dev_err(sdev->dev, "unknown trigger command %d\n", cmd);
ret = -EINVAL;
break;
}
return ret;
}
static const struct hda_dai_widget_dma_ops hda_ipc4_dma_ops = {
.get_hext_stream = hda_ipc4_get_hext_stream,
.assign_hext_stream = hda_assign_hext_stream,
.release_hext_stream = hda_release_hext_stream,
.setup_hext_stream = hda_setup_hext_stream,
.reset_hext_stream = hda_reset_hext_stream,
.pre_trigger = hda_ipc4_pre_trigger,
.trigger = hda_trigger,
.post_trigger = hda_ipc4_post_trigger,
.codec_dai_set_stream = hda_codec_dai_set_stream,
.calc_stream_format = hda_calc_stream_format,
.get_hlink = hda_get_hlink,
};
static const struct hda_dai_widget_dma_ops ssp_ipc4_dma_ops = {
.get_hext_stream = hda_ipc4_get_hext_stream,
.assign_hext_stream = hda_assign_hext_stream,
.release_hext_stream = hda_release_hext_stream,
.setup_hext_stream = hda_setup_hext_stream,
.reset_hext_stream = hda_reset_hext_stream,
.pre_trigger = hda_ipc4_pre_trigger,
.trigger = hda_trigger,
.post_trigger = hda_ipc4_post_trigger,
.calc_stream_format = generic_calc_stream_format,
.get_hlink = ssp_get_hlink,
};
static const struct hda_dai_widget_dma_ops dmic_ipc4_dma_ops = {
.get_hext_stream = hda_ipc4_get_hext_stream,
.assign_hext_stream = hda_assign_hext_stream,
.release_hext_stream = hda_release_hext_stream,
.setup_hext_stream = hda_setup_hext_stream,
.reset_hext_stream = hda_reset_hext_stream,
.pre_trigger = hda_ipc4_pre_trigger,
.trigger = hda_trigger,
.post_trigger = hda_ipc4_post_trigger,
.calc_stream_format = dmic_calc_stream_format,
.get_hlink = dmic_get_hlink,
};
static const struct hda_dai_widget_dma_ops sdw_ipc4_dma_ops = {
.get_hext_stream = hda_ipc4_get_hext_stream,
.assign_hext_stream = hda_assign_hext_stream,
.release_hext_stream = hda_release_hext_stream,
.setup_hext_stream = hda_setup_hext_stream,
.reset_hext_stream = hda_reset_hext_stream,
.pre_trigger = hda_ipc4_pre_trigger,
.trigger = hda_trigger,
.post_trigger = hda_ipc4_post_trigger,
.calc_stream_format = generic_calc_stream_format,
.get_hlink = sdw_get_hlink,
};
static const struct hda_dai_widget_dma_ops hda_ipc4_chain_dma_ops = {
.get_hext_stream = hda_get_hext_stream,
.assign_hext_stream = hda_assign_hext_stream,
.release_hext_stream = hda_release_hext_stream,
.setup_hext_stream = hda_setup_hext_stream,
.reset_hext_stream = hda_reset_hext_stream,
.trigger = hda_trigger,
.codec_dai_set_stream = hda_codec_dai_set_stream,
.calc_stream_format = hda_calc_stream_format,
.get_hlink = hda_get_hlink,
};
static const struct hda_dai_widget_dma_ops sdw_ipc4_chain_dma_ops = {
.get_hext_stream = hda_get_hext_stream,
.assign_hext_stream = hda_assign_hext_stream,
.release_hext_stream = hda_release_hext_stream,
.setup_hext_stream = hda_setup_hext_stream,
.reset_hext_stream = hda_reset_hext_stream,
.trigger = hda_trigger,
.calc_stream_format = generic_calc_stream_format,
.get_hlink = sdw_get_hlink,
};
static int hda_ipc3_post_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
struct snd_pcm_substream *substream, int cmd)
{
struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(cpu_dai, substream->stream);
switch (cmd) {
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_STOP:
{
struct snd_sof_dai_config_data data = { 0 };
int ret;
data.dai_data = DMA_CHAN_INVALID;
ret = hda_dai_config(w, SOF_DAI_CONFIG_FLAGS_HW_FREE, &data);
if (ret < 0)
return ret;
break;
}
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
return hda_dai_config(w, SOF_DAI_CONFIG_FLAGS_PAUSE, NULL);
default:
break;
}
return 0;
}
static const struct hda_dai_widget_dma_ops hda_ipc3_dma_ops = {
.get_hext_stream = hda_get_hext_stream,
.assign_hext_stream = hda_assign_hext_stream,
.release_hext_stream = hda_release_hext_stream,
.setup_hext_stream = hda_setup_hext_stream,
.reset_hext_stream = hda_reset_hext_stream,
.trigger = hda_trigger,
.post_trigger = hda_ipc3_post_trigger,
.codec_dai_set_stream = hda_codec_dai_set_stream,
.calc_stream_format = hda_calc_stream_format,
.get_hlink = hda_get_hlink,
};
static struct hdac_ext_stream *
hda_dspless_get_hext_stream(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai,
struct snd_pcm_substream *substream)
{
struct hdac_stream *hstream = substream->runtime->private_data;
return stream_to_hdac_ext_stream(hstream);
}
static void hda_dspless_setup_hext_stream(struct snd_sof_dev *sdev,
struct hdac_ext_stream *hext_stream,
unsigned int format_val)
{
/*
* Save the format_val which was adjusted by the maxbps of the codec.
* This information is not available on the FE side since there we are
* using dummy_codec.
*/
hext_stream->hstream.format_val = format_val;
}
static const struct hda_dai_widget_dma_ops hda_dspless_dma_ops = {
.get_hext_stream = hda_dspless_get_hext_stream,
.setup_hext_stream = hda_dspless_setup_hext_stream,
.codec_dai_set_stream = hda_codec_dai_set_stream,
.calc_stream_format = hda_calc_stream_format,
.get_hlink = hda_get_hlink,
};
static const struct hda_dai_widget_dma_ops sdw_dspless_dma_ops = {
.get_hext_stream = hda_dspless_get_hext_stream,
.setup_hext_stream = hda_dspless_setup_hext_stream,
.calc_stream_format = generic_calc_stream_format,
.get_hlink = sdw_get_hlink,
};
#endif
const struct hda_dai_widget_dma_ops *
hda_select_dai_widget_ops(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
{
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_LINK)
struct snd_sof_dai *sdai;
const struct sof_intel_dsp_desc *chip;
chip = get_chip_info(sdev->pdata);
sdai = swidget->private;
if (sdev->dspless_mode_selected) {
switch (sdai->type) {
case SOF_DAI_INTEL_HDA:
return &hda_dspless_dma_ops;
case SOF_DAI_INTEL_ALH:
if (chip->hw_ip_version < SOF_INTEL_ACE_2_0)
return NULL;
return &sdw_dspless_dma_ops;
default:
return NULL;
}
}
switch (sdev->pdata->ipc_type) {
case SOF_IPC_TYPE_3:
{
struct sof_dai_private_data *private = sdai->private;
if (private->dai_config->type == SOF_DAI_INTEL_HDA)
return &hda_ipc3_dma_ops;
break;
}
case SOF_IPC_TYPE_4:
{
struct snd_sof_widget *pipe_widget = swidget->spipe->pipe_widget;
struct sof_ipc4_pipeline *pipeline = pipe_widget->private;
switch (sdai->type) {
case SOF_DAI_INTEL_HDA:
if (pipeline->use_chain_dma)
return &hda_ipc4_chain_dma_ops;
return &hda_ipc4_dma_ops;
case SOF_DAI_INTEL_SSP:
if (chip->hw_ip_version < SOF_INTEL_ACE_2_0)
return NULL;
return &ssp_ipc4_dma_ops;
case SOF_DAI_INTEL_DMIC:
if (chip->hw_ip_version < SOF_INTEL_ACE_2_0)
return NULL;
return &dmic_ipc4_dma_ops;
case SOF_DAI_INTEL_ALH:
if (chip->hw_ip_version < SOF_INTEL_ACE_2_0)
return NULL;
if (pipeline->use_chain_dma)
return &sdw_ipc4_chain_dma_ops;
return &sdw_ipc4_dma_ops;
default:
break;
}
break;
}
default:
break;
}
#endif
return NULL;
}
+960
View File
@@ -0,0 +1,960 @@
// 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
//
// Authors: Keyon Jie <yang.jie@linux.intel.com>
//
#include <sound/pcm_params.h>
#include <sound/hdaudio_ext.h>
#include <sound/hda-mlink.h>
#include <sound/hda_register.h>
#include <sound/intel-nhlt.h>
#include <sound/sof/ipc4/header.h>
#include <uapi/sound/sof/header.h>
#include "../ipc4-priv.h"
#include "../ipc4-topology.h"
#include "../sof-priv.h"
#include "../sof-audio.h"
#include "hda.h"
/*
* 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");
int hda_dai_config(struct snd_soc_dapm_widget *w, unsigned int flags,
struct snd_sof_dai_config_data *data)
{
struct snd_sof_widget *swidget = w->dobj.private;
const struct sof_ipc_tplg_ops *tplg_ops;
struct snd_sof_dev *sdev;
int ret;
if (!swidget)
return 0;
sdev = widget_to_sdev(w);
tplg_ops = sof_ipc_get_ops(sdev, tplg);
if (tplg_ops && tplg_ops->dai_config) {
ret = tplg_ops->dai_config(sdev, swidget, flags, data);
if (ret < 0) {
dev_err(sdev->dev, "DAI config with flags %x failed for widget %s\n",
flags, w->name);
return ret;
}
}
return 0;
}
EXPORT_SYMBOL_NS(hda_dai_config, "SND_SOC_SOF_INTEL_HDA_COMMON");
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_LINK)
static struct snd_sof_dev *dai_to_sdev(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(cpu_dai, substream->stream);
return widget_to_sdev(w);
}
static const struct hda_dai_widget_dma_ops *
hda_dai_get_ops(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai)
{
struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(cpu_dai, substream->stream);
struct snd_sof_widget *swidget;
struct snd_sof_dev *sdev;
struct snd_sof_dai *sdai;
/*
* this is unlikely if the topology and the machine driver DAI links match.
* But if there's a missing DAI link in topology, this will prevent a NULL pointer
* dereference later on.
*/
if (!w) {
dev_err(cpu_dai->dev, "%s: widget is NULL\n", __func__);
return NULL;
}
sdev = widget_to_sdev(w);
swidget = w->dobj.private;
if (!swidget) {
dev_err(sdev->dev, "%s: swidget is NULL\n", __func__);
return NULL;
}
if (sdev->dspless_mode_selected)
return hda_select_dai_widget_ops(sdev, swidget);
sdai = swidget->private;
/* select and set the DAI widget ops if not set already */
if (!sdai->platform_private) {
const struct hda_dai_widget_dma_ops *ops =
hda_select_dai_widget_ops(sdev, swidget);
if (!ops)
return NULL;
/* check if mandatory ops are set */
if (!ops || !ops->get_hext_stream)
return NULL;
sdai->platform_private = ops;
}
return sdai->platform_private;
}
static int
hda_link_dma_cleanup(struct snd_pcm_substream *substream,
struct hdac_ext_stream *hext_stream,
struct snd_soc_dai *cpu_dai, bool release)
{
const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, cpu_dai);
struct sof_intel_hda_stream *hda_stream;
struct hdac_ext_link *hlink;
struct snd_sof_dev *sdev;
int stream_tag;
if (!ops) {
dev_err(cpu_dai->dev, "DAI widget ops not set\n");
return -EINVAL;
}
sdev = dai_to_sdev(substream, cpu_dai);
hlink = ops->get_hlink(sdev, substream);
if (!hlink)
return -EINVAL;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
stream_tag = hdac_stream(hext_stream)->stream_tag;
snd_hdac_ext_bus_link_clear_stream_id(hlink, stream_tag);
}
if (!release) {
/*
* Force stream reconfiguration without releasing the channel on
* subsequent stream restart (without free), including LinkDMA
* reset.
* The stream is released via hda_dai_hw_free()
*/
hext_stream->link_prepared = 0;
return 0;
}
if (ops->release_hext_stream)
ops->release_hext_stream(sdev, cpu_dai, substream);
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_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params, struct snd_soc_dai *cpu_dai)
{
const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, cpu_dai);
struct hdac_ext_stream *hext_stream;
struct hdac_stream *hstream;
struct hdac_ext_link *hlink;
struct snd_sof_dev *sdev;
int stream_tag;
if (!ops) {
dev_err(cpu_dai->dev, "DAI widget ops not set\n");
return -EINVAL;
}
sdev = dai_to_sdev(substream, cpu_dai);
hlink = ops->get_hlink(sdev, substream);
if (!hlink)
return -EINVAL;
hext_stream = ops->get_hext_stream(sdev, cpu_dai, substream);
if (!hext_stream) {
if (ops->assign_hext_stream)
hext_stream = ops->assign_hext_stream(sdev, cpu_dai, substream);
}
if (!hext_stream)
return -EBUSY;
hstream = &hext_stream->hstream;
stream_tag = hstream->stream_tag;
if (hext_stream->hstream.direction == SNDRV_PCM_STREAM_PLAYBACK)
snd_hdac_ext_bus_link_set_stream_id(hlink, stream_tag);
/* set the hdac_stream in the codec dai */
if (ops->codec_dai_set_stream)
ops->codec_dai_set_stream(sdev, substream, hstream);
if (ops->reset_hext_stream)
ops->reset_hext_stream(sdev, hext_stream);
if (ops->calc_stream_format && ops->setup_hext_stream) {
unsigned int format_val = ops->calc_stream_format(sdev, substream, params);
ops->setup_hext_stream(sdev, hext_stream, format_val);
}
hext_stream->link_prepared = 1;
return 0;
}
static int __maybe_unused hda_dai_hw_free(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, cpu_dai);
struct hdac_ext_stream *hext_stream;
struct snd_sof_dev *sdev = dai_to_sdev(substream, cpu_dai);
if (!ops) {
dev_err(cpu_dai->dev, "DAI widget ops not set\n");
return -EINVAL;
}
hext_stream = ops->get_hext_stream(sdev, cpu_dai, substream);
if (!hext_stream)
return 0;
return hda_link_dma_cleanup(substream, hext_stream, cpu_dai, true);
}
static int __maybe_unused hda_dai_hw_params_data(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai,
struct snd_sof_dai_config_data *data,
unsigned int flags)
{
struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(dai, substream->stream);
const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, dai);
struct hdac_ext_stream *hext_stream;
struct snd_sof_dev *sdev = widget_to_sdev(w);
int ret;
if (!ops) {
dev_err(sdev->dev, "DAI widget ops not set\n");
return -EINVAL;
}
hext_stream = ops->get_hext_stream(sdev, dai, substream);
if (hext_stream && hext_stream->link_prepared)
return 0;
ret = hda_link_dma_hw_params(substream, params, dai);
if (ret < 0)
return ret;
hext_stream = ops->get_hext_stream(sdev, dai, substream);
flags |= SOF_DAI_CONFIG_FLAGS_2_STEP_STOP << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT;
data->dai_data = hdac_stream(hext_stream)->stream_tag - 1;
return hda_dai_config(w, flags, data);
}
static int __maybe_unused hda_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct snd_sof_dai_config_data data = { 0 };
unsigned int flags = SOF_DAI_CONFIG_FLAGS_HW_PARAMS;
return hda_dai_hw_params_data(substream, params, dai, &data, flags);
}
/*
* In contrast to IPC3, the dai trigger in IPC4 mixes pipeline state changes
* (over IPC channel) and DMA state change (direct host register changes).
*/
static int __maybe_unused hda_dai_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, dai);
struct hdac_ext_stream *hext_stream;
struct snd_sof_dev *sdev;
int ret;
if (!ops) {
dev_err(dai->dev, "DAI widget ops not set\n");
return -EINVAL;
}
dev_dbg(dai->dev, "cmd=%d dai %s direction %d\n", cmd,
dai->name, substream->stream);
sdev = dai_to_sdev(substream, dai);
hext_stream = ops->get_hext_stream(sdev, dai, substream);
if (!hext_stream)
return -EINVAL;
if (ops->pre_trigger) {
ret = ops->pre_trigger(sdev, dai, substream, cmd);
if (ret < 0)
return ret;
}
if (ops->trigger) {
ret = ops->trigger(sdev, dai, substream, cmd);
if (ret < 0)
return ret;
}
if (ops->post_trigger) {
ret = ops->post_trigger(sdev, dai, substream, cmd);
if (ret < 0)
return ret;
}
switch (cmd) {
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
ret = hda_link_dma_cleanup(substream, hext_stream, dai,
cmd != SNDRV_PCM_TRIGGER_STOP);
if (ret < 0) {
dev_err(sdev->dev, "%s: failed to clean up link DMA\n", __func__);
return ret;
}
break;
default:
break;
}
return 0;
}
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
static int hda_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
int stream = substream->stream;
return hda_dai_hw_params(substream, &rtd->dpcm[stream].hw_params, dai);
}
static const struct snd_soc_dai_ops hda_dai_ops = {
.hw_params = hda_dai_hw_params,
.hw_free = hda_dai_hw_free,
.trigger = hda_dai_trigger,
.prepare = hda_dai_prepare,
};
#endif
static struct sof_ipc4_copier *widget_to_copier(struct snd_soc_dapm_widget *w)
{
struct snd_sof_widget *swidget = w->dobj.private;
struct snd_sof_dai *sdai = swidget->private;
struct sof_ipc4_copier *ipc4_copier = (struct sof_ipc4_copier *)sdai->private;
return ipc4_copier;
}
static int non_hda_dai_hw_params_data(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *cpu_dai,
struct snd_sof_dai_config_data *data,
unsigned int flags)
{
struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(cpu_dai, substream->stream);
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct sof_ipc4_dma_config_tlv *dma_config_tlv;
const struct hda_dai_widget_dma_ops *ops;
struct sof_ipc4_dma_config *dma_config;
struct sof_ipc4_copier *ipc4_copier;
struct hdac_ext_stream *hext_stream;
struct hdac_stream *hstream;
struct snd_sof_dev *sdev;
struct snd_soc_dai *dai;
int cpu_dai_id;
int stream_id;
int ret;
ops = hda_dai_get_ops(substream, cpu_dai);
if (!ops) {
dev_err(cpu_dai->dev, "DAI widget ops not set\n");
return -EINVAL;
}
sdev = widget_to_sdev(w);
hext_stream = ops->get_hext_stream(sdev, cpu_dai, substream);
/* nothing more to do if the link is already prepared */
if (hext_stream && hext_stream->link_prepared)
return 0;
/* use HDaudio stream handling */
ret = hda_dai_hw_params_data(substream, params, cpu_dai, data, flags);
if (ret < 0) {
dev_err(cpu_dai->dev, "%s: hda_dai_hw_params_data failed: %d\n", __func__, ret);
return ret;
}
if (sdev->dspless_mode_selected)
return 0;
/* get stream_id */
hext_stream = ops->get_hext_stream(sdev, cpu_dai, substream);
if (!hext_stream) {
dev_err(cpu_dai->dev, "%s: no hext_stream found\n", __func__);
return -ENODEV;
}
hstream = &hext_stream->hstream;
stream_id = hstream->stream_tag;
if (!stream_id) {
dev_err(cpu_dai->dev, "%s: no stream_id allocated\n", __func__);
return -ENODEV;
}
/* configure TLV */
ipc4_copier = widget_to_copier(w);
for_each_rtd_cpu_dais(rtd, cpu_dai_id, dai) {
if (dai == cpu_dai)
break;
}
dma_config_tlv = &ipc4_copier->dma_config_tlv[cpu_dai_id];
dma_config_tlv->type = SOF_IPC4_GTW_DMA_CONFIG_ID;
/* dma_config_priv_size is zero */
dma_config_tlv->length = sizeof(dma_config_tlv->dma_config);
dma_config = &dma_config_tlv->dma_config;
dma_config->dma_method = SOF_IPC4_DMA_METHOD_HDA;
dma_config->pre_allocated_by_host = 1;
dma_config->dma_channel_id = stream_id - 1;
dma_config->stream_id = stream_id;
/*
* Currently we use a DMA for each device in ALH blob. The device will
* be copied in sof_ipc4_prepare_copier_module.
*/
dma_config->dma_stream_channel_map.device_count = 1;
dma_config->dma_priv_config_size = 0;
return 0;
}
static int non_hda_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *cpu_dai)
{
struct snd_sof_dai_config_data data = { 0 };
unsigned int flags = SOF_DAI_CONFIG_FLAGS_HW_PARAMS;
return non_hda_dai_hw_params_data(substream, params, cpu_dai, &data, flags);
}
static int non_hda_dai_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
int stream = substream->stream;
return non_hda_dai_hw_params(substream, &rtd->dpcm[stream].hw_params, cpu_dai);
}
static const struct snd_soc_dai_ops ssp_dai_ops = {
.hw_params = non_hda_dai_hw_params,
.hw_free = hda_dai_hw_free,
.trigger = hda_dai_trigger,
.prepare = non_hda_dai_prepare,
};
static const struct snd_soc_dai_ops dmic_dai_ops = {
.hw_params = non_hda_dai_hw_params,
.hw_free = hda_dai_hw_free,
.trigger = hda_dai_trigger,
.prepare = non_hda_dai_prepare,
};
int sdw_hda_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *cpu_dai,
int link_id,
int intel_alh_id)
{
struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(cpu_dai, substream->stream);
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct sof_ipc4_dma_config_tlv *dma_config_tlv;
struct snd_sof_dai_config_data data = { 0 };
unsigned int flags = SOF_DAI_CONFIG_FLAGS_HW_PARAMS;
const struct hda_dai_widget_dma_ops *ops;
struct sof_ipc4_dma_config *dma_config;
struct sof_ipc4_copier *ipc4_copier;
struct hdac_ext_stream *hext_stream;
struct snd_soc_dai *dai;
struct snd_sof_dev *sdev;
bool cpu_dai_found = false;
int cpu_dai_id;
int ch_mask;
int ret;
int i;
if (!w) {
dev_err(cpu_dai->dev, "%s widget not found, check amp link num in the topology\n",
cpu_dai->name);
return -EINVAL;
}
ops = hda_dai_get_ops(substream, cpu_dai);
if (!ops) {
dev_err(cpu_dai->dev, "DAI widget ops not set\n");
return -EINVAL;
}
sdev = widget_to_sdev(w);
hext_stream = ops->get_hext_stream(sdev, cpu_dai, substream);
/* nothing more to do if the link is already prepared */
if (hext_stream && hext_stream->link_prepared)
return 0;
/*
* reset the PCMSyCM registers to handle a prepare callback when the PCM is restarted
* due to xruns or after a call to snd_pcm_drain/drop()
*/
ret = hdac_bus_eml_sdw_map_stream_ch(sof_to_bus(sdev), link_id, cpu_dai->id,
0, 0, substream->stream);
if (ret < 0) {
dev_err(cpu_dai->dev, "%s: hdac_bus_eml_sdw_map_stream_ch failed %d\n",
__func__, ret);
return ret;
}
data.dai_index = (link_id << 8) | cpu_dai->id;
data.dai_node_id = intel_alh_id;
ret = non_hda_dai_hw_params_data(substream, params, cpu_dai, &data, flags);
if (ret < 0) {
dev_err(cpu_dai->dev, "%s: non_hda_dai_hw_params failed %d\n", __func__, ret);
return ret;
}
hext_stream = ops->get_hext_stream(sdev, cpu_dai, substream);
if (!hext_stream)
return -ENODEV;
/*
* in the case of SoundWire we need to program the PCMSyCM registers. In case
* of aggregated devices, we need to define the channel mask for each sublink
* by reconstructing the split done in soc-pcm.c
*/
for_each_rtd_cpu_dais(rtd, cpu_dai_id, dai) {
if (dai == cpu_dai) {
cpu_dai_found = true;
break;
}
}
if (!cpu_dai_found)
return -ENODEV;
ch_mask = GENMASK(params_channels(params) - 1, 0);
ret = hdac_bus_eml_sdw_map_stream_ch(sof_to_bus(sdev), link_id, cpu_dai->id,
ch_mask,
hdac_stream(hext_stream)->stream_tag,
substream->stream);
if (ret < 0) {
dev_err(cpu_dai->dev, "%s: hdac_bus_eml_sdw_map_stream_ch failed %d\n",
__func__, ret);
return ret;
}
if (sdev->dspless_mode_selected)
return 0;
ipc4_copier = widget_to_copier(w);
dma_config_tlv = &ipc4_copier->dma_config_tlv[cpu_dai_id];
dma_config = &dma_config_tlv->dma_config;
dma_config->dma_stream_channel_map.mapping[0].device = data.dai_index;
dma_config->dma_stream_channel_map.mapping[0].channel_mask = ch_mask;
/*
* copy the dma_config_tlv to all ipc4_copier in the same link. Because only one copier
* will be handled in sof_ipc4_prepare_copier_module.
*/
for_each_rtd_cpu_dais(rtd, i, dai) {
w = snd_soc_dai_get_widget(dai, substream->stream);
if (!w) {
dev_err(cpu_dai->dev,
"%s widget not found, check amp link num in the topology\n",
dai->name);
return -EINVAL;
}
ipc4_copier = widget_to_copier(w);
memcpy(&ipc4_copier->dma_config_tlv[cpu_dai_id], dma_config_tlv,
sizeof(*dma_config_tlv));
}
return 0;
}
EXPORT_SYMBOL_NS(sdw_hda_dai_hw_params, "SND_SOC_SOF_INTEL_HDA_COMMON");
int sdw_hda_dai_hw_free(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai,
int link_id)
{
struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(cpu_dai, substream->stream);
struct snd_sof_dev *sdev;
int ret;
ret = hda_dai_hw_free(substream, cpu_dai);
if (ret < 0) {
dev_err(cpu_dai->dev, "%s: non_hda_dai_hw_free failed %d\n", __func__, ret);
return ret;
}
sdev = widget_to_sdev(w);
/* in the case of SoundWire we need to reset the PCMSyCM registers */
ret = hdac_bus_eml_sdw_map_stream_ch(sof_to_bus(sdev), link_id, cpu_dai->id,
0, 0, substream->stream);
if (ret < 0) {
dev_err(cpu_dai->dev, "%s: hdac_bus_eml_sdw_map_stream_ch failed %d\n",
__func__, ret);
return ret;
}
return 0;
}
EXPORT_SYMBOL_NS(sdw_hda_dai_hw_free, "SND_SOC_SOF_INTEL_HDA_COMMON");
int sdw_hda_dai_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *cpu_dai)
{
return hda_dai_trigger(substream, cmd, cpu_dai);
}
EXPORT_SYMBOL_NS(sdw_hda_dai_trigger, "SND_SOC_SOF_INTEL_HDA_COMMON");
static int hda_dai_suspend(struct hdac_bus *bus)
{
struct snd_soc_pcm_runtime *rtd;
struct hdac_ext_stream *hext_stream;
struct hdac_stream *s;
int ret;
/* set internal flag for BE */
list_for_each_entry(s, &bus->stream_list, list) {
hext_stream = stream_to_hdac_ext_stream(s);
/*
* clear stream. This should already be taken care for running
* streams when the SUSPEND trigger is called. But paused
* streams do not get suspended, so this needs to be done
* explicitly during suspend.
*/
if (hext_stream->link_substream) {
const struct hda_dai_widget_dma_ops *ops;
struct snd_sof_widget *swidget;
struct snd_soc_dapm_widget *w;
struct snd_soc_dai *cpu_dai;
struct snd_sof_dev *sdev;
struct snd_sof_dai *sdai;
rtd = snd_soc_substream_to_rtd(hext_stream->link_substream);
cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
w = snd_soc_dai_get_widget(cpu_dai, hdac_stream(hext_stream)->direction);
swidget = w->dobj.private;
sdev = widget_to_sdev(w);
sdai = swidget->private;
ops = sdai->platform_private;
if (rtd->dpcm[hext_stream->link_substream->stream].state !=
SND_SOC_DPCM_STATE_PAUSED)
continue;
/* for consistency with TRIGGER_SUSPEND */
if (ops->post_trigger) {
ret = ops->post_trigger(sdev, cpu_dai,
hext_stream->link_substream,
SNDRV_PCM_TRIGGER_SUSPEND);
if (ret < 0)
return ret;
}
ret = hda_link_dma_cleanup(hext_stream->link_substream,
hext_stream, cpu_dai, true);
if (ret < 0)
return ret;
}
}
return 0;
}
static void ssp_set_dai_drv_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *ops)
{
const struct sof_intel_dsp_desc *chip;
int i;
chip = get_chip_info(sdev->pdata);
if (chip->hw_ip_version >= SOF_INTEL_ACE_2_0) {
for (i = 0; i < ops->num_drv; i++) {
if (strstr(ops->drv[i].name, "SSP"))
ops->drv[i].ops = &ssp_dai_ops;
}
}
}
static void dmic_set_dai_drv_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *ops)
{
const struct sof_intel_dsp_desc *chip;
int i;
chip = get_chip_info(sdev->pdata);
if (chip->hw_ip_version >= SOF_INTEL_ACE_2_0) {
for (i = 0; i < ops->num_drv; i++) {
if (strstr(ops->drv[i].name, "DMIC"))
ops->drv[i].ops = &dmic_dai_ops;
}
}
}
#else
static inline void ssp_set_dai_drv_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *ops) {}
static inline void dmic_set_dai_drv_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *ops) {}
#endif /* CONFIG_SND_SOC_SOF_HDA_LINK */
void hda_set_dai_drv_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *ops)
{
int i;
for (i = 0; i < ops->num_drv; i++) {
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
if (strstr(ops->drv[i].name, "iDisp") ||
strstr(ops->drv[i].name, "Analog") ||
strstr(ops->drv[i].name, "Digital"))
ops->drv[i].ops = &hda_dai_ops;
#endif
}
ssp_set_dai_drv_ops(sdev, ops);
dmic_set_dai_drv_ops(sdev, ops);
if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4 && !hda_use_tplg_nhlt) {
struct sof_ipc4_fw_data *ipc4_data = sdev->private;
ipc4_data->nhlt = intel_nhlt_init(sdev->dev);
}
}
EXPORT_SYMBOL_NS(hda_set_dai_drv_ops, "SND_SOC_SOF_INTEL_HDA_COMMON");
void hda_ops_free(struct snd_sof_dev *sdev)
{
if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4) {
struct sof_ipc4_fw_data *ipc4_data = sdev->private;
if (!hda_use_tplg_nhlt)
intel_nhlt_free(ipc4_data->nhlt);
kfree(sdev->private);
sdev->private = NULL;
}
}
EXPORT_SYMBOL_NS(hda_ops_free, "SND_SOC_SOF_INTEL_HDA_COMMON");
/*
* common dai driver for skl+ platforms.
* some products who use this DAI array only physically have a subset of
* the DAIs, but no harm is done here by adding the whole set.
*/
struct snd_soc_dai_driver skl_dai[] = {
{
.name = "SSP0 Pin",
.playback = {
.channels_min = 1,
.channels_max = 8,
},
.capture = {
.channels_min = 1,
.channels_max = 8,
},
},
{
.name = "SSP1 Pin",
.playback = {
.channels_min = 1,
.channels_max = 8,
},
.capture = {
.channels_min = 1,
.channels_max = 8,
},
},
{
.name = "SSP2 Pin",
.playback = {
.channels_min = 1,
.channels_max = 8,
},
.capture = {
.channels_min = 1,
.channels_max = 8,
},
},
{
.name = "SSP3 Pin",
.playback = {
.channels_min = 1,
.channels_max = 8,
},
.capture = {
.channels_min = 1,
.channels_max = 8,
},
},
{
.name = "SSP4 Pin",
.playback = {
.channels_min = 1,
.channels_max = 8,
},
.capture = {
.channels_min = 1,
.channels_max = 8,
},
},
{
.name = "SSP5 Pin",
.playback = {
.channels_min = 1,
.channels_max = 8,
},
.capture = {
.channels_min = 1,
.channels_max = 8,
},
},
{
.name = "DMIC01 Pin",
.capture = {
.channels_min = 1,
.channels_max = 4,
},
},
{
.name = "DMIC16k Pin",
.capture = {
.channels_min = 1,
.channels_max = 4,
},
},
{
/* Virtual CPU DAI for Echo reference */
.name = "Loopback Virtual Pin",
.capture = {
.channels_min = 1,
.channels_max = 2,
},
},
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
{
.name = "iDisp1 Pin",
.playback = {
.channels_min = 1,
.channels_max = 8,
},
},
{
.name = "iDisp2 Pin",
.playback = {
.channels_min = 1,
.channels_max = 8,
},
},
{
.name = "iDisp3 Pin",
.playback = {
.channels_min = 1,
.channels_max = 8,
},
},
{
.name = "iDisp4 Pin",
.playback = {
.channels_min = 1,
.channels_max = 8,
},
},
{
.name = "Analog CPU DAI",
.playback = {
.channels_min = 1,
.channels_max = 16,
},
.capture = {
.channels_min = 1,
.channels_max = 16,
},
},
{
.name = "Digital CPU DAI",
.playback = {
.channels_min = 1,
.channels_max = 16,
},
.capture = {
.channels_min = 1,
.channels_max = 16,
},
},
{
.name = "Alt Analog CPU DAI",
.playback = {
.channels_min = 1,
.channels_max = 16,
},
.capture = {
.channels_min = 1,
.channels_max = 16,
},
},
#endif
};
EXPORT_SYMBOL_NS(skl_dai, "SND_SOC_SOF_INTEL_HDA_COMMON");
int hda_dsp_dais_suspend(struct snd_sof_dev *sdev)
{
/*
* In the corner case where a SUSPEND happens during a PAUSE, the ALSA core
* does not throw the TRIGGER_SUSPEND. This leaves the DAIs in an unbalanced state.
* Since the component suspend is called last, we can trap this corner case
* and force the DAIs to release their resources.
*/
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_LINK)
int ret;
ret = hda_dai_suspend(sof_to_bus(sdev));
if (ret < 0)
return ret;
#endif
return 0;
}
File diff suppressed because it is too large Load Diff
+549
View File
@@ -0,0 +1,549 @@
// 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
//
// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com>
// Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
// Rander Wang <rander.wang@intel.com>
// Keyon Jie <yang.jie@linux.intel.com>
//
/*
* Hardware interface for generic Intel audio DSP HDA IP
*/
#include <sound/hda_register.h>
#include <sound/sof/ipc4/header.h>
#include <trace/events/sof_intel.h>
#include "../ops.h"
#include "hda.h"
#include "telemetry.h"
EXPORT_TRACEPOINT_SYMBOL(sof_intel_ipc_firmware_initiated);
EXPORT_TRACEPOINT_SYMBOL(sof_intel_ipc_firmware_response);
EXPORT_TRACEPOINT_SYMBOL(sof_intel_hda_irq_ipc_check);
static void hda_dsp_ipc_host_done(struct snd_sof_dev *sdev)
{
/*
* tell DSP cmd is done - clear busy
* interrupt and send reply msg to dsp
*/
snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR,
HDA_DSP_REG_HIPCT,
HDA_DSP_REG_HIPCT_BUSY,
HDA_DSP_REG_HIPCT_BUSY);
/* unmask BUSY interrupt */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
HDA_DSP_REG_HIPCCTL,
HDA_DSP_REG_HIPCCTL_BUSY,
HDA_DSP_REG_HIPCCTL_BUSY);
}
static void hda_dsp_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,
HDA_DSP_REG_HIPCIE,
HDA_DSP_REG_HIPCIE_DONE,
HDA_DSP_REG_HIPCIE_DONE);
/* unmask Done interrupt */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
HDA_DSP_REG_HIPCCTL,
HDA_DSP_REG_HIPCCTL_DONE,
HDA_DSP_REG_HIPCCTL_DONE);
}
int hda_dsp_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
{
/* send IPC message to DSP */
sof_mailbox_write(sdev, sdev->host_box.offset, msg->msg_data,
msg->msg_size);
snd_sof_dsp_write(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCI,
HDA_DSP_REG_HIPCI_BUSY);
return 0;
}
EXPORT_SYMBOL_NS(hda_dsp_ipc_send_msg, "SND_SOC_SOF_INTEL_HDA_COMMON");
static inline bool hda_dsp_ipc4_pm_msg(u32 primary)
{
/* pm setting is only supported by module msg */
if (SOF_IPC4_MSG_IS_MODULE_MSG(primary) != SOF_IPC4_MODULE_MSG)
return false;
if (SOF_IPC4_MSG_TYPE_GET(primary) == SOF_IPC4_MOD_SET_DX ||
SOF_IPC4_MSG_TYPE_GET(primary) == SOF_IPC4_MOD_SET_D0IX)
return true;
return false;
}
void hda_dsp_ipc4_schedule_d0i3_work(struct sof_intel_hda_dev *hdev,
struct snd_sof_ipc_msg *msg)
{
struct sof_ipc4_msg *msg_data = msg->msg_data;
/* Schedule a delayed work for d0i3 entry after sending non-pm ipc msg */
if (hda_dsp_ipc4_pm_msg(msg_data->primary))
return;
mod_delayed_work(system_dfl_wq, &hdev->d0i3_work,
msecs_to_jiffies(SOF_HDA_D0I3_WORK_DELAY_MS));
}
EXPORT_SYMBOL_NS(hda_dsp_ipc4_schedule_d0i3_work, "SND_SOC_SOF_INTEL_HDA_COMMON");
int hda_dsp_ipc4_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg)
{
struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
struct sof_ipc4_msg *msg_data = msg->msg_data;
if (hda_ipc4_tx_is_busy(sdev)) {
hdev->delayed_ipc_tx_msg = msg;
return 0;
}
hdev->delayed_ipc_tx_msg = NULL;
/* send the message via mailbox */
if (msg_data->data_size)
sof_mailbox_write(sdev, sdev->host_box.offset, msg_data->data_ptr,
msg_data->data_size);
snd_sof_dsp_write(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCIE, msg_data->extension);
snd_sof_dsp_write(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCI,
msg_data->primary | HDA_DSP_REG_HIPCI_BUSY);
hda_dsp_ipc4_schedule_d0i3_work(hdev, msg);
return 0;
}
EXPORT_SYMBOL_NS(hda_dsp_ipc4_send_msg, "SND_SOC_SOF_INTEL_HDA_COMMON");
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;
/*
* 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));
msg->reply_error = 0;
} else {
snd_sof_ipc_get_reply(sdev);
}
}
EXPORT_SYMBOL_NS(hda_dsp_ipc_get_reply, "SND_SOC_SOF_INTEL_HDA_COMMON");
irqreturn_t hda_dsp_ipc4_irq_thread(int irq, void *context)
{
struct sof_ipc4_msg notification_data = {{ 0 }};
struct snd_sof_dev *sdev = context;
bool ack_received = false;
bool ipc_irq = false;
u32 hipcie, hipct;
hipcie = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCIE);
hipct = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCT);
if (hipcie & HDA_DSP_REG_HIPCIE_DONE) {
/* DSP received the message */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCCTL,
HDA_DSP_REG_HIPCCTL_DONE, 0);
hda_dsp_ipc_dsp_done(sdev);
ipc_irq = true;
ack_received = true;
}
if (hipct & HDA_DSP_REG_HIPCT_BUSY) {
/* Message from DSP (reply or notification) */
u32 hipcte = snd_sof_dsp_read(sdev, HDA_DSP_BAR,
HDA_DSP_REG_HIPCTE);
u32 primary = hipct & HDA_DSP_REG_HIPCT_MSG_MASK;
u32 extension = hipcte & HDA_DSP_REG_HIPCTE_MSG_MASK;
/* mask BUSY interrupt */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCCTL,
HDA_DSP_REG_HIPCCTL_BUSY, 0);
if (primary & SOF_IPC4_MSG_DIR_MASK) {
/* Reply received */
if (likely(sdev->fw_state == SOF_FW_BOOT_COMPLETE)) {
struct sof_ipc4_msg *data = sdev->ipc->msg.reply_data;
data->primary = primary;
data->extension = extension;
guard(spinlock_irq)(&sdev->ipc_lock);
snd_sof_ipc_get_reply(sdev);
hda_dsp_ipc_host_done(sdev);
snd_sof_ipc_reply(sdev, data->primary);
} else {
dev_dbg_ratelimited(sdev->dev,
"IPC reply before FW_READY: %#x|%#x\n",
primary, extension);
}
} else {
/* Notification received */
notification_data.primary = primary;
notification_data.extension = extension;
sdev->ipc->msg.rx_data = &notification_data;
snd_sof_ipc_msgs_rx(sdev);
sdev->ipc->msg.rx_data = NULL;
/* Let DSP know that we have finished processing the message */
hda_dsp_ipc_host_done(sdev);
}
ipc_irq = true;
}
if (!ipc_irq)
/* This interrupt is not shared so no need to return IRQ_NONE. */
dev_dbg_ratelimited(sdev->dev, "nothing to do in IPC IRQ thread\n");
if (ack_received) {
struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
if (hdev->delayed_ipc_tx_msg)
hda_dsp_ipc4_send_msg(sdev, hdev->delayed_ipc_tx_msg);
}
return IRQ_HANDLED;
}
EXPORT_SYMBOL_NS(hda_dsp_ipc4_irq_thread, "SND_SOC_SOF_INTEL_HDA_COMMON");
/* IPC handler thread */
irqreturn_t hda_dsp_ipc_irq_thread(int irq, void *context)
{
struct snd_sof_dev *sdev = context;
u32 hipci;
u32 hipcie;
u32 hipct;
u32 hipcte;
u32 msg;
u32 msg_ext;
bool ipc_irq = false;
/* read IPC status */
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);
hipci = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCI);
hipcte = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_HIPCTE);
/* is this a reply message from the DSP */
if (hipcie & HDA_DSP_REG_HIPCIE_DONE) {
msg = hipci & HDA_DSP_REG_HIPCI_MSG_MASK;
msg_ext = hipcie & HDA_DSP_REG_HIPCIE_MSG_MASK;
trace_sof_intel_ipc_firmware_response(sdev, msg, msg_ext);
/* mask Done interrupt */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
HDA_DSP_REG_HIPCCTL,
HDA_DSP_REG_HIPCCTL_DONE, 0);
/*
* Make sure the interrupt thread cannot be preempted between
* waking up the sender and re-enabling the interrupt. Also
* protect against a theoretical race with sof_ipc_tx_message():
* if the DSP is fast enough to receive an IPC message, reply to
* it, and the host interrupt processing calls this function on
* a different core from the one, where the sending is taking
* place, the message might not yet be marked as expecting a
* reply.
*/
if (likely(sdev->fw_state == SOF_FW_BOOT_COMPLETE)) {
/* handle immediate reply from DSP core */
guard(spinlock_irq)(&sdev->ipc_lock);
hda_dsp_ipc_get_reply(sdev);
snd_sof_ipc_reply(sdev, msg);
/* set the done bit */
hda_dsp_ipc_dsp_done(sdev);
} else {
dev_dbg_ratelimited(sdev->dev, "IPC reply before FW_READY: %#x\n",
msg);
}
ipc_irq = true;
}
/* is this a new message from DSP */
if (hipct & HDA_DSP_REG_HIPCT_BUSY) {
msg = hipct & HDA_DSP_REG_HIPCT_MSG_MASK;
msg_ext = hipcte & HDA_DSP_REG_HIPCTE_MSG_MASK;
trace_sof_intel_ipc_firmware_initiated(sdev, msg, msg_ext);
/* mask BUSY interrupt */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
HDA_DSP_REG_HIPCCTL,
HDA_DSP_REG_HIPCCTL_BUSY, 0);
/* handle messages from DSP */
if ((hipct & 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 {
/* normal message - process normally */
snd_sof_ipc_msgs_rx(sdev);
}
hda_dsp_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;
}
EXPORT_SYMBOL_NS(hda_dsp_ipc_irq_thread, "SND_SOC_SOF_INTEL_HDA_COMMON");
/* Check if an IPC IRQ occurred */
bool hda_dsp_check_ipc_irq(struct snd_sof_dev *sdev)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
bool ret = false;
u32 irq_status;
if (sdev->dspless_mode_selected)
return false;
/* store status */
irq_status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIS);
trace_sof_intel_hda_irq_ipc_check(sdev, irq_status);
/* invalid message ? */
if (irq_status == 0xffffffff)
goto out;
/* IPC message ? */
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;
}
EXPORT_SYMBOL_NS(hda_dsp_check_ipc_irq, "SND_SOC_SOF_INTEL_HDA_COMMON");
int hda_dsp_ipc_get_mailbox_offset(struct snd_sof_dev *sdev)
{
return HDA_DSP_MBOX_UPLINK_OFFSET;
}
EXPORT_SYMBOL_NS(hda_dsp_ipc_get_mailbox_offset, "SND_SOC_SOF_INTEL_HDA_COMMON");
int hda_dsp_ipc_get_window_offset(struct snd_sof_dev *sdev, u32 id)
{
return SRAM_WINDOW_OFFSET(id);
}
EXPORT_SYMBOL_NS(hda_dsp_ipc_get_window_offset, "SND_SOC_SOF_INTEL_HDA_COMMON");
int hda_ipc_msg_data(struct snd_sof_dev *sdev,
struct snd_sof_pcm_stream *sps,
void *p, size_t sz)
{
if (!sps || !sdev->stream_box.size) {
sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz);
} else {
struct snd_pcm_substream *substream = sps->substream;
struct hdac_stream *hstream = substream->runtime->private_data;
struct sof_intel_hda_stream *hda_stream;
hda_stream = container_of(hstream,
struct sof_intel_hda_stream,
hext_stream.hstream);
/* The stream might already be closed */
if (!hstream)
return -ESTRPIPE;
sof_mailbox_read(sdev, hda_stream->sof_intel_stream.posn_offset, p, sz);
}
return 0;
}
EXPORT_SYMBOL_NS(hda_ipc_msg_data, "SND_SOC_SOF_INTEL_HDA_COMMON");
int hda_set_stream_data_offset(struct snd_sof_dev *sdev,
struct snd_sof_pcm_stream *sps,
size_t posn_offset)
{
struct snd_pcm_substream *substream = sps->substream;
struct hdac_stream *hstream = substream->runtime->private_data;
struct sof_intel_hda_stream *hda_stream;
hda_stream = container_of(hstream, struct sof_intel_hda_stream,
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->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->sof_intel_stream.posn_offset);
return 0;
}
EXPORT_SYMBOL_NS(hda_set_stream_data_offset, "SND_SOC_SOF_INTEL_HDA_COMMON");
void hda_ipc4_dsp_dump(struct snd_sof_dev *sdev, u32 flags)
{
char *level = (flags & SOF_DBG_DUMP_OPTIONAL) ? KERN_DEBUG : KERN_ERR;
/* print ROM/FW status */
hda_dsp_get_state(sdev, level);
if (flags & SOF_DBG_DUMP_REGS)
sof_ipc4_intel_dump_telemetry_state(sdev, flags);
else
hda_dsp_dump_ext_rom_status(sdev, level, flags);
}
EXPORT_SYMBOL_NS(hda_ipc4_dsp_dump, "SND_SOC_SOF_INTEL_HDA_COMMON");
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;
}
EXPORT_SYMBOL_NS(hda_check_ipc_irq, "SND_SOC_SOF_INTEL_HDA_COMMON");
void hda_ipc_irq_dump(struct snd_sof_dev *sdev)
{
u32 adspis;
u32 intsts;
u32 intctl;
u32 ppsts;
u8 rirbsts;
/* read key IRQ stats and config registers */
adspis = snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIS);
intsts = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTSTS);
intctl = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL);
ppsts = snd_sof_dsp_read(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPSTS);
rirbsts = snd_sof_dsp_read8(sdev, HDA_DSP_HDA_BAR, AZX_REG_RIRBSTS);
dev_err(sdev->dev, "hda irq intsts 0x%8.8x intlctl 0x%8.8x rirb %2.2x\n",
intsts, intctl, rirbsts);
dev_err(sdev->dev, "dsp irq ppsts 0x%8.8x adspis 0x%8.8x\n", ppsts, adspis);
}
EXPORT_SYMBOL_NS(hda_ipc_irq_dump, "SND_SOC_SOF_INTEL_HDA_COMMON");
void hda_ipc_dump(struct snd_sof_dev *sdev)
{
u32 hipcie;
u32 hipct;
u32 hipcctl;
hda_ipc_irq_dump(sdev);
/* read IPC status */
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);
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 status 0x%8.8x dsp status 0x%8.8x mask 0x%8.8x\n",
hipcie, hipct, hipcctl);
}
EXPORT_SYMBOL_NS(hda_ipc_dump, "SND_SOC_SOF_INTEL_HDA_COMMON");
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);
}
EXPORT_SYMBOL_NS(hda_ipc4_dump, "SND_SOC_SOF_INTEL_HDA_COMMON");
bool hda_ipc4_tx_is_busy(struct snd_sof_dev *sdev)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
const struct sof_intel_dsp_desc *chip = hda->desc;
u32 val;
val = snd_sof_dsp_read(sdev, HDA_DSP_BAR, chip->ipc_req);
return !!(val & chip->ipc_req_mask);
}
EXPORT_SYMBOL_NS(hda_ipc4_tx_is_busy, "SND_SOC_SOF_INTEL_HDA_COMMON");
+56
View File
@@ -0,0 +1,56 @@
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
/*
* This file is provided under a dual BSD/GPLv2 license. When using or
* redistributing this file, you may do so under either license.
*
* Copyright(c) 2019 Intel Corporation
*
* Author: Keyon Jie <yang.jie@linux.intel.com>
*/
#ifndef __SOF_INTEL_HDA_IPC_H
#define __SOF_INTEL_HDA_IPC_H
/*
* Primary register, mapped to
* - DIPCTDR (HIPCIDR) in sideband IPC (cAVS 1.8+)
* - DIPCT in cAVS 1.5 IPC
*
* Secondary register, mapped to:
* - DIPCTDD (HIPCIDD) in sideband IPC (cAVS 1.8+)
* - DIPCTE in cAVS 1.5 IPC
*/
/* Common bits in primary register */
/* Reserved for doorbell */
#define HDA_IPC_RSVD_31 BIT(31)
/* Target, 0 - normal message, 1 - compact message(cAVS compatible) */
#define HDA_IPC_MSG_COMPACT BIT(30)
/* Direction, 0 - request, 1 - response */
#define HDA_IPC_RSP BIT(29)
#define HDA_IPC_TYPE_SHIFT 24
#define HDA_IPC_TYPE_MASK GENMASK(28, 24)
#define HDA_IPC_TYPE(x) ((x) << HDA_IPC_TYPE_SHIFT)
#define HDA_IPC_PM_GATE HDA_IPC_TYPE(0x8U)
/* Command specific payload bits in secondary register */
/* Disable DMA tracing (0 - keep tracing, 1 - to disable DMA trace) */
#define HDA_PM_NO_DMA_TRACE BIT(4)
/* Prevent clock gating (0 - cg allowed, 1 - DSP clock always on) */
#define HDA_PM_PCG BIT(3)
/* Prevent power gating (0 - deep power state transitions allowed) */
#define HDA_PM_PPG BIT(2)
/* Indicates whether streaming is active */
#define HDA_PM_PG_STREAMING BIT(1)
#define HDA_PM_PG_RSVD BIT(0)
irqreturn_t cnl_ipc_irq_thread(int irq, void *context);
int cnl_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *msg);
void cnl_ipc_dump(struct snd_sof_dev *sdev);
void cnl_ipc4_dump(struct snd_sof_dev *sdev);
#endif
@@ -0,0 +1,578 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2018-2022 Intel Corporation
//
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/firmware.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <sound/hdaudio_ext.h>
#include <sound/sof.h>
#include <sound/pcm_params.h>
#include "../sof-priv.h"
#include "../ops.h"
#include "hda.h"
#define HDA_SKL_WAIT_TIMEOUT 500 /* 500 msec */
#define HDA_SKL_CLDMA_MAX_BUFFER_SIZE (32 * PAGE_SIZE)
/* Stream Reset */
#define HDA_CL_SD_CTL_SRST_SHIFT 0
#define HDA_CL_SD_CTL_SRST(x) (((x) & 0x1) << \
HDA_CL_SD_CTL_SRST_SHIFT)
/* Stream Run */
#define HDA_CL_SD_CTL_RUN_SHIFT 1
#define HDA_CL_SD_CTL_RUN(x) (((x) & 0x1) << \
HDA_CL_SD_CTL_RUN_SHIFT)
/* Interrupt On Completion Enable */
#define HDA_CL_SD_CTL_IOCE_SHIFT 2
#define HDA_CL_SD_CTL_IOCE(x) (((x) & 0x1) << \
HDA_CL_SD_CTL_IOCE_SHIFT)
/* FIFO Error Interrupt Enable */
#define HDA_CL_SD_CTL_FEIE_SHIFT 3
#define HDA_CL_SD_CTL_FEIE(x) (((x) & 0x1) << \
HDA_CL_SD_CTL_FEIE_SHIFT)
/* Descriptor Error Interrupt Enable */
#define HDA_CL_SD_CTL_DEIE_SHIFT 4
#define HDA_CL_SD_CTL_DEIE(x) (((x) & 0x1) << \
HDA_CL_SD_CTL_DEIE_SHIFT)
/* FIFO Limit Change */
#define HDA_CL_SD_CTL_FIFOLC_SHIFT 5
#define HDA_CL_SD_CTL_FIFOLC(x) (((x) & 0x1) << \
HDA_CL_SD_CTL_FIFOLC_SHIFT)
/* Stripe Control */
#define HDA_CL_SD_CTL_STRIPE_SHIFT 16
#define HDA_CL_SD_CTL_STRIPE(x) (((x) & 0x3) << \
HDA_CL_SD_CTL_STRIPE_SHIFT)
/* Traffic Priority */
#define HDA_CL_SD_CTL_TP_SHIFT 18
#define HDA_CL_SD_CTL_TP(x) (((x) & 0x1) << \
HDA_CL_SD_CTL_TP_SHIFT)
/* Bidirectional Direction Control */
#define HDA_CL_SD_CTL_DIR_SHIFT 19
#define HDA_CL_SD_CTL_DIR(x) (((x) & 0x1) << \
HDA_CL_SD_CTL_DIR_SHIFT)
/* Stream Number */
#define HDA_CL_SD_CTL_STRM_SHIFT 20
#define HDA_CL_SD_CTL_STRM(x) (((x) & 0xf) << \
HDA_CL_SD_CTL_STRM_SHIFT)
#define HDA_CL_SD_CTL_INT(x) \
(HDA_CL_SD_CTL_IOCE(x) | \
HDA_CL_SD_CTL_FEIE(x) | \
HDA_CL_SD_CTL_DEIE(x))
#define HDA_CL_SD_CTL_INT_MASK \
(HDA_CL_SD_CTL_IOCE(1) | \
HDA_CL_SD_CTL_FEIE(1) | \
HDA_CL_SD_CTL_DEIE(1))
#define DMA_ADDRESS_128_BITS_ALIGNMENT 7
#define BDL_ALIGN(x) ((x) >> DMA_ADDRESS_128_BITS_ALIGNMENT)
/* Buffer Descriptor List Lower Base Address */
#define HDA_CL_SD_BDLPLBA_SHIFT 7
#define HDA_CL_SD_BDLPLBA_MASK GENMASK(31, 7)
#define HDA_CL_SD_BDLPLBA(x) \
((BDL_ALIGN(lower_32_bits(x)) << HDA_CL_SD_BDLPLBA_SHIFT) & \
HDA_CL_SD_BDLPLBA_MASK)
/* Buffer Descriptor List Upper Base Address */
#define HDA_CL_SD_BDLPUBA(x) \
(upper_32_bits(x))
/* Software Position in Buffer Enable */
#define HDA_CL_SPBFIFO_SPBFCCTL_SPIBE_SHIFT 0
#define HDA_CL_SPBFIFO_SPBFCCTL_SPIBE_MASK \
(1 << HDA_CL_SPBFIFO_SPBFCCTL_SPIBE_SHIFT)
#define HDA_CL_SPBFIFO_SPBFCCTL_SPIBE(x) \
(((x) << HDA_CL_SPBFIFO_SPBFCCTL_SPIBE_SHIFT) & \
HDA_CL_SPBFIFO_SPBFCCTL_SPIBE_MASK)
#define HDA_CL_DMA_SD_INT_COMPLETE 0x4
static int cl_skl_cldma_setup_bdle(struct snd_sof_dev *sdev,
struct snd_dma_buffer *dmab_data,
__le32 **bdlp, int size, int with_ioc)
{
phys_addr_t addr = virt_to_phys(dmab_data->area);
__le32 *bdl = *bdlp;
/*
* This code is simplified by using one fragment of physical memory and assuming
* all the code fits. This could be improved with scatter-gather but the firmware
* size is limited by DSP memory anyways
*/
bdl[0] = cpu_to_le32(lower_32_bits(addr));
bdl[1] = cpu_to_le32(upper_32_bits(addr));
bdl[2] = cpu_to_le32(size);
bdl[3] = (!with_ioc) ? 0 : cpu_to_le32(0x01);
return 1; /* one fragment */
}
static void cl_skl_cldma_stream_run(struct snd_sof_dev *sdev, bool enable)
{
int sd_offset = SOF_HDA_ADSP_LOADER_BASE;
unsigned char val;
int retries;
u32 run = enable ? 0x1 : 0;
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
sd_offset + SOF_HDA_ADSP_REG_SD_CTL,
HDA_CL_SD_CTL_RUN(1), HDA_CL_SD_CTL_RUN(run));
retries = 300;
do {
udelay(3);
/* waiting for hardware to report the stream Run bit set */
val = snd_sof_dsp_read(sdev, HDA_DSP_BAR,
sd_offset + SOF_HDA_ADSP_REG_SD_CTL);
val &= HDA_CL_SD_CTL_RUN(1);
if (enable && val)
break;
else if (!enable && !val)
break;
} while (--retries);
if (retries == 0)
dev_err(sdev->dev, "%s: failed to set Run bit=%d enable=%d\n",
__func__, val, enable);
}
static void cl_skl_cldma_stream_clear(struct snd_sof_dev *sdev)
{
int sd_offset = SOF_HDA_ADSP_LOADER_BASE;
/* make sure Run bit is cleared before setting stream register */
cl_skl_cldma_stream_run(sdev, 0);
/* Disable the Interrupt On Completion, FIFO Error Interrupt,
* Descriptor Error Interrupt and set the cldma stream number to 0.
*/
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
sd_offset + SOF_HDA_ADSP_REG_SD_CTL,
HDA_CL_SD_CTL_INT_MASK, HDA_CL_SD_CTL_INT(0));
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
sd_offset + SOF_HDA_ADSP_REG_SD_CTL,
HDA_CL_SD_CTL_STRM(0xf), HDA_CL_SD_CTL_STRM(0));
snd_sof_dsp_write(sdev, HDA_DSP_BAR,
sd_offset + SOF_HDA_ADSP_REG_SD_BDLPL, HDA_CL_SD_BDLPLBA(0));
snd_sof_dsp_write(sdev, HDA_DSP_BAR,
sd_offset + SOF_HDA_ADSP_REG_SD_BDLPU, 0);
/* Set the Cyclic Buffer Length to 0. */
snd_sof_dsp_write(sdev, HDA_DSP_BAR,
sd_offset + SOF_HDA_ADSP_REG_SD_CBL, 0);
/* Set the Last Valid Index. */
snd_sof_dsp_write(sdev, HDA_DSP_BAR,
sd_offset + SOF_HDA_ADSP_REG_SD_LVI, 0);
}
static void cl_skl_cldma_setup_spb(struct snd_sof_dev *sdev,
unsigned int size, bool enable)
{
int sd_offset = SOF_DSP_REG_CL_SPBFIFO;
if (enable)
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
sd_offset + SOF_HDA_ADSP_REG_CL_SPBFIFO_SPBFCCTL,
HDA_CL_SPBFIFO_SPBFCCTL_SPIBE_MASK,
HDA_CL_SPBFIFO_SPBFCCTL_SPIBE(1));
snd_sof_dsp_write(sdev, HDA_DSP_BAR,
sd_offset + SOF_HDA_ADSP_REG_CL_SPBFIFO_SPIB, size);
}
static void cl_skl_cldma_set_intr(struct snd_sof_dev *sdev, bool enable)
{
u32 val = enable ? HDA_DSP_ADSPIC_CL_DMA : 0;
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIC,
HDA_DSP_ADSPIC_CL_DMA, val);
}
static void cl_skl_cldma_cleanup_spb(struct snd_sof_dev *sdev)
{
int sd_offset = SOF_DSP_REG_CL_SPBFIFO;
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
sd_offset + SOF_HDA_ADSP_REG_CL_SPBFIFO_SPBFCCTL,
HDA_CL_SPBFIFO_SPBFCCTL_SPIBE_MASK,
HDA_CL_SPBFIFO_SPBFCCTL_SPIBE(0));
snd_sof_dsp_write(sdev, HDA_DSP_BAR,
sd_offset + SOF_HDA_ADSP_REG_CL_SPBFIFO_SPIB, 0);
}
static void cl_skl_cldma_setup_controller(struct snd_sof_dev *sdev,
struct snd_dma_buffer *dmab_bdl,
unsigned int max_size, u32 count)
{
int sd_offset = SOF_HDA_ADSP_LOADER_BASE;
/* Clear the stream first and then set it. */
cl_skl_cldma_stream_clear(sdev);
/* setting the stream register */
snd_sof_dsp_write(sdev, HDA_DSP_BAR,
sd_offset + SOF_HDA_ADSP_REG_SD_BDLPL,
HDA_CL_SD_BDLPLBA(dmab_bdl->addr));
snd_sof_dsp_write(sdev, HDA_DSP_BAR,
sd_offset + SOF_HDA_ADSP_REG_SD_BDLPU,
HDA_CL_SD_BDLPUBA(dmab_bdl->addr));
/* Set the Cyclic Buffer Length. */
snd_sof_dsp_write(sdev, HDA_DSP_BAR,
sd_offset + SOF_HDA_ADSP_REG_SD_CBL, max_size);
/* Set the Last Valid Index. */
snd_sof_dsp_write(sdev, HDA_DSP_BAR,
sd_offset + SOF_HDA_ADSP_REG_SD_LVI, count - 1);
/* Set the Interrupt On Completion, FIFO Error Interrupt,
* Descriptor Error Interrupt and the cldma stream number.
*/
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
sd_offset + SOF_HDA_ADSP_REG_SD_CTL,
HDA_CL_SD_CTL_INT_MASK, HDA_CL_SD_CTL_INT(1));
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR,
sd_offset + SOF_HDA_ADSP_REG_SD_CTL,
HDA_CL_SD_CTL_STRM(0xf),
HDA_CL_SD_CTL_STRM(1));
}
static int cl_stream_prepare_skl(struct snd_sof_dev *sdev,
struct snd_dma_buffer *dmab,
struct snd_dma_buffer *dmab_bdl)
{
unsigned int bufsize = HDA_SKL_CLDMA_MAX_BUFFER_SIZE;
__le32 *bdl;
int frags;
int ret;
ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->dev, bufsize, dmab);
if (ret < 0) {
dev_err(sdev->dev, "%s: failed to alloc fw buffer: %x\n", __func__, ret);
return ret;
}
ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, sdev->dev, bufsize, dmab_bdl);
if (ret < 0) {
dev_err(sdev->dev, "%s: failed to alloc blde: %x\n", __func__, ret);
snd_dma_free_pages(dmab);
return ret;
}
bdl = (__le32 *)dmab_bdl->area;
frags = cl_skl_cldma_setup_bdle(sdev, dmab, &bdl, bufsize, 1);
cl_skl_cldma_setup_controller(sdev, dmab_bdl, bufsize, frags);
return ret;
}
static void cl_cleanup_skl(struct snd_sof_dev *sdev,
struct snd_dma_buffer *dmab,
struct snd_dma_buffer *dmab_bdl)
{
cl_skl_cldma_cleanup_spb(sdev);
cl_skl_cldma_stream_clear(sdev);
snd_dma_free_pages(dmab);
snd_dma_free_pages(dmab_bdl);
}
static int cl_dsp_init_skl(struct snd_sof_dev *sdev,
struct snd_dma_buffer *dmab,
struct snd_dma_buffer *dmab_bdl)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
const struct sof_intel_dsp_desc *chip = hda->desc;
unsigned int status;
u32 flags;
int ret;
/* check if the init_core is already enabled, if yes, reset and make it run,
* if not, powerdown and enable it again.
*/
if (hda_dsp_core_is_enabled(sdev, chip->init_core_mask)) {
/* if enabled, reset it, and run the init_core. */
ret = hda_dsp_core_stall_reset(sdev, chip->init_core_mask);
if (ret < 0)
goto err;
ret = hda_dsp_core_run(sdev, chip->init_core_mask);
if (ret < 0) {
dev_err(sdev->dev, "%s: dsp core start failed %d\n", __func__, ret);
goto err;
}
} else {
/* if not enabled, power down it first and then powerup and run
* the init_core.
*/
ret = hda_dsp_core_reset_power_down(sdev, chip->init_core_mask);
if (ret < 0) {
dev_err(sdev->dev, "%s: dsp core0 disable fail: %d\n", __func__, ret);
goto err;
}
ret = hda_dsp_enable_core(sdev, chip->init_core_mask);
if (ret < 0) {
dev_err(sdev->dev, "%s: dsp core0 enable fail: %d\n", __func__, ret);
goto err;
}
}
/* prepare DMA for code loader stream */
ret = cl_stream_prepare_skl(sdev, dmab, dmab_bdl);
if (ret < 0) {
dev_err(sdev->dev, "%s: dma prepare fw loading err: %x\n", __func__, ret);
return ret;
}
/* enable the interrupt */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPIC,
HDA_DSP_ADSPIC_IPC, HDA_DSP_ADSPIC_IPC);
/* enable IPC DONE interrupt */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, chip->ipc_ctl,
HDA_DSP_REG_HIPCCTL_DONE,
HDA_DSP_REG_HIPCCTL_DONE);
/* enable IPC BUSY interrupt */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, chip->ipc_ctl,
HDA_DSP_REG_HIPCCTL_BUSY,
HDA_DSP_REG_HIPCCTL_BUSY);
/* polling the ROM init status information. */
ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR,
chip->rom_status_reg, status,
(FSR_TO_STATE_CODE(status)
== FSR_STATE_INIT_DONE),
HDA_DSP_REG_POLL_INTERVAL_US,
chip->rom_init_timeout *
USEC_PER_MSEC);
if (ret < 0)
goto err;
return ret;
err:
flags = SOF_DBG_DUMP_PCI | SOF_DBG_DUMP_MBOX;
snd_sof_dsp_dbg_dump(sdev, "Boot failed\n", flags);
cl_cleanup_skl(sdev, dmab, dmab_bdl);
hda_dsp_core_reset_power_down(sdev, chip->init_core_mask);
return ret;
}
static void cl_skl_cldma_fill_buffer(struct snd_sof_dev *sdev,
struct snd_dma_buffer *dmab,
unsigned int bufsize,
unsigned int copysize,
const void *curr_pos,
bool intr_enable)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
/* copy the image into the buffer with the maximum buffer size. */
unsigned int size = (bufsize == copysize) ? bufsize : copysize;
memcpy(dmab->area, curr_pos, size);
/* Set the wait condition for every load. */
hda->code_loading = 1;
/* Set the interrupt. */
if (intr_enable)
cl_skl_cldma_set_intr(sdev, true);
/* Set the SPB. */
cl_skl_cldma_setup_spb(sdev, size, true);
/* Trigger the code loading stream. */
cl_skl_cldma_stream_run(sdev, true);
}
static int cl_skl_cldma_wait_interruptible(struct snd_sof_dev *sdev,
bool intr_wait)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
const struct sof_intel_dsp_desc *chip = hda->desc;
int sd_offset = SOF_HDA_ADSP_LOADER_BASE;
u8 cl_dma_intr_status;
/*
* Wait for CLDMA interrupt to inform the binary segment transfer is
* complete.
*/
if (!wait_event_timeout(hda->waitq, !hda->code_loading,
msecs_to_jiffies(HDA_SKL_WAIT_TIMEOUT))) {
dev_err(sdev->dev, "cldma copy timeout\n");
dev_err(sdev->dev, "ROM code=%#x: FW status=%#x\n",
snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_SRAM_REG_ROM_ERROR),
snd_sof_dsp_read(sdev, HDA_DSP_BAR, chip->rom_status_reg));
return -EIO;
}
/* now check DMA interrupt status */
cl_dma_intr_status = snd_sof_dsp_read(sdev, HDA_DSP_BAR,
sd_offset + SOF_HDA_ADSP_REG_SD_STS);
if (!(cl_dma_intr_status & HDA_CL_DMA_SD_INT_COMPLETE)) {
dev_err(sdev->dev, "cldma copy failed\n");
return -EIO;
}
dev_dbg(sdev->dev, "cldma buffer copy complete\n");
return 0;
}
static int
cl_skl_cldma_copy_to_buf(struct snd_sof_dev *sdev,
struct snd_dma_buffer *dmab,
const void *bin,
u32 total_size, u32 bufsize)
{
unsigned int bytes_left = total_size;
const void *curr_pos = bin;
int ret;
if (total_size <= 0)
return -EINVAL;
while (bytes_left > 0) {
if (bytes_left > bufsize) {
dev_dbg(sdev->dev, "cldma copy %#x bytes\n", bufsize);
cl_skl_cldma_fill_buffer(sdev, dmab, bufsize, bufsize, curr_pos, true);
ret = cl_skl_cldma_wait_interruptible(sdev, false);
if (ret < 0) {
dev_err(sdev->dev, "%s: fw failed to load. %#x bytes remaining\n",
__func__, bytes_left);
return ret;
}
bytes_left -= bufsize;
curr_pos += bufsize;
} else {
dev_dbg(sdev->dev, "cldma copy %#x bytes\n", bytes_left);
cl_skl_cldma_set_intr(sdev, false);
cl_skl_cldma_fill_buffer(sdev, dmab, bufsize, bytes_left, curr_pos, false);
return 0;
}
}
return bytes_left;
}
static int cl_copy_fw_skl(struct snd_sof_dev *sdev,
struct snd_dma_buffer *dmab)
{
const struct firmware *fw = sdev->basefw.fw;
struct firmware stripped_firmware;
unsigned int bufsize = HDA_SKL_CLDMA_MAX_BUFFER_SIZE;
int ret;
stripped_firmware.data = fw->data + sdev->basefw.payload_offset;
stripped_firmware.size = fw->size - sdev->basefw.payload_offset;
dev_dbg(sdev->dev, "firmware size: %#zx buffer size %#x\n", fw->size, bufsize);
ret = cl_skl_cldma_copy_to_buf(sdev, dmab, stripped_firmware.data,
stripped_firmware.size, bufsize);
if (ret < 0)
dev_err(sdev->dev, "%s: fw copy failed %d\n", __func__, ret);
return ret;
}
int hda_dsp_cl_boot_firmware_skl(struct snd_sof_dev *sdev)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
const struct sof_intel_dsp_desc *chip = hda->desc;
struct snd_dma_buffer dmab_bdl;
struct snd_dma_buffer dmab;
unsigned int reg;
u32 flags;
int ret;
ret = cl_dsp_init_skl(sdev, &dmab, &dmab_bdl);
/* retry enabling core and ROM load. seemed to help */
if (ret < 0) {
ret = cl_dsp_init_skl(sdev, &dmab, &dmab_bdl);
if (ret < 0) {
dev_err(sdev->dev, "Error code=%#x: FW status=%#x\n",
snd_sof_dsp_read(sdev, HDA_DSP_BAR, HDA_DSP_SRAM_REG_ROM_ERROR),
snd_sof_dsp_read(sdev, HDA_DSP_BAR, chip->rom_status_reg));
dev_err(sdev->dev, "Core En/ROM load fail:%d\n", ret);
return ret;
}
}
dev_dbg(sdev->dev, "ROM init successful\n");
/* at this point DSP ROM has been initialized and should be ready for
* code loading and firmware boot
*/
ret = cl_copy_fw_skl(sdev, &dmab);
if (ret < 0) {
dev_err(sdev->dev, "%s: load firmware failed : %d\n", __func__, ret);
goto err;
}
ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR,
chip->rom_status_reg, reg,
(FSR_TO_STATE_CODE(reg)
== FSR_STATE_ROM_BASEFW_ENTERED),
HDA_DSP_REG_POLL_INTERVAL_US,
HDA_DSP_BASEFW_TIMEOUT_US);
dev_dbg(sdev->dev, "Firmware download successful, booting...\n");
cl_skl_cldma_stream_run(sdev, false);
cl_cleanup_skl(sdev, &dmab, &dmab_bdl);
if (!ret)
return chip->init_core_mask;
return ret;
err:
flags = SOF_DBG_DUMP_PCI | SOF_DBG_DUMP_MBOX;
snd_sof_dsp_dbg_dump(sdev, "Boot failed\n", flags);
/* power down DSP */
hda_dsp_core_reset_power_down(sdev, chip->init_core_mask);
cl_skl_cldma_stream_run(sdev, false);
cl_cleanup_skl(sdev, &dmab, &dmab_bdl);
dev_err(sdev->dev, "%s: load fw failed err: %d\n", __func__, ret);
return ret;
}
@@ -0,0 +1,640 @@
// 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
//
// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com>
// Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
// Rander Wang <rander.wang@intel.com>
// Keyon Jie <yang.jie@linux.intel.com>
//
/*
* Hardware interface for HDA DSP code loader
*/
#include <linux/firmware.h>
#include <sound/hdaudio_ext.h>
#include <sound/hda_register.h>
#include <sound/sof.h>
#include <sound/sof/ipc4/header.h>
#include "ext_manifest.h"
#include "../ipc4-priv.h"
#include "../ops.h"
#include "../sof-priv.h"
#include "hda.h"
static bool persistent_cl_buffer = true;
module_param(persistent_cl_buffer, bool, 0444);
MODULE_PARM_DESC(persistent_cl_buffer, "Persistent Code Loader DMA buffer "
"(default = Y, use N to force buffer re-allocation)");
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);
}
}
struct hdac_ext_stream*
hda_cl_prepare(struct device *dev, unsigned int format, unsigned int size,
struct snd_dma_buffer *dmab, bool persistent_buffer, int direction,
bool is_iccmax)
{
return hda_data_stream_prepare(dev, format, size, dmab, persistent_buffer,
direction, is_iccmax, false);
}
EXPORT_SYMBOL_NS(hda_cl_prepare, "SND_SOC_SOF_INTEL_HDA_COMMON");
/*
* 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.
*/
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, target_status;
u32 flags, ipc_hdr, j;
unsigned long mask;
char *dump_msg;
int ret;
/* step 1: power up corex */
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;
}
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);
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, chip->init_core_mask);
if (ret < 0) {
if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS)
dev_err(sdev->dev,
"error: dsp core start failed %d\n", ret);
ret = -EIO;
goto err;
}
/* step 4: wait for IPC DONE bit from ROM */
ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR,
chip->ipc_ack, status,
((status & chip->ipc_ack_mask)
== chip->ipc_ack_mask),
HDA_DSP_REG_POLL_INTERVAL_US,
HDA_DSP_INIT_TIMEOUT_US);
if (ret < 0) {
if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS)
dev_err(sdev->dev,
"error: %s: timeout for HIPCIE done\n",
__func__);
goto err;
}
/* set DONE bit to clear the reply IPC message */
snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR,
chip->ipc_ack,
chip->ipc_ack_mask,
chip->ipc_ack_mask);
/* step 5: power down cores that are no longer needed */
ret = hda_dsp_core_reset_power_down(sdev, chip->host_managed_cores_mask &
~(chip->init_core_mask));
if (ret < 0) {
if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS)
dev_err(sdev->dev,
"error: dsp core x power down failed\n");
goto err;
}
/* step 6: enable IPC interrupts */
hda_dsp_ipc_int_enable(sdev);
/*
* step 7:
* - 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,
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) {
/* 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,
"%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;
/* after max boot attempts make sure that the dump is printed */
if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS)
flags &= ~SOF_DBG_DUMP_OPTIONAL;
dump_msg = kasprintf(GFP_KERNEL, "Boot iteration failed: %d/%d",
hda->boot_iteration, HDA_FW_BOOT_ATTEMPTS);
snd_sof_dsp_dbg_dump(sdev, dump_msg, flags);
hda_dsp_core_reset_power_down(sdev, chip->host_managed_cores_mask);
kfree(dump_msg);
return ret;
}
EXPORT_SYMBOL_NS(cl_dsp_init, "SND_SOC_SOF_INTEL_HDA_COMMON");
int hda_cl_trigger(struct device *dev, struct hdac_ext_stream *hext_stream, int cmd)
{
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
struct hdac_stream *hstream = &hext_stream->hstream;
int sd_offset = SOF_STREAM_SD_OFFSET(hstream);
struct sof_intel_hda_stream *hda_stream;
/* code loader is special case that reuses stream ops */
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
hda_stream = container_of(hext_stream, struct sof_intel_hda_stream,
hext_stream);
reinit_completion(&hda_stream->ioc);
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL,
1 << hstream->index,
1 << hstream->index);
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
sd_offset,
SOF_HDA_SD_CTL_DMA_START |
SOF_HDA_CL_DMA_SD_INT_MASK,
SOF_HDA_SD_CTL_DMA_START |
SOF_HDA_CL_DMA_SD_INT_MASK);
hstream->running = true;
return 0;
default:
return hda_dsp_stream_trigger(sdev, hext_stream, cmd);
}
}
EXPORT_SYMBOL_NS(hda_cl_trigger, "SND_SOC_SOF_INTEL_HDA_COMMON");
int hda_cl_cleanup(struct device *dev, struct snd_dma_buffer *dmab,
bool persistent_buffer, struct hdac_ext_stream *hext_stream)
{
return hda_data_stream_cleanup(dev, dmab, persistent_buffer, hext_stream, false);
}
EXPORT_SYMBOL_NS(hda_cl_cleanup, "SND_SOC_SOF_INTEL_HDA_COMMON");
#define HDA_CL_DMA_IOC_TIMEOUT_MS 500
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;
dev_dbg(sdev->dev, "Code loader DMA starting\n");
ret = hda_cl_trigger(sdev->dev, hext_stream, SNDRV_PCM_TRIGGER_START);
if (ret < 0) {
dev_err(sdev->dev, "error: DMA trigger start failed\n");
return ret;
}
dev_dbg(sdev->dev, "waiting for FW_ENTERED status\n");
status = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR,
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);
/*
* even in case of errors we still need to stop the DMAs,
* but we return the initial error should the DMA stop also fail
*/
if (status < 0) {
dev_err(sdev->dev,
"%s: timeout with rom_status_reg (%#x) read\n",
__func__, chip->rom_status_reg);
} else {
dev_dbg(sdev->dev, "Code loader FW_ENTERED status\n");
}
ret = hda_cl_trigger(sdev->dev, hext_stream, SNDRV_PCM_TRIGGER_STOP);
if (ret < 0) {
dev_err(sdev->dev, "error: DMA trigger stop failed\n");
if (!status)
status = ret;
} else {
dev_dbg(sdev->dev, "Code loader DMA stopped\n");
}
return status;
}
int hda_dsp_cl_boot_firmware_iccmax(struct snd_sof_dev *sdev)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
struct hdac_ext_stream *iccmax_stream;
int ret, ret1;
u8 original_gb;
/* save the original LTRP guardband value */
original_gb = snd_sof_dsp_read8(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_LTRP) &
HDA_VS_INTEL_LTRP_GB_MASK;
/*
* Prepare capture stream for ICCMAX. We do not need to store
* the data, so use a buffer of PAGE_SIZE for receiving.
*/
iccmax_stream = hda_cl_prepare(sdev->dev, HDA_CL_STREAM_FORMAT, PAGE_SIZE,
&hda->iccmax_dmab, persistent_cl_buffer,
SNDRV_PCM_STREAM_CAPTURE, true);
if (IS_ERR(iccmax_stream)) {
dev_err(sdev->dev, "error: dma prepare for ICCMAX stream failed\n");
return PTR_ERR(iccmax_stream);
}
ret = hda_dsp_cl_boot_firmware(sdev);
/*
* Perform iccmax stream cleanup. This should be done even if firmware loading fails.
* If the cleanup also fails, we return the initial error
*/
ret1 = hda_cl_cleanup(sdev->dev, &hda->iccmax_dmab,
persistent_cl_buffer, iccmax_stream);
if (ret1 < 0) {
dev_err(sdev->dev, "error: ICCMAX stream cleanup failed\n");
/* set return value to indicate cleanup failure */
if (!ret)
ret = ret1;
}
/* restore the original guardband value after FW boot */
snd_sof_dsp_update8(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_LTRP,
HDA_VS_INTEL_LTRP_GB_MASK, original_gb);
return ret;
}
EXPORT_SYMBOL_NS(hda_dsp_cl_boot_firmware_iccmax, "SND_SOC_SOF_INTEL_CNL");
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 *hext_stream;
struct firmware stripped_firmware;
int ret, ret1, i;
if (hda->imrboot_supported && !sdev->first_boot && !hda->skip_imr_boot) {
dev_dbg(sdev->dev, "IMR restore supported, booting from IMR directly\n");
hda->boot_iteration = 0;
ret = hda_dsp_boot_imr(sdev);
if (!ret) {
hda->booted_from_imr = true;
return 0;
}
dev_warn(sdev->dev, "IMR restore failed, trying to cold boot\n");
}
hda->booted_from_imr = false;
chip_info = desc->chip_info;
if (sdev->basefw.fw->size <= sdev->basefw.payload_offset) {
dev_err(sdev->dev, "error: firmware size must be greater than firmware offset\n");
return -EINVAL;
}
/* init for booting wait */
init_waitqueue_head(&sdev->boot_wait);
/* prepare DMA for code loader stream */
stripped_firmware.size = sdev->basefw.fw->size - sdev->basefw.payload_offset;
hext_stream = hda_cl_prepare(sdev->dev, HDA_CL_STREAM_FORMAT,
stripped_firmware.size,
&hda->cl_dmab, persistent_cl_buffer,
SNDRV_PCM_STREAM_PLAYBACK, false);
if (IS_ERR(hext_stream)) {
dev_err(sdev->dev, "error: dma prepare for fw loading failed\n");
return PTR_ERR(hext_stream);
}
/*
* Copy the payload to the DMA buffer if it is temporary or if the
* buffer is persistent but it does not have the basefw payload either
* because this is the first boot and the buffer needs to be initialized,
* or a library got loaded and it replaced the basefw.
*/
if (!persistent_cl_buffer || !hda->cl_dmab_contains_basefw) {
stripped_firmware.data = sdev->basefw.fw->data + sdev->basefw.payload_offset;
memcpy(hda->cl_dmab.area, stripped_firmware.data, stripped_firmware.size);
hda->cl_dmab_contains_basefw = true;
}
/* try ROM init a few times before giving up */
for (i = 0; i < HDA_FW_BOOT_ATTEMPTS; i++) {
dev_dbg(sdev->dev,
"Attempting iteration %d of Core En/ROM load...\n", i);
hda->boot_iteration = i + 1;
if (chip_info->cl_init)
ret = chip_info->cl_init(sdev, hext_stream->hstream.stream_tag, false);
else
ret = -EINVAL;
/* don't retry anymore if successful */
if (!ret)
break;
}
if (i == HDA_FW_BOOT_ATTEMPTS) {
dev_err(sdev->dev, "error: dsp init failed after %d attempts with err: %d\n",
i, ret);
goto cleanup;
}
/*
* When a SoundWire link is in clock stop state, a Slave
* device may trigger in-band wakes for events such as jack
* insertion or acoustic event detection. This event will lead
* to a WAKEEN interrupt, handled by the PCI device and routed
* to PME if the PCI device is in D3. The resume function in
* audio PCI driver will be invoked by ACPI for PME event and
* initialize the device and process WAKEEN interrupt.
*
* The WAKEEN interrupt should be processed ASAP to prevent an
* interrupt flood, otherwise other interrupts, such IPC,
* cannot work normally. The WAKEEN is handled after the ROM
* is initialized successfully, which ensures power rails are
* enabled before accessing the SoundWire SHIM registers
*/
if (!sdev->first_boot)
hda_sdw_process_wakeen(sdev);
/*
* 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
*/
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, "Firmware download failed",
SOF_DBG_DUMP_PCI | SOF_DBG_DUMP_MBOX);
hda->skip_imr_boot = true;
}
cleanup:
/*
* Perform codeloader stream cleanup.
* This should be done even if firmware loading fails.
* If the cleanup also fails, we return the initial error
*/
ret1 = hda_cl_cleanup(sdev->dev, &hda->cl_dmab,
persistent_cl_buffer, hext_stream);
if (ret1 < 0) {
dev_err(sdev->dev, "error: Code loader DSP cleanup failed\n");
/* set return value to indicate cleanup failure */
if (!ret)
ret = ret1;
}
/*
* return primary core id if both fw copy
* and stream clean up are successful
*/
if (!ret)
return chip_info->init_core_mask;
/* disable DSP */
hda_dsp_ctrl_ppcap_enable(sdev, false);
return ret;
}
EXPORT_SYMBOL_NS(hda_dsp_cl_boot_firmware, "SND_SOC_SOF_INTEL_HDA_COMMON");
int hda_dsp_ipc4_load_library(struct snd_sof_dev *sdev,
struct sof_ipc4_fw_library *fw_lib, bool reload)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
struct sof_ipc4_fw_data *ipc4_data = sdev->private;
struct hdac_ext_stream *hext_stream;
struct firmware stripped_firmware;
struct sof_ipc4_msg msg = {};
int ret, ret1;
/*
* if IMR booting is enabled and libraries have been restored during fw
* boot, skip the loading
*/
if (reload && hda->booted_from_imr && ipc4_data->libraries_restored)
return 0;
/* the fw_lib has been verified during loading, we can trust the validity here */
stripped_firmware.data = fw_lib->sof_fw.fw->data + fw_lib->sof_fw.payload_offset;
stripped_firmware.size = fw_lib->sof_fw.fw->size - fw_lib->sof_fw.payload_offset;
/*
* force re-allocation of the cl_dmab if the preserved DMA buffer is
* smaller than what is needed for the library
*/
if (persistent_cl_buffer && stripped_firmware.size > hda->cl_dmab.bytes) {
snd_dma_free_pages(&hda->cl_dmab);
hda->cl_dmab.area = NULL;
hda->cl_dmab.bytes = 0;
}
/* prepare DMA for code loader stream */
hext_stream = hda_cl_prepare(sdev->dev, HDA_CL_STREAM_FORMAT,
stripped_firmware.size,
&hda->cl_dmab, persistent_cl_buffer,
SNDRV_PCM_STREAM_PLAYBACK, false);
if (IS_ERR(hext_stream)) {
dev_err(sdev->dev, "%s: DMA prepare failed\n", __func__);
return PTR_ERR(hext_stream);
}
memcpy(hda->cl_dmab.area, stripped_firmware.data, stripped_firmware.size);
hda->cl_dmab_contains_basefw = false;
/*
* 1st stage: SOF_IPC4_GLB_LOAD_LIBRARY_PREPARE
* Message includes the dma_id to be prepared for the library loading.
* If the firmware does not have support for the message, we will
* receive -EOPNOTSUPP. In this case we will use single step library
* loading and proceed to send the LOAD_LIBRARY message.
*/
msg.primary = hext_stream->hstream.stream_tag - 1;
msg.primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_LOAD_LIBRARY_PREPARE);
msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
msg.primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG);
ret = sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0);
if (!ret) {
int sd_offset = SOF_STREAM_SD_OFFSET(&hext_stream->hstream);
unsigned int status;
/*
* Make sure that the FIFOS value is not 0 in SDxFIFOS register
* which indicates that the firmware set the GEN bit and we can
* continue to start the DMA
*/
ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_HDA_BAR,
sd_offset + SOF_HDA_ADSP_REG_SD_FIFOSIZE,
status,
status & SOF_HDA_SD_FIFOSIZE_FIFOS_MASK,
HDA_DSP_REG_POLL_INTERVAL_US,
HDA_DSP_BASEFW_TIMEOUT_US);
if (ret < 0)
dev_warn(sdev->dev,
"%s: timeout waiting for FIFOS\n", __func__);
} else if (ret != -EOPNOTSUPP) {
goto cleanup;
}
ret = hda_cl_trigger(sdev->dev, hext_stream, SNDRV_PCM_TRIGGER_START);
if (ret < 0) {
dev_err(sdev->dev, "%s: DMA trigger start failed\n", __func__);
goto cleanup;
}
/*
* 2nd stage: LOAD_LIBRARY
* Message includes the dma_id and the lib_id, the dma_id must be
* identical to the one sent via LOAD_LIBRARY_PREPARE
*/
msg.primary &= ~SOF_IPC4_MSG_TYPE_MASK;
msg.primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_LOAD_LIBRARY);
msg.primary |= SOF_IPC4_GLB_LOAD_LIBRARY_LIB_ID(fw_lib->id);
ret = sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0);
/* Stop the DMA channel */
ret1 = hda_cl_trigger(sdev->dev, hext_stream, SNDRV_PCM_TRIGGER_STOP);
if (ret1 < 0) {
dev_err(sdev->dev, "%s: DMA trigger stop failed\n", __func__);
if (!ret)
ret = ret1;
}
cleanup:
/* clean up even in case of error and return the first error */
ret1 = hda_cl_cleanup(sdev->dev, &hda->cl_dmab, persistent_cl_buffer,
hext_stream);
if (ret1 < 0) {
dev_err(sdev->dev, "%s: Code loader DSP cleanup failed\n", __func__);
/* set return value to indicate cleanup failure */
if (!ret)
ret = ret1;
}
return ret;
}
EXPORT_SYMBOL_NS(hda_dsp_ipc4_load_library, "SND_SOC_SOF_INTEL_HDA_COMMON");
int hda_dsp_ext_man_get_cavs_config_data(struct snd_sof_dev *sdev,
const struct sof_ext_man_elem_header *hdr)
{
const struct sof_ext_man_cavs_config_data *config_data =
container_of(hdr, struct sof_ext_man_cavs_config_data, hdr);
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
int i, elem_num;
/* calculate total number of config data elements */
elem_num = (hdr->size - sizeof(struct sof_ext_man_elem_header))
/ sizeof(struct sof_config_elem);
if (elem_num <= 0) {
dev_err(sdev->dev, "cavs config data is inconsistent: %d\n", elem_num);
return -EINVAL;
}
for (i = 0; i < elem_num; i++)
switch (config_data->elems[i].token) {
case SOF_EXT_MAN_CAVS_CONFIG_EMPTY:
/* skip empty token */
break;
case SOF_EXT_MAN_CAVS_CONFIG_CAVS_LPRO:
hda->clk_config_lpro = config_data->elems[i].value;
dev_dbg(sdev->dev, "FW clock config: %s\n",
hda->clk_config_lpro ? "LPRO" : "HPRO");
break;
case SOF_EXT_MAN_CAVS_CONFIG_OUTBOX_SIZE:
case SOF_EXT_MAN_CAVS_CONFIG_INBOX_SIZE:
/* These elements are defined but not being used yet. No warn is required */
break;
default:
dev_info(sdev->dev, "unsupported token type: %d\n",
config_data->elems[i].token);
}
return 0;
}
EXPORT_SYMBOL_NS(hda_dsp_ext_man_get_cavs_config_data, "SND_SOC_SOF_INTEL_HDA_COMMON");
File diff suppressed because it is too large Load Diff
+363
View File
@@ -0,0 +1,363 @@
// 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
//
// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com>
// Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
// Rander Wang <rander.wang@intel.com>
// Keyon Jie <yang.jie@linux.intel.com>
//
/*
* Hardware interface for generic Intel audio DSP HDA IP
*/
#include <linux/moduleparam.h>
#include <sound/hda_register.h>
#include <sound/pcm_params.h>
#include <trace/events/sof_intel.h>
#include "../sof-audio.h"
#include "../ops.h"
#include "hda.h"
#define SDnFMT_BASE(x) ((x) << 14)
#define SDnFMT_MULT(x) (((x) - 1) << 11)
#define SDnFMT_DIV(x) (((x) - 1) << 8)
#define SDnFMT_BITS(x) ((x) << 4)
#define SDnFMT_CHAN(x) ((x) << 0)
#define HDA_MAX_PERIOD_TIME_HEADROOM 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;
module_param_named(disable_rewinds, hda_disable_rewinds, bool, 0444);
MODULE_PARM_DESC(disable_rewinds, "SOF HDA disable rewinds");
static int hda_force_pause_support = -1;
module_param_named(force_pause_support, hda_force_pause_support, int, 0444);
MODULE_PARM_DESC(force_pause_support,
"Pause support: -1: Use default, 0: Disable, 1: Enable (default -1)");
u32 hda_dsp_get_mult_div(struct snd_sof_dev *sdev, int rate)
{
switch (rate) {
case 8000:
return SDnFMT_DIV(6);
case 9600:
return SDnFMT_DIV(5);
case 11025:
return SDnFMT_BASE(1) | SDnFMT_DIV(4);
case 16000:
return SDnFMT_DIV(3);
case 22050:
return SDnFMT_BASE(1) | SDnFMT_DIV(2);
case 32000:
return SDnFMT_DIV(3) | SDnFMT_MULT(2);
case 44100:
return SDnFMT_BASE(1);
case 48000:
return 0;
case 88200:
return SDnFMT_BASE(1) | SDnFMT_MULT(2);
case 96000:
return SDnFMT_MULT(2);
case 176400:
return SDnFMT_BASE(1) | SDnFMT_MULT(4);
case 192000:
return SDnFMT_MULT(4);
default:
dev_warn(sdev->dev, "can't find div rate %d using 48kHz\n",
rate);
return 0; /* use 48KHz if not found */
}
};
u32 hda_dsp_get_bits(struct snd_sof_dev *sdev, int sample_bits)
{
switch (sample_bits) {
case 8:
return SDnFMT_BITS(0);
case 16:
return SDnFMT_BITS(1);
case 20:
return SDnFMT_BITS(2);
case 24:
return SDnFMT_BITS(3);
case 32:
return SDnFMT_BITS(4);
default:
dev_warn(sdev->dev, "can't find %d bits using 16bit\n",
sample_bits);
return SDnFMT_BITS(1); /* use 16bits format if not found */
}
};
int hda_dsp_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 hdac_stream *hstream = substream->runtime->private_data;
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;
int ret;
hstream->substream = substream;
dmab = substream->runtime->dma_buffer_p;
/*
* Use the codec required format val (which is link_bps adjusted) when
* the DSP is not in use
*/
if (!sdev->dspless_mode_selected) {
u32 rate = hda_dsp_get_mult_div(sdev, params_rate(params));
u32 bits = hda_dsp_get_bits(sdev, params_width(params));
hstream->format_val = rate | bits | (params_channels(params) - 1);
}
hstream->bufsize = params_buffer_bytes(params);
hstream->period_bytes = params_period_bytes(params);
hstream->no_period_wakeup =
(params->info & SNDRV_PCM_INFO_NO_PERIOD_WAKEUP) &&
(params->flags & SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP);
ret = hda_dsp_stream_hw_params(sdev, hext_stream, dmab, params);
if (ret < 0) {
dev_err(sdev->dev, "error: hdac prepare failed: %d\n", ret);
return ret;
}
/* 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;
}
EXPORT_SYMBOL_NS(hda_dsp_pcm_hw_params, "SND_SOC_SOF_INTEL_HDA_COMMON");
/* update SPIB register with appl position */
int hda_dsp_pcm_ack(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream)
{
struct hdac_stream *hstream = substream->runtime->private_data;
struct snd_pcm_runtime *runtime = substream->runtime;
ssize_t appl_pos, buf_size;
u32 spib;
appl_pos = frames_to_bytes(runtime, runtime->control->appl_ptr);
buf_size = frames_to_bytes(runtime, runtime->buffer_size);
spib = appl_pos % buf_size;
/* Allowable value for SPIB is 1 byte to max buffer size */
if (!spib)
spib = buf_size;
sof_io_write(sdev, hstream->spib_addr, spib);
return 0;
}
EXPORT_SYMBOL_NS(hda_dsp_pcm_ack, "SND_SOC_SOF_INTEL_HDA_COMMON");
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 *hext_stream = stream_to_hdac_ext_stream(hstream);
return hda_dsp_stream_trigger(sdev, hext_stream, cmd);
}
EXPORT_SYMBOL_NS(hda_dsp_pcm_trigger, "SND_SOC_SOF_INTEL_HDA_COMMON");
snd_pcm_uframes_t hda_dsp_pcm_pointer(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_soc_component *scomp = sdev->component;
struct hdac_stream *hstream = substream->runtime->private_data;
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
struct snd_sof_pcm *spcm;
snd_pcm_uframes_t pos;
spcm = snd_sof_find_spcm_dai(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;
}
if (hda && !hda->no_ipc_position) {
/* read position from IPC position */
pos = spcm->stream[substream->stream].posn.host_posn;
goto found;
}
pos = hda_dsp_stream_get_position(hstream, substream->stream, true);
found:
pos = bytes_to_frames(substream->runtime, pos);
trace_sof_intel_hda_dsp_pcm(sdev, hstream, substream, pos);
return pos;
}
EXPORT_SYMBOL_NS(hda_dsp_pcm_pointer, "SND_SOC_SOF_INTEL_HDA_COMMON");
int hda_dsp_pcm_open(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream)
{
const struct sof_intel_dsp_desc *chip_info = get_chip_info(sdev->pdata);
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_component *scomp = sdev->component;
struct hdac_ext_stream *dsp_stream;
struct snd_sof_pcm *spcm;
int direction = substream->stream;
u32 flags = 0;
spcm = snd_sof_find_spcm_dai(scomp, rtd);
if (!spcm) {
dev_err(sdev->dev, "error: can't find PCM with DAI ID %d\n", rtd->dai_link->id);
return -EINVAL;
}
/*
* if we want the .ack to work, we need to prevent the control from being mapped.
* The status can still be mapped.
*/
if (hda_disable_rewinds)
runtime->hw.info |= SNDRV_PCM_INFO_NO_REWINDS | SNDRV_PCM_INFO_SYNC_APPLPTR;
/*
* All playback streams are DMI L1 capable, capture streams need
* pause push/release to be disabled
*/
if (hda_always_enable_dmi_l1 && direction == SNDRV_PCM_STREAM_CAPTURE)
runtime->hw.info &= ~SNDRV_PCM_INFO_PAUSE;
/*
* Do not advertise the PAUSE support if it is forced to be disabled via
* module parameter or if the pause_supported is false for the PCM
* device
*/
if (hda_force_pause_support == 0 ||
(hda_force_pause_support == -1 &&
!spcm->stream[substream->stream].pause_supported))
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;
dsp_stream = hda_dsp_stream_get(sdev, direction, flags);
if (!dsp_stream) {
dev_err(sdev->dev, "error: no stream available\n");
return -ENODEV;
}
/*
* Set period size constraint to ensure BDLE buffer length and
* start address alignment requirements are met. Align to 128
* bytes for newer Intel platforms, with older ones using 4 byte alignment.
*/
if (chip_info->hw_ip_version >= SOF_INTEL_ACE_4_0)
snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 128);
else
snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 4);
/* avoid circular buffer wrap in middle of period */
snd_pcm_hw_constraint_integer(substream->runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
/* Limit the maximum number of periods to not exceed the BDL entries count */
if (runtime->hw.periods_max > HDA_DSP_MAX_BDL_ENTRIES)
snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIODS,
runtime->hw.periods_min,
HDA_DSP_MAX_BDL_ENTRIES);
/* Only S16 and S32 supported by HDA hardware when used without DSP */
if (sdev->dspless_mode_selected)
snd_pcm_hw_constraint_mask64(substream->runtime, SNDRV_PCM_HW_PARAM_FORMAT,
SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_S32);
/*
* The dsp_max_burst_size_in_ms is the length of the maximum burst size
* of the host DMA in the ALSA buffer.
*
* On playback start the DMA will transfer dsp_max_burst_size_in_ms
* amount of data in one initial burst to fill up the host DMA buffer.
* Consequent DMA burst sizes are shorter and their length can vary.
* To avoid immediate xrun by the initial burst we need to place
* constraint on the period size (via PERIOD_TIME) to cover the size of
* the host buffer.
* We need to add headroom of max 10ms as the firmware needs time to
* settle to the 1ms pacing and initially it can run faster for few
* internal periods.
*
* On capture the DMA will transfer 1ms chunks.
*/
if (spcm->stream[direction].dsp_max_burst_size_in_ms) {
unsigned int period_time = spcm->stream[direction].dsp_max_burst_size_in_ms;
/*
* add headroom over the maximum burst size to cover the time
* needed for the DMA pace to settle.
* Limit the headroom time to HDA_MAX_PERIOD_TIME_HEADROOM
*/
period_time += min(period_time, HDA_MAX_PERIOD_TIME_HEADROOM);
snd_pcm_hw_constraint_minmax(substream->runtime,
SNDRV_PCM_HW_PARAM_PERIOD_TIME,
period_time * USEC_PER_MSEC,
UINT_MAX);
}
/* binding pcm substream to hda stream */
substream->runtime->private_data = &dsp_stream->hstream;
/*
* Reset the llp cache values (they are used for LLP compensation in
* case the counter is not reset)
*/
dsp_stream->pplcllpl = 0;
dsp_stream->pplcllpu = 0;
return 0;
}
EXPORT_SYMBOL_NS(hda_dsp_pcm_open, "SND_SOC_SOF_INTEL_HDA_COMMON");
int hda_dsp_pcm_close(struct snd_sof_dev *sdev,
struct snd_pcm_substream *substream)
{
struct hdac_stream *hstream = substream->runtime->private_data;
int direction = substream->stream;
int ret;
ret = hda_dsp_stream_put(sdev, direction, hstream->stream_tag);
if (ret) {
dev_dbg(sdev->dev, "stream %s not opened!\n", substream->name);
return -ENODEV;
}
/* unbinding pcm substream to hda stream */
substream->runtime->private_data = NULL;
return 0;
}
EXPORT_SYMBOL_NS(hda_dsp_pcm_close, "SND_SOC_SOF_INTEL_HDA_COMMON");
@@ -0,0 +1,150 @@
// 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
//
// Author: Cezary Rojewski <cezary.rojewski@intel.com>
// Converted to SOF client:
// Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
// Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
//
#include <linux/module.h>
#include <sound/hdaudio_ext.h>
#include <sound/soc.h>
#include "../sof-priv.h"
#include "../sof-client-probes.h"
#include "../sof-client.h"
#include "hda.h"
static inline struct hdac_ext_stream *
hda_compr_get_stream(struct snd_compr_stream *cstream)
{
return cstream->runtime->private_data;
}
static int hda_probes_compr_startup(struct sof_client_dev *cdev,
struct snd_compr_stream *cstream,
struct snd_soc_dai *dai, u32 *stream_id)
{
struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
struct hdac_ext_stream *hext_stream;
hext_stream = hda_dsp_stream_get(sdev, cstream->direction, 0);
if (!hext_stream)
return -EBUSY;
hdac_stream(hext_stream)->curr_pos = 0;
hdac_stream(hext_stream)->cstream = cstream;
cstream->runtime->private_data = hext_stream;
*stream_id = hdac_stream(hext_stream)->stream_tag;
return 0;
}
static int hda_probes_compr_shutdown(struct sof_client_dev *cdev,
struct snd_compr_stream *cstream,
struct snd_soc_dai *dai)
{
struct hdac_ext_stream *hext_stream = hda_compr_get_stream(cstream);
struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
int ret;
ret = hda_dsp_stream_put(sdev, cstream->direction,
hdac_stream(hext_stream)->stream_tag);
if (ret < 0) {
dev_dbg(sdev->dev, "stream put failed: %d\n", ret);
return ret;
}
hdac_stream(hext_stream)->cstream = NULL;
cstream->runtime->private_data = NULL;
return 0;
}
static int hda_probes_compr_set_params(struct sof_client_dev *cdev,
struct snd_compr_stream *cstream,
struct snd_compr_params *params,
struct snd_soc_dai *dai)
{
struct hdac_ext_stream *hext_stream = hda_compr_get_stream(cstream);
struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
struct hdac_stream *hstream = hdac_stream(hext_stream);
struct snd_dma_buffer *dmab;
u32 bits, rate;
int bps, ret;
dmab = cstream->runtime->dma_buffer_p;
/* compr params do not store bit depth, default to S32_LE */
bps = snd_pcm_format_physical_width(SNDRV_PCM_FORMAT_S32_LE);
if (bps < 0)
return bps;
bits = hda_dsp_get_bits(sdev, bps);
rate = hda_dsp_get_mult_div(sdev, params->codec.sample_rate);
hstream->format_val = rate | bits | (params->codec.ch_out - 1);
hstream->bufsize = cstream->runtime->buffer_size;
hstream->period_bytes = cstream->runtime->fragment_size;
hstream->no_period_wakeup = 0;
ret = hda_dsp_stream_hw_params(sdev, hext_stream, dmab, NULL);
if (ret < 0) {
dev_err(sdev->dev, "error: hdac prepare failed: %d\n", ret);
return ret;
}
return 0;
}
static int hda_probes_compr_trigger(struct sof_client_dev *cdev,
struct snd_compr_stream *cstream,
int cmd, struct snd_soc_dai *dai)
{
struct hdac_ext_stream *hext_stream = hda_compr_get_stream(cstream);
struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
return hda_dsp_stream_trigger(sdev, hext_stream, cmd);
}
static int hda_probes_compr_pointer(struct sof_client_dev *cdev,
struct snd_compr_stream *cstream,
struct snd_compr_tstamp64 *tstamp,
struct snd_soc_dai *dai)
{
struct hdac_ext_stream *hext_stream = hda_compr_get_stream(cstream);
struct snd_soc_pcm_stream *pstream;
pstream = &dai->driver->capture;
tstamp->copied_total = hdac_stream(hext_stream)->curr_pos;
tstamp->sampling_rate = snd_pcm_rate_bit_to_rate(pstream->rates);
return 0;
}
/* SOF client implementation */
static const struct sof_probes_host_ops hda_probes_ops = {
.startup = hda_probes_compr_startup,
.shutdown = hda_probes_compr_shutdown,
.set_params = hda_probes_compr_set_params,
.trigger = hda_probes_compr_trigger,
.pointer = hda_probes_compr_pointer,
};
int hda_probes_register(struct snd_sof_dev *sdev)
{
return sof_client_dev_register(sdev, "hda-probes", 0, &hda_probes_ops,
sizeof(hda_probes_ops));
}
EXPORT_SYMBOL_NS(hda_probes_register, "SND_SOC_SOF_INTEL_HDA_COMMON");
void hda_probes_unregister(struct snd_sof_dev *sdev)
{
sof_client_dev_unregister(sdev, "hda-probes", 0);
}
EXPORT_SYMBOL_NS(hda_probes_unregister, "SND_SOC_SOF_INTEL_HDA_COMMON");
MODULE_IMPORT_NS("SND_SOC_SOF_CLIENT");
@@ -0,0 +1,495 @@
// 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) 2025 Intel Corporation.
//
/*
* Hardware interface for SoundWire BPT support with HDA DMA
*/
#include <linux/lcm.h>
#include <sound/hdaudio_ext.h>
#include <sound/hda-mlink.h>
#include <sound/hda-sdw-bpt.h>
#include <sound/sof.h>
#include <sound/sof/ipc4/header.h>
#include "../ops.h"
#include "../sof-priv.h"
#include "../ipc4-priv.h"
#include "hda.h"
#define BPT_FREQUENCY 192000 /* The max rate defined in rate_bits[] hdac_device.c */
#define BPT_MULTIPLIER ((BPT_FREQUENCY / 48000) - 1)
#define BPT_CHAIN_DMA_FIFO_MS 10
/*
* This routine is directly inspired by sof_ipc4_chain_dma_trigger(),
* with major simplifications since there are no pipelines defined
* and no dependency on ALSA hw_params
*/
static int chain_dma_trigger(struct snd_sof_dev *sdev, unsigned int stream_tag,
int direction, int state)
{
struct sof_ipc4_fw_data *ipc4_data = sdev->private;
bool allocate, enable, set_fifo_size;
struct sof_ipc4_msg msg = {{ 0 }};
int dma_id;
if (sdev->pdata->ipc_type != SOF_IPC_TYPE_4)
return -EOPNOTSUPP;
switch (state) {
case SOF_IPC4_PIPE_RUNNING: /* Allocate and start the chain */
allocate = true;
enable = true;
set_fifo_size = true;
break;
case SOF_IPC4_PIPE_PAUSED: /* Stop the chain */
allocate = true;
enable = false;
set_fifo_size = false;
break;
case SOF_IPC4_PIPE_RESET: /* Deallocate chain resources and remove the chain */
allocate = false;
enable = false;
set_fifo_size = false;
break;
default:
dev_err(sdev->dev, "Unexpected state %d", state);
return -EINVAL;
}
msg.primary = SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_CHAIN_DMA);
msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
msg.primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG);
/* for BPT/BRA we can use the same stream tag for host and link */
dma_id = stream_tag - 1;
if (direction == SNDRV_PCM_STREAM_CAPTURE)
dma_id += ipc4_data->num_playback_streams;
msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_HOST_ID(dma_id);
msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_LINK_ID(dma_id);
/* For BPT/BRA we use 32 bits so SCS is not set */
/* CHAIN DMA needs at least 2ms */
if (set_fifo_size)
msg.extension |= SOF_IPC4_GLB_EXT_CHAIN_DMA_FIFO_SIZE(BPT_FREQUENCY / 1000 *
BPT_CHAIN_DMA_FIFO_MS *
sizeof(u32));
if (allocate)
msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_ALLOCATE_MASK;
if (enable)
msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_ENABLE_MASK;
return sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0);
}
static int hda_sdw_bpt_dma_prepare(struct device *dev, struct hdac_ext_stream **sdw_bpt_stream,
struct snd_dma_buffer *dmab_bdl, u32 bpt_num_bytes,
unsigned int num_channels, int direction)
{
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
struct hdac_ext_stream *bpt_stream;
unsigned int format = HDA_CL_STREAM_FORMAT;
if (!sdev->dspless_mode_selected) {
int ret;
/*
* Make sure that the DSP is booted up, which might not be the
* case if the on-demand DSP boot is used
*/
ret = snd_sof_boot_dsp_firmware(sdev);
if (ret)
return ret;
}
/*
* the baseline format needs to be adjusted to
* bandwidth requirements
*/
format |= (num_channels - 1);
format |= BPT_MULTIPLIER << AC_FMT_MULT_SHIFT;
dev_dbg(dev, "direction %d format_val %#x\n", direction, format);
bpt_stream = hda_data_stream_prepare(dev, format, bpt_num_bytes, dmab_bdl,
false, direction, false, true);
if (IS_ERR(bpt_stream)) {
dev_err(sdev->dev, "%s: SDW BPT DMA prepare failed: dir %d\n",
__func__, direction);
return PTR_ERR(bpt_stream);
}
*sdw_bpt_stream = bpt_stream;
if (!sdev->dspless_mode_selected) {
struct hdac_stream *hstream;
u32 mask;
/* decouple host and link DMA if the DSP is used */
hstream = &bpt_stream->hstream;
mask = BIT(hstream->index);
snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, mask, mask);
snd_hdac_ext_stream_reset(bpt_stream);
snd_hdac_ext_stream_setup(bpt_stream, format);
}
if (hdac_stream(bpt_stream)->direction == SNDRV_PCM_STREAM_PLAYBACK) {
struct hdac_bus *bus = sof_to_bus(sdev);
struct hdac_ext_link *hlink;
int stream_tag;
stream_tag = hdac_stream(bpt_stream)->stream_tag;
hlink = hdac_bus_eml_sdw_get_hlink(bus);
snd_hdac_ext_bus_link_set_stream_id(hlink, stream_tag);
}
return 0;
}
static int hda_sdw_bpt_dma_deprepare(struct device *dev, struct hdac_ext_stream *sdw_bpt_stream,
struct snd_dma_buffer *dmab_bdl)
{
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
struct hdac_stream *hstream;
u32 mask;
int ret;
ret = hda_data_stream_cleanup(sdev->dev, dmab_bdl, false, sdw_bpt_stream, true);
if (ret < 0) {
dev_err(sdev->dev, "%s: SDW BPT DMA cleanup failed\n",
__func__);
return ret;
}
if (hdac_stream(sdw_bpt_stream)->direction == SNDRV_PCM_STREAM_PLAYBACK) {
struct hdac_bus *bus = sof_to_bus(sdev);
struct hdac_ext_link *hlink;
int stream_tag;
stream_tag = hdac_stream(sdw_bpt_stream)->stream_tag;
hlink = hdac_bus_eml_sdw_get_hlink(bus);
snd_hdac_ext_bus_link_clear_stream_id(hlink, stream_tag);
}
if (!sdev->dspless_mode_selected) {
/* Release CHAIN_DMA resources */
ret = chain_dma_trigger(sdev, hdac_stream(sdw_bpt_stream)->stream_tag,
hdac_stream(sdw_bpt_stream)->direction,
SOF_IPC4_PIPE_RESET);
if (ret < 0)
dev_err(sdev->dev, "%s: chain_dma_trigger PIPE_RESET failed: %d\n",
__func__, ret);
/* couple host and link DMA */
hstream = &sdw_bpt_stream->hstream;
mask = BIT(hstream->index);
snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, mask, 0);
}
return 0;
}
static int hda_sdw_bpt_dma_enable(struct device *dev, struct hdac_ext_stream *sdw_bpt_stream)
{
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
int ret;
ret = hda_cl_trigger(sdev->dev, sdw_bpt_stream, SNDRV_PCM_TRIGGER_START);
if (ret < 0)
dev_err(sdev->dev, "%s: SDW BPT DMA trigger start failed\n", __func__);
if (!sdev->dspless_mode_selected) {
/* the chain DMA needs to be programmed before the DMAs */
ret = chain_dma_trigger(sdev, hdac_stream(sdw_bpt_stream)->stream_tag,
hdac_stream(sdw_bpt_stream)->direction,
SOF_IPC4_PIPE_RUNNING);
if (ret < 0) {
dev_err(sdev->dev, "%s: chain_dma_trigger failed: %d\n",
__func__, ret);
hda_cl_trigger(sdev->dev, sdw_bpt_stream, SNDRV_PCM_TRIGGER_STOP);
return ret;
}
snd_hdac_ext_stream_start(sdw_bpt_stream);
}
return ret;
}
static int hda_sdw_bpt_dma_disable(struct device *dev, struct hdac_ext_stream *sdw_bpt_stream)
{
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
int ret;
if (!sdev->dspless_mode_selected) {
snd_hdac_ext_stream_clear(sdw_bpt_stream);
ret = chain_dma_trigger(sdev, hdac_stream(sdw_bpt_stream)->stream_tag,
hdac_stream(sdw_bpt_stream)->direction,
SOF_IPC4_PIPE_PAUSED);
if (ret < 0)
dev_err(sdev->dev, "%s: chain_dma_trigger PIPE_PAUSED failed: %d\n",
__func__, ret);
}
ret = hda_cl_trigger(sdev->dev, sdw_bpt_stream, SNDRV_PCM_TRIGGER_STOP);
if (ret < 0)
dev_err(sdev->dev, "%s: SDW BPT DMA trigger stop failed\n", __func__);
return ret;
}
#define FIFO_ALIGNMENT 64
unsigned int hda_sdw_bpt_get_buf_size_alignment(unsigned int dma_bandwidth)
{
unsigned int num_channels = DIV_ROUND_UP(dma_bandwidth, BPT_FREQUENCY * 32);
unsigned int data_block = num_channels * 4;
unsigned int alignment = lcm(data_block, FIFO_ALIGNMENT);
return alignment;
}
EXPORT_SYMBOL_NS(hda_sdw_bpt_get_buf_size_alignment, "SND_SOC_SOF_INTEL_HDA_SDW_BPT");
int hda_sdw_bpt_open(struct device *dev, int link_id, struct hdac_ext_stream **bpt_tx_stream,
struct snd_dma_buffer *dmab_tx_bdl, u32 bpt_tx_num_bytes,
u32 tx_dma_bandwidth, struct hdac_ext_stream **bpt_rx_stream,
struct snd_dma_buffer *dmab_rx_bdl, u32 bpt_rx_num_bytes,
u32 rx_dma_bandwidth)
{
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
unsigned int num_channels_tx;
unsigned int num_channels_rx;
int ret1;
int ret;
num_channels_tx = DIV_ROUND_UP(tx_dma_bandwidth, BPT_FREQUENCY * 32);
ret = hda_sdw_bpt_dma_prepare(dev, bpt_tx_stream, dmab_tx_bdl, bpt_tx_num_bytes,
num_channels_tx, SNDRV_PCM_STREAM_PLAYBACK);
if (ret < 0) {
dev_err(dev, "%s: hda_sdw_bpt_dma_prepare failed for TX: %d\n",
__func__, ret);
return ret;
}
num_channels_rx = DIV_ROUND_UP(rx_dma_bandwidth, BPT_FREQUENCY * 32);
ret = hda_sdw_bpt_dma_prepare(dev, bpt_rx_stream, dmab_rx_bdl, bpt_rx_num_bytes,
num_channels_rx, SNDRV_PCM_STREAM_CAPTURE);
if (ret < 0) {
dev_err(dev, "%s: hda_sdw_bpt_dma_prepare failed for RX: %d\n",
__func__, ret);
ret1 = hda_sdw_bpt_dma_deprepare(dev, *bpt_tx_stream, dmab_tx_bdl);
if (ret1 < 0)
dev_err(dev, "%s: hda_sdw_bpt_dma_deprepare failed for TX: %d\n",
__func__, ret1);
return ret;
}
/* we need to map the channels in PCMSyCM registers */
ret = hdac_bus_eml_sdw_map_stream_ch(sof_to_bus(sdev), link_id,
0, /* cpu_dai->id -> PDI0 */
GENMASK(num_channels_tx - 1, 0),
hdac_stream(*bpt_tx_stream)->stream_tag,
SNDRV_PCM_STREAM_PLAYBACK);
if (ret < 0) {
dev_err(dev, "%s: hdac_bus_eml_sdw_map_stream_ch failed for TX: %d\n",
__func__, ret);
goto close;
}
ret = hdac_bus_eml_sdw_map_stream_ch(sof_to_bus(sdev), link_id,
1, /* cpu_dai->id -> PDI1 */
GENMASK(num_channels_rx - 1, 0),
hdac_stream(*bpt_rx_stream)->stream_tag,
SNDRV_PCM_STREAM_CAPTURE);
if (!ret)
return 0;
dev_err(dev, "%s: hdac_bus_eml_sdw_map_stream_ch failed for RX: %d\n",
__func__, ret);
close:
ret1 = hda_sdw_bpt_close(dev, link_id, *bpt_tx_stream, dmab_tx_bdl,
*bpt_rx_stream, dmab_rx_bdl);
if (ret1 < 0)
dev_err(dev, "%s: hda_sdw_bpt_close failed: %d\n",
__func__, ret1);
return ret;
}
EXPORT_SYMBOL_NS(hda_sdw_bpt_open, "SND_SOC_SOF_INTEL_HDA_SDW_BPT");
int hda_sdw_bpt_send_async(struct device *dev, struct hdac_ext_stream *bpt_tx_stream,
struct hdac_ext_stream *bpt_rx_stream)
{
int ret1;
int ret;
ret = hda_sdw_bpt_dma_enable(dev, bpt_tx_stream);
if (ret < 0) {
dev_err(dev, "%s: hda_sdw_bpt_dma_enable failed for TX: %d\n",
__func__, ret);
return ret;
}
ret = hda_sdw_bpt_dma_enable(dev, bpt_rx_stream);
if (ret < 0) {
dev_err(dev, "%s: hda_sdw_bpt_dma_enable failed for RX: %d\n",
__func__, ret);
ret1 = hda_sdw_bpt_dma_disable(dev, bpt_tx_stream);
if (ret1 < 0)
dev_err(dev, "%s: hda_sdw_bpt_dma_disable failed for TX: %d\n",
__func__, ret1);
}
return ret;
}
EXPORT_SYMBOL_NS(hda_sdw_bpt_send_async, "SND_SOC_SOF_INTEL_HDA_SDW_BPT");
/*
* 3s is several orders of magnitude larger than what is needed for a
* typical firmware download.
*/
#define HDA_BPT_IOC_TIMEOUT_MS 3000
int hda_sdw_bpt_wait(struct device *dev, struct hdac_ext_stream *bpt_tx_stream,
struct hdac_ext_stream *bpt_rx_stream)
{
struct sof_intel_hda_stream *hda_tx_stream;
struct sof_intel_hda_stream *hda_rx_stream;
snd_pcm_uframes_t tx_position;
snd_pcm_uframes_t rx_position;
unsigned long time_tx_left;
unsigned long time_rx_left;
int ret = 0;
int ret1;
int i;
hda_tx_stream = container_of(bpt_tx_stream, struct sof_intel_hda_stream, hext_stream);
hda_rx_stream = container_of(bpt_rx_stream, struct sof_intel_hda_stream, hext_stream);
time_tx_left = wait_for_completion_timeout(&hda_tx_stream->ioc,
msecs_to_jiffies(HDA_BPT_IOC_TIMEOUT_MS));
if (!time_tx_left) {
tx_position = hda_dsp_stream_get_position(hdac_stream(bpt_tx_stream),
SNDRV_PCM_STREAM_PLAYBACK, false);
dev_err(dev, "%s: SDW BPT TX DMA did not complete: %ld\n",
__func__, tx_position);
ret = -ETIMEDOUT;
goto dma_disable;
}
/* Make sure the DMA is flushed */
i = 0;
do {
tx_position = hda_dsp_stream_get_position(hdac_stream(bpt_tx_stream),
SNDRV_PCM_STREAM_PLAYBACK, false);
usleep_range(1000, 1010);
i++;
} while (tx_position && i < HDA_BPT_IOC_TIMEOUT_MS);
if (tx_position) {
dev_err(dev, "%s: SDW BPT TX DMA position %ld was not cleared\n",
__func__, tx_position);
ret = -ETIMEDOUT;
goto dma_disable;
}
/* the wait should be minimal here */
time_rx_left = wait_for_completion_timeout(&hda_rx_stream->ioc,
msecs_to_jiffies(HDA_BPT_IOC_TIMEOUT_MS));
if (!time_rx_left) {
rx_position = hda_dsp_stream_get_position(hdac_stream(bpt_rx_stream),
SNDRV_PCM_STREAM_CAPTURE, false);
dev_err(dev, "%s: SDW BPT RX DMA did not complete: %ld\n",
__func__, rx_position);
ret = -ETIMEDOUT;
goto dma_disable;
}
/* Make sure the DMA is flushed */
i = 0;
do {
rx_position = hda_dsp_stream_get_position(hdac_stream(bpt_rx_stream),
SNDRV_PCM_STREAM_CAPTURE, false);
usleep_range(1000, 1010);
i++;
} while (rx_position && i < HDA_BPT_IOC_TIMEOUT_MS);
if (rx_position) {
dev_err(dev, "%s: SDW BPT RX DMA position %ld was not cleared\n",
__func__, rx_position);
ret = -ETIMEDOUT;
goto dma_disable;
}
dma_disable:
ret1 = hda_sdw_bpt_dma_disable(dev, bpt_rx_stream);
if (!ret)
ret = ret1;
ret1 = hda_sdw_bpt_dma_disable(dev, bpt_tx_stream);
if (!ret)
ret = ret1;
return ret;
}
EXPORT_SYMBOL_NS(hda_sdw_bpt_wait, "SND_SOC_SOF_INTEL_HDA_SDW_BPT");
int hda_sdw_bpt_close(struct device *dev, int link_id, struct hdac_ext_stream *bpt_tx_stream,
struct snd_dma_buffer *dmab_tx_bdl, struct hdac_ext_stream *bpt_rx_stream,
struct snd_dma_buffer *dmab_rx_bdl)
{
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
int ret;
int ret1;
/*
* In the case of SoundWire we need to reset the PCMSyCM registers.
* Need to continue depreparing the DMA buffers even if this fails.
*/
ret = hdac_bus_eml_sdw_map_stream_ch(sof_to_bus(sdev), link_id,
0, /* PDI0 */
0, 0, SNDRV_PCM_STREAM_PLAYBACK);
if (ret < 0)
dev_err(dev, "%s: hdac_bus_eml_sdw_map_stream_ch failed %d for PDI0\n",
__func__, ret);
ret1 = hdac_bus_eml_sdw_map_stream_ch(sof_to_bus(sdev), link_id,
1, /* PDI1 */
0, 0, SNDRV_PCM_STREAM_CAPTURE);
if (ret1 < 0) {
dev_err(dev, "%s: hdac_bus_eml_sdw_map_stream_ch failed %d for PDI1\n",
__func__, ret1);
if (!ret)
ret = ret1;
}
ret1 = hda_sdw_bpt_dma_deprepare(dev, bpt_rx_stream, dmab_rx_bdl);
if (!ret)
ret = ret1;
ret1 = hda_sdw_bpt_dma_deprepare(dev, bpt_tx_stream, dmab_tx_bdl);
if (!ret)
ret = ret1;
return ret;
}
EXPORT_SYMBOL_NS(hda_sdw_bpt_close, "SND_SOC_SOF_INTEL_HDA_SDW_BPT");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("SOF helpers for HDaudio SoundWire BPT");
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_HDA_COMMON");
MODULE_IMPORT_NS("SND_SOC_SOF_HDA_MLINK");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,98 @@
// 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
//
// Authors: Liam Girdwood <liam.r.girdwood@linux.intel.com>
// Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
// Rander Wang <rander.wang@intel.com>
// Keyon Jie <yang.jie@linux.intel.com>
//
/*
* Hardware interface for generic Intel audio DSP HDA IP
*/
#include <sound/hdaudio_ext.h>
#include "../ops.h"
#include "hda.h"
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 *hext_stream = hda->dtrace_stream;
struct hdac_stream *hstream = &hext_stream->hstream;
int ret;
hstream->period_bytes = 0;/* initialize period_bytes */
hstream->bufsize = dmab->bytes;
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, struct snd_dma_buffer *dmab,
struct sof_ipc_dma_trace_params_ext *dtrace_params)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
int ret;
hda->dtrace_stream = hda_dsp_stream_get(sdev, SNDRV_PCM_STREAM_CAPTURE,
SOF_HDA_STREAM_DMI_L1_COMPATIBLE);
if (!hda->dtrace_stream) {
dev_err(sdev->dev,
"error: no available capture stream for DMA trace\n");
return -ENODEV;
}
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, 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,
dtrace_params->stream_tag);
hda->dtrace_stream = NULL;
dtrace_params->stream_tag = 0;
}
return ret;
}
EXPORT_SYMBOL_NS(hda_dsp_trace_init, "SND_SOC_SOF_INTEL_HDA_COMMON");
int hda_dsp_trace_release(struct snd_sof_dev *sdev)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
struct hdac_stream *hstream;
if (hda->dtrace_stream) {
hstream = &hda->dtrace_stream->hstream;
hda_dsp_stream_put(sdev,
SNDRV_PCM_STREAM_CAPTURE,
hstream->stream_tag);
hda->dtrace_stream = NULL;
return 0;
}
dev_dbg(sdev->dev, "DMA trace stream is not opened!\n");
return -ENODEV;
}
EXPORT_SYMBOL_NS(hda_dsp_trace_release, "SND_SOC_SOF_INTEL_HDA_COMMON");
int hda_dsp_trace_trigger(struct snd_sof_dev *sdev, int cmd)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
return hda_dsp_stream_trigger(sdev, hda->dtrace_stream, cmd);
}
EXPORT_SYMBOL_NS(hda_dsp_trace_trigger, "SND_SOC_SOF_INTEL_HDA_COMMON");
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+197
View File
@@ -0,0 +1,197 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// Copyright(c) 2020 Intel Corporation
//
// Author: Fred Oh <fred.oh@linux.intel.com>
//
/*
* Hardware interface for audio DSP on IceLake.
*/
#include <linux/kernel.h>
#include <linux/kconfig.h>
#include <linux/export.h>
#include <linux/bits.h>
#include "../ipc4-priv.h"
#include "../ops.h"
#include "hda.h"
#include "hda-ipc.h"
#include "../sof-audio.h"
#define ICL_DSP_HPRO_CORE_ID 3
static const struct snd_sof_debugfs_map icl_dsp_debugfs[] = {
{"hda", HDA_DSP_HDA_BAR, 0, 0x4000, SOF_DEBUGFS_ACCESS_ALWAYS},
{"pp", HDA_DSP_PP_BAR, 0, 0x1000, SOF_DEBUGFS_ACCESS_ALWAYS},
{"dsp", HDA_DSP_BAR, 0, 0x10000, SOF_DEBUGFS_ACCESS_ALWAYS},
};
static int icl_dsp_core_stall(struct snd_sof_dev *sdev, unsigned int core_mask)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
const struct sof_intel_dsp_desc *chip = hda->desc;
/* make sure core_mask in host managed cores */
core_mask &= chip->host_managed_cores_mask;
if (!core_mask) {
dev_err(sdev->dev, "error: core_mask is not in host managed cores\n");
return -EINVAL;
}
/* stall core */
snd_sof_dsp_update_bits_unlocked(sdev, HDA_DSP_BAR, HDA_DSP_REG_ADSPCS,
HDA_DSP_ADSPCS_CSTALL_MASK(core_mask),
HDA_DSP_ADSPCS_CSTALL_MASK(core_mask));
return 0;
}
/*
* post fw run operation for ICL.
* Core 3 will be powered up and in stall when HPRO is enabled
*/
static int icl_dsp_post_fw_run(struct snd_sof_dev *sdev)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
int ret;
if (sdev->first_boot) {
struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
ret = hda_sdw_startup(sdev);
if (ret < 0) {
dev_err(sdev->dev, "error: could not startup SoundWire links\n");
return ret;
}
/* Check if IMR boot is usable */
if (!sof_debug_check_flag(SOF_DBG_IGNORE_D3_PERSISTENT) &&
sdev->fw_ready.flags & SOF_IPC_INFO_D3_PERSISTENT)
hdev->imrboot_supported = true;
}
hda_sdw_int_enable(sdev, true);
/*
* The recommended HW programming sequence for ICL is to
* power up core 3 and keep it in stall if HPRO is enabled.
*/
if (!hda->clk_config_lpro) {
ret = hda_dsp_enable_core(sdev, BIT(ICL_DSP_HPRO_CORE_ID));
if (ret < 0) {
dev_err(sdev->dev, "error: dsp core power up failed on core %d\n",
ICL_DSP_HPRO_CORE_ID);
return ret;
}
sdev->enabled_cores_mask |= BIT(ICL_DSP_HPRO_CORE_ID);
sdev->dsp_core_ref_count[ICL_DSP_HPRO_CORE_ID]++;
snd_sof_dsp_stall(sdev, BIT(ICL_DSP_HPRO_CORE_ID));
}
/* re-enable clock gating and power gating */
return hda_dsp_ctrl_clock_power_gating(sdev, true);
}
/* Icelake ops */
struct snd_sof_dsp_ops sof_icl_ops;
int sof_icl_ops_init(struct snd_sof_dev *sdev)
{
/* common defaults */
memcpy(&sof_icl_ops, &sof_hda_common_ops, sizeof(struct snd_sof_dsp_ops));
/* probe/remove/shutdown */
sof_icl_ops.shutdown = hda_dsp_shutdown;
if (sdev->pdata->ipc_type == SOF_IPC_TYPE_3) {
/* doorbell */
sof_icl_ops.irq_thread = cnl_ipc_irq_thread;
/* ipc */
sof_icl_ops.send_msg = cnl_ipc_send_msg;
/* debug */
sof_icl_ops.ipc_dump = cnl_ipc_dump;
sof_icl_ops.set_power_state = hda_dsp_set_power_state_ipc3;
}
if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4) {
struct sof_ipc4_fw_data *ipc4_data;
sdev->private = kzalloc_obj(*ipc4_data);
if (!sdev->private)
return -ENOMEM;
ipc4_data = sdev->private;
ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET;
ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_2;
/* External library loading support */
ipc4_data->load_library = hda_dsp_ipc4_load_library;
/* doorbell */
sof_icl_ops.irq_thread = cnl_ipc4_irq_thread;
/* ipc */
sof_icl_ops.send_msg = cnl_ipc4_send_msg;
/* debug */
sof_icl_ops.ipc_dump = cnl_ipc4_dump;
sof_icl_ops.set_power_state = hda_dsp_set_power_state_ipc4;
}
/* debug */
sof_icl_ops.debug_map = icl_dsp_debugfs;
sof_icl_ops.debug_map_count = ARRAY_SIZE(icl_dsp_debugfs);
/* pre/post fw run */
sof_icl_ops.post_fw_run = icl_dsp_post_fw_run;
/* firmware run */
sof_icl_ops.run = hda_dsp_cl_boot_firmware_iccmax;
sof_icl_ops.stall = icl_dsp_core_stall;
/* dsp core get/put */
sof_icl_ops.core_get = hda_dsp_core_get;
/* set DAI driver ops */
hda_set_dai_drv_ops(sdev, &sof_icl_ops);
return 0;
};
const struct sof_intel_dsp_desc icl_chip_info = {
/* Icelake */
.cores_num = 4,
.init_core_mask = 1,
.host_managed_cores_mask = GENMASK(3, 0),
.ipc_req = CNL_DSP_REG_HIPCIDR,
.ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY,
.ipc_ack = CNL_DSP_REG_HIPCIDA,
.ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE,
.ipc_ctl = CNL_DSP_REG_HIPCCTL,
.rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS,
.rom_init_timeout = 300,
.ssp_count = ICL_SSP_COUNT,
.ssp_base_offset = CNL_SSP_BASE_OFFSET,
.sdw_shim_base = SDW_SHIM_BASE,
.sdw_alh_base = SDW_ALH_BASE,
.d0i3_offset = SOF_HDA_VS_D0I3C,
.read_sdw_lcount = hda_sdw_check_lcount_common,
.enable_sdw_irq = hda_common_enable_sdw_irq,
.check_sdw_irq = hda_common_check_sdw_irq,
.check_sdw_wakeen_irq = hda_sdw_check_wakeen_irq_common,
.sdw_process_wakeen = hda_sdw_process_wakeen_common,
.check_ipc_irq = hda_dsp_check_ipc_irq,
.cl_init = cl_dsp_init,
.power_down_dsp = hda_power_down_dsp,
.disable_interrupts = hda_dsp_disable_interrupts,
.hw_ip_version = SOF_INTEL_CAVS_2_0,
.platform = "icl",
};
+180
View File
@@ -0,0 +1,180 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// Copyright(c) 2023 Intel Corporation
/*
* Hardware interface for audio DSP on LunarLake.
*/
#include <linux/debugfs.h>
#include <linux/firmware.h>
#include <sound/hda_register.h>
#include <sound/sof/ipc4/header.h>
#include <trace/events/sof_intel.h>
#include "../ipc4-priv.h"
#include "../ops.h"
#include "hda.h"
#include "hda-ipc.h"
#include "../sof-audio.h"
#include "mtl.h"
#include "lnl.h"
#include <sound/hda-mlink.h>
/* Configure DSP offload for DMIC/SSP/UAOL */
static void hdac_bus_set_dsp_offload(struct hdac_bus *bus, bool enable)
{
hdac_bus_eml_enable_offload(bus, true, AZX_REG_ML_LEPTR_ID_INTEL_SSP, enable);
hdac_bus_eml_enable_offload(bus, true, AZX_REG_ML_LEPTR_ID_INTEL_DMIC, enable);
hdac_bus_eml_enable_offload(bus, true, AZX_REG_ML_LEPTR_ID_INTEL_UAOL, enable);
}
static int lnl_hda_dsp_probe(struct snd_sof_dev *sdev)
{
int ret;
ret = hda_dsp_probe(sdev);
if (ret < 0)
return ret;
hdac_bus_set_dsp_offload(sof_to_bus(sdev), true);
return 0;
}
static void lnl_hda_dsp_remove(struct snd_sof_dev *sdev)
{
hdac_bus_set_dsp_offload(sof_to_bus(sdev), false);
hda_dsp_remove(sdev);
}
static int lnl_hda_dsp_resume(struct snd_sof_dev *sdev)
{
int ret;
ret = hda_dsp_resume(sdev);
if (ret < 0)
return ret;
hdac_bus_set_dsp_offload(sof_to_bus(sdev), true);
return 0;
}
static int lnl_hda_dsp_runtime_resume(struct snd_sof_dev *sdev)
{
int ret;
ret = hda_dsp_runtime_resume(sdev);
if (ret < 0)
return ret;
hdac_bus_set_dsp_offload(sof_to_bus(sdev), true);
return 0;
}
static int lnl_dsp_post_fw_run(struct snd_sof_dev *sdev)
{
if (sdev->first_boot) {
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
/* Check if IMR boot is usable */
if (!sof_debug_check_flag(SOF_DBG_IGNORE_D3_PERSISTENT)) {
hda->imrboot_supported = true;
debugfs_create_bool("skip_imr_boot",
0644, sdev->debugfs_root,
&hda->skip_imr_boot);
}
}
return 0;
}
int sof_lnl_set_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *dsp_ops)
{
int ret;
ret = sof_mtl_set_ops(sdev, dsp_ops);
if (ret)
return ret;
/* probe/remove */
if (!sdev->dspless_mode_selected) {
dsp_ops->probe = lnl_hda_dsp_probe;
dsp_ops->remove = lnl_hda_dsp_remove;
}
/* post fw run */
dsp_ops->post_fw_run = lnl_dsp_post_fw_run;
/* PM */
if (!sdev->dspless_mode_selected) {
dsp_ops->resume = lnl_hda_dsp_resume;
dsp_ops->runtime_resume = lnl_hda_dsp_runtime_resume;
}
return 0;
}
EXPORT_SYMBOL_NS(sof_lnl_set_ops, "SND_SOC_SOF_INTEL_LNL");
/* Check if an SDW IRQ occurred */
bool lnl_dsp_check_sdw_irq(struct snd_sof_dev *sdev)
{
struct hdac_bus *bus = sof_to_bus(sdev);
return hdac_bus_eml_check_interrupt(bus, true, AZX_REG_ML_LEPTR_ID_SDW);
}
EXPORT_SYMBOL_NS(lnl_dsp_check_sdw_irq, "SND_SOC_SOF_INTEL_LNL");
int lnl_dsp_disable_interrupts(struct snd_sof_dev *sdev)
{
mtl_disable_ipc_interrupts(sdev);
return mtl_enable_interrupts(sdev, false);
}
EXPORT_SYMBOL_NS(lnl_dsp_disable_interrupts, "SND_SOC_SOF_INTEL_LNL");
bool lnl_sdw_check_wakeen_irq(struct snd_sof_dev *sdev)
{
struct hdac_bus *bus = sof_to_bus(sdev);
u16 wake_sts;
/*
* we need to use the global HDaudio WAKEEN/STS to be able to
* detect wakes in low-power modes. The link-specific information
* is handled in the process_wakeen() helper, this helper only
* detects a SoundWire wake without identifying the link.
*/
wake_sts = snd_hdac_chip_readw(bus, STATESTS);
/* filter out the range of SDIs that can be set for SoundWire */
return wake_sts & GENMASK(SDW_MAX_DEVICES, SDW_INTEL_DEV_NUM_IDA_MIN);
}
EXPORT_SYMBOL_NS(lnl_sdw_check_wakeen_irq, "SND_SOC_SOF_INTEL_LNL");
const struct sof_intel_dsp_desc lnl_chip_info = {
.cores_num = 5,
.init_core_mask = BIT(0),
.host_managed_cores_mask = BIT(0),
.ipc_req = MTL_DSP_REG_HFIPCXIDR,
.ipc_req_mask = MTL_DSP_REG_HFIPCXIDR_BUSY,
.ipc_ack = MTL_DSP_REG_HFIPCXIDA,
.ipc_ack_mask = MTL_DSP_REG_HFIPCXIDA_DONE,
.ipc_ctl = MTL_DSP_REG_HFIPCXCTL,
.rom_status_reg = LNL_DSP_REG_HFDSC,
.rom_init_timeout = 300,
.ssp_count = MTL_SSP_COUNT,
.d0i3_offset = MTL_HDA_VS_D0I3C,
.read_sdw_lcount = hda_sdw_check_lcount_ext,
.check_sdw_irq = lnl_dsp_check_sdw_irq,
.check_sdw_wakeen_irq = lnl_sdw_check_wakeen_irq,
.sdw_process_wakeen = hda_sdw_process_wakeen_common,
.check_ipc_irq = mtl_dsp_check_ipc_irq,
.cl_init = mtl_dsp_cl_init,
.power_down_dsp = mtl_power_down_dsp,
.disable_interrupts = lnl_dsp_disable_interrupts,
.hw_ip_version = SOF_INTEL_ACE_2_0,
.platform = "lnl",
};
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_MTL");
MODULE_IMPORT_NS("SND_SOC_SOF_HDA_MLINK");
+21
View File
@@ -0,0 +1,21 @@
/* 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) 2024 Intel Corporation
*/
#ifndef __SOF_INTEL_LNL_H
#define __SOF_INTEL_LNL_H
#define LNL_DSP_REG_HFDSC 0x160200 /* DSP core0 status */
#define LNL_DSP_REG_HFDEC 0x160204 /* DSP core0 error */
int sof_lnl_set_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *dsp_ops);
bool lnl_dsp_check_sdw_irq(struct snd_sof_dev *sdev);
int lnl_dsp_disable_interrupts(struct snd_sof_dev *sdev);
bool lnl_sdw_check_wakeen_irq(struct snd_sof_dev *sdev);
#endif /* __SOF_INTEL_LNL_H */
+816
View File
@@ -0,0 +1,816 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// Copyright(c) 2022 Intel Corporation
//
// Authors: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
//
/*
* Hardware interface for audio DSP on Meteorlake.
*/
#include <linux/debugfs.h>
#include <linux/firmware.h>
#include <linux/string_choices.h>
#include <sound/sof/ipc4/header.h>
#include <trace/events/sof_intel.h>
#include "../ipc4-priv.h"
#include "../ops.h"
#include "hda.h"
#include "hda-ipc.h"
#include "../sof-audio.h"
#include "mtl.h"
#include "telemetry.h"
static const struct snd_sof_debugfs_map mtl_dsp_debugfs[] = {
{"hda", HDA_DSP_HDA_BAR, 0, 0x4000, SOF_DEBUGFS_ACCESS_ALWAYS},
{"pp", HDA_DSP_PP_BAR, 0, 0x1000, SOF_DEBUGFS_ACCESS_ALWAYS},
{"dsp", HDA_DSP_BAR, 0, 0x10000, SOF_DEBUGFS_ACCESS_ALWAYS},
{"fw_regs", HDA_DSP_BAR, MTL_SRAM_WINDOW_OFFSET(0), 0x1000, SOF_DEBUGFS_ACCESS_D0_ONLY},
};
static void mtl_ipc_host_done(struct snd_sof_dev *sdev)
{
/*
* clear busy interrupt to tell dsp controller this interrupt has been accepted,
* not trigger it again
*/
snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXTDR,
MTL_DSP_REG_HFIPCXTDR_BUSY, MTL_DSP_REG_HFIPCXTDR_BUSY);
/*
* clear busy bit to ack dsp the msg has been processed and send reply msg to dsp
*/
snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXTDA,
MTL_DSP_REG_HFIPCXTDA_BUSY, 0);
}
static void mtl_ipc_dsp_done(struct snd_sof_dev *sdev)
{
/*
* set DONE bit - tell DSP we have received the reply msg from DSP, and processed it,
* don't send more reply to host
*/
snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXIDA,
MTL_DSP_REG_HFIPCXIDA_DONE, MTL_DSP_REG_HFIPCXIDA_DONE);
/* unmask Done interrupt */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXCTL,
MTL_DSP_REG_HFIPCXCTL_DONE, MTL_DSP_REG_HFIPCXCTL_DONE);
}
/* Check if an IPC IRQ occurred */
bool mtl_dsp_check_ipc_irq(struct snd_sof_dev *sdev)
{
u32 irq_status;
u32 hfintipptr;
if (sdev->dspless_mode_selected)
return false;
/* read Interrupt IP Pointer */
hfintipptr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFINTIPPTR) & MTL_HFINTIPPTR_PTR_MASK;
irq_status = snd_sof_dsp_read(sdev, HDA_DSP_BAR, hfintipptr + MTL_DSP_IRQSTS);
trace_sof_intel_hda_irq_ipc_check(sdev, irq_status);
if (irq_status != U32_MAX && (irq_status & MTL_DSP_IRQSTS_IPC))
return true;
return false;
}
EXPORT_SYMBOL_NS(mtl_dsp_check_ipc_irq, "SND_SOC_SOF_INTEL_MTL");
/* 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_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
struct sof_ipc4_msg *msg_data = msg->msg_data;
if (hda_ipc4_tx_is_busy(sdev)) {
hdev->delayed_ipc_tx_msg = msg;
return 0;
}
hdev->delayed_ipc_tx_msg = NULL;
/* send the message via mailbox */
if (msg_data->data_size)
sof_mailbox_write(sdev, sdev->host_box.offset, msg_data->data_ptr,
msg_data->data_size);
snd_sof_dsp_write(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXIDDY,
msg_data->extension);
snd_sof_dsp_write(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXIDR,
msg_data->primary | MTL_DSP_REG_HFIPCXIDR_BUSY);
hda_dsp_ipc4_schedule_d0i3_work(hdev, msg);
return 0;
}
void mtl_enable_ipc_interrupts(struct snd_sof_dev *sdev)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
const struct sof_intel_dsp_desc *chip = hda->desc;
if (sdev->dspless_mode_selected)
return;
/* enable IPC DONE and BUSY interrupts */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, chip->ipc_ctl,
MTL_DSP_REG_HFIPCXCTL_BUSY | MTL_DSP_REG_HFIPCXCTL_DONE,
MTL_DSP_REG_HFIPCXCTL_BUSY | MTL_DSP_REG_HFIPCXCTL_DONE);
}
void mtl_disable_ipc_interrupts(struct snd_sof_dev *sdev)
{
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
const struct sof_intel_dsp_desc *chip = hda->desc;
if (sdev->dspless_mode_selected)
return;
/* disable IPC DONE and BUSY interrupts */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, chip->ipc_ctl,
MTL_DSP_REG_HFIPCXCTL_BUSY | MTL_DSP_REG_HFIPCXCTL_DONE, 0);
}
EXPORT_SYMBOL_NS(mtl_disable_ipc_interrupts, "SND_SOC_SOF_INTEL_MTL");
static void mtl_enable_sdw_irq(struct snd_sof_dev *sdev, bool enable)
{
u32 hipcie;
u32 mask;
u32 val;
int ret;
if (sdev->dspless_mode_selected)
return;
/* Enable/Disable SoundWire interrupt */
mask = MTL_DSP_REG_HfSNDWIE_IE_MASK;
if (enable)
val = mask;
else
val = 0;
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfSNDWIE, mask, val);
/* check if operation was successful */
ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfSNDWIE, hipcie,
(hipcie & mask) == val,
HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_RESET_TIMEOUT_US);
if (ret < 0)
dev_err(sdev->dev, "failed to set SoundWire IPC interrupt %s\n",
str_enable_disable(enable));
}
int mtl_enable_interrupts(struct snd_sof_dev *sdev, bool enable)
{
u32 hfintipptr;
u32 irqinten;
u32 hipcie;
u32 mask;
u32 val;
int ret;
if (sdev->dspless_mode_selected)
return 0;
/* read Interrupt IP Pointer */
hfintipptr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFINTIPPTR) & MTL_HFINTIPPTR_PTR_MASK;
/* Enable/Disable Host IPC and SOUNDWIRE */
mask = MTL_IRQ_INTEN_L_HOST_IPC_MASK | MTL_IRQ_INTEN_L_SOUNDWIRE_MASK;
if (enable)
val = mask;
else
val = 0;
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, hfintipptr, mask, val);
/* check if operation was successful */
ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, hfintipptr, irqinten,
(irqinten & mask) == val,
HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_RESET_TIMEOUT_US);
if (ret < 0) {
dev_err(sdev->dev, "failed to %s Host IPC and/or SOUNDWIRE\n",
str_enable_disable(enable));
return ret;
}
/* Enable/Disable Host IPC interrupt*/
mask = MTL_DSP_REG_HfHIPCIE_IE_MASK;
if (enable)
val = mask;
else
val = 0;
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfHIPCIE, mask, val);
/* check if operation was successful */
ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_DSP_REG_HfHIPCIE, hipcie,
(hipcie & mask) == val,
HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_RESET_TIMEOUT_US);
if (ret < 0) {
dev_err(sdev->dev, "failed to set Host IPC interrupt %s\n",
str_enable_disable(enable));
return ret;
}
return ret;
}
EXPORT_SYMBOL_NS(mtl_enable_interrupts, "SND_SOC_SOF_INTEL_MTL");
/* pre fw run operations */
static int mtl_dsp_pre_fw_run(struct snd_sof_dev *sdev)
{
struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
u32 dsphfpwrsts;
u32 dsphfdsscs;
u32 cpa;
u32 pgs;
int ret;
u32 dsppwrctl;
u32 dsppwrsts;
const struct sof_intel_dsp_desc *chip;
chip = get_chip_info(sdev->pdata);
if (chip->hw_ip_version > SOF_INTEL_ACE_2_0) {
dsppwrctl = PTL_HFPWRCTL2;
dsppwrsts = PTL_HFPWRSTS2;
} else {
dsppwrctl = MTL_HFPWRCTL;
dsppwrsts = MTL_HFPWRSTS;
}
/* 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, dsppwrctl,
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, dsppwrsts, dsphfpwrsts,
(dsphfpwrsts & pgs) == pgs,
HDA_DSP_REG_POLL_INTERVAL_US,
HDA_DSP_RESET_TIMEOUT_US);
if (ret < 0)
dev_err(sdev->dev, "failed to power up gated DSP domain\n");
/* if SoundWire is used, make sure it is not power-gated */
if (hdev->info.handle && hdev->info.link_mask > 0)
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_HFPWRCTL,
MTL_HfPWRCTL_WPIOXPG(1), MTL_HfPWRCTL_WPIOXPG(1));
return ret;
}
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;
debugfs_create_bool("skip_imr_boot",
0644, sdev->debugfs_root,
&hdev->skip_imr_boot);
}
}
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 fwsts;
u32 fwlec;
hda_dsp_get_state(sdev, level);
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);
if (fwsts != 0xffffffff)
dev_err(sdev->dev, "Firmware state: %#x, status/error code: %#x\n",
fwsts, fwlec);
sof_ipc4_intel_dump_telemetry_state(sdev, flags);
}
static bool mtl_dsp_primary_core_is_enabled(struct snd_sof_dev *sdev)
{
int val;
val = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP2CXCTL_PRIMARY_CORE);
if (val != U32_MAX && val & MTL_DSP2CXCTL_PRIMARY_CORE_CPA_MASK)
return true;
return false;
}
static int mtl_dsp_core_power_up(struct snd_sof_dev *sdev, int core)
{
unsigned int cpa;
u32 dspcxctl;
int ret;
/* Only the primary core can be powered up by the host */
if (core != SOF_DSP_PRIMARY_CORE || mtl_dsp_primary_core_is_enabled(sdev))
return 0;
/* Program the owner of the IP & shim registers (10: Host CPU) */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP2CXCTL_PRIMARY_CORE,
MTL_DSP2CXCTL_PRIMARY_CORE_OSEL,
0x2 << MTL_DSP2CXCTL_PRIMARY_CORE_OSEL_SHIFT);
/* enable SPA bit */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP2CXCTL_PRIMARY_CORE,
MTL_DSP2CXCTL_PRIMARY_CORE_SPA_MASK,
MTL_DSP2CXCTL_PRIMARY_CORE_SPA_MASK);
/* Wait for unstable CPA read (1 then 0 then 1) just after setting SPA bit */
usleep_range(1000, 1010);
/* poll with timeout to check if operation successful */
cpa = MTL_DSP2CXCTL_PRIMARY_CORE_CPA_MASK;
ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_DSP2CXCTL_PRIMARY_CORE, dspcxctl,
(dspcxctl & cpa) == cpa, HDA_DSP_REG_POLL_INTERVAL_US,
HDA_DSP_RESET_TIMEOUT_US);
if (ret < 0) {
dev_err(sdev->dev, "%s: timeout on MTL_DSP2CXCTL_PRIMARY_CORE read\n",
__func__);
return ret;
}
/* set primary core mask and refcount to 1 */
sdev->enabled_cores_mask = BIT(SOF_DSP_PRIMARY_CORE);
sdev->dsp_core_ref_count[SOF_DSP_PRIMARY_CORE] = 1;
return 0;
}
static int mtl_dsp_core_power_down(struct snd_sof_dev *sdev, int core)
{
u32 dspcxctl;
int ret;
/* Only the primary core can be powered down by the host */
if (core != SOF_DSP_PRIMARY_CORE || !mtl_dsp_primary_core_is_enabled(sdev))
return 0;
/* disable SPA bit */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP2CXCTL_PRIMARY_CORE,
MTL_DSP2CXCTL_PRIMARY_CORE_SPA_MASK, 0);
/* Wait for unstable CPA read (0 then 1 then 0) just after setting SPA bit */
usleep_range(1000, 1010);
ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_DSP2CXCTL_PRIMARY_CORE, dspcxctl,
!(dspcxctl & MTL_DSP2CXCTL_PRIMARY_CORE_CPA_MASK),
HDA_DSP_REG_POLL_INTERVAL_US,
HDA_DSP_PD_TIMEOUT * USEC_PER_MSEC);
if (ret < 0) {
dev_err(sdev->dev, "failed to power down primary core\n");
return ret;
}
sdev->enabled_cores_mask = 0;
sdev->dsp_core_ref_count[SOF_DSP_PRIMARY_CORE] = 0;
return 0;
}
int mtl_power_down_dsp(struct snd_sof_dev *sdev)
{
u32 dsphfdsscs, cpa;
int ret;
/* first power down core */
ret = mtl_dsp_core_power_down(sdev, SOF_DSP_PRIMARY_CORE);
if (ret) {
dev_err(sdev->dev, "mtl dsp power down error, %d\n", ret);
return ret;
}
/* Set the DSP subsystem power down */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_HFDSSCS,
MTL_HFDSSCS_SPA_MASK, 0);
/* Wait for unstable CPA read (0 then 1 then 0) just after setting SPA bit */
usleep_range(1000, 1010);
/* poll with timeout to check if operation successful */
cpa = MTL_HFDSSCS_CPA_MASK;
dsphfdsscs = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_HFDSSCS);
return snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_HFDSSCS, dsphfdsscs,
(dsphfdsscs & cpa) == 0, HDA_DSP_REG_POLL_INTERVAL_US,
HDA_DSP_RESET_TIMEOUT_US);
}
EXPORT_SYMBOL_NS(mtl_power_down_dsp, "SND_SOC_SOF_INTEL_MTL");
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, target_status;
u32 ipc_hdr, flags;
char *dump_msg;
int ret;
/* step 1: purge FW request */
ipc_hdr = chip->ipc_req_mask | HDA_DSP_ROM_IPC_CONTROL;
if (!imr_boot)
ipc_hdr |= HDA_DSP_ROM_IPC_PURGE_FW | ((stream_tag - 1) << 9);
snd_sof_dsp_write(sdev, HDA_DSP_BAR, chip->ipc_req, ipc_hdr);
/* step 2: power up primary core */
ret = mtl_dsp_core_power_up(sdev, SOF_DSP_PRIMARY_CORE);
if (ret < 0) {
if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS)
dev_err(sdev->dev, "dsp core 0/1 power up failed\n");
goto err;
}
dev_dbg(sdev->dev, "Primary core power up successful\n");
/* step 3: wait for IPC DONE bit from ROM */
ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, chip->ipc_ack, status,
((status & chip->ipc_ack_mask) == chip->ipc_ack_mask),
HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_INIT_TIMEOUT_US);
if (ret < 0) {
if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS)
dev_err(sdev->dev, "timeout waiting for purge IPC done\n");
goto err;
}
/* set DONE bit to clear the reply IPC message */
snd_sof_dsp_update_bits_forced(sdev, HDA_DSP_BAR, chip->ipc_ack, chip->ipc_ack_mask,
chip->ipc_ack_mask);
/* step 4: enable interrupts */
ret = mtl_enable_interrupts(sdev, true);
if (ret < 0) {
if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS)
dev_err(sdev->dev, "%s: failed to enable interrupts\n", __func__);
goto err;
}
mtl_enable_ipc_interrupts(sdev);
if (chip->rom_status_reg == MTL_DSP_ROM_STS) {
/*
* Workaround: when the ROM status register is pointing to
* the SRAM window (MTL_DSP_ROM_STS) the platform cannot catch
* ROM_INIT_DONE because of a very short timing window.
* Follow the recommendations and skip target state waiting.
*/
return 0;
}
/*
* 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,
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)
return 0;
if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS)
dev_err(sdev->dev,
"%s: timeout with rom_status_reg (%#x) read\n",
__func__, chip->rom_status_reg);
err:
flags = SOF_DBG_DUMP_PCI | SOF_DBG_DUMP_MBOX | SOF_DBG_DUMP_OPTIONAL;
/* after max boot attempts make sure that the dump is printed */
if (hda->boot_iteration == HDA_FW_BOOT_ATTEMPTS)
flags &= ~SOF_DBG_DUMP_OPTIONAL;
dump_msg = kasprintf(GFP_KERNEL, "Boot iteration failed: %d/%d",
hda->boot_iteration, HDA_FW_BOOT_ATTEMPTS);
snd_sof_dsp_dbg_dump(sdev, dump_msg, flags);
mtl_enable_interrupts(sdev, false);
mtl_dsp_core_power_down(sdev, SOF_DSP_PRIMARY_CORE);
kfree(dump_msg);
return ret;
}
EXPORT_SYMBOL_NS(mtl_dsp_cl_init, "SND_SOC_SOF_INTEL_MTL");
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 ack_received = false;
bool ipc_irq = false;
u32 hipcida;
u32 hipctdr;
hipcida = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXIDA);
hipctdr = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXTDR);
/* reply message from DSP */
if (hipcida & MTL_DSP_REG_HFIPCXIDA_DONE) {
/* DSP received the message */
snd_sof_dsp_update_bits(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXCTL,
MTL_DSP_REG_HFIPCXCTL_DONE, 0);
mtl_ipc_dsp_done(sdev);
ipc_irq = true;
ack_received = true;
}
if (hipctdr & MTL_DSP_REG_HFIPCXTDR_BUSY) {
/* Message from DSP (reply or notification) */
u32 extension = snd_sof_dsp_read(sdev, HDA_DSP_BAR, MTL_DSP_REG_HFIPCXTDDY);
u32 primary = hipctdr & MTL_DSP_REG_HFIPCXTDR_MSG_MASK;
/*
* ACE fw sends a new fw ipc message to host to
* notify the status of the last host ipc message
*/
if (primary & SOF_IPC4_MSG_DIR_MASK) {
/* Reply received */
if (likely(sdev->fw_state == SOF_FW_BOOT_COMPLETE)) {
struct sof_ipc4_msg *data = sdev->ipc->msg.reply_data;
data->primary = primary;
data->extension = extension;
guard(spinlock_irq)(&sdev->ipc_lock);
snd_sof_ipc_get_reply(sdev);
mtl_ipc_host_done(sdev);
snd_sof_ipc_reply(sdev, data->primary);
} else {
dev_dbg_ratelimited(sdev->dev,
"IPC reply before FW_READY: %#x|%#x\n",
primary, extension);
}
} else {
/* Notification received */
notification_data.primary = primary;
notification_data.extension = extension;
sdev->ipc->msg.rx_data = &notification_data;
snd_sof_ipc_msgs_rx(sdev);
sdev->ipc->msg.rx_data = NULL;
mtl_ipc_host_done(sdev);
}
ipc_irq = true;
}
if (!ipc_irq) {
/* This interrupt is not shared so no need to return IRQ_NONE. */
dev_dbg_ratelimited(sdev->dev, "nothing to do in IPC IRQ thread\n");
}
if (ack_received) {
struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
if (hdev->delayed_ipc_tx_msg)
mtl_ipc_send_msg(sdev, hdev->delayed_ipc_tx_msg);
}
return IRQ_HANDLED;
}
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_enable_sdw_irq(sdev, false);
mtl_disable_ipc_interrupts(sdev);
return mtl_enable_interrupts(sdev, false);
}
static int mtl_dsp_core_get(struct snd_sof_dev *sdev, int core)
{
const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm;
if (core == SOF_DSP_PRIMARY_CORE)
return mtl_dsp_core_power_up(sdev, SOF_DSP_PRIMARY_CORE);
if (pm_ops->set_core_state)
return pm_ops->set_core_state(sdev, core, true);
return 0;
}
static int mtl_dsp_core_put(struct snd_sof_dev *sdev, int core)
{
const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm;
int ret;
if (pm_ops->set_core_state) {
ret = pm_ops->set_core_state(sdev, core, false);
if (ret < 0)
return ret;
}
if (core == SOF_DSP_PRIMARY_CORE)
return mtl_dsp_core_power_down(sdev, SOF_DSP_PRIMARY_CORE);
return 0;
}
int sof_mtl_set_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *dsp_ops)
{
struct sof_ipc4_fw_data *ipc4_data;
/* common defaults */
memcpy(dsp_ops, &sof_hda_common_ops, sizeof(struct snd_sof_dsp_ops));
/* shutdown */
dsp_ops->shutdown = hda_dsp_shutdown;
/* doorbell */
dsp_ops->irq_thread = mtl_ipc_irq_thread;
/* ipc */
dsp_ops->send_msg = mtl_ipc_send_msg;
dsp_ops->get_mailbox_offset = mtl_dsp_ipc_get_mailbox_offset;
dsp_ops->get_window_offset = mtl_dsp_ipc_get_window_offset;
/* debug */
dsp_ops->debug_map = mtl_dsp_debugfs;
dsp_ops->debug_map_count = ARRAY_SIZE(mtl_dsp_debugfs);
dsp_ops->dbg_dump = mtl_dsp_dump;
dsp_ops->ipc_dump = mtl_ipc_dump;
/* pre/post fw run */
dsp_ops->pre_fw_run = mtl_dsp_pre_fw_run;
dsp_ops->post_fw_run = mtl_dsp_post_fw_run;
/* parse platform specific extended manifest */
dsp_ops->parse_platform_ext_manifest = NULL;
/* dsp core get/put */
dsp_ops->core_get = mtl_dsp_core_get;
dsp_ops->core_put = mtl_dsp_core_put;
sdev->private = kzalloc_obj(struct sof_ipc4_fw_data);
if (!sdev->private)
return -ENOMEM;
ipc4_data = sdev->private;
ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET;
ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_2;
ipc4_data->fw_context_save = true;
/* External library loading support */
ipc4_data->load_library = hda_dsp_ipc4_load_library;
dsp_ops->set_power_state = hda_dsp_set_power_state_ipc4;
/* set DAI ops */
hda_set_dai_drv_ops(sdev, dsp_ops);
return 0;
}
EXPORT_SYMBOL_NS(sof_mtl_set_ops, "SND_SOC_SOF_INTEL_MTL");
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_REG_HFFLGPXQWY,
.rom_init_timeout = 300,
.ssp_count = MTL_SSP_COUNT,
.ssp_base_offset = CNL_SSP_BASE_OFFSET,
.sdw_shim_base = SDW_SHIM_BASE_ACE,
.sdw_alh_base = SDW_ALH_BASE_ACE,
.d0i3_offset = MTL_HDA_VS_D0I3C,
.read_sdw_lcount = hda_sdw_check_lcount_common,
.enable_sdw_irq = mtl_enable_sdw_irq,
.check_sdw_irq = mtl_dsp_check_sdw_irq,
.check_sdw_wakeen_irq = hda_sdw_check_wakeen_irq_common,
.sdw_process_wakeen = hda_sdw_process_wakeen_common,
.check_ipc_irq = mtl_dsp_check_ipc_irq,
.cl_init = mtl_dsp_cl_init,
.power_down_dsp = mtl_power_down_dsp,
.disable_interrupts = mtl_dsp_disable_interrupts,
.hw_ip_version = SOF_INTEL_ACE_1_0,
.platform = "mtl",
};
const struct sof_intel_dsp_desc arl_s_chip_info = {
.cores_num = 2,
.init_core_mask = BIT(0),
.host_managed_cores_mask = BIT(0),
.ipc_req = MTL_DSP_REG_HFIPCXIDR,
.ipc_req_mask = MTL_DSP_REG_HFIPCXIDR_BUSY,
.ipc_ack = MTL_DSP_REG_HFIPCXIDA,
.ipc_ack_mask = MTL_DSP_REG_HFIPCXIDA_DONE,
.ipc_ctl = MTL_DSP_REG_HFIPCXCTL,
.rom_status_reg = MTL_DSP_REG_HFFLGPXQWY,
.rom_init_timeout = 300,
.ssp_count = MTL_SSP_COUNT,
.ssp_base_offset = CNL_SSP_BASE_OFFSET,
.sdw_shim_base = SDW_SHIM_BASE_ACE,
.sdw_alh_base = SDW_ALH_BASE_ACE,
.d0i3_offset = MTL_HDA_VS_D0I3C,
.read_sdw_lcount = hda_sdw_check_lcount_common,
.enable_sdw_irq = mtl_enable_sdw_irq,
.check_sdw_irq = mtl_dsp_check_sdw_irq,
.check_sdw_wakeen_irq = hda_sdw_check_wakeen_irq_common,
.sdw_process_wakeen = hda_sdw_process_wakeen_common,
.check_ipc_irq = mtl_dsp_check_ipc_irq,
.cl_init = mtl_dsp_cl_init,
.power_down_dsp = mtl_power_down_dsp,
.disable_interrupts = mtl_dsp_disable_interrupts,
.hw_ip_version = SOF_INTEL_ACE_1_0,
.platform = "arl",
};
+134
View File
@@ -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) 2020-2022 Intel Corporation
*/
/* 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 PTL_HFPWRCTL2 0x1D20
#define MTL_HfPWRCTL_WPIOXPG(x) BIT((x) + 8)
#define MTL_HFPWRCTL_WPDSPHPXPG BIT(0)
#define MTL_HFPWRSTS 0x1D1C
#define PTL_HFPWRSTS2 0x1D24
#define MTL_HFPWRSTS_DSPHPXPGS_MASK BIT(0)
#define MTL_HFINTIPPTR 0x1108
#define MTL_IRQ_INTEN_L_HOST_IPC_MASK BIT(0)
#define MTL_IRQ_INTEN_L_SOUNDWIRE_MASK BIT(6)
#define MTL_HFINTIPPTR_PTR_MASK GENMASK(20, 0)
#define MTL_HDA_VS_D0I3C 0x1D4A
#define MTL_DSP2CXCAP_PRIMARY_CORE 0x178D00
#define MTL_DSP2CXCTL_PRIMARY_CORE 0x178D04
#define MTL_DSP2CXCTL_PRIMARY_CORE_SPA_MASK BIT(0)
#define MTL_DSP2CXCTL_PRIMARY_CORE_CPA_MASK BIT(8)
#define MTL_DSP2CXCTL_PRIMARY_CORE_OSEL GENMASK(25, 24)
#define MTL_DSP2CXCTL_PRIMARY_CORE_OSEL_SHIFT 24
/* IPC Registers */
#define MTL_DSP_REG_HFIPCXTDR 0x73200
#define MTL_DSP_REG_HFIPCXTDR_BUSY BIT(31)
#define MTL_DSP_REG_HFIPCXTDR_MSG_MASK GENMASK(30, 0)
#define MTL_DSP_REG_HFIPCXTDA 0x73204
#define MTL_DSP_REG_HFIPCXTDA_BUSY BIT(31)
#define MTL_DSP_REG_HFIPCXIDR 0x73210
#define MTL_DSP_REG_HFIPCXIDR_BUSY BIT(31)
#define MTL_DSP_REG_HFIPCXIDR_MSG_MASK GENMASK(30, 0)
#define MTL_DSP_REG_HFIPCXIDA 0x73214
#define MTL_DSP_REG_HFIPCXIDA_DONE BIT(31)
#define MTL_DSP_REG_HFIPCXIDA_MSG_MASK GENMASK(30, 0)
#define MTL_DSP_REG_HFIPCXCTL 0x73228
#define MTL_DSP_REG_HFIPCXCTL_BUSY BIT(0)
#define MTL_DSP_REG_HFIPCXCTL_DONE BIT(1)
#define MTL_DSP_REG_HFIPCXTDDY 0x73300
#define MTL_DSP_REG_HFIPCXIDDY 0x73380
#define MTL_DSP_REG_HfHIPCIE 0x1140
#define MTL_DSP_REG_HfHIPCIE_IE_MASK BIT(0)
#define MTL_DSP_REG_HfSNDWIE 0x114C
#define MTL_DSP_REG_HfSNDWIE_IE_MASK GENMASK(3, 0)
#define MTL_DSP_IRQSTS 0x20
#define MTL_DSP_IRQSTS_IPC BIT(0)
#define MTL_DSP_IRQSTS_SDW BIT(6)
#define MTL_DSP_REG_POLL_INTERVAL_US 10 /* 10 us */
/* Memory windows */
#define MTL_SRAM_WINDOW_OFFSET(x) (0x180000 + 0x8000 * (x))
#define MTL_DSP_MBOX_UPLINK_OFFSET (MTL_SRAM_WINDOW_OFFSET(0) + 0x1000)
#define MTL_DSP_MBOX_UPLINK_SIZE 0x1000
#define MTL_DSP_MBOX_DOWNLINK_OFFSET MTL_SRAM_WINDOW_OFFSET(1)
#define MTL_DSP_MBOX_DOWNLINK_SIZE 0x1000
/* FW registers */
#define MTL_DSP_ROM_STS MTL_SRAM_WINDOW_OFFSET(0) /* ROM status */
#define MTL_DSP_ROM_ERROR (MTL_SRAM_WINDOW_OFFSET(0) + 0x4) /* ROM error code */
#define MTL_DSP_REG_HFFLGPXQWY 0x163200 /* DSP core0 status */
#define MTL_DSP_REG_HFFLGPXQWY_ERROR 0x163204 /* DSP core0 error */
/* FSR status codes */
#define FSR_STATE_ROM_RESET_VECTOR_DONE 0x8
#define FSR_STATE_ROM_PURGE_BOOT 0x9
#define FSR_STATE_ROM_RESTORE_BOOT 0xA
#define FSR_STATE_ROM_FW_ENTRY_POINT 0xB
#define FSR_STATE_ROM_VALIDATE_PUB_KEY 0xC
#define FSR_STATE_ROM_POWER_DOWN_HPSRAM 0xD
#define FSR_STATE_ROM_POWER_DOWN_ULPSRAM 0xE
#define FSR_STATE_ROM_POWER_UP_ULPSRAM_STACK 0xF
#define FSR_STATE_ROM_POWER_UP_HPSRAM_DMA 0x10
#define FSR_STATE_ROM_BEFORE_EP_POINTER_READ 0x11
#define FSR_STATE_ROM_VALIDATE_MANIFEST 0x12
#define FSR_STATE_ROM_VALIDATE_FW_MODULE 0x13
#define FSR_STATE_ROM_PROTECT_IMR_REGION 0x14
#define FSR_STATE_ROM_PUSH_MODEL_ROUTINE 0x15
#define FSR_STATE_ROM_PULL_MODEL_ROUTINE 0x16
#define FSR_STATE_ROM_VALIDATE_PKG_DIR 0x17
#define FSR_STATE_ROM_VALIDATE_CPD 0x18
#define FSR_STATE_ROM_VALIDATE_CSS_MAN_HEADER 0x19
#define FSR_STATE_ROM_VALIDATE_BLOB_SVN 0x1A
#define FSR_STATE_ROM_VERIFY_IFWI_PARTITION 0x1B
#define FSR_STATE_ROM_REMOVE_ACCESS_CONTROL 0x1C
#define FSR_STATE_ROM_AUTH_BYPASS 0x1D
#define FSR_STATE_ROM_AUTH_ENABLED 0x1E
#define FSR_STATE_ROM_INIT_DMA 0x1F
#define FSR_STATE_ROM_PURGE_FW_ENTRY 0x20
#define FSR_STATE_ROM_PURGE_FW_END 0x21
#define FSR_STATE_ROM_CLEAN_UP_BSS_DONE 0x22
#define FSR_STATE_ROM_IMR_RESTORE_ENTRY 0x23
#define FSR_STATE_ROM_IMR_RESTORE_END 0x24
#define FSR_STATE_ROM_FW_MANIFEST_IN_DMA_BUFF 0x25
#define FSR_STATE_ROM_LOAD_CSE_MAN_TO_IMR 0x26
#define FSR_STATE_ROM_LOAD_FW_MAN_TO_IMR 0x27
#define FSR_STATE_ROM_LOAD_FW_CODE_TO_IMR 0x28
#define FSR_STATE_ROM_FW_LOADING_DONE 0x29
#define FSR_STATE_ROM_FW_CODE_LOADED 0x2A
#define FSR_STATE_ROM_VERIFY_IMAGE_TYPE 0x2B
#define FSR_STATE_ROM_AUTH_API_INIT 0x2C
#define FSR_STATE_ROM_AUTH_API_PROC 0x2D
#define FSR_STATE_ROM_AUTH_API_FIRST_BUSY 0x2E
#define FSR_STATE_ROM_AUTH_API_FIRST_RESULT 0x2F
#define FSR_STATE_ROM_AUTH_API_CLEANUP 0x30
#define MTL_DSP_REG_HfIMRIS1 0x162088
#define MTL_DSP_REG_HfIMRIS1_IU_MASK BIT(0)
bool mtl_dsp_check_ipc_irq(struct snd_sof_dev *sdev);
void mtl_enable_ipc_interrupts(struct snd_sof_dev *sdev);
void mtl_disable_ipc_interrupts(struct snd_sof_dev *sdev);
int mtl_enable_interrupts(struct snd_sof_dev *sdev, bool enable);
int mtl_power_down_dsp(struct snd_sof_dev *sdev);
int mtl_dsp_cl_init(struct snd_sof_dev *sdev, int stream_tag, bool imr_boot);
int sof_mtl_set_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *dsp_ops);
+81
View File
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// Copyright(c) 2025 Intel Corporation
/*
* Hardware interface for audio DSP on NovaLake.
*/
#include <sound/hda_register.h>
#include <sound/hda-mlink.h>
#include <sound/sof/ipc4/header.h>
#include "../ipc4-priv.h"
#include "../ops.h"
#include "hda.h"
#include "hda-ipc.h"
#include "../sof-audio.h"
#include "mtl.h"
#include "lnl.h"
#include "ptl.h"
#include "nvl.h"
int sof_nvl_set_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *dsp_ops)
{
/* Use PTL ops for NVL */
return sof_ptl_set_ops(sdev, dsp_ops);
};
EXPORT_SYMBOL_NS(sof_nvl_set_ops, "SND_SOC_SOF_INTEL_NVL");
const struct sof_intel_dsp_desc nvl_chip_info = {
.cores_num = 4,
.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 = LNL_DSP_REG_HFDSC,
.rom_init_timeout = 300,
.ssp_count = MTL_SSP_COUNT,
.d0i3_offset = MTL_HDA_VS_D0I3C,
.read_sdw_lcount = hda_sdw_check_lcount_ext,
.check_sdw_irq = lnl_dsp_check_sdw_irq,
.check_sdw_wakeen_irq = lnl_sdw_check_wakeen_irq,
.sdw_process_wakeen = hda_sdw_process_wakeen_common,
.check_ipc_irq = mtl_dsp_check_ipc_irq,
.cl_init = mtl_dsp_cl_init,
.power_down_dsp = mtl_power_down_dsp,
.disable_interrupts = lnl_dsp_disable_interrupts,
.hw_ip_version = SOF_INTEL_ACE_4_0,
.platform = "nvl",
};
const struct sof_intel_dsp_desc nvl_s_chip_info = {
.cores_num = 2,
.init_core_mask = BIT(0),
.host_managed_cores_mask = BIT(0),
.ipc_req = MTL_DSP_REG_HFIPCXIDR,
.ipc_req_mask = MTL_DSP_REG_HFIPCXIDR_BUSY,
.ipc_ack = MTL_DSP_REG_HFIPCXIDA,
.ipc_ack_mask = MTL_DSP_REG_HFIPCXIDA_DONE,
.ipc_ctl = MTL_DSP_REG_HFIPCXCTL,
.rom_status_reg = LNL_DSP_REG_HFDSC,
.rom_init_timeout = 300,
.ssp_count = MTL_SSP_COUNT,
.d0i3_offset = MTL_HDA_VS_D0I3C,
.read_sdw_lcount = hda_sdw_check_lcount_ext,
.check_sdw_irq = lnl_dsp_check_sdw_irq,
.check_sdw_wakeen_irq = lnl_sdw_check_wakeen_irq,
.sdw_process_wakeen = hda_sdw_process_wakeen_common,
.check_ipc_irq = mtl_dsp_check_ipc_irq,
.cl_init = mtl_dsp_cl_init,
.power_down_dsp = mtl_power_down_dsp,
.disable_interrupts = lnl_dsp_disable_interrupts,
.hw_ip_version = SOF_INTEL_ACE_4_0,
.platform = "nvl",
};
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_MTL");
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_LNL");
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_PTL");
+14
View File
@@ -0,0 +1,14 @@
/* 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) 2025 Intel Corporation
*/
#ifndef __SOF_INTEL_NVL_H
#define __SOF_INTEL_NVL_H
int sof_nvl_set_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *dsp_ops);
#endif /* __SOF_INTEL_NVL_H */
+111
View File
@@ -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) 2018-2021 Intel Corporation
//
// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
//
#include <linux/module.h>
#include <linux/pci.h>
#include <sound/soc-acpi.h>
#include <sound/soc-acpi-intel-match.h>
#include <sound/sof.h>
#include "../ops.h"
#include "../sof-pci-dev.h"
/* platform specific devices */
#include "hda.h"
static const struct sof_dev_desc bxt_desc = {
.machines = snd_soc_acpi_intel_bxt_machines,
.use_acpi_target_states = true,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
.irqindex_host_ipc = -1,
.chip_info = &apl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
.ipc_default = SOF_IPC_TYPE_3,
.dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC_TYPE_3] = "intel/sof",
[SOF_IPC_TYPE_4] = "intel/avs/apl",
},
.default_lib_path = {
[SOF_IPC_TYPE_4] = "intel/avs-lib/apl",
},
.default_tplg_path = {
[SOF_IPC_TYPE_3] = "intel/sof-tplg",
[SOF_IPC_TYPE_4] = "intel/avs-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_3] = "sof-apl.ri",
[SOF_IPC_TYPE_4] = "dsp_basefw.bin",
},
.nocodec_tplg_filename = "sof-apl-nocodec.tplg",
.ops = &sof_apl_ops,
.ops_init = sof_apl_ops_init,
.ops_free = hda_ops_free,
};
static const struct sof_dev_desc glk_desc = {
.machines = snd_soc_acpi_intel_glk_machines,
.use_acpi_target_states = true,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
.irqindex_host_ipc = -1,
.chip_info = &apl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
.ipc_default = SOF_IPC_TYPE_3,
.dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC_TYPE_3] = "intel/sof",
[SOF_IPC_TYPE_4] = "intel/avs/glk",
},
.default_lib_path = {
[SOF_IPC_TYPE_4] = "intel/avs-lib/glk",
},
.default_tplg_path = {
[SOF_IPC_TYPE_3] = "intel/sof-tplg",
[SOF_IPC_TYPE_4] = "intel/avs-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_3] = "sof-glk.ri",
[SOF_IPC_TYPE_4] = "dsp_basefw.bin",
},
.nocodec_tplg_filename = "sof-glk-nocodec.tplg",
.ops = &sof_apl_ops,
.ops_init = sof_apl_ops_init,
.ops_free = hda_ops_free,
};
/* PCI IDs */
static const struct pci_device_id sof_pci_ids[] = {
{ PCI_DEVICE_DATA(INTEL, HDA_APL, &bxt_desc) },
{ PCI_DEVICE_DATA(INTEL, HDA_GLK, &glk_desc) },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, sof_pci_ids);
/* pci_driver definition */
static struct pci_driver snd_sof_pci_intel_apl_driver = {
.name = "sof-audio-pci-intel-apl",
.id_table = sof_pci_ids,
.probe = hda_pci_intel_probe,
.remove = sof_pci_remove,
.shutdown = sof_pci_shutdown,
.driver = {
.pm = pm_ptr(&sof_pci_pm),
},
};
module_pci_driver(snd_sof_pci_intel_apl_driver);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("SOF support for ApolloLake platforms");
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_HDA_GENERIC");
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_HDA_COMMON");
MODULE_IMPORT_NS("SND_SOC_SOF_PCI_DEV");
+149
View File
@@ -0,0 +1,149 @@
// 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
//
// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
//
#include <linux/module.h>
#include <linux/pci.h>
#include <sound/soc-acpi.h>
#include <sound/soc-acpi-intel-match.h>
#include <sound/sof.h>
#include "../ops.h"
#include "../sof-pci-dev.h"
/* platform specific devices */
#include "hda.h"
static const struct sof_dev_desc cnl_desc = {
.machines = snd_soc_acpi_intel_cnl_machines,
.alt_machines = snd_soc_acpi_intel_cnl_sdw_machines,
.use_acpi_target_states = true,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
.irqindex_host_ipc = -1,
.chip_info = &cnl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
.ipc_default = SOF_IPC_TYPE_3,
.dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC_TYPE_3] = "intel/sof",
[SOF_IPC_TYPE_4] = "intel/avs/cnl",
},
.default_lib_path = {
[SOF_IPC_TYPE_4] = "intel/avs-lib/cnl",
},
.default_tplg_path = {
[SOF_IPC_TYPE_3] = "intel/sof-tplg",
[SOF_IPC_TYPE_4] = "intel/avs-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_3] = "sof-cnl.ri",
[SOF_IPC_TYPE_4] = "dsp_basefw.bin",
},
.nocodec_tplg_filename = "sof-cnl-nocodec.tplg",
.ops = &sof_cnl_ops,
.ops_init = sof_cnl_ops_init,
.ops_free = hda_ops_free,
};
static const struct sof_dev_desc cfl_desc = {
.machines = snd_soc_acpi_intel_cfl_machines,
.alt_machines = snd_soc_acpi_intel_cfl_sdw_machines,
.use_acpi_target_states = true,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
.irqindex_host_ipc = -1,
.chip_info = &cnl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
.ipc_default = SOF_IPC_TYPE_3,
.dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC_TYPE_3] = "intel/sof",
[SOF_IPC_TYPE_4] = "intel/avs/cnl",
},
.default_lib_path = {
[SOF_IPC_TYPE_4] = "intel/avs-lib/cnl",
},
.default_tplg_path = {
[SOF_IPC_TYPE_3] = "intel/sof-tplg",
[SOF_IPC_TYPE_4] = "intel/avs-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_3] = "sof-cfl.ri",
[SOF_IPC_TYPE_4] = "dsp_basefw.bin",
},
.nocodec_tplg_filename = "sof-cnl-nocodec.tplg",
.ops = &sof_cnl_ops,
.ops_init = sof_cnl_ops_init,
.ops_free = hda_ops_free,
};
static const struct sof_dev_desc cml_desc = {
.machines = snd_soc_acpi_intel_cml_machines,
.alt_machines = snd_soc_acpi_intel_cml_sdw_machines,
.use_acpi_target_states = true,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
.irqindex_host_ipc = -1,
.chip_info = &cnl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
.ipc_default = SOF_IPC_TYPE_3,
.dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC_TYPE_3] = "intel/sof",
[SOF_IPC_TYPE_4] = "intel/avs/cnl",
},
.default_lib_path = {
[SOF_IPC_TYPE_4] = "intel/avs-lib/cnl",
},
.default_tplg_path = {
[SOF_IPC_TYPE_3] = "intel/sof-tplg",
[SOF_IPC_TYPE_4] = "intel/avs-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_3] = "sof-cml.ri",
[SOF_IPC_TYPE_4] = "dsp_basefw.bin",
},
.nocodec_tplg_filename = "sof-cnl-nocodec.tplg",
.ops = &sof_cnl_ops,
.ops_init = sof_cnl_ops_init,
.ops_free = hda_ops_free,
};
/* PCI IDs */
static const struct pci_device_id sof_pci_ids[] = {
{ PCI_DEVICE_DATA(INTEL, HDA_CNL_LP, &cnl_desc) },
{ PCI_DEVICE_DATA(INTEL, HDA_CNL_H, &cfl_desc) },
{ PCI_DEVICE_DATA(INTEL, HDA_CML_LP, &cml_desc) },
{ PCI_DEVICE_DATA(INTEL, HDA_CML_H, &cml_desc) },
{ PCI_DEVICE_DATA(INTEL, HDA_CML_S, &cml_desc) },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, sof_pci_ids);
/* pci_driver definition */
static struct pci_driver snd_sof_pci_intel_cnl_driver = {
.name = "sof-audio-pci-intel-cnl",
.id_table = sof_pci_ids,
.probe = hda_pci_intel_probe,
.remove = sof_pci_remove,
.shutdown = sof_pci_shutdown,
.driver = {
.pm = pm_ptr(&sof_pci_pm),
},
};
module_pci_driver(snd_sof_pci_intel_cnl_driver);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("SOF support for CannonLake platforms");
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_HDA_GENERIC");
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_HDA_COMMON");
MODULE_IMPORT_NS("SND_SOC_SOF_PCI_DEV");
+115
View File
@@ -0,0 +1,115 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2018-2021 Intel Corporation
//
// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
//
#include <linux/module.h>
#include <linux/pci.h>
#include <sound/soc-acpi.h>
#include <sound/soc-acpi-intel-match.h>
#include <sound/sof.h>
#include "../ops.h"
#include "../sof-pci-dev.h"
/* platform specific devices */
#include "hda.h"
static const struct sof_dev_desc icl_desc = {
.machines = snd_soc_acpi_intel_icl_machines,
.alt_machines = snd_soc_acpi_intel_icl_sdw_machines,
.use_acpi_target_states = true,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
.irqindex_host_ipc = -1,
.chip_info = &icl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
.ipc_default = SOF_IPC_TYPE_3,
.dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC_TYPE_3] = "intel/sof",
[SOF_IPC_TYPE_4] = "intel/avs/icl",
},
.default_lib_path = {
[SOF_IPC_TYPE_4] = "intel/avs-lib/icl",
},
.default_tplg_path = {
[SOF_IPC_TYPE_3] = "intel/sof-tplg",
[SOF_IPC_TYPE_4] = "intel/avs-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_3] = "sof-icl.ri",
[SOF_IPC_TYPE_4] = "dsp_basefw.bin",
},
.nocodec_tplg_filename = "sof-icl-nocodec.tplg",
.ops = &sof_icl_ops,
.ops_init = sof_icl_ops_init,
.ops_free = hda_ops_free,
};
static const struct sof_dev_desc jsl_desc = {
.machines = snd_soc_acpi_intel_jsl_machines,
.use_acpi_target_states = true,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
.irqindex_host_ipc = -1,
.chip_info = &jsl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
.ipc_default = SOF_IPC_TYPE_3,
.dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC_TYPE_3] = "intel/sof",
[SOF_IPC_TYPE_4] = "intel/avs/jsl",
},
.default_lib_path = {
[SOF_IPC_TYPE_4] = "intel/avs-lib/jsl",
},
.default_tplg_path = {
[SOF_IPC_TYPE_3] = "intel/sof-tplg",
[SOF_IPC_TYPE_4] = "intel/avs-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_3] = "sof-jsl.ri",
[SOF_IPC_TYPE_4] = "dsp_basefw.bin",
},
.nocodec_tplg_filename = "sof-jsl-nocodec.tplg",
.ops = &sof_cnl_ops,
.ops_init = sof_cnl_ops_init,
.ops_free = hda_ops_free,
};
/* PCI IDs */
static const struct pci_device_id sof_pci_ids[] = {
{ PCI_DEVICE_DATA(INTEL, HDA_ICL_LP, &icl_desc) },
{ PCI_DEVICE_DATA(INTEL, HDA_ICL_H, &icl_desc) },
{ PCI_DEVICE_DATA(INTEL, HDA_ICL_N, &jsl_desc) },
{ PCI_DEVICE_DATA(INTEL, HDA_JSL_N, &jsl_desc) },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, sof_pci_ids);
/* pci_driver definition */
static struct pci_driver snd_sof_pci_intel_icl_driver = {
.name = "sof-audio-pci-intel-icl",
.id_table = sof_pci_ids,
.probe = hda_pci_intel_probe,
.remove = sof_pci_remove,
.shutdown = sof_pci_shutdown,
.driver = {
.pm = pm_ptr(&sof_pci_pm),
},
};
module_pci_driver(snd_sof_pci_intel_icl_driver);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("SOF support for IceLake platforms");
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_HDA_GENERIC");
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_HDA_COMMON");
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_CNL");
MODULE_IMPORT_NS("SND_SOC_SOF_PCI_DEV");
+85
View File
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2023 Intel Corporation
//
// 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 "lnl.h"
/* LunarLake ops */
static struct snd_sof_dsp_ops sof_lnl_ops;
static int sof_lnl_ops_init(struct snd_sof_dev *sdev)
{
return sof_lnl_set_ops(sdev, &sof_lnl_ops);
}
static const struct sof_dev_desc lnl_desc = {
.use_acpi_target_states = true,
.machines = snd_soc_acpi_intel_lnl_machines,
.alt_machines = snd_soc_acpi_intel_lnl_sdw_machines,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
.irqindex_host_ipc = -1,
.chip_info = &lnl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_4),
.ipc_default = SOF_IPC_TYPE_4,
.dspless_mode_supported = true,
.on_demand_dsp_boot = true,
.default_fw_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4/lnl",
},
.default_lib_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-lib/lnl",
},
.default_tplg_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_4] = "sof-lnl.ri",
},
.nocodec_tplg_filename = "sof-lnl-nocodec.tplg",
.ops = &sof_lnl_ops,
.ops_init = sof_lnl_ops_init,
};
/* PCI IDs */
static const struct pci_device_id sof_pci_ids[] = {
{ PCI_DEVICE_DATA(INTEL, HDA_LNL_P, &lnl_desc) }, /* LNL-P */
{ 0, }
};
MODULE_DEVICE_TABLE(pci, sof_pci_ids);
/* pci_driver definition */
static struct pci_driver snd_sof_pci_intel_lnl_driver = {
.name = "sof-audio-pci-intel-lnl",
.id_table = sof_pci_ids,
.probe = hda_pci_intel_probe,
.remove = sof_pci_remove,
.shutdown = sof_pci_shutdown,
.driver = {
.pm = pm_ptr(&sof_pci_pm),
},
};
module_pci_driver(snd_sof_pci_intel_lnl_driver);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("SOF support for LunarLake platforms");
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_HDA_GENERIC");
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_HDA_COMMON");
MODULE_IMPORT_NS("SND_SOC_SOF_PCI_DEV");
+147
View File
@@ -0,0 +1,147 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2018-2022 Intel Corporation
//
// 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"
/* Meteorlake ops */
static struct snd_sof_dsp_ops sof_mtl_ops;
static int sof_mtl_ops_init(struct snd_sof_dev *sdev)
{
return sof_mtl_set_ops(sdev, &sof_mtl_ops);
}
static const struct sof_dev_desc mtl_desc = {
.use_acpi_target_states = true,
.machines = snd_soc_acpi_intel_mtl_machines,
.alt_machines = snd_soc_acpi_intel_mtl_sdw_machines,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
.irqindex_host_ipc = -1,
.chip_info = &mtl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_4),
.ipc_default = SOF_IPC_TYPE_4,
.dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4/mtl",
},
.default_lib_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-lib/mtl",
},
.default_tplg_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_4] = "sof-mtl.ri",
},
.nocodec_tplg_filename = "sof-mtl-nocodec.tplg",
.ops = &sof_mtl_ops,
.ops_init = sof_mtl_ops_init,
.ops_free = hda_ops_free,
};
static const struct sof_dev_desc arl_desc = {
.use_acpi_target_states = true,
.machines = snd_soc_acpi_intel_arl_machines,
.alt_machines = snd_soc_acpi_intel_arl_sdw_machines,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
.irqindex_host_ipc = -1,
.chip_info = &mtl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_4),
.ipc_default = SOF_IPC_TYPE_4,
.dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4/arl",
},
.default_lib_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-lib/arl",
},
.default_tplg_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_4] = "sof-arl.ri",
},
.nocodec_tplg_filename = "sof-arl-nocodec.tplg",
.ops = &sof_mtl_ops,
.ops_init = sof_mtl_ops_init,
.ops_free = hda_ops_free,
};
static const struct sof_dev_desc arl_s_desc = {
.use_acpi_target_states = true,
.machines = snd_soc_acpi_intel_arl_machines,
.alt_machines = snd_soc_acpi_intel_arl_sdw_machines,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
.irqindex_host_ipc = -1,
.chip_info = &arl_s_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_4),
.ipc_default = SOF_IPC_TYPE_4,
.dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4/arl-s",
},
.default_lib_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-lib/arl-s",
},
.default_tplg_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_4] = "sof-arl-s.ri",
},
.nocodec_tplg_filename = "sof-arl-nocodec.tplg",
.ops = &sof_mtl_ops,
.ops_init = sof_mtl_ops_init,
.ops_free = hda_ops_free,
};
/* PCI IDs */
static const struct pci_device_id sof_pci_ids[] = {
{ PCI_DEVICE_DATA(INTEL, HDA_MTL, &mtl_desc) },
{ PCI_DEVICE_DATA(INTEL, HDA_ARL_S, &arl_s_desc) },
{ PCI_DEVICE_DATA(INTEL, HDA_ARL, &arl_desc) },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, sof_pci_ids);
/* pci_driver definition */
static struct pci_driver snd_sof_pci_intel_mtl_driver = {
.name = "sof-audio-pci-intel-mtl",
.id_table = sof_pci_ids,
.probe = hda_pci_intel_probe,
.remove = sof_pci_remove,
.shutdown = sof_pci_shutdown,
.driver = {
.pm = pm_ptr(&sof_pci_pm),
},
};
module_pci_driver(snd_sof_pci_intel_mtl_driver);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("SOF support for MeteorLake platforms");
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_HDA_GENERIC");
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_HDA_COMMON");
MODULE_IMPORT_NS("SND_SOC_SOF_PCI_DEV");
+114
View File
@@ -0,0 +1,114 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2025 Intel Corporation.
//
#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 "nvl.h"
/* PantherLake ops */
static struct snd_sof_dsp_ops sof_nvl_ops;
static int sof_nvl_ops_init(struct snd_sof_dev *sdev)
{
return sof_nvl_set_ops(sdev, &sof_nvl_ops);
}
static const struct sof_dev_desc nvl_desc = {
.use_acpi_target_states = true,
.machines = snd_soc_acpi_intel_nvl_machines,
.alt_machines = snd_soc_acpi_intel_nvl_sdw_machines,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
.irqindex_host_ipc = -1,
.chip_info = &nvl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_4),
.ipc_default = SOF_IPC_TYPE_4,
.dspless_mode_supported = true,
.on_demand_dsp_boot = true,
.default_fw_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4/nvl",
},
.default_lib_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-lib/nvl",
},
.default_tplg_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_4] = "sof-nvl.ri",
},
.nocodec_tplg_filename = "sof-nvl-nocodec.tplg",
.ops = &sof_nvl_ops,
.ops_init = sof_nvl_ops_init,
};
static const struct sof_dev_desc nvl_s_desc = {
.use_acpi_target_states = true,
.machines = snd_soc_acpi_intel_nvl_machines,
.alt_machines = snd_soc_acpi_intel_nvl_sdw_machines,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
.irqindex_host_ipc = -1,
.chip_info = &nvl_s_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_4),
.ipc_default = SOF_IPC_TYPE_4,
.dspless_mode_supported = true,
.on_demand_dsp_boot = true,
.default_fw_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4/nvl-s",
},
.default_lib_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-lib/nvl-s",
},
.default_tplg_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_4] = "sof-nvl-s.ri",
},
.nocodec_tplg_filename = "sof-nvl-nocodec.tplg",
.ops = &sof_nvl_ops,
.ops_init = sof_nvl_ops_init,
};
/* PCI IDs */
static const struct pci_device_id sof_pci_ids[] = {
{ PCI_DEVICE_DATA(INTEL, HDA_NVL, &nvl_desc) }, /* NVL */
{ PCI_DEVICE_DATA(INTEL, HDA_NVL_S, &nvl_s_desc) }, /* NVL-S */
{ 0, }
};
MODULE_DEVICE_TABLE(pci, sof_pci_ids);
/* pci_driver definition */
static struct pci_driver snd_sof_pci_intel_nvl_driver = {
.name = "sof-audio-pci-intel-nvl",
.id_table = sof_pci_ids,
.probe = hda_pci_intel_probe,
.remove = sof_pci_remove,
.shutdown = sof_pci_shutdown,
.driver = {
.pm = pm_ptr(&sof_pci_pm),
},
};
module_pci_driver(snd_sof_pci_intel_nvl_driver);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("SOF support for NovaLake platforms");
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_HDA_GENERIC");
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_HDA_COMMON");
MODULE_IMPORT_NS("SND_SOC_SOF_PCI_DEV");
+115
View File
@@ -0,0 +1,115 @@
// 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) 2024 Intel Corporation.
//
#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 "ptl.h"
/* PantherLake ops */
static struct snd_sof_dsp_ops sof_ptl_ops;
static int sof_ptl_ops_init(struct snd_sof_dev *sdev)
{
return sof_ptl_set_ops(sdev, &sof_ptl_ops);
}
static const struct sof_dev_desc ptl_desc = {
.use_acpi_target_states = true,
.machines = snd_soc_acpi_intel_ptl_machines,
.alt_machines = snd_soc_acpi_intel_ptl_sdw_machines,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
.irqindex_host_ipc = -1,
.chip_info = &ptl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_4),
.ipc_default = SOF_IPC_TYPE_4,
.dspless_mode_supported = true,
.on_demand_dsp_boot = true,
.default_fw_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4/ptl",
},
.default_lib_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-lib/ptl",
},
.default_tplg_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_4] = "sof-ptl.ri",
},
.nocodec_tplg_filename = "sof-ptl-nocodec.tplg",
.ops = &sof_ptl_ops,
.ops_init = sof_ptl_ops_init,
};
static const struct sof_dev_desc wcl_desc = {
.use_acpi_target_states = true,
.machines = snd_soc_acpi_intel_ptl_machines,
.alt_machines = snd_soc_acpi_intel_ptl_sdw_machines,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
.irqindex_host_ipc = -1,
.chip_info = &wcl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_4),
.ipc_default = SOF_IPC_TYPE_4,
.dspless_mode_supported = true,
.on_demand_dsp_boot = true,
.default_fw_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4/wcl",
},
.default_lib_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-lib/wcl",
},
.default_tplg_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_4] = "sof-wcl.ri",
},
.nocodec_tplg_filename = "sof-ptl-nocodec.tplg",
.ops = &sof_ptl_ops,
.ops_init = sof_ptl_ops_init,
};
/* PCI IDs */
static const struct pci_device_id sof_pci_ids[] = {
{ PCI_DEVICE_DATA(INTEL, HDA_PTL, &ptl_desc) }, /* PTL */
{ PCI_DEVICE_DATA(INTEL, HDA_PTL_H, &ptl_desc) }, /* PTL-H */
{ PCI_DEVICE_DATA(INTEL, HDA_WCL, &wcl_desc) }, /* WCL */
{ 0, }
};
MODULE_DEVICE_TABLE(pci, sof_pci_ids);
/* pci_driver definition */
static struct pci_driver snd_sof_pci_intel_ptl_driver = {
.name = "sof-audio-pci-intel-ptl",
.id_table = sof_pci_ids,
.probe = hda_pci_intel_probe,
.remove = sof_pci_remove,
.shutdown = sof_pci_shutdown,
.driver = {
.pm = pm_ptr(&sof_pci_pm),
},
};
module_pci_driver(snd_sof_pci_intel_ptl_driver);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("SOF support for PantherLake platforms");
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_HDA_GENERIC");
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_HDA_COMMON");
MODULE_IMPORT_NS("SND_SOC_SOF_PCI_DEV");
+95
View File
@@ -0,0 +1,95 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2018-2022 Intel Corporation
//
#include <linux/module.h>
#include <linux/pci.h>
#include <sound/soc-acpi.h>
#include <sound/soc-acpi-intel-match.h>
#include <sound/sof.h>
#include "../ops.h"
#include "../sof-pci-dev.h"
/* platform specific devices */
#include "hda.h"
static struct sof_dev_desc skl_desc = {
.machines = snd_soc_acpi_intel_skl_machines,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
.chip_info = &skl_chip_info,
.irqindex_host_ipc = -1,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_4),
.ipc_default = SOF_IPC_TYPE_4,
.dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC_TYPE_4] = "intel/avs/skl",
},
.default_tplg_path = {
[SOF_IPC_TYPE_4] = "intel/avs-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_4] = "dsp_basefw.bin",
},
.nocodec_tplg_filename = "sof-skl-nocodec.tplg",
.ops = &sof_skl_ops,
.ops_init = sof_skl_ops_init,
.ops_free = hda_ops_free,
};
static struct sof_dev_desc kbl_desc = {
.machines = snd_soc_acpi_intel_kbl_machines,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
.chip_info = &skl_chip_info,
.irqindex_host_ipc = -1,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_4),
.ipc_default = SOF_IPC_TYPE_4,
.dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC_TYPE_4] = "intel/avs/kbl",
},
.default_tplg_path = {
[SOF_IPC_TYPE_4] = "intel/avs-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_4] = "dsp_basefw.bin",
},
.nocodec_tplg_filename = "sof-kbl-nocodec.tplg",
.ops = &sof_skl_ops,
.ops_init = sof_skl_ops_init,
.ops_free = hda_ops_free,
};
/* PCI IDs */
static const struct pci_device_id sof_pci_ids[] = {
{ PCI_DEVICE_DATA(INTEL, HDA_SKL_LP, &skl_desc) },
{ PCI_DEVICE_DATA(INTEL, HDA_KBL_LP, &kbl_desc) },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, sof_pci_ids);
/* pci_driver definition */
static struct pci_driver snd_sof_pci_intel_skl_driver = {
.name = "sof-audio-pci-intel-skl",
.id_table = sof_pci_ids,
.probe = hda_pci_intel_probe,
.remove = sof_pci_remove,
.shutdown = sof_pci_shutdown,
.driver = {
.pm = pm_ptr(&sof_pci_pm),
},
};
module_pci_driver(snd_sof_pci_intel_skl_driver);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("SOF support for SkyLake platforms");
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_HDA_GENERIC");
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_HDA_COMMON");
MODULE_IMPORT_NS("SND_SOC_SOF_PCI_DEV");
+324
View File
@@ -0,0 +1,324 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2018-2021 Intel Corporation
//
// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
//
#include <linux/module.h>
#include <linux/pci.h>
#include <sound/soc-acpi.h>
#include <sound/soc-acpi-intel-match.h>
#include <sound/sof.h>
#include "../ops.h"
#include "../sof-pci-dev.h"
/* platform specific devices */
#include "hda.h"
static const struct sof_dev_desc tgl_desc = {
.machines = snd_soc_acpi_intel_tgl_machines,
.alt_machines = snd_soc_acpi_intel_tgl_sdw_machines,
.use_acpi_target_states = true,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
.irqindex_host_ipc = -1,
.chip_info = &tgl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
.ipc_default = SOF_IPC_TYPE_3,
.dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC_TYPE_3] = "intel/sof",
[SOF_IPC_TYPE_4] = "intel/sof-ipc4/tgl",
},
.default_lib_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-lib/tgl",
},
.default_tplg_path = {
[SOF_IPC_TYPE_3] = "intel/sof-tplg",
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_3] = "sof-tgl.ri",
[SOF_IPC_TYPE_4] = "sof-tgl.ri",
},
.nocodec_tplg_filename = "sof-tgl-nocodec.tplg",
.ops = &sof_tgl_ops,
.ops_init = sof_tgl_ops_init,
.ops_free = hda_ops_free,
};
static const struct sof_dev_desc tglh_desc = {
.machines = snd_soc_acpi_intel_tgl_machines,
.alt_machines = snd_soc_acpi_intel_tgl_sdw_machines,
.use_acpi_target_states = true,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
.irqindex_host_ipc = -1,
.chip_info = &tglh_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
.ipc_default = SOF_IPC_TYPE_3,
.dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC_TYPE_3] = "intel/sof",
[SOF_IPC_TYPE_4] = "intel/sof-ipc4/tgl-h",
},
.default_lib_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-lib/tgl-h",
},
.default_tplg_path = {
[SOF_IPC_TYPE_3] = "intel/sof-tplg",
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_3] = "sof-tgl-h.ri",
[SOF_IPC_TYPE_4] = "sof-tgl-h.ri",
},
.nocodec_tplg_filename = "sof-tgl-nocodec.tplg",
.ops = &sof_tgl_ops,
.ops_init = sof_tgl_ops_init,
.ops_free = hda_ops_free,
};
static const struct sof_dev_desc ehl_desc = {
.machines = snd_soc_acpi_intel_ehl_machines,
.use_acpi_target_states = true,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
.irqindex_host_ipc = -1,
.chip_info = &ehl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
.ipc_default = SOF_IPC_TYPE_3,
.dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC_TYPE_3] = "intel/sof",
[SOF_IPC_TYPE_4] = "intel/sof-ipc4/ehl",
},
.default_lib_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-lib/ehl",
},
.default_tplg_path = {
[SOF_IPC_TYPE_3] = "intel/sof-tplg",
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_3] = "sof-ehl.ri",
[SOF_IPC_TYPE_4] = "sof-ehl.ri",
},
.nocodec_tplg_filename = "sof-ehl-nocodec.tplg",
.ops = &sof_tgl_ops,
.ops_init = sof_tgl_ops_init,
.ops_free = hda_ops_free,
};
static const struct sof_dev_desc adls_desc = {
.machines = snd_soc_acpi_intel_adl_machines,
.alt_machines = snd_soc_acpi_intel_adl_sdw_machines,
.use_acpi_target_states = true,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
.irqindex_host_ipc = -1,
.chip_info = &adls_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
.ipc_default = SOF_IPC_TYPE_3,
.dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC_TYPE_3] = "intel/sof",
[SOF_IPC_TYPE_4] = "intel/sof-ipc4/adl-s",
},
.default_lib_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-lib/adl-s",
},
.default_tplg_path = {
[SOF_IPC_TYPE_3] = "intel/sof-tplg",
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_3] = "sof-adl-s.ri",
[SOF_IPC_TYPE_4] = "sof-adl-s.ri",
},
.nocodec_tplg_filename = "sof-adl-nocodec.tplg",
.ops = &sof_tgl_ops,
.ops_init = sof_tgl_ops_init,
.ops_free = hda_ops_free,
};
static const struct sof_dev_desc adl_desc = {
.machines = snd_soc_acpi_intel_adl_machines,
.alt_machines = snd_soc_acpi_intel_adl_sdw_machines,
.use_acpi_target_states = true,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
.irqindex_host_ipc = -1,
.chip_info = &tgl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
.ipc_default = SOF_IPC_TYPE_3,
.dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC_TYPE_3] = "intel/sof",
[SOF_IPC_TYPE_4] = "intel/sof-ipc4/adl",
},
.default_lib_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-lib/adl",
},
.default_tplg_path = {
[SOF_IPC_TYPE_3] = "intel/sof-tplg",
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_3] = "sof-adl.ri",
[SOF_IPC_TYPE_4] = "sof-adl.ri",
},
.nocodec_tplg_filename = "sof-adl-nocodec.tplg",
.ops = &sof_tgl_ops,
.ops_init = sof_tgl_ops_init,
.ops_free = hda_ops_free,
};
static const struct sof_dev_desc adln_desc = {
.machines = snd_soc_acpi_intel_adl_machines,
.alt_machines = snd_soc_acpi_intel_adl_sdw_machines,
.use_acpi_target_states = true,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
.irqindex_host_ipc = -1,
.chip_info = &tgl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
.ipc_default = SOF_IPC_TYPE_3,
.dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC_TYPE_3] = "intel/sof",
[SOF_IPC_TYPE_4] = "intel/sof-ipc4/adl-n",
},
.default_lib_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-lib/adl-n",
},
.default_tplg_path = {
[SOF_IPC_TYPE_3] = "intel/sof-tplg",
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_3] = "sof-adl-n.ri",
[SOF_IPC_TYPE_4] = "sof-adl-n.ri",
},
.nocodec_tplg_filename = "sof-adl-nocodec.tplg",
.ops = &sof_tgl_ops,
.ops_init = sof_tgl_ops_init,
.ops_free = hda_ops_free,
};
static const struct sof_dev_desc rpls_desc = {
.machines = snd_soc_acpi_intel_rpl_machines,
.alt_machines = snd_soc_acpi_intel_rpl_sdw_machines,
.use_acpi_target_states = true,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
.irqindex_host_ipc = -1,
.chip_info = &adls_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
.ipc_default = SOF_IPC_TYPE_3,
.dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC_TYPE_3] = "intel/sof",
[SOF_IPC_TYPE_4] = "intel/sof-ipc4/rpl-s",
},
.default_lib_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-lib/rpl-s",
},
.default_tplg_path = {
[SOF_IPC_TYPE_3] = "intel/sof-tplg",
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_3] = "sof-rpl-s.ri",
[SOF_IPC_TYPE_4] = "sof-rpl-s.ri",
},
.nocodec_tplg_filename = "sof-rpl-nocodec.tplg",
.ops = &sof_tgl_ops,
.ops_init = sof_tgl_ops_init,
.ops_free = hda_ops_free,
};
static const struct sof_dev_desc rpl_desc = {
.machines = snd_soc_acpi_intel_rpl_machines,
.alt_machines = snd_soc_acpi_intel_rpl_sdw_machines,
.use_acpi_target_states = true,
.resindex_lpe_base = 0,
.resindex_pcicfg_base = -1,
.resindex_imr_base = -1,
.irqindex_host_ipc = -1,
.chip_info = &tgl_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_3) | BIT(SOF_IPC_TYPE_4),
.ipc_default = SOF_IPC_TYPE_3,
.dspless_mode_supported = true, /* Only supported for HDaudio */
.default_fw_path = {
[SOF_IPC_TYPE_3] = "intel/sof",
[SOF_IPC_TYPE_4] = "intel/sof-ipc4/rpl",
},
.default_lib_path = {
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-lib/rpl",
},
.default_tplg_path = {
[SOF_IPC_TYPE_3] = "intel/sof-tplg",
[SOF_IPC_TYPE_4] = "intel/sof-ipc4-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_3] = "sof-rpl.ri",
[SOF_IPC_TYPE_4] = "sof-rpl.ri",
},
.nocodec_tplg_filename = "sof-rpl-nocodec.tplg",
.ops = &sof_tgl_ops,
.ops_init = sof_tgl_ops_init,
.ops_free = hda_ops_free,
};
/* PCI IDs */
static const struct pci_device_id sof_pci_ids[] = {
{ PCI_DEVICE_DATA(INTEL, HDA_TGL_LP, &tgl_desc) },
{ PCI_DEVICE_DATA(INTEL, HDA_TGL_H, &tglh_desc) },
{ PCI_DEVICE_DATA(INTEL, HDA_EHL_0, &ehl_desc) },
{ PCI_DEVICE_DATA(INTEL, HDA_EHL_3, &ehl_desc) },
{ PCI_DEVICE_DATA(INTEL, HDA_ADL_S, &adls_desc) },
{ PCI_DEVICE_DATA(INTEL, HDA_RPL_S, &rpls_desc) },
{ PCI_DEVICE_DATA(INTEL, HDA_ADL_P, &adl_desc) },
{ PCI_DEVICE_DATA(INTEL, HDA_ADL_PS, &adl_desc) },
{ PCI_DEVICE_DATA(INTEL, HDA_RPL_P_0, &rpl_desc) },
{ PCI_DEVICE_DATA(INTEL, HDA_RPL_P_1, &rpl_desc) },
{ PCI_DEVICE_DATA(INTEL, HDA_ADL_M, &adl_desc) },
{ PCI_DEVICE_DATA(INTEL, HDA_ADL_PX, &adl_desc) },
{ PCI_DEVICE_DATA(INTEL, HDA_RPL_M, &rpl_desc) },
{ PCI_DEVICE_DATA(INTEL, HDA_RPL_PX, &rpl_desc) },
{ PCI_DEVICE_DATA(INTEL, HDA_ADL_N, &adln_desc) },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, sof_pci_ids);
/* pci_driver definition */
static struct pci_driver snd_sof_pci_intel_tgl_driver = {
.name = "sof-audio-pci-intel-tgl",
.id_table = sof_pci_ids,
.probe = hda_pci_intel_probe,
.remove = sof_pci_remove,
.shutdown = sof_pci_shutdown,
.driver = {
.pm = pm_ptr(&sof_pci_pm),
},
};
module_pci_driver(snd_sof_pci_intel_tgl_driver);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("SOF support for TigerLake platforms");
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_HDA_GENERIC");
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_HDA_COMMON");
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_CNL");
MODULE_IMPORT_NS("SND_SOC_SOF_PCI_DEV");
+250
View File
@@ -0,0 +1,250 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2018-2021 Intel Corporation
//
// Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
//
#include <linux/module.h>
#include <linux/pci.h>
#include <sound/soc-acpi.h>
#include <sound/soc-acpi-intel-match.h>
#include <sound/sof.h>
#include "../ops.h"
#include "atom.h"
#include "../sof-pci-dev.h"
#include "../sof-audio.h"
/* platform specific devices */
#include "shim.h"
static struct snd_soc_acpi_mach sof_tng_machines[] = {
{
.id = "INT343A",
.drv_name = "edison",
.sof_tplg_filename = "sof-byt.tplg",
},
{}
};
static const struct snd_sof_debugfs_map tng_debugfs[] = {
{"dmac0", DSP_BAR, DMAC0_OFFSET, DMAC_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
{"dmac1", DSP_BAR, DMAC1_OFFSET, DMAC_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
{"ssp0", DSP_BAR, SSP0_OFFSET, SSP_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
{"ssp1", DSP_BAR, SSP1_OFFSET, SSP_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
{"ssp2", DSP_BAR, SSP2_OFFSET, SSP_SIZE,
SOF_DEBUGFS_ACCESS_ALWAYS},
{"iram", DSP_BAR, IRAM_OFFSET, IRAM_SIZE,
SOF_DEBUGFS_ACCESS_D0_ONLY},
{"dram", DSP_BAR, DRAM_OFFSET, DRAM_SIZE,
SOF_DEBUGFS_ACCESS_D0_ONLY},
{"shim", DSP_BAR, SHIM_OFFSET, SHIM_SIZE_BYT,
SOF_DEBUGFS_ACCESS_ALWAYS},
};
static int tangier_pci_probe(struct snd_sof_dev *sdev)
{
struct snd_sof_pdata *pdata = sdev->pdata;
const struct sof_dev_desc *desc = pdata->desc;
struct pci_dev *pci = to_pci_dev(sdev->dev);
const struct sof_intel_dsp_desc *chip;
u32 base, size;
int ret;
chip = get_chip_info(sdev->pdata);
if (!chip) {
dev_err(sdev->dev, "error: no such device supported\n");
return -EIO;
}
sdev->num_cores = chip->cores_num;
/* DSP DMA can only access low 31 bits of host memory */
ret = dma_coerce_mask_and_coherent(&pci->dev, DMA_BIT_MASK(31));
if (ret < 0) {
dev_err(sdev->dev, "error: failed to set DMA mask %d\n", ret);
return ret;
}
/* LPE base */
base = pci_resource_start(pci, desc->resindex_lpe_base) - IRAM_OFFSET;
size = PCI_BAR_SIZE;
dev_dbg(sdev->dev, "LPE PHY base at 0x%x size 0x%x", base, size);
sdev->bar[DSP_BAR] = devm_ioremap(sdev->dev, base, size);
if (!sdev->bar[DSP_BAR]) {
dev_err(sdev->dev, "error: failed to ioremap LPE base 0x%x size 0x%x\n",
base, size);
return -ENODEV;
}
dev_dbg(sdev->dev, "LPE VADDR %p\n", sdev->bar[DSP_BAR]);
/* IMR base - optional */
if (desc->resindex_imr_base == -1)
goto irq;
base = pci_resource_start(pci, desc->resindex_imr_base);
size = pci_resource_len(pci, desc->resindex_imr_base);
/* some BIOSes don't map IMR */
if (base == 0x55aa55aa || base == 0x0) {
dev_info(sdev->dev, "IMR not set by BIOS. Ignoring\n");
goto irq;
}
dev_dbg(sdev->dev, "IMR base at 0x%x size 0x%x", base, size);
sdev->bar[IMR_BAR] = devm_ioremap(sdev->dev, base, size);
if (!sdev->bar[IMR_BAR]) {
dev_err(sdev->dev, "error: failed to ioremap IMR base 0x%x size 0x%x\n",
base, size);
return -ENODEV;
}
dev_dbg(sdev->dev, "IMR VADDR %p\n", sdev->bar[IMR_BAR]);
irq:
/* register our IRQ */
sdev->ipc_irq = pci->irq;
dev_dbg(sdev->dev, "using IRQ %d\n", sdev->ipc_irq);
ret = devm_request_threaded_irq(sdev->dev, sdev->ipc_irq,
atom_irq_handler, atom_irq_thread,
0, "AudioDSP", sdev);
if (ret < 0) {
dev_err(sdev->dev, "error: failed to register IRQ %d\n",
sdev->ipc_irq);
return ret;
}
/* enable BUSY and disable DONE Interrupt by default */
snd_sof_dsp_update_bits64(sdev, DSP_BAR, SHIM_IMRX,
SHIM_IMRX_BUSY | SHIM_IMRX_DONE,
SHIM_IMRX_DONE);
/* set default mailbox offset for FW ready message */
sdev->dsp_box.offset = MBOX_OFFSET;
return ret;
}
const struct snd_sof_dsp_ops sof_tng_ops = {
/* device init */
.probe = tangier_pci_probe,
/* DSP core boot / reset */
.run = atom_run,
.reset = atom_reset,
/* Register IO uses direct mmio */
/* Block IO */
.block_read = sof_block_read,
.block_write = sof_block_write,
/* Mailbox IO */
.mailbox_read = sof_mailbox_read,
.mailbox_write = sof_mailbox_write,
/* doorbell */
.irq_handler = atom_irq_handler,
.irq_thread = atom_irq_thread,
/* ipc */
.send_msg = atom_send_msg,
.get_mailbox_offset = atom_get_mailbox_offset,
.get_window_offset = atom_get_window_offset,
.ipc_msg_data = sof_ipc_msg_data,
.set_stream_data_offset = sof_set_stream_data_offset,
/* machine driver */
.machine_select = atom_machine_select,
.machine_register = sof_machine_register,
.machine_unregister = sof_machine_unregister,
.set_mach_params = atom_set_mach_params,
/* debug */
.debug_map = tng_debugfs,
.debug_map_count = ARRAY_SIZE(tng_debugfs),
.dbg_dump = atom_dump,
.debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem,
/* stream callbacks */
.pcm_open = sof_stream_pcm_open,
.pcm_close = sof_stream_pcm_close,
/*Firmware loading */
.load_firmware = snd_sof_load_firmware_memcpy,
/* DAI drivers */
.drv = atom_dai,
.num_drv = 3, /* we have only 3 SSPs on byt*/
/* ALSA HW info flags */
.hw_info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_BATCH,
.dsp_arch_ops = &sof_xtensa_arch_ops,
};
const struct sof_intel_dsp_desc tng_chip_info = {
.cores_num = 1,
.host_managed_cores_mask = 1,
.hw_ip_version = SOF_INTEL_TANGIER,
};
static const struct sof_dev_desc tng_desc = {
.machines = sof_tng_machines,
.resindex_lpe_base = 3, /* IRAM, but subtract IRAM offset */
.resindex_pcicfg_base = -1,
.resindex_imr_base = 0,
.irqindex_host_ipc = -1,
.chip_info = &tng_chip_info,
.ipc_supported_mask = BIT(SOF_IPC_TYPE_3),
.ipc_default = SOF_IPC_TYPE_3,
.default_fw_path = {
[SOF_IPC_TYPE_3] = "intel/sof",
},
.default_tplg_path = {
[SOF_IPC_TYPE_3] = "intel/sof-tplg",
},
.default_fw_filename = {
[SOF_IPC_TYPE_3] = "sof-byt.ri",
},
.nocodec_tplg_filename = "sof-byt.tplg",
.ops = &sof_tng_ops,
};
/* PCI IDs */
static const struct pci_device_id sof_pci_ids[] = {
{ PCI_DEVICE_DATA(INTEL, SST_TNG, &tng_desc) },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, sof_pci_ids);
/* pci_driver definition */
static struct pci_driver snd_sof_pci_intel_tng_driver = {
.name = "sof-audio-pci-intel-tng",
.id_table = sof_pci_ids,
.probe = sof_pci_probe,
.remove = sof_pci_remove,
.shutdown = sof_pci_shutdown,
.driver = {
.pm = pm_ptr(&sof_pci_pm),
},
};
module_pci_driver(snd_sof_pci_intel_tng_driver);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("SOF support for Tangier platforms");
MODULE_IMPORT_NS("SND_SOC_SOF_XTENSA");
MODULE_IMPORT_NS("SND_SOC_SOF_PCI_DEV");
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_ATOM_HIFI_EP");
+158
View File
@@ -0,0 +1,158 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// Copyright(c) 2025 Intel Corporation
/*
* Hardware interface for audio DSP on PantherLake.
*/
#include <sound/hda_register.h>
#include <sound/hda-mlink.h>
#include <sound/sof/ipc4/header.h>
#include "../ipc4-priv.h"
#include "../ops.h"
#include "hda.h"
#include "hda-ipc.h"
#include "../sof-audio.h"
#include "mtl.h"
#include "lnl.h"
#include "ptl.h"
static bool sof_ptl_check_mic_privacy_irq(struct snd_sof_dev *sdev, bool alt,
int elid)
{
if (!alt || elid != AZX_REG_ML_LEPTR_ID_SDW)
return false;
return hdac_bus_eml_is_mic_privacy_changed(sof_to_bus(sdev), alt, elid);
}
static void sof_ptl_mic_privacy_work(struct work_struct *work)
{
struct sof_intel_hda_dev *hdev = container_of(work,
struct sof_intel_hda_dev,
mic_privacy.work);
struct hdac_bus *bus = &hdev->hbus.core;
struct snd_sof_dev *sdev = dev_get_drvdata(bus->dev);
bool state;
/*
* The microphone privacy state is only available via Soundwire shim
* in PTL
* The work is only scheduled on change.
*/
state = hdac_bus_eml_get_mic_privacy_state(bus, 1,
AZX_REG_ML_LEPTR_ID_SDW);
sof_ipc4_mic_privacy_state_change(sdev, state);
}
static void sof_ptl_process_mic_privacy(struct snd_sof_dev *sdev, bool alt,
int elid)
{
struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
if (!alt || elid != AZX_REG_ML_LEPTR_ID_SDW)
return;
/*
* Schedule the work to read the microphone privacy state and send IPC
* message about the new state to the firmware
*/
schedule_work(&hdev->mic_privacy.work);
}
static void sof_ptl_set_mic_privacy(struct snd_sof_dev *sdev,
struct sof_ipc4_intel_mic_privacy_cap *caps)
{
struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata;
u32 micpvcp;
if (!caps || !caps->capabilities_length)
return;
micpvcp = caps->capabilities[0];
/* No need to set the mic privacy if it is not enabled or forced */
if (!(micpvcp & PTL_MICPVCP_DDZE_ENABLED) ||
micpvcp & PTL_MICPVCP_DDZE_FORCED)
return;
hdac_bus_eml_set_mic_privacy_mask(sof_to_bus(sdev), true,
AZX_REG_ML_LEPTR_ID_SDW,
PTL_MICPVCP_GET_SDW_MASK(micpvcp));
INIT_WORK(&hdev->mic_privacy.work, sof_ptl_mic_privacy_work);
hdev->mic_privacy.active = true;
}
int sof_ptl_set_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *dsp_ops)
{
struct sof_ipc4_fw_data *ipc4_data;
int ret;
ret = sof_lnl_set_ops(sdev, dsp_ops);
if (ret)
return ret;
ipc4_data = sdev->private;
ipc4_data->intel_configure_mic_privacy = sof_ptl_set_mic_privacy;
return 0;
};
EXPORT_SYMBOL_NS(sof_ptl_set_ops, "SND_SOC_SOF_INTEL_PTL");
const struct sof_intel_dsp_desc ptl_chip_info = {
.cores_num = 5,
.init_core_mask = BIT(0),
.host_managed_cores_mask = BIT(0),
.ipc_req = MTL_DSP_REG_HFIPCXIDR,
.ipc_req_mask = MTL_DSP_REG_HFIPCXIDR_BUSY,
.ipc_ack = MTL_DSP_REG_HFIPCXIDA,
.ipc_ack_mask = MTL_DSP_REG_HFIPCXIDA_DONE,
.ipc_ctl = MTL_DSP_REG_HFIPCXCTL,
.rom_status_reg = LNL_DSP_REG_HFDSC,
.rom_init_timeout = 300,
.ssp_count = MTL_SSP_COUNT,
.d0i3_offset = MTL_HDA_VS_D0I3C,
.read_sdw_lcount = hda_sdw_check_lcount_ext,
.check_sdw_irq = lnl_dsp_check_sdw_irq,
.check_sdw_wakeen_irq = lnl_sdw_check_wakeen_irq,
.sdw_process_wakeen = hda_sdw_process_wakeen_common,
.check_ipc_irq = mtl_dsp_check_ipc_irq,
.check_mic_privacy_irq = sof_ptl_check_mic_privacy_irq,
.process_mic_privacy = sof_ptl_process_mic_privacy,
.cl_init = mtl_dsp_cl_init,
.power_down_dsp = mtl_power_down_dsp,
.disable_interrupts = lnl_dsp_disable_interrupts,
.hw_ip_version = SOF_INTEL_ACE_3_0,
.platform = "ptl",
};
const struct sof_intel_dsp_desc wcl_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 = LNL_DSP_REG_HFDSC,
.rom_init_timeout = 300,
.ssp_count = MTL_SSP_COUNT,
.d0i3_offset = MTL_HDA_VS_D0I3C,
.read_sdw_lcount = hda_sdw_check_lcount_ext,
.check_sdw_irq = lnl_dsp_check_sdw_irq,
.check_sdw_wakeen_irq = lnl_sdw_check_wakeen_irq,
.sdw_process_wakeen = hda_sdw_process_wakeen_common,
.check_ipc_irq = mtl_dsp_check_ipc_irq,
.cl_init = mtl_dsp_cl_init,
.power_down_dsp = mtl_power_down_dsp,
.disable_interrupts = lnl_dsp_disable_interrupts,
.hw_ip_version = SOF_INTEL_ACE_3_0,
.platform = "wcl",
};
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_MTL");
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_LNL");
MODULE_IMPORT_NS("SND_SOC_SOF_HDA_MLINK");
+19
View File
@@ -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) 2025 Intel Corporation
*/
#ifndef __SOF_INTEL_PTL_H
#define __SOF_INTEL_PTL_H
#define PTL_MICPVCP_DDZE_FORCED BIT(16)
#define PTL_MICPVCP_DDZE_ENABLED BIT(17)
#define PTL_MICPVCP_DDZLS_SDW GENMASK(26, 20)
#define PTL_MICPVCP_GET_SDW_MASK(x) (((x) & PTL_MICPVCP_DDZLS_SDW) >> 20)
int sof_ptl_set_ops(struct snd_sof_dev *sdev, struct snd_sof_dsp_ops *dsp_ops);
#endif /* __SOF_INTEL_PTL_H */
+220
View File
@@ -0,0 +1,220 @@
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
/*
* This file is provided under a dual BSD/GPLv2 license. When using or
* redistributing this file, you may do so under either license.
*
* Copyright(c) 2017 Intel Corporation
*
* Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
*/
#ifndef __SOF_INTEL_SHIM_H
#define __SOF_INTEL_SHIM_H
enum sof_intel_hw_ip_version {
SOF_INTEL_TANGIER,
SOF_INTEL_BAYTRAIL,
SOF_INTEL_BROADWELL,
SOF_INTEL_CAVS_1_5, /* SkyLake, KabyLake, AmberLake */
SOF_INTEL_CAVS_1_5_PLUS,/* ApolloLake, GeminiLake */
SOF_INTEL_CAVS_1_8, /* CannonLake, CometLake, CoffeeLake */
SOF_INTEL_CAVS_2_0, /* IceLake, JasperLake */
SOF_INTEL_CAVS_2_5, /* TigerLake, AlderLake */
SOF_INTEL_ACE_1_0, /* MeteorLake */
SOF_INTEL_ACE_2_0, /* LunarLake */
SOF_INTEL_ACE_3_0, /* PantherLake */
SOF_INTEL_ACE_4_0, /* NovaLake */
};
/*
* SHIM registers for BYT, BSW, CHT, BDW
*/
#define SHIM_CSR (SHIM_OFFSET + 0x00)
#define SHIM_PISR (SHIM_OFFSET + 0x08)
#define SHIM_PIMR (SHIM_OFFSET + 0x10)
#define SHIM_ISRX (SHIM_OFFSET + 0x18)
#define SHIM_ISRD (SHIM_OFFSET + 0x20)
#define SHIM_IMRX (SHIM_OFFSET + 0x28)
#define SHIM_IMRD (SHIM_OFFSET + 0x30)
#define SHIM_IPCX (SHIM_OFFSET + 0x38)
#define SHIM_IPCD (SHIM_OFFSET + 0x40)
#define SHIM_ISRSC (SHIM_OFFSET + 0x48)
#define SHIM_ISRLPESC (SHIM_OFFSET + 0x50)
#define SHIM_IMRSC (SHIM_OFFSET + 0x58)
#define SHIM_IMRLPESC (SHIM_OFFSET + 0x60)
#define SHIM_IPCSC (SHIM_OFFSET + 0x68)
#define SHIM_IPCLPESC (SHIM_OFFSET + 0x70)
#define SHIM_CLKCTL (SHIM_OFFSET + 0x78)
#define SHIM_CSR2 (SHIM_OFFSET + 0x80)
#define SHIM_LTRC (SHIM_OFFSET + 0xE0)
#define SHIM_HMDC (SHIM_OFFSET + 0xE8)
#define SHIM_PWMCTRL 0x1000
/*
* SST SHIM register bits for BYT, BSW, CHT, BDW
* Register bit naming and functionaility can differ between devices.
*/
/* CSR / CS */
#define SHIM_CSR_RST BIT(1)
#define SHIM_CSR_SBCS0 BIT(2)
#define SHIM_CSR_SBCS1 BIT(3)
#define SHIM_CSR_DCS(x) ((x) << 4)
#define SHIM_CSR_DCS_MASK (0x7 << 4)
#define SHIM_CSR_STALL BIT(10)
#define SHIM_CSR_S0IOCS BIT(21)
#define SHIM_CSR_S1IOCS BIT(23)
#define SHIM_CSR_LPCS BIT(31)
#define SHIM_CSR_24MHZ_LPCS \
(SHIM_CSR_SBCS0 | SHIM_CSR_SBCS1 | SHIM_CSR_LPCS)
#define SHIM_CSR_24MHZ_NO_LPCS (SHIM_CSR_SBCS0 | SHIM_CSR_SBCS1)
#define SHIM_BYT_CSR_RST BIT(0)
#define SHIM_BYT_CSR_VECTOR_SEL BIT(1)
#define SHIM_BYT_CSR_STALL BIT(2)
#define SHIM_BYT_CSR_PWAITMODE BIT(3)
/* ISRX / ISC */
#define SHIM_ISRX_BUSY BIT(1)
#define SHIM_ISRX_DONE BIT(0)
#define SHIM_BYT_ISRX_REQUEST BIT(1)
/* ISRD / ISD */
#define SHIM_ISRD_BUSY BIT(1)
#define SHIM_ISRD_DONE BIT(0)
/* IMRX / IMC */
#define SHIM_IMRX_BUSY BIT(1)
#define SHIM_IMRX_DONE BIT(0)
#define SHIM_BYT_IMRX_REQUEST BIT(1)
/* IMRD / IMD */
#define SHIM_IMRD_DONE BIT(0)
#define SHIM_IMRD_BUSY BIT(1)
#define SHIM_IMRD_SSP0 BIT(16)
#define SHIM_IMRD_DMAC0 BIT(21)
#define SHIM_IMRD_DMAC1 BIT(22)
#define SHIM_IMRD_DMAC (SHIM_IMRD_DMAC0 | SHIM_IMRD_DMAC1)
/* IPCX / IPCC */
#define SHIM_IPCX_DONE BIT(30)
#define SHIM_IPCX_BUSY BIT(31)
#define SHIM_BYT_IPCX_DONE BIT_ULL(62)
#define SHIM_BYT_IPCX_BUSY BIT_ULL(63)
/* IPCD */
#define SHIM_IPCD_DONE BIT(30)
#define SHIM_IPCD_BUSY BIT(31)
#define SHIM_BYT_IPCD_DONE BIT_ULL(62)
#define SHIM_BYT_IPCD_BUSY BIT_ULL(63)
/* CLKCTL */
#define SHIM_CLKCTL_SMOS(x) ((x) << 24)
#define SHIM_CLKCTL_MASK (3 << 24)
#define SHIM_CLKCTL_DCPLCG BIT(18)
#define SHIM_CLKCTL_SCOE1 BIT(17)
#define SHIM_CLKCTL_SCOE0 BIT(16)
/* CSR2 / CS2 */
#define SHIM_CSR2_SDFD_SSP0 BIT(1)
#define SHIM_CSR2_SDFD_SSP1 BIT(2)
/* LTRC */
#define SHIM_LTRC_VAL(x) ((x) << 0)
/* HMDC */
#define SHIM_HMDC_HDDA0(x) ((x) << 0)
#define SHIM_HMDC_HDDA1(x) ((x) << 7)
#define SHIM_HMDC_HDDA_E0_CH0 1
#define SHIM_HMDC_HDDA_E0_CH1 2
#define SHIM_HMDC_HDDA_E0_CH2 4
#define SHIM_HMDC_HDDA_E0_CH3 8
#define SHIM_HMDC_HDDA_E1_CH0 SHIM_HMDC_HDDA1(SHIM_HMDC_HDDA_E0_CH0)
#define SHIM_HMDC_HDDA_E1_CH1 SHIM_HMDC_HDDA1(SHIM_HMDC_HDDA_E0_CH1)
#define SHIM_HMDC_HDDA_E1_CH2 SHIM_HMDC_HDDA1(SHIM_HMDC_HDDA_E0_CH2)
#define SHIM_HMDC_HDDA_E1_CH3 SHIM_HMDC_HDDA1(SHIM_HMDC_HDDA_E0_CH3)
#define SHIM_HMDC_HDDA_E0_ALLCH \
(SHIM_HMDC_HDDA_E0_CH0 | SHIM_HMDC_HDDA_E0_CH1 | \
SHIM_HMDC_HDDA_E0_CH2 | SHIM_HMDC_HDDA_E0_CH3)
#define SHIM_HMDC_HDDA_E1_ALLCH \
(SHIM_HMDC_HDDA_E1_CH0 | SHIM_HMDC_HDDA_E1_CH1 | \
SHIM_HMDC_HDDA_E1_CH2 | SHIM_HMDC_HDDA_E1_CH3)
/* Audio DSP PCI registers */
#define PCI_VDRTCTL0 0xa0
#define PCI_VDRTCTL1 0xa4
#define PCI_VDRTCTL2 0xa8
#define PCI_VDRTCTL3 0xaC
/* VDRTCTL0 */
#define PCI_VDRTCL0_D3PGD BIT(0)
#define PCI_VDRTCL0_D3SRAMPGD BIT(1)
#define PCI_VDRTCL0_DSRAMPGE_SHIFT 12
#define PCI_VDRTCL0_DSRAMPGE_MASK GENMASK(PCI_VDRTCL0_DSRAMPGE_SHIFT + 19,\
PCI_VDRTCL0_DSRAMPGE_SHIFT)
#define PCI_VDRTCL0_ISRAMPGE_SHIFT 2
#define PCI_VDRTCL0_ISRAMPGE_MASK GENMASK(PCI_VDRTCL0_ISRAMPGE_SHIFT + 9,\
PCI_VDRTCL0_ISRAMPGE_SHIFT)
/* VDRTCTL2 */
#define PCI_VDRTCL2_DCLCGE BIT(1)
#define PCI_VDRTCL2_DTCGE BIT(10)
#define PCI_VDRTCL2_APLLSE_MASK BIT(31)
/* PMCS */
#define PCI_PMCS 0x84
#define PCI_PMCS_PS_MASK 0x3
/* Intel quirks */
#define SOF_INTEL_PROCEN_FMT_QUIRK BIT(0)
/* DSP hardware descriptor */
struct sof_intel_dsp_desc {
int cores_num;
int host_managed_cores_mask;
int init_core_mask; /* cores available after fw boot */
int ipc_req;
int ipc_req_mask;
int ipc_ack;
int ipc_ack_mask;
int ipc_ctl;
int rom_status_reg;
int rom_init_timeout;
int ssp_count; /* ssp count of the platform */
int ssp_base_offset; /* base address of the SSPs */
u32 sdw_shim_base;
u32 sdw_alh_base;
u32 d0i3_offset;
u32 quirks;
const char *platform;
enum sof_intel_hw_ip_version hw_ip_version;
int (*read_sdw_lcount)(struct snd_sof_dev *sdev);
void (*enable_sdw_irq)(struct snd_sof_dev *sdev, bool enable);
bool (*check_sdw_irq)(struct snd_sof_dev *sdev);
bool (*check_sdw_wakeen_irq)(struct snd_sof_dev *sdev);
void (*sdw_process_wakeen)(struct snd_sof_dev *sdev);
bool (*check_ipc_irq)(struct snd_sof_dev *sdev);
bool (*check_mic_privacy_irq)(struct snd_sof_dev *sdev, bool alt, int elid);
void (*process_mic_privacy)(struct snd_sof_dev *sdev, bool alt, int elid);
int (*power_down_dsp)(struct snd_sof_dev *sdev);
int (*disable_interrupts)(struct snd_sof_dev *sdev);
int (*cl_init)(struct snd_sof_dev *sdev, int stream_tag, bool imr_boot);
};
extern const struct snd_sof_dsp_ops sof_tng_ops;
extern const struct sof_intel_dsp_desc tng_chip_info;
struct sof_intel_stream {
size_t posn_offset;
};
static inline const struct sof_intel_dsp_desc *get_chip_info(struct snd_sof_pdata *pdata)
{
const struct sof_dev_desc *desc = pdata->desc;
return desc->chip_info;
}
#endif
+118
View File
@@ -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
//
/*
* Hardware interface for audio DSP on Skylake and Kabylake.
*/
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/firmware.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/pci.h>
#include <sound/hdaudio_ext.h>
#include <sound/pcm_params.h>
#include <sound/sof.h>
#include <sound/sof/ext_manifest4.h>
#include "../sof-priv.h"
#include "../ipc4-priv.h"
#include "../ops.h"
#include "hda.h"
#include "../sof-audio.h"
#define SRAM_MEMORY_WINDOW_BASE 0x8000
static const __maybe_unused struct snd_sof_debugfs_map skl_dsp_debugfs[] = {
{"hda", HDA_DSP_HDA_BAR, 0, 0x4000},
{"pp", HDA_DSP_PP_BAR, 0, 0x1000},
{"dsp", HDA_DSP_BAR, 0, 0x10000},
};
static int skl_dsp_ipc_get_window_offset(struct snd_sof_dev *sdev, u32 id)
{
return SRAM_MEMORY_WINDOW_BASE + (0x2000 * id);
}
static int skl_dsp_ipc_get_mailbox_offset(struct snd_sof_dev *sdev)
{
return SRAM_MEMORY_WINDOW_BASE + 0x1000;
}
/* skylake ops */
struct snd_sof_dsp_ops sof_skl_ops;
EXPORT_SYMBOL_NS(sof_skl_ops, "SND_SOC_SOF_INTEL_HDA_COMMON");
int sof_skl_ops_init(struct snd_sof_dev *sdev)
{
struct sof_ipc4_fw_data *ipc4_data;
/* common defaults */
memcpy(&sof_skl_ops, &sof_hda_common_ops, sizeof(struct snd_sof_dsp_ops));
/* probe/remove/shutdown */
sof_skl_ops.shutdown = hda_dsp_shutdown;
sdev->private = kzalloc_obj(*ipc4_data);
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,
.platform = "skl",
};
EXPORT_SYMBOL_NS(skl_chip_info, "SND_SOC_SOF_INTEL_HDA_COMMON");
@@ -0,0 +1,96 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2023 Intel Corporation
/* telemetry data queried from debug window */
#include <sound/sof/ipc4/header.h>
#include <sound/sof/xtensa.h>
#include "../ipc4-priv.h"
#include "../sof-priv.h"
#include "hda.h"
#include "telemetry.h"
void sof_ipc4_intel_dump_telemetry_state(struct snd_sof_dev *sdev, u32 flags)
{
static const char invalid_slot_msg[] = "Core dump is not available due to";
struct sof_ipc4_telemetry_slot_data *telemetry_data;
struct sof_ipc_dsp_oops_xtensa *xoops;
struct xtensa_arch_block *block;
u32 slot_offset;
char *level;
level = (flags & SOF_DBG_DUMP_OPTIONAL) ? KERN_DEBUG : KERN_ERR;
slot_offset = sof_ipc4_find_debug_slot_offset_by_type(sdev, SOF_IPC4_DEBUG_SLOT_TELEMETRY);
if (!slot_offset)
return;
telemetry_data = kmalloc_obj(*telemetry_data);
if (!telemetry_data)
return;
sof_mailbox_read(sdev, slot_offset, telemetry_data, sizeof(*telemetry_data));
if (telemetry_data->separator != XTENSA_CORE_DUMP_SEPARATOR) {
dev_err(sdev->dev, "%s invalid separator %#x\n", invalid_slot_msg,
telemetry_data->separator);
goto free_telemetry_data;
}
block = kmalloc_obj(*block);
if (!block)
goto free_telemetry_data;
sof_mailbox_read(sdev, slot_offset + sizeof(*telemetry_data), block, sizeof(*block));
if (block->soc != XTENSA_SOC_INTEL_ADSP) {
dev_err(sdev->dev, "%s invalid SOC %d\n", invalid_slot_msg, block->soc);
goto free_block;
}
if (telemetry_data->hdr.id[0] != COREDUMP_HDR_ID0 ||
telemetry_data->hdr.id[1] != COREDUMP_HDR_ID1 ||
telemetry_data->arch_hdr.id != COREDUMP_ARCH_HDR_ID) {
dev_err(sdev->dev, "%s invalid coredump header %c%c, arch hdr %c\n",
invalid_slot_msg, telemetry_data->hdr.id[0],
telemetry_data->hdr.id[1],
telemetry_data->arch_hdr.id);
goto free_block;
}
switch (block->toolchain) {
case XTENSA_TOOL_CHAIN_ZEPHYR:
dev_printk(level, sdev->dev, "FW is built with Zephyr toolchain\n");
break;
case XTENSA_TOOL_CHAIN_XCC:
dev_printk(level, sdev->dev, "FW is built with XCC toolchain\n");
break;
default:
dev_printk(level, sdev->dev, "Unknown toolchain is used\n");
break;
}
xoops = kzalloc_flex(*xoops, ar, XTENSA_CORE_AR_REGS_COUNT);
if (!xoops)
goto free_block;
xoops->exccause = block->exccause;
xoops->excvaddr = block->excvaddr;
xoops->epc1 = block->pc;
xoops->ps = block->ps;
xoops->sar = block->sar;
xoops->plat_hdr.numaregs = XTENSA_CORE_AR_REGS_COUNT;
memcpy((void *)xoops->ar, block->ar, XTENSA_CORE_AR_REGS_COUNT * sizeof(u32));
sof_oops(sdev, level, xoops);
sof_stack(sdev, level, xoops, NULL, 0);
kfree(xoops);
free_block:
kfree(block);
free_telemetry_data:
kfree(telemetry_data);
}
EXPORT_SYMBOL_NS(sof_ipc4_intel_dump_telemetry_state, "SND_SOC_SOF_INTEL_HDA_COMMON");
@@ -0,0 +1,35 @@
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
/*
* This file is provided under a dual BSD/GPLv2 license. When using or
* redistributing this file, you may do so under either license.
*
* Copyright(c) 2023 Intel Corporation
*
* telemetry data in debug windows
*/
#ifndef _SOF_INTEL_TELEMETRY_H
#define _SOF_INTEL_TELEMETRY_H
#include "../ipc4-telemetry.h"
struct xtensa_arch_block {
u8 soc; /* should be equal to XTENSA_SOC_INTEL_ADSP */
u16 version;
u8 toolchain; /* ZEPHYR or XCC */
u32 pc;
u32 exccause;
u32 excvaddr;
u32 sar;
u32 ps;
u32 scompare1;
u32 ar[XTENSA_CORE_AR_REGS_COUNT];
u32 lbeg;
u32 lend;
u32 lcount;
} __packed;
void sof_ipc4_intel_dump_telemetry_state(struct snd_sof_dev *sdev, u32 flags);
#endif /* _SOF_INTEL_TELEMETRY_H */
+256
View File
@@ -0,0 +1,256 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// Copyright(c) 2020 Intel Corporation
//
// Authors: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
//
/*
* Hardware interface for audio DSP on Tigerlake.
*/
#include <sound/sof/ext_manifest4.h>
#include "../ipc4-priv.h"
#include "../ops.h"
#include "hda.h"
#include "hda-ipc.h"
#include "../sof-audio.h"
static const struct snd_sof_debugfs_map tgl_dsp_debugfs[] = {
{"hda", HDA_DSP_HDA_BAR, 0, 0x4000, SOF_DEBUGFS_ACCESS_ALWAYS},
{"pp", HDA_DSP_PP_BAR, 0, 0x1000, SOF_DEBUGFS_ACCESS_ALWAYS},
{"dsp", HDA_DSP_BAR, 0, 0x10000, SOF_DEBUGFS_ACCESS_ALWAYS},
};
static const struct snd_sof_debugfs_map tgl_ipc4_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},
{"fw_regs", HDA_DSP_BAR, SRAM_WINDOW_OFFSET(0), 0x1000, SOF_DEBUGFS_ACCESS_D0_ONLY},
};
static int tgl_dsp_core_get(struct snd_sof_dev *sdev, int core)
{
const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm;
/* power up primary core if not already powered up and return */
if (core == SOF_DSP_PRIMARY_CORE)
return hda_dsp_enable_core(sdev, BIT(core));
if (pm_ops->set_core_state)
return pm_ops->set_core_state(sdev, core, true);
return 0;
}
static int tgl_dsp_core_put(struct snd_sof_dev *sdev, int core)
{
const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm;
int ret;
if (pm_ops->set_core_state) {
ret = pm_ops->set_core_state(sdev, core, false);
if (ret < 0)
return ret;
}
/* power down primary core and return */
if (core == SOF_DSP_PRIMARY_CORE)
return hda_dsp_core_reset_power_down(sdev, BIT(core));
return 0;
}
/* Tigerlake ops */
struct snd_sof_dsp_ops sof_tgl_ops;
int sof_tgl_ops_init(struct snd_sof_dev *sdev)
{
/* common defaults */
memcpy(&sof_tgl_ops, &sof_hda_common_ops, sizeof(struct snd_sof_dsp_ops));
/* probe/remove/shutdown */
sof_tgl_ops.shutdown = hda_dsp_shutdown_dma_flush;
if (sdev->pdata->ipc_type == SOF_IPC_TYPE_3) {
/* doorbell */
sof_tgl_ops.irq_thread = cnl_ipc_irq_thread;
/* ipc */
sof_tgl_ops.send_msg = cnl_ipc_send_msg;
/* debug */
sof_tgl_ops.ipc_dump = cnl_ipc_dump;
sof_tgl_ops.debug_map = tgl_dsp_debugfs;
sof_tgl_ops.debug_map_count = ARRAY_SIZE(tgl_dsp_debugfs);
sof_tgl_ops.set_power_state = hda_dsp_set_power_state_ipc3;
}
if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4) {
struct sof_ipc4_fw_data *ipc4_data;
sdev->private = kzalloc_obj(*ipc4_data);
if (!sdev->private)
return -ENOMEM;
ipc4_data = sdev->private;
ipc4_data->manifest_fw_hdr_offset = SOF_MAN4_FW_HDR_OFFSET;
ipc4_data->mtrace_type = SOF_IPC4_MTRACE_INTEL_CAVS_2;
ipc4_data->fw_context_save = true;
/* External library loading support */
ipc4_data->load_library = hda_dsp_ipc4_load_library;
/* doorbell */
sof_tgl_ops.irq_thread = cnl_ipc4_irq_thread;
/* ipc */
sof_tgl_ops.send_msg = cnl_ipc4_send_msg;
/* debug */
sof_tgl_ops.ipc_dump = cnl_ipc4_dump;
sof_tgl_ops.dbg_dump = hda_ipc4_dsp_dump;
sof_tgl_ops.debug_map = tgl_ipc4_dsp_debugfs;
sof_tgl_ops.debug_map_count = ARRAY_SIZE(tgl_ipc4_dsp_debugfs);
sof_tgl_ops.set_power_state = hda_dsp_set_power_state_ipc4;
}
/* set DAI driver ops */
hda_set_dai_drv_ops(sdev, &sof_tgl_ops);
/* pre/post fw run */
sof_tgl_ops.post_fw_run = hda_dsp_post_fw_run;
/* firmware run */
sof_tgl_ops.run = hda_dsp_cl_boot_firmware_iccmax;
/* dsp core get/put */
sof_tgl_ops.core_get = tgl_dsp_core_get;
sof_tgl_ops.core_put = tgl_dsp_core_put;
return 0;
};
const struct sof_intel_dsp_desc tgl_chip_info = {
/* Tigerlake , Alderlake */
.cores_num = 4,
.init_core_mask = 1,
.host_managed_cores_mask = BIT(0),
.ipc_req = CNL_DSP_REG_HIPCIDR,
.ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY,
.ipc_ack = CNL_DSP_REG_HIPCIDA,
.ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE,
.ipc_ctl = CNL_DSP_REG_HIPCCTL,
.rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS,
.rom_init_timeout = 300,
.ssp_count = TGL_SSP_COUNT,
.ssp_base_offset = CNL_SSP_BASE_OFFSET,
.sdw_shim_base = SDW_SHIM_BASE,
.sdw_alh_base = SDW_ALH_BASE,
.d0i3_offset = SOF_HDA_VS_D0I3C,
.read_sdw_lcount = hda_sdw_check_lcount_common,
.enable_sdw_irq = hda_common_enable_sdw_irq,
.check_sdw_irq = hda_common_check_sdw_irq,
.check_sdw_wakeen_irq = hda_sdw_check_wakeen_irq_common,
.sdw_process_wakeen = hda_sdw_process_wakeen_common,
.check_ipc_irq = hda_dsp_check_ipc_irq,
.cl_init = cl_dsp_init,
.power_down_dsp = hda_power_down_dsp,
.disable_interrupts = hda_dsp_disable_interrupts,
.hw_ip_version = SOF_INTEL_CAVS_2_5,
.platform = "tgl",
};
const struct sof_intel_dsp_desc tglh_chip_info = {
/* Tigerlake-H */
.cores_num = 2,
.init_core_mask = 1,
.host_managed_cores_mask = BIT(0),
.ipc_req = CNL_DSP_REG_HIPCIDR,
.ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY,
.ipc_ack = CNL_DSP_REG_HIPCIDA,
.ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE,
.ipc_ctl = CNL_DSP_REG_HIPCCTL,
.rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS,
.rom_init_timeout = 300,
.ssp_count = TGL_SSP_COUNT,
.ssp_base_offset = CNL_SSP_BASE_OFFSET,
.sdw_shim_base = SDW_SHIM_BASE,
.sdw_alh_base = SDW_ALH_BASE,
.d0i3_offset = SOF_HDA_VS_D0I3C,
.read_sdw_lcount = hda_sdw_check_lcount_common,
.enable_sdw_irq = hda_common_enable_sdw_irq,
.check_sdw_irq = hda_common_check_sdw_irq,
.check_sdw_wakeen_irq = hda_sdw_check_wakeen_irq_common,
.sdw_process_wakeen = hda_sdw_process_wakeen_common,
.check_ipc_irq = hda_dsp_check_ipc_irq,
.cl_init = cl_dsp_init,
.power_down_dsp = hda_power_down_dsp,
.disable_interrupts = hda_dsp_disable_interrupts,
.hw_ip_version = SOF_INTEL_CAVS_2_5,
.platform = "tgl",
};
const struct sof_intel_dsp_desc ehl_chip_info = {
/* Elkhartlake */
.cores_num = 4,
.init_core_mask = 1,
.host_managed_cores_mask = BIT(0),
.ipc_req = CNL_DSP_REG_HIPCIDR,
.ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY,
.ipc_ack = CNL_DSP_REG_HIPCIDA,
.ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE,
.ipc_ctl = CNL_DSP_REG_HIPCCTL,
.rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS,
.rom_init_timeout = 300,
.ssp_count = TGL_SSP_COUNT,
.ssp_base_offset = CNL_SSP_BASE_OFFSET,
.sdw_shim_base = SDW_SHIM_BASE,
.sdw_alh_base = SDW_ALH_BASE,
.d0i3_offset = SOF_HDA_VS_D0I3C,
.read_sdw_lcount = hda_sdw_check_lcount_common,
.enable_sdw_irq = hda_common_enable_sdw_irq,
.check_sdw_irq = hda_common_check_sdw_irq,
.check_sdw_wakeen_irq = hda_sdw_check_wakeen_irq_common,
.sdw_process_wakeen = hda_sdw_process_wakeen_common,
.check_ipc_irq = hda_dsp_check_ipc_irq,
.cl_init = cl_dsp_init,
.power_down_dsp = hda_power_down_dsp,
.disable_interrupts = hda_dsp_disable_interrupts,
.hw_ip_version = SOF_INTEL_CAVS_2_5,
.platform = "ehl",
};
const struct sof_intel_dsp_desc adls_chip_info = {
/* Alderlake-S */
.cores_num = 2,
.init_core_mask = BIT(0),
.host_managed_cores_mask = BIT(0),
.ipc_req = CNL_DSP_REG_HIPCIDR,
.ipc_req_mask = CNL_DSP_REG_HIPCIDR_BUSY,
.ipc_ack = CNL_DSP_REG_HIPCIDA,
.ipc_ack_mask = CNL_DSP_REG_HIPCIDA_DONE,
.ipc_ctl = CNL_DSP_REG_HIPCCTL,
.rom_status_reg = HDA_DSP_SRAM_REG_ROM_STATUS,
.rom_init_timeout = 300,
.ssp_count = TGL_SSP_COUNT,
.ssp_base_offset = CNL_SSP_BASE_OFFSET,
.sdw_shim_base = SDW_SHIM_BASE,
.sdw_alh_base = SDW_ALH_BASE,
.d0i3_offset = SOF_HDA_VS_D0I3C,
.read_sdw_lcount = hda_sdw_check_lcount_common,
.enable_sdw_irq = hda_common_enable_sdw_irq,
.check_sdw_irq = hda_common_check_sdw_irq,
.check_sdw_wakeen_irq = hda_sdw_check_wakeen_irq_common,
.sdw_process_wakeen = hda_sdw_process_wakeen_common,
.check_ipc_irq = hda_dsp_check_ipc_irq,
.cl_init = cl_dsp_init,
.power_down_dsp = hda_power_down_dsp,
.disable_interrupts = hda_dsp_disable_interrupts,
.hw_ip_version = SOF_INTEL_CAVS_2_5,
.platform = "adl",
};
@@ -0,0 +1,5 @@
// SPDX-License-Identifier: GPL-2.0
#define CREATE_TRACE_POINTS
#include <trace/events/sof_intel.h>
EXPORT_TRACEPOINT_SYMBOL(sof_intel_hda_irq);