Make the SOF control, PCM and PM code IPC agnostic

Merge series from Ranjani Sridharan <ranjani.sridharan@linux.intel.com>:

This series is a continuation to the SOF IPC abstraction work to support
the new IPC version introduced in the SOF firmware. It makes the top-level
control IO, PCM and PM code IPC-agnostic. Other than the first patch,
the rest are purely for abstraction and include no changes in
functionality.
This commit is contained in:
Mark Brown 2022-03-18 20:11:08 +00:00
commit 49a24e9d9c
No known key found for this signature in database
GPG Key ID: 24D68B725D5487D0
17 changed files with 1811 additions and 1465 deletions

View File

@ -2,7 +2,7 @@
snd-sof-objs := core.o ops.o loader.o ipc.o pcm.o pm.o debug.o topology.o\
control.o trace.o iomem-utils.o sof-audio.o stream-ipc.o\
ipc3-topology.o
ipc3-topology.o ipc3.o ipc3-control.o ipc3-pcm.o
ifneq ($(CONFIG_SND_SOC_SOF_CLIENT),)
snd-sof-objs += sof-client.o
endif

View File

@ -45,68 +45,17 @@ static void update_mute_led(struct snd_sof_control *scontrol,
#endif
}
static inline u32 mixer_to_ipc(unsigned int value, u32 *volume_map, int size)
{
if (value >= size)
return volume_map[size - 1];
return volume_map[value];
}
static inline u32 ipc_to_mixer(u32 value, u32 *volume_map, int size)
{
int i;
for (i = 0; i < size; i++) {
if (volume_map[i] >= value)
return i;
}
return i - 1;
}
static void snd_sof_refresh_control(struct snd_sof_control *scontrol)
{
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
struct snd_soc_component *scomp = scontrol->scomp;
int ret;
if (!scontrol->comp_data_dirty)
return;
if (!pm_runtime_active(scomp->dev))
return;
/* set the ABI header values */
cdata->data->magic = SOF_ABI_MAGIC;
cdata->data->abi = SOF_ABI_VERSION;
/* refresh the component data from DSP */
scontrol->comp_data_dirty = false;
ret = snd_sof_ipc_set_get_comp_data(scontrol, false);
if (ret < 0) {
dev_err(scomp->dev, "error: failed to get control data: %d\n", ret);
/* Set the flag to re-try next time to get the data */
scontrol->comp_data_dirty = true;
}
}
int snd_sof_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *sm =
(struct soc_mixer_control *)kcontrol->private_value;
struct soc_mixer_control *sm = (struct soc_mixer_control *)kcontrol->private_value;
struct snd_sof_control *scontrol = sm->dobj.private;
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
unsigned int i, channels = scontrol->num_channels;
struct snd_soc_component *scomp = scontrol->scomp;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
snd_sof_refresh_control(scontrol);
/* read back each channel */
for (i = 0; i < channels; i++)
ucontrol->value.integer.value[i] =
ipc_to_mixer(cdata->chanv[i].value,
scontrol->volume_table, sm->max + 1);
if (tplg_ops->control->volume_get)
return tplg_ops->control->volume_get(scontrol, ucontrol);
return 0;
}
@ -114,28 +63,16 @@ int snd_sof_volume_get(struct snd_kcontrol *kcontrol,
int snd_sof_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *sm =
(struct soc_mixer_control *)kcontrol->private_value;
struct soc_mixer_control *sm = (struct soc_mixer_control *)kcontrol->private_value;
struct snd_sof_control *scontrol = sm->dobj.private;
struct snd_soc_component *scomp = scontrol->scomp;
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
unsigned int i, channels = scontrol->num_channels;
bool change = false;
u32 value;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
/* update each channel */
for (i = 0; i < channels; i++) {
value = mixer_to_ipc(ucontrol->value.integer.value[i],
scontrol->volume_table, sm->max + 1);
change = change || (value != cdata->chanv[i].value);
cdata->chanv[i].channel = i;
cdata->chanv[i].value = value;
}
if (tplg_ops->control->volume_put)
return tplg_ops->control->volume_put(scontrol, ucontrol);
/* notify DSP of mixer updates */
if (pm_runtime_active(scomp->dev))
snd_sof_ipc_set_get_comp_data(scontrol, true);
return change;
return false;
}
int snd_sof_volume_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
@ -163,17 +100,14 @@ int snd_sof_volume_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info
int snd_sof_switch_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *sm =
(struct soc_mixer_control *)kcontrol->private_value;
struct soc_mixer_control *sm = (struct soc_mixer_control *)kcontrol->private_value;
struct snd_sof_control *scontrol = sm->dobj.private;
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
unsigned int i, channels = scontrol->num_channels;
struct snd_soc_component *scomp = scontrol->scomp;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
snd_sof_refresh_control(scontrol);
/* read back each channel */
for (i = 0; i < channels; i++)
ucontrol->value.integer.value[i] = cdata->chanv[i].value;
if (tplg_ops->control->switch_get)
return tplg_ops->control->switch_get(scontrol, ucontrol);
return 0;
}
@ -181,47 +115,32 @@ int snd_sof_switch_get(struct snd_kcontrol *kcontrol,
int snd_sof_switch_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_mixer_control *sm =
(struct soc_mixer_control *)kcontrol->private_value;
struct soc_mixer_control *sm = (struct soc_mixer_control *)kcontrol->private_value;
struct snd_sof_control *scontrol = sm->dobj.private;
struct snd_soc_component *scomp = scontrol->scomp;
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
unsigned int i, channels = scontrol->num_channels;
bool change = false;
u32 value;
/* update each channel */
for (i = 0; i < channels; i++) {
value = ucontrol->value.integer.value[i];
change = change || (value != cdata->chanv[i].value);
cdata->chanv[i].channel = i;
cdata->chanv[i].value = value;
}
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
if (scontrol->led_ctl.use_led)
update_mute_led(scontrol, kcontrol, ucontrol);
/* notify DSP of mixer updates */
if (pm_runtime_active(scomp->dev))
snd_sof_ipc_set_get_comp_data(scontrol, true);
if (tplg_ops->control->switch_put)
return tplg_ops->control->switch_put(scontrol, ucontrol);
return change;
return false;
}
int snd_sof_enum_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_enum *se =
(struct soc_enum *)kcontrol->private_value;
struct soc_enum *se = (struct soc_enum *)kcontrol->private_value;
struct snd_sof_control *scontrol = se->dobj.private;
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
unsigned int i, channels = scontrol->num_channels;
struct snd_soc_component *scomp = scontrol->scomp;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
snd_sof_refresh_control(scontrol);
/* read back each channel */
for (i = 0; i < channels; i++)
ucontrol->value.enumerated.item[i] = cdata->chanv[i].value;
if (tplg_ops->control->enum_get)
return tplg_ops->control->enum_get(scontrol, ucontrol);
return 0;
}
@ -229,62 +148,29 @@ int snd_sof_enum_get(struct snd_kcontrol *kcontrol,
int snd_sof_enum_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_enum *se =
(struct soc_enum *)kcontrol->private_value;
struct soc_enum *se = (struct soc_enum *)kcontrol->private_value;
struct snd_sof_control *scontrol = se->dobj.private;
struct snd_soc_component *scomp = scontrol->scomp;
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
unsigned int i, channels = scontrol->num_channels;
bool change = false;
u32 value;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
/* update each channel */
for (i = 0; i < channels; i++) {
value = ucontrol->value.enumerated.item[i];
change = change || (value != cdata->chanv[i].value);
cdata->chanv[i].channel = i;
cdata->chanv[i].value = value;
}
if (tplg_ops->control->enum_put)
return tplg_ops->control->enum_put(scontrol, ucontrol);
/* notify DSP of enum updates */
if (pm_runtime_active(scomp->dev))
snd_sof_ipc_set_get_comp_data(scontrol, true);
return change;
return false;
}
int snd_sof_bytes_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_bytes_ext *be =
(struct soc_bytes_ext *)kcontrol->private_value;
struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value;
struct snd_sof_control *scontrol = be->dobj.private;
struct snd_soc_component *scomp = scontrol->scomp;
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
struct sof_abi_hdr *data = cdata->data;
size_t size;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
snd_sof_refresh_control(scontrol);
if (be->max > sizeof(ucontrol->value.bytes.data)) {
dev_err_ratelimited(scomp->dev,
"error: data max %d exceeds ucontrol data array size\n",
be->max);
return -EINVAL;
}
/* be->max has been verified to be >= sizeof(struct sof_abi_hdr) */
if (data->size > be->max - sizeof(*data)) {
dev_err_ratelimited(scomp->dev,
"error: %u bytes of control data is invalid, max is %zu\n",
data->size, be->max - sizeof(*data));
return -EINVAL;
}
size = data->size + sizeof(*data);
/* copy back to kcontrol */
memcpy(ucontrol->value.bytes.data, data, size);
if (tplg_ops->control->bytes_get)
return tplg_ops->control->bytes_get(scontrol, ucontrol);
return 0;
}
@ -292,37 +178,14 @@ int snd_sof_bytes_get(struct snd_kcontrol *kcontrol,
int snd_sof_bytes_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct soc_bytes_ext *be =
(struct soc_bytes_ext *)kcontrol->private_value;
struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value;
struct snd_sof_control *scontrol = be->dobj.private;
struct snd_soc_component *scomp = scontrol->scomp;
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
struct sof_abi_hdr *data = cdata->data;
size_t size;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
if (be->max > sizeof(ucontrol->value.bytes.data)) {
dev_err_ratelimited(scomp->dev,
"error: data max %d exceeds ucontrol data array size\n",
be->max);
return -EINVAL;
}
/* be->max has been verified to be >= sizeof(struct sof_abi_hdr) */
if (data->size > be->max - sizeof(*data)) {
dev_err_ratelimited(scomp->dev,
"error: data size too big %u bytes max is %zu\n",
data->size, be->max - sizeof(*data));
return -EINVAL;
}
size = data->size + sizeof(*data);
/* copy from kcontrol */
memcpy(data, ucontrol->value.bytes.data, size);
/* notify DSP of byte control updates */
if (pm_runtime_active(scomp->dev))
snd_sof_ipc_set_get_comp_data(scontrol, true);
if (tplg_ops->control->bytes_put)
return tplg_ops->control->bytes_put(scontrol, ucontrol);
return 0;
}
@ -331,74 +194,18 @@ int snd_sof_bytes_ext_put(struct snd_kcontrol *kcontrol,
const unsigned int __user *binary_data,
unsigned int size)
{
struct soc_bytes_ext *be =
(struct soc_bytes_ext *)kcontrol->private_value;
struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value;
struct snd_sof_control *scontrol = be->dobj.private;
struct snd_soc_component *scomp = scontrol->scomp;
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
struct snd_ctl_tlv header;
const struct snd_ctl_tlv __user *tlvd =
(const struct snd_ctl_tlv __user *)binary_data;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
/* make sure we have at least a header */
if (size < sizeof(struct snd_ctl_tlv))
return -EINVAL;
/*
* The beginning of bytes data contains a header from where
* the length (as bytes) is needed to know the correct copy
* length of data from tlvd->tlv.
*/
if (copy_from_user(&header, tlvd, sizeof(struct snd_ctl_tlv)))
return -EFAULT;
/* make sure TLV info is consistent */
if (header.length + sizeof(struct snd_ctl_tlv) > size) {
dev_err_ratelimited(scomp->dev, "error: inconsistent TLV, data %d + header %zu > %d\n",
header.length, sizeof(struct snd_ctl_tlv), size);
return -EINVAL;
}
/* be->max is coming from topology */
if (header.length > be->max) {
dev_err_ratelimited(scomp->dev, "error: Bytes data size %d exceeds max %d.\n",
header.length, be->max);
return -EINVAL;
}
/* Check that header id matches the command */
if (header.numid != cdata->cmd) {
dev_err_ratelimited(scomp->dev,
"error: incorrect numid %d\n",
header.numid);
return -EINVAL;
}
if (copy_from_user(cdata->data, tlvd->tlv, header.length))
return -EFAULT;
if (cdata->data->magic != SOF_ABI_MAGIC) {
dev_err_ratelimited(scomp->dev,
"error: Wrong ABI magic 0x%08x.\n",
cdata->data->magic);
return -EINVAL;
}
if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, cdata->data->abi)) {
dev_err_ratelimited(scomp->dev, "error: Incompatible ABI version 0x%08x.\n",
cdata->data->abi);
return -EINVAL;
}
/* be->max has been verified to be >= sizeof(struct sof_abi_hdr) */
if (cdata->data->size > be->max - sizeof(struct sof_abi_hdr)) {
dev_err_ratelimited(scomp->dev, "error: Mismatch in ABI data size (truncated?).\n");
return -EINVAL;
}
/* notify DSP of byte control updates */
if (pm_runtime_active(scomp->dev))
snd_sof_ipc_set_get_comp_data(scontrol, true);
if (tplg_ops->control->bytes_ext_put)
return tplg_ops->control->bytes_ext_put(scontrol, binary_data, size);
return 0;
}
@ -409,67 +216,24 @@ int snd_sof_bytes_ext_volatile_get(struct snd_kcontrol *kcontrol, unsigned int _
struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value;
struct snd_sof_control *scontrol = be->dobj.private;
struct snd_soc_component *scomp = scontrol->scomp;
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
struct snd_ctl_tlv header;
struct snd_ctl_tlv __user *tlvd = (struct snd_ctl_tlv __user *)binary_data;
size_t data_size;
int ret;
int err;
/*
* Decrement the limit by ext bytes header size to
* ensure the user space buffer is not exceeded.
*/
if (size < sizeof(struct snd_ctl_tlv))
return -ENOSPC;
size -= sizeof(struct snd_ctl_tlv);
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
int ret, err;
ret = pm_runtime_get_sync(scomp->dev);
if (ret < 0 && ret != -EACCES) {
dev_err_ratelimited(scomp->dev, "error: bytes_ext get failed to resume %d\n", ret);
dev_err_ratelimited(scomp->dev, "%s: failed to resume %d\n", __func__, ret);
pm_runtime_put_noidle(scomp->dev);
return ret;
}
/* set the ABI header values */
cdata->data->magic = SOF_ABI_MAGIC;
cdata->data->abi = SOF_ABI_VERSION;
/* get all the component data from DSP */
ret = snd_sof_ipc_set_get_comp_data(scontrol, false);
if (ret < 0)
goto out;
if (tplg_ops->control->bytes_ext_volatile_get)
ret = tplg_ops->control->bytes_ext_volatile_get(scontrol, binary_data, size);
/* check data size doesn't exceed max coming from topology */
if (cdata->data->size > be->max - sizeof(struct sof_abi_hdr)) {
dev_err_ratelimited(scomp->dev, "error: user data size %d exceeds max size %zu.\n",
cdata->data->size,
be->max - sizeof(struct sof_abi_hdr));
ret = -EINVAL;
goto out;
}
data_size = cdata->data->size + sizeof(struct sof_abi_hdr);
/* make sure we don't exceed size provided by user space for data */
if (data_size > size) {
ret = -ENOSPC;
goto out;
}
header.numid = cdata->cmd;
header.length = data_size;
if (copy_to_user(tlvd, &header, sizeof(struct snd_ctl_tlv))) {
ret = -EFAULT;
goto out;
}
if (copy_to_user(tlvd->tlv, cdata->data, data_size))
ret = -EFAULT;
out:
pm_runtime_mark_last_busy(scomp->dev);
err = pm_runtime_put_autosuspend(scomp->dev);
if (err < 0)
dev_err_ratelimited(scomp->dev, "error: bytes_ext get failed to idle %d\n", err);
dev_err_ratelimited(scomp->dev, "%s: failed to idle %d\n", __func__, err);
return ret;
}
@ -478,195 +242,14 @@ int snd_sof_bytes_ext_get(struct snd_kcontrol *kcontrol,
unsigned int __user *binary_data,
unsigned int size)
{
struct soc_bytes_ext *be =
(struct soc_bytes_ext *)kcontrol->private_value;
struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value;
struct snd_sof_control *scontrol = be->dobj.private;
struct snd_soc_component *scomp = scontrol->scomp;
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
struct snd_ctl_tlv header;
struct snd_ctl_tlv __user *tlvd =
(struct snd_ctl_tlv __user *)binary_data;
size_t data_size;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
snd_sof_refresh_control(scontrol);
/*
* Decrement the limit by ext bytes header size to
* ensure the user space buffer is not exceeded.
*/
if (size < sizeof(struct snd_ctl_tlv))
return -ENOSPC;
size -= sizeof(struct snd_ctl_tlv);
/* set the ABI header values */
cdata->data->magic = SOF_ABI_MAGIC;
cdata->data->abi = SOF_ABI_VERSION;
/* check data size doesn't exceed max coming from topology */
if (cdata->data->size > be->max - sizeof(struct sof_abi_hdr)) {
dev_err_ratelimited(scomp->dev, "error: user data size %d exceeds max size %zu.\n",
cdata->data->size,
be->max - sizeof(struct sof_abi_hdr));
return -EINVAL;
}
data_size = cdata->data->size + sizeof(struct sof_abi_hdr);
/* make sure we don't exceed size provided by user space for data */
if (data_size > size)
return -ENOSPC;
header.numid = cdata->cmd;
header.length = data_size;
if (copy_to_user(tlvd, &header, sizeof(struct snd_ctl_tlv)))
return -EFAULT;
if (copy_to_user(tlvd->tlv, cdata->data, data_size))
return -EFAULT;
if (tplg_ops->control->bytes_ext_get)
return tplg_ops->control->bytes_ext_get(scontrol, binary_data, size);
return 0;
}
static void snd_sof_update_control(struct snd_sof_control *scontrol,
struct sof_ipc_ctrl_data *cdata)
{
struct snd_soc_component *scomp = scontrol->scomp;
struct sof_ipc_ctrl_data *local_cdata;
int i;
local_cdata = scontrol->ipc_control_data;
if (cdata->cmd == SOF_CTRL_CMD_BINARY) {
if (cdata->num_elems != local_cdata->data->size) {
dev_err(scomp->dev,
"error: cdata binary size mismatch %u - %u\n",
cdata->num_elems, local_cdata->data->size);
return;
}
/* copy the new binary data */
memcpy(local_cdata->data, cdata->data, cdata->num_elems);
} else if (cdata->num_elems != scontrol->num_channels) {
dev_err(scomp->dev,
"error: cdata channel count mismatch %u - %d\n",
cdata->num_elems, scontrol->num_channels);
} else {
/* copy the new values */
for (i = 0; i < cdata->num_elems; i++)
local_cdata->chanv[i].value = cdata->chanv[i].value;
}
}
void snd_sof_control_notify(struct snd_sof_dev *sdev,
struct sof_ipc_ctrl_data *cdata)
{
struct snd_soc_dapm_widget *widget;
struct snd_sof_control *scontrol;
struct snd_sof_widget *swidget;
struct snd_kcontrol *kc = NULL;
struct soc_mixer_control *sm;
struct soc_bytes_ext *be;
size_t expected_size;
struct soc_enum *se;
bool found = false;
int i, type;
if (cdata->type == SOF_CTRL_TYPE_VALUE_COMP_GET ||
cdata->type == SOF_CTRL_TYPE_VALUE_COMP_SET) {
dev_err(sdev->dev,
"Component data is not supported in control notification\n");
return;
}
/* Find the swidget first */
list_for_each_entry(swidget, &sdev->widget_list, list) {
if (swidget->comp_id == cdata->comp_id) {
found = true;
break;
}
}
if (!found)
return;
/* Translate SOF cmd to TPLG type */
switch (cdata->cmd) {
case SOF_CTRL_CMD_VOLUME:
case SOF_CTRL_CMD_SWITCH:
type = SND_SOC_TPLG_TYPE_MIXER;
break;
case SOF_CTRL_CMD_BINARY:
type = SND_SOC_TPLG_TYPE_BYTES;
break;
case SOF_CTRL_CMD_ENUM:
type = SND_SOC_TPLG_TYPE_ENUM;
break;
default:
dev_err(sdev->dev, "error: unknown cmd %u\n", cdata->cmd);
return;
}
widget = swidget->widget;
for (i = 0; i < widget->num_kcontrols; i++) {
/* skip non matching types or non matching indexes within type */
if (widget->dobj.widget.kcontrol_type[i] == type &&
widget->kcontrol_news[i].index == cdata->index) {
kc = widget->kcontrols[i];
break;
}
}
if (!kc)
return;
switch (cdata->cmd) {
case SOF_CTRL_CMD_VOLUME:
case SOF_CTRL_CMD_SWITCH:
sm = (struct soc_mixer_control *)kc->private_value;
scontrol = sm->dobj.private;
break;
case SOF_CTRL_CMD_BINARY:
be = (struct soc_bytes_ext *)kc->private_value;
scontrol = be->dobj.private;
break;
case SOF_CTRL_CMD_ENUM:
se = (struct soc_enum *)kc->private_value;
scontrol = se->dobj.private;
break;
default:
return;
}
expected_size = sizeof(struct sof_ipc_ctrl_data);
switch (cdata->type) {
case SOF_CTRL_TYPE_VALUE_CHAN_GET:
case SOF_CTRL_TYPE_VALUE_CHAN_SET:
expected_size += cdata->num_elems *
sizeof(struct sof_ipc_ctrl_value_chan);
break;
case SOF_CTRL_TYPE_DATA_GET:
case SOF_CTRL_TYPE_DATA_SET:
expected_size += cdata->num_elems + sizeof(struct sof_abi_hdr);
break;
default:
return;
}
if (cdata->rhdr.hdr.size != expected_size) {
dev_err(sdev->dev, "error: component notification size mismatch\n");
return;
}
if (cdata->num_elems)
/*
* The message includes the updated value/data, update the
* control's local cache using the received notification
*/
snd_sof_update_control(scontrol, cdata);
else
/* Mark the scontrol that the value/data is changed in SOF */
scontrol->comp_data_dirty = true;
snd_ctl_notify_one(swidget->scomp->card->snd_card,
SNDRV_CTL_EVENT_MASK_VALUE, kc, 0);
}

