// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) // // This file is provided under a dual BSD/GPLv2 license. When using or // redistributing this file, you may do so under either license. // // Copyright(c) 2018 Intel Corporation // // Author: Liam Girdwood <liam.r.girdwood@linux.intel.com> //
staticbool disable_function_topology;
module_param(disable_function_topology, bool, 0444);
MODULE_PARM_DESC(disable_function_topology, "Disable function topology loading");
#define COMP_ID_UNASSIGNED 0xffffffff /* * Constants used in the computation of linear volume gain * from dB gain 20th root of 10 in Q1.16 fixed-point notation
*/ #define VOL_TWENTIETH_ROOT_OF_TEN 73533 /* 40th root of 10 in Q1.16 fixed-point notation*/ #define VOL_FORTIETH_ROOT_OF_TEN 69419
/* 0.5 dB step value in topology TLV */ #define VOL_HALF_DB_STEP 50
/** * sof_update_ipc_object - Parse multiple sets of tokens within the token array associated with the * token ID. * @scomp: pointer to SOC component * @object: target IPC struct to save the parsed values * @token_id: token ID for the token array to be searched * @tuples: pointer to the tuples array * @num_tuples: number of tuples in the tuples array * @object_size: size of the object * @token_instance_num: number of times the same @token_id needs to be parsed i.e. the function * looks for @token_instance_num of each token in the token array associated * with the @token_id
*/ int sof_update_ipc_object(struct snd_soc_component *scomp, void *object, enum sof_tokens token_id, struct snd_sof_tuple *tuples, int num_tuples,
size_t object_size, int token_instance_num)
{ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); conststruct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg); conststruct sof_token_info *token_list; conststruct sof_topology_token *tokens; int i, j;
token_list = tplg_ops ? tplg_ops->token_list : NULL; /* nothing to do if token_list is NULL */ if (!token_list) return 0;
if (token_list[token_id].count < 0) {
dev_err(scomp->dev, "Invalid token count for token ID: %d\n", token_id); return -EINVAL;
}
/* No tokens to match */ if (!token_list[token_id].count) return 0;
tokens = token_list[token_id].tokens; if (!tokens) {
dev_err(scomp->dev, "Invalid tokens for token id: %d\n", token_id); return -EINVAL;
}
for (i = 0; i < token_list[token_id].count; i++) { int offset = 0; int num_tokens_matched = 0;
for (j = 0; j < num_tuples; j++) { if (tokens[i].token == tuples[j].token) { switch (tokens[i].type) { case SND_SOC_TPLG_TUPLE_TYPE_WORD:
{
u32 *val = (u32 *)((u8 *)object + tokens[i].offset +
offset);
*val = tuples[j].value.v; break;
} case SND_SOC_TPLG_TUPLE_TYPE_SHORT: case SND_SOC_TPLG_TUPLE_TYPE_BOOL:
{
u16 *val = (u16 *)((u8 *)object + tokens[i].offset +
offset);
*val = (u16)tuples[j].value.v; break;
} case SND_SOC_TPLG_TUPLE_TYPE_STRING:
{ if (!tokens[i].get_token) {
dev_err(scomp->dev, "get_token not defined for token %d in %s\n",
tokens[i].token, token_list[token_id].name); return -EINVAL;
}
/* found all required sets of current token. Move to the next one */ if (!(num_tokens_matched % token_instance_num)) break;
/* move to the next object */
offset += object_size;
}
}
}
return 0;
}
staticinlineint get_tlv_data(constint *p, int tlv[SOF_TLV_ITEMS])
{ /* we only support dB scale TLV type at the moment */ if ((int)p[SNDRV_CTL_TLVO_TYPE] != SNDRV_CTL_TLVT_DB_SCALE) return -EINVAL;
/* min value in topology tlv data is multiplied by 100 */
tlv[TLV_MIN] = (int)p[SNDRV_CTL_TLVO_DB_SCALE_MIN] / 100;
/* * Function to truncate an unsigned 64-bit number * by x bits and return 32-bit unsigned number. This * function also takes care of rounding while truncating
*/ staticinline u32 vol_shift_64(u64 i, u32 x)
{ /* do not truncate more than 32 bits */ if (x > 32)
x = 32;
if (x == 0) return (u32)i;
return (u32)(((i >> (x - 1)) + 1) >> 1);
}
/* * Function to compute a ^ exp where, * a is a fractional number represented by a fixed-point * integer with a fractional world length of "fwl" * exp is an integer * fwl is the fractional word length * Return value is a fractional number represented by a * fixed-point integer with a fractional word length of "fwl"
*/ static u32 vol_pow32(u32 a, int exp, u32 fwl)
{ int i, iter;
u32 power = 1 << fwl;
u64 numerator;
/* if exponent is 0, return 1 */ if (exp == 0) return power;
/* determine the number of iterations based on the exponent */ if (exp < 0)
iter = exp * -1; else
iter = exp;
/* mutiply a "iter" times to compute power */ for (i = 0; i < iter; i++) { /* * Product of 2 Qx.fwl fixed-point numbers yields a Q2*x.2*fwl * Truncate product back to fwl fractional bits with rounding
*/
power = vol_shift_64((u64)power * a, fwl);
}
if (exp > 0) { /* if exp is positive, return the result */ return power;
}
/* if exp is negative, return the multiplicative inverse */
numerator = (u64)1 << (fwl << 1);
do_div(numerator, power);
return (u32)numerator;
}
/* * Function to calculate volume gain from TLV data. * This function can only handle gain steps that are multiples of 0.5 dB
*/
u32 vol_compute_gain(u32 value, int *tlv)
{ int dB_gain;
u32 linear_gain; int f_step;
/* * compute dB gain from tlv. tlv_step * in topology is multiplied by 100
*/
dB_gain = tlv[TLV_MIN] + (value * tlv[TLV_STEP]) / 100;
/* * compute linear gain represented by fixed-point * int with VOLUME_FWL fractional bits
*/
linear_gain = vol_pow32(VOL_TWENTIETH_ROOT_OF_TEN, dB_gain, VOLUME_FWL);
/* extract the fractional part of volume step */
f_step = tlv[TLV_STEP] - (tlv[TLV_STEP] / 100);
/* if volume step is an odd multiple of 0.5 dB */ if (f_step == VOL_HALF_DB_STEP && (value & 1))
linear_gain = vol_shift_64((u64)linear_gain *
VOL_FORTIETH_ROOT_OF_TEN,
VOLUME_FWL);
return linear_gain;
}
/* * Set up volume table for kcontrols from tlv data * "size" specifies the number of entries in the table
*/ staticint set_up_volume_table(struct snd_sof_control *scontrol, int tlv[SOF_TLV_ITEMS], int size)
{ struct snd_soc_component *scomp = scontrol->scomp; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); conststruct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg);
if (tplg_ops && tplg_ops->control && tplg_ops->control->set_up_volume_table) return tplg_ops->control->set_up_volume_table(scontrol, tlv, size);
dev_err(scomp->dev, "Mandatory op %s not set\n", __func__); return -EINVAL;
}
/* * The string gets from topology will be stored in heap, the owner only * holds a char* member point to the heap.
*/ int get_token_string(void *elem, void *object, u32 offset)
{ /* "dst" here points to the char* member of the owner */ char **dst = (char **)((u8 *)object + offset);
/** * sof_parse_uuid_tokens - Parse multiple sets of UUID tokens * @scomp: pointer to soc component * @object: target ipc struct for parsed values * @offset: offset within the object pointer * @tokens: array of struct sof_topology_token containing the tokens to be matched * @num_tokens: number of tokens in tokens array * @array: source pointer to consecutive vendor arrays in topology * * This function parses multiple sets of string type tokens in vendor arrays
*/ staticint sof_parse_uuid_tokens(struct snd_soc_component *scomp, void *object, size_t offset, conststruct sof_topology_token *tokens, int num_tokens, struct snd_soc_tplg_vendor_array *array)
{ struct snd_soc_tplg_vendor_uuid_elem *elem; int found = 0; int i, j;
/* parse element by element */ for (i = 0; i < le32_to_cpu(array->num_elems); i++) {
elem = &array->uuid[i];
/* search for token */ for (j = 0; j < num_tokens; j++) { /* match token type */ if (tokens[j].type != SND_SOC_TPLG_TUPLE_TYPE_UUID) continue;
/* match token id */ if (tokens[j].token != le32_to_cpu(elem->token)) continue;
/** * sof_copy_tuples - Parse tokens and copy them to the @tuples array * @sdev: pointer to struct snd_sof_dev * @array: source pointer to consecutive vendor arrays in topology * @array_size: size of @array * @token_id: Token ID associated with a token array * @token_instance_num: number of times the same @token_id needs to be parsed i.e. the function * looks for @token_instance_num of each token in the token array associated * with the @token_id * @tuples: tuples array to copy the matched tuples to * @tuples_size: size of @tuples * @num_copied_tuples: pointer to the number of copied tuples in the tuples array *
*/ staticint sof_copy_tuples(struct snd_sof_dev *sdev, struct snd_soc_tplg_vendor_array *array, int array_size, u32 token_id, int token_instance_num, struct snd_sof_tuple *tuples, int tuples_size, int *num_copied_tuples)
{ conststruct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg); conststruct sof_token_info *token_list; conststruct sof_topology_token *tokens; int found = 0; int num_tokens, asize; int i, j;
token_list = tplg_ops ? tplg_ops->token_list : NULL; /* nothing to do if token_list is NULL */ if (!token_list) return 0;
if (!tokens) {
dev_err(sdev->dev, "No token array defined for token ID: %d\n", token_id); return -EINVAL;
}
/* check if there's space in the tuples array for new tokens */ if (*num_copied_tuples >= tuples_size) {
dev_err(sdev->dev, "No space in tuples array for new tokens from %s",
token_list[token_id].name); return -EINVAL;
}
while (array_size > 0 && found < num_tokens * token_instance_num) {
asize = le32_to_cpu(array->size);
/* make sure there is enough data before parsing */
array_size -= asize; if (array_size < 0) {
dev_err(sdev->dev, "Invalid array size 0x%x\n", asize); return -EINVAL;
}
/* parse element by element */ for (i = 0; i < le32_to_cpu(array->num_elems); i++) { /* search for token */ for (j = 0; j < num_tokens; j++) { /* match token type */ if (!(tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_WORD ||
tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_SHORT ||
tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_BYTE ||
tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_BOOL ||
tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_STRING)) continue;
if (tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_STRING) { struct snd_soc_tplg_vendor_string_elem *elem;
elem = &array->string[i];
/* match token id */ if (tokens[j].token != le32_to_cpu(elem->token)) continue;
/** * sof_parse_string_tokens - Parse multiple sets of tokens * @scomp: pointer to soc component * @object: target ipc struct for parsed values * @offset: offset within the object pointer * @tokens: array of struct sof_topology_token containing the tokens to be matched * @num_tokens: number of tokens in tokens array * @array: source pointer to consecutive vendor arrays in topology * * This function parses multiple sets of string type tokens in vendor arrays
*/ staticint sof_parse_string_tokens(struct snd_soc_component *scomp, void *object, int offset, conststruct sof_topology_token *tokens, int num_tokens, struct snd_soc_tplg_vendor_array *array)
{ struct snd_soc_tplg_vendor_string_elem *elem; int found = 0; int i, j, ret;
/* parse element by element */ for (i = 0; i < le32_to_cpu(array->num_elems); i++) {
elem = &array->string[i];
/* search for token */ for (j = 0; j < num_tokens; j++) { /* match token type */ if (tokens[j].type != SND_SOC_TPLG_TUPLE_TYPE_STRING) continue;
/* match token id */ if (tokens[j].token != le32_to_cpu(elem->token)) continue;
/* matched - now load token */
ret = tokens[j].get_token(elem->string, object, offset + tokens[j].offset); if (ret < 0) return ret;
found++;
}
}
return found;
}
/** * sof_parse_word_tokens - Parse multiple sets of tokens * @scomp: pointer to soc component * @object: target ipc struct for parsed values * @offset: offset within the object pointer * @tokens: array of struct sof_topology_token containing the tokens to be matched * @num_tokens: number of tokens in tokens array * @array: source pointer to consecutive vendor arrays in topology * * This function parses multiple sets of word type tokens in vendor arrays
*/ staticint sof_parse_word_tokens(struct snd_soc_component *scomp, void *object, int offset, conststruct sof_topology_token *tokens, int num_tokens, struct snd_soc_tplg_vendor_array *array)
{ struct snd_soc_tplg_vendor_value_elem *elem; int found = 0; int i, j;
/* parse element by element */ for (i = 0; i < le32_to_cpu(array->num_elems); i++) {
elem = &array->value[i];
/* search for token */ for (j = 0; j < num_tokens; j++) { /* match token type */ if (!(tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_WORD ||
tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_SHORT ||
tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_BYTE ||
tokens[j].type == SND_SOC_TPLG_TUPLE_TYPE_BOOL)) continue;
/* match token id */ if (tokens[j].token != le32_to_cpu(elem->token)) continue;
/** * sof_parse_token_sets - Parse multiple sets of tokens * @scomp: pointer to soc component * @object: target ipc struct for parsed values * @tokens: token definition array describing what tokens to parse * @count: number of tokens in definition array * @array: source pointer to consecutive vendor arrays in topology * @array_size: total size of @array * @token_instance_num: number of times the same tokens needs to be parsed i.e. the function * looks for @token_instance_num of each token in the @tokens * @object_size: offset to next target ipc struct with multiple sets * * This function parses multiple sets of tokens in vendor arrays into * consecutive ipc structs.
*/ staticint sof_parse_token_sets(struct snd_soc_component *scomp, void *object, conststruct sof_topology_token *tokens, int count, struct snd_soc_tplg_vendor_array *array, int array_size, int token_instance_num, size_t object_size)
{
size_t offset = 0; int found = 0; int total = 0; int asize; int ret;
while (array_size > 0 && total < count * token_instance_num) {
asize = le32_to_cpu(array->size);
/* validate asize */ if (asize < 0) { /* FIXME: A zero-size array makes no sense */
dev_err(scomp->dev, "error: invalid array size 0x%x\n",
asize); return -EINVAL;
}
/* make sure there is enough data before parsing */
array_size -= asize; if (array_size < 0) {
dev_err(scomp->dev, "error: invalid array size 0x%x\n",
asize); return -EINVAL;
}
/* call correct parser depending on type */ switch (le32_to_cpu(array->type)) { case SND_SOC_TPLG_TUPLE_TYPE_UUID:
found += sof_parse_uuid_tokens(scomp, object, offset, tokens, count,
array); break; case SND_SOC_TPLG_TUPLE_TYPE_STRING:
ret = sof_parse_string_tokens(scomp, object, offset, tokens, count,
array); if (ret < 0) {
dev_err(scomp->dev, "error: no memory to copy string token\n"); return ret;
}
found += ret; break; case SND_SOC_TPLG_TUPLE_TYPE_BOOL: case SND_SOC_TPLG_TUPLE_TYPE_BYTE: case SND_SOC_TPLG_TUPLE_TYPE_WORD: case SND_SOC_TPLG_TUPLE_TYPE_SHORT:
found += sof_parse_word_tokens(scomp, object, offset, tokens, count,
array); break; default:
dev_err(scomp->dev, "error: unknown token type %d\n",
array->type); return -EINVAL;
}
/* move to next target struct */ if (found >= count) {
offset += object_size;
total += found;
found = 0;
}
}
return 0;
}
/** * sof_parse_tokens - Parse one set of tokens * @scomp: pointer to soc component * @object: target ipc struct for parsed values * @tokens: token definition array describing what tokens to parse * @num_tokens: number of tokens in definition array * @array: source pointer to consecutive vendor arrays in topology * @array_size: total size of @array * * This function parses a single set of tokens in vendor arrays into * consecutive ipc structs.
*/ staticint sof_parse_tokens(struct snd_soc_component *scomp, void *object, conststruct sof_topology_token *tokens, int num_tokens, struct snd_soc_tplg_vendor_array *array, int array_size)
{ /* * sof_parse_tokens is used when topology contains only a single set of * identical tuples arrays. So additional parameters to * sof_parse_token_sets are sets = 1 (only 1 set) and * object_size = 0 (irrelevant).
*/ return sof_parse_token_sets(scomp, object, tokens, num_tokens, array,
array_size, 1, 0);
}
/* validate topology data */ if (le32_to_cpu(mc->num_channels) > SND_SOC_TPLG_MAX_CHAN) return -EINVAL;
/* * If control has more than 2 channels we need to override the info. This is because even if * ASoC layer has defined topology's max channel count to SND_SOC_TPLG_MAX_CHAN = 8, the * pre-defined dapm control types (and related functions) creating the actual control * restrict the channels only to mono or stereo.
*/ if (le32_to_cpu(mc->num_channels) > 2)
kc->info = snd_sof_volume_info;
/* set up volume table */
ret = set_up_volume_table(scontrol, tlv, le32_to_cpu(mc->max) + 1); if (ret < 0) {
dev_err(scomp->dev, "error: setting up volume table\n"); return ret;
}
skip: /* set up possible led control from mixer private data */
ret = sof_parse_tokens(scomp, &scontrol->led_ctl, led_tokens,
ARRAY_SIZE(led_tokens), mc->priv.array,
le32_to_cpu(mc->priv.size)); if (ret != 0) {
dev_err(scomp->dev, "error: parse led tokens failed %d\n",
le32_to_cpu(mc->priv.size)); goto err;
}
dev_dbg(scomp->dev, "tplg: load kcontrol index %d\n", scontrol->comp_id);
/* copy the private data */ if (priv_size > 0) {
scontrol->priv = kmemdup(control->priv.data, priv_size, GFP_KERNEL); if (!scontrol->priv) return -ENOMEM;
scontrol->priv_size = priv_size;
}
return 0;
}
/* external kcontrol init - used for any driver specific init */ staticint sof_control_load(struct snd_soc_component *scomp, int index, struct snd_kcontrol_new *kc, struct snd_soc_tplg_ctl_hdr *hdr)
{ struct soc_mixer_control *sm; struct soc_bytes_ext *sbe; struct soc_enum *se; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct snd_soc_dobj *dobj; struct snd_sof_control *scontrol; int ret;
dev_dbg(scomp->dev, "tplg: load control type %d name : %s\n",
hdr->type, hdr->name);
scontrol = kzalloc(sizeof(*scontrol), GFP_KERNEL); if (!scontrol) return -ENOMEM;
switch (le32_to_cpu(hdr->ops.info)) { case SND_SOC_TPLG_CTL_VOLSW: case SND_SOC_TPLG_CTL_VOLSW_SX: case SND_SOC_TPLG_CTL_VOLSW_XR_SX:
sm = (struct soc_mixer_control *)kc->private_value;
dobj = &sm->dobj;
ret = sof_control_load_volume(scomp, scontrol, kc, hdr); break; case SND_SOC_TPLG_CTL_BYTES:
sbe = (struct soc_bytes_ext *)kc->private_value;
dobj = &sbe->dobj;
ret = sof_control_load_bytes(scomp, scontrol, kc, hdr); break; case SND_SOC_TPLG_CTL_ENUM: case SND_SOC_TPLG_CTL_ENUM_VALUE:
se = (struct soc_enum *)kc->private_value;
dobj = &se->dobj;
ret = sof_control_load_enum(scomp, scontrol, kc, hdr); break; case SND_SOC_TPLG_CTL_RANGE: case SND_SOC_TPLG_CTL_STROBE: case SND_SOC_TPLG_DAPM_CTL_VOLSW: case SND_SOC_TPLG_DAPM_CTL_ENUM_DOUBLE: case SND_SOC_TPLG_DAPM_CTL_ENUM_VIRT: case SND_SOC_TPLG_DAPM_CTL_ENUM_VALUE: case SND_SOC_TPLG_DAPM_CTL_PIN: default:
dev_warn(scomp->dev, "control type not supported %d:%d:%d\n",
hdr->ops.get, hdr->ops.put, hdr->ops.info);
kfree(scontrol->name);
kfree(scontrol); return 0;
}
if (ret < 0) {
kfree(scontrol->name);
kfree(scontrol); return ret;
}
dev_dbg(scomp->dev, "tplg: unload control name : %s\n", scontrol->name);
if (tplg_ops && tplg_ops->control_free) {
ret = tplg_ops->control_free(sdev, scontrol); if (ret < 0)
dev_err(scomp->dev, "failed to free control: %s\n", scontrol->name);
}
/* free all data before returning in case of error too */
kfree(scontrol->ipc_control_data);
kfree(scontrol->priv);
kfree(scontrol->name);
list_del(&scontrol->list);
kfree(scontrol);
full = NULL;
partial = NULL;
list_for_each_entry(rtd, &card->rtd_list, list) { /* does stream match DAI link ? */ if (rtd->dai_link->stream_name) { if (!strcmp(rtd->dai_link->stream_name, w->sname)) {
full = rtd; break;
} elseif (strstr(rtd->dai_link->stream_name, w->sname)) {
partial = rtd;
}
}
}
rtd = full ? full : partial; if (rtd) {
for_each_rtd_cpu_dais(rtd, i, cpu_dai) { /* * Please create DAI widget in the right order * to ensure BE will connect to the right DAI * widget.
*/ if (!snd_soc_dai_get_widget(cpu_dai, stream)) {
snd_soc_dai_set_widget(cpu_dai, stream, w); break;
}
} if (i == rtd->dai_link->num_cpus) {
dev_err(scomp->dev, "error: can't find BE for DAI %s\n", w->name);
return -EINVAL;
}
dai->name = rtd->dai_link->name;
dev_dbg(scomp->dev, "tplg: connected widget %s -> DAI link %s\n",
w->name, rtd->dai_link->name);
}
end: /* check we have a connection */ if (!dai->name) {
dev_err(scomp->dev, "error: can't connect DAI %s stream %s\n",
w->name, w->sname); return -EINVAL;
}
list_for_each_entry(rtd, &card->rtd_list, list) { /* does stream match DAI link ? */ if (!rtd->dai_link->stream_name ||
!strstr(rtd->dai_link->stream_name, sname)) continue;
for_each_rtd_cpu_dais(rtd, i, cpu_dai) if (snd_soc_dai_get_widget(cpu_dai, stream) == w) {
snd_soc_dai_set_widget(cpu_dai, stream, NULL); break;
}
}
}
/* bind PCM ID to host component ID */ staticint spcm_bind(struct snd_soc_component *scomp, struct snd_sof_pcm *spcm, int dir)
{ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct snd_sof_widget *host_widget;
if (sdev->dspless_mode_selected) return 0;
host_widget = snd_sof_find_swidget_sname(scomp,
spcm->pcm.caps[dir].name,
dir); if (!host_widget) {
dev_err(scomp->dev, "can't find host comp to bind pcm\n"); return -EINVAL;
}
spcm->stream[dir].comp_id = host_widget->comp_id;
return 0;
}
staticint sof_get_token_value(u32 token_id, struct snd_sof_tuple *tuples, int num_tuples)
{ int i;
if (!tuples) return -EINVAL;
for (i = 0; i < num_tuples; i++) { if (tuples[i].token == token_id) return tuples[i].value.v;
}
/* copy one set of tuples per token ID into swidget->tuples */
ret = sof_copy_tuples(sdev, private->array, le32_to_cpu(private->size),
object_token_list[i], num_sets, swidget->tuples,
num_tuples, &swidget->num_tuples); if (ret < 0) {
dev_err(scomp->dev, "Failed parsing %s for widget %s err: %d\n",
token_list[object_token_list[i]].name, swidget->widget->name, ret); goto err;
}
}
/* copy pin binding array to swidget only if it is defined in topology */ if (pin_binding[0]) {
pb = kmemdup_array(pin_binding, num_pins, sizeof(char *), GFP_KERNEL); if (!pb) {
ret = -ENOMEM; goto err;
} if (pin_type == SOF_PIN_TYPE_INPUT)
swidget->input_pin_binding = pb; else
swidget->output_pin_binding = pb;
}
return 0;
err: for (i = 0; i < num_pins; i++)
kfree(pin_binding[i]);
ret = sof_parse_tokens(scomp, w, dapm_widget_tokens, ARRAY_SIZE(dapm_widget_tokens),
priv->array, le32_to_cpu(priv->size)); if (ret < 0) {
dev_err(scomp->dev, "failed to parse dapm widget tokens for %s\n",
w->name); goto widget_free;
}
ret = sof_parse_tokens(scomp, swidget, comp_pin_tokens,
ARRAY_SIZE(comp_pin_tokens), priv->array,
le32_to_cpu(priv->size)); if (ret < 0) {
dev_err(scomp->dev, "failed to parse component pin tokens for %s\n",
w->name); goto widget_free;
}
if (swidget->num_input_pins > SOF_WIDGET_MAX_NUM_PINS ||
swidget->num_output_pins > SOF_WIDGET_MAX_NUM_PINS) {
dev_err(scomp->dev, "invalid pins for %s: [input: %d, output: %d]\n",
swidget->widget->name, swidget->num_input_pins, swidget->num_output_pins);
ret = -EINVAL; goto widget_free;
}
if (swidget->num_input_pins > 1) {
ret = sof_parse_pin_binding(swidget, priv, SOF_PIN_TYPE_INPUT); /* on parsing error, pin binding is not allocated, nothing to free. */ if (ret < 0) {
dev_err(scomp->dev, "failed to parse input pin binding for %s\n",
w->name); goto widget_free;
}
}
if (swidget->num_output_pins > 1) {
ret = sof_parse_pin_binding(swidget, priv, SOF_PIN_TYPE_OUTPUT); /* on parsing error, pin binding is not allocated, nothing to free. */ if (ret < 0) {
dev_err(scomp->dev, "failed to parse output pin binding for %s\n",
w->name); goto widget_free;
}
}
/* handle any special case widgets */ switch (w->id) { case snd_soc_dapm_dai_in: case snd_soc_dapm_dai_out:
dai = kzalloc(sizeof(*dai), GFP_KERNEL); if (!dai) {
ret = -ENOMEM; goto widget_free;
}
ret = sof_widget_parse_tokens(scomp, swidget, tw, token_list, token_list_size); if (!ret)
ret = sof_connect_dai_widget(scomp, w, tw, dai); if (ret < 0) {
kfree(dai); break;
}
list_add(&dai->list, &sdev->dai_list);
swidget->private = dai; break; case snd_soc_dapm_effect: /* check we have some tokens - we need at least process type */ if (le32_to_cpu(tw->priv.size) == 0) {
dev_err(scomp->dev, "error: process tokens not found\n");
ret = -EINVAL; break;
}
ret = sof_widget_parse_tokens(scomp, swidget, tw, token_list, token_list_size); break; case snd_soc_dapm_pga: if (!le32_to_cpu(tw->num_kcontrols)) {
dev_err(scomp->dev, "invalid kcontrol count %d for volume\n",
tw->num_kcontrols);
ret = -EINVAL; break;
}
fallthrough; case snd_soc_dapm_mixer: case snd_soc_dapm_buffer: case snd_soc_dapm_scheduler: case snd_soc_dapm_aif_out: case snd_soc_dapm_aif_in: case snd_soc_dapm_src: case snd_soc_dapm_asrc: case snd_soc_dapm_siggen: case snd_soc_dapm_mux: case snd_soc_dapm_demux:
ret = sof_widget_parse_tokens(scomp, swidget, tw, token_list, token_list_size); break; case snd_soc_dapm_switch: case snd_soc_dapm_dai_link: case snd_soc_dapm_kcontrol: default:
dev_dbg(scomp->dev, "widget type %d name %s not handled\n", swidget->id, tw->name); break;
}
/* check token parsing reply */ if (ret < 0) {
dev_err(scomp->dev, "failed to add widget type %d name : %s stream %s\n",
swidget->id, tw->name, strnlen(tw->sname, SNDRV_CTL_ELEM_ID_NAME_MAXLEN) > 0
? tw->sname : "none"); goto widget_free;
}
if (sof_debug_check_flag(SOF_DBG_DISABLE_MULTICORE)) {
swidget->core = SOF_DSP_PRIMARY_CORE;
} else { int core = sof_get_token_value(SOF_TKN_COMP_CORE_ID, swidget->tuples,
swidget->num_tuples);
if (core >= 0)
swidget->core = core;
}
/* bind widget to external event */ if (tw->event_type) { if (widget_ops && widget_ops[w->id].bind_event) {
ret = widget_ops[w->id].bind_event(scomp, swidget,
le16_to_cpu(tw->event_type)); if (ret) {
dev_err(scomp->dev, "widget event binding failed for %s\n",
swidget->widget->name); goto free;
}
}
}
/* create and add pipeline for scheduler type widgets */ if (w->id == snd_soc_dapm_scheduler) { struct snd_sof_pipeline *spipe;
spipe = kzalloc(sizeof(*spipe), GFP_KERNEL); if (!spipe) {
ret = -ENOMEM; goto free;
}
/* perform pcm set op */ if (ipc_pcm_ops && ipc_pcm_ops->pcm_setup) {
ret = ipc_pcm_ops->pcm_setup(sdev, spcm); if (ret < 0) {
kfree(spcm); return ret;
}
}
/* DAI link - used for any driver specific init */ staticint sof_link_load(struct snd_soc_component *scomp, int index, struct snd_soc_dai_link *link, struct snd_soc_tplg_link_config *cfg)
{ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); conststruct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg); struct snd_soc_tplg_private *private = &cfg->priv; conststruct sof_token_info *token_list; struct snd_sof_dai_link *slink;
u32 token_id = 0; int num_tuples = 0; int ret, num_sets;
if (!link->platforms) {
dev_err(scomp->dev, "error: no platforms\n"); return -EINVAL;
}
link->platforms->name = dev_name(scomp->dev);
if (tplg_ops && tplg_ops->link_setup) {
ret = tplg_ops->link_setup(sdev, link); if (ret < 0) return ret;
}
/* Set nonatomic property for FE dai links as their trigger action involves IPC's */ if (!link->no_pcm) {
link->nonatomic = true; return 0;
}
/* check we have some tokens - we need at least DAI type */ if (le32_to_cpu(private->size) == 0) {
dev_err(scomp->dev, "error: expected tokens for DAI, none found\n"); return -EINVAL;
}
slink = kzalloc(sizeof(*slink), GFP_KERNEL); if (!slink) return -ENOMEM;
/* Allocate memory for max PDM controllers */
num_tuples += token_list[SOF_DMIC_PDM_TOKENS].count * SOF_DAI_INTEL_DMIC_NUM_CTRL; break; case SOF_DAI_INTEL_HDA:
token_id = SOF_HDA_TOKENS;
num_tuples += token_list[SOF_HDA_TOKENS].count; break; case SOF_DAI_INTEL_ALH:
token_id = SOF_ALH_TOKENS;
num_tuples += token_list[SOF_ALH_TOKENS].count; break; case SOF_DAI_IMX_SAI:
token_id = SOF_SAI_TOKENS;
num_tuples += token_list[SOF_SAI_TOKENS].count; break; case SOF_DAI_IMX_ESAI:
token_id = SOF_ESAI_TOKENS;
num_tuples += token_list[SOF_ESAI_TOKENS].count; break; case SOF_DAI_MEDIATEK_AFE:
token_id = SOF_AFE_TOKENS;
num_tuples += token_list[SOF_AFE_TOKENS].count; break; case SOF_DAI_AMD_DMIC:
token_id = SOF_ACPDMIC_TOKENS;
num_tuples += token_list[SOF_ACPDMIC_TOKENS].count; break; case SOF_DAI_AMD_BT: case SOF_DAI_AMD_SP: case SOF_DAI_AMD_HS: case SOF_DAI_AMD_SP_VIRTUAL: case SOF_DAI_AMD_HS_VIRTUAL:
token_id = SOF_ACPI2S_TOKENS;
num_tuples += token_list[SOF_ACPI2S_TOKENS].count; break; case SOF_DAI_IMX_MICFIL:
token_id = SOF_MICFIL_TOKENS;
num_tuples += token_list[SOF_MICFIL_TOKENS].count; break; case SOF_DAI_AMD_SDW:
token_id = SOF_ACP_SDW_TOKENS;
num_tuples += token_list[SOF_ACP_SDW_TOKENS].count; break; default: break;
}
/* allocate memory for tuples array */
slink->tuples = kcalloc(num_tuples, sizeof(*slink->tuples), GFP_KERNEL); if (!slink->tuples) {
kfree(slink->hw_configs);
kfree(slink); return -ENOMEM;
}
if (token_list[SOF_DAI_LINK_TOKENS].tokens) { /* parse one set of DAI link tokens */
ret = sof_copy_tuples(sdev, private->array, le32_to_cpu(private->size),
SOF_DAI_LINK_TOKENS, 1, slink->tuples,
num_tuples, &slink->num_tuples); if (ret < 0) {
dev_err(scomp->dev, "failed to parse %s for dai link %s\n",
token_list[SOF_DAI_LINK_TOKENS].name, link->name); goto err;
}
}
/* nothing more to do if there are no DAI type-specific tokens defined */ if (!token_id || !token_list[token_id].tokens) goto out;
/* parse "num_sets" sets of DAI-specific tokens */
ret = sof_copy_tuples(sdev, private->array, le32_to_cpu(private->size),
token_id, num_sets, slink->tuples, num_tuples, &slink->num_tuples); if (ret < 0) {
dev_err(scomp->dev, "failed to parse %s for dai link %s\n",
token_list[token_id].name, link->name); goto err;
}
/* for DMIC, also parse all sets of DMIC PDM tokens based on active PDM count */ if (token_id == SOF_DMIC_TOKENS) {
num_sets = sof_get_token_value(SOF_TKN_INTEL_DMIC_NUM_PDM_ACTIVE,
slink->tuples, slink->num_tuples);
if (num_sets < 0) {
dev_err(sdev->dev, "Invalid active PDM count for %s\n", link->name);
ret = num_sets; goto err;
}
ret = sof_copy_tuples(sdev, private->array, le32_to_cpu(private->size),
SOF_DMIC_PDM_TOKENS, num_sets, slink->tuples,
num_tuples, &slink->num_tuples); if (ret < 0) {
dev_err(scomp->dev, "failed to parse %s for dai link %s\n",
token_list[SOF_DMIC_PDM_TOKENS].name, link->name); goto err;
}
}
out:
link->dobj.private = slink;
list_add(&slink->list, &sdev->dai_link_list);
/* DAI link - used for any driver specific init */ staticint sof_route_load(struct snd_soc_component *scomp, int index, struct snd_soc_dapm_route *route)
{ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct snd_sof_widget *source_swidget, *sink_swidget; struct snd_soc_dobj *dobj = &route->dobj; struct snd_sof_route *sroute; int ret = 0;
/* allocate memory for sroute and connect */
sroute = kzalloc(sizeof(*sroute), GFP_KERNEL); if (!sroute) return -ENOMEM;
/* source component */
source_swidget = snd_sof_find_swidget(scomp, (char *)route->source); if (!source_swidget) {
dev_err(scomp->dev, "error: source %s not found\n",
route->source);
ret = -EINVAL; goto err;
}
/* * Virtual widgets of type output/out_drv may be added in topology * for compatibility. These are not handled by the FW. * So, don't send routes whose source/sink widget is of such types * to the DSP.
*/ if (source_swidget->id == snd_soc_dapm_out_drv ||
source_swidget->id == snd_soc_dapm_output) goto err;
/* sink component */
sink_swidget = snd_sof_find_swidget(scomp, (char *)route->sink); if (!sink_swidget) {
dev_err(scomp->dev, "error: sink %s not found\n",
route->sink);
ret = -EINVAL; goto err;
}
/* * Don't send routes whose sink widget is of type * output or out_drv to the DSP
*/ if (sink_swidget->id == snd_soc_dapm_out_drv ||
sink_swidget->id == snd_soc_dapm_output) goto err;
/* add route to route list */
list_add(&sroute->list, &sdev->route_list);
return 0;
err:
kfree(sroute); return ret;
}
/** * sof_set_widget_pipeline - Set pipeline for a component * @sdev: pointer to struct snd_sof_dev * @spipe: pointer to struct snd_sof_pipeline * @swidget: pointer to struct snd_sof_widget that has the same pipeline ID as @pipe_widget * * Return: 0 if successful, -EINVAL on error. * The function checks if @swidget is associated with any volatile controls. If so, setting * the dynamic_pipeline_widget is disallowed.
*/ staticint sof_set_widget_pipeline(struct snd_sof_dev *sdev, struct snd_sof_pipeline *spipe, struct snd_sof_widget *swidget)
{ struct snd_sof_widget *pipe_widget = spipe->pipe_widget; struct snd_sof_control *scontrol;
if (pipe_widget->dynamic_pipeline_widget) { /* dynamic widgets cannot have volatile kcontrols */
list_for_each_entry(scontrol, &sdev->kcontrol_list, list) if (scontrol->comp_id == swidget->comp_id &&
(scontrol->access & SNDRV_CTL_ELEM_ACCESS_VOLATILE)) {
dev_err(sdev->dev, "error: volatile control found for dynamic widget %s\n",
swidget->widget->name); return -EINVAL;
}
}
/* set the pipeline and apply the dynamic_pipeline_widget_flag */
swidget->spipe = spipe;
swidget->dynamic_pipeline_widget = pipe_widget->dynamic_pipeline_widget;
return 0;
}
/* completion - called at completion of firmware loading */ staticint sof_complete(struct snd_soc_component *scomp)
{ struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); conststruct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg); conststruct sof_ipc_tplg_widget_ops *widget_ops; struct snd_sof_control *scontrol; struct snd_sof_pipeline *spipe; int ret;
widget_ops = tplg_ops ? tplg_ops->widget : NULL;
/* first update all control IPC structures based on the IPC version */ if (tplg_ops && tplg_ops->control_setup)
list_for_each_entry(scontrol, &sdev->kcontrol_list, list) {
ret = tplg_ops->control_setup(sdev, scontrol); if (ret < 0) {
dev_err(sdev->dev, "failed updating IPC struct for control %s\n",
scontrol->name); return ret;
}
}
/* set up the IPC structures for the pipeline widgets */
list_for_each_entry(spipe, &sdev->pipeline_list, list) { struct snd_sof_widget *pipe_widget = spipe->pipe_widget; struct snd_sof_widget *swidget;
pipe_widget->instance_id = -EINVAL;
/* Update the scheduler widget's IPC structure */ if (widget_ops && widget_ops[pipe_widget->id].ipc_setup) {
ret = widget_ops[pipe_widget->id].ipc_setup(pipe_widget); if (ret < 0) {
dev_err(sdev->dev, "failed updating IPC struct for %s\n",
pipe_widget->widget->name); return ret;
}
}
/* set the pipeline and update the IPC structure for the non scheduler widgets */
list_for_each_entry(swidget, &sdev->widget_list, list) if (swidget->widget->id != snd_soc_dapm_scheduler &&
swidget->pipeline_id == pipe_widget->pipeline_id) {
ret = sof_set_widget_pipeline(sdev, spipe, swidget); if (ret < 0) return ret;
if (widget_ops && widget_ops[swidget->id].ipc_setup) {
ret = widget_ops[swidget->id].ipc_setup(swidget); if (ret < 0) {
dev_err(sdev->dev, "failed updating IPC struct for %s\n",
swidget->widget->name); return ret;
}
}
}
}
/* verify topology components loading including dynamic pipelines */ if (sof_debug_check_flag(SOF_DBG_VERIFY_TPLG)) { if (tplg_ops && tplg_ops->set_up_all_pipelines &&
tplg_ops->tear_down_all_pipelines) {
ret = tplg_ops->set_up_all_pipelines(sdev, true); if (ret < 0) {
dev_err(sdev->dev, "Failed to set up all topology pipelines: %d\n",
ret); return ret;
}
ret = tplg_ops->tear_down_all_pipelines(sdev, true); if (ret < 0) {
dev_err(sdev->dev, "Failed to tear down topology pipelines: %d\n",
ret); return ret;
}
}
}
/* set up static pipelines */ if (tplg_ops && tplg_ops->set_up_all_pipelines) return tplg_ops->set_up_all_pipelines(sdev, false);
if (tplg_ops && tplg_ops->parse_manifest) return tplg_ops->parse_manifest(scomp, index, man);
return 0;
}
/* vendor specific kcontrol handlers available for binding */ staticconststruct snd_soc_tplg_kcontrol_ops sof_io_ops[] = {
{SOF_TPLG_KCTL_VOL_ID, snd_sof_volume_get, snd_sof_volume_put},
{SOF_TPLG_KCTL_BYTES_ID, snd_sof_bytes_get, snd_sof_bytes_put},
{SOF_TPLG_KCTL_ENUM_ID, snd_sof_enum_get, snd_sof_enum_put},
{SOF_TPLG_KCTL_SWITCH_ID, snd_sof_switch_get, snd_sof_switch_put},
};
/* vendor specific bytes ext handlers available for binding */ staticconststruct snd_soc_tplg_bytes_ext_ops sof_bytes_ext_ops[] = {
{SOF_TPLG_KCTL_BYTES_ID, snd_sof_bytes_ext_get, snd_sof_bytes_ext_put},
{SOF_TPLG_KCTL_BYTES_VOLATILE_RO, snd_sof_bytes_ext_volatile_get},
};
staticconststruct snd_soc_tplg_ops sof_tplg_ops = { /* external kcontrol init - used for any driver specific init */
.control_load = sof_control_load,
.control_unload = sof_control_unload,
/* external kcontrol init - used for any driver specific init */
.dapm_route_load = sof_route_load,
.dapm_route_unload = sof_route_unload,
/* external widget init - used for any driver specific init */ /* .widget_load is not currently used */
.widget_ready = sof_widget_ready,
.widget_unload = sof_widget_unload,
/* FE DAI - used for any driver specific init */
.dai_load = sof_dai_load,
.dai_unload = sof_dai_unload,
/* DAI link - used for any driver specific init */
.link_load = sof_link_load,
.link_unload = sof_link_unload,
/* * No need to set the complete callback. sof_complete will be called explicitly after * topology loading is complete.
*/
/* manifest - optional to inform component of manifest */
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.