// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) // // This file is provided under a dual BSD/GPLv2 license. When using or // redistributing this file, you may do so under either license. // // Copyright(c) 2022 Intel Corporation. All rights reserved. // // #include #include #include #include #include "sof-priv.h" #include "sof-audio.h" #include "ipc4-priv.h" #include "ipc4-topology.h" #include "ops.h" #define SOF_IPC4_GAIN_PARAM_ID 0 #define SOF_IPC4_TPLG_ABI_SIZE 6 static DEFINE_IDA(alh_group_ida); static const struct sof_topology_token ipc4_sched_tokens[] = { {SOF_TKN_SCHED_LP_MODE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, offsetof(struct sof_ipc4_pipeline, lp_mode)} }; static const struct sof_topology_token pipeline_tokens[] = { {SOF_TKN_SCHED_DYNAMIC_PIPELINE, SND_SOC_TPLG_TUPLE_TYPE_BOOL, get_token_u16, offsetof(struct snd_sof_widget, dynamic_pipeline_widget)}, }; static const struct sof_topology_token ipc4_comp_tokens[] = { {SOF_TKN_COMP_CPC, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, offsetof(struct sof_ipc4_base_module_cfg, cpc)}, {SOF_TKN_COMP_IS_PAGES, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, offsetof(struct sof_ipc4_base_module_cfg, is_pages)}, }; static const struct sof_topology_token ipc4_audio_format_buffer_size_tokens[] = { {SOF_TKN_CAVS_AUDIO_FORMAT_IBS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, offsetof(struct sof_ipc4_base_module_cfg, ibs)}, {SOF_TKN_CAVS_AUDIO_FORMAT_OBS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, offsetof(struct sof_ipc4_base_module_cfg, obs)}, }; static const struct sof_topology_token ipc4_in_audio_format_tokens[] = { {SOF_TKN_CAVS_AUDIO_FORMAT_IN_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, offsetof(struct sof_ipc4_audio_format, sampling_frequency)}, {SOF_TKN_CAVS_AUDIO_FORMAT_IN_BIT_DEPTH, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, offsetof(struct sof_ipc4_audio_format, bit_depth)}, {SOF_TKN_CAVS_AUDIO_FORMAT_IN_CH_MAP, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, offsetof(struct sof_ipc4_audio_format, ch_map)}, {SOF_TKN_CAVS_AUDIO_FORMAT_IN_CH_CFG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, offsetof(struct sof_ipc4_audio_format, ch_cfg)}, {SOF_TKN_CAVS_AUDIO_FORMAT_IN_INTERLEAVING_STYLE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, offsetof(struct sof_ipc4_audio_format, interleaving_style)}, {SOF_TKN_CAVS_AUDIO_FORMAT_IN_FMT_CFG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, offsetof(struct sof_ipc4_audio_format, fmt_cfg)}, }; static const struct sof_topology_token ipc4_out_audio_format_tokens[] = { {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, offsetof(struct sof_ipc4_audio_format, sampling_frequency)}, {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_BIT_DEPTH, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, offsetof(struct sof_ipc4_audio_format, bit_depth)}, {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_CH_MAP, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, offsetof(struct sof_ipc4_audio_format, ch_map)}, {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_CH_CFG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, offsetof(struct sof_ipc4_audio_format, ch_cfg)}, {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_INTERLEAVING_STYLE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, offsetof(struct sof_ipc4_audio_format, interleaving_style)}, {SOF_TKN_CAVS_AUDIO_FORMAT_OUT_FMT_CFG, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, offsetof(struct sof_ipc4_audio_format, fmt_cfg)}, }; static const struct sof_topology_token ipc4_copier_gateway_cfg_tokens[] = { {SOF_TKN_CAVS_AUDIO_FORMAT_DMA_BUFFER_SIZE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, 0}, }; static const struct sof_topology_token ipc4_copier_tokens[] = { {SOF_TKN_INTEL_COPIER_NODE_TYPE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, 0}, }; static const struct sof_topology_token ipc4_audio_fmt_num_tokens[] = { {SOF_TKN_COMP_NUM_AUDIO_FORMATS, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, 0}, }; static const struct sof_topology_token dai_tokens[] = { {SOF_TKN_DAI_TYPE, SND_SOC_TPLG_TUPLE_TYPE_STRING, get_token_dai_type, offsetof(struct sof_ipc4_copier, dai_type)}, {SOF_TKN_DAI_INDEX, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, offsetof(struct sof_ipc4_copier, dai_index)}, }; /* Component extended tokens */ static const struct sof_topology_token comp_ext_tokens[] = { {SOF_TKN_COMP_UUID, SND_SOC_TPLG_TUPLE_TYPE_UUID, get_token_uuid, offsetof(struct snd_sof_widget, uuid)}, }; static const struct sof_topology_token gain_tokens[] = { {SOF_TKN_GAIN_RAMP_TYPE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, offsetof(struct sof_ipc4_gain_data, curve_type)}, {SOF_TKN_GAIN_RAMP_DURATION, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, offsetof(struct sof_ipc4_gain_data, curve_duration_l)}, {SOF_TKN_GAIN_VAL, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, offsetof(struct sof_ipc4_gain_data, init_val)}, }; /* SRC */ static const struct sof_topology_token src_tokens[] = { {SOF_TKN_SRC_RATE_OUT, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, offsetof(struct sof_ipc4_src, sink_rate)}, }; static const struct sof_token_info ipc4_token_list[SOF_TOKEN_COUNT] = { [SOF_DAI_TOKENS] = {"DAI tokens", dai_tokens, ARRAY_SIZE(dai_tokens)}, [SOF_PIPELINE_TOKENS] = {"Pipeline tokens", pipeline_tokens, ARRAY_SIZE(pipeline_tokens)}, [SOF_SCHED_TOKENS] = {"Scheduler tokens", ipc4_sched_tokens, ARRAY_SIZE(ipc4_sched_tokens)}, [SOF_COMP_EXT_TOKENS] = {"Comp extended tokens", comp_ext_tokens, ARRAY_SIZE(comp_ext_tokens)}, [SOF_COMP_TOKENS] = {"IPC4 Component tokens", ipc4_comp_tokens, ARRAY_SIZE(ipc4_comp_tokens)}, [SOF_IN_AUDIO_FORMAT_TOKENS] = {"IPC4 Input Audio format tokens", ipc4_in_audio_format_tokens, ARRAY_SIZE(ipc4_in_audio_format_tokens)}, [SOF_OUT_AUDIO_FORMAT_TOKENS] = {"IPC4 Output Audio format tokens", ipc4_out_audio_format_tokens, ARRAY_SIZE(ipc4_out_audio_format_tokens)}, [SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS] = {"IPC4 Audio format buffer size tokens", ipc4_audio_format_buffer_size_tokens, ARRAY_SIZE(ipc4_audio_format_buffer_size_tokens)}, [SOF_COPIER_GATEWAY_CFG_TOKENS] = {"IPC4 Copier gateway config tokens", ipc4_copier_gateway_cfg_tokens, ARRAY_SIZE(ipc4_copier_gateway_cfg_tokens)}, [SOF_COPIER_TOKENS] = {"IPC4 Copier tokens", ipc4_copier_tokens, ARRAY_SIZE(ipc4_copier_tokens)}, [SOF_AUDIO_FMT_NUM_TOKENS] = {"IPC4 Audio format number tokens", ipc4_audio_fmt_num_tokens, ARRAY_SIZE(ipc4_audio_fmt_num_tokens)}, [SOF_GAIN_TOKENS] = {"Gain tokens", gain_tokens, ARRAY_SIZE(gain_tokens)}, [SOF_SRC_TOKENS] = {"SRC tokens", src_tokens, ARRAY_SIZE(src_tokens)}, }; static void sof_ipc4_dbg_audio_format(struct device *dev, struct sof_ipc4_audio_format *format, size_t object_size, int num_format) { struct sof_ipc4_audio_format *fmt; void *ptr = format; int i; for (i = 0; i < num_format; i++, ptr = (u8 *)ptr + object_size) { fmt = ptr; dev_dbg(dev, " #%d: %uHz, %ubit (ch_map %#x ch_cfg %u interleaving_style %u fmt_cfg %#x)\n", i, fmt->sampling_frequency, fmt->bit_depth, fmt->ch_map, fmt->ch_cfg, fmt->interleaving_style, fmt->fmt_cfg); } } /** * sof_ipc4_get_audio_fmt - get available audio formats from swidget->tuples * @scomp: pointer to pointer to SOC component * @swidget: pointer to struct snd_sof_widget containing tuples * @available_fmt: pointer to struct sof_ipc4_available_audio_format being filling in * @has_out_format: true if available_fmt contains output format * * Return: 0 if successful */ static int sof_ipc4_get_audio_fmt(struct snd_soc_component *scomp, struct snd_sof_widget *swidget, struct sof_ipc4_available_audio_format *available_fmt, bool has_out_format) { struct sof_ipc4_base_module_cfg *base_config; struct sof_ipc4_audio_format *out_format; int audio_fmt_num = 0; int ret, i; ret = sof_update_ipc_object(scomp, &audio_fmt_num, SOF_AUDIO_FMT_NUM_TOKENS, swidget->tuples, swidget->num_tuples, sizeof(audio_fmt_num), 1); if (ret || audio_fmt_num <= 0) { dev_err(scomp->dev, "Invalid number of audio formats: %d\n", audio_fmt_num); return -EINVAL; } available_fmt->audio_fmt_num = audio_fmt_num; dev_dbg(scomp->dev, "Number of audio formats: %d\n", available_fmt->audio_fmt_num); base_config = kcalloc(available_fmt->audio_fmt_num, sizeof(*base_config), GFP_KERNEL); if (!base_config) return -ENOMEM; /* set cpc and is_pages for all base_cfg */ for (i = 0; i < available_fmt->audio_fmt_num; i++) { ret = sof_update_ipc_object(scomp, &base_config[i], SOF_COMP_TOKENS, swidget->tuples, swidget->num_tuples, sizeof(*base_config), 1); if (ret) { dev_err(scomp->dev, "parse comp tokens failed %d\n", ret); goto err_in; } } /* copy the ibs/obs for each base_cfg */ ret = sof_update_ipc_object(scomp, base_config, SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS, swidget->tuples, swidget->num_tuples, sizeof(*base_config), available_fmt->audio_fmt_num); if (ret) { dev_err(scomp->dev, "parse buffer size tokens failed %d\n", ret); goto err_in; } for (i = 0; i < available_fmt->audio_fmt_num; i++) dev_dbg(scomp->dev, "%d: ibs: %d obs: %d cpc: %d is_pages: %d\n", i, base_config[i].ibs, base_config[i].obs, base_config[i].cpc, base_config[i].is_pages); ret = sof_update_ipc_object(scomp, &base_config->audio_fmt, SOF_IN_AUDIO_FORMAT_TOKENS, swidget->tuples, swidget->num_tuples, sizeof(*base_config), available_fmt->audio_fmt_num); if (ret) { dev_err(scomp->dev, "parse base_config audio_fmt tokens failed %d\n", ret); goto err_in; } dev_dbg(scomp->dev, "Get input audio formats for %s\n", swidget->widget->name); sof_ipc4_dbg_audio_format(scomp->dev, &base_config->audio_fmt, sizeof(*base_config), available_fmt->audio_fmt_num); available_fmt->base_config = base_config; if (!has_out_format) return 0; out_format = kcalloc(available_fmt->audio_fmt_num, sizeof(*out_format), GFP_KERNEL); if (!out_format) { ret = -ENOMEM; goto err_in; } ret = sof_update_ipc_object(scomp, out_format, SOF_OUT_AUDIO_FORMAT_TOKENS, swidget->tuples, swidget->num_tuples, sizeof(*out_format), available_fmt->audio_fmt_num); if (ret) { dev_err(scomp->dev, "parse output audio_fmt tokens failed\n"); goto err_out; } available_fmt->out_audio_fmt = out_format; dev_dbg(scomp->dev, "Get output audio formats for %s\n", swidget->widget->name); sof_ipc4_dbg_audio_format(scomp->dev, out_format, sizeof(*out_format), available_fmt->audio_fmt_num); return 0; err_out: kfree(out_format); err_in: kfree(base_config); return ret; } /* release the memory allocated in sof_ipc4_get_audio_fmt */ static void sof_ipc4_free_audio_fmt(struct sof_ipc4_available_audio_format *available_fmt) { kfree(available_fmt->base_config); available_fmt->base_config = NULL; kfree(available_fmt->out_audio_fmt); available_fmt->out_audio_fmt = NULL; } static void sof_ipc4_widget_free_comp(struct snd_sof_widget *swidget) { kfree(swidget->private); } static int sof_ipc4_widget_set_module_info(struct snd_sof_widget *swidget) { struct snd_soc_component *scomp = swidget->scomp; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct sof_ipc4_fw_data *ipc4_data = sdev->private; struct sof_ipc4_fw_module *fw_modules = ipc4_data->fw_modules; int i; if (!fw_modules) { dev_err(sdev->dev, "no fw_module information\n"); return -EINVAL; } /* set module info */ for (i = 0; i < ipc4_data->num_fw_modules; i++) { if (guid_equal(&swidget->uuid, &fw_modules[i].man4_module_entry.uuid)) { swidget->module_info = &fw_modules[i]; return 0; } } dev_err(sdev->dev, "failed to find module info for widget %s with UUID %pUL\n", swidget->widget->name, &swidget->uuid); return -EINVAL; } static int sof_ipc4_widget_setup_msg(struct snd_sof_widget *swidget, struct sof_ipc4_msg *msg) { struct sof_ipc4_fw_module *fw_module; uint32_t type; int ret; ret = sof_ipc4_widget_set_module_info(swidget); if (ret) return ret; fw_module = swidget->module_info; msg->primary = fw_module->man4_module_entry.id; msg->primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_INIT_INSTANCE); msg->primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST); msg->primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG); msg->extension = SOF_IPC4_MOD_EXT_PPL_ID(swidget->pipeline_id); msg->extension |= SOF_IPC4_MOD_EXT_CORE_ID(swidget->core); type = (fw_module->man4_module_entry.type & SOF_IPC4_MODULE_DP) ? 1 : 0; msg->extension |= SOF_IPC4_MOD_EXT_DOMAIN(type); return 0; } static int sof_ipc4_widget_setup_pcm(struct snd_sof_widget *swidget) { struct sof_ipc4_available_audio_format *available_fmt; struct snd_soc_component *scomp = swidget->scomp; struct sof_ipc4_copier *ipc4_copier; int node_type = 0; int ret, i; ipc4_copier = kzalloc(sizeof(*ipc4_copier), GFP_KERNEL); if (!ipc4_copier) return -ENOMEM; swidget->private = ipc4_copier; available_fmt = &ipc4_copier->available_fmt; dev_dbg(scomp->dev, "Updating IPC structure for %s\n", swidget->widget->name); ret = sof_ipc4_get_audio_fmt(scomp, swidget, available_fmt, true); if (ret) goto free_copier; available_fmt->dma_buffer_size = kcalloc(available_fmt->audio_fmt_num, sizeof(u32), GFP_KERNEL); if (!available_fmt->dma_buffer_size) { ret = -ENOMEM; goto free_available_fmt; } ret = sof_update_ipc_object(scomp, available_fmt->dma_buffer_size, SOF_COPIER_GATEWAY_CFG_TOKENS, swidget->tuples, swidget->num_tuples, sizeof(u32), available_fmt->audio_fmt_num); if (ret) { dev_err(scomp->dev, "Failed to parse dma buffer size in audio format for %s\n", swidget->widget->name); goto err; } dev_dbg(scomp->dev, "dma buffer size:\n"); for (i = 0; i < available_fmt->audio_fmt_num; i++) dev_dbg(scomp->dev, "%d: %u\n", i, available_fmt->dma_buffer_size[i]); ret = sof_update_ipc_object(scomp, &node_type, SOF_COPIER_TOKENS, swidget->tuples, swidget->num_tuples, sizeof(node_type), 1); if (ret) { dev_err(scomp->dev, "parse host copier node type token failed %d\n", ret); goto err; } dev_dbg(scomp->dev, "host copier '%s' node_type %u\n", swidget->widget->name, node_type); ipc4_copier->data.gtw_cfg.node_id = SOF_IPC4_NODE_TYPE(node_type); ipc4_copier->gtw_attr = kzalloc(sizeof(*ipc4_copier->gtw_attr), GFP_KERNEL); if (!ipc4_copier->gtw_attr) { ret = -ENOMEM; goto err; } ipc4_copier->copier_config = (uint32_t *)ipc4_copier->gtw_attr; ipc4_copier->data.gtw_cfg.config_length = sizeof(struct sof_ipc4_gtw_attributes) >> 2; /* set up module info and message header */ ret = sof_ipc4_widget_setup_msg(swidget, &ipc4_copier->msg); if (ret) goto free_gtw_attr; return 0; free_gtw_attr: kfree(ipc4_copier->gtw_attr); err: kfree(available_fmt->dma_buffer_size); free_available_fmt: sof_ipc4_free_audio_fmt(available_fmt); free_copier: kfree(ipc4_copier); swidget->private = NULL; return ret; } static void sof_ipc4_widget_free_comp_pcm(struct snd_sof_widget *swidget) { struct sof_ipc4_copier *ipc4_copier = swidget->private; struct sof_ipc4_available_audio_format *available_fmt; if (!ipc4_copier) return; available_fmt = &ipc4_copier->available_fmt; kfree(available_fmt->dma_buffer_size); kfree(available_fmt->base_config); kfree(available_fmt->out_audio_fmt); kfree(ipc4_copier->gtw_attr); kfree(ipc4_copier); swidget->private = NULL; } static int sof_ipc4_widget_setup_comp_dai(struct snd_sof_widget *swidget) { struct sof_ipc4_available_audio_format *available_fmt; struct snd_soc_component *scomp = swidget->scomp; struct snd_sof_dai *dai = swidget->private; struct sof_ipc4_copier *ipc4_copier; int node_type = 0; int ret, i; ipc4_copier = kzalloc(sizeof(*ipc4_copier), GFP_KERNEL); if (!ipc4_copier) return -ENOMEM; available_fmt = &ipc4_copier->available_fmt; dev_dbg(scomp->dev, "Updating IPC structure for %s\n", swidget->widget->name); ret = sof_ipc4_get_audio_fmt(scomp, swidget, available_fmt, true); if (ret) goto free_copier; available_fmt->dma_buffer_size = kcalloc(available_fmt->audio_fmt_num, sizeof(u32), GFP_KERNEL); if (!available_fmt->dma_buffer_size) { ret = -ENOMEM; goto free_available_fmt; } ret = sof_update_ipc_object(scomp, available_fmt->dma_buffer_size, SOF_COPIER_GATEWAY_CFG_TOKENS, swidget->tuples, swidget->num_tuples, sizeof(u32), available_fmt->audio_fmt_num); if (ret) { dev_err(scomp->dev, "Failed to parse dma buffer size in audio format for %s\n", swidget->widget->name); goto err; } for (i = 0; i < available_fmt->audio_fmt_num; i++) dev_dbg(scomp->dev, "%d: dma buffer size: %u\n", i, available_fmt->dma_buffer_size[i]); ret = sof_update_ipc_object(scomp, &node_type, SOF_COPIER_TOKENS, swidget->tuples, swidget->num_tuples, sizeof(node_type), 1); if (ret) { dev_err(scomp->dev, "parse dai node type failed %d\n", ret); goto err; } ret = sof_update_ipc_object(scomp, ipc4_copier, SOF_DAI_TOKENS, swidget->tuples, swidget->num_tuples, sizeof(u32), 1); if (ret) { dev_err(scomp->dev, "parse dai copier node token failed %d\n", ret); goto err; } dev_dbg(scomp->dev, "dai %s node_type %u dai_type %u dai_index %d\n", swidget->widget->name, node_type, ipc4_copier->dai_type, ipc4_copier->dai_index); ipc4_copier->data.gtw_cfg.node_id = SOF_IPC4_NODE_TYPE(node_type); switch (ipc4_copier->dai_type) { case SOF_DAI_INTEL_ALH: { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct sof_ipc4_alh_configuration_blob *blob; struct snd_sof_widget *w; blob = kzalloc(sizeof(*blob), GFP_KERNEL); if (!blob) { ret = -ENOMEM; goto err; } list_for_each_entry(w, &sdev->widget_list, list) { if (w->widget->sname && strcmp(w->widget->sname, swidget->widget->sname)) continue; blob->alh_cfg.count++; } ipc4_copier->copier_config = (uint32_t *)blob; ipc4_copier->data.gtw_cfg.config_length = sizeof(*blob) >> 2; break; } case SOF_DAI_INTEL_SSP: /* set SSP DAI index as the node_id */ ipc4_copier->data.gtw_cfg.node_id |= SOF_IPC4_NODE_INDEX_INTEL_SSP(ipc4_copier->dai_index); break; case SOF_DAI_INTEL_DMIC: /* set DMIC DAI index as the node_id */ ipc4_copier->data.gtw_cfg.node_id |= SOF_IPC4_NODE_INDEX_INTEL_DMIC(ipc4_copier->dai_index); break; default: ipc4_copier->gtw_attr = kzalloc(sizeof(*ipc4_copier->gtw_attr), GFP_KERNEL); if (!ipc4_copier->gtw_attr) { ret = -ENOMEM; goto err; } ipc4_copier->copier_config = (uint32_t *)ipc4_copier->gtw_attr; ipc4_copier->data.gtw_cfg.config_length = sizeof(struct sof_ipc4_gtw_attributes) >> 2; break; } dai->scomp = scomp; dai->private = ipc4_copier; /* set up module info and message header */ ret = sof_ipc4_widget_setup_msg(swidget, &ipc4_copier->msg); if (ret) goto free_copier_config; return 0; free_copier_config: kfree(ipc4_copier->copier_config); err: kfree(available_fmt->dma_buffer_size); free_available_fmt: sof_ipc4_free_audio_fmt(available_fmt); free_copier: kfree(ipc4_copier); dai->private = NULL; dai->scomp = NULL; return ret; } static void sof_ipc4_widget_free_comp_dai(struct snd_sof_widget *swidget) { struct sof_ipc4_available_audio_format *available_fmt; struct snd_sof_dai *dai = swidget->private; struct sof_ipc4_copier *ipc4_copier; if (!dai) return; if (!dai->private) { kfree(dai); swidget->private = NULL; return; } ipc4_copier = dai->private; available_fmt = &ipc4_copier->available_fmt; kfree(available_fmt->dma_buffer_size); kfree(available_fmt->base_config); kfree(available_fmt->out_audio_fmt); if (ipc4_copier->dai_type != SOF_DAI_INTEL_SSP && ipc4_copier->dai_type != SOF_DAI_INTEL_DMIC) kfree(ipc4_copier->copier_config); kfree(dai->private); kfree(dai); swidget->private = NULL; } static int sof_ipc4_widget_setup_comp_pipeline(struct snd_sof_widget *swidget) { struct snd_soc_component *scomp = swidget->scomp; struct sof_ipc4_pipeline *pipeline; int ret; pipeline = kzalloc(sizeof(*pipeline), GFP_KERNEL); if (!pipeline) return -ENOMEM; ret = sof_update_ipc_object(scomp, pipeline, SOF_SCHED_TOKENS, swidget->tuples, swidget->num_tuples, sizeof(*pipeline), 1); if (ret) { dev_err(scomp->dev, "parsing scheduler tokens failed\n"); goto err; } /* parse one set of pipeline tokens */ ret = sof_update_ipc_object(scomp, swidget, SOF_PIPELINE_TOKENS, swidget->tuples, swidget->num_tuples, sizeof(*swidget), 1); if (ret) { dev_err(scomp->dev, "parsing pipeline tokens failed\n"); goto err; } /* TODO: Get priority from topology */ pipeline->priority = 0; dev_dbg(scomp->dev, "pipeline '%s': id %d pri %d lp mode %d\n", swidget->widget->name, swidget->pipeline_id, pipeline->priority, pipeline->lp_mode); swidget->private = pipeline; pipeline->msg.primary = SOF_IPC4_GLB_PIPE_PRIORITY(pipeline->priority); pipeline->msg.primary |= SOF_IPC4_GLB_PIPE_INSTANCE_ID(swidget->pipeline_id); pipeline->msg.primary |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_CREATE_PIPELINE); pipeline->msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST); pipeline->msg.primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG); pipeline->msg.extension = pipeline->lp_mode; pipeline->state = SOF_IPC4_PIPE_UNINITIALIZED; return 0; err: kfree(pipeline); return ret; } static int sof_ipc4_widget_setup_comp_pga(struct snd_sof_widget *swidget) { struct snd_soc_component *scomp = swidget->scomp; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct sof_ipc4_fw_module *fw_module; struct snd_sof_control *scontrol; struct sof_ipc4_gain *gain; int ret; gain = kzalloc(sizeof(*gain), GFP_KERNEL); if (!gain) return -ENOMEM; swidget->private = gain; gain->data.channels = SOF_IPC4_GAIN_ALL_CHANNELS_MASK; gain->data.init_val = SOF_IPC4_VOL_ZERO_DB; /* The out_audio_fmt in topology is ignored as it is not required to be sent to the FW */ ret = sof_ipc4_get_audio_fmt(scomp, swidget, &gain->available_fmt, false); if (ret) goto err; ret = sof_update_ipc_object(scomp, &gain->data, SOF_GAIN_TOKENS, swidget->tuples, swidget->num_tuples, sizeof(gain->data), 1); if (ret) { dev_err(scomp->dev, "Parsing gain tokens failed\n"); goto err; } dev_dbg(scomp->dev, "pga widget %s: ramp type: %d, ramp duration %d, initial gain value: %#x, cpc %d\n", swidget->widget->name, gain->data.curve_type, gain->data.curve_duration_l, gain->data.init_val, gain->base_config.cpc); ret = sof_ipc4_widget_setup_msg(swidget, &gain->msg); if (ret) goto err; fw_module = swidget->module_info; /* update module ID for all kcontrols for this widget */ list_for_each_entry(scontrol, &sdev->kcontrol_list, list) if (scontrol->comp_id == swidget->comp_id) { struct sof_ipc4_control_data *cdata = scontrol->ipc_control_data; struct sof_ipc4_msg *msg = &cdata->msg; msg->primary |= fw_module->man4_module_entry.id; } return 0; err: sof_ipc4_free_audio_fmt(&gain->available_fmt); kfree(gain); swidget->private = NULL; return ret; } static void sof_ipc4_widget_free_comp_pga(struct snd_sof_widget *swidget) { struct sof_ipc4_gain *gain = swidget->private; if (!gain) return; sof_ipc4_free_audio_fmt(&gain->available_fmt); kfree(swidget->private); swidget->private = NULL; } static int sof_ipc4_widget_setup_comp_mixer(struct snd_sof_widget *swidget) { struct snd_soc_component *scomp = swidget->scomp; struct sof_ipc4_mixer *mixer; int ret; dev_dbg(scomp->dev, "Updating IPC structure for %s\n", swidget->widget->name); mixer = kzalloc(sizeof(*mixer), GFP_KERNEL); if (!mixer) return -ENOMEM; swidget->private = mixer; /* The out_audio_fmt in topology is ignored as it is not required to be sent to the FW */ ret = sof_ipc4_get_audio_fmt(scomp, swidget, &mixer->available_fmt, false); if (ret) goto err; ret = sof_ipc4_widget_setup_msg(swidget, &mixer->msg); if (ret) goto err; return 0; err: sof_ipc4_free_audio_fmt(&mixer->available_fmt); kfree(mixer); swidget->private = NULL; return ret; } static int sof_ipc4_widget_setup_comp_src(struct snd_sof_widget *swidget) { struct snd_soc_component *scomp = swidget->scomp; struct sof_ipc4_src *src; int ret; dev_dbg(scomp->dev, "Updating IPC structure for %s\n", swidget->widget->name); src = kzalloc(sizeof(*src), GFP_KERNEL); if (!src) return -ENOMEM; swidget->private = src; /* The out_audio_fmt in topology is ignored as it is not required by SRC */ ret = sof_ipc4_get_audio_fmt(scomp, swidget, &src->available_fmt, false); if (ret) goto err; ret = sof_update_ipc_object(scomp, src, SOF_SRC_TOKENS, swidget->tuples, swidget->num_tuples, sizeof(*src), 1); if (ret) { dev_err(scomp->dev, "Parsing SRC tokens failed\n"); goto err; } dev_dbg(scomp->dev, "SRC sink rate %d\n", src->sink_rate); ret = sof_ipc4_widget_setup_msg(swidget, &src->msg); if (ret) goto err; return 0; err: sof_ipc4_free_audio_fmt(&src->available_fmt); kfree(src); swidget->private = NULL; return ret; } static void sof_ipc4_widget_free_comp_src(struct snd_sof_widget *swidget) { struct sof_ipc4_src *src = swidget->private; if (!src) return; sof_ipc4_free_audio_fmt(&src->available_fmt); kfree(swidget->private); swidget->private = NULL; } static void sof_ipc4_widget_free_comp_mixer(struct snd_sof_widget *swidget) { struct sof_ipc4_mixer *mixer = swidget->private; if (!mixer) return; sof_ipc4_free_audio_fmt(&mixer->available_fmt); kfree(swidget->private); swidget->private = NULL; } static void sof_ipc4_update_pipeline_mem_usage(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget, struct sof_ipc4_base_module_cfg *base_config) { struct sof_ipc4_fw_module *fw_module = swidget->module_info; struct snd_sof_widget *pipe_widget; struct sof_ipc4_pipeline *pipeline; int task_mem, queue_mem; int ibs, bss, total; ibs = base_config->ibs; bss = base_config->is_pages; task_mem = SOF_IPC4_PIPELINE_OBJECT_SIZE; task_mem += SOF_IPC4_MODULE_INSTANCE_LIST_ITEM_SIZE + bss; if (fw_module->man4_module_entry.type & SOF_IPC4_MODULE_LL) { task_mem += SOF_IPC4_FW_ROUNDUP(SOF_IPC4_LL_TASK_OBJECT_SIZE); task_mem += SOF_IPC4_FW_MAX_QUEUE_COUNT * SOF_IPC4_MODULE_INSTANCE_LIST_ITEM_SIZE; task_mem += SOF_IPC4_LL_TASK_LIST_ITEM_SIZE; } else { task_mem += SOF_IPC4_FW_ROUNDUP(SOF_IPC4_DP_TASK_OBJECT_SIZE); task_mem += SOF_IPC4_DP_TASK_LIST_SIZE; } ibs = SOF_IPC4_FW_ROUNDUP(ibs); queue_mem = SOF_IPC4_FW_MAX_QUEUE_COUNT * (SOF_IPC4_DATA_QUEUE_OBJECT_SIZE + ibs); total = SOF_IPC4_FW_PAGE(task_mem + queue_mem); pipe_widget = swidget->pipe_widget; pipeline = pipe_widget->private; pipeline->mem_usage += total; } static int sof_ipc4_widget_assign_instance_id(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) { struct sof_ipc4_fw_module *fw_module = swidget->module_info; int max_instances = fw_module->man4_module_entry.instance_max_count; swidget->instance_id = ida_alloc_max(&fw_module->m_ida, max_instances, GFP_KERNEL); if (swidget->instance_id < 0) { dev_err(sdev->dev, "failed to assign instance id for widget %s", swidget->widget->name); return swidget->instance_id; } return 0; } static int sof_ipc4_init_audio_fmt(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget, struct sof_ipc4_base_module_cfg *base_config, struct sof_ipc4_audio_format *out_format, struct snd_pcm_hw_params *params, struct sof_ipc4_available_audio_format *available_fmt, size_t object_offset) { void *ptr = available_fmt->ref_audio_fmt; u32 valid_bits; u32 channels; u32 rate; int sample_valid_bits; int i; if (!ptr) { dev_err(sdev->dev, "no reference formats for %s\n", swidget->widget->name); return -EINVAL; } switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: sample_valid_bits = 16; break; case SNDRV_PCM_FORMAT_S24_LE: sample_valid_bits = 24; break; case SNDRV_PCM_FORMAT_S32_LE: sample_valid_bits = 32; break; default: dev_err(sdev->dev, "invalid pcm frame format %d\n", params_format(params)); return -EINVAL; } if (!available_fmt->audio_fmt_num) { dev_err(sdev->dev, "no formats available for %s\n", swidget->widget->name); return -EINVAL; } /* * Search supported audio formats to match rate, channels ,and * sample_valid_bytes from runtime params */ for (i = 0; i < available_fmt->audio_fmt_num; i++, ptr = (u8 *)ptr + object_offset) { struct sof_ipc4_audio_format *fmt = ptr; rate = fmt->sampling_frequency; channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg); valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg); if (params_rate(params) == rate && params_channels(params) == channels && sample_valid_bits == valid_bits) { dev_dbg(sdev->dev, "matching audio format index for %uHz, %ubit, %u channels: %d\n", rate, valid_bits, channels, i); /* copy ibs/obs and input format */ memcpy(base_config, &available_fmt->base_config[i], sizeof(struct sof_ipc4_base_module_cfg)); /* copy output format */ if (out_format) memcpy(out_format, &available_fmt->out_audio_fmt[i], sizeof(struct sof_ipc4_audio_format)); break; } } if (i == available_fmt->audio_fmt_num) { dev_err(sdev->dev, "%s: Unsupported audio format: %uHz, %ubit, %u channels\n", __func__, params_rate(params), sample_valid_bits, params_channels(params)); return -EINVAL; } dev_dbg(sdev->dev, "Init input audio formats for %s\n", swidget->widget->name); sof_ipc4_dbg_audio_format(sdev->dev, &base_config->audio_fmt, sizeof(*base_config), 1); if (out_format) { dev_dbg(sdev->dev, "Init output audio formats for %s\n", swidget->widget->name); sof_ipc4_dbg_audio_format(sdev->dev, out_format, sizeof(*out_format), 1); } /* Return the index of the matched format */ return i; } static void sof_ipc4_unprepare_copier_module(struct snd_sof_widget *swidget) { struct sof_ipc4_copier *ipc4_copier = NULL; struct snd_sof_widget *pipe_widget; struct sof_ipc4_pipeline *pipeline; /* reset pipeline memory usage */ pipe_widget = swidget->pipe_widget; pipeline = pipe_widget->private; pipeline->mem_usage = 0; if (WIDGET_IS_AIF(swidget->id)) { ipc4_copier = swidget->private; } else if (WIDGET_IS_DAI(swidget->id)) { struct snd_sof_dai *dai = swidget->private; ipc4_copier = dai->private; if (ipc4_copier->dai_type == SOF_DAI_INTEL_ALH) { struct sof_ipc4_alh_configuration_blob *blob; unsigned int group_id; blob = (struct sof_ipc4_alh_configuration_blob *)ipc4_copier->copier_config; if (blob->alh_cfg.count > 1) { group_id = SOF_IPC4_NODE_INDEX(ipc4_copier->data.gtw_cfg.node_id) - ALH_MULTI_GTW_BASE; ida_free(&alh_group_ida, group_id); } } } if (ipc4_copier) { kfree(ipc4_copier->ipc_config_data); ipc4_copier->ipc_config_data = NULL; ipc4_copier->ipc_config_size = 0; } } #if IS_ENABLED(CONFIG_ACPI) && IS_ENABLED(CONFIG_SND_INTEL_NHLT) static int snd_sof_get_hw_config_params(struct snd_sof_dev *sdev, struct snd_sof_dai *dai, int *sample_rate, int *channel_count, int *bit_depth) { struct snd_soc_tplg_hw_config *hw_config; struct snd_sof_dai_link *slink; bool dai_link_found = false; bool hw_cfg_found = false; int i; /* get current hw_config from link */ list_for_each_entry(slink, &sdev->dai_link_list, list) { if (!strcmp(slink->link->name, dai->name)) { dai_link_found = true; break; } } if (!dai_link_found) { dev_err(sdev->dev, "%s: no DAI link found for DAI %s\n", __func__, dai->name); return -EINVAL; } for (i = 0; i < slink->num_hw_configs; i++) { hw_config = &slink->hw_configs[i]; if (dai->current_config == le32_to_cpu(hw_config->id)) { hw_cfg_found = true; break; } } if (!hw_cfg_found) { dev_err(sdev->dev, "%s: no matching hw_config found for DAI %s\n", __func__, dai->name); return -EINVAL; } *bit_depth = le32_to_cpu(hw_config->tdm_slot_width); *channel_count = le32_to_cpu(hw_config->tdm_slots); *sample_rate = le32_to_cpu(hw_config->fsync_rate); dev_dbg(sdev->dev, "sample rate: %d sample width: %d channels: %d\n", *sample_rate, *bit_depth, *channel_count); return 0; } static int snd_sof_get_nhlt_endpoint_data(struct snd_sof_dev *sdev, struct snd_sof_dai *dai, struct snd_pcm_hw_params *params, u32 dai_index, u32 linktype, u8 dir, u32 **dst, u32 *len) { struct sof_ipc4_fw_data *ipc4_data = sdev->private; struct nhlt_specific_cfg *cfg; int sample_rate, channel_count; int bit_depth, ret; u32 nhlt_type; /* convert to NHLT type */ switch (linktype) { case SOF_DAI_INTEL_DMIC: nhlt_type = NHLT_LINK_DMIC; bit_depth = params_width(params); channel_count = params_channels(params); sample_rate = params_rate(params); break; case SOF_DAI_INTEL_SSP: nhlt_type = NHLT_LINK_SSP; ret = snd_sof_get_hw_config_params(sdev, dai, &sample_rate, &channel_count, &bit_depth); if (ret < 0) return ret; break; default: return 0; } dev_dbg(sdev->dev, "dai index %d nhlt type %d direction %d\n", dai_index, nhlt_type, dir); /* find NHLT blob with matching params */ cfg = intel_nhlt_get_endpoint_blob(sdev->dev, ipc4_data->nhlt, dai_index, nhlt_type, bit_depth, bit_depth, channel_count, sample_rate, dir, 0); if (!cfg) { dev_err(sdev->dev, "no matching blob for sample rate: %d sample width: %d channels: %d\n", sample_rate, bit_depth, channel_count); return -EINVAL; } /* config length should be in dwords */ *len = cfg->size >> 2; *dst = (u32 *)cfg->caps; return 0; } #else static int snd_sof_get_nhlt_endpoint_data(struct snd_sof_dev *sdev, struct snd_sof_dai *dai, struct snd_pcm_hw_params *params, u32 dai_index, u32 linktype, u8 dir, u32 **dst, u32 *len) { return 0; } #endif static int sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, struct snd_pcm_hw_params *fe_params, struct snd_sof_platform_stream_params *platform_params, struct snd_pcm_hw_params *pipeline_params, int dir) { struct sof_ipc4_available_audio_format *available_fmt; struct snd_soc_component *scomp = swidget->scomp; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct sof_ipc4_copier_data *copier_data; struct snd_pcm_hw_params *ref_params; struct sof_ipc4_copier *ipc4_copier; struct snd_sof_dai *dai; struct snd_mask *fmt; int out_sample_valid_bits; size_t ref_audio_fmt_size; void **ipc_config_data; int *ipc_config_size; u32 **data; int ipc_size, ret; dev_dbg(sdev->dev, "copier %s, type %d", swidget->widget->name, swidget->id); switch (swidget->id) { case snd_soc_dapm_aif_in: case snd_soc_dapm_aif_out: { struct sof_ipc4_gtw_attributes *gtw_attr; struct snd_sof_widget *pipe_widget; struct sof_ipc4_pipeline *pipeline; pipe_widget = swidget->pipe_widget; pipeline = pipe_widget->private; ipc4_copier = (struct sof_ipc4_copier *)swidget->private; gtw_attr = ipc4_copier->gtw_attr; copier_data = &ipc4_copier->data; available_fmt = &ipc4_copier->available_fmt; /* * base_config->audio_fmt and out_audio_fmt represent the input and output audio * formats. Use the input format as the reference to match pcm params for playback * and the output format as reference for capture. */ if (dir == SNDRV_PCM_STREAM_PLAYBACK) { available_fmt->ref_audio_fmt = &available_fmt->base_config->audio_fmt; ref_audio_fmt_size = sizeof(struct sof_ipc4_base_module_cfg); } else { available_fmt->ref_audio_fmt = available_fmt->out_audio_fmt; ref_audio_fmt_size = sizeof(struct sof_ipc4_audio_format); } copier_data->gtw_cfg.node_id &= ~SOF_IPC4_NODE_INDEX_MASK; copier_data->gtw_cfg.node_id |= SOF_IPC4_NODE_INDEX(platform_params->stream_tag - 1); /* set gateway attributes */ gtw_attr->lp_buffer_alloc = pipeline->lp_mode; ref_params = fe_params; break; } case snd_soc_dapm_dai_in: case snd_soc_dapm_dai_out: { dai = swidget->private; ipc4_copier = (struct sof_ipc4_copier *)dai->private; copier_data = &ipc4_copier->data; available_fmt = &ipc4_copier->available_fmt; if (dir == SNDRV_PCM_STREAM_CAPTURE) { available_fmt->ref_audio_fmt = available_fmt->out_audio_fmt; ref_audio_fmt_size = sizeof(struct sof_ipc4_audio_format); /* * modify the input params for the dai copier as it only supports * 32-bit always */ fmt = hw_param_mask(pipeline_params, SNDRV_PCM_HW_PARAM_FORMAT); snd_mask_none(fmt); snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE); } else { available_fmt->ref_audio_fmt = &available_fmt->base_config->audio_fmt; ref_audio_fmt_size = sizeof(struct sof_ipc4_base_module_cfg); } ref_params = pipeline_params; ret = snd_sof_get_nhlt_endpoint_data(sdev, dai, fe_params, ipc4_copier->dai_index, ipc4_copier->dai_type, dir, &ipc4_copier->copier_config, &copier_data->gtw_cfg.config_length); if (ret < 0) return ret; break; } default: dev_err(sdev->dev, "unsupported type %d for copier %s", swidget->id, swidget->widget->name); return -EINVAL; } /* set input and output audio formats */ ret = sof_ipc4_init_audio_fmt(sdev, swidget, &copier_data->base_config, &copier_data->out_format, ref_params, available_fmt, ref_audio_fmt_size); if (ret < 0) return ret; switch (swidget->id) { case snd_soc_dapm_dai_in: case snd_soc_dapm_dai_out: { /* * Only SOF_DAI_INTEL_ALH needs copier_data to set blob. * That's why only ALH dai's blob is set after sof_ipc4_init_audio_fmt */ if (ipc4_copier->dai_type == SOF_DAI_INTEL_ALH) { struct sof_ipc4_alh_configuration_blob *blob; struct sof_ipc4_copier_data *alh_data; struct sof_ipc4_copier *alh_copier; struct snd_sof_widget *w; u32 ch_mask = 0; u32 ch_map; int i; blob = (struct sof_ipc4_alh_configuration_blob *)ipc4_copier->copier_config; blob->gw_attr.lp_buffer_alloc = 0; /* Get channel_mask from ch_map */ ch_map = copier_data->base_config.audio_fmt.ch_map; for (i = 0; ch_map; i++) { if ((ch_map & 0xf) != 0xf) ch_mask |= BIT(i); ch_map >>= 4; } /* * Set each gtw_cfg.node_id to blob->alh_cfg.mapping[] * for all widgets with the same stream name */ i = 0; list_for_each_entry(w, &sdev->widget_list, list) { if (w->widget->sname && strcmp(w->widget->sname, swidget->widget->sname)) continue; dai = w->private; alh_copier = (struct sof_ipc4_copier *)dai->private; alh_data = &alh_copier->data; blob->alh_cfg.mapping[i].alh_id = alh_data->gtw_cfg.node_id; blob->alh_cfg.mapping[i].channel_mask = ch_mask; i++; } if (blob->alh_cfg.count > 1) { int group_id; group_id = ida_alloc_max(&alh_group_ida, ALH_MULTI_GTW_COUNT - 1, GFP_KERNEL); if (group_id < 0) return group_id; /* add multi-gateway base */ group_id += ALH_MULTI_GTW_BASE; copier_data->gtw_cfg.node_id &= ~SOF_IPC4_NODE_INDEX_MASK; copier_data->gtw_cfg.node_id |= SOF_IPC4_NODE_INDEX(group_id); } } } } /* modify the input params for the next widget */ fmt = hw_param_mask(pipeline_params, SNDRV_PCM_HW_PARAM_FORMAT); out_sample_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(copier_data->out_format.fmt_cfg); snd_mask_none(fmt); switch (out_sample_valid_bits) { case 16: snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); break; case 24: snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); break; case 32: snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE); break; default: dev_err(sdev->dev, "invalid sample frame format %d\n", params_format(pipeline_params)); return -EINVAL; } /* set the gateway dma_buffer_size using the matched ID returned above */ copier_data->gtw_cfg.dma_buffer_size = available_fmt->dma_buffer_size[ret]; data = &ipc4_copier->copier_config; ipc_config_size = &ipc4_copier->ipc_config_size; ipc_config_data = &ipc4_copier->ipc_config_data; /* config_length is DWORD based */ ipc_size = sizeof(*copier_data) + copier_data->gtw_cfg.config_length * 4; dev_dbg(sdev->dev, "copier %s, IPC size is %d", swidget->widget->name, ipc_size); *ipc_config_data = kzalloc(ipc_size, GFP_KERNEL); if (!*ipc_config_data) return -ENOMEM; *ipc_config_size = ipc_size; /* copy IPC data */ memcpy(*ipc_config_data, (void *)copier_data, sizeof(*copier_data)); if (copier_data->gtw_cfg.config_length) memcpy(*ipc_config_data + sizeof(*copier_data), *data, copier_data->gtw_cfg.config_length * 4); /* update pipeline memory usage */ sof_ipc4_update_pipeline_mem_usage(sdev, swidget, &copier_data->base_config); return 0; } static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, struct snd_pcm_hw_params *fe_params, struct snd_sof_platform_stream_params *platform_params, struct snd_pcm_hw_params *pipeline_params, int dir) { struct snd_soc_component *scomp = swidget->scomp; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct sof_ipc4_gain *gain = swidget->private; int ret; gain->available_fmt.ref_audio_fmt = &gain->available_fmt.base_config->audio_fmt; /* output format is not required to be sent to the FW for gain */ ret = sof_ipc4_init_audio_fmt(sdev, swidget, &gain->base_config, NULL, pipeline_params, &gain->available_fmt, sizeof(gain->base_config)); if (ret < 0) return ret; /* update pipeline memory usage */ sof_ipc4_update_pipeline_mem_usage(sdev, swidget, &gain->base_config); return 0; } static int sof_ipc4_prepare_mixer_module(struct snd_sof_widget *swidget, struct snd_pcm_hw_params *fe_params, struct snd_sof_platform_stream_params *platform_params, struct snd_pcm_hw_params *pipeline_params, int dir) { struct snd_soc_component *scomp = swidget->scomp; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct sof_ipc4_mixer *mixer = swidget->private; int ret; /* only 32bit is supported by mixer */ mixer->available_fmt.ref_audio_fmt = &mixer->available_fmt.base_config->audio_fmt; /* output format is not required to be sent to the FW for mixer */ ret = sof_ipc4_init_audio_fmt(sdev, swidget, &mixer->base_config, NULL, pipeline_params, &mixer->available_fmt, sizeof(mixer->base_config)); if (ret < 0) return ret; /* update pipeline memory usage */ sof_ipc4_update_pipeline_mem_usage(sdev, swidget, &mixer->base_config); return 0; } static int sof_ipc4_prepare_src_module(struct snd_sof_widget *swidget, struct snd_pcm_hw_params *fe_params, struct snd_sof_platform_stream_params *platform_params, struct snd_pcm_hw_params *pipeline_params, int dir) { struct snd_soc_component *scomp = swidget->scomp; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct sof_ipc4_src *src = swidget->private; struct snd_interval *rate; int ret; src->available_fmt.ref_audio_fmt = &src->available_fmt.base_config->audio_fmt; /* output format is not required to be sent to the FW for SRC */ ret = sof_ipc4_init_audio_fmt(sdev, swidget, &src->base_config, NULL, pipeline_params, &src->available_fmt, sizeof(src->base_config)); if (ret < 0) return ret; /* update pipeline memory usage */ sof_ipc4_update_pipeline_mem_usage(sdev, swidget, &src->base_config); /* update pipeline_params for sink widgets */ rate = hw_param_interval(pipeline_params, SNDRV_PCM_HW_PARAM_RATE); rate->min = src->sink_rate; rate->max = rate->min; return 0; } static int sof_ipc4_control_load_volume(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol) { struct sof_ipc4_control_data *control_data; struct sof_ipc4_msg *msg; int i; scontrol->size = struct_size(control_data, chanv, scontrol->num_channels); /* scontrol->ipc_control_data will be freed in sof_control_unload */ scontrol->ipc_control_data = kzalloc(scontrol->size, GFP_KERNEL); if (!scontrol->ipc_control_data) return -ENOMEM; control_data = scontrol->ipc_control_data; control_data->index = scontrol->index; msg = &control_data->msg; msg->primary = SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_LARGE_CONFIG_SET); msg->primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST); msg->primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG); msg->extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(SOF_IPC4_GAIN_PARAM_ID); /* set default volume values to 0dB in control */ for (i = 0; i < scontrol->num_channels; i++) { control_data->chanv[i].channel = i; control_data->chanv[i].value = SOF_IPC4_VOL_ZERO_DB; } return 0; } static int sof_ipc4_control_setup(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol) { switch (scontrol->info_type) { case SND_SOC_TPLG_CTL_VOLSW: case SND_SOC_TPLG_CTL_VOLSW_SX: case SND_SOC_TPLG_CTL_VOLSW_XR_SX: return sof_ipc4_control_load_volume(sdev, scontrol); default: break; } return 0; } static int sof_ipc4_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) { struct sof_ipc4_pipeline *pipeline; struct sof_ipc4_msg *msg; void *ipc_data = NULL; u32 ipc_size = 0; int ret; switch (swidget->id) { case snd_soc_dapm_scheduler: pipeline = swidget->private; dev_dbg(sdev->dev, "pipeline: %d memory pages: %d\n", swidget->pipeline_id, pipeline->mem_usage); msg = &pipeline->msg; msg->primary |= pipeline->mem_usage; break; case snd_soc_dapm_aif_in: case snd_soc_dapm_aif_out: { struct sof_ipc4_copier *ipc4_copier = swidget->private; ipc_size = ipc4_copier->ipc_config_size; ipc_data = ipc4_copier->ipc_config_data; msg = &ipc4_copier->msg; break; } case snd_soc_dapm_dai_in: case snd_soc_dapm_dai_out: { struct snd_sof_dai *dai = swidget->private; struct sof_ipc4_copier *ipc4_copier = dai->private; ipc_size = ipc4_copier->ipc_config_size; ipc_data = ipc4_copier->ipc_config_data; msg = &ipc4_copier->msg; break; } case snd_soc_dapm_pga: { struct sof_ipc4_gain *gain = swidget->private; ipc_size = sizeof(struct sof_ipc4_base_module_cfg) + sizeof(struct sof_ipc4_gain_data); ipc_data = gain; msg = &gain->msg; break; } case snd_soc_dapm_mixer: { struct sof_ipc4_mixer *mixer = swidget->private; ipc_size = sizeof(mixer->base_config); ipc_data = &mixer->base_config; msg = &mixer->msg; break; } case snd_soc_dapm_src: { struct sof_ipc4_src *src = swidget->private; ipc_size = sizeof(struct sof_ipc4_base_module_cfg) + sizeof(src->sink_rate); ipc_data = src; msg = &src->msg; break; } default: dev_err(sdev->dev, "widget type %d not supported", swidget->id); return -EINVAL; } if (swidget->id != snd_soc_dapm_scheduler) { ret = sof_ipc4_widget_assign_instance_id(sdev, swidget); if (ret < 0) { dev_err(sdev->dev, "failed to assign instance id for %s\n", swidget->widget->name); return ret; } msg->primary &= ~SOF_IPC4_MOD_INSTANCE_MASK; msg->primary |= SOF_IPC4_MOD_INSTANCE(swidget->instance_id); msg->extension &= ~SOF_IPC4_MOD_EXT_PARAM_SIZE_MASK; msg->extension |= ipc_size >> 2; } dev_dbg(sdev->dev, "Create widget %s instance %d - pipe %d - core %d\n", swidget->widget->name, swidget->instance_id, swidget->pipeline_id, swidget->core); msg->data_size = ipc_size; msg->data_ptr = ipc_data; ret = sof_ipc_tx_message(sdev->ipc, msg, ipc_size, NULL, 0); if (ret < 0) { dev_err(sdev->dev, "failed to create module %s\n", swidget->widget->name); if (swidget->id != snd_soc_dapm_scheduler) { struct sof_ipc4_fw_module *fw_module = swidget->module_info; ida_free(&fw_module->m_ida, swidget->instance_id); } } return ret; } static int sof_ipc4_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) { struct sof_ipc4_fw_module *fw_module = swidget->module_info; int ret = 0; /* freeing a pipeline frees all the widgets associated with it */ if (swidget->id == snd_soc_dapm_scheduler) { struct sof_ipc4_pipeline *pipeline = swidget->private; struct sof_ipc4_msg msg = {{ 0 }}; u32 header; header = SOF_IPC4_GLB_PIPE_INSTANCE_ID(swidget->pipeline_id); header |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_DELETE_PIPELINE); header |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST); header |= SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG); msg.primary = header; ret = sof_ipc_tx_message(sdev->ipc, &msg, 0, NULL, 0); if (ret < 0) dev_err(sdev->dev, "failed to free pipeline widget %s\n", swidget->widget->name); pipeline->mem_usage = 0; pipeline->state = SOF_IPC4_PIPE_UNINITIALIZED; } else { ida_free(&fw_module->m_ida, swidget->instance_id); } return ret; } static int sof_ipc4_route_setup(struct snd_sof_dev *sdev, struct snd_sof_route *sroute) { struct snd_sof_widget *src_widget = sroute->src_widget; struct snd_sof_widget *sink_widget = sroute->sink_widget; struct sof_ipc4_fw_module *src_fw_module = src_widget->module_info; struct sof_ipc4_fw_module *sink_fw_module = sink_widget->module_info; struct sof_ipc4_msg msg = {{ 0 }}; u32 header, extension; int src_queue = 0; int dst_queue = 0; int ret; dev_dbg(sdev->dev, "bind %s -> %s\n", src_widget->widget->name, sink_widget->widget->name); header = src_fw_module->man4_module_entry.id; header |= SOF_IPC4_MOD_INSTANCE(src_widget->instance_id); header |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_BIND); header |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST); header |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG); extension = sink_fw_module->man4_module_entry.id; extension |= SOF_IPC4_MOD_EXT_DST_MOD_INSTANCE(sink_widget->instance_id); extension |= SOF_IPC4_MOD_EXT_DST_MOD_QUEUE_ID(dst_queue); extension |= SOF_IPC4_MOD_EXT_SRC_MOD_QUEUE_ID(src_queue); msg.primary = header; msg.extension = extension; ret = sof_ipc_tx_message(sdev->ipc, &msg, 0, NULL, 0); if (ret < 0) dev_err(sdev->dev, "%s: failed to bind modules %s -> %s\n", __func__, src_widget->widget->name, sink_widget->widget->name); return ret; } static int sof_ipc4_route_free(struct snd_sof_dev *sdev, struct snd_sof_route *sroute) { struct snd_sof_widget *src_widget = sroute->src_widget; struct snd_sof_widget *sink_widget = sroute->sink_widget; struct sof_ipc4_fw_module *src_fw_module = src_widget->module_info; struct sof_ipc4_fw_module *sink_fw_module = sink_widget->module_info; struct sof_ipc4_msg msg = {{ 0 }}; u32 header, extension; int src_queue = 0; int dst_queue = 0; int ret; dev_dbg(sdev->dev, "unbind modules %s -> %s\n", src_widget->widget->name, sink_widget->widget->name); header = src_fw_module->man4_module_entry.id; header |= SOF_IPC4_MOD_INSTANCE(src_widget->instance_id); header |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_UNBIND); header |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST); header |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG); extension = sink_fw_module->man4_module_entry.id; extension |= SOF_IPC4_MOD_EXT_DST_MOD_INSTANCE(sink_widget->instance_id); extension |= SOF_IPC4_MOD_EXT_DST_MOD_QUEUE_ID(dst_queue); extension |= SOF_IPC4_MOD_EXT_SRC_MOD_QUEUE_ID(src_queue); msg.primary = header; msg.extension = extension; ret = sof_ipc_tx_message(sdev->ipc, &msg, 0, NULL, 0); if (ret < 0) dev_err(sdev->dev, "failed to unbind modules %s -> %s\n", src_widget->widget->name, sink_widget->widget->name); return ret; } static int sof_ipc4_dai_config(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget, unsigned int flags, struct snd_sof_dai_config_data *data) { struct snd_sof_widget *pipe_widget = swidget->pipe_widget; struct sof_ipc4_pipeline *pipeline = pipe_widget->private; struct snd_sof_dai *dai = swidget->private; struct sof_ipc4_gtw_attributes *gtw_attr; struct sof_ipc4_copier_data *copier_data; struct sof_ipc4_copier *ipc4_copier; if (!dai || !dai->private) { dev_err(sdev->dev, "Invalid DAI or DAI private data for %s\n", swidget->widget->name); return -EINVAL; } ipc4_copier = (struct sof_ipc4_copier *)dai->private; copier_data = &ipc4_copier->data; if (!data) return 0; switch (ipc4_copier->dai_type) { case SOF_DAI_INTEL_HDA: gtw_attr = ipc4_copier->gtw_attr; gtw_attr->lp_buffer_alloc = pipeline->lp_mode; fallthrough; case SOF_DAI_INTEL_ALH: copier_data->gtw_cfg.node_id &= ~SOF_IPC4_NODE_INDEX_MASK; copier_data->gtw_cfg.node_id |= SOF_IPC4_NODE_INDEX(data->dai_data); break; case SOF_DAI_INTEL_DMIC: case SOF_DAI_INTEL_SSP: /* nothing to do for SSP/DMIC */ break; default: dev_err(sdev->dev, "%s: unsupported dai type %d\n", __func__, ipc4_copier->dai_type); return -EINVAL; } return 0; } static int sof_ipc4_parse_manifest(struct snd_soc_component *scomp, int index, struct snd_soc_tplg_manifest *man) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct sof_ipc4_fw_data *ipc4_data = sdev->private; struct sof_manifest_tlv *manifest_tlv; struct sof_manifest *manifest; u32 size = le32_to_cpu(man->priv.size); u8 *man_ptr = man->priv.data; u32 len_check; int i; if (!size || size < SOF_IPC4_TPLG_ABI_SIZE) { dev_err(scomp->dev, "%s: Invalid topology ABI size: %u\n", __func__, size); return -EINVAL; } manifest = (struct sof_manifest *)man_ptr; dev_info(scomp->dev, "Topology: ABI %d:%d:%d Kernel ABI %u:%u:%u\n", le16_to_cpu(manifest->abi_major), le16_to_cpu(manifest->abi_minor), le16_to_cpu(manifest->abi_patch), SOF_ABI_MAJOR, SOF_ABI_MINOR, SOF_ABI_PATCH); /* TODO: Add ABI compatibility check */ /* no more data after the ABI version */ if (size <= SOF_IPC4_TPLG_ABI_SIZE) return 0; manifest_tlv = manifest->items; len_check = sizeof(struct sof_manifest); for (i = 0; i < le16_to_cpu(manifest->count); i++) { len_check += sizeof(struct sof_manifest_tlv) + le32_to_cpu(manifest_tlv->size); if (len_check > size) return -EINVAL; switch (le32_to_cpu(manifest_tlv->type)) { case SOF_MANIFEST_DATA_TYPE_NHLT: /* no NHLT in BIOS, so use the one from topology manifest */ if (ipc4_data->nhlt) break; ipc4_data->nhlt = devm_kmemdup(sdev->dev, manifest_tlv->data, le32_to_cpu(manifest_tlv->size), GFP_KERNEL); if (!ipc4_data->nhlt) return -ENOMEM; break; default: dev_warn(scomp->dev, "Skipping unknown manifest data type %d\n", manifest_tlv->type); break; } man_ptr += sizeof(struct sof_manifest_tlv) + le32_to_cpu(manifest_tlv->size); manifest_tlv = (struct sof_manifest_tlv *)man_ptr; } return 0; } static int sof_ipc4_dai_get_clk(struct snd_sof_dev *sdev, struct snd_sof_dai *dai, int clk_type) { struct sof_ipc4_copier *ipc4_copier = dai->private; struct snd_soc_tplg_hw_config *hw_config; struct snd_sof_dai_link *slink; bool dai_link_found = false; bool hw_cfg_found = false; int i; if (!ipc4_copier) return 0; list_for_each_entry(slink, &sdev->dai_link_list, list) { if (!strcmp(slink->link->name, dai->name)) { dai_link_found = true; break; } } if (!dai_link_found) { dev_err(sdev->dev, "no DAI link found for DAI %s\n", dai->name); return -EINVAL; } for (i = 0; i < slink->num_hw_configs; i++) { hw_config = &slink->hw_configs[i]; if (dai->current_config == le32_to_cpu(hw_config->id)) { hw_cfg_found = true; break; } } if (!hw_cfg_found) { dev_err(sdev->dev, "no matching hw_config found for DAI %s\n", dai->name); return -EINVAL; } switch (ipc4_copier->dai_type) { case SOF_DAI_INTEL_SSP: switch (clk_type) { case SOF_DAI_CLK_INTEL_SSP_MCLK: return le32_to_cpu(hw_config->mclk_rate); case SOF_DAI_CLK_INTEL_SSP_BCLK: return le32_to_cpu(hw_config->bclk_rate); default: dev_err(sdev->dev, "Invalid clk type for SSP %d\n", clk_type); break; } break; default: dev_err(sdev->dev, "DAI type %d not supported yet!\n", ipc4_copier->dai_type); break; } return -EINVAL; } static enum sof_tokens host_token_list[] = { SOF_COMP_TOKENS, SOF_AUDIO_FMT_NUM_TOKENS, SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS, SOF_IN_AUDIO_FORMAT_TOKENS, SOF_OUT_AUDIO_FORMAT_TOKENS, SOF_COPIER_GATEWAY_CFG_TOKENS, SOF_COPIER_TOKENS, SOF_COMP_EXT_TOKENS, }; static enum sof_tokens pipeline_token_list[] = { SOF_SCHED_TOKENS, SOF_PIPELINE_TOKENS, }; static enum sof_tokens dai_token_list[] = { SOF_COMP_TOKENS, SOF_AUDIO_FMT_NUM_TOKENS, SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS, SOF_IN_AUDIO_FORMAT_TOKENS, SOF_OUT_AUDIO_FORMAT_TOKENS, SOF_COPIER_GATEWAY_CFG_TOKENS, SOF_COPIER_TOKENS, SOF_DAI_TOKENS, SOF_COMP_EXT_TOKENS, }; static enum sof_tokens pga_token_list[] = { SOF_COMP_TOKENS, SOF_GAIN_TOKENS, SOF_AUDIO_FMT_NUM_TOKENS, SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS, SOF_IN_AUDIO_FORMAT_TOKENS, SOF_COMP_EXT_TOKENS, }; static enum sof_tokens mixer_token_list[] = { SOF_COMP_TOKENS, SOF_AUDIO_FMT_NUM_TOKENS, SOF_IN_AUDIO_FORMAT_TOKENS, SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS, SOF_COMP_EXT_TOKENS, }; static enum sof_tokens src_token_list[] = { SOF_COMP_TOKENS, SOF_SRC_TOKENS, SOF_AUDIO_FMT_NUM_TOKENS, SOF_IN_AUDIO_FORMAT_TOKENS, SOF_AUDIO_FORMAT_BUFFER_SIZE_TOKENS, SOF_COMP_EXT_TOKENS, }; static const struct sof_ipc_tplg_widget_ops tplg_ipc4_widget_ops[SND_SOC_DAPM_TYPE_COUNT] = { [snd_soc_dapm_aif_in] = {sof_ipc4_widget_setup_pcm, sof_ipc4_widget_free_comp_pcm, host_token_list, ARRAY_SIZE(host_token_list), NULL, sof_ipc4_prepare_copier_module, sof_ipc4_unprepare_copier_module}, [snd_soc_dapm_aif_out] = {sof_ipc4_widget_setup_pcm, sof_ipc4_widget_free_comp_pcm, host_token_list, ARRAY_SIZE(host_token_list), NULL, sof_ipc4_prepare_copier_module, sof_ipc4_unprepare_copier_module}, [snd_soc_dapm_dai_in] = {sof_ipc4_widget_setup_comp_dai, sof_ipc4_widget_free_comp_dai, dai_token_list, ARRAY_SIZE(dai_token_list), NULL, sof_ipc4_prepare_copier_module, sof_ipc4_unprepare_copier_module}, [snd_soc_dapm_dai_out] = {sof_ipc4_widget_setup_comp_dai, sof_ipc4_widget_free_comp_dai, dai_token_list, ARRAY_SIZE(dai_token_list), NULL, sof_ipc4_prepare_copier_module, sof_ipc4_unprepare_copier_module}, [snd_soc_dapm_scheduler] = {sof_ipc4_widget_setup_comp_pipeline, sof_ipc4_widget_free_comp, pipeline_token_list, ARRAY_SIZE(pipeline_token_list), NULL, NULL, NULL}, [snd_soc_dapm_pga] = {sof_ipc4_widget_setup_comp_pga, sof_ipc4_widget_free_comp_pga, pga_token_list, ARRAY_SIZE(pga_token_list), NULL, sof_ipc4_prepare_gain_module, NULL}, [snd_soc_dapm_mixer] = {sof_ipc4_widget_setup_comp_mixer, sof_ipc4_widget_free_comp_mixer, mixer_token_list, ARRAY_SIZE(mixer_token_list), NULL, sof_ipc4_prepare_mixer_module, NULL}, [snd_soc_dapm_src] = {sof_ipc4_widget_setup_comp_src, sof_ipc4_widget_free_comp_src, src_token_list, ARRAY_SIZE(src_token_list), NULL, sof_ipc4_prepare_src_module, NULL}, }; const struct sof_ipc_tplg_ops ipc4_tplg_ops = { .widget = tplg_ipc4_widget_ops, .token_list = ipc4_token_list, .control_setup = sof_ipc4_control_setup, .control = &tplg_ipc4_control_ops, .widget_setup = sof_ipc4_widget_setup, .widget_free = sof_ipc4_widget_free, .route_setup = sof_ipc4_route_setup, .route_free = sof_ipc4_route_free, .dai_config = sof_ipc4_dai_config, .parse_manifest = sof_ipc4_parse_manifest, .dai_get_clk = sof_ipc4_dai_get_clk, };