View File

@ -162,58 +162,19 @@ static int hda_link_dma_params(struct hdac_ext_stream *hext_stream,
return 0;
}
/* Update config for the DAI widget */
static struct sof_ipc_dai_config *hda_dai_update_config(struct snd_soc_dapm_widget *w,
int channel)
{
struct snd_sof_widget *swidget = w->dobj.private;
struct sof_dai_private_data *private;
struct sof_ipc_dai_config *config;
struct snd_sof_dai *sof_dai;
if (!swidget)
return NULL;
sof_dai = swidget->private;
if (!sof_dai || !sof_dai->private) {
dev_err(swidget->scomp->dev, "%s: No private data for DAI %s\n", __func__,
w->name);
return NULL;
}
private = sof_dai->private;
if (!private->dai_config) {
dev_err(swidget->scomp->dev, "%s: No config for DAI %s\n", __func__, w->name);
return NULL;
}
config = &private->dai_config[sof_dai->current_config];
/* update config with stream tag */
config->hda.link_dma_ch = channel;
return config;
}
static int hda_link_dai_widget_update(struct sof_intel_hda_stream *hda_stream,
struct snd_soc_dapm_widget *w,
int channel, bool widget_setup)
{
struct snd_sof_dev *sdev = hda_stream->sdev;
struct sof_ipc_dai_config *config;
struct snd_sof_dai_config_data data;
config = hda_dai_update_config(w, channel);
if (!config) {
dev_err(sdev->dev, "error: no config for DAI %s\n", w->name);
return -ENOENT;
}
data.dai_data = channel;
/* set up/free DAI widget and send DAI_CONFIG IPC */
if (widget_setup)
return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_2_STEP_STOP);
return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_2_STEP_STOP, &data);
return hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE);
return hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE, &data);
}
static int hda_link_hw_params(struct snd_pcm_substream *substream,
@ -302,35 +263,16 @@ static int hda_link_dai_config_pause_push_ipc(struct snd_soc_dapm_widget *w)
struct snd_sof_widget *swidget = w->dobj.private;
struct snd_soc_component *component = swidget->scomp;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
struct sof_dai_private_data *private;
struct sof_ipc_dai_config *config;
struct snd_sof_dai *sof_dai;
struct sof_ipc_reply reply;
int ret;
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
int ret = 0;
sof_dai = swidget->private;
if (!sof_dai || !sof_dai->private) {
dev_err(sdev->dev, "%s: No private data for DAI %s\n", __func__, w->name);
return -EINVAL;
if (tplg_ops->dai_config) {
ret = tplg_ops->dai_config(sdev, swidget, SOF_DAI_CONFIG_FLAGS_PAUSE, NULL);
if (ret < 0)
dev_err(sdev->dev, "%s: DAI config failed for widget %s\n", __func__,
w->name);
}
private = sof_dai->private;
if (!private->dai_config) {
dev_err(sdev->dev, "%s: No config for DAI %s\n", __func__, w->name);
return -EINVAL;
}
config = &private->dai_config[sof_dai->current_config];
/* set PAUSE command flag */
config->flags = FIELD_PREP(SOF_DAI_CONFIG_FLAGS_CMD_MASK, SOF_DAI_CONFIG_FLAGS_PAUSE);
ret = sof_ipc_tx_message(sdev->ipc, config->hdr.cmd, config, config->hdr.size,
&reply, sizeof(reply));
if (ret < 0)
dev_err(sdev->dev, "DAI config for %s failed during pause push\n", w->name);
return ret;
}
@ -470,30 +412,17 @@ struct ssp_dai_dma_data {
static int ssp_dai_setup_or_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai,
bool setup)
{
struct snd_soc_component *component;
struct snd_sof_widget *swidget;
struct snd_soc_dapm_widget *w;
struct sof_ipc_fw_version *v;
struct snd_sof_dev *sdev;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
w = dai->playback_widget;
else
w = dai->capture_widget;
swidget = w->dobj.private;
component = swidget->scomp;
sdev = snd_soc_component_get_drvdata(component);
v = &sdev->fw_ready.version;
/* DAI_CONFIG IPC during hw_params is not supported in older firmware */
if (v->abi_version < SOF_ABI_VER(3, 18, 0))
return 0;
if (setup)
return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_NONE);
return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_NONE, NULL);
return hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE);
return hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE, NULL);
}
static int ssp_dai_startup(struct snd_pcm_substream *substream,

View File

@ -41,114 +41,68 @@
#define EXCEPT_MAX_HDR_SIZE 0x400
#define HDA_EXT_ROM_STATUS_SIZE 8
int hda_ctrl_dai_widget_setup(struct snd_soc_dapm_widget *w, unsigned int quirk_flags)
int hda_ctrl_dai_widget_setup(struct snd_soc_dapm_widget *w, unsigned int quirk_flags,
struct snd_sof_dai_config_data *data)
{
struct snd_sof_widget *swidget = w->dobj.private;
struct snd_soc_component *component = swidget->scomp;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
struct sof_ipc_dai_config *config;
struct sof_dai_private_data *private;
struct snd_sof_dai *sof_dai;
struct sof_ipc_reply reply;
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
struct snd_sof_dai *sof_dai = swidget->private;
int ret;
sof_dai = swidget->private;
if (!sof_dai || !sof_dai->private) {
dev_err(sdev->dev, "%s: No private data for DAI %s\n", __func__, w->name);
if (!sof_dai) {
dev_err(sdev->dev, "%s: No DAI for DAI widget %s\n", __func__, w->name);
return -EINVAL;
}
private = sof_dai->private;
if (!private->dai_config) {
dev_err(sdev->dev, "%s: No config for DAI %s\n", __func__, w->name);
return -EINVAL;
}
if (tplg_ops->dai_config) {
unsigned int flags;
/* DAI already configured, reset it before reconfiguring it */
if (sof_dai->configured) {
ret = hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE);
if (ret < 0)
/* set HW_PARAMS flag along with quirks */
flags = SOF_DAI_CONFIG_FLAGS_HW_PARAMS |
quirk_flags << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT;
ret = tplg_ops->dai_config(sdev, swidget, flags, data);
if (ret < 0) {
dev_err(sdev->dev, "%s: DAI config failed for widget %s\n", __func__,
w->name);
return ret;
}
}
config = &private->dai_config[sof_dai->current_config];
/*
* For static pipelines, the DAI widget would already be set up and calling
* sof_widget_setup() simply returns without doing anything.
* For dynamic pipelines, the DAI widget will be set up now.
*/
ret = sof_widget_setup(sdev, swidget);
if (ret < 0) {
dev_err(sdev->dev, "error: failed setting up DAI widget %s\n", w->name);
return ret;
}
/* set HW_PARAMS flag along with quirks */
config->flags = SOF_DAI_CONFIG_FLAGS_HW_PARAMS |
quirk_flags << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT;
/* send DAI_CONFIG IPC */
ret = sof_ipc_tx_message(sdev->ipc, config->hdr.cmd, config, config->hdr.size,
&reply, sizeof(reply));
if (ret < 0) {
dev_err(sdev->dev, "error: failed setting DAI config for %s\n", w->name);
return ret;
}
sof_dai->configured = true;
return 0;
}
int hda_ctrl_dai_widget_free(struct snd_soc_dapm_widget *w, unsigned int quirk_flags)
int hda_ctrl_dai_widget_free(struct snd_soc_dapm_widget *w, unsigned int quirk_flags,
struct snd_sof_dai_config_data *data)
{
struct snd_sof_widget *swidget = w->dobj.private;
struct snd_soc_component *component = swidget->scomp;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
struct sof_dai_private_data *private;
struct sof_ipc_dai_config *config;
struct snd_sof_dai *sof_dai;
struct sof_ipc_reply reply;
int ret;
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
struct snd_sof_dai *sof_dai = swidget->private;
sof_dai = swidget->private;
if (!sof_dai || !sof_dai->private) {
dev_err(sdev->dev, "%s: No private data for DAI %s\n", __func__, w->name);
if (!sof_dai) {
dev_err(sdev->dev, "%s: No DAI for BE DAI widget %s\n", __func__, w->name);
return -EINVAL;
}
private = sof_dai->private;
if (!private->dai_config) {
dev_err(sdev->dev, "%s: No config for DAI %s\n", __func__, w->name);
return -EINVAL;
if (tplg_ops->dai_config) {
unsigned int flags;
int ret;
/* set HW_FREE flag along with any quirks */
flags = SOF_DAI_CONFIG_FLAGS_HW_FREE |
quirk_flags << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT;
ret = tplg_ops->dai_config(sdev, swidget, flags, data);
if (ret < 0)
dev_err(sdev->dev, "%s: DAI config failed for widget '%s'\n", __func__,
w->name);
}
/* nothing to do if hw_free() is called without restarting the stream after resume. */
if (!sof_dai->configured)
return 0;
config = &private->dai_config[sof_dai->current_config];
/* set HW_FREE flag along with any quirks */
config->flags = SOF_DAI_CONFIG_FLAGS_HW_FREE |
quirk_flags << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT;
ret = sof_ipc_tx_message(sdev->ipc, config->hdr.cmd, config, config->hdr.size,
&reply, sizeof(reply));
if (ret < 0)
dev_err(sdev->dev, "error: failed resetting DAI config for %s\n", w->name);
/*
* Reset the configured_flag and free the widget even if the IPC fails to keep
* the widget use_count balanced
*/
sof_dai->configured = false;
return sof_widget_free(sdev, swidget);
return 0;
}
#if IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE)
@ -163,69 +117,34 @@ static int sdw_clock_stop_quirks = SDW_INTEL_CLK_STOP_BUS_RESET;
module_param(sdw_clock_stop_quirks, int, 0444);
MODULE_PARM_DESC(sdw_clock_stop_quirks, "SOF SoundWire clock stop quirks");
static int sdw_dai_config_ipc(struct snd_sof_dev *sdev,
struct snd_soc_dapm_widget *w,
int link_id, int alh_stream_id, int dai_id, bool setup)
{
struct snd_sof_widget *swidget = w->dobj.private;
struct sof_dai_private_data *private;
struct sof_ipc_dai_config *config;
struct snd_sof_dai *sof_dai;
if (!swidget) {
dev_err(sdev->dev, "error: No private data for widget %s\n", w->name);
return -EINVAL;
}
sof_dai = swidget->private;
if (!sof_dai || !sof_dai->private) {
dev_err(sdev->dev, "%s: No private data for DAI %s\n", __func__, w->name);
return -EINVAL;
}
private = sof_dai->private;
if (!private->dai_config) {
dev_err(sdev->dev, "%s: No config for DAI %s\n", __func__, w->name);
return -EINVAL;
}
config = &private->dai_config[sof_dai->current_config];
/* update config with link and stream ID */
config->dai_index = (link_id << 8) | dai_id;
config->alh.stream_id = alh_stream_id;
if (setup)
return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_NONE);
return hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE);
}
static int sdw_params_stream(struct device *dev,
struct sdw_intel_stream_params_data *params_data)
{
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
struct snd_soc_dai *d = params_data->dai;
struct snd_sof_dai_config_data data;
struct snd_soc_dapm_widget *w;
w = snd_soc_dai_get_widget(d, params_data->stream);
data.dai_index = (params_data->link_id << 8) | d->id;
data.dai_data = params_data->alh_stream_id;
return sdw_dai_config_ipc(sdev, w, params_data->link_id, params_data->alh_stream_id,
d->id, true);
return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_NONE, &data);
}
static int sdw_free_stream(struct device *dev,
struct sdw_intel_stream_free_data *free_data)
{
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
struct snd_soc_dai *d = free_data->dai;
struct snd_sof_dai_config_data data;
struct snd_soc_dapm_widget *w;
w = snd_soc_dai_get_widget(d, free_data->stream);
data.dai_index = (free_data->link_id << 8) | d->id;
/* send invalid stream_id */
return sdw_dai_config_ipc(sdev, w, free_data->link_id, 0xFFFF, d->id, false);
data.dai_data = 0xFFFF;
return hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE, &data);
}
static const struct sdw_intel_ops sdw_callback = {

View File

@ -17,6 +17,7 @@
#include <sound/hda_codec.h>
#include <sound/hdaudio_ext.h>
#include "../sof-client-probes.h"
#include "../sof-audio.h"
#include "shim.h"
/* PCI registers */
@ -730,8 +731,10 @@ int hda_pci_intel_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
struct snd_sof_dai;
struct sof_ipc_dai_config;
int hda_ctrl_dai_widget_setup(struct snd_soc_dapm_widget *w, unsigned int quirk_flags);
int hda_ctrl_dai_widget_free(struct snd_soc_dapm_widget *w, unsigned int quirk_flags);
int hda_ctrl_dai_widget_setup(struct snd_soc_dapm_widget *w, unsigned int quirk_flags,
struct snd_sof_dai_config_data *data);
int hda_ctrl_dai_widget_free(struct snd_soc_dapm_widget *w, unsigned int quirk_flags,
struct snd_sof_dai_config_data *data);
#define SOF_HDA_POSITION_QUIRK_USE_SKYLAKE_LEGACY (0) /* previous implementation */
#define SOF_HDA_POSITION_QUIRK_USE_DPIB_REGISTERS (1) /* recommended if VC0 only */

View File

@ -17,6 +17,7 @@
#include "sof-priv.h"
#include "sof-audio.h"
#include "ops.h"
#include "ipc3-ops.h"
typedef void (*ipc_rx_callback)(struct snd_sof_dev *sdev, void *msg_buf);
@ -469,6 +470,7 @@ EXPORT_SYMBOL(snd_sof_ipc_reply);
static void ipc_comp_notification(struct snd_sof_dev *sdev, void *msg_buf)
{
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
struct sof_ipc_cmd_hdr *hdr = msg_buf;
u32 msg_type = hdr->cmd & SOF_CMD_TYPE_MASK;
@ -481,7 +483,8 @@ static void ipc_comp_notification(struct snd_sof_dev *sdev, void *msg_buf)
return;
}
snd_sof_control_notify(sdev, msg_buf);
if (tplg_ops->control->update)
tplg_ops->control->update(sdev, msg_buf);
}
/* DSP firmware has sent host a message */
@ -1030,8 +1033,9 @@ struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev)
ipc->ops = &ipc3_ops;
/* check for mandatory ops */
if (!ipc->ops->tplg || !ipc->ops->tplg->widget) {
dev_err(sdev->dev, "Invalid topology IPC ops\n");
if (!ipc->ops->pcm || !ipc->ops->tplg || !ipc->ops->tplg->widget ||
!ipc->ops->tplg->control) {
dev_err(sdev->dev, "Invalid IPC ops\n");
return NULL;
}

View File

@ -0,0 +1,594 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2021 Intel Corporation. All rights reserved.
//
//
#include "sof-priv.h"
#include "sof-audio.h"
#include "ipc3-ops.h"
static inline u32 mixer_to_ipc(unsigned int value, u32 *volume_map, int size)
{
if (value >= size)
return volume_map[size - 1];
return volume_map[value];
}
static inline u32 ipc_to_mixer(u32 value, u32 *volume_map, int size)
{
int i;
for (i = 0; i < size; i++) {
if (volume_map[i] >= value)
return i;
}
return i - 1;
}
static void snd_sof_refresh_control(struct snd_sof_control *scontrol)
{
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
struct snd_soc_component *scomp = scontrol->scomp;
int ret;
if (!scontrol->comp_data_dirty)
return;
if (!pm_runtime_active(scomp->dev))
return;
/* set the ABI header values */
cdata->data->magic = SOF_ABI_MAGIC;
cdata->data->abi = SOF_ABI_VERSION;
/* refresh the component data from DSP */
scontrol->comp_data_dirty = false;
ret = snd_sof_ipc_set_get_comp_data(scontrol, false);
if (ret < 0) {
dev_err(scomp->dev, "Failed to get control data: %d\n", ret);
/* Set the flag to re-try next time to get the data */
scontrol->comp_data_dirty = true;
}
}
static int sof_ipc3_volume_get(struct snd_sof_control *scontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
unsigned int channels = scontrol->num_channels;
unsigned int i;
snd_sof_refresh_control(scontrol);
/* read back each channel */
for (i = 0; i < channels; i++)
ucontrol->value.integer.value[i] = ipc_to_mixer(cdata->chanv[i].value,
scontrol->volume_table,
scontrol->max + 1);
return 0;
}
static bool sof_ipc3_volume_put(struct snd_sof_control *scontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
struct snd_soc_component *scomp = scontrol->scomp;
unsigned int channels = scontrol->num_channels;
unsigned int i;
bool change = false;
/* update each channel */
for (i = 0; i < channels; i++) {
u32 value = mixer_to_ipc(ucontrol->value.integer.value[i],
scontrol->volume_table, scontrol->max + 1);
change = change || (value != cdata->chanv[i].value);
cdata->chanv[i].channel = i;
cdata->chanv[i].value = value;
}
/* notify DSP of mixer updates */
if (pm_runtime_active(scomp->dev)) {
int ret = snd_sof_ipc_set_get_comp_data(scontrol, true);
if (ret < 0) {
dev_err(scomp->dev, "Failed to set mixer updates for %s\n",
scontrol->name);
return false;
}
}
return change;
}
static int sof_ipc3_switch_get(struct snd_sof_control *scontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
unsigned int channels = scontrol->num_channels;
unsigned int i;
snd_sof_refresh_control(scontrol);
/* read back each channel */
for (i = 0; i < channels; i++)
ucontrol->value.integer.value[i] = cdata->chanv[i].value;
return 0;
}
static bool sof_ipc3_switch_put(struct snd_sof_control *scontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
struct snd_soc_component *scomp = scontrol->scomp;
unsigned int channels = scontrol->num_channels;
unsigned int i;
bool change = false;
u32 value;
/* update each channel */
for (i = 0; i < channels; i++) {
value = ucontrol->value.integer.value[i];
change = change || (value != cdata->chanv[i].value);
cdata->chanv[i].channel = i;
cdata->chanv[i].value = value;
}
/* notify DSP of mixer updates */
if (pm_runtime_active(scomp->dev)) {
int ret = snd_sof_ipc_set_get_comp_data(scontrol, true);
if (ret < 0) {
dev_err(scomp->dev, "Failed to set mixer updates for %s\n",
scontrol->name);
return false;
}
}
return change;
}
static int sof_ipc3_enum_get(struct snd_sof_control *scontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
unsigned int channels = scontrol->num_channels;
unsigned int i;
snd_sof_refresh_control(scontrol);
/* read back each channel */
for (i = 0; i < channels; i++)
ucontrol->value.enumerated.item[i] = cdata->chanv[i].value;
return 0;
}
static bool sof_ipc3_enum_put(struct snd_sof_control *scontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
struct snd_soc_component *scomp = scontrol->scomp;
unsigned int channels = scontrol->num_channels;
unsigned int i;
bool change = false;
u32 value;
/* update each channel */
for (i = 0; i < channels; i++) {
value = ucontrol->value.enumerated.item[i];
change = change || (value != cdata->chanv[i].value);
cdata->chanv[i].channel = i;
cdata->chanv[i].value = value;
}
/* notify DSP of enum updates */
if (pm_runtime_active(scomp->dev)) {
int ret = snd_sof_ipc_set_get_comp_data(scontrol, true);
if (ret < 0) {
dev_err(scomp->dev, "Failed to set enum updates for %s\n",
scontrol->name);
return false;
}
}
return change;
}
static int sof_ipc3_bytes_get(struct snd_sof_control *scontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
struct snd_soc_component *scomp = scontrol->scomp;
struct sof_abi_hdr *data = cdata->data;
size_t size;
snd_sof_refresh_control(scontrol);
if (scontrol->max_size > sizeof(ucontrol->value.bytes.data)) {
dev_err_ratelimited(scomp->dev, "data max %zu exceeds ucontrol data array size\n",
scontrol->max_size);
return -EINVAL;
}
/* be->max has been verified to be >= sizeof(struct sof_abi_hdr) */
if (data->size > scontrol->max_size - sizeof(*data)) {
dev_err_ratelimited(scomp->dev,
"%u bytes of control data is invalid, max is %zu\n",
data->size, scontrol->max_size - sizeof(*data));
return -EINVAL;
}
size = data->size + sizeof(*data);
/* copy back to kcontrol */
memcpy(ucontrol->value.bytes.data, data, size);
return 0;
}
static int sof_ipc3_bytes_put(struct snd_sof_control *scontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
struct snd_soc_component *scomp = scontrol->scomp;
struct sof_abi_hdr *data = cdata->data;
size_t size;
if (scontrol->max_size > sizeof(ucontrol->value.bytes.data)) {
dev_err_ratelimited(scomp->dev, "data max %zu exceeds ucontrol data array size\n",
scontrol->max_size);
return -EINVAL;
}
/* scontrol->max_size has been verified to be >= sizeof(struct sof_abi_hdr) */
if (data->size > scontrol->max_size - sizeof(*data)) {
dev_err_ratelimited(scomp->dev, "data size too big %u bytes max is %zu\n",
data->size, scontrol->max_size - sizeof(*data));
return -EINVAL;
}
size = data->size + sizeof(*data);
/* copy from kcontrol */
memcpy(data, ucontrol->value.bytes.data, size);
/* notify DSP of byte control updates */
if (pm_runtime_active(scomp->dev))
return snd_sof_ipc_set_get_comp_data(scontrol, true);
return 0;
}
static int sof_ipc3_bytes_ext_get(struct snd_sof_control *scontrol,
const unsigned int __user *binary_data, unsigned int size)
{
struct snd_ctl_tlv __user *tlvd = (struct snd_ctl_tlv __user *)binary_data;
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
struct snd_soc_component *scomp = scontrol->scomp;
struct snd_ctl_tlv header;
size_t data_size;
snd_sof_refresh_control(scontrol);
/*
* Decrement the limit by ext bytes header size to
* ensure the user space buffer is not exceeded.
*/
if (size < sizeof(struct snd_ctl_tlv))
return -ENOSPC;
size -= sizeof(struct snd_ctl_tlv);
/* set the ABI header values */
cdata->data->magic = SOF_ABI_MAGIC;
cdata->data->abi = SOF_ABI_VERSION;
/* check data size doesn't exceed max coming from topology */
if (cdata->data->size > scontrol->max_size - sizeof(struct sof_abi_hdr)) {
dev_err_ratelimited(scomp->dev, "User data size %d exceeds max size %zu\n",
cdata->data->size,
scontrol->max_size - sizeof(struct sof_abi_hdr));
return -EINVAL;
}
data_size = cdata->data->size + sizeof(struct sof_abi_hdr);
/* make sure we don't exceed size provided by user space for data */
if (data_size > size)
return -ENOSPC;
header.numid = cdata->cmd;
header.length = data_size;
if (copy_to_user(tlvd, &header, sizeof(struct snd_ctl_tlv)))
return -EFAULT;
if (copy_to_user(tlvd->tlv, cdata->data, data_size))
return -EFAULT;
return 0;
}
static int sof_ipc3_bytes_ext_put(struct snd_sof_control *scontrol,
const unsigned int __user *binary_data,
unsigned int size)
{
const struct snd_ctl_tlv __user *tlvd = (const struct snd_ctl_tlv __user *)binary_data;
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
struct snd_soc_component *scomp = scontrol->scomp;
struct snd_ctl_tlv header;
/*
* The beginning of bytes data contains a header from where
* the length (as bytes) is needed to know the correct copy
* length of data from tlvd->tlv.
*/
if (copy_from_user(&header, tlvd, sizeof(struct snd_ctl_tlv)))
return -EFAULT;
/* make sure TLV info is consistent */
if (header.length + sizeof(struct snd_ctl_tlv) > size) {
dev_err_ratelimited(scomp->dev, "Inconsistent TLV, data %d + header %zu > %d\n",
header.length, sizeof(struct snd_ctl_tlv), size);
return -EINVAL;
}
/* be->max is coming from topology */
if (header.length > scontrol->max_size) {
dev_err_ratelimited(scomp->dev, "Bytes data size %d exceeds max %zu\n",
header.length, scontrol->max_size);
return -EINVAL;
}
/* Check that header id matches the command */
if (header.numid != cdata->cmd) {
dev_err_ratelimited(scomp->dev, "Incorrect command for bytes put %d\n",
header.numid);
return -EINVAL;
}
if (copy_from_user(cdata->data, tlvd->tlv, header.length))
return -EFAULT;
if (cdata->data->magic != SOF_ABI_MAGIC) {
dev_err_ratelimited(scomp->dev, "Wrong ABI magic 0x%08x\n", cdata->data->magic);
return -EINVAL;
}
if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, cdata->data->abi)) {
dev_err_ratelimited(scomp->dev, "Incompatible ABI version 0x%08x\n",
cdata->data->abi);
return -EINVAL;
}
/* be->max has been verified to be >= sizeof(struct sof_abi_hdr) */
if (cdata->data->size > scontrol->max_size - sizeof(struct sof_abi_hdr)) {
dev_err_ratelimited(scomp->dev, "Mismatch in ABI data size (truncated?)\n");
return -EINVAL;
}
/* notify DSP of byte control updates */
if (pm_runtime_active(scomp->dev))
return snd_sof_ipc_set_get_comp_data(scontrol, true);
return 0;
}
static int sof_ipc3_bytes_ext_volatile_get(struct snd_sof_control *scontrol,
const unsigned int __user *binary_data,
unsigned int size)
{
struct snd_ctl_tlv __user *tlvd = (struct snd_ctl_tlv __user *)binary_data;
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
struct snd_soc_component *scomp = scontrol->scomp;
struct snd_ctl_tlv header;
size_t data_size;
int ret;
/*
* Decrement the limit by ext bytes header size to
* ensure the user space buffer is not exceeded.
*/
if (size < sizeof(struct snd_ctl_tlv))
return -ENOSPC;
size -= sizeof(struct snd_ctl_tlv);
/* set the ABI header values */
cdata->data->magic = SOF_ABI_MAGIC;
cdata->data->abi = SOF_ABI_VERSION;
/* get all the component data from DSP */
ret = snd_sof_ipc_set_get_comp_data(scontrol, false);
if (ret < 0)
return ret;
/* check data size doesn't exceed max coming from topology */
if (cdata->data->size > scontrol->max_size - sizeof(struct sof_abi_hdr)) {
dev_err_ratelimited(scomp->dev, "User data size %d exceeds max size %zu\n",
cdata->data->size,
scontrol->max_size - sizeof(struct sof_abi_hdr));
return -EINVAL;
}
data_size = cdata->data->size + sizeof(struct sof_abi_hdr);
/* make sure we don't exceed size provided by user space for data */
if (data_size > size)
return -ENOSPC;
header.numid = cdata->cmd;
header.length = data_size;
if (copy_to_user(tlvd, &header, sizeof(struct snd_ctl_tlv)))
return -EFAULT;
if (copy_to_user(tlvd->tlv, cdata->data, data_size))
return -EFAULT;
return ret;
}
static void snd_sof_update_control(struct snd_sof_control *scontrol,
struct sof_ipc_ctrl_data *cdata)
{
struct snd_soc_component *scomp = scontrol->scomp;
struct sof_ipc_ctrl_data *local_cdata;
int i;
local_cdata = scontrol->ipc_control_data;
if (cdata->cmd == SOF_CTRL_CMD_BINARY) {
if (cdata->num_elems != local_cdata->data->size) {
dev_err(scomp->dev, "cdata binary size mismatch %u - %u\n",
cdata->num_elems, local_cdata->data->size);
return;
}
/* copy the new binary data */
memcpy(local_cdata->data, cdata->data, cdata->num_elems);
} else if (cdata->num_elems != scontrol->num_channels) {
dev_err(scomp->dev, "cdata channel count mismatch %u - %d\n",
cdata->num_elems, scontrol->num_channels);
} else {
/* copy the new values */
for (i = 0; i < cdata->num_elems; i++)
local_cdata->chanv[i].value = cdata->chanv[i].value;
}
}
static void sof_ipc3_control_update(struct snd_sof_dev *sdev, void *ipc_control_message)
{
struct sof_ipc_ctrl_data *cdata = ipc_control_message;
struct snd_soc_dapm_widget *widget;
struct snd_sof_control *scontrol;
struct snd_sof_widget *swidget;
struct snd_kcontrol *kc = NULL;
struct soc_mixer_control *sm;
struct soc_bytes_ext *be;
size_t expected_size;
struct soc_enum *se;
bool found = false;
int i, type;
if (cdata->type == SOF_CTRL_TYPE_VALUE_COMP_GET ||
cdata->type == SOF_CTRL_TYPE_VALUE_COMP_SET) {
dev_err(sdev->dev, "Component data is not supported in control notification\n");
return;
}
/* Find the swidget first */
list_for_each_entry(swidget, &sdev->widget_list, list) {
if (swidget->comp_id == cdata->comp_id) {
found = true;
break;
}
}
if (!found)
return;
/* Translate SOF cmd to TPLG type */
switch (cdata->cmd) {
case SOF_CTRL_CMD_VOLUME:
case SOF_CTRL_CMD_SWITCH:
type = SND_SOC_TPLG_TYPE_MIXER;
break;
case SOF_CTRL_CMD_BINARY:
type = SND_SOC_TPLG_TYPE_BYTES;
break;
case SOF_CTRL_CMD_ENUM:
type = SND_SOC_TPLG_TYPE_ENUM;
break;
default:
dev_err(sdev->dev, "Unknown cmd %u in %s\n", cdata->cmd, __func__);
return;
}
widget = swidget->widget;
for (i = 0; i < widget->num_kcontrols; i++) {
/* skip non matching types or non matching indexes within type */
if (widget->dobj.widget.kcontrol_type[i] == type &&
widget->kcontrol_news[i].index == cdata->index) {
kc = widget->kcontrols[i];
break;
}
}
if (!kc)
return;
switch (cdata->cmd) {
case SOF_CTRL_CMD_VOLUME:
case SOF_CTRL_CMD_SWITCH:
sm = (struct soc_mixer_control *)kc->private_value;
scontrol = sm->dobj.private;
break;
case SOF_CTRL_CMD_BINARY:
be = (struct soc_bytes_ext *)kc->private_value;
scontrol = be->dobj.private;
break;
case SOF_CTRL_CMD_ENUM:
se = (struct soc_enum *)kc->private_value;
scontrol = se->dobj.private;
break;
default:
return;
}
expected_size = sizeof(struct sof_ipc_ctrl_data);
switch (cdata->type) {
case SOF_CTRL_TYPE_VALUE_CHAN_GET:
case SOF_CTRL_TYPE_VALUE_CHAN_SET:
expected_size += cdata->num_elems *
sizeof(struct sof_ipc_ctrl_value_chan);
break;
case SOF_CTRL_TYPE_DATA_GET:
case SOF_CTRL_TYPE_DATA_SET:
expected_size += cdata->num_elems + sizeof(struct sof_abi_hdr);
break;
default:
return;
}
if (cdata->rhdr.hdr.size != expected_size) {
dev_err(sdev->dev, "Component notification size mismatch\n");
return;
}
if (cdata->num_elems)
/*
* The message includes the updated value/data, update the
* control's local cache using the received notification
*/
snd_sof_update_control(scontrol, cdata);
else
/* Mark the scontrol that the value/data is changed in SOF */
scontrol->comp_data_dirty = true;
snd_ctl_notify_one(swidget->scomp->card->snd_card, SNDRV_CTL_EVENT_MASK_VALUE, kc, 0);
}
const struct sof_ipc_tplg_control_ops tplg_ipc3_control_ops = {
.volume_put = sof_ipc3_volume_put,
.volume_get = sof_ipc3_volume_get,
.switch_put = sof_ipc3_switch_put,
.switch_get = sof_ipc3_switch_get,
.enum_put = sof_ipc3_enum_put,
.enum_get = sof_ipc3_enum_get,
.bytes_put = sof_ipc3_bytes_put,
.bytes_get = sof_ipc3_bytes_get,
.bytes_ext_put = sof_ipc3_bytes_ext_put,
.bytes_ext_get = sof_ipc3_bytes_ext_get,
.bytes_ext_volatile_get = sof_ipc3_bytes_ext_volatile_get,
.update = sof_ipc3_control_update,
};

