linux-stable/sound/soc/sof/ipc3-topology.c

2563 lines
77 KiB
C
Raw Normal View History

// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2021 Intel Corporation. All rights reserved.
//
//
#include <uapi/sound/sof/tokens.h>
#include <sound/pcm_params.h>
#include "sof-priv.h"
#include "sof-audio.h"
#include "ipc3-priv.h"
#include "ops.h"
/* Full volume for default values */
#define VOL_ZERO_DB BIT(VOLUME_FWL)
/* size of tplg ABI in bytes */
#define SOF_IPC3_TPLG_ABI_SIZE 3
struct sof_widget_data {
int ctrl_type;
int ipc_cmd;
void *pdata;
size_t pdata_size;
struct snd_sof_control *control;
};
struct sof_process_types {
const char *name;
enum sof_ipc_process_type type;
enum sof_comp_type comp_type;
};
static const struct sof_process_types sof_process[] = {
{"EQFIR", SOF_PROCESS_EQFIR, SOF_COMP_EQ_FIR},
{"EQIIR", SOF_PROCESS_EQIIR, SOF_COMP_EQ_IIR},
{"KEYWORD_DETECT", SOF_PROCESS_KEYWORD_DETECT, SOF_COMP_KEYWORD_DETECT},
{"KPB", SOF_PROCESS_KPB, SOF_COMP_KPB},
{"CHAN_SELECTOR", SOF_PROCESS_CHAN_SELECTOR, SOF_COMP_SELECTOR},
{"MUX", SOF_PROCESS_MUX, SOF_COMP_MUX},
{"DEMUX", SOF_PROCESS_DEMUX, SOF_COMP_DEMUX},
{"DCBLOCK", SOF_PROCESS_DCBLOCK, SOF_COMP_DCBLOCK},
{"SMART_AMP", SOF_PROCESS_SMART_AMP, SOF_COMP_SMART_AMP},
};
static enum sof_ipc_process_type find_process(const char *name)
{
int i;
for (i = 0; i < ARRAY_SIZE(sof_process); i++) {
if (strcmp(name, sof_process[i].name) == 0)
return sof_process[i].type;
}
return SOF_PROCESS_NONE;
}
static int get_token_process_type(void *elem, void *object, u32 offset)
{
u32 *val = (u32 *)((u8 *)object + offset);
*val = find_process((const char *)elem);
return 0;
}
/* Buffers */
static const struct sof_topology_token buffer_tokens[] = {
{SOF_TKN_BUF_SIZE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_buffer, size)},
{SOF_TKN_BUF_CAPS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_buffer, caps)},
};
/* DAI */
static const struct sof_topology_token dai_tokens[] = {
{SOF_TKN_DAI_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_dai_type,
offsetof(struct sof_ipc_comp_dai, type)},
{SOF_TKN_DAI_INDEX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_comp_dai, dai_index)},
{SOF_TKN_DAI_DIRECTION, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_comp_dai, direction)},
};
/* BE DAI link */
static const struct sof_topology_token dai_link_tokens[] = {
{SOF_TKN_DAI_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_dai_type,
offsetof(struct sof_ipc_dai_config, type)},
{SOF_TKN_DAI_INDEX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_dai_config, dai_index)},
};
/* scheduling */
static const struct sof_topology_token sched_tokens[] = {
{SOF_TKN_SCHED_PERIOD, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_pipe_new, period)},
{SOF_TKN_SCHED_PRIORITY, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_pipe_new, priority)},
{SOF_TKN_SCHED_MIPS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_pipe_new, period_mips)},
{SOF_TKN_SCHED_CORE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_pipe_new, core)},
{SOF_TKN_SCHED_FRAMES, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_pipe_new, frames_per_sched)},
{SOF_TKN_SCHED_TIME_DOMAIN, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_pipe_new, time_domain)},
};
static const struct sof_topology_token pipeline_tokens[] = {
{SOF_TKN_SCHED_DYNAMIC_PIPELINE, SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16,
offsetof(struct snd_sof_widget, dynamic_pipeline_widget)},
};
/* volume */
static const struct sof_topology_token volume_tokens[] = {
{SOF_TKN_VOLUME_RAMP_STEP_TYPE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_comp_volume, ramp)},
{SOF_TKN_VOLUME_RAMP_STEP_MS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_comp_volume, initial_ramp)},
};
/* SRC */
static const struct sof_topology_token src_tokens[] = {
{SOF_TKN_SRC_RATE_IN, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_comp_src, source_rate)},
{SOF_TKN_SRC_RATE_OUT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_comp_src, sink_rate)},
};
/* ASRC */
static const struct sof_topology_token asrc_tokens[] = {
{SOF_TKN_ASRC_RATE_IN, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_comp_asrc, source_rate)},
{SOF_TKN_ASRC_RATE_OUT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_comp_asrc, sink_rate)},
{SOF_TKN_ASRC_ASYNCHRONOUS_MODE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_comp_asrc, asynchronous_mode)},
{SOF_TKN_ASRC_OPERATION_MODE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_comp_asrc, operation_mode)},
};
/* EFFECT */
static const struct sof_topology_token process_tokens[] = {
{SOF_TKN_PROCESS_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_process_type,
offsetof(struct sof_ipc_comp_process, type)},
};
/* PCM */
static const struct sof_topology_token pcm_tokens[] = {
{SOF_TKN_PCM_DMAC_CONFIG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_comp_host, dmac_config)},
};
/* Generic components */
static const struct sof_topology_token comp_tokens[] = {
{SOF_TKN_COMP_PERIOD_SINK_COUNT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_comp_config, periods_sink)},
{SOF_TKN_COMP_PERIOD_SOURCE_COUNT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_comp_config, periods_source)},
{SOF_TKN_COMP_FORMAT,
SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_comp_format,
offsetof(struct sof_ipc_comp_config, frame_fmt)},
};
/* SSP */
static const struct sof_topology_token ssp_tokens[] = {
{SOF_TKN_INTEL_SSP_CLKS_CONTROL, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_dai_ssp_params, clks_control)},
{SOF_TKN_INTEL_SSP_MCLK_ID, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
offsetof(struct sof_ipc_dai_ssp_params, mclk_id)},
{SOF_TKN_INTEL_SSP_SAMPLE_BITS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_dai_ssp_params, sample_valid_bits)},
{SOF_TKN_INTEL_SSP_FRAME_PULSE_WIDTH, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
offsetof(struct sof_ipc_dai_ssp_params, frame_pulse_width)},
{SOF_TKN_INTEL_SSP_QUIRKS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_dai_ssp_params, quirks)},
{SOF_TKN_INTEL_SSP_TDM_PADDING_PER_SLOT, SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16,
offsetof(struct sof_ipc_dai_ssp_params, tdm_per_slot_padding_flag)},
{SOF_TKN_INTEL_SSP_BCLK_DELAY, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_dai_ssp_params, bclk_delay)},
};
/* ALH */
static const struct sof_topology_token alh_tokens[] = {
{SOF_TKN_INTEL_ALH_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_dai_alh_params, rate)},
{SOF_TKN_INTEL_ALH_CH, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_dai_alh_params, channels)},
};
/* DMIC */
static const struct sof_topology_token dmic_tokens[] = {
{SOF_TKN_INTEL_DMIC_DRIVER_VERSION, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_dai_dmic_params, driver_ipc_version)},
{SOF_TKN_INTEL_DMIC_CLK_MIN, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_dai_dmic_params, pdmclk_min)},
{SOF_TKN_INTEL_DMIC_CLK_MAX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_dai_dmic_params, pdmclk_max)},
{SOF_TKN_INTEL_DMIC_SAMPLE_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_dai_dmic_params, fifo_fs)},
{SOF_TKN_INTEL_DMIC_DUTY_MIN, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
offsetof(struct sof_ipc_dai_dmic_params, duty_min)},
{SOF_TKN_INTEL_DMIC_DUTY_MAX, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
offsetof(struct sof_ipc_dai_dmic_params, duty_max)},
{SOF_TKN_INTEL_DMIC_NUM_PDM_ACTIVE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_dai_dmic_params, num_pdm_active)},
{SOF_TKN_INTEL_DMIC_FIFO_WORD_LENGTH, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
offsetof(struct sof_ipc_dai_dmic_params, fifo_bits)},
{SOF_TKN_INTEL_DMIC_UNMUTE_RAMP_TIME_MS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_dai_dmic_params, unmute_ramp_time)},
};
/* ESAI */
static const struct sof_topology_token esai_tokens[] = {
{SOF_TKN_IMX_ESAI_MCLK_ID, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
offsetof(struct sof_ipc_dai_esai_params, mclk_id)},
};
/* SAI */
static const struct sof_topology_token sai_tokens[] = {
{SOF_TKN_IMX_SAI_MCLK_ID, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
offsetof(struct sof_ipc_dai_sai_params, mclk_id)},
};
/*
* DMIC PDM Tokens
* SOF_TKN_INTEL_DMIC_PDM_CTRL_ID should be the first token
* as it increments the index while parsing the array of pdm tokens
* and determines the correct offset
*/
static const struct sof_topology_token dmic_pdm_tokens[] = {
{SOF_TKN_INTEL_DMIC_PDM_CTRL_ID, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, id)},
{SOF_TKN_INTEL_DMIC_PDM_MIC_A_Enable, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, enable_mic_a)},
{SOF_TKN_INTEL_DMIC_PDM_MIC_B_Enable, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, enable_mic_b)},
{SOF_TKN_INTEL_DMIC_PDM_POLARITY_A, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, polarity_mic_a)},
{SOF_TKN_INTEL_DMIC_PDM_POLARITY_B, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, polarity_mic_b)},
{SOF_TKN_INTEL_DMIC_PDM_CLK_EDGE, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, clk_edge)},
{SOF_TKN_INTEL_DMIC_PDM_SKEW, SND_SOC_TPLG_TUPLE_TYPE_SHORT, get_token_u16,
offsetof(struct sof_ipc_dai_dmic_pdm_ctrl, skew)},
};
/* HDA */
static const struct sof_topology_token hda_tokens[] = {
{SOF_TKN_INTEL_HDA_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_dai_hda_params, rate)},
{SOF_TKN_INTEL_HDA_CH, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_dai_hda_params, channels)},
};
/* AFE */
static const struct sof_topology_token afe_tokens[] = {
{SOF_TKN_MEDIATEK_AFE_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_dai_mtk_afe_params, rate)},
{SOF_TKN_MEDIATEK_AFE_CH, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_dai_mtk_afe_params, channels)},
{SOF_TKN_MEDIATEK_AFE_FORMAT, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_comp_format,
offsetof(struct sof_ipc_dai_mtk_afe_params, format)},
};
/* ACPDMIC */
static const struct sof_topology_token acpdmic_tokens[] = {
{SOF_TKN_AMD_ACPDMIC_RATE,
SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_dai_acpdmic_params, pdm_rate)},
{SOF_TKN_AMD_ACPDMIC_CH,
SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_dai_acpdmic_params, pdm_ch)},
};
/* ACPI2S */
static const struct sof_topology_token acpi2s_tokens[] = {
{SOF_TKN_AMD_ACPI2S_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_dai_acp_params, fsync_rate)},
{SOF_TKN_AMD_ACPI2S_CH, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_dai_acp_params, tdm_slots)},
{SOF_TKN_AMD_ACPI2S_TDM_MODE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_dai_acp_params, tdm_mode)},
};
/* Core tokens */
static const struct sof_topology_token core_tokens[] = {
{SOF_TKN_COMP_CORE_ID, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32,
offsetof(struct sof_ipc_comp, core)},
};
/* Component extended tokens */
static const struct sof_topology_token comp_ext_tokens[] = {
{SOF_TKN_COMP_UUID, SND_SOC_TPLG_TUPLE_TYPE_UUID, get_token_uuid,
offsetof(struct snd_sof_widget, uuid)},
};
static const struct sof_token_info ipc3_token_list[SOF_TOKEN_COUNT] = {
[SOF_PCM_TOKENS] = {"PCM tokens", pcm_tokens, ARRAY_SIZE(pcm_tokens)},
[SOF_PIPELINE_TOKENS] = {"Pipeline tokens", pipeline_tokens, ARRAY_SIZE(pipeline_tokens)},
[SOF_SCHED_TOKENS] = {"Scheduler tokens", sched_tokens, ARRAY_SIZE(sched_tokens)},
[SOF_COMP_TOKENS] = {"Comp tokens", comp_tokens, ARRAY_SIZE(comp_tokens)},
[SOF_CORE_TOKENS] = {"Core tokens", core_tokens, ARRAY_SIZE(core_tokens)},
[SOF_COMP_EXT_TOKENS] = {"AFE tokens", comp_ext_tokens, ARRAY_SIZE(comp_ext_tokens)},
[SOF_BUFFER_TOKENS] = {"Buffer tokens", buffer_tokens, ARRAY_SIZE(buffer_tokens)},
[SOF_VOLUME_TOKENS] = {"Volume tokens", volume_tokens, ARRAY_SIZE(volume_tokens)},
[SOF_SRC_TOKENS] = {"SRC tokens", src_tokens, ARRAY_SIZE(src_tokens)},
[SOF_ASRC_TOKENS] = {"ASRC tokens", asrc_tokens, ARRAY_SIZE(asrc_tokens)},
[SOF_PROCESS_TOKENS] = {"Process tokens", process_tokens, ARRAY_SIZE(process_tokens)},
[SOF_DAI_TOKENS] = {"DAI tokens", dai_tokens, ARRAY_SIZE(dai_tokens)},
[SOF_DAI_LINK_TOKENS] = {"DAI link tokens", dai_link_tokens, ARRAY_SIZE(dai_link_tokens)},
[SOF_HDA_TOKENS] = {"HDA tokens", hda_tokens, ARRAY_SIZE(hda_tokens)},
[SOF_SSP_TOKENS] = {"SSP tokens", ssp_tokens, ARRAY_SIZE(ssp_tokens)},
[SOF_ALH_TOKENS] = {"ALH tokens", alh_tokens, ARRAY_SIZE(alh_tokens)},
[SOF_DMIC_TOKENS] = {"DMIC tokens", dmic_tokens, ARRAY_SIZE(dmic_tokens)},
[SOF_DMIC_PDM_TOKENS] = {"DMIC PDM tokens", dmic_pdm_tokens, ARRAY_SIZE(dmic_pdm_tokens)},
[SOF_ESAI_TOKENS] = {"ESAI tokens", esai_tokens, ARRAY_SIZE(esai_tokens)},
[SOF_SAI_TOKENS] = {"SAI tokens", sai_tokens, ARRAY_SIZE(sai_tokens)},
[SOF_AFE_TOKENS] = {"AFE tokens", afe_tokens, ARRAY_SIZE(afe_tokens)},
[SOF_ACPDMIC_TOKENS] = {"ACPDMIC tokens", acpdmic_tokens, ARRAY_SIZE(acpdmic_tokens)},
[SOF_ACPI2S_TOKENS] = {"ACPI2S tokens", acpi2s_tokens, ARRAY_SIZE(acpi2s_tokens)},
};
/**
* sof_comp_alloc - allocate and initialize buffer for a new component
* @swidget: pointer to struct snd_sof_widget containing extended data
* @ipc_size: IPC payload size that will be updated depending on valid
* extended data.
* @index: ID of the pipeline the component belongs to
*
* Return: The pointer to the new allocated component, NULL if failed.
*/
static void *sof_comp_alloc(struct snd_sof_widget *swidget, size_t *ipc_size,
int index)
{
struct sof_ipc_comp *comp;
size_t total_size = *ipc_size;
size_t ext_size = sizeof(swidget->uuid);
/* only non-zero UUID is valid */
if (!guid_is_null(&swidget->uuid))
total_size += ext_size;
comp = kzalloc(total_size, GFP_KERNEL);
if (!comp)
return NULL;
/* configure comp new IPC message */
comp->hdr.size = total_size;
comp->hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_NEW;
comp->id = swidget->comp_id;
comp->pipeline_id = index;
comp->core = swidget->core;
/* handle the extended data if needed */
if (total_size > *ipc_size) {
/* append extended data to the end of the component */
memcpy((u8 *)comp + *ipc_size, &swidget->uuid, ext_size);
comp->ext_data_length = ext_size;
}
/* update ipc_size and return */
*ipc_size = total_size;
return comp;
}
static void sof_dbg_comp_config(struct snd_soc_component *scomp, struct sof_ipc_comp_config *config)
{
dev_dbg(scomp->dev, " config: periods snk %d src %d fmt %d\n",
config->periods_sink, config->periods_source,
config->frame_fmt);
}
static int sof_ipc3_widget_setup_comp_host(struct snd_sof_widget *swidget)
{
struct snd_soc_component *scomp = swidget->scomp;
struct sof_ipc_comp_host *host;
size_t ipc_size = sizeof(*host);
int ret;
host = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id);
if (!host)
return -ENOMEM;
swidget->private = host;
/* configure host comp IPC message */
host->comp.type = SOF_COMP_HOST;
host->config.hdr.size = sizeof(host->config);
if (swidget->id == snd_soc_dapm_aif_out)
host->direction = SOF_IPC_STREAM_CAPTURE;
else
host->direction = SOF_IPC_STREAM_PLAYBACK;
/* parse one set of pcm_tokens */
ret = sof_update_ipc_object(scomp, host, SOF_PCM_TOKENS, swidget->tuples,
swidget->num_tuples, sizeof(*host), 1);
if (ret < 0)
goto err;
/* parse one set of comp_tokens */
ret = sof_update_ipc_object(scomp, &host->config, SOF_COMP_TOKENS, swidget->tuples,
swidget->num_tuples, sizeof(host->config), 1);
if (ret < 0)
goto err;
dev_dbg(scomp->dev, "loaded host %s\n", swidget->widget->name);
sof_dbg_comp_config(scomp, &host->config);
return 0;
err:
kfree(swidget->private);
swidget->private = NULL;
return ret;
}
static void sof_ipc3_widget_free_comp(struct snd_sof_widget *swidget)
{
kfree(swidget->private);
}
static int sof_ipc3_widget_setup_comp_tone(struct snd_sof_widget *swidget)
{
struct snd_soc_component *scomp = swidget->scomp;
struct sof_ipc_comp_tone *tone;
size_t ipc_size = sizeof(*tone);
int ret;
tone = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id);
if (!tone)
return -ENOMEM;
swidget->private = tone;
/* configure siggen IPC message */
tone->comp.type = SOF_COMP_TONE;
tone->config.hdr.size = sizeof(tone->config);
/* parse one set of comp tokens */
ret = sof_update_ipc_object(scomp, &tone->config, SOF_COMP_TOKENS, swidget->tuples,
swidget->num_tuples, sizeof(tone->config), 1);
if (ret < 0) {
kfree(swidget->private);
swidget->private = NULL;
return ret;
}
dev_dbg(scomp->dev, "tone %s: frequency %d amplitude %d\n",
swidget->widget->name, tone->frequency, tone->amplitude);
sof_dbg_comp_config(scomp, &tone->config);
return 0;
}
static int sof_ipc3_widget_setup_comp_mixer(struct snd_sof_widget *swidget)
{
struct snd_soc_component *scomp = swidget->scomp;
struct sof_ipc_comp_mixer *mixer;
size_t ipc_size = sizeof(*mixer);
int ret;
mixer = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id);
if (!mixer)
return -ENOMEM;
swidget->private = mixer;
/* configure mixer IPC message */
mixer->comp.type = SOF_COMP_MIXER;
mixer->config.hdr.size = sizeof(mixer->config);
/* parse one set of comp tokens */
ret = sof_update_ipc_object(scomp, &mixer->config, SOF_COMP_TOKENS,
swidget->tuples, swidget->num_tuples,
sizeof(mixer->config), 1);
if (ret < 0) {
kfree(swidget->private);
swidget->private = NULL;
return ret;
}
dev_dbg(scomp->dev, "loaded mixer %s\n", swidget->widget->name);
sof_dbg_comp_config(scomp, &mixer->config);
return 0;
}
static int sof_ipc3_widget_setup_comp_pipeline(struct snd_sof_widget *swidget)
{
struct snd_soc_component *scomp = swidget->scomp;
struct sof_ipc_pipe_new *pipeline;
struct snd_sof_widget *comp_swidget;
int ret;
pipeline = kzalloc(sizeof(*pipeline), GFP_KERNEL);
if (!pipeline)
return -ENOMEM;
/* configure pipeline IPC message */
pipeline->hdr.size = sizeof(*pipeline);
pipeline->hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_PIPE_NEW;
pipeline->pipeline_id = swidget->pipeline_id;
pipeline->comp_id = swidget->comp_id;
swidget->private = pipeline;
/* component at start of pipeline is our stream id */
comp_swidget = snd_sof_find_swidget(scomp, swidget->widget->sname);
if (!comp_swidget) {
dev_err(scomp->dev, "scheduler %s refers to non existent widget %s\n",
swidget->widget->name, swidget->widget->sname);
ret = -EINVAL;
goto err;
}
pipeline->sched_id = comp_swidget->comp_id;
/* parse one set of scheduler tokens */
ret = sof_update_ipc_object(scomp, pipeline, SOF_SCHED_TOKENS, swidget->tuples,
swidget->num_tuples, sizeof(*pipeline), 1);
if (ret < 0)
goto err;
/* parse one set of pipeline tokens */
ret = sof_update_ipc_object(scomp, swidget, SOF_PIPELINE_TOKENS, swidget->tuples,
swidget->num_tuples, sizeof(*swidget), 1);
if (ret < 0)
goto err;
if (sof_debug_check_flag(SOF_DBG_DISABLE_MULTICORE))
pipeline->core = SOF_DSP_PRIMARY_CORE;
if (sof_debug_check_flag(SOF_DBG_DYNAMIC_PIPELINES_OVERRIDE))
swidget->dynamic_pipeline_widget =
sof_debug_check_flag(SOF_DBG_DYNAMIC_PIPELINES_ENABLE);
dev_dbg(scomp->dev, "pipeline %s: period %d pri %d mips %d core %d frames %d dynamic %d\n",
swidget->widget->name, pipeline->period, pipeline->priority,
pipeline->period_mips, pipeline->core, pipeline->frames_per_sched,
swidget->dynamic_pipeline_widget);
swidget->core = pipeline->core;
return 0;
err:
kfree(swidget->private);
swidget->private = NULL;
return ret;
}
static int sof_ipc3_widget_setup_comp_buffer(struct snd_sof_widget *swidget)
{
struct snd_soc_component *scomp = swidget->scomp;
struct sof_ipc_buffer *buffer;
int ret;
buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
if (!buffer)
return -ENOMEM;
swidget->private = buffer;
/* configure dai IPC message */
buffer->comp.hdr.size = sizeof(*buffer);
buffer->comp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_BUFFER_NEW;
buffer->comp.id = swidget->comp_id;
buffer->comp.type = SOF_COMP_BUFFER;
buffer->comp.pipeline_id = swidget->pipeline_id;
buffer->comp.core = swidget->core;
/* parse one set of buffer tokens */
ret = sof_update_ipc_object(scomp, buffer, SOF_BUFFER_TOKENS, swidget->tuples,
swidget->num_tuples, sizeof(*buffer), 1);
if (ret < 0) {
kfree(swidget->private);
swidget->private = NULL;
return ret;
}
dev_dbg(scomp->dev, "buffer %s: size %d caps 0x%x\n",
swidget->widget->name, buffer->size, buffer->caps);
return 0;
}
static int sof_ipc3_widget_setup_comp_src(struct snd_sof_widget *swidget)
{
struct snd_soc_component *scomp = swidget->scomp;
struct sof_ipc_comp_src *src;
size_t ipc_size = sizeof(*src);
int ret;
src = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id);
if (!src)
return -ENOMEM;
swidget->private = src;
/* configure src IPC message */
src->comp.type = SOF_COMP_SRC;
src->config.hdr.size = sizeof(src->config);
/* parse one set of src tokens */
ret = sof_update_ipc_object(scomp, src, SOF_SRC_TOKENS, swidget->tuples,
swidget->num_tuples, sizeof(*src), 1);
if (ret < 0)
goto err;
/* parse one set of comp tokens */
ret = sof_update_ipc_object(scomp, &src->config, SOF_COMP_TOKENS,
swidget->tuples, swidget->num_tuples, sizeof(src->config), 1);
if (ret < 0)
goto err;
dev_dbg(scomp->dev, "src %s: source rate %d sink rate %d\n",
swidget->widget->name, src->source_rate, src->sink_rate);
sof_dbg_comp_config(scomp, &src->config);
return 0;
err:
kfree(swidget->private);
swidget->private = NULL;
return ret;
}
static int sof_ipc3_widget_setup_comp_asrc(struct snd_sof_widget *swidget)
{
struct snd_soc_component *scomp = swidget->scomp;
struct sof_ipc_comp_asrc *asrc;
size_t ipc_size = sizeof(*asrc);
int ret;
asrc = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id);
if (!asrc)
return -ENOMEM;
swidget->private = asrc;
/* configure ASRC IPC message */
asrc->comp.type = SOF_COMP_ASRC;
asrc->config.hdr.size = sizeof(asrc->config);
/* parse one set of asrc tokens */
ret = sof_update_ipc_object(scomp, asrc, SOF_ASRC_TOKENS, swidget->tuples,
swidget->num_tuples, sizeof(*asrc), 1);
if (ret < 0)
goto err;
/* parse one set of comp tokens */
ret = sof_update_ipc_object(scomp, &asrc->config, SOF_COMP_TOKENS,
swidget->tuples, swidget->num_tuples, sizeof(asrc->config), 1);
if (ret < 0)
goto err;
dev_dbg(scomp->dev, "asrc %s: source rate %d sink rate %d asynch %d operation %d\n",
swidget->widget->name, asrc->source_rate, asrc->sink_rate,
asrc->asynchronous_mode, asrc->operation_mode);
sof_dbg_comp_config(scomp, &asrc->config);
return 0;
err:
kfree(swidget->private);
swidget->private = NULL;
return ret;
}
/*
* Mux topology
*/
static int sof_ipc3_widget_setup_comp_mux(struct snd_sof_widget *swidget)
{
struct snd_soc_component *scomp = swidget->scomp;
struct sof_ipc_comp_mux *mux;
size_t ipc_size = sizeof(*mux);
int ret;
mux = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id);
if (!mux)
return -ENOMEM;
swidget->private = mux;
/* configure mux IPC message */
mux->comp.type = SOF_COMP_MUX;
mux->config.hdr.size = sizeof(mux->config);
/* parse one set of comp tokens */
ret = sof_update_ipc_object(scomp, &mux->config, SOF_COMP_TOKENS,
swidget->tuples, swidget->num_tuples, sizeof(mux->config), 1);
if (ret < 0) {
kfree(swidget->private);
swidget->private = NULL;
return ret;
}
dev_dbg(scomp->dev, "loaded mux %s\n", swidget->widget->name);
sof_dbg_comp_config(scomp, &mux->config);
return 0;
}
/*
* PGA Topology
*/
static int sof_ipc3_widget_setup_comp_pga(struct snd_sof_widget *swidget)
{
struct snd_soc_component *scomp = swidget->scomp;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct sof_ipc_comp_volume *volume;
struct snd_sof_control *scontrol;
size_t ipc_size = sizeof(*volume);
int min_step, max_step;
int ret;
volume = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id);
if (!volume)
return -ENOMEM;
swidget->private = volume;
/* configure volume IPC message */
volume->comp.type = SOF_COMP_VOLUME;
volume->config.hdr.size = sizeof(volume->config);
/* parse one set of volume tokens */
ret = sof_update_ipc_object(scomp, volume, SOF_VOLUME_TOKENS, swidget->tuples,
swidget->num_tuples, sizeof(*volume), 1);
if (ret < 0)
goto err;
/* parse one set of comp tokens */
ret = sof_update_ipc_object(scomp, &volume->config, SOF_COMP_TOKENS,
swidget->tuples, swidget->num_tuples,
sizeof(volume->config), 1);
if (ret < 0)
goto err;
dev_dbg(scomp->dev, "loaded PGA %s\n", swidget->widget->name);
sof_dbg_comp_config(scomp, &volume->config);
list_for_each_entry(scontrol, &sdev->kcontrol_list, list) {
if (scontrol->comp_id == swidget->comp_id &&
scontrol->volume_table) {
min_step = scontrol->min_volume_step;
max_step = scontrol->max_volume_step;
volume->min_value = scontrol->volume_table[min_step];
volume->max_value = scontrol->volume_table[max_step];
volume->channels = scontrol->num_channels;
break;
}
}
return 0;
err:
kfree(swidget->private);
swidget->private = NULL;
return ret;
}
static int sof_get_control_data(struct snd_soc_component *scomp,
struct snd_soc_dapm_widget *widget,
struct sof_widget_data *wdata, size_t *size)
{
const struct snd_kcontrol_new *kc;
struct sof_ipc_ctrl_data *cdata;
struct soc_mixer_control *sm;
struct soc_bytes_ext *sbe;
struct soc_enum *se;
int i;
*size = 0;
for (i = 0; i < widget->num_kcontrols; i++) {
kc = &widget->kcontrol_news[i];
switch (widget->dobj.widget.kcontrol_type[i]) {
case SND_SOC_TPLG_TYPE_MIXER:
sm = (struct soc_mixer_control *)kc->private_value;
wdata[i].control = sm->dobj.private;
break;
case SND_SOC_TPLG_TYPE_BYTES:
sbe = (struct soc_bytes_ext *)kc->private_value;
wdata[i].control = sbe->dobj.private;
break;
case SND_SOC_TPLG_TYPE_ENUM:
se = (struct soc_enum *)kc->private_value;
wdata[i].control = se->dobj.private;
break;
default:
dev_err(scomp->dev, "Unknown kcontrol type %u in widget %s\n",
widget->dobj.widget.kcontrol_type[i], widget->name);
return -EINVAL;
}
if (!wdata[i].control) {
dev_err(scomp->dev, "No scontrol for widget %s\n", widget->name);
return -EINVAL;
}
cdata = wdata[i].control->ipc_control_data;
if (widget->dobj.widget.kcontrol_type[i] == SND_SOC_TPLG_TYPE_BYTES) {
/* make sure data is valid - data can be updated at runtime */
if (cdata->data->magic != SOF_ABI_MAGIC)
return -EINVAL;
wdata[i].pdata = cdata->data->data;
wdata[i].pdata_size = cdata->data->size;
} else {
/* points to the control data union */
wdata[i].pdata = cdata->chanv;
/*
* wdata[i].control->size is calculated with struct_size
* and includes the size of struct sof_ipc_ctrl_data
*/
wdata[i].pdata_size = wdata[i].control->size -
sizeof(struct sof_ipc_ctrl_data);
}
*size += wdata[i].pdata_size;
/* get data type */
switch (cdata->cmd) {
case SOF_CTRL_CMD_VOLUME:
case SOF_CTRL_CMD_ENUM:
case SOF_CTRL_CMD_SWITCH:
wdata[i].ipc_cmd = SOF_IPC_COMP_SET_VALUE;
wdata[i].ctrl_type = SOF_CTRL_TYPE_VALUE_CHAN_SET;
break;
case SOF_CTRL_CMD_BINARY:
wdata[i].ipc_cmd = SOF_IPC_COMP_SET_DATA;
wdata[i].ctrl_type = SOF_CTRL_TYPE_DATA_SET;
break;
default:
break;
}
}
return 0;
}
static int sof_process_load(struct snd_soc_component *scomp,
struct snd_sof_widget *swidget, int type)
{
struct snd_soc_dapm_widget *widget = swidget->widget;
struct sof_ipc_comp_process *process;
struct sof_widget_data *wdata = NULL;
size_t ipc_data_size = 0;
size_t ipc_size;
int offset = 0;
int ret;
int i;
/* allocate struct for widget control data sizes and types */
if (widget->num_kcontrols) {
wdata = kcalloc(widget->num_kcontrols, sizeof(*wdata), GFP_KERNEL);
if (!wdata)
return -ENOMEM;
/* get possible component controls and get size of all pdata */
ret = sof_get_control_data(scomp, widget, wdata, &ipc_data_size);
if (ret < 0)
goto out;
}
ipc_size = sizeof(struct sof_ipc_comp_process) + ipc_data_size;
/* we are exceeding max ipc size, config needs to be sent separately */
if (ipc_size > SOF_IPC_MSG_MAX_SIZE) {
ipc_size -= ipc_data_size;
ipc_data_size = 0;
}
process = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id);
if (!process) {
ret = -ENOMEM;
goto out;
}
swidget->private = process;
/* configure iir IPC message */
process->comp.type = type;
process->config.hdr.size = sizeof(process->config);
/* parse one set of comp tokens */
ret = sof_update_ipc_object(scomp, &process->config, SOF_COMP_TOKENS,
swidget->tuples, swidget->num_tuples,
sizeof(process->config), 1);
if (ret < 0)
goto err;
dev_dbg(scomp->dev, "loaded process %s\n", swidget->widget->name);
sof_dbg_comp_config(scomp, &process->config);
/*
* found private data in control, so copy it.
* get possible component controls - get size of all pdata,
* then memcpy with headers
*/
if (ipc_data_size) {
for (i = 0; i < widget->num_kcontrols; i++) {
if (!wdata[i].pdata_size)
continue;
memcpy(&process->data[offset], wdata[i].pdata,
wdata[i].pdata_size);
offset += wdata[i].pdata_size;
}
}
process->size = ipc_data_size;
kfree(wdata);
return 0;
err:
kfree(swidget->private);
swidget->private = NULL;
out:
kfree(wdata);
return ret;
}
static enum sof_comp_type find_process_comp_type(enum sof_ipc_process_type type)
{
int i;
for (i = 0; i < ARRAY_SIZE(sof_process); i++) {
if (sof_process[i].type == type)
return sof_process[i].comp_type;
}
return SOF_COMP_NONE;
}
/*
* Processing Component Topology - can be "effect", "codec", or general
* "processing".
*/
static int sof_widget_update_ipc_comp_process(struct snd_sof_widget *swidget)
{
struct snd_soc_component *scomp = swidget->scomp;
struct sof_ipc_comp_process config;
int ret;
memset(&config, 0, sizeof(config));
config.comp.core = swidget->core;
/* parse one set of process tokens */
ret = sof_update_ipc_object(scomp, &config, SOF_PROCESS_TOKENS, swidget->tuples,
swidget->num_tuples, sizeof(config), 1);
if (ret < 0)
return ret;
/* now load process specific data and send IPC */
return sof_process_load(scomp, swidget, find_process_comp_type(config.type));
}
static int sof_link_hda_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink,
struct sof_ipc_dai_config *config, struct snd_sof_dai *dai)
{
struct sof_dai_private_data *private = dai->private;
u32 size = sizeof(*config);
int ret;
/* init IPC */
memset(&config->hda, 0, sizeof(config->hda));
config->hdr.size = size;
/* parse one set of HDA tokens */
ret = sof_update_ipc_object(scomp, &config->hda, SOF_HDA_TOKENS, slink->tuples,
slink->num_tuples, size, 1);
if (ret < 0)
return ret;
dev_dbg(scomp->dev, "HDA config rate %d channels %d\n",
config->hda.rate, config->hda.channels);
config->hda.link_dma_ch = DMA_CHAN_INVALID;
dai->number_configs = 1;
dai->current_config = 0;
private->dai_config = kmemdup(config, size, GFP_KERNEL);
if (!private->dai_config)
return -ENOMEM;
return 0;
}
static void sof_dai_set_format(struct snd_soc_tplg_hw_config *hw_config,
struct sof_ipc_dai_config *config)
{
/* clock directions wrt codec */
config->format &= ~SOF_DAI_FMT_CLOCK_PROVIDER_MASK;
if (hw_config->bclk_provider == SND_SOC_TPLG_BCLK_CP) {
/* codec is bclk provider */
if (hw_config->fsync_provider == SND_SOC_TPLG_FSYNC_CP)
config->format |= SOF_DAI_FMT_CBP_CFP;
else
config->format |= SOF_DAI_FMT_CBP_CFC;
} else {
/* codec is bclk consumer */
if (hw_config->fsync_provider == SND_SOC_TPLG_FSYNC_CP)
config->format |= SOF_DAI_FMT_CBC_CFP;
else
config->format |= SOF_DAI_FMT_CBC_CFC;
}
/* inverted clocks ? */
config->format &= ~SOF_DAI_FMT_INV_MASK;
if (hw_config->invert_bclk) {
if (hw_config->invert_fsync)
config->format |= SOF_DAI_FMT_IB_IF;
else
config->format |= SOF_DAI_FMT_IB_NF;
} else {
if (hw_config->invert_fsync)
config->format |= SOF_DAI_FMT_NB_IF;
else
config->format |= SOF_DAI_FMT_NB_NF;
}
}
static int sof_link_sai_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink,
struct sof_ipc_dai_config *config, struct snd_sof_dai *dai)
{
struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs;
struct sof_dai_private_data *private = dai->private;
u32 size = sizeof(*config);
int ret;
/* handle master/slave and inverted clocks */
sof_dai_set_format(hw_config, config);
/* init IPC */
memset(&config->sai, 0, sizeof(config->sai));
config->hdr.size = size;
/* parse one set of SAI tokens */
ret = sof_update_ipc_object(scomp, &config->sai, SOF_SAI_TOKENS, slink->tuples,
slink->num_tuples, size, 1);
if (ret < 0)
return ret;
config->sai.mclk_rate = le32_to_cpu(hw_config->mclk_rate);
config->sai.bclk_rate = le32_to_cpu(hw_config->bclk_rate);
config->sai.fsync_rate = le32_to_cpu(hw_config->fsync_rate);
config->sai.mclk_direction = hw_config->mclk_direction;
config->sai.tdm_slots = le32_to_cpu(hw_config->tdm_slots);
config->sai.tdm_slot_width = le32_to_cpu(hw_config->tdm_slot_width);
config->sai.rx_slots = le32_to_cpu(hw_config->rx_slots);
config->sai.tx_slots = le32_to_cpu(hw_config->tx_slots);
dev_info(scomp->dev,
"tplg: config SAI%d fmt 0x%x mclk %d width %d slots %d mclk id %d\n",
config->dai_index, config->format,
config->sai.mclk_rate, config->sai.tdm_slot_width,
config->sai.tdm_slots, config->sai.mclk_id);
if (config->sai.tdm_slots < 1 || config->sai.tdm_slots > 8) {
dev_err(scomp->dev, "Invalid channel count for SAI%d\n", config->dai_index);
return -EINVAL;
}
dai->number_configs = 1;
dai->current_config = 0;
private->dai_config = kmemdup(config, size, GFP_KERNEL);
if (!private->dai_config)
return -ENOMEM;
return 0;
}
static int sof_link_esai_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink,
struct sof_ipc_dai_config *config, struct snd_sof_dai *dai)
{
struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs;
struct sof_dai_private_data *private = dai->private;
u32 size = sizeof(*config);
int ret;
/* handle master/slave and inverted clocks */
sof_dai_set_format(hw_config, config);
/* init IPC */
memset(&config->esai, 0, sizeof(config->esai));
config->hdr.size = size;
/* parse one set of ESAI tokens */
ret = sof_update_ipc_object(scomp, &config->esai, SOF_ESAI_TOKENS, slink->tuples,
slink->num_tuples, size, 1);
if (ret < 0)
return ret;
config->esai.mclk_rate = le32_to_cpu(hw_config->mclk_rate);
config->esai.bclk_rate = le32_to_cpu(hw_config->bclk_rate);
config->esai.fsync_rate = le32_to_cpu(hw_config->fsync_rate);
config->esai.mclk_direction = hw_config->mclk_direction;
config->esai.tdm_slots = le32_to_cpu(hw_config->tdm_slots);
config->esai.tdm_slot_width = le32_to_cpu(hw_config->tdm_slot_width);
config->esai.rx_slots = le32_to_cpu(hw_config->rx_slots);
config->esai.tx_slots = le32_to_cpu(hw_config->tx_slots);
dev_info(scomp->dev,
"tplg: config ESAI%d fmt 0x%x mclk %d width %d slots %d mclk id %d\n",
config->dai_index, config->format,
config->esai.mclk_rate, config->esai.tdm_slot_width,
config->esai.tdm_slots, config->esai.mclk_id);
if (config->esai.tdm_slots < 1 || config->esai.tdm_slots > 8) {
dev_err(scomp->dev, "Invalid channel count for ESAI%d\n", config->dai_index);
return -EINVAL;
}
dai->number_configs = 1;
dai->current_config = 0;
private->dai_config = kmemdup(config, size, GFP_KERNEL);
if (!private->dai_config)
return -ENOMEM;
return 0;
}
static int sof_link_acp_dmic_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink,
struct sof_ipc_dai_config *config, struct snd_sof_dai *dai)
{
struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs;
struct sof_dai_private_data *private = dai->private;
u32 size = sizeof(*config);
int ret;
/* handle master/slave and inverted clocks */
sof_dai_set_format(hw_config, config);
config->hdr.size = size;
/* parse the required set of ACPDMIC tokens based on num_hw_cfgs */
ret = sof_update_ipc_object(scomp, &config->acpdmic, SOF_ACPDMIC_TOKENS, slink->tuples,
slink->num_tuples, size, slink->num_hw_configs);
if (ret < 0)
return ret;
dev_info(scomp->dev, "ACP_DMIC config ACP%d channel %d rate %d\n",
config->dai_index, config->acpdmic.pdm_ch,
config->acpdmic.pdm_rate);
dai->number_configs = 1;
dai->current_config = 0;
private->dai_config = kmemdup(config, size, GFP_KERNEL);
if (!private->dai_config)
return -ENOMEM;
return 0;
}
static int sof_link_acp_bt_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink,
struct sof_ipc_dai_config *config, struct snd_sof_dai *dai)
{
struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs;
struct sof_dai_private_data *private = dai->private;
u32 size = sizeof(*config);
/* handle master/slave and inverted clocks */
sof_dai_set_format(hw_config, config);
/* init IPC */
memset(&config->acpbt, 0, sizeof(config->acpbt));
config->hdr.size = size;
config->acpbt.fsync_rate = le32_to_cpu(hw_config->fsync_rate);
config->acpbt.tdm_slots = le32_to_cpu(hw_config->tdm_slots);
dev_info(scomp->dev, "ACP_BT config ACP%d channel %d rate %d\n",
config->dai_index, config->acpbt.tdm_slots,
config->acpbt.fsync_rate);
dai->number_configs = 1;
dai->current_config = 0;
private->dai_config = kmemdup(config, size, GFP_KERNEL);
if (!private->dai_config)
return -ENOMEM;
return 0;
}
static int sof_link_acp_sp_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink,
struct sof_ipc_dai_config *config, struct snd_sof_dai *dai)
{
struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs;
struct sof_dai_private_data *private = dai->private;
u32 size = sizeof(*config);
int ret;
/* handle master/slave and inverted clocks */
sof_dai_set_format(hw_config, config);
/* init IPC */
memset(&config->acpsp, 0, sizeof(config->acpsp));
config->hdr.size = size;
ret = sof_update_ipc_object(scomp, &config->acpsp, SOF_ACPI2S_TOKENS, slink->tuples,
slink->num_tuples, size, slink->num_hw_configs);
if (ret < 0)
return ret;
dev_info(scomp->dev, "ACP_SP config ACP%d channel %d rate %d tdm_mode %d\n",
config->dai_index, config->acpsp.tdm_slots,
config->acpsp.fsync_rate, config->acpsp.tdm_mode);
dai->number_configs = 1;
dai->current_config = 0;
private->dai_config = kmemdup(config, size, GFP_KERNEL);
if (!private->dai_config)
return -ENOMEM;
return 0;
}
static int sof_link_acp_hs_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink,
struct sof_ipc_dai_config *config, struct snd_sof_dai *dai)
{
struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs;
struct sof_dai_private_data *private = dai->private;
u32 size = sizeof(*config);
int ret;
/* Configures the DAI hardware format and inverted clocks */
sof_dai_set_format(hw_config, config);
/* init IPC */
memset(&config->acphs, 0, sizeof(config->acphs));
config->hdr.size = size;
ret = sof_update_ipc_object(scomp, &config->acphs, SOF_ACPI2S_TOKENS, slink->tuples,
slink->num_tuples, size, slink->num_hw_configs);
if (ret < 0)
return ret;
dev_info(scomp->dev, "ACP_HS config ACP%d channel %d rate %d tdm_mode %d\n",
config->dai_index, config->acphs.tdm_slots,
config->acphs.fsync_rate, config->acphs.tdm_mode);
dai->number_configs = 1;
dai->current_config = 0;
private->dai_config = kmemdup(config, size, GFP_KERNEL);
if (!private->dai_config)
return -ENOMEM;
return 0;
}
static int sof_link_afe_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink,
struct sof_ipc_dai_config *config, struct snd_sof_dai *dai)
{
struct sof_dai_private_data *private = dai->private;
u32 size = sizeof(*config);
int ret;
config->hdr.size = size;
/* parse the required set of AFE tokens based on num_hw_cfgs */
ret = sof_update_ipc_object(scomp, &config->afe, SOF_AFE_TOKENS, slink->tuples,
slink->num_tuples, size, slink->num_hw_configs);
if (ret < 0)
return ret;
dev_dbg(scomp->dev, "AFE config rate %d channels %d format:%d\n",
config->afe.rate, config->afe.channels, config->afe.format);
config->afe.stream_id = DMA_CHAN_INVALID;
dai->number_configs = 1;
dai->current_config = 0;
private->dai_config = kmemdup(config, size, GFP_KERNEL);
if (!private->dai_config)
return -ENOMEM;
return 0;
}
static int sof_link_ssp_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink,
struct sof_ipc_dai_config *config, struct snd_sof_dai *dai)
{
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct snd_soc_tplg_hw_config *hw_config = slink->hw_configs;
struct sof_dai_private_data *private = dai->private;
u32 size = sizeof(*config);
int current_config = 0;
int i, ret;
/*
* Parse common data, we should have 1 common data per hw_config.
*/
ret = sof_update_ipc_object(scomp, &config->ssp, SOF_SSP_TOKENS, slink->tuples,
slink->num_tuples, size, slink->num_hw_configs);
if (ret < 0)
return ret;
/* process all possible hw configs */
for (i = 0; i < slink->num_hw_configs; i++) {
if (le32_to_cpu(hw_config[i].id) == slink->default_hw_cfg_id)
current_config = i;
/* handle master/slave and inverted clocks */
sof_dai_set_format(&hw_config[i], &config[i]);
config[i].hdr.size = size;
if (sdev->mclk_id_override) {
dev_dbg(scomp->dev, "tplg: overriding topology mclk_id %d by quirk %d\n",
config[i].ssp.mclk_id, sdev->mclk_id_quirk);
config[i].ssp.mclk_id = sdev->mclk_id_quirk;
}
/* copy differentiating hw configs to ipc structs */
config[i].ssp.mclk_rate = le32_to_cpu(hw_config[i].mclk_rate);
config[i].ssp.bclk_rate = le32_to_cpu(hw_config[i].bclk_rate);
config[i].ssp.fsync_rate = le32_to_cpu(hw_config[i].fsync_rate);
config[i].ssp.tdm_slots = le32_to_cpu(hw_config[i].tdm_slots);
config[i].ssp.tdm_slot_width = le32_to_cpu(hw_config[i].tdm_slot_width);
config[i].ssp.mclk_direction = hw_config[i].mclk_direction;
config[i].ssp.rx_slots = le32_to_cpu(hw_config[i].rx_slots);
config[i].ssp.tx_slots = le32_to_cpu(hw_config[i].tx_slots);
dev_dbg(scomp->dev, "tplg: config SSP%d fmt %#x mclk %d bclk %d fclk %d width (%d)%d slots %d mclk id %d quirks %d clks_control %#x\n",
config[i].dai_index, config[i].format,
config[i].ssp.mclk_rate, config[i].ssp.bclk_rate,
config[i].ssp.fsync_rate, config[i].ssp.sample_valid_bits,
config[i].ssp.tdm_slot_width, config[i].ssp.tdm_slots,
config[i].ssp.mclk_id, config[i].ssp.quirks, config[i].ssp.clks_control);
/* validate SSP fsync rate and channel count */
if (config[i].ssp.fsync_rate < 8000 || config[i].ssp.fsync_rate > 192000) {
dev_err(scomp->dev, "Invalid fsync rate for SSP%d\n", config[i].dai_index);
return -EINVAL;
}
if (config[i].ssp.tdm_slots < 1 || config[i].ssp.tdm_slots > 8) {
dev_err(scomp->dev, "Invalid channel count for SSP%d\n",
config[i].dai_index);
return -EINVAL;
}
}
dai->number_configs = slink->num_hw_configs;
dai->current_config = current_config;
private->dai_config = kmemdup(config, size * slink->num_hw_configs, GFP_KERNEL);
if (!private->dai_config)
return -ENOMEM;
return 0;
}
static int sof_link_dmic_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink,
struct sof_ipc_dai_config *config, struct snd_sof_dai *dai)
{
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct sof_dai_private_data *private = dai->private;
struct sof_ipc_fw_ready *ready = &sdev->fw_ready;
struct sof_ipc_fw_version *v = &ready->version;
size_t size = sizeof(*config);
int i, ret;
/* Ensure the entire DMIC config struct is zeros */
memset(&config->dmic, 0, sizeof(config->dmic));
/* parse the required set of DMIC tokens based on num_hw_cfgs */
ret = sof_update_ipc_object(scomp, &config->dmic, SOF_DMIC_TOKENS, slink->tuples,
slink->num_tuples, size, slink->num_hw_configs);
if (ret < 0)
return ret;
/* parse the required set of DMIC PDM tokens based on number of active PDM's */
ret = sof_update_ipc_object(scomp, &config->dmic.pdm[0], SOF_DMIC_PDM_TOKENS,
slink->tuples, slink->num_tuples,
sizeof(struct sof_ipc_dai_dmic_pdm_ctrl),
config->dmic.num_pdm_active);
if (ret < 0)
return ret;
/* set IPC header size */
config->hdr.size = size;
/* debug messages */
dev_dbg(scomp->dev, "tplg: config DMIC%d driver version %d\n",
config->dai_index, config->dmic.driver_ipc_version);
dev_dbg(scomp->dev, "pdmclk_min %d pdm_clkmax %d duty_min %d\n",
config->dmic.pdmclk_min, config->dmic.pdmclk_max,
config->dmic.duty_min);
dev_dbg(scomp->dev, "duty_max %d fifo_fs %d num_pdms active %d\n",
config->dmic.duty_max, config->dmic.fifo_fs,
config->dmic.num_pdm_active);
dev_dbg(scomp->dev, "fifo word length %d\n", config->dmic.fifo_bits);
for (i = 0; i < config->dmic.num_pdm_active; i++) {
dev_dbg(scomp->dev, "pdm %d mic a %d mic b %d\n",
config->dmic.pdm[i].id,
config->dmic.pdm[i].enable_mic_a,
config->dmic.pdm[i].enable_mic_b);
dev_dbg(scomp->dev, "pdm %d polarity a %d polarity b %d\n",
config->dmic.pdm[i].id,
config->dmic.pdm[i].polarity_mic_a,
config->dmic.pdm[i].polarity_mic_b);
dev_dbg(scomp->dev, "pdm %d clk_edge %d skew %d\n",
config->dmic.pdm[i].id,
config->dmic.pdm[i].clk_edge,
config->dmic.pdm[i].skew);
}
/*
* this takes care of backwards compatible handling of fifo_bits_b.
* It is deprecated since firmware ABI version 3.0.1.
*/
if (SOF_ABI_VER(v->major, v->minor, v->micro) < SOF_ABI_VER(3, 0, 1))
config->dmic.fifo_bits_b = config->dmic.fifo_bits;
dai->number_configs = 1;
dai->current_config = 0;
private->dai_config = kmemdup(config, size, GFP_KERNEL);
if (!private->dai_config)
return -ENOMEM;
return 0;
}
static int sof_link_alh_load(struct snd_soc_component *scomp, struct snd_sof_dai_link *slink,
struct sof_ipc_dai_config *config, struct snd_sof_dai *dai)
{
struct sof_dai_private_data *private = dai->private;
u32 size = sizeof(*config);
int ret;
/* parse the required set of ALH tokens based on num_hw_cfgs */
ret = sof_update_ipc_object(scomp, &config->alh, SOF_ALH_TOKENS, slink->tuples,
slink->num_tuples, size, slink->num_hw_configs);
if (ret < 0)
return ret;
/* init IPC */
config->hdr.size = size;
/* set config for all DAI's with name matching the link name */
dai->number_configs = 1;
dai->current_config = 0;
private->dai_config = kmemdup(config, size, GFP_KERNEL);
if (!private->dai_config)
return -ENOMEM;
return 0;
}
static int sof_ipc3_widget_setup_comp_dai(struct snd_sof_widget *swidget)
{
struct snd_soc_component *scomp = swidget->scomp;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct snd_sof_dai *dai = swidget->private;
struct sof_dai_private_data *private;
struct sof_ipc_comp_dai *comp_dai;
size_t ipc_size = sizeof(*comp_dai);
struct sof_ipc_dai_config *config;
struct snd_sof_dai_link *slink;
int ret;
private = kzalloc(sizeof(*private), GFP_KERNEL);
if (!private)
return -ENOMEM;
dai->private = private;
private->comp_dai = sof_comp_alloc(swidget, &ipc_size, swidget->pipeline_id);
if (!private->comp_dai) {
ret = -ENOMEM;
goto free;
}
/* configure dai IPC message */
comp_dai = private->comp_dai;
comp_dai->comp.type = SOF_COMP_DAI;
comp_dai->config.hdr.size = sizeof(comp_dai->config);
/* parse one set of DAI tokens */
ret = sof_update_ipc_object(scomp, comp_dai, SOF_DAI_TOKENS, swidget->tuples,
swidget->num_tuples, sizeof(*comp_dai), 1);
if (ret < 0)
goto free;
/* update comp_tokens */
ret = sof_update_ipc_object(scomp, &comp_dai->config, SOF_COMP_TOKENS,
swidget->tuples, swidget->num_tuples,
sizeof(comp_dai->config), 1);
if (ret < 0)
goto free;
dev_dbg(scomp->dev, "dai %s: type %d index %d\n",
swidget->widget->name, comp_dai->type, comp_dai->dai_index);
sof_dbg_comp_config(scomp, &comp_dai->config);
/* now update DAI config */
list_for_each_entry(slink, &sdev->dai_link_list, list) {
struct sof_ipc_dai_config common_config;
int i;
if (strcmp(slink->link->name, dai->name))
continue;
/* Reserve memory for all hw configs, eventually freed by widget */
config = kcalloc(slink->num_hw_configs, sizeof(*config), GFP_KERNEL);
if (!config) {
ret = -ENOMEM;
goto free_comp;
}
/* parse one set of DAI link tokens */
ret = sof_update_ipc_object(scomp, &common_config, SOF_DAI_LINK_TOKENS,
slink->tuples, slink->num_tuples,
sizeof(common_config), 1);
if (ret < 0)
goto free_config;
for (i = 0; i < slink->num_hw_configs; i++) {
config[i].hdr.cmd = SOF_IPC_GLB_DAI_MSG | SOF_IPC_DAI_CONFIG;
config[i].format = le32_to_cpu(slink->hw_configs[i].fmt);
config[i].type = common_config.type;
config[i].dai_index = comp_dai->dai_index;
}
switch (common_config.type) {
case SOF_DAI_INTEL_SSP:
ret = sof_link_ssp_load(scomp, slink, config, dai);
break;
case SOF_DAI_INTEL_DMIC:
ret = sof_link_dmic_load(scomp, slink, config, dai);
break;
case SOF_DAI_INTEL_HDA:
ret = sof_link_hda_load(scomp, slink, config, dai);
break;
case SOF_DAI_INTEL_ALH:
ret = sof_link_alh_load(scomp, slink, config, dai);
break;
case SOF_DAI_IMX_SAI:
ret = sof_link_sai_load(scomp, slink, config, dai);
break;
case SOF_DAI_IMX_ESAI:
ret = sof_link_esai_load(scomp, slink, config, dai);
break;
case SOF_DAI_AMD_BT:
ret = sof_link_acp_bt_load(scomp, slink, config, dai);
break;
case SOF_DAI_AMD_SP:
case SOF_DAI_AMD_SP_VIRTUAL:
ret = sof_link_acp_sp_load(scomp, slink, config, dai);
break;
case SOF_DAI_AMD_HS:
case SOF_DAI_AMD_HS_VIRTUAL:
ret = sof_link_acp_hs_load(scomp, slink, config, dai);
break;
case SOF_DAI_AMD_DMIC:
ret = sof_link_acp_dmic_load(scomp, slink, config, dai);
break;
case SOF_DAI_MEDIATEK_AFE:
ret = sof_link_afe_load(scomp, slink, config, dai);
break;
default:
break;
}
if (ret < 0) {
dev_err(scomp->dev, "failed to load config for dai %s\n", dai->name);
goto free_config;
}
kfree(config);
}
return 0;
free_config:
kfree(config);
free_comp:
kfree(comp_dai);
free:
kfree(private);
dai->private = NULL;
return ret;
}
static void sof_ipc3_widget_free_comp_dai(struct snd_sof_widget *swidget)
{
switch (swidget->id) {
case snd_soc_dapm_dai_in:
case snd_soc_dapm_dai_out:
{
struct snd_sof_dai *dai = swidget->private;
struct sof_dai_private_data *dai_data;
if (!dai)
return;
dai_data = dai->private;
if (dai_data) {
kfree(dai_data->comp_dai);
kfree(dai_data->dai_config);
kfree(dai_data);
}
kfree(dai);
break;
}
default:
break;
}
}
static int sof_ipc3_route_setup(struct snd_sof_dev *sdev, struct snd_sof_route *sroute)
{
struct sof_ipc_pipe_comp_connect connect;
struct sof_ipc_reply reply;
int ret;
connect.hdr.size = sizeof(connect);
connect.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_CONNECT;
connect.source_id = sroute->src_widget->comp_id;
connect.sink_id = sroute->sink_widget->comp_id;
dev_dbg(sdev->dev, "setting up route %s -> %s\n",
sroute->src_widget->widget->name,
sroute->sink_widget->widget->name);
/* send ipc */
ret = sof_ipc_tx_message(sdev->ipc, &connect, sizeof(connect), &reply, sizeof(reply));
if (ret < 0)
dev_err(sdev->dev, "%s: route %s -> %s failed\n", __func__,
sroute->src_widget->widget->name, sroute->sink_widget->widget->name);
return ret;
}
static int sof_ipc3_control_load_bytes(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol)
{
struct sof_ipc_ctrl_data *cdata;
size_t priv_size_check;
int ret;
if (scontrol->max_size < (sizeof(*cdata) + sizeof(struct sof_abi_hdr))) {
dev_err(sdev->dev, "%s: insufficient size for a bytes control: %zu.\n",
__func__, scontrol->max_size);
return -EINVAL;
}
if (scontrol->priv_size > scontrol->max_size - sizeof(*cdata)) {
dev_err(sdev->dev,
"%s: bytes data size %zu exceeds max %zu.\n", __func__,
scontrol->priv_size, scontrol->max_size - sizeof(*cdata));
return -EINVAL;
}
scontrol->ipc_control_data = kzalloc(scontrol->max_size, GFP_KERNEL);
if (!scontrol->ipc_control_data)
return -ENOMEM;
scontrol->size = sizeof(struct sof_ipc_ctrl_data) + scontrol->priv_size;
cdata = scontrol->ipc_control_data;
cdata->cmd = SOF_CTRL_CMD_BINARY;
cdata->index = scontrol->index;
if (scontrol->priv_size > 0) {
memcpy(cdata->data, scontrol->priv, scontrol->priv_size);
kfree(scontrol->priv);
scontrol->priv = NULL;
if (cdata->data->magic != SOF_ABI_MAGIC) {
dev_err(sdev->dev, "Wrong ABI magic 0x%08x.\n", cdata->data->magic);
ret = -EINVAL;
goto err;
}
if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, cdata->data->abi)) {
dev_err(sdev->dev, "Incompatible ABI version 0x%08x.\n",
cdata->data->abi);
ret = -EINVAL;
goto err;
}
priv_size_check = cdata->data->size + sizeof(struct sof_abi_hdr);
if (priv_size_check != scontrol->priv_size) {
dev_err(sdev->dev, "Conflict in bytes (%zu) vs. priv size (%zu).\n",
priv_size_check, scontrol->priv_size);
ret = -EINVAL;
goto err;
}
}
return 0;
err:
kfree(scontrol->ipc_control_data);
scontrol->ipc_control_data = NULL;
return ret;
}
static int sof_ipc3_control_load_volume(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol)
{
struct sof_ipc_ctrl_data *cdata;
int i;
/* init the volume get/put data */
scontrol->size = struct_size(cdata, chanv, scontrol->num_channels);
scontrol->ipc_control_data = kzalloc(scontrol->size, GFP_KERNEL);
if (!scontrol->ipc_control_data)
return -ENOMEM;
cdata = scontrol->ipc_control_data;
cdata->index = scontrol->index;
/* set cmd for mixer control */
if (scontrol->max == 1) {
cdata->cmd = SOF_CTRL_CMD_SWITCH;
return 0;
}
cdata->cmd = SOF_CTRL_CMD_VOLUME;
/* set default volume values to 0dB in control */
for (i = 0; i < scontrol->num_channels; i++) {
cdata->chanv[i].channel = i;
cdata->chanv[i].value = VOL_ZERO_DB;
}
return 0;
}
static int sof_ipc3_control_load_enum(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol)
{
struct sof_ipc_ctrl_data *cdata;
/* init the enum get/put data */
scontrol->size = struct_size(cdata, chanv, scontrol->num_channels);
scontrol->ipc_control_data = kzalloc(scontrol->size, GFP_KERNEL);
if (!scontrol->ipc_control_data)
return -ENOMEM;
cdata = scontrol->ipc_control_data;
cdata->index = scontrol->index;
cdata->cmd = SOF_CTRL_CMD_ENUM;
return 0;
}
static int sof_ipc3_control_setup(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol)
{
switch (scontrol->info_type) {
case SND_SOC_TPLG_CTL_VOLSW:
case SND_SOC_TPLG_CTL_VOLSW_SX:
case SND_SOC_TPLG_CTL_VOLSW_XR_SX:
return sof_ipc3_control_load_volume(sdev, scontrol);
case SND_SOC_TPLG_CTL_BYTES:
return sof_ipc3_control_load_bytes(sdev, scontrol);
case SND_SOC_TPLG_CTL_ENUM:
case SND_SOC_TPLG_CTL_ENUM_VALUE:
return sof_ipc3_control_load_enum(sdev, scontrol);
default:
break;
}
return 0;
}
static int sof_ipc3_control_free(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol)
{
struct sof_ipc_free fcomp;
fcomp.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_COMP_FREE;
fcomp.hdr.size = sizeof(fcomp);
fcomp.id = scontrol->comp_id;
/* send IPC to the DSP */
return sof_ipc_tx_message(sdev->ipc, &fcomp, sizeof(fcomp), NULL, 0);
}
/* send pcm params ipc */
static int sof_ipc3_keyword_detect_pcm_params(struct snd_sof_widget *swidget, int dir)
{
struct snd_soc_component *scomp = swidget->scomp;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct sof_ipc_pcm_params_reply ipc_params_reply;
struct snd_pcm_hw_params *params;
struct sof_ipc_pcm_params pcm;
struct snd_sof_pcm *spcm;
int ret;
/* get runtime PCM params using widget's stream name */
spcm = snd_sof_find_spcm_name(scomp, swidget->widget->sname);
if (!spcm) {
dev_err(scomp->dev, "Cannot find PCM for %s\n", swidget->widget->name);
return -EINVAL;
}
params = &spcm->params[dir];
/* set IPC PCM params */
memset(&pcm, 0, sizeof(pcm));
pcm.hdr.size = sizeof(pcm);
pcm.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS;
pcm.comp_id = swidget->comp_id;
pcm.params.hdr.size = sizeof(pcm.params);
pcm.params.direction = dir;
pcm.params.sample_valid_bytes = params_width(params) >> 3;
pcm.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED;
pcm.params.rate = params_rate(params);
pcm.params.channels = params_channels(params);
pcm.params.host_period_bytes = params_period_bytes(params);
/* set format */
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16:
pcm.params.frame_fmt = SOF_IPC_FRAME_S16_LE;
break;
case SNDRV_PCM_FORMAT_S24:
pcm.params.frame_fmt = SOF_IPC_FRAME_S24_4LE;
break;
case SNDRV_PCM_FORMAT_S32:
pcm.params.frame_fmt = SOF_IPC_FRAME_S32_LE;
break;
default:
return -EINVAL;
}
/* send IPC to the DSP */
ret = sof_ipc_tx_message(sdev->ipc, &pcm, sizeof(pcm),
&ipc_params_reply, sizeof(ipc_params_reply));
if (ret < 0)
dev_err(scomp->dev, "%s: PCM params failed for %s\n", __func__,
swidget->widget->name);
return ret;
}
/* send stream trigger ipc */
static int sof_ipc3_keyword_detect_trigger(struct snd_sof_widget *swidget, int cmd)
{
struct snd_soc_component *scomp = swidget->scomp;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct sof_ipc_stream stream;
struct sof_ipc_reply reply;
int ret;
/* set IPC stream params */
stream.hdr.size = sizeof(stream);
stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | cmd;
stream.comp_id = swidget->comp_id;
/* send IPC to the DSP */
ret = sof_ipc_tx_message(sdev->ipc, &stream, sizeof(stream), &reply, sizeof(reply));
if (ret < 0)
dev_err(scomp->dev, "%s: Failed to trigger %s\n", __func__, swidget->widget->name);
return ret;
}
static int sof_ipc3_keyword_dapm_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *k, int event)
{
struct snd_sof_widget *swidget = w->dobj.private;
struct snd_soc_component *scomp;
int stream = SNDRV_PCM_STREAM_CAPTURE;
struct snd_sof_pcm *spcm;
int ret = 0;
if (!swidget)
return 0;
scomp = swidget->scomp;
dev_dbg(scomp->dev, "received event %d for widget %s\n",
event, w->name);
/* get runtime PCM params using widget's stream name */
spcm = snd_sof_find_spcm_name(scomp, swidget->widget->sname);
if (!spcm) {
dev_err(scomp->dev, "%s: Cannot find PCM for %s\n", __func__,
swidget->widget->name);
return -EINVAL;
}
/* process events */
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
if (spcm->stream[stream].suspend_ignored) {
dev_dbg(scomp->dev, "PRE_PMU event ignored, KWD pipeline is already RUNNING\n");
return 0;
}
/* set pcm params */
ret = sof_ipc3_keyword_detect_pcm_params(swidget, stream);
if (ret < 0) {
dev_err(scomp->dev, "%s: Failed to set pcm params for widget %s\n",
__func__, swidget->widget->name);
break;
}
/* start trigger */
ret = sof_ipc3_keyword_detect_trigger(swidget, SOF_IPC_STREAM_TRIG_START);
if (ret < 0)
dev_err(scomp->dev, "%s: Failed to trigger widget %s\n", __func__,
swidget->widget->name);
break;
case SND_SOC_DAPM_POST_PMD:
if (spcm->stream[stream].suspend_ignored) {
dev_dbg(scomp->dev,
"POST_PMD event ignored, KWD pipeline will remain RUNNING\n");
return 0;
}
/* stop trigger */
ret = sof_ipc3_keyword_detect_trigger(swidget, SOF_IPC_STREAM_TRIG_STOP);
if (ret < 0)
dev_err(scomp->dev, "%s: Failed to trigger widget %s\n", __func__,
swidget->widget->name);
/* pcm free */
ret = sof_ipc3_keyword_detect_trigger(swidget, SOF_IPC_STREAM_PCM_FREE);
if (ret < 0)
dev_err(scomp->dev, "%s: Failed to free PCM for widget %s\n", __func__,
swidget->widget->name);
break;
default:
break;
}
return ret;
}
/* event handlers for keyword detect component */
static const struct snd_soc_tplg_widget_events sof_kwd_events[] = {
{SOF_KEYWORD_DETECT_DAPM_EVENT, sof_ipc3_keyword_dapm_event},
};
static int sof_ipc3_widget_bind_event(struct snd_soc_component *scomp,
struct snd_sof_widget *swidget, u16 event_type)
{
struct sof_ipc_comp *ipc_comp;
/* validate widget event type */
switch (event_type) {
case SOF_KEYWORD_DETECT_DAPM_EVENT:
/* only KEYWORD_DETECT comps should handle this */
if (swidget->id != snd_soc_dapm_effect)
break;
ipc_comp = swidget->private;
if (ipc_comp && ipc_comp->type != SOF_COMP_KEYWORD_DETECT)
break;
/* bind event to keyword detect comp */
return snd_soc_tplg_widget_bind_event(swidget->widget, sof_kwd_events,
ARRAY_SIZE(sof_kwd_events), event_type);
default:
break;
}
dev_err(scomp->dev, "Invalid event type %d for widget %s\n", event_type,
swidget->widget->name);
return -EINVAL;
}
static int sof_ipc3_complete_pipeline(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
{
struct sof_ipc_pipe_ready ready;
struct sof_ipc_reply reply;
int ret;
dev_dbg(sdev->dev, "tplg: complete pipeline %s id %d\n",
swidget->widget->name, swidget->comp_id);
memset(&ready, 0, sizeof(ready));
ready.hdr.size = sizeof(ready);
ready.hdr.cmd = SOF_IPC_GLB_TPLG_MSG | SOF_IPC_TPLG_PIPE_COMPLETE;
ready.comp_id = swidget->comp_id;
ret = sof_ipc_tx_message(sdev->ipc, &ready, sizeof(ready), &reply, sizeof(reply));
if (ret < 0)
return ret;
return 1;
}
static int sof_ipc3_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
{
struct sof_ipc_free ipc_free = {
.hdr = {
.size = sizeof(ipc_free),
.cmd = SOF_IPC_GLB_TPLG_MSG,
},
.id = swidget->comp_id,
};
struct sof_ipc_reply reply;
int ret;
if (!swidget->private)
return 0;
switch (swidget->id) {
case snd_soc_dapm_scheduler:
{
ipc_free.hdr.cmd |= SOF_IPC_TPLG_PIPE_FREE;
break;
}
case snd_soc_dapm_buffer:
ipc_free.hdr.cmd |= SOF_IPC_TPLG_BUFFER_FREE;
break;
default:
ipc_free.hdr.cmd |= SOF_IPC_TPLG_COMP_FREE;
break;
}
ret = sof_ipc_tx_message(sdev->ipc, &ipc_free, sizeof(ipc_free),
&reply, sizeof(reply));
if (ret < 0)
dev_err(sdev->dev, "failed to free widget %s\n", swidget->widget->name);
return ret;
}
static int sof_ipc3_dai_config(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget,
unsigned int flags, struct snd_sof_dai_config_data *data)
{
struct sof_ipc_fw_version *v = &sdev->fw_ready.version;
struct snd_sof_dai *dai = swidget->private;
struct sof_dai_private_data *private;
struct sof_ipc_dai_config *config;
struct sof_ipc_reply reply;
int ret = 0;
if (!dai || !dai->private) {
dev_err(sdev->dev, "No private data for DAI %s\n", swidget->widget->name);
return -EINVAL;
}
private = dai->private;
if (!private->dai_config) {
dev_err(sdev->dev, "No config for DAI %s\n", dai->name);
return -EINVAL;
}
config = &private->dai_config[dai->current_config];
if (!config) {
dev_err(sdev->dev, "Invalid current config for DAI %s\n", dai->name);
return -EINVAL;
}
switch (config->type) {
case SOF_DAI_INTEL_SSP:
/*
* DAI_CONFIG IPC during hw_params/hw_free for SSP DAI's is not supported in older
* firmware
*/
if (v->abi_version < SOF_ABI_VER(3, 18, 0) &&
((flags & SOF_DAI_CONFIG_FLAGS_HW_PARAMS) ||
(flags & SOF_DAI_CONFIG_FLAGS_HW_FREE)))
return 0;
break;
case SOF_DAI_INTEL_HDA:
if (data)
config->hda.link_dma_ch = data->dai_data;
break;
case SOF_DAI_INTEL_ALH:
if (data) {
config->dai_index = data->dai_index;
config->alh.stream_id = data->dai_data;
}
break;
default:
break;
}
config->flags = flags;
/* only send the IPC if the widget is set up in the DSP */
if (swidget->use_count > 0) {
ret = sof_ipc_tx_message(sdev->ipc, config, config->hdr.size,
&reply, sizeof(reply));
if (ret < 0)
dev_err(sdev->dev, "Failed to set dai config for %s\n", dai->name);
}
return ret;
}
static int sof_ipc3_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
{
struct sof_ipc_comp_reply reply;
int ret;
if (!swidget->private)
return 0;
switch (swidget->id) {
case snd_soc_dapm_dai_in:
case snd_soc_dapm_dai_out:
{
struct snd_sof_dai *dai = swidget->private;
struct sof_dai_private_data *dai_data = dai->private;
struct sof_ipc_comp *comp = &dai_data->comp_dai->comp;
ret = sof_ipc_tx_message(sdev->ipc, dai_data->comp_dai,
comp->hdr.size, &reply, sizeof(reply));
break;
}
case snd_soc_dapm_scheduler:
{
struct sof_ipc_pipe_new *pipeline;
pipeline = swidget->private;
ret = sof_ipc_tx_message(sdev->ipc, pipeline, sizeof(*pipeline),
&reply, sizeof(reply));
break;
}
default:
{
struct sof_ipc_cmd_hdr *hdr;
hdr = swidget->private;
ret = sof_ipc_tx_message(sdev->ipc, swidget->private, hdr->size,
&reply, sizeof(reply));
break;
}
}
if (ret < 0)
dev_err(sdev->dev, "Failed to setup widget %s\n", swidget->widget->name);
return ret;
}
static int sof_ipc3_set_up_all_pipelines(struct snd_sof_dev *sdev, bool verify)
{
struct sof_ipc_fw_version *v = &sdev->fw_ready.version;
struct snd_sof_widget *swidget;
struct snd_sof_route *sroute;
int ret;
/* restore pipeline components */
list_for_each_entry(swidget, &sdev->widget_list, list) {
/* only set up the widgets belonging to static pipelines */
if (!verify && swidget->dynamic_pipeline_widget)
continue;
/*
* For older firmware, skip scheduler widgets in this loop,
* sof_widget_setup() will be called in the 'complete pipeline' loop
*/
if (v->abi_version < SOF_ABI_VER(3, 19, 0) &&
swidget->id == snd_soc_dapm_scheduler)
continue;
/* update DAI config. The IPC will be sent in sof_widget_setup() */
if (WIDGET_IS_DAI(swidget->id)) {
struct snd_sof_dai *dai = swidget->private;
struct sof_dai_private_data *private;
struct sof_ipc_dai_config *config;
if (!dai || !dai->private)
continue;
private = dai->private;
if (!private->dai_config)
continue;
config = private->dai_config;
/*
* The link DMA channel would be invalidated for running
* streams but not for streams that were in the PAUSED
* state during suspend. So invalidate it here before setting
* the dai config in the DSP.
*/
if (config->type == SOF_DAI_INTEL_HDA)
config->hda.link_dma_ch = DMA_CHAN_INVALID;
}
ret = sof_widget_setup(sdev, swidget);
if (ret < 0)
return ret;
}
/* restore pipeline connections */
list_for_each_entry(sroute, &sdev->route_list, list) {
/* only set up routes belonging to static pipelines */
if (!verify && (sroute->src_widget->dynamic_pipeline_widget ||
sroute->sink_widget->dynamic_pipeline_widget))
continue;
/*
* For virtual routes, both sink and source are not buffer. IPC3 only supports
* connections between a buffer and a component. Ignore the rest.
*/
if (sroute->src_widget->id != snd_soc_dapm_buffer &&
sroute->sink_widget->id != snd_soc_dapm_buffer)
continue;
ret = sof_route_setup(sdev, sroute->src_widget->widget,
sroute->sink_widget->widget);
if (ret < 0) {
dev_err(sdev->dev, "%s: route set up failed\n", __func__);
return ret;
}
}
/* complete pipeline */
list_for_each_entry(swidget, &sdev->widget_list, list) {
switch (swidget->id) {
case snd_soc_dapm_scheduler:
/* only complete static pipelines */
if (!verify && swidget->dynamic_pipeline_widget)
continue;
if (v->abi_version < SOF_ABI_VER(3, 19, 0)) {
ret = sof_widget_setup(sdev, swidget);
if (ret < 0)
return ret;
}
swidget->spipe->complete = sof_ipc3_complete_pipeline(sdev, swidget);
if (swidget->spipe->complete < 0)
return swidget->spipe->complete;
break;
default:
break;
}
}
return 0;
}
/*
* Free the PCM, its associated widgets and set the prepared flag to false for all PCMs that
* did not get suspended(ex: paused streams) so the widgets can be set up again during resume.
*/
static int sof_tear_down_left_over_pipelines(struct snd_sof_dev *sdev)
{
struct snd_sof_widget *swidget;
struct snd_sof_pcm *spcm;
int dir, ret;
/*
* free all PCMs and their associated DAPM widgets if their connected DAPM widget
* list is not NULL. This should only be true for paused streams at this point.
* This is equivalent to the handling of FE DAI suspend trigger for running streams.
*/
list_for_each_entry(spcm, &sdev->pcm_list, list) {
for_each_pcm_streams(dir) {
struct snd_pcm_substream *substream = spcm->stream[dir].substream;
ASoC: SOF: pcm: do not free widgets during suspend trigger IPC3 and IPC4 have different requirements for the order in which the FE CPU and BE CPU DAI trigger callbacks must be invoked. With a regular PCM start/stop, pipeline widgets are set up during hw_params and freed during hw_free. But when the system is suspended when a PCM is running, pipeline widgets are freed during the SUSPEND trigger callback for the FE CPU DAI. In order to avoid freeing the pipeline widgets before the BE CPU DAI trigger is executed, the trigger order was modified in previous contributions in the PCM dai_link_fixup callback to make sure that the BE CPU DAI trigger stop/suspend is always invoked before the FE CPU DAI trigger. But this contradicts the firmware requirement for IPC4 w.r.t. ordering of pipeline triggers. So, remove the freeing of pipeline widgets during FE CPU DAI suspend trigger and handle it during system suspend when the tear_down_all_pipelines() IPC op is invoked. This will be followed up with a patch to fix the trigger order for IPC4. Signed-off-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com> Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> Reviewed-by: Bard Liao <yung-chuan.liao@linux.intel.com> Reviewed-by: Péter Ujfalusi <peter.ujfalusi@linux.intel.com> Signed-off-by: Peter Ujfalusi <peter.ujfalusi@linux.intel.com> Link: https://lore.kernel.org/r/20230127120031.10709-6-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown <broonie@kernel.org>
2023-01-27 12:00:18 +00:00
if (!substream || !substream->runtime || spcm->stream[dir].suspend_ignored)
continue;
if (spcm->stream[dir].list) {
ret = sof_pcm_stream_free(sdev, substream, spcm, dir, true);
if (ret < 0)
return ret;
}
}
}
/*
* free any left over DAI widgets. This is equivalent to the handling of suspend trigger
* for the BE DAI for running streams.
*/
list_for_each_entry(swidget, &sdev->widget_list, list)
if (WIDGET_IS_DAI(swidget->id) && swidget->use_count == 1) {
ret = sof_widget_free(sdev, swidget);
if (ret < 0)
return ret;
}
return 0;
}
/*
* For older firmware, this function doesn't free widgets for static pipelines during suspend.
* It only resets use_count for all widgets.
*/
static int sof_ipc3_tear_down_all_pipelines(struct snd_sof_dev *sdev, bool verify)
{
struct sof_ipc_fw_version *v = &sdev->fw_ready.version;
struct snd_sof_widget *swidget;
struct snd_sof_route *sroute;
ASoC: SOF: ipc3-topology: use old pipeline teardown flow with SOF2.1 and older Originally in commit b2ebcf42a48f ("ASoC: SOF: free widgets in sof_tear_down_pipelines() for static pipelines"), freeing of pipeline components at suspend was only done with recent FW as there were known limitations in older firmware versions. Tests show that if static pipelines are used, i.e. all pipelines are setup whenever firmware is powered up, the reverse action of freeing all components at power down, leads to firmware failures with also SOF2.0 and SOF2.1 based firmware. The problems can be specific to certain topologies with e.g. components not prepared to be freed at suspend (as this did not happen with older SOF kernels). To avoid hitting these problems when kernel is upgraded and used with an older firmware, bump the firmware requirement to SOF2.2 or newer. If an older firmware is used, and pipeline is a static one, do not free the components at suspend. This ensures the suspend flow remains backwards compatible with older firmware versions. This limitation does not apply if the product configuration is updated to dynamic pipelines. The limitation is not linked to firmware ABI, as the interface to free pipeline components has been available already before ABI3.19. The problem is in the implementation, so firmware version should be used to decide whether it is safe to use the newer flow or not. This patch adds a new SOF_FW_VER() macro to compare SOF firmware release versions. Link: https://github.com/thesofproject/sof/issues/6475 Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com> Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> Reviewed-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com> Reviewed-by: Péter Ujfalusi <peter.ujfalusi@linux.intel.com> Link: https://lore.kernel.org/r/20221101114913.1292671-1-kai.vehmanen@linux.intel.com Signed-off-by: Mark Brown <broonie@kernel.org>
2022-11-01 11:49:13 +00:00
bool dyn_widgets = false;
int ret;
/*
* This function is called during suspend and for one-time topology verification during
* first boot. In both cases, there is no need to protect swidget->use_count and
* sroute->setup because during suspend all running streams are suspended and during
* topology loading the sound card unavailable to open PCMs.
*/
list_for_each_entry(swidget, &sdev->widget_list, list) {
ASoC: SOF: ipc3-topology: use old pipeline teardown flow with SOF2.1 and older Originally in commit b2ebcf42a48f ("ASoC: SOF: free widgets in sof_tear_down_pipelines() for static pipelines"), freeing of pipeline components at suspend was only done with recent FW as there were known limitations in older firmware versions. Tests show that if static pipelines are used, i.e. all pipelines are setup whenever firmware is powered up, the reverse action of freeing all components at power down, leads to firmware failures with also SOF2.0 and SOF2.1 based firmware. The problems can be specific to certain topologies with e.g. components not prepared to be freed at suspend (as this did not happen with older SOF kernels). To avoid hitting these problems when kernel is upgraded and used with an older firmware, bump the firmware requirement to SOF2.2 or newer. If an older firmware is used, and pipeline is a static one, do not free the components at suspend. This ensures the suspend flow remains backwards compatible with older firmware versions. This limitation does not apply if the product configuration is updated to dynamic pipelines. The limitation is not linked to firmware ABI, as the interface to free pipeline components has been available already before ABI3.19. The problem is in the implementation, so firmware version should be used to decide whether it is safe to use the newer flow or not. This patch adds a new SOF_FW_VER() macro to compare SOF firmware release versions. Link: https://github.com/thesofproject/sof/issues/6475 Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com> Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> Reviewed-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com> Reviewed-by: Péter Ujfalusi <peter.ujfalusi@linux.intel.com> Link: https://lore.kernel.org/r/20221101114913.1292671-1-kai.vehmanen@linux.intel.com Signed-off-by: Mark Brown <broonie@kernel.org>
2022-11-01 11:49:13 +00:00
if (swidget->dynamic_pipeline_widget) {
dyn_widgets = true;
continue;
ASoC: SOF: ipc3-topology: use old pipeline teardown flow with SOF2.1 and older Originally in commit b2ebcf42a48f ("ASoC: SOF: free widgets in sof_tear_down_pipelines() for static pipelines"), freeing of pipeline components at suspend was only done with recent FW as there were known limitations in older firmware versions. Tests show that if static pipelines are used, i.e. all pipelines are setup whenever firmware is powered up, the reverse action of freeing all components at power down, leads to firmware failures with also SOF2.0 and SOF2.1 based firmware. The problems can be specific to certain topologies with e.g. components not prepared to be freed at suspend (as this did not happen with older SOF kernels). To avoid hitting these problems when kernel is upgraded and used with an older firmware, bump the firmware requirement to SOF2.2 or newer. If an older firmware is used, and pipeline is a static one, do not free the components at suspend. This ensures the suspend flow remains backwards compatible with older firmware versions. This limitation does not apply if the product configuration is updated to dynamic pipelines. The limitation is not linked to firmware ABI, as the interface to free pipeline components has been available already before ABI3.19. The problem is in the implementation, so firmware version should be used to decide whether it is safe to use the newer flow or not. This patch adds a new SOF_FW_VER() macro to compare SOF firmware release versions. Link: https://github.com/thesofproject/sof/issues/6475 Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com> Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> Reviewed-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com> Reviewed-by: Péter Ujfalusi <peter.ujfalusi@linux.intel.com> Link: https://lore.kernel.org/r/20221101114913.1292671-1-kai.vehmanen@linux.intel.com Signed-off-by: Mark Brown <broonie@kernel.org>
2022-11-01 11:49:13 +00:00
}
ASoC: SOF: ipc3-topology: use old pipeline teardown flow with SOF2.1 and older Originally in commit b2ebcf42a48f ("ASoC: SOF: free widgets in sof_tear_down_pipelines() for static pipelines"), freeing of pipeline components at suspend was only done with recent FW as there were known limitations in older firmware versions. Tests show that if static pipelines are used, i.e. all pipelines are setup whenever firmware is powered up, the reverse action of freeing all components at power down, leads to firmware failures with also SOF2.0 and SOF2.1 based firmware. The problems can be specific to certain topologies with e.g. components not prepared to be freed at suspend (as this did not happen with older SOF kernels). To avoid hitting these problems when kernel is upgraded and used with an older firmware, bump the firmware requirement to SOF2.2 or newer. If an older firmware is used, and pipeline is a static one, do not free the components at suspend. This ensures the suspend flow remains backwards compatible with older firmware versions. This limitation does not apply if the product configuration is updated to dynamic pipelines. The limitation is not linked to firmware ABI, as the interface to free pipeline components has been available already before ABI3.19. The problem is in the implementation, so firmware version should be used to decide whether it is safe to use the newer flow or not. This patch adds a new SOF_FW_VER() macro to compare SOF firmware release versions. Link: https://github.com/thesofproject/sof/issues/6475 Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com> Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> Reviewed-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com> Reviewed-by: Péter Ujfalusi <peter.ujfalusi@linux.intel.com> Link: https://lore.kernel.org/r/20221101114913.1292671-1-kai.vehmanen@linux.intel.com Signed-off-by: Mark Brown <broonie@kernel.org>
2022-11-01 11:49:13 +00:00
/* Do not free widgets for static pipelines with FW older than SOF2.2 */
if (!verify && !swidget->dynamic_pipeline_widget &&
ASoC: SOF: ipc3-topology: use old pipeline teardown flow with SOF2.1 and older Originally in commit b2ebcf42a48f ("ASoC: SOF: free widgets in sof_tear_down_pipelines() for static pipelines"), freeing of pipeline components at suspend was only done with recent FW as there were known limitations in older firmware versions. Tests show that if static pipelines are used, i.e. all pipelines are setup whenever firmware is powered up, the reverse action of freeing all components at power down, leads to firmware failures with also SOF2.0 and SOF2.1 based firmware. The problems can be specific to certain topologies with e.g. components not prepared to be freed at suspend (as this did not happen with older SOF kernels). To avoid hitting these problems when kernel is upgraded and used with an older firmware, bump the firmware requirement to SOF2.2 or newer. If an older firmware is used, and pipeline is a static one, do not free the components at suspend. This ensures the suspend flow remains backwards compatible with older firmware versions. This limitation does not apply if the product configuration is updated to dynamic pipelines. The limitation is not linked to firmware ABI, as the interface to free pipeline components has been available already before ABI3.19. The problem is in the implementation, so firmware version should be used to decide whether it is safe to use the newer flow or not. This patch adds a new SOF_FW_VER() macro to compare SOF firmware release versions. Link: https://github.com/thesofproject/sof/issues/6475 Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com> Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> Reviewed-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com> Reviewed-by: Péter Ujfalusi <peter.ujfalusi@linux.intel.com> Link: https://lore.kernel.org/r/20221101114913.1292671-1-kai.vehmanen@linux.intel.com Signed-off-by: Mark Brown <broonie@kernel.org>
2022-11-01 11:49:13 +00:00
SOF_FW_VER(v->major, v->minor, v->micro) < SOF_FW_VER(2, 2, 0)) {
mutex_lock(&swidget->setup_mutex);
swidget->use_count = 0;
mutex_unlock(&swidget->setup_mutex);
if (swidget->spipe)
swidget->spipe->complete = 0;
continue;
}
ret = sof_widget_free(sdev, swidget);
if (ret < 0)
return ret;
}
/*
* Tear down all pipelines associated with PCMs that did not get suspended
* and unset the prepare flag so that they can be set up again during resume.
ASoC: SOF: ipc3-topology: use old pipeline teardown flow with SOF2.1 and older Originally in commit b2ebcf42a48f ("ASoC: SOF: free widgets in sof_tear_down_pipelines() for static pipelines"), freeing of pipeline components at suspend was only done with recent FW as there were known limitations in older firmware versions. Tests show that if static pipelines are used, i.e. all pipelines are setup whenever firmware is powered up, the reverse action of freeing all components at power down, leads to firmware failures with also SOF2.0 and SOF2.1 based firmware. The problems can be specific to certain topologies with e.g. components not prepared to be freed at suspend (as this did not happen with older SOF kernels). To avoid hitting these problems when kernel is upgraded and used with an older firmware, bump the firmware requirement to SOF2.2 or newer. If an older firmware is used, and pipeline is a static one, do not free the components at suspend. This ensures the suspend flow remains backwards compatible with older firmware versions. This limitation does not apply if the product configuration is updated to dynamic pipelines. The limitation is not linked to firmware ABI, as the interface to free pipeline components has been available already before ABI3.19. The problem is in the implementation, so firmware version should be used to decide whether it is safe to use the newer flow or not. This patch adds a new SOF_FW_VER() macro to compare SOF firmware release versions. Link: https://github.com/thesofproject/sof/issues/6475 Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com> Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> Reviewed-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com> Reviewed-by: Péter Ujfalusi <peter.ujfalusi@linux.intel.com> Link: https://lore.kernel.org/r/20221101114913.1292671-1-kai.vehmanen@linux.intel.com Signed-off-by: Mark Brown <broonie@kernel.org>
2022-11-01 11:49:13 +00:00
* Skip this step for older firmware unless topology has any
* dynamic pipeline (in which case the step is mandatory).
*/
ASoC: SOF: ipc3-topology: use old pipeline teardown flow with SOF2.1 and older Originally in commit b2ebcf42a48f ("ASoC: SOF: free widgets in sof_tear_down_pipelines() for static pipelines"), freeing of pipeline components at suspend was only done with recent FW as there were known limitations in older firmware versions. Tests show that if static pipelines are used, i.e. all pipelines are setup whenever firmware is powered up, the reverse action of freeing all components at power down, leads to firmware failures with also SOF2.0 and SOF2.1 based firmware. The problems can be specific to certain topologies with e.g. components not prepared to be freed at suspend (as this did not happen with older SOF kernels). To avoid hitting these problems when kernel is upgraded and used with an older firmware, bump the firmware requirement to SOF2.2 or newer. If an older firmware is used, and pipeline is a static one, do not free the components at suspend. This ensures the suspend flow remains backwards compatible with older firmware versions. This limitation does not apply if the product configuration is updated to dynamic pipelines. The limitation is not linked to firmware ABI, as the interface to free pipeline components has been available already before ABI3.19. The problem is in the implementation, so firmware version should be used to decide whether it is safe to use the newer flow or not. This patch adds a new SOF_FW_VER() macro to compare SOF firmware release versions. Link: https://github.com/thesofproject/sof/issues/6475 Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com> Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> Reviewed-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com> Reviewed-by: Péter Ujfalusi <peter.ujfalusi@linux.intel.com> Link: https://lore.kernel.org/r/20221101114913.1292671-1-kai.vehmanen@linux.intel.com Signed-off-by: Mark Brown <broonie@kernel.org>
2022-11-01 11:49:13 +00:00
if (!verify && (dyn_widgets || SOF_FW_VER(v->major, v->minor, v->micro) >=
SOF_FW_VER(2, 2, 0))) {
ret = sof_tear_down_left_over_pipelines(sdev);
if (ret < 0) {
dev_err(sdev->dev, "failed to tear down paused pipelines\n");
return ret;
}
}
list_for_each_entry(sroute, &sdev->route_list, list)
sroute->setup = false;
/*
* before suspending, make sure the refcounts are all zeroed out. There's no way
* to recover at this point but this will help root cause bad sequences leading to
* more issues on resume
*/
list_for_each_entry(swidget, &sdev->widget_list, list) {
if (swidget->use_count != 0) {
dev_err(sdev->dev, "%s: widget %s is still in use: count %d\n",
__func__, swidget->widget->name, swidget->use_count);
}
}
return 0;
}
static int sof_ipc3_dai_get_clk(struct snd_sof_dev *sdev, struct snd_sof_dai *dai, int clk_type)
{
struct sof_dai_private_data *private = dai->private;
if (!private || !private->dai_config)
return 0;
switch (private->dai_config->type) {
case SOF_DAI_INTEL_SSP:
switch (clk_type) {
case SOF_DAI_CLK_INTEL_SSP_MCLK:
return private->dai_config->ssp.mclk_rate;
case SOF_DAI_CLK_INTEL_SSP_BCLK:
return private->dai_config->ssp.bclk_rate;
default:
break;
}
dev_err(sdev->dev, "fail to get SSP clk %d rate\n", clk_type);
break;
default:
/* not yet implemented for platforms other than the above */
dev_err(sdev->dev, "DAI type %d not supported yet!\n", private->dai_config->type);
break;
}
return -EINVAL;
}
static int sof_ipc3_parse_manifest(struct snd_soc_component *scomp, int index,
struct snd_soc_tplg_manifest *man)
{
u32 size = le32_to_cpu(man->priv.size);
u32 abi_version;
/* backward compatible with tplg without ABI info */
if (!size) {
dev_dbg(scomp->dev, "No topology ABI info\n");
return 0;
}
if (size != SOF_IPC3_TPLG_ABI_SIZE) {
dev_err(scomp->dev, "%s: Invalid topology ABI size: %u\n",
__func__, size);
return -EINVAL;
}
dev_info(scomp->dev,
"Topology: ABI %d:%d:%d Kernel ABI %d:%d:%d\n",
man->priv.data[0], man->priv.data[1], man->priv.data[2],
SOF_ABI_MAJOR, SOF_ABI_MINOR, SOF_ABI_PATCH);
abi_version = SOF_ABI_VER(man->priv.data[0], man->priv.data[1], man->priv.data[2]);
if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, abi_version)) {
dev_err(scomp->dev, "%s: Incompatible topology ABI version\n", __func__);
return -EINVAL;
}
if (IS_ENABLED(CONFIG_SND_SOC_SOF_STRICT_ABI_CHECKS) &&
SOF_ABI_VERSION_MINOR(abi_version) > SOF_ABI_MINOR) {
dev_err(scomp->dev, "%s: Topology ABI is more recent than kernel\n", __func__);
return -EINVAL;
}
return 0;
}
static int sof_ipc3_link_setup(struct snd_sof_dev *sdev, struct snd_soc_dai_link *link)
{
if (link->no_pcm)
return 0;
/*
* set default trigger order for all links. Exceptions to
* the rule will be handled in sof_pcm_dai_link_fixup()
* For playback, the sequence is the following: start FE,
* start BE, stop BE, stop FE; for Capture the sequence is
* inverted start BE, start FE, stop FE, stop BE
*/
link->trigger[SNDRV_PCM_STREAM_PLAYBACK] = SND_SOC_DPCM_TRIGGER_PRE;
link->trigger[SNDRV_PCM_STREAM_CAPTURE] = SND_SOC_DPCM_TRIGGER_POST;
return 0;
}
/* token list for each topology object */
static enum sof_tokens host_token_list[] = {
SOF_CORE_TOKENS,
SOF_COMP_EXT_TOKENS,
SOF_PCM_TOKENS,
SOF_COMP_TOKENS,
};
static enum sof_tokens comp_generic_token_list[] = {
SOF_CORE_TOKENS,
SOF_COMP_EXT_TOKENS,
SOF_COMP_TOKENS,
};
static enum sof_tokens buffer_token_list[] = {
SOF_BUFFER_TOKENS,
};
static enum sof_tokens pipeline_token_list[] = {
SOF_CORE_TOKENS,
SOF_COMP_EXT_TOKENS,
SOF_PIPELINE_TOKENS,
SOF_SCHED_TOKENS,
};
static enum sof_tokens asrc_token_list[] = {
SOF_CORE_TOKENS,
SOF_COMP_EXT_TOKENS,
SOF_ASRC_TOKENS,
SOF_COMP_TOKENS,
};
static enum sof_tokens src_token_list[] = {
SOF_CORE_TOKENS,
SOF_COMP_EXT_TOKENS,
SOF_SRC_TOKENS,
SOF_COMP_TOKENS
};
static enum sof_tokens pga_token_list[] = {
SOF_CORE_TOKENS,
SOF_COMP_EXT_TOKENS,
SOF_VOLUME_TOKENS,
SOF_COMP_TOKENS,
};
static enum sof_tokens dai_token_list[] = {
SOF_CORE_TOKENS,
SOF_COMP_EXT_TOKENS,
SOF_DAI_TOKENS,
SOF_COMP_TOKENS,
};
static enum sof_tokens process_token_list[] = {
SOF_CORE_TOKENS,
SOF_COMP_EXT_TOKENS,
SOF_PROCESS_TOKENS,
SOF_COMP_TOKENS,
};
static const struct sof_ipc_tplg_widget_ops tplg_ipc3_widget_ops[SND_SOC_DAPM_TYPE_COUNT] = {
[snd_soc_dapm_aif_in] = {sof_ipc3_widget_setup_comp_host, sof_ipc3_widget_free_comp,
host_token_list, ARRAY_SIZE(host_token_list), NULL},
[snd_soc_dapm_aif_out] = {sof_ipc3_widget_setup_comp_host, sof_ipc3_widget_free_comp,
host_token_list, ARRAY_SIZE(host_token_list), NULL},
[snd_soc_dapm_dai_in] = {sof_ipc3_widget_setup_comp_dai, sof_ipc3_widget_free_comp_dai,
dai_token_list, ARRAY_SIZE(dai_token_list), NULL},
[snd_soc_dapm_dai_out] = {sof_ipc3_widget_setup_comp_dai, sof_ipc3_widget_free_comp_dai,
dai_token_list, ARRAY_SIZE(dai_token_list), NULL},
[snd_soc_dapm_buffer] = {sof_ipc3_widget_setup_comp_buffer, sof_ipc3_widget_free_comp,
buffer_token_list, ARRAY_SIZE(buffer_token_list), NULL},
[snd_soc_dapm_mixer] = {sof_ipc3_widget_setup_comp_mixer, sof_ipc3_widget_free_comp,
comp_generic_token_list, ARRAY_SIZE(comp_generic_token_list),
NULL},
[snd_soc_dapm_src] = {sof_ipc3_widget_setup_comp_src, sof_ipc3_widget_free_comp,
src_token_list, ARRAY_SIZE(src_token_list), NULL},
[snd_soc_dapm_asrc] = {sof_ipc3_widget_setup_comp_asrc, sof_ipc3_widget_free_comp,
asrc_token_list, ARRAY_SIZE(asrc_token_list), NULL},
[snd_soc_dapm_siggen] = {sof_ipc3_widget_setup_comp_tone, sof_ipc3_widget_free_comp,
comp_generic_token_list, ARRAY_SIZE(comp_generic_token_list),
NULL},
[snd_soc_dapm_scheduler] = {sof_ipc3_widget_setup_comp_pipeline, sof_ipc3_widget_free_comp,
pipeline_token_list, ARRAY_SIZE(pipeline_token_list), NULL},
[snd_soc_dapm_pga] = {sof_ipc3_widget_setup_comp_pga, sof_ipc3_widget_free_comp,
pga_token_list, ARRAY_SIZE(pga_token_list), NULL},
[snd_soc_dapm_mux] = {sof_ipc3_widget_setup_comp_mux, sof_ipc3_widget_free_comp,
comp_generic_token_list, ARRAY_SIZE(comp_generic_token_list), NULL},
[snd_soc_dapm_demux] = {sof_ipc3_widget_setup_comp_mux, sof_ipc3_widget_free_comp,
comp_generic_token_list, ARRAY_SIZE(comp_generic_token_list),
NULL},
[snd_soc_dapm_effect] = {sof_widget_update_ipc_comp_process, sof_ipc3_widget_free_comp,
process_token_list, ARRAY_SIZE(process_token_list),
sof_ipc3_widget_bind_event},
};
const struct sof_ipc_tplg_ops ipc3_tplg_ops = {
.widget = tplg_ipc3_widget_ops,
.control = &tplg_ipc3_control_ops,
.route_setup = sof_ipc3_route_setup,
.control_setup = sof_ipc3_control_setup,
.control_free = sof_ipc3_control_free,
.pipeline_complete = sof_ipc3_complete_pipeline,
.token_list = ipc3_token_list,
.widget_free = sof_ipc3_widget_free,
.widget_setup = sof_ipc3_widget_setup,
.dai_config = sof_ipc3_dai_config,
.dai_get_clk = sof_ipc3_dai_get_clk,
.set_up_all_pipelines = sof_ipc3_set_up_all_pipelines,
.tear_down_all_pipelines = sof_ipc3_tear_down_all_pipelines,
.parse_manifest = sof_ipc3_parse_manifest,
.link_setup = sof_ipc3_link_setup,
};