// 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: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
//
#include <linux/bitfield.h>
#include <trace/events/sof.h>
#include "sof-audio.h"
#include "ops.h"
static bool is_virtual_widget(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget,
const char *func)
{
switch (widget->id) {
case snd_soc_dapm_out_drv:
case snd_soc_dapm_output:
case snd_soc_dapm_input:
dev_dbg(sdev->dev, "%s: %s is a virtual widget\n", func, widget->name);
return true;
default:
return false;
}
}
static void sof_reset_route_setup_status(struct snd_sof_dev *sdev, struct snd_sof_widget *widget)
{
const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
struct snd_sof_route *sroute;
list_for_each_entry(sroute, &sdev->route_list, list)
if (sroute->src_widget == widget || sroute->sink_widget == widget) {
if (sroute->setup && tplg_ops && tplg_ops->route_free)
tplg_ops->route_free(sdev, sroute);
sroute->setup = false;
}
}
static int sof_widget_free_unlocked(struct snd_sof_dev *sdev,
struct snd_sof_widget *swidget)
{
const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
struct snd_sof_pipeline *spipe = swidget->spipe;
int err = 0;
int ret;
if (!swidget->private)
return 0;
trace_sof_widget_free(swidget);
/* only free when use_count is 0 */
if (--swidget->use_count)
return 0;
/* reset route setup status for all routes that contain this widget */
sof_reset_route_setup_status(sdev, swidget);
/* free DAI config and continue to free widget even if it fails */
if (WIDGET_IS_DAI(swidget->id)) {
struct snd_sof_dai_config_data data;
unsigned int flags = SOF_DAI_CONFIG_FLAGS_HW_FREE;
data.dai_data = DMA_CHAN_INVALID;
if (tplg_ops && tplg_ops->dai_config) {
err = tplg_ops->dai_config(sdev, swidget, flags, &data);
if (err < 0)
dev_err(sdev->dev, "failed to free config for widget %s\n",
swidget->widget->name);
}
}
/* continue to disable core even if IPC fails */
if (tplg_ops && tplg_ops->widget_free) {
ret = tplg_ops->widget_free(sdev, swidget);
if (ret < 0 && !err)
err = ret;
}
/*
* decrement ref count for cores associated with all modules in the pipeline and clear
* the complete flag
*/
if (swidget->id == snd_soc_dapm_scheduler) {
int i;
for_each_set_bit(i, &spipe->core_mask, sdev->num_cores) {
ret = snd_sof_dsp_core_put(sdev, i);
if (ret < 0) {
dev_err(sdev->dev, "failed to disable target core: %d for pipeline %s\n",
i, swidget->widget->name);
if (!err)
err = ret;
}
}
swidget->spipe->complete = 0;
}
/*
* free the scheduler widget (same as pipe_widget) associated with the current swidget.
* skip for static pipelines
*/
if (swidget->spipe && swidget->dynamic_pipeline_widget &&
swidget->id != snd_soc_dapm_scheduler) {
ret = sof_widget_free_unlocked(sdev, swidget->spipe->pipe_widget);
if (ret < 0 && !err)
err = ret;
}
if (!err)
dev_dbg(sdev->dev, "widget %s freed\n", swidget->widget->name);
return err;
}
int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
{
int ret;
mutex_lock(&swidget->setup_mutex);
ret = sof_widget_free_unlocked(sdev, swidget);
mutex_unlock(&swidget->setup_mutex);
return ret;
}
EXPORT_SYMBOL(sof_widget_free);
static int sof_widget_setup_unlocked(struct snd_sof_dev *sdev,
struct snd_sof_widget *swidget)
{
const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
struct snd_sof_pipeline *spipe = swidget->spipe;
bool use_count_decremented = false;
int ret;
int i;
/* skip if there is no private data */
if (!swidget->private)
return 0;
trace_sof_widget_setup(swidget);
/* widget already set up */
if (++swidget->use_count > 1)
return 0;
/*
* The scheduler widget for a pipeline is not part of the connected DAPM
* widget list and it needs to be set up before the widgets in the pipeline
* are set up. The use_count for the scheduler widget is incremented for every
* widget in a given pipeline to ensure that it is freed only after the last
* widget in the pipeline is freed. Skip setting up scheduler widget for static pipelines.
*/
if (swidget->dynamic_pipeline_widget && swidget->id != snd_soc_dapm_scheduler) {
if (!swidget->spipe || !swidget->spipe->pipe_widget) {
dev_err(sdev->dev, "No pipeline set for %s\n", swidget->widget->name);
ret = -EINVAL;
goto use_count_dec;
}
ret = sof_widget_setup_unlocked(sdev, swidget->spipe->pipe_widget);
if (ret < 0)
goto use_count_dec;
}
/* update ref count for cores associated with all modules in the pipeline */
if (swidget->id == snd_soc_dapm_scheduler) {
for_each_set_bit(i,