21
sound/soc/sof/ipc3-ops.h Normal file
View File

@ -0,0 +1,21 @@
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
/*
* This file is provided under a dual BSD/GPLv2 license. When using or
* redistributing this file, you may do so under either license.
*
* Copyright(c) 2021 Intel Corporation. All rights reserved.
*
* Author: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
*/
#ifndef __SOUND_SOC_SOF_IPC3_OPS_H
#define __SOUND_SOC_SOF_IPC3_OPS_H
#include "sof-priv.h"
extern const struct sof_ipc_tplg_ops ipc3_tplg_ops;
extern const struct sof_ipc_ops ipc3_ops;
extern const struct sof_ipc_tplg_control_ops tplg_ipc3_control_ops;
extern const struct sof_ipc_pcm_ops ipc3_pcm_ops;
#endif

372
sound/soc/sof/ipc3-pcm.c Normal file
View File

@ -0,0 +1,372 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2021 Intel Corporation. All rights reserved.
//
//
#include <sound/pcm_params.h>
#include "ipc3-ops.h"
#include "ops.h"
#include "sof-priv.h"
#include "sof-audio.h"
static int sof_ipc3_pcm_hw_free(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct sof_ipc_stream stream;
struct sof_ipc_reply reply;
struct snd_sof_pcm *spcm;
spcm = snd_sof_find_spcm_dai(component, rtd);
if (!spcm)
return -EINVAL;
if (!spcm->prepared[substream->stream])
return 0;
stream.hdr.size = sizeof(stream);
stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_FREE;
stream.comp_id = spcm->stream[substream->stream].comp_id;
/* send IPC to the DSP */
return sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream,
sizeof(stream), &reply, sizeof(reply));
}
static int sof_ipc3_pcm_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_sof_platform_stream_params *platform_params)
{
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct sof_ipc_fw_version *v = &sdev->fw_ready.version;
struct snd_pcm_runtime *runtime = substream->runtime;
struct sof_ipc_pcm_params_reply ipc_params_reply;
struct sof_ipc_pcm_params pcm;
struct snd_sof_pcm *spcm;
int ret;
spcm = snd_sof_find_spcm_dai(component, rtd);
if (!spcm)
return -EINVAL;
memset(&pcm, 0, sizeof(pcm));
/* number of pages should be rounded up */
pcm.params.buffer.pages = PFN_UP(runtime->dma_bytes);
/* set IPC PCM parameters */
pcm.hdr.size = sizeof(pcm);
pcm.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS;
pcm.comp_id = spcm->stream[substream->stream].comp_id;
pcm.params.hdr.size = sizeof(pcm.params);
pcm.params.buffer.phy_addr = spcm->stream[substream->stream].page_table.addr;
pcm.params.buffer.size = runtime->dma_bytes;
pcm.params.direction = substream->stream;
pcm.params.sample_valid_bytes = params_width(params) >> 3;
pcm.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED;
pcm.params.rate = params_rate(params);
pcm.params.channels = params_channels(params);
pcm.params.host_period_bytes = params_period_bytes(params);
/* container size */
ret = snd_pcm_format_physical_width(params_format(params));
if (ret < 0)
return ret;
pcm.params.sample_container_bytes = ret >> 3;
/* format */
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16:
pcm.params.frame_fmt = SOF_IPC_FRAME_S16_LE;
break;
case SNDRV_PCM_FORMAT_S24:
pcm.params.frame_fmt = SOF_IPC_FRAME_S24_4LE;
break;
case SNDRV_PCM_FORMAT_S32:
pcm.params.frame_fmt = SOF_IPC_FRAME_S32_LE;
break;
case SNDRV_PCM_FORMAT_FLOAT:
pcm.params.frame_fmt = SOF_IPC_FRAME_FLOAT;
break;
default:
return -EINVAL;
}
/* Update the IPC message with information from the platform */
pcm.params.stream_tag = platform_params->stream_tag;
if (platform_params->use_phy_address)
pcm.params.buffer.phy_addr = platform_params->phy_addr;
if (platform_params->no_ipc_position) {
/* For older ABIs set host_period_bytes to zero to inform
* FW we don't want position updates. Newer versions use
* no_stream_position for this purpose.
*/
if (v->abi_version < SOF_ABI_VER(3, 10, 0))
pcm.params.host_period_bytes = 0;
else
pcm.params.no_stream_position = 1;
}
dev_dbg(component->dev, "stream_tag %d", pcm.params.stream_tag);
/* send hw_params IPC to the DSP */
ret = sof_ipc_tx_message(sdev->ipc, pcm.hdr.cmd, &pcm, sizeof(pcm),
&ipc_params_reply, sizeof(ipc_params_reply));
if (ret < 0) {
dev_err(component->dev, "HW params ipc failed for stream %d\n",
pcm.params.stream_tag);
return ret;
}
ret = snd_sof_set_stream_data_offset(sdev, substream, ipc_params_reply.posn_offset);
if (ret < 0) {
dev_err(component->dev, "%s: invalid stream data offset for PCM %d\n",
__func__, spcm->pcm.pcm_id);
return ret;
}
return ret;
}
static int sof_ipc3_pcm_trigger(struct snd_soc_component *component,
struct snd_pcm_substream *substream, int cmd)
{
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
struct sof_ipc_stream stream;
struct sof_ipc_reply reply;
struct snd_sof_pcm *spcm;
spcm = snd_sof_find_spcm_dai(component, rtd);
if (!spcm)
return -EINVAL;
stream.hdr.size = sizeof(stream);
stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG;
stream.comp_id = spcm->stream[substream->stream].comp_id;
switch (cmd) {
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_PAUSE;
break;
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_RELEASE;
break;
case SNDRV_PCM_TRIGGER_START:
stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_START;
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
fallthrough;
case SNDRV_PCM_TRIGGER_STOP:
stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_STOP;
break;
default:
dev_err(component->dev, "Unhandled trigger cmd %d\n", cmd);
return -EINVAL;
}
/* send IPC to the DSP */
return sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream,
sizeof(stream), &reply, sizeof(reply));
}
static void ssp_dai_config_pcm_params_match(struct snd_sof_dev *sdev, const char *link_name,
struct snd_pcm_hw_params *params)
{
struct sof_ipc_dai_config *config;
struct snd_sof_dai *dai;
int i;
/*
* Search for all matching DAIs as we can have both playback and capture DAI
* associated with the same link.
*/
list_for_each_entry(dai, &sdev->dai_list, list) {
if (!dai->name || strcmp(link_name, dai->name))
continue;
for (i = 0; i < dai->number_configs; i++) {
struct sof_dai_private_data *private = dai->private;
config = &private->dai_config[i];
if (config->ssp.fsync_rate == params_rate(params)) {
dev_dbg(sdev->dev, "DAI config %d matches pcm hw params\n", i);
dai->current_config = i;
break;
}
}
}
}
static int sof_ipc3_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd,
struct snd_pcm_hw_params *params)
{
struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME);
struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
struct snd_sof_dai *dai = snd_sof_find_dai(component, (char *)rtd->dai_link->name);
struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
struct sof_dai_private_data *private;
struct snd_soc_dpcm *dpcm;
if (!dai) {
dev_err(component->dev, "%s: No DAI found with name %s\n", __func__,
rtd->dai_link->name);
return -EINVAL;
}
private = dai->private;
if (!private) {
dev_err(component->dev, "%s: No private data found for DAI %s\n", __func__,
rtd->dai_link->name);
return -EINVAL;
}
/* read format from topology */
snd_mask_none(fmt);
switch (private->comp_dai->config.frame_fmt) {
case SOF_IPC_FRAME_S16_LE:
snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE);
break;
case SOF_IPC_FRAME_S24_4LE:
snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE);
break;
case SOF_IPC_FRAME_S32_LE:
snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE);
break;
default:
dev_err(component->dev, "No available DAI format!\n");
return -EINVAL;
}
/* read rate and channels from topology */
switch (private->dai_config->type) {
case SOF_DAI_INTEL_SSP:
/* search for config to pcm params match, if not found use default */
ssp_dai_config_pcm_params_match(sdev, (char *)rtd->dai_link->name, params);
rate->min = private->dai_config[dai->current_config].ssp.fsync_rate;
rate->max = private->dai_config[dai->current_config].ssp.fsync_rate;
channels->min = private->dai_config[dai->current_config].ssp.tdm_slots;
channels->max = private->dai_config[dai->current_config].ssp.tdm_slots;
dev_dbg(component->dev, "rate_min: %d rate_max: %d\n", rate->min, rate->max);
dev_dbg(component->dev, "channels_min: %d channels_max: %d\n",
channels->min, channels->max);
break;
case SOF_DAI_INTEL_DMIC:
/* DMIC only supports 16 or 32 bit formats */
if (private->comp_dai->config.frame_fmt == SOF_IPC_FRAME_S24_4LE) {
dev_err(component->dev, "Invalid fmt %d for DAI type %d\n",
private->comp_dai->config.frame_fmt,
private->dai_config->type);
}
break;
case SOF_DAI_INTEL_HDA:
/*
* HDAudio does not follow the default trigger
* sequence due to firmware implementation
*/
for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_PLAYBACK, dpcm) {
struct snd_soc_pcm_runtime *fe = dpcm->fe;
fe->dai_link->trigger[SNDRV_PCM_STREAM_PLAYBACK] =
SND_SOC_DPCM_TRIGGER_POST;
}
break;
case SOF_DAI_INTEL_ALH:
/*
* Dai could run with different channel count compared with
* front end, so get dai channel count from topology
*/
channels->min = private->dai_config->alh.channels;
channels->max = private->dai_config->alh.channels;
break;
case SOF_DAI_IMX_ESAI:
rate->min = private->dai_config->esai.fsync_rate;
rate->max = private->dai_config->esai.fsync_rate;
channels->min = private->dai_config->esai.tdm_slots;
channels->max = private->dai_config->esai.tdm_slots;
dev_dbg(component->dev, "rate_min: %d rate_max: %d\n", rate->min, rate->max);
dev_dbg(component->dev, "channels_min: %d channels_max: %d\n",
channels->min, channels->max);
break;
case SOF_DAI_MEDIATEK_AFE:
rate->min = private->dai_config->afe.rate;
rate->max = private->dai_config->afe.rate;
channels->min = private->dai_config->afe.channels;
channels->max = private->dai_config->afe.channels;
dev_dbg(component->dev, "rate_min: %d rate_max: %d\n", rate->min, rate->max);
dev_dbg(component->dev, "channels_min: %d channels_max: %d\n",
channels->min, channels->max);
break;
case SOF_DAI_IMX_SAI:
rate->min = private->dai_config->sai.fsync_rate;
rate->max = private->dai_config->sai.fsync_rate;
channels->min = private->dai_config->sai.tdm_slots;
channels->max = private->dai_config->sai.tdm_slots;
dev_dbg(component->dev, "rate_min: %d rate_max: %d\n", rate->min, rate->max);
dev_dbg(component->dev, "channels_min: %d channels_max: %d\n",
channels->min, channels->max);
break;
case SOF_DAI_AMD_BT:
rate->min = private->dai_config->acpbt.fsync_rate;
rate->max = private->dai_config->acpbt.fsync_rate;
channels->min = private->dai_config->acpbt.tdm_slots;
channels->max = private->dai_config->acpbt.tdm_slots;
dev_dbg(component->dev,
"AMD_BT rate_min: %d rate_max: %d\n", rate->min, rate->max);
dev_dbg(component->dev, "AMD_BT channels_min: %d channels_max: %d\n",
channels->min, channels->max);
break;
case SOF_DAI_AMD_SP:
rate->min = private->dai_config->acpsp.fsync_rate;
rate->max = private->dai_config->acpsp.fsync_rate;
channels->min = private->dai_config->acpsp.tdm_slots;
channels->max = private->dai_config->acpsp.tdm_slots;
dev_dbg(component->dev,
"AMD_SP rate_min: %d rate_max: %d\n", rate->min, rate->max);
dev_dbg(component->dev, "AMD_SP channels_min: %d channels_max: %d\n",
channels->min, channels->max);
break;
case SOF_DAI_AMD_DMIC:
rate->min = private->dai_config->acpdmic.fsync_rate;
rate->max = private->dai_config->acpdmic.fsync_rate;
channels->min = private->dai_config->acpdmic.tdm_slots;
channels->max = private->dai_config->acpdmic.tdm_slots;
dev_dbg(component->dev,
"AMD_DMIC rate_min: %d rate_max: %d\n", rate->min, rate->max);
dev_dbg(component->dev, "AMD_DMIC channels_min: %d channels_max: %d\n",
channels->min, channels->max);
break;
default:
dev_err(component->dev, "Invalid DAI type %d\n", private->dai_config->type);
break;
}
return 0;
}
const struct sof_ipc_pcm_ops ipc3_pcm_ops = {
.hw_params = sof_ipc3_pcm_hw_params,
.hw_free = sof_ipc3_pcm_hw_free,
.trigger = sof_ipc3_pcm_trigger,
.dai_link_fixup = sof_ipc3_pcm_dai_link_fixup,
};

