// 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 <sound/hda_register.h>
#include <linux/acpi.h>
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_intel.h>
#include <sound/intel-dsp-config.h>
#include <sound/intel-nhlt.h>
#include <sound/soc-acpi-intel-ssp-common.h>
#include <sound/sof.h>
#include <sound/sof/xtensa.h>
#include <sound/hda-mlink.h>
#include "../sof-audio.h"
#include "../sof-pci-dev.h"
#include "../ops.h"
#include "../ipc4-topology.h"
#include "hda.h"
#include <trace/events/sof_intel.h>
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
#include <sound/soc-acpi-intel-match.h>
#endif
/* platform specific devices */
#include "shim.h"
#if IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE)
/*
* The default for SoundWire clock stop quirks is to power gate the IP
* and do a Bus Reset, this will need to be modified when the DSP
* needs to remain in D0i3 so that the Master does not lose context
* and enumeration is not required on clock restart
*/
static int sdw_clock_stop_quirks = SDW_INTEL_CLK_STOP_BUS_RESET;
module_param(sdw_clock_stop_quirks, int, 0444);
MODULE_PARM_DESC(sdw_clock_stop_quirks, "SOF SoundWire clock stop quirks");
static int sdw_params_stream(struct device *dev,
struct sdw_intel_stream_params_data *params_data)
{
struct snd_soc_dai *d = params_data->dai;
struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(d, params_data->substream->stream);
struct snd_sof_dai_config_data data = { 0 };
if (!w) {
dev_err(dev, "%s widget not found, check amp link num in the topology\n",
d->name);
return -EINVAL;
}
data.dai_index = (params_data->link_id << 8) | d->id;
data.dai_data = params_data->alh_stream_id;
data.dai_node_id = data.dai_data;
return hda_dai_config(w, SOF_DAI_CONFIG_FLAGS_HW_PARAMS, &data);
}
static int sdw_params_free(struct device *dev, struct sdw_intel_stream_free_data *free_data)
{
struct snd_soc_dai *d = free_data->dai;
struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(d, free_data->substream->stream);
struct snd_sof_dev *sdev = widget_to_sdev(w);
if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4) {
struct snd_sof_widget *swidget = w->dobj.private;
struct snd_sof_dai *dai = swidget->private;
struct sof_ipc4_copier_data *copier_data;
struct sof_ipc4_copier *ipc4_copier;
ipc4_copier = dai->private;
ipc4_copier->dai_index = 0;
copier_data = &ipc4_copier->data;
/* clear the node ID */
copier_data->gtw_cfg.node_id &= ~SOF_IPC4_NODE_INDEX_MASK;
}
return 0;
}
struct sdw_intel_ops sdw_callback = {
.params_stream = sdw_params_stream,
.free_stream = sdw_params_free,
};
static int sdw_ace2x_params_stream(struct device *dev,
struct sdw_intel_stream_params_data *params_data)
{
return sdw_hda_dai_hw_params(params_data->substream,
params_data->hw_params,
params_data->dai,
params_data->link_id,
params_data->alh_stream_id);
}
static int sdw_ace2x_free_stream(struct device *dev,
struct sdw_intel_stream_free_data *free_data)
{
return sdw_hda_dai_hw_free(free_data->substream,
free_data->dai,
free_data->link_id);
}
static int sdw_ace2x_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai)
{
return sdw_hda_dai_trigger(substream, cmd, dai);
}
static struct sdw_intel_ops sdw_ace2x_callback = {
.params_stream = sdw_ace2x_params_stream,
.free_stream = sdw_ace2x_free_stream,
.trigger = sdw_ace2x_trigger,
};
static int hda_sdw_acpi_scan(struct snd_sof_dev *sdev)
{
u32 interface_mask = hda_get_interface_mask(sdev);
struct sof_intel_hda_dev *hdev;
acpi_handle handle;
int ret;
if (!(interface_mask & BIT(SOF_DAI_INTEL_ALH)))
return -EINVAL;
handle = ACPI_HANDLE(sdev->dev);
/* save ACPI info for the probe step */
hdev = sdev->pdata->hw_pdata;
ret = sdw_intel_acpi_scan(handle, &hdev->info);
if (ret < 0)
return -EINVAL;
return 0;
}
static int hda_sdw_probe(struct snd_sof_dev *sdev)
{
const struct sof_intel_dsp_desc *chip;
struct sof_intel_hda_dev *hdev;
struct sdw_intel_res res;
void *sdw;
hdev = sdev->pdata->hw_pdata;
memset(&res, 0, sizeof(res));
chip = get_chip_info(sdev->pdata);
if (chip->hw_ip_version < SOF_INTEL_ACE_2_0) {
res.mmio_base = sdev->bar[HDA_DSP_BAR];
res.hw_ops = &sdw_intel_cnl_hw_ops;
res.shim_base = hdev->desc->sdw_shim_base;
res.alh_base = hdev->desc->sdw_alh_base;
res.ext = false;
res.ops = &sdw_callback;
} else {
/*
* retrieve eml_lock needed to protect shared registers
* in the HDaudio multi-link areas
*/
res.eml_lock = hdac_bus_eml_get_mutex(sof_to_bus(sdev), true,
AZX_REG_ML_LEPTR_ID_SDW);
if (!res.eml_lock)
return -ENODEV;
res.mmio_base = sdev->bar[HDA_DSP_HDA_BAR];
/*
* the SHIM and SoundWire register offsets are link-specific
* and will be determined when adding auxiliary devices
*/
res.hw_ops = &sdw_intel_lnl_hw_ops;
res.ext = true;
res.ops = &sdw_ace2x_callback;
/* ACE3+ supports microphone privacy */
if (chip->hw_ip_version >= SOF_INTEL_ACE_3_0)
res.mic_privacy = true;
}
res.irq = sdev->ipc_irq;
res.handle = hdev->info.handle;
res.parent = sdev->dev;
res.dev = sdev->dev;
res.clock_stop_quirks = sdw_clock_stop_quirks;
res.hbus = sof_to_bus(sdev);
/*
* ops and arg fields are not populated for now,
* they will be needed when the DAI callbacks are
* provided
*/
/* we could filter links here if needed, e.g for quirks */
res.count = hdev->info.count;
res.link_mask = hdev->info.link_mask;
sdw = sdw_intel_probe(&res);
if (!sdw) {
dev_err(sdev->dev, "error: SoundWire probe failed\n");
return -EINVAL;
}
/* save context */
hdev->sdw = sdw;
return 0;
}
int hda_sdw_startup(struct snd_sof_dev *sdev)
{
struct sof_intel_hda_dev *hdev;
struct snd_sof_pdata *pdata = sdev->pdata;
int ret;
hdev = sdev->pdata->hw_pdata;
if (!hdev->sdw)
return 0;
if (pdata->machine && !pdata->machine->mach_params.link_mask)
return 0;
ret = hda_sdw_check_lcount(sdev);
if (ret < 0)
return ret;
return sdw_intel_startup(hdev->sdw);
}
EXPORT_SYMBOL_NS(hda_sdw_startup, "SND_SOC_SOF_INTEL_HDA_GENERIC");
static int hda_sdw_exit(struct snd_sof_dev *sdev)
{
struct sof_intel_hda_dev *hdev;
hdev = sdev->pdata->hw_pdata;
if (hdev->sdw)
sdw_intel_exit(hdev->sdw);
hdev->sdw = NULL;
hda_sdw_int_enable(sdev, false);
return 0;
}
bool hda_common_check_sdw_irq(struct snd_sof_dev *sdev)
{
struct sof_intel_hda_dev *hdev;
bool ret = false;
u32 irq_status;
hdev = sdev->pdata->hw_pdata;
if (!hdev->sdw)
return ret;
/* store status
|