View File

@ -11,6 +11,7 @@
#include <sound/pcm_params.h>
#include "sof-priv.h"
#include "sof-audio.h"
#include "ipc3-ops.h"
#include "ops.h"
/* Full volume for default values */
@ -1909,6 +1910,376 @@ static int sof_ipc3_complete_pipeline(struct snd_sof_dev *sdev, struct snd_sof_w
return 1;
}
static int sof_ipc3_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
{
struct sof_ipc_free ipc_free = {
.hdr = {
.size = sizeof(ipc_free),
.cmd = SOF_IPC_GLB_TPLG_MSG,
},
.id = swidget->comp_id,
};
struct sof_ipc_reply reply;
int ret;
if (!swidget->private)
return 0;
switch (swidget->id) {
case snd_soc_dapm_scheduler:
{
ipc_free.hdr.cmd |= SOF_IPC_TPLG_PIPE_FREE;
break;
}
case snd_soc_dapm_buffer:
ipc_free.hdr.cmd |= SOF_IPC_TPLG_BUFFER_FREE;
break;
default:
ipc_free.hdr.cmd |= SOF_IPC_TPLG_COMP_FREE;
break;
}
ret = sof_ipc_tx_message(sdev->ipc, ipc_free.hdr.cmd, &ipc_free, sizeof(ipc_free),
&reply, sizeof(reply));
if (ret < 0)
dev_err(sdev->dev, "failed to free widget %s\n", swidget->widget->name);
return ret;
}
static int sof_ipc3_dai_config(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget,
unsigned int flags, struct snd_sof_dai_config_data *data)
{
struct sof_ipc_fw_version *v = &sdev->fw_ready.version;
struct snd_sof_dai *dai = swidget->private;
struct sof_dai_private_data *private;
struct sof_ipc_dai_config *config;
struct sof_ipc_reply reply;
int ret = 0;
if (!dai || !dai->private) {
dev_err(sdev->dev, "No private data for DAI %s\n", swidget->widget->name);
return -EINVAL;
}
private = dai->private;
if (!private->dai_config) {
dev_err(sdev->dev, "No config for DAI %s\n", dai->name);
return -EINVAL;
}
config = &private->dai_config[dai->current_config];
if (!config) {
dev_err(sdev->dev, "Invalid current config for DAI %s\n", dai->name);
return -EINVAL;
}
switch (config->type) {
case SOF_DAI_INTEL_SSP:
/*
* DAI_CONFIG IPC during hw_params/hw_free for SSP DAI's is not supported in older
* firmware
*/
if (v->abi_version < SOF_ABI_VER(3, 18, 0) &&
((flags & SOF_DAI_CONFIG_FLAGS_HW_PARAMS) ||
(flags & SOF_DAI_CONFIG_FLAGS_HW_FREE)))
return 0;
break;
case SOF_DAI_INTEL_HDA:
if (data)
config->hda.link_dma_ch = data->dai_data;
break;
case SOF_DAI_INTEL_ALH:
if (data) {
config->dai_index = data->dai_index;
config->alh.stream_id = data->dai_data;
}
break;
default:
break;
}
config->flags = flags;
/* only send the IPC if the widget is set up in the DSP */
if (swidget->use_count > 0) {
ret = sof_ipc_tx_message(sdev->ipc, config->hdr.cmd, config, config->hdr.size,
&reply, sizeof(reply));
if (ret < 0)
dev_err(sdev->dev, "Failed to set dai config for %s\n", dai->name);
}
return ret;
}
static int sof_ipc3_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
{
struct sof_ipc_comp_reply reply;
int ret;
if (!swidget->private)
return 0;
switch (swidget->id) {
case snd_soc_dapm_dai_in:
case snd_soc_dapm_dai_out:
{
struct snd_sof_dai *dai = swidget->private;
struct sof_dai_private_data *dai_data = dai->private;
struct sof_ipc_comp *comp = &dai_data->comp_dai->comp;
ret = sof_ipc_tx_message(sdev->ipc, comp->hdr.cmd, dai_data->comp_dai,
comp->hdr.size, &reply, sizeof(reply));
break;
}
case snd_soc_dapm_scheduler:
{
struct sof_ipc_pipe_new *pipeline;
pipeline = swidget->private;
ret = sof_ipc_tx_message(sdev->ipc, pipeline->hdr.cmd, pipeline,
sizeof(*pipeline), &reply, sizeof(reply));
break;
}
default:
{
struct sof_ipc_cmd_hdr *hdr;
hdr = swidget->private;
ret = sof_ipc_tx_message(sdev->ipc, hdr->cmd, swidget->private, hdr->size,
&reply, sizeof(reply));
break;
}
}
if (ret < 0)
dev_err(sdev->dev, "Failed to setup widget %s\n", swidget->widget->name);
return ret;
}
static int sof_ipc3_set_up_all_pipelines(struct snd_sof_dev *sdev, bool verify)
{
struct sof_ipc_fw_version *v = &sdev->fw_ready.version;
struct snd_sof_widget *swidget;
struct snd_sof_route *sroute;
int ret;
/* restore pipeline components */
list_for_each_entry(swidget, &sdev->widget_list, list) {
/* only set up the widgets belonging to static pipelines */
if (!verify && swidget->dynamic_pipeline_widget)
continue;
/*
* For older firmware, skip scheduler widgets in this loop,
* sof_widget_setup() will be called in the 'complete pipeline' loop
*/
if (v->abi_version < SOF_ABI_VER(3, 19, 0) &&
swidget->id == snd_soc_dapm_scheduler)
continue;
/* update DAI config. The IPC will be sent in sof_widget_setup() */
if (WIDGET_IS_DAI(swidget->id)) {
struct snd_sof_dai *dai = swidget->private;
struct sof_dai_private_data *private;
struct sof_ipc_dai_config *config;
if (!dai || !dai->private)
continue;
private = dai->private;
if (!private->dai_config)
continue;
config = private->dai_config;
/*
* The link DMA channel would be invalidated for running
* streams but not for streams that were in the PAUSED
* state during suspend. So invalidate it here before setting
* the dai config in the DSP.
*/
if (config->type == SOF_DAI_INTEL_HDA)
config->hda.link_dma_ch = DMA_CHAN_INVALID;
}
ret = sof_widget_setup(sdev, swidget);
if (ret < 0)
return ret;
}
/* restore pipeline connections */
list_for_each_entry(sroute, &sdev->route_list, list) {
/* only set up routes belonging to static pipelines */
if (!verify && (sroute->src_widget->dynamic_pipeline_widget ||
sroute->sink_widget->dynamic_pipeline_widget))
continue;
/*
* For virtual routes, both sink and source are not buffer. IPC3 only supports
* connections between a buffer and a component. Ignore the rest.
*/
if (sroute->src_widget->id != snd_soc_dapm_buffer &&
sroute->sink_widget->id != snd_soc_dapm_buffer)
continue;
ret = sof_route_setup(sdev, sroute->src_widget->widget,
sroute->sink_widget->widget);
if (ret < 0) {
dev_err(sdev->dev, "%s: route set up failed\n", __func__);
return ret;
}
}
/* complete pipeline */
list_for_each_entry(swidget, &sdev->widget_list, list) {
switch (swidget->id) {
case snd_soc_dapm_scheduler:
/* only complete static pipelines */
if (!verify && swidget->dynamic_pipeline_widget)
continue;
if (v->abi_version < SOF_ABI_VER(3, 19, 0)) {
ret = sof_widget_setup(sdev, swidget);
if (ret < 0)
return ret;
}
swidget->complete = sof_ipc3_complete_pipeline(sdev, swidget);
if (swidget->complete < 0)
return swidget->complete;
break;
default:
break;
}
}
return 0;
}
/*
* Free the PCM, its associated widgets and set the prepared flag to false for all PCMs that
* did not get suspended(ex: paused streams) so the widgets can be set up again during resume.
*/
static int sof_tear_down_left_over_pipelines(struct snd_sof_dev *sdev)
{
struct snd_sof_widget *swidget;
struct snd_sof_pcm *spcm;
int dir, ret;
/*
* free all PCMs and their associated DAPM widgets if their connected DAPM widget
* list is not NULL. This should only be true for paused streams at this point.
* This is equivalent to the handling of FE DAI suspend trigger for running streams.
*/
list_for_each_entry(spcm, &sdev->pcm_list, list) {
for_each_pcm_streams(dir) {
struct snd_pcm_substream *substream = spcm->stream[dir].substream;
if (!substream || !substream->runtime)
continue;
if (spcm->stream[dir].list) {
ret = sof_pcm_stream_free(sdev, substream, spcm, dir, true);
if (ret < 0)
return ret;
}
}
}
/*
* free any left over DAI widgets. This is equivalent to the handling of suspend trigger
* for the BE DAI for running streams.
*/
list_for_each_entry(swidget, &sdev->widget_list, list)
if (WIDGET_IS_DAI(swidget->id) && swidget->use_count == 1) {
ret = sof_widget_free(sdev, swidget);
if (ret < 0)
return ret;
}
return 0;
}
/*
* For older firmware, this function doesn't free widgets for static pipelines during suspend.
* It only resets use_count for all widgets.
*/
static int sof_ipc3_tear_down_all_pipelines(struct snd_sof_dev *sdev, bool verify)
{
struct sof_ipc_fw_version *v = &sdev->fw_ready.version;
struct snd_sof_widget *swidget;
struct snd_sof_route *sroute;
int ret;
/*
* This function is called during suspend and for one-time topology verification during
* first boot. In both cases, there is no need to protect swidget->use_count and
* sroute->setup because during suspend all running streams are suspended and during
* topology loading the sound card unavailable to open PCMs.
*/
list_for_each_entry(swidget, &sdev->widget_list, list) {
if (swidget->dynamic_pipeline_widget)
continue;
/* Do not free widgets for static pipelines with FW ABI older than 3.19 */
if (!verify && !swidget->dynamic_pipeline_widget &&
v->abi_version < SOF_ABI_VER(3, 19, 0)) {
swidget->use_count = 0;
swidget->complete = 0;
continue;
}
ret = sof_widget_free(sdev, swidget);
if (ret < 0)
return ret;
}
/*
* Tear down all pipelines associated with PCMs that did not get suspended
* and unset the prepare flag so that they can be set up again during resume.
* Skip this step for older firmware.
*/
if (!verify && v->abi_version >= SOF_ABI_VER(3, 19, 0)) {
ret = sof_tear_down_left_over_pipelines(sdev);
if (ret < 0) {
dev_err(sdev->dev, "failed to tear down paused pipelines\n");
return ret;
}
}
list_for_each_entry(sroute, &sdev->route_list, list)
sroute->setup = false;
return 0;
}
static int sof_ipc3_dai_get_clk(struct snd_sof_dev *sdev, struct snd_sof_dai *dai, int clk_type)
{
struct sof_dai_private_data *private = dai->private;
if (!private || !private->dai_config)
return 0;
switch (private->dai_config->type) {
case SOF_DAI_INTEL_SSP:
switch (clk_type) {
case SOF_DAI_CLK_INTEL_SSP_MCLK:
return private->dai_config->ssp.mclk_rate;
case SOF_DAI_CLK_INTEL_SSP_BCLK:
return private->dai_config->ssp.bclk_rate;
default:
break;
}
dev_err(sdev->dev, "fail to get SSP clk %d rate\n", clk_type);
break;
default:
/* not yet implemented for platforms other than the above */
dev_err(sdev->dev, "DAI type %d not supported yet!\n", private->dai_config->type);
break;
}
return -EINVAL;
}
/* token list for each topology object */
static enum sof_tokens host_token_list[] = {
SOF_CORE_TOKENS,
@ -2005,15 +2376,18 @@ static const struct sof_ipc_tplg_widget_ops tplg_ipc3_widget_ops[SND_SOC_DAPM_TY
sof_ipc3_widget_bind_event},
};
static const struct sof_ipc_tplg_ops ipc3_tplg_ops = {
const struct sof_ipc_tplg_ops ipc3_tplg_ops = {
.widget = tplg_ipc3_widget_ops,
.control = &tplg_ipc3_control_ops,
.route_setup = sof_ipc3_route_setup,
.control_setup = sof_ipc3_control_setup,
.control_free = sof_ipc3_control_free,
.pipeline_complete = sof_ipc3_complete_pipeline,
.token_list = ipc3_token_list,
};
const struct sof_ipc_ops ipc3_ops = {
.tplg = &ipc3_tplg_ops,
.widget_free = sof_ipc3_widget_free,
.widget_setup = sof_ipc3_widget_setup,
.dai_config = sof_ipc3_dai_config,
.dai_get_clk = sof_ipc3_dai_get_clk,
.set_up_all_pipelines = sof_ipc3_set_up_all_pipelines,
.tear_down_all_pipelines = sof_ipc3_tear_down_all_pipelines,
};

45
sound/soc/sof/ipc3.c Normal file
View File

@ -0,0 +1,45 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2021 Intel Corporation. All rights reserved.
//
//
#include "sof-priv.h"
#include "ipc3-ops.h"
static int sof_ipc3_ctx_ipc(struct snd_sof_dev *sdev, int cmd)
{
struct sof_ipc_pm_ctx pm_ctx = {
.hdr.size = sizeof(pm_ctx),
.hdr.cmd = SOF_IPC_GLB_PM_MSG | cmd,
};
struct sof_ipc_reply reply;
/* send ctx save ipc to dsp */
return sof_ipc_tx_message(sdev->ipc, pm_ctx.hdr.cmd, &pm_ctx,
sizeof(pm_ctx), &reply, sizeof(reply));
}
static int sof_ipc3_ctx_save(struct snd_sof_dev *sdev)
{
return sof_ipc3_ctx_ipc(sdev, SOF_IPC_PM_CTX_SAVE);
}
static int sof_ipc3_ctx_restore(struct snd_sof_dev *sdev)
{
return sof_ipc3_ctx_ipc(sdev, SOF_IPC_PM_CTX_RESTORE);
}
static const struct sof_ipc_pm_ops ipc3_pm_ops = {
.ctx_save = sof_ipc3_ctx_save,
.ctx_restore = sof_ipc3_ctx_restore,
};
const struct sof_ipc_ops ipc3_ops = {
.tplg = &ipc3_tplg_ops,
.pm = &ipc3_pm_ops,
.pcm = &ipc3_pcm_ops,
};

View File

@ -82,32 +82,8 @@ void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream)
}
EXPORT_SYMBOL(snd_sof_pcm_period_elapsed);
int sof_pcm_dsp_pcm_free(struct snd_pcm_substream *substream, struct snd_sof_dev *sdev,
struct snd_sof_pcm *spcm)
{
struct sof_ipc_stream stream;
struct sof_ipc_reply reply;
int ret;
if (!spcm->prepared[substream->stream])
return 0;
stream.hdr.size = sizeof(stream);
stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_FREE;
stream.comp_id = spcm->stream[substream->stream].comp_id;
/* send IPC to the DSP */
ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream,
sizeof(stream), &reply, sizeof(reply));
if (!ret)
spcm->prepared[substream->stream] = false;
return ret;
}
static int sof_pcm_setup_connected_widgets(struct snd_sof_dev *sdev,
struct snd_soc_pcm_runtime *rtd,
struct snd_sof_pcm *spcm, int dir)
int sof_pcm_setup_connected_widgets(struct snd_sof_dev *sdev, struct snd_soc_pcm_runtime *rtd,
struct snd_sof_pcm *spcm, int dir)
{
struct snd_soc_dai *dai;
int ret, j;
@ -143,14 +119,12 @@ static int sof_pcm_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_sof_platform_stream_params platform_params = { 0 };
struct sof_ipc_fw_version *v = &sdev->fw_ready.version;
const struct sof_ipc_pcm_ops *pcm_ops = sdev->ipc->ops->pcm;
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_sof_pcm *spcm;
struct sof_ipc_pcm_params pcm;
struct sof_ipc_pcm_params_reply ipc_params_reply;
int ret;
/* nothing to do for BE */
@ -165,94 +139,17 @@ static int sof_pcm_hw_params(struct snd_soc_component *component,
* Handle repeated calls to hw_params() without free_pcm() in
* between. At least ALSA OSS emulation depends on this.
*/
ret = sof_pcm_dsp_pcm_free(substream, sdev, spcm);
if (ret < 0)
return ret;
if (pcm_ops->hw_free && spcm->prepared[substream->stream]) {
ret = pcm_ops->hw_free(component, substream);
if (ret < 0)
return ret;
spcm->prepared[substream->stream] = false;
}
dev_dbg(component->dev, "pcm: hw params stream %d dir %d\n",
spcm->pcm.pcm_id, substream->stream);
memset(&pcm, 0, sizeof(pcm));
/* create compressed page table for audio firmware */
if (runtime->buffer_changed) {
ret = create_page_table(component, substream, runtime->dma_area,
runtime->dma_bytes);
if (ret < 0)
return ret;
}
/* number of pages should be rounded up */
pcm.params.buffer.pages = PFN_UP(runtime->dma_bytes);
/* set IPC PCM parameters */
pcm.hdr.size = sizeof(pcm);
pcm.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS;
pcm.comp_id = spcm->stream[substream->stream].comp_id;
pcm.params.hdr.size = sizeof(pcm.params);
pcm.params.buffer.phy_addr =
spcm->stream[substream->stream].page_table.addr;
pcm.params.buffer.size = runtime->dma_bytes;
pcm.params.direction = substream->stream;
pcm.params.sample_valid_bytes = params_width(params) >> 3;
pcm.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED;
pcm.params.rate = params_rate(params);
pcm.params.channels = params_channels(params);
pcm.params.host_period_bytes = params_period_bytes(params);
/* container size */
ret = snd_pcm_format_physical_width(params_format(params));
if (ret < 0)
return ret;
pcm.params.sample_container_bytes = ret >> 3;
/* format */
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16:
pcm.params.frame_fmt = SOF_IPC_FRAME_S16_LE;
break;
case SNDRV_PCM_FORMAT_S24:
pcm.params.frame_fmt = SOF_IPC_FRAME_S24_4LE;
break;
case SNDRV_PCM_FORMAT_S32:
pcm.params.frame_fmt = SOF_IPC_FRAME_S32_LE;
break;
case SNDRV_PCM_FORMAT_FLOAT:
pcm.params.frame_fmt = SOF_IPC_FRAME_FLOAT;
break;
default:
return -EINVAL;
}
/* firmware already configured host stream */
ret = snd_sof_pcm_platform_hw_params(sdev,
substream,
params,
&platform_params);
if (ret < 0) {
dev_err(component->dev, "error: platform hw params failed\n");
return ret;
}
/* Update the IPC message with information from the platform */
pcm.params.stream_tag = platform_params.stream_tag;
if (platform_params.use_phy_address)
pcm.params.buffer.phy_addr = platform_params.phy_addr;
if (platform_params.no_ipc_position) {
/* For older ABIs set host_period_bytes to zero to inform
* FW we don't want position updates. Newer versions use
* no_stream_position for this purpose.
*/
if (v->abi_version < SOF_ABI_VER(3, 10, 0))
pcm.params.host_period_bytes = 0;
else
pcm.params.no_stream_position = 1;
}
dev_dbg(component->dev, "stream_tag %d", pcm.params.stream_tag);
/* if this is a repeated hw_params without hw_free, skip setting up widgets */
if (!spcm->stream[substream->stream].list) {
ret = sof_pcm_setup_connected_widgets(sdev, rtd, spcm, substream->stream);
@ -260,21 +157,25 @@ static int sof_pcm_hw_params(struct snd_soc_component *component,
return ret;
}
/* send hw_params IPC to the DSP */
ret = sof_ipc_tx_message(sdev->ipc, pcm.hdr.cmd, &pcm, sizeof(pcm),
&ipc_params_reply, sizeof(ipc_params_reply));
/* create compressed page table for audio firmware */
if (runtime->buffer_changed) {
ret = create_page_table(component, substream, runtime->dma_area,
runtime->dma_bytes);
if (ret < 0)
return ret;
}
ret = snd_sof_pcm_platform_hw_params(sdev, substream, params, &platform_params);
if (ret < 0) {
dev_err(component->dev, "error: hw params ipc failed for stream %d\n",
pcm.params.stream_tag);
dev_err(component->dev, "platform hw params failed\n");
return ret;
}
ret = snd_sof_set_stream_data_offset(sdev, substream,
ipc_params_reply.posn_offset);
if (ret < 0) {
dev_err(component->dev, "%s: invalid stream data offset for PCM %d\n",
__func__, spcm->pcm.pcm_id);
return ret;
if (pcm_ops->hw_params) {
ret = pcm_ops->hw_params(component, substream, params, &platform_params);
if (ret < 0)
return ret;
}
spcm->prepared[substream->stream] = true;
@ -282,7 +183,7 @@ static int sof_pcm_hw_params(struct snd_soc_component *component,
/* save pcm hw_params */
memcpy(&spcm->params[substream->stream], params, sizeof(*params));
return ret;
return 0;
}
static int sof_pcm_hw_free(struct snd_soc_component *component,
@ -290,6 +191,7 @@ static int sof_pcm_hw_free(struct snd_soc_component *component,
{
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
const struct sof_ipc_pcm_ops *pcm_ops = sdev->ipc->ops->pcm;
struct snd_sof_pcm *spcm;
int ret, err = 0;
@ -305,10 +207,13 @@ static int sof_pcm_hw_free(struct snd_soc_component *component,
spcm->pcm.pcm_id, substream->stream);
/* free PCM in the DSP */
ret = sof_pcm_dsp_pcm_free(substream, sdev, spcm);
if (ret < 0)
err = ret;
if (pcm_ops->hw_free && spcm->prepared[substream->stream]) {
ret = pcm_ops->hw_free(component, substream);
if (ret < 0)
err = ret;
spcm->prepared[substream->stream] = false;
}
/* stop DMA */
ret = snd_sof_pcm_platform_hw_free(sdev, substream);
@ -369,13 +274,12 @@ static int sof_pcm_trigger(struct snd_soc_component *component,
{
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
const struct sof_ipc_pcm_ops *pcm_ops = sdev->ipc->ops->pcm;
struct snd_sof_pcm *spcm;
struct sof_ipc_stream stream;
struct sof_ipc_reply reply;
bool reset_hw_params = false;
bool free_widget_list = false;
bool ipc_first = false;
int ret;
int ret = 0;
/* nothing to do for BE */
if (rtd->dai_link->no_pcm)
@ -388,17 +292,11 @@ static int sof_pcm_trigger(struct snd_soc_component *component,
dev_dbg(component->dev, "pcm: trigger stream %d dir %d cmd %d\n",
spcm->pcm.pcm_id, substream->stream, cmd);
stream.hdr.size = sizeof(stream);
stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG;
stream.comp_id = spcm->stream[substream->stream].comp_id;
switch (cmd) {
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_PAUSE;
ipc_first = true;
break;
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_RELEASE;
break;
case SNDRV_PCM_TRIGGER_START:
if (spcm->stream[substream->stream].suspend_ignored) {
@ -410,7 +308,6 @@ static int sof_pcm_trigger(struct snd_soc_component *component,
spcm->stream[substream->stream].suspend_ignored = false;
return 0;
}
stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_START;
break;
case SNDRV_PCM_TRIGGER_SUSPEND:
if (sdev->system_suspend_target == SOF_SUSPEND_S0IX &&
@ -427,13 +324,11 @@ static int sof_pcm_trigger(struct snd_soc_component *component,
free_widget_list = true;
fallthrough;
case SNDRV_PCM_TRIGGER_STOP:
stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_STOP;
ipc_first = true;
reset_hw_params = true;
break;
default:
dev_err(component->dev, "error: unhandled trigger cmd %d\n",
cmd);
dev_err(component->dev, "Unhandled trigger cmd %d\n", cmd);
return -EINVAL;
}
@ -444,11 +339,10 @@ static int sof_pcm_trigger(struct snd_soc_component *component,
if (!ipc_first)
snd_sof_pcm_platform_trigger(sdev, substream, cmd);
/* send IPC to the DSP */
ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream,
sizeof(stream), &reply, sizeof(reply));
if (pcm_ops->trigger)
ret = pcm_ops->trigger(component, substream, cmd);
/* need to STOP DMA even if STOP IPC failed */
/* need to STOP DMA even if trigger IPC failed */
if (ipc_first)
snd_sof_pcm_platform_trigger(sdev, substream, cmd);
@ -662,33 +556,6 @@ capture:
return 0;
}
static void ssp_dai_config_pcm_params_match(struct snd_sof_dev *sdev, const char *link_name,
struct snd_pcm_hw_params *params)
{
struct sof_ipc_dai_config *config;
struct snd_sof_dai *dai;
int i;
/*
* Search for all matching DAIs as we can have both playback and capture DAI
* associated with the same link.
*/
list_for_each_entry(dai, &sdev->dai_list, list) {
if (!dai->name || strcmp(link_name, dai->name))
continue;
for (i = 0; i < dai->number_configs; i++) {
struct sof_dai_private_data *private = dai->private;
config = &private->dai_config[i];
if (config->ssp.fsync_rate == params_rate(params)) {
dev_dbg(sdev->dev, "DAI config %d matches pcm hw params\n", i);
dai->current_config = i;
break;
}
}
}
}
/* fixup the BE DAI link to match any values from topology */
int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params)
{
@ -702,8 +569,7 @@ int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_pa
struct snd_sof_dai *dai =
snd_sof_find_dai(component, (char *)rtd->dai_link->name);
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
struct sof_dai_private_data *private = dai->private;
struct snd_soc_dpcm *dpcm;
const struct sof_ipc_pcm_ops *pcm_ops = sdev->ipc->ops->pcm;
/* no topology exists for this BE, try a common configuration */
if (!dai) {
@ -724,148 +590,8 @@ int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_pa
return 0;
}
/* read format from topology */
snd_mask_none(fmt);
switch (private->comp_dai->config.frame_fmt) {
case SOF_IPC_FRAME_S16_LE:
snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE);
break;
case SOF_IPC_FRAME_S24_4LE:
snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE);
break;
case SOF_IPC_FRAME_S32_LE:
snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE);
break;
default:
dev_err(component->dev, "error: No available DAI format!\n");
return -EINVAL;
}
/* read rate and channels from topology */
switch (private->dai_config->type) {
case SOF_DAI_INTEL_SSP:
/* search for config to pcm params match, if not found use default */
ssp_dai_config_pcm_params_match(sdev, (char *)rtd->dai_link->name, params);
rate->min = private->dai_config[dai->current_config].ssp.fsync_rate;
rate->max = private->dai_config[dai->current_config].ssp.fsync_rate;
channels->min = private->dai_config[dai->current_config].ssp.tdm_slots;
channels->max = private->dai_config[dai->current_config].ssp.tdm_slots;
dev_dbg(component->dev,
"rate_min: %d rate_max: %d\n", rate->min, rate->max);
dev_dbg(component->dev,
"channels_min: %d channels_max: %d\n",
channels->min, channels->max);
break;
case SOF_DAI_INTEL_DMIC:
/* DMIC only supports 16 or 32 bit formats */
if (private->comp_dai->config.frame_fmt == SOF_IPC_FRAME_S24_4LE) {
dev_err(component->dev,
"error: invalid fmt %d for DAI type %d\n",
private->comp_dai->config.frame_fmt,
private->dai_config->type);
}
break;
case SOF_DAI_INTEL_HDA:
/*
* HDAudio does not follow the default trigger
* sequence due to firmware implementation
*/
for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_PLAYBACK, dpcm) {
struct snd_soc_pcm_runtime *fe = dpcm->fe;
fe->dai_link->trigger[SNDRV_PCM_STREAM_PLAYBACK] =
SND_SOC_DPCM_TRIGGER_POST;
}
break;
case SOF_DAI_INTEL_ALH:
/*
* Dai could run with different channel count compared with
* front end, so get dai channel count from topology
*/
channels->min = private->dai_config->alh.channels;
channels->max = private->dai_config->alh.channels;
break;
case SOF_DAI_IMX_ESAI:
rate->min = private->dai_config->esai.fsync_rate;
rate->max = private->dai_config->esai.fsync_rate;
channels->min = private->dai_config->esai.tdm_slots;
channels->max = private->dai_config->esai.tdm_slots;
dev_dbg(component->dev,
"rate_min: %d rate_max: %d\n", rate->min, rate->max);
dev_dbg(component->dev,
"channels_min: %d channels_max: %d\n",
channels->min, channels->max);
break;
case SOF_DAI_MEDIATEK_AFE:
rate->min = private->dai_config->afe.rate;
rate->max = private->dai_config->afe.rate;
channels->min = private->dai_config->afe.channels;
channels->max = private->dai_config->afe.channels;
dev_dbg(component->dev,
"rate_min: %d rate_max: %d\n", rate->min, rate->max);
dev_dbg(component->dev,
"channels_min: %d channels_max: %d\n",
channels->min, channels->max);
break;
case SOF_DAI_IMX_SAI:
rate->min = private->dai_config->sai.fsync_rate;
rate->max = private->dai_config->sai.fsync_rate;
channels->min = private->dai_config->sai.tdm_slots;
channels->max = private->dai_config->sai.tdm_slots;
dev_dbg(component->dev,
"rate_min: %d rate_max: %d\n", rate->min, rate->max);
dev_dbg(component->dev,
"channels_min: %d channels_max: %d\n",
channels->min, channels->max);
break;
case SOF_DAI_AMD_BT:
rate->min = private->dai_config->acpbt.fsync_rate;
rate->max = private->dai_config->acpbt.fsync_rate;
channels->min = private->dai_config->acpbt.tdm_slots;
channels->max = private->dai_config->acpbt.tdm_slots;
dev_dbg(component->dev,
"AMD_BT rate_min: %d rate_max: %d\n", rate->min, rate->max);
dev_dbg(component->dev,
"AMD_BT channels_min: %d channels_max: %d\n",
channels->min, channels->max);
break;
case SOF_DAI_AMD_SP:
rate->min = private->dai_config->acpsp.fsync_rate;
rate->max = private->dai_config->acpsp.fsync_rate;
channels->min = private->dai_config->acpsp.tdm_slots;
channels->max = private->dai_config->acpsp.tdm_slots;
dev_dbg(component->dev,
"AMD_SP rate_min: %d rate_max: %d\n", rate->min, rate->max);
dev_dbg(component->dev,
"AMD_SP channels_min: %d channels_max: %d\n",
channels->min, channels->max);
break;
case SOF_DAI_AMD_DMIC:
rate->min = private->dai_config->acpdmic.fsync_rate;
rate->max = private->dai_config->acpdmic.fsync_rate;
channels->min = private->dai_config->acpdmic.tdm_slots;
channels->max = private->dai_config->acpdmic.tdm_slots;
dev_dbg(component->dev,
"AMD_DMIC rate_min: %d rate_max: %d\n", rate->min, rate->max);
dev_dbg(component->dev,
"AMD_DMIC channels_min: %d channels_max: %d\n",
channels->min, channels->max);
break;
default:
dev_err(component->dev, "error: invalid DAI type %d\n",
private->dai_config->type);
break;
}
if (pcm_ops->dai_link_fixup)
return pcm_ops->dai_link_fixup(rtd, params);
return 0;
}

View File

@ -48,22 +48,6 @@ static u32 snd_sof_dsp_power_target(struct snd_sof_dev *sdev)
return target_dsp_state;
}
static int sof_send_pm_ctx_ipc(struct snd_sof_dev *sdev, int cmd)
{
struct sof_ipc_pm_ctx pm_ctx;
struct sof_ipc_reply reply;
memset(&pm_ctx, 0, sizeof(pm_ctx));
/* configure ctx save ipc message */
pm_ctx.hdr.size = sizeof(pm_ctx);
pm_ctx.hdr.cmd = SOF_IPC_GLB_PM_MSG | cmd;
/* send ctx save ipc to dsp */
return sof_ipc_tx_message(sdev->ipc, pm_ctx.hdr.cmd, &pm_ctx,
sizeof(pm_ctx), &reply, sizeof(reply));
}
#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE)
static void sof_cache_debugfs(struct snd_sof_dev *sdev)
{
@ -86,6 +70,8 @@ static void sof_cache_debugfs(struct snd_sof_dev *sdev)
static int sof_resume(struct device *dev, bool runtime_resume)
{
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm;
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
u32 old_state = sdev->dsp_power_state.state;
int ret;
@ -159,23 +145,23 @@ static int sof_resume(struct device *dev, bool runtime_resume)
}
/* restore pipelines */
ret = sof_set_up_pipelines(sdev, false);
if (ret < 0) {
dev_err(sdev->dev,
"error: failed to restore pipeline after resume %d\n",
ret);
return ret;
if (tplg_ops->set_up_all_pipelines) {
ret = tplg_ops->set_up_all_pipelines(sdev, false);
if (ret < 0) {
dev_err(sdev->dev, "Failed to restore pipeline after resume %d\n", ret);
return ret;
}
}
/* Notify clients not managed by pm framework about core resume */
sof_resume_clients(sdev);
/* notify DSP of system resume */
ret = sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_RESTORE);
if (ret < 0)
dev_err(sdev->dev,
"error: ctx_restore ipc error during resume %d\n",
ret);
if (pm_ops && pm_ops->ctx_restore) {
ret = pm_ops->ctx_restore(sdev);
if (ret < 0)
dev_err(sdev->dev, "ctx_restore IPC error during resume: %d\n", ret);
}
return ret;
}
@ -183,6 +169,8 @@ static int sof_resume(struct device *dev, bool runtime_resume)
static int sof_suspend(struct device *dev, bool runtime_suspend)
{
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm;
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
pm_message_t pm_state;
u32 target_state = 0;
int ret;
@ -218,7 +206,8 @@ static int sof_suspend(struct device *dev, bool runtime_suspend)
goto suspend;
}
sof_tear_down_pipelines(sdev, false);
if (tplg_ops->tear_down_all_pipelines)
tplg_ops->tear_down_all_pipelines(sdev, false);
/* release trace */
snd_sof_release_trace(sdev);
@ -232,21 +221,20 @@ static int sof_suspend(struct device *dev, bool runtime_suspend)
sof_cache_debugfs(sdev);
#endif
/* notify DSP of upcoming power down */
ret = sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_SAVE);
if (ret == -EBUSY || ret == -EAGAIN) {
/*
* runtime PM has logic to handle -EBUSY/-EAGAIN so
* pass these errors up
*/
dev_err(sdev->dev,
"error: ctx_save ipc error during suspend %d\n",
ret);
return ret;
} else if (ret < 0) {
/* FW in unexpected state, continue to power down */
dev_warn(sdev->dev,
"ctx_save ipc error %d, proceeding with suspend\n",
ret);
if (pm_ops && pm_ops->ctx_save) {
ret = pm_ops->ctx_save(sdev);
if (ret == -EBUSY || ret == -EAGAIN) {
/*
* runtime PM has logic to handle -EBUSY/-EAGAIN so
* pass these errors up
*/
dev_err(sdev->dev, "ctx_save IPC error during suspend: %d\n", ret);
return ret;
} else if (ret < 0) {
/* FW in unexpected state, continue to power down */
dev_warn(sdev->dev, "ctx_save IPC error: %d, proceeding with suspend\n",
ret);
}
}
suspend:
@ -278,9 +266,11 @@ suspend:
int snd_sof_dsp_power_down_notify(struct snd_sof_dev *sdev)
{
const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm;
/* Notify DSP of upcoming power down */
if (sof_ops(sdev)->remove)
return sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_SAVE);
if (sof_ops(sdev)->remove && pm_ops && pm_ops->ctx_save)
return pm_ops->ctx_save(sdev);
return 0;
}

View File

@ -27,31 +27,6 @@ static int sof_kcontrol_setup(struct snd_sof_dev *sdev, struct snd_sof_control *
return ret;
}
static int sof_dai_config_setup(struct snd_sof_dev *sdev, struct snd_sof_dai *dai)
{
struct sof_dai_private_data *private = dai->private;
struct sof_ipc_dai_config *config;
struct sof_ipc_reply reply;
int ret;
config = &private->dai_config[dai->current_config];
if (!config) {
dev_err(sdev->dev, "error: no config for DAI %s\n", dai->name);
return -EINVAL;
}
/* set NONE flag to clear all previous settings */
config->flags = SOF_DAI_CONFIG_FLAGS_NONE;
ret = sof_ipc_tx_message(sdev->ipc, config->hdr.cmd, config, config->hdr.size,
&reply, sizeof(reply));
if (ret < 0)
dev_err(sdev->dev, "error: failed to set dai config for %s\n", dai->name);
return ret;
}
static int sof_widget_kcontrol_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
{
struct snd_sof_control *scontrol;
@ -96,15 +71,9 @@ static void sof_reset_route_setup_status(struct snd_sof_dev *sdev, struct snd_so
int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
{
struct sof_ipc_free ipc_free = {
.hdr = {
.size = sizeof(ipc_free),
.cmd = SOF_IPC_GLB_TPLG_MSG,
},
.id = swidget->comp_id,
};
struct sof_ipc_reply reply;
int ret, ret1;
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
int err = 0;
int ret;
if (!swidget->private)
return 0;
@ -113,64 +82,46 @@ int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
if (--swidget->use_count)
return 0;
switch (swidget->id) {
case snd_soc_dapm_scheduler:
{
ipc_free.hdr.cmd |= SOF_IPC_TPLG_PIPE_FREE;
break;
}
case snd_soc_dapm_buffer:
ipc_free.hdr.cmd |= SOF_IPC_TPLG_BUFFER_FREE;
break;
case snd_soc_dapm_dai_in:
case snd_soc_dapm_dai_out:
{
struct snd_sof_dai *dai = swidget->private;
dai->configured = false;
fallthrough;
}
default:
ipc_free.hdr.cmd |= SOF_IPC_TPLG_COMP_FREE;
break;
}
/* continue to disable core even if IPC fails */
ret = sof_ipc_tx_message(sdev->ipc, ipc_free.hdr.cmd, &ipc_free, sizeof(ipc_free),
&reply, sizeof(reply));
if (ret < 0)
dev_err(sdev->dev, "error: failed to free widget %s\n", swidget->widget->name);
if (tplg_ops->widget_free)
err = tplg_ops->widget_free(sdev, swidget);
/*
* disable widget core. continue to route setup status and complete flag
* even if this fails and return the appropriate error
*/
ret1 = snd_sof_dsp_core_put(sdev, swidget->core);
if (ret1 < 0) {
ret = snd_sof_dsp_core_put(sdev, swidget->core);
if (ret < 0) {
dev_err(sdev->dev, "error: failed to disable target core: %d for widget %s\n",
swidget->core, swidget->widget->name);
if (!ret)
ret = ret1;
if (!err)
err = ret;
}
/* reset route setup status for all routes that contain this widget */
sof_reset_route_setup_status(sdev, swidget);
swidget->complete = 0;
if (!ret)
/*
* free the scheduler widget (same as pipe_widget) associated with the current swidget.
* skip for static pipelines
*/
if (swidget->dynamic_pipeline_widget && swidget->id != snd_soc_dapm_scheduler) {
ret = sof_widget_free(sdev, swidget->pipe_widget);
if (ret < 0 && !err)
err = ret;
}
if (!err)
dev_dbg(sdev->dev, "widget %s freed\n", swidget->widget->name);
return ret;
return err;
}
EXPORT_SYMBOL(sof_widget_free);
int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
{
struct sof_ipc_pipe_new *pipeline;
struct sof_ipc_comp_reply r;
struct sof_ipc_cmd_hdr *hdr;
struct sof_ipc_comp *comp;
struct snd_sof_dai *dai;
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
int ret;
/* skip if there is no private data */
@ -181,61 +132,50 @@ int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
if (++swidget->use_count > 1)
return 0;
/*
* The scheduler widget for a pipeline is not part of the connected DAPM
* widget list and it needs to be set up before the widgets in the pipeline
* are set up. The use_count for the scheduler widget is incremented for every
* widget in a given pipeline to ensure that it is freed only after the last
* widget in the pipeline is freed. Skip setting up scheduler widget for static pipelines.
*/
if (swidget->dynamic_pipeline_widget && swidget->id != snd_soc_dapm_scheduler) {
if (!swidget->pipe_widget) {
dev_err(sdev->dev, "No scheduler widget set for %s\n",
swidget->widget->name);
ret = -EINVAL;
goto use_count_dec;
}
ret = sof_widget_setup(sdev, swidget->pipe_widget);
if (ret < 0)
goto use_count_dec;
}
/* enable widget core */
ret = snd_sof_dsp_core_get(sdev, swidget->core);
if (ret < 0) {
dev_err(sdev->dev, "error: failed to enable target core for widget %s\n",
swidget->widget->name);
goto use_count_dec;
goto pipe_widget_free;
}
switch (swidget->id) {
case snd_soc_dapm_dai_in:
case snd_soc_dapm_dai_out:
{
struct sof_dai_private_data *dai_data;
dai = swidget->private;
dai_data = dai->private;
comp = &dai_data->comp_dai->comp;
dai->configured = false;
ret = sof_ipc_tx_message(sdev->ipc, comp->hdr.cmd, dai_data->comp_dai,
comp->hdr.size, &r, sizeof(r));
if (ret < 0) {
dev_err(sdev->dev, "error: failed to load widget %s\n",
swidget->widget->name);
/* setup widget in the DSP */
if (tplg_ops->widget_setup) {
ret = tplg_ops->widget_setup(sdev, swidget);
if (ret < 0)
goto core_put;
}
ret = sof_dai_config_setup(sdev, dai);
if (ret < 0) {
dev_err(sdev->dev, "error: failed to load dai config for DAI %s\n",
swidget->widget->name);
/*
* widget use_count and core ref_count will both be decremented by
* sof_widget_free()
*/
sof_widget_free(sdev, swidget);
return ret;
}
break;
}
case snd_soc_dapm_scheduler:
pipeline = swidget->private;
ret = sof_ipc_tx_message(sdev->ipc, pipeline->hdr.cmd, pipeline,
sizeof(*pipeline), &r, sizeof(r));
break;
default:
hdr = swidget->private;
ret = sof_ipc_tx_message(sdev->ipc, hdr->cmd, swidget->private, hdr->size,
&r, sizeof(r));
break;
}
if (ret < 0) {
dev_err(sdev->dev, "error: failed to load widget %s\n", swidget->widget->name);
goto core_put;
/* send config for DAI components */
if (WIDGET_IS_DAI(swidget->id)) {
unsigned int flags = SOF_DAI_CONFIG_FLAGS_NONE;
if (tplg_ops->dai_config) {
ret = tplg_ops->dai_config(sdev, swidget, flags, NULL);
if (ret < 0)
goto widget_free;
}
}
/* restore kcontrols for widget */
@ -243,28 +183,29 @@ int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
if (ret < 0) {
dev_err(sdev->dev, "error: failed to restore kcontrols for widget %s\n",
swidget->widget->name);
/*
* widget use_count and core ref_count will both be decremented by
* sof_widget_free()
*/
sof_widget_free(sdev, swidget);
return ret;
goto widget_free;
}
dev_dbg(sdev->dev, "widget %s setup complete\n", swidget->widget->name);
return 0;
widget_free:
/* widget use_count and core ref_count will both be decremented by sof_widget_free() */
sof_widget_free(sdev, swidget);
core_put:
snd_sof_dsp_core_put(sdev, swidget->core);
pipe_widget_free:
if (swidget->id != snd_soc_dapm_scheduler)
sof_widget_free(sdev, swidget->pipe_widget);
use_count_dec:
swidget->use_count--;
return ret;
}
EXPORT_SYMBOL(sof_widget_setup);
static int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsource,
struct snd_soc_dapm_widget *wsink)
int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsource,
struct snd_soc_dapm_widget *wsink)
{
const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg;
struct snd_sof_widget *src_widget = wsource->dobj.private;
@ -374,36 +315,14 @@ int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, in
/* set up widgets in the list */
for_each_dapm_widgets(list, num_widgets, widget) {
struct snd_sof_widget *swidget = widget->dobj.private;
struct snd_sof_widget *pipe_widget;
if (!swidget)
continue;
/*
* The scheduler widget for a pipeline is not part of the connected DAPM
* widget list and it needs to be set up before the widgets in the pipeline
* are set up. The use_count for the scheduler widget is incremented for every
* widget in a given pipeline to ensure that it is freed only after the last
* widget in the pipeline is freed.
*/
pipe_widget = swidget->pipe_widget;
if (!pipe_widget) {
dev_err(sdev->dev, "error: no pipeline widget found for %s\n",
swidget->widget->name);
ret = -EINVAL;
goto widget_free;
}
ret = sof_widget_setup(sdev, pipe_widget);
if (ret < 0)
goto widget_free;
/* set up the widget */
ret = sof_widget_setup(sdev, swidget);
if (ret < 0) {
sof_widget_free(sdev, pipe_widget);
if (ret < 0)
goto widget_free;
}
}
/*
@ -456,7 +375,6 @@ widget_free:
break;
sof_widget_free(sdev, swidget);
sof_widget_free(sdev, swidget->pipe_widget);
}
return ret;
@ -490,10 +408,6 @@ int sof_widget_list_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int
ret = sof_widget_free(sdev, swidget);
if (ret < 0)
ret1 = ret;
ret = sof_widget_free(sdev, swidget->pipe_widget);
if (ret < 0)
ret1 = ret;
}
snd_soc_dapm_dai_free_widgets(&list);
@ -584,105 +498,20 @@ int sof_set_hw_params_upon_resume(struct device *dev)
return snd_sof_dsp_hw_params_upon_resume(sdev);
}
int sof_set_up_pipelines(struct snd_sof_dev *sdev, bool verify)
int sof_pcm_stream_free(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream,
struct snd_sof_pcm *spcm, int dir, bool free_widget_list)
{
const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg;
struct sof_ipc_fw_version *v = &sdev->fw_ready.version;
struct snd_sof_widget *swidget;
struct snd_sof_route *sroute;
const struct sof_ipc_pcm_ops *pcm_ops = sdev->ipc->ops->pcm;
int ret;
/* restore pipeline components */
list_for_each_entry(swidget, &sdev->widget_list, list) {
/* only set up the widgets belonging to static pipelines */
if (!verify && swidget->dynamic_pipeline_widget)
continue;
/*
* For older firmware, skip scheduler widgets in this loop,
* sof_widget_setup() will be called in the 'complete pipeline' loop
*/
if (v->abi_version < SOF_ABI_VER(3, 19, 0) &&
swidget->id == snd_soc_dapm_scheduler)
continue;
/* update DAI config. The IPC will be sent in sof_widget_setup() */
if (WIDGET_IS_DAI(swidget->id)) {
struct snd_sof_dai *dai = swidget->private;
struct sof_dai_private_data *private = dai->private;
struct sof_ipc_dai_config *config;
if (!dai || !private || !private->dai_config)
continue;
config = private->dai_config;
/*
* The link DMA channel would be invalidated for running
* streams but not for streams that were in the PAUSED
* state during suspend. So invalidate it here before setting
* the dai config in the DSP.
*/
if (config->type == SOF_DAI_INTEL_HDA)
config->hda.link_dma_ch = DMA_CHAN_INVALID;
}
ret = sof_widget_setup(sdev, swidget);
/* Send PCM_FREE IPC to reset pipeline */
if (pcm_ops->hw_free && spcm->prepared[substream->stream]) {
ret = pcm_ops->hw_free(sdev->component, substream);
if (ret < 0)
return ret;
}
/* restore pipeline connections */
list_for_each_entry(sroute, &sdev->route_list, list) {
/* only set up routes belonging to static pipelines */
if (!verify && (sroute->src_widget->dynamic_pipeline_widget ||
sroute->sink_widget->dynamic_pipeline_widget))
continue;
ret = ipc_tplg_ops->route_setup(sdev, sroute);
if (ret < 0) {
dev_err(sdev->dev, "%s: restore pipeline connections failed\n", __func__);
return ret;
}
}
/* complete pipeline */
list_for_each_entry(swidget, &sdev->widget_list, list) {
switch (swidget->id) {
case snd_soc_dapm_scheduler:
/* only complete static pipelines */
if (!verify && swidget->dynamic_pipeline_widget)
continue;
if (v->abi_version < SOF_ABI_VER(3, 19, 0)) {
ret = sof_widget_setup(sdev, swidget);
if (ret < 0)
return ret;
}
if (ipc_tplg_ops->pipeline_complete) {
swidget->complete = ipc_tplg_ops->pipeline_complete(sdev, swidget);
if (swidget->complete < 0)
return swidget->complete;
}
break;
default:
break;
}
}
return 0;
}
int sof_pcm_stream_free(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream,
struct snd_sof_pcm *spcm, int dir, bool free_widget_list)
{
int ret;
/* Send PCM_FREE IPC to reset pipeline */
ret = sof_pcm_dsp_pcm_free(substream, sdev, spcm);
if (ret < 0)
return ret;
spcm->prepared[substream->stream] = false;
/* stop the DMA */
ret = snd_sof_pcm_platform_hw_free(sdev, substream);
@ -699,102 +528,6 @@ int sof_pcm_stream_free(struct snd_sof_dev *sdev, struct snd_pcm_substream *subs
return ret;
}
/*
* Free the PCM, its associated widgets and set the prepared flag to false for all PCMs that
* did not get suspended(ex: paused streams) so the widgets can be set up again during resume.
*/
static int sof_tear_down_left_over_pipelines(struct snd_sof_dev *sdev)
{
struct snd_sof_widget *swidget;
struct snd_sof_pcm *spcm;
int dir, ret;
/*
* free all PCMs and their associated DAPM widgets if their connected DAPM widget
* list is not NULL. This should only be true for paused streams at this point.
* This is equivalent to the handling of FE DAI suspend trigger for running streams.
*/
list_for_each_entry(spcm, &sdev->pcm_list, list)
for_each_pcm_streams(dir) {
struct snd_pcm_substream *substream = spcm->stream[dir].substream;
if (!substream || !substream->runtime)
continue;
if (spcm->stream[dir].list) {
ret = sof_pcm_stream_free(sdev, substream, spcm, dir, true);
if (ret < 0)
return ret;
}
}
/*
* free any left over DAI widgets. This is equivalent to the handling of suspend trigger
* for the BE DAI for running streams.
*/
list_for_each_entry(swidget, &sdev->widget_list, list)
if (WIDGET_IS_DAI(swidget->id) && swidget->use_count == 1) {
ret = sof_widget_free(sdev, swidget);
if (ret < 0)
return ret;
}
return 0;
}
/*
* For older firmware, this function doesn't free widgets for static pipelines during suspend.
* It only resets use_count for all widgets.
*/
int sof_tear_down_pipelines(struct snd_sof_dev *sdev, bool verify)
{
struct sof_ipc_fw_version *v = &sdev->fw_ready.version;
struct snd_sof_widget *swidget;
struct snd_sof_route *sroute;
int ret;
/*
* This function is called during suspend and for one-time topology verification during
* first boot. In both cases, there is no need to protect swidget->use_count and
* sroute->setup because during suspend all running streams are suspended and during
* topology loading the sound card unavailable to open PCMs.
*/
list_for_each_entry(swidget, &sdev->widget_list, list) {
if (swidget->dynamic_pipeline_widget)
continue;
/* Do not free widgets for static pipelines with FW ABI older than 3.19 */
if (!verify && !swidget->dynamic_pipeline_widget &&
v->abi_version < SOF_ABI_VER(3, 19, 0)) {
swidget->use_count = 0;
swidget->complete = 0;
continue;
}
ret = sof_widget_free(sdev, swidget);
if (ret < 0)
return ret;
}
/*
* Tear down all pipelines associated with PCMs that did not get suspended
* and unset the prepare flag so that they can be set up again during resume.
* Skip this step for older firmware.
*/
if (!verify && v->abi_version >= SOF_ABI_VER(3, 19, 0)) {
ret = sof_tear_down_left_over_pipelines(sdev);
if (ret < 0) {
dev_err(sdev->dev, "failed to tear down paused pipelines\n");
return ret;
}
}
list_for_each_entry(sroute, &sdev->route_list, list)
sroute->setup = false;
return 0;
}
/*
* Generic object lookup APIs.
*/
@ -895,40 +628,23 @@ struct snd_sof_dai *snd_sof_find_dai(struct snd_soc_component *scomp,
return NULL;
}
#define SOF_DAI_CLK_INTEL_SSP_MCLK 0
#define SOF_DAI_CLK_INTEL_SSP_BCLK 1
static int sof_dai_get_clk(struct snd_soc_pcm_runtime *rtd, int clk_type)
{
struct snd_soc_component *component =
snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME);
struct snd_sof_dai *dai =
snd_sof_find_dai(component, (char *)rtd->dai_link->name);
struct sof_dai_private_data *private = dai->private;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
/* use the tplg configured mclk if existed */
if (!dai || !private || !private->dai_config)
if (!dai)
return 0;
switch (private->dai_config->type) {
case SOF_DAI_INTEL_SSP:
switch (clk_type) {
case SOF_DAI_CLK_INTEL_SSP_MCLK:
return private->dai_config->ssp.mclk_rate;
case SOF_DAI_CLK_INTEL_SSP_BCLK:
return private->dai_config->ssp.bclk_rate;
default:
dev_err(rtd->dev, "fail to get SSP clk %d rate\n",
clk_type);
return -EINVAL;
}
break;
default:
/* not yet implemented for platforms other than the above */
dev_err(rtd->dev, "DAI type %d not supported yet!\n",
private->dai_config->type);
return -EINVAL;
}
if (tplg_ops->dai_get_clk)
return tplg_ops->dai_get_clk(sdev, dai, clk_type);
return 0;
}
/*

View File

@ -30,6 +30,9 @@
#define WIDGET_IS_DAI(id) ((id) == snd_soc_dapm_dai_in || (id) == snd_soc_dapm_dai_out)
#define SOF_DAI_CLK_INTEL_SSP_MCLK 0
#define SOF_DAI_CLK_INTEL_SSP_BCLK 1
/*
* Volume fractional word length define to 16 sets
* the volume linear gain value to use Qx.16 format
@ -39,6 +42,51 @@
struct snd_sof_widget;
struct snd_sof_route;
struct snd_sof_control;
struct snd_sof_dai;
struct snd_sof_dai_config_data {
int dai_index;
int dai_data; /* contains DAI-specific information */
};
/**
* struct sof_ipc_pcm_ops - IPC-specific PCM ops
* @hw_params: Function pointer for hw_params
* @hw_free: Function pointer for hw_free
* @trigger: Function pointer for trigger
* @dai_link_fixup: Function pointer for DAI link fixup
*/
struct sof_ipc_pcm_ops {
int (*hw_params)(struct snd_soc_component *component, struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_sof_platform_stream_params *platform_params);
int (*hw_free)(struct snd_soc_component *component, struct snd_pcm_substream *substream);
int (*trigger)(struct snd_soc_component *component, struct snd_pcm_substream *substream,
int cmd);
int (*dai_link_fixup)(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params);
};
/**
* struct sof_ipc_tplg_control_ops - IPC-specific ops for topology kcontrol IO
*/
struct sof_ipc_tplg_control_ops {
bool (*volume_put)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol);
int (*volume_get)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol);
bool (*switch_put)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol);
int (*switch_get)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol);
bool (*enum_put)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol);
int (*enum_get)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol);
int (*bytes_put)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol);
int (*bytes_get)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol);
int (*bytes_ext_get)(struct snd_sof_control *scontrol,
const unsigned int __user *binary_data, unsigned int size);
int (*bytes_ext_volatile_get)(struct snd_sof_control *scontrol,
const unsigned int __user *binary_data, unsigned int size);
int (*bytes_ext_put)(struct snd_sof_control *scontrol,
const unsigned int __user *binary_data, unsigned int size);
/* update control data based on notification from the DSP */
void (*update)(struct snd_sof_dev *sdev, void *ipc_control_message);
};
/**
* struct sof_ipc_tplg_widget_ops - IPC-specific ops for topology widgets
@ -62,6 +110,7 @@ struct sof_ipc_tplg_widget_ops {
* @widget: Array of pointers to IPC-specific ops for widgets. This should always be of size
* SND_SOF_DAPM_TYPE_COUNT i.e one per widget type. Unsupported widget types will be
* initialized to 0.
* @control: Pointer to the IPC-specific ops for topology kcontrol IO
* @route_setup: Function pointer for setting up pipeline connections
* @token_list: List of all tokens supported by the IPC version. The size of the token_list
* array should be SOF_TOKEN_COUNT. The unused elements in the array will be
@ -69,14 +118,28 @@ struct sof_ipc_tplg_widget_ops {
* @control_setup: Function pointer for setting up kcontrol IPC-specific data
* @control_free: Function pointer for freeing kcontrol IPC-specific data
* @pipeline_complete: Function pointer for pipeline complete IPC
* @widget_setup: Function pointer for setting up setup in the DSP
* @widget_free: Function pointer for freeing widget in the DSP
* @dai_config: Function pointer for sending DAI config IPC to the DSP
* @dai_get_clk: Function pointer for getting the DAI clock setting
* @set_up_all_pipelines: Function pointer for setting up all topology pipelines
* @tear_down_all_pipelines: Function pointer for tearing down all topology pipelines
*/
struct sof_ipc_tplg_ops {
const struct sof_ipc_tplg_widget_ops *widget;
const struct sof_ipc_tplg_control_ops *control;
int (*route_setup)(struct snd_sof_dev *sdev, struct snd_sof_route *sroute);
const struct sof_token_info *token_list;
int (*control_setup)(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol);
int (*control_free)(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol);
int (*pipeline_complete)(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget);
int (*widget_setup)(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget);
int (*widget_free)(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget);
int (*dai_config)(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget,
unsigned int flags, struct snd_sof_dai_config_data *data);
int (*dai_get_clk)(struct snd_sof_dev *sdev, struct snd_sof_dai *dai, int clk_type);
int (*set_up_all_pipelines)(struct snd_sof_dev *sdev, bool verify);
int (*tear_down_all_pipelines)(struct snd_sof_dev *sdev, bool verify);
};
/** struct snd_sof_tuple - Tuple info
@ -276,7 +339,6 @@ struct snd_sof_dai {
int number_configs;
int current_config;
bool configured; /* DAI configured during BE hw_params */
struct list_head list; /* list in sdev dai list */
void *private;
};
@ -377,8 +439,6 @@ int snd_sof_ipc_set_get_comp_data(struct snd_sof_control *scontrol, bool set);
int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params);
/* PM */
int sof_set_up_pipelines(struct snd_sof_dev *sdev, bool verify);
int sof_tear_down_pipelines(struct snd_sof_dev *sdev, bool verify);
int sof_set_hw_params_upon_resume(struct device *dev);
bool snd_sof_stream_suspend_ignored(struct snd_sof_dev *sdev);
bool snd_sof_dsp_only_d0i3_compatible_stream_active(struct snd_sof_dev *sdev);
@ -389,6 +449,8 @@ void sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata);
int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget);
int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget);
int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsource,
struct snd_soc_dapm_widget *wsink);
/* PCM */
int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir);
@ -405,4 +467,6 @@ int get_token_uuid(void *elem, void *object, u32 offset);
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);
int sof_pcm_setup_connected_widgets(struct snd_sof_dev *sdev, struct snd_soc_pcm_runtime *rtd,
struct snd_sof_pcm *spcm, int dir);
#endif

View File

@ -360,18 +360,31 @@ struct snd_sof_ipc_msg {
bool ipc_complete;
};
/**
* struct sof_ipc_pm_ops - IPC-specific PM ops
* @ctx_save: Function pointer for context save
* @ctx_restore: Function pointer for context restore
*/
struct sof_ipc_pm_ops {
int (*ctx_save)(struct snd_sof_dev *sdev);
int (*ctx_restore)(struct snd_sof_dev *sdev);
};
struct sof_ipc_tplg_ops;
struct sof_ipc_pcm_ops;
/**
* struct sof_ipc_ops - IPC-specific ops
* @tplg: Pointer to IPC-specific topology ops
* @pm: Pointer to PM ops
* @pcm: Pointer to PCM ops
*/
struct sof_ipc_ops {
const struct sof_ipc_tplg_ops *tplg;
const struct sof_ipc_pm_ops *pm;
const struct sof_ipc_pcm_ops *pcm;
};
extern const struct sof_ipc_ops ipc3_ops;
/* SOF generic IPC data */
struct snd_sof_ipc {
struct snd_sof_dev *sdev;

View File

@ -1796,29 +1796,15 @@ static int sof_route_load(struct snd_soc_component *scomp, int index,
sink_swidget->id == snd_soc_dapm_output)
goto err;
/*
* For virtual routes, both sink and source are not
* buffer. Since only buffer linked to component is supported by
* FW, others are reported as error, add check in route function,
* do not send it to FW when both source and sink are not buffer
*/
if (source_swidget->id != snd_soc_dapm_buffer &&
sink_swidget->id != snd_soc_dapm_buffer) {
dev_dbg(scomp->dev, "warning: neither Linked source component %s nor sink component %s is of buffer type, ignoring link\n",
route->source, route->sink);
goto err;
} else {
sroute->route = route;
dobj->private = sroute;
sroute->src_widget = source_swidget;
sroute->sink_widget = sink_swidget;
sroute->route = route;
dobj->private = sroute;
sroute->src_widget = source_swidget;
sroute->sink_widget = sink_swidget;
/* add route to route list */
list_add(&sroute->list, &sdev->route_list);
return 0;
}
/* add route to route list */
list_add(&sroute->list, &sdev->route_list);
return 0;
err:
kfree(sroute);
return ret;
@ -1917,21 +1903,28 @@ static int sof_complete(struct snd_soc_component *scomp)
/* verify topology components loading including dynamic pipelines */
if (sof_debug_check_flag(SOF_DBG_VERIFY_TPLG)) {
ret = sof_set_up_pipelines(sdev, true);
if (ret < 0) {
dev_err(sdev->dev, "error: topology verification failed %d\n", ret);
return ret;
}
if (ipc_tplg_ops->set_up_all_pipelines && ipc_tplg_ops->tear_down_all_pipelines) {
ret = ipc_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 = sof_tear_down_pipelines(sdev, true);
if (ret < 0) {
dev_err(sdev->dev, "error: topology tear down pipelines failed %d\n", ret);
return ret;
ret = ipc_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 */
return sof_set_up_pipelines(sdev, false);
if (ipc_tplg_ops->set_up_all_pipelines)
return ipc_tplg_ops->set_up_all_pipelines(sdev, false);
return 0;
}
/* manifest - optional to inform component of manifest */