未验证 提交 49a24e9d 编写于 作者: M Mark Brown

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.
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
snd-sof-objs := core.o ops.o loader.o ipc.o pcm.o pm.o debug.o topology.o\ 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\ 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),) ifneq ($(CONFIG_SND_SOC_SOF_CLIENT),)
snd-sof-objs += sof-client.o snd-sof-objs += sof-client.o
endif endif
......
...@@ -45,68 +45,17 @@ static void update_mute_led(struct snd_sof_control *scontrol, ...@@ -45,68 +45,17 @@ static void update_mute_led(struct snd_sof_control *scontrol,
#endif #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, int snd_sof_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol) struct snd_ctl_elem_value *ucontrol)
{ {
struct soc_mixer_control *sm = struct soc_mixer_control *sm = (struct soc_mixer_control *)kcontrol->private_value;
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_sof_control *scontrol = sm->dobj.private; struct snd_sof_control *scontrol = sm->dobj.private;
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; struct snd_soc_component *scomp = scontrol->scomp;
unsigned int i, channels = scontrol->num_channels; 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 */ if (tplg_ops->control->volume_get)
for (i = 0; i < channels; i++) return tplg_ops->control->volume_get(scontrol, ucontrol);
ucontrol->value.integer.value[i] =
ipc_to_mixer(cdata->chanv[i].value,
scontrol->volume_table, sm->max + 1);
return 0; return 0;
} }
...@@ -114,28 +63,16 @@ int snd_sof_volume_get(struct snd_kcontrol *kcontrol, ...@@ -114,28 +63,16 @@ int snd_sof_volume_get(struct snd_kcontrol *kcontrol,
int snd_sof_volume_put(struct snd_kcontrol *kcontrol, int snd_sof_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol) struct snd_ctl_elem_value *ucontrol)
{ {
struct soc_mixer_control *sm = struct soc_mixer_control *sm = (struct soc_mixer_control *)kcontrol->private_value;
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_sof_control *scontrol = sm->dobj.private; struct snd_sof_control *scontrol = sm->dobj.private;
struct snd_soc_component *scomp = scontrol->scomp; struct snd_soc_component *scomp = scontrol->scomp;
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
unsigned int i, channels = scontrol->num_channels; const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
bool change = false;
u32 value;
/* 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;
}
/* notify DSP of mixer updates */ if (tplg_ops->control->volume_put)
if (pm_runtime_active(scomp->dev)) return tplg_ops->control->volume_put(scontrol, ucontrol);
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) 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 ...@@ -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, int snd_sof_switch_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol) struct snd_ctl_elem_value *ucontrol)
{ {
struct soc_mixer_control *sm = struct soc_mixer_control *sm = (struct soc_mixer_control *)kcontrol->private_value;
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_sof_control *scontrol = sm->dobj.private; struct snd_sof_control *scontrol = sm->dobj.private;
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; struct snd_soc_component *scomp = scontrol->scomp;
unsigned int i, channels = scontrol->num_channels; 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 */ if (tplg_ops->control->switch_get)
for (i = 0; i < channels; i++) return tplg_ops->control->switch_get(scontrol, ucontrol);
ucontrol->value.integer.value[i] = cdata->chanv[i].value;
return 0; return 0;
} }
...@@ -181,47 +115,32 @@ int snd_sof_switch_get(struct snd_kcontrol *kcontrol, ...@@ -181,47 +115,32 @@ int snd_sof_switch_get(struct snd_kcontrol *kcontrol,
int snd_sof_switch_put(struct snd_kcontrol *kcontrol, int snd_sof_switch_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol) struct snd_ctl_elem_value *ucontrol)
{ {
struct soc_mixer_control *sm = struct soc_mixer_control *sm = (struct soc_mixer_control *)kcontrol->private_value;
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_sof_control *scontrol = sm->dobj.private; struct snd_sof_control *scontrol = sm->dobj.private;
struct snd_soc_component *scomp = scontrol->scomp; struct snd_soc_component *scomp = scontrol->scomp;
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
unsigned int i, channels = scontrol->num_channels; const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
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;
}
if (scontrol->led_ctl.use_led) if (scontrol->led_ctl.use_led)
update_mute_led(scontrol, kcontrol, ucontrol); update_mute_led(scontrol, kcontrol, ucontrol);
/* notify DSP of mixer updates */ if (tplg_ops->control->switch_put)
if (pm_runtime_active(scomp->dev)) return tplg_ops->control->switch_put(scontrol, ucontrol);
snd_sof_ipc_set_get_comp_data(scontrol, true);
return change; return false;
} }
int snd_sof_enum_get(struct snd_kcontrol *kcontrol, int snd_sof_enum_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol) struct snd_ctl_elem_value *ucontrol)
{ {
struct soc_enum *se = struct soc_enum *se = (struct soc_enum *)kcontrol->private_value;
(struct soc_enum *)kcontrol->private_value;
struct snd_sof_control *scontrol = se->dobj.private; struct snd_sof_control *scontrol = se->dobj.private;
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; struct snd_soc_component *scomp = scontrol->scomp;
unsigned int i, channels = scontrol->num_channels; 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 */ if (tplg_ops->control->enum_get)
for (i = 0; i < channels; i++) return tplg_ops->control->enum_get(scontrol, ucontrol);
ucontrol->value.enumerated.item[i] = cdata->chanv[i].value;
return 0; return 0;
} }
...@@ -229,62 +148,29 @@ int snd_sof_enum_get(struct snd_kcontrol *kcontrol, ...@@ -229,62 +148,29 @@ int snd_sof_enum_get(struct snd_kcontrol *kcontrol,
int snd_sof_enum_put(struct snd_kcontrol *kcontrol, int snd_sof_enum_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol) struct snd_ctl_elem_value *ucontrol)
{ {
struct soc_enum *se = struct soc_enum *se = (struct soc_enum *)kcontrol->private_value;
(struct soc_enum *)kcontrol->private_value;
struct snd_sof_control *scontrol = se->dobj.private; struct snd_sof_control *scontrol = se->dobj.private;
struct snd_soc_component *scomp = scontrol->scomp; struct snd_soc_component *scomp = scontrol->scomp;
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
unsigned int i, channels = scontrol->num_channels; const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
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 (tplg_ops->control->enum_put)
if (pm_runtime_active(scomp->dev)) return tplg_ops->control->enum_put(scontrol, ucontrol);
snd_sof_ipc_set_get_comp_data(scontrol, true);
return change; return false;
} }
int snd_sof_bytes_get(struct snd_kcontrol *kcontrol, int snd_sof_bytes_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol) struct snd_ctl_elem_value *ucontrol)
{ {
struct soc_bytes_ext *be = struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value;
(struct soc_bytes_ext *)kcontrol->private_value;
struct snd_sof_control *scontrol = be->dobj.private; struct snd_sof_control *scontrol = be->dobj.private;
struct snd_soc_component *scomp = scontrol->scomp; struct snd_soc_component *scomp = scontrol->scomp;
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct sof_abi_hdr *data = cdata->data; const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
size_t size;
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 (tplg_ops->control->bytes_get)
if (data->size > be->max - sizeof(*data)) { return tplg_ops->control->bytes_get(scontrol, ucontrol);
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);
return 0; return 0;
} }
...@@ -292,37 +178,14 @@ int snd_sof_bytes_get(struct snd_kcontrol *kcontrol, ...@@ -292,37 +178,14 @@ int snd_sof_bytes_get(struct snd_kcontrol *kcontrol,
int snd_sof_bytes_put(struct snd_kcontrol *kcontrol, int snd_sof_bytes_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol) struct snd_ctl_elem_value *ucontrol)
{ {
struct soc_bytes_ext *be = struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value;
(struct soc_bytes_ext *)kcontrol->private_value;
struct snd_sof_control *scontrol = be->dobj.private; struct snd_sof_control *scontrol = be->dobj.private;
struct snd_soc_component *scomp = scontrol->scomp; struct snd_soc_component *scomp = scontrol->scomp;
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct sof_abi_hdr *data = cdata->data; const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
size_t size;
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 (tplg_ops->control->bytes_put)
if (pm_runtime_active(scomp->dev)) return tplg_ops->control->bytes_put(scontrol, ucontrol);
snd_sof_ipc_set_get_comp_data(scontrol, true);
return 0; return 0;
} }
...@@ -331,74 +194,18 @@ int snd_sof_bytes_ext_put(struct snd_kcontrol *kcontrol, ...@@ -331,74 +194,18 @@ int snd_sof_bytes_ext_put(struct snd_kcontrol *kcontrol,
const unsigned int __user *binary_data, const unsigned int __user *binary_data,
unsigned int size) unsigned int size)
{ {
struct soc_bytes_ext *be = struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value;
(struct soc_bytes_ext *)kcontrol->private_value;
struct snd_sof_control *scontrol = be->dobj.private; struct snd_sof_control *scontrol = be->dobj.private;
struct snd_soc_component *scomp = scontrol->scomp; struct snd_soc_component *scomp = scontrol->scomp;
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct snd_ctl_tlv header; const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
const struct snd_ctl_tlv __user *tlvd =
(const struct snd_ctl_tlv __user *)binary_data;
/* make sure we have at least a header */ /* make sure we have at least a header */
if (size < sizeof(struct snd_ctl_tlv)) if (size < sizeof(struct snd_ctl_tlv))
return -EINVAL; return -EINVAL;
/* if (tplg_ops->control->bytes_ext_put)
* The beginning of bytes data contains a header from where return tplg_ops->control->bytes_ext_put(scontrol, binary_data, size);
* 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);
return 0; return 0;
} }
...@@ -409,67 +216,24 @@ int snd_sof_bytes_ext_volatile_get(struct snd_kcontrol *kcontrol, unsigned int _ ...@@ -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 soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value;
struct snd_sof_control *scontrol = be->dobj.private; struct snd_sof_control *scontrol = be->dobj.private;
struct snd_soc_component *scomp = scontrol->scomp; struct snd_soc_component *scomp = scontrol->scomp;
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct snd_ctl_tlv header; const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
struct snd_ctl_tlv __user *tlvd = (struct snd_ctl_tlv __user *)binary_data; int ret, err;
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);
ret = pm_runtime_get_sync(scomp->dev); ret = pm_runtime_get_sync(scomp->dev);
if (ret < 0 && ret != -EACCES) { 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); pm_runtime_put_noidle(scomp->dev);
return ret; return ret;
} }
/* set the ABI header values */ if (tplg_ops->control->bytes_ext_volatile_get)
cdata->data->magic = SOF_ABI_MAGIC; ret = tplg_ops->control->bytes_ext_volatile_get(scontrol, binary_data, size);
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;
/* 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); pm_runtime_mark_last_busy(scomp->dev);
err = pm_runtime_put_autosuspend(scomp->dev); err = pm_runtime_put_autosuspend(scomp->dev);
if (err < 0) 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; return ret;
} }
...@@ -478,195 +242,14 @@ int snd_sof_bytes_ext_get(struct snd_kcontrol *kcontrol, ...@@ -478,195 +242,14 @@ int snd_sof_bytes_ext_get(struct snd_kcontrol *kcontrol,
unsigned int __user *binary_data, unsigned int __user *binary_data,
unsigned int size) unsigned int size)
{ {
struct soc_bytes_ext *be = struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value;
(struct soc_bytes_ext *)kcontrol->private_value;
struct snd_sof_control *scontrol = be->dobj.private; struct snd_sof_control *scontrol = be->dobj.private;
struct snd_soc_component *scomp = scontrol->scomp; struct snd_soc_component *scomp = scontrol->scomp;
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
struct snd_ctl_tlv header; const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
struct snd_ctl_tlv __user *tlvd =
(struct snd_ctl_tlv __user *)binary_data;
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 > 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 (tplg_ops->control->bytes_ext_get)
if (data_size > size) return tplg_ops->control->bytes_ext_get(scontrol, binary_data, 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; 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);
}
...@@ -162,58 +162,19 @@ static int hda_link_dma_params(struct hdac_ext_stream *hext_stream, ...@@ -162,58 +162,19 @@ static int hda_link_dma_params(struct hdac_ext_stream *hext_stream,
return 0; 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, static int hda_link_dai_widget_update(struct sof_intel_hda_stream *hda_stream,
struct snd_soc_dapm_widget *w, struct snd_soc_dapm_widget *w,
int channel, bool widget_setup) int channel, bool widget_setup)
{ {
struct snd_sof_dev *sdev = hda_stream->sdev; struct snd_sof_dai_config_data data;
struct sof_ipc_dai_config *config;
config = hda_dai_update_config(w, channel); data.dai_data = channel;
if (!config) {
dev_err(sdev->dev, "error: no config for DAI %s\n", w->name);
return -ENOENT;
}
/* set up/free DAI widget and send DAI_CONFIG IPC */ /* set up/free DAI widget and send DAI_CONFIG IPC */
if (widget_setup) 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, 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) ...@@ -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_sof_widget *swidget = w->dobj.private;
struct snd_soc_component *component = swidget->scomp; struct snd_soc_component *component = swidget->scomp;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
struct sof_dai_private_data *private; const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
struct sof_ipc_dai_config *config; int ret = 0;
struct snd_sof_dai *sof_dai;
struct sof_ipc_reply reply;
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);
return -EINVAL;
}
private = sof_dai->private; if (tplg_ops->dai_config) {
if (!private->dai_config) { ret = tplg_ops->dai_config(sdev, swidget, SOF_DAI_CONFIG_FLAGS_PAUSE, NULL);
dev_err(sdev->dev, "%s: No config for DAI %s\n", __func__, w->name); if (ret < 0)
return -EINVAL; dev_err(sdev->dev, "%s: DAI config failed for widget %s\n", __func__,
w->name);
} }
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; return ret;
} }
...@@ -470,30 +412,17 @@ struct ssp_dai_dma_data { ...@@ -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, static int ssp_dai_setup_or_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai,
bool setup) bool setup)
{ {
struct snd_soc_component *component;
struct snd_sof_widget *swidget;
struct snd_soc_dapm_widget *w; struct snd_soc_dapm_widget *w;
struct sof_ipc_fw_version *v;
struct snd_sof_dev *sdev;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
w = dai->playback_widget; w = dai->playback_widget;
else else
w = dai->capture_widget; 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) 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, static int ssp_dai_startup(struct snd_pcm_substream *substream,
......
...@@ -41,114 +41,68 @@ ...@@ -41,114 +41,68 @@
#define EXCEPT_MAX_HDR_SIZE 0x400 #define EXCEPT_MAX_HDR_SIZE 0x400
#define HDA_EXT_ROM_STATUS_SIZE 8 #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_sof_widget *swidget = w->dobj.private;
struct snd_soc_component *component = swidget->scomp; struct snd_soc_component *component = swidget->scomp;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
struct sof_ipc_dai_config *config; const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
struct sof_dai_private_data *private; struct snd_sof_dai *sof_dai = swidget->private;
struct snd_sof_dai *sof_dai;
struct sof_ipc_reply reply;
int ret; int ret;
sof_dai = swidget->private; if (!sof_dai) {
dev_err(sdev->dev, "%s: No DAI for DAI widget %s\n", __func__, w->name);
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; return -EINVAL;
} }
/* DAI already configured, reset it before reconfiguring it */ if (tplg_ops->dai_config) {
if (sof_dai->configured) { unsigned int flags;
ret = hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE);
if (ret < 0)
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 */ /* set HW_PARAMS flag along with quirks */
config->flags = SOF_DAI_CONFIG_FLAGS_HW_PARAMS | flags = SOF_DAI_CONFIG_FLAGS_HW_PARAMS |
quirk_flags << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT; quirk_flags << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT;
ret = tplg_ops->dai_config(sdev, swidget, flags, data);
/* send DAI_CONFIG IPC */ if (ret < 0) {
ret = sof_ipc_tx_message(sdev->ipc, config->hdr.cmd, config, config->hdr.size, dev_err(sdev->dev, "%s: DAI config failed for widget %s\n", __func__,
&reply, sizeof(reply)); w->name);
if (ret < 0) { return ret;
dev_err(sdev->dev, "error: failed setting DAI config for %s\n", w->name); }
return ret;
} }
sof_dai->configured = true;
return 0; 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_sof_widget *swidget = w->dobj.private;
struct snd_soc_component *component = swidget->scomp; struct snd_soc_component *component = swidget->scomp;
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
struct sof_dai_private_data *private; const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
struct sof_ipc_dai_config *config; struct snd_sof_dai *sof_dai = swidget->private;
struct snd_sof_dai *sof_dai;
struct sof_ipc_reply reply;
int ret;
sof_dai = swidget->private; if (!sof_dai) {
dev_err(sdev->dev, "%s: No DAI for BE DAI widget %s\n", __func__, w->name);
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; return -EINVAL;
} }
/* nothing to do if hw_free() is called without restarting the stream after resume. */ if (tplg_ops->dai_config) {
if (!sof_dai->configured) unsigned int flags;
return 0; int ret;
config = &private->dai_config[sof_dai->current_config];
/* set HW_FREE flag along with any quirks */ /* set HW_FREE flag along with any quirks */
config->flags = SOF_DAI_CONFIG_FLAGS_HW_FREE | flags = SOF_DAI_CONFIG_FLAGS_HW_FREE |
quirk_flags << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT; quirk_flags << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT;
ret = sof_ipc_tx_message(sdev->ipc, config->hdr.cmd, config, config->hdr.size, ret = tplg_ops->dai_config(sdev, swidget, flags, data);
&reply, sizeof(reply)); if (ret < 0)
if (ret < 0) dev_err(sdev->dev, "%s: DAI config failed for widget '%s'\n", __func__,
dev_err(sdev->dev, "error: failed resetting DAI config for %s\n", w->name); 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) #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; ...@@ -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_param(sdw_clock_stop_quirks, int, 0444);
MODULE_PARM_DESC(sdw_clock_stop_quirks, "SOF SoundWire clock stop quirks"); 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, static int sdw_params_stream(struct device *dev,
struct sdw_intel_stream_params_data *params_data) 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_soc_dai *d = params_data->dai;
struct snd_sof_dai_config_data data;
struct snd_soc_dapm_widget *w; struct snd_soc_dapm_widget *w;
w = snd_soc_dai_get_widget(d, params_data->stream); 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, return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_NONE, &data);
d->id, true);
} }
static int sdw_free_stream(struct device *dev, static int sdw_free_stream(struct device *dev,
struct sdw_intel_stream_free_data *free_data) 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_soc_dai *d = free_data->dai;
struct snd_sof_dai_config_data data;
struct snd_soc_dapm_widget *w; struct snd_soc_dapm_widget *w;
w = snd_soc_dai_get_widget(d, free_data->stream); w = snd_soc_dai_get_widget(d, free_data->stream);
data.dai_index = (free_data->link_id << 8) | d->id;
/* send invalid stream_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 = { static const struct sdw_intel_ops sdw_callback = {
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
#include <sound/hda_codec.h> #include <sound/hda_codec.h>
#include <sound/hdaudio_ext.h> #include <sound/hdaudio_ext.h>
#include "../sof-client-probes.h" #include "../sof-client-probes.h"
#include "../sof-audio.h"
#include "shim.h" #include "shim.h"
/* PCI registers */ /* PCI registers */
...@@ -730,8 +731,10 @@ int hda_pci_intel_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) ...@@ -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 snd_sof_dai;
struct sof_ipc_dai_config; 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_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); 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_SKYLAKE_LEGACY (0) /* previous implementation */
#define SOF_HDA_POSITION_QUIRK_USE_DPIB_REGISTERS (1) /* recommended if VC0 only */ #define SOF_HDA_POSITION_QUIRK_USE_DPIB_REGISTERS (1) /* recommended if VC0 only */
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
#include "sof-priv.h" #include "sof-priv.h"
#include "sof-audio.h" #include "sof-audio.h"
#include "ops.h" #include "ops.h"
#include "ipc3-ops.h"
typedef void (*ipc_rx_callback)(struct snd_sof_dev *sdev, void *msg_buf); typedef void (*ipc_rx_callback)(struct snd_sof_dev *sdev, void *msg_buf);
...@@ -469,6 +470,7 @@ EXPORT_SYMBOL(snd_sof_ipc_reply); ...@@ -469,6 +470,7 @@ EXPORT_SYMBOL(snd_sof_ipc_reply);
static void ipc_comp_notification(struct snd_sof_dev *sdev, void *msg_buf) 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; struct sof_ipc_cmd_hdr *hdr = msg_buf;
u32 msg_type = hdr->cmd & SOF_CMD_TYPE_MASK; 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) ...@@ -481,7 +483,8 @@ static void ipc_comp_notification(struct snd_sof_dev *sdev, void *msg_buf)
return; 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 */ /* DSP firmware has sent host a message */
...@@ -1030,8 +1033,9 @@ struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev) ...@@ -1030,8 +1033,9 @@ struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev)
ipc->ops = &ipc3_ops; ipc->ops = &ipc3_ops;
/* check for mandatory ops */ /* check for mandatory ops */
if (!ipc->ops->tplg || !ipc->ops->tplg->widget) { if (!ipc->ops->pcm || !ipc->ops->tplg || !ipc->ops->tplg->widget ||
dev_err(sdev->dev, "Invalid topology IPC ops\n"); !ipc->ops->tplg->control) {
dev_err(sdev->dev, "Invalid IPC ops\n");
return NULL; return NULL;
} }
......
// 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,
};
/* 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
// 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,
};
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include <sound/pcm_params.h> #include <sound/pcm_params.h>
#include "sof-priv.h" #include "sof-priv.h"
#include "sof-audio.h" #include "sof-audio.h"
#include "ipc3-ops.h"
#include "ops.h" #include "ops.h"
/* Full volume for default values */ /* Full volume for default values */
...@@ -1909,6 +1910,376 @@ static int sof_ipc3_complete_pipeline(struct snd_sof_dev *sdev, struct snd_sof_w ...@@ -1909,6 +1910,376 @@ static int sof_ipc3_complete_pipeline(struct snd_sof_dev *sdev, struct snd_sof_w
return 1; 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 */ /* token list for each topology object */
static enum sof_tokens host_token_list[] = { static enum sof_tokens host_token_list[] = {
SOF_CORE_TOKENS, SOF_CORE_TOKENS,
...@@ -2005,15 +2376,18 @@ static const struct sof_ipc_tplg_widget_ops tplg_ipc3_widget_ops[SND_SOC_DAPM_TY ...@@ -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}, 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, .widget = tplg_ipc3_widget_ops,
.control = &tplg_ipc3_control_ops,
.route_setup = sof_ipc3_route_setup, .route_setup = sof_ipc3_route_setup,
.control_setup = sof_ipc3_control_setup, .control_setup = sof_ipc3_control_setup,
.control_free = sof_ipc3_control_free, .control_free = sof_ipc3_control_free,
.pipeline_complete = sof_ipc3_complete_pipeline, .pipeline_complete = sof_ipc3_complete_pipeline,
.token_list = ipc3_token_list, .token_list = ipc3_token_list,
}; .widget_free = sof_ipc3_widget_free,
.widget_setup = sof_ipc3_widget_setup,
const struct sof_ipc_ops ipc3_ops = { .dai_config = sof_ipc3_dai_config,
.tplg = &ipc3_tplg_ops, .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,
}; };
// 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,
};
...@@ -82,32 +82,8 @@ void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream) ...@@ -82,32 +82,8 @@ void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream)
} }
EXPORT_SYMBOL(snd_sof_pcm_period_elapsed); EXPORT_SYMBOL(snd_sof_pcm_period_elapsed);
int sof_pcm_dsp_pcm_free(struct snd_pcm_substream *substream, struct snd_sof_dev *sdev, int sof_pcm_setup_connected_widgets(struct snd_sof_dev *sdev, struct snd_soc_pcm_runtime *rtd,
struct snd_sof_pcm *spcm) struct snd_sof_pcm *spcm, int dir)
{
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)
{ {
struct snd_soc_dai *dai; struct snd_soc_dai *dai;
int ret, j; int ret, j;
...@@ -143,14 +119,12 @@ static int sof_pcm_hw_params(struct snd_soc_component *component, ...@@ -143,14 +119,12 @@ static int sof_pcm_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *substream, struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params) 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_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 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 snd_sof_pcm *spcm;
struct sof_ipc_pcm_params pcm;
struct sof_ipc_pcm_params_reply ipc_params_reply;
int ret; int ret;
/* nothing to do for BE */ /* nothing to do for BE */
...@@ -165,124 +139,51 @@ static int sof_pcm_hw_params(struct snd_soc_component *component, ...@@ -165,124 +139,51 @@ static int sof_pcm_hw_params(struct snd_soc_component *component,
* Handle repeated calls to hw_params() without free_pcm() in * Handle repeated calls to hw_params() without free_pcm() in
* between. At least ALSA OSS emulation depends on this. * between. At least ALSA OSS emulation depends on this.
*/ */
ret = sof_pcm_dsp_pcm_free(substream, sdev, spcm); if (pcm_ops->hw_free && spcm->prepared[substream->stream]) {
if (ret < 0) ret = pcm_ops->hw_free(component, substream);
return ret; if (ret < 0)
return ret;
spcm->prepared[substream->stream] = false;
}
dev_dbg(component->dev, "pcm: hw params stream %d dir %d\n", dev_dbg(component->dev, "pcm: hw params stream %d dir %d\n",
spcm->pcm.pcm_id, substream->stream); spcm->pcm.pcm_id, substream->stream);
memset(&pcm, 0, sizeof(pcm)); /* 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);
if (ret < 0)
return ret;
}
/* create compressed page table for audio firmware */ /* create compressed page table for audio firmware */
if (runtime->buffer_changed) { if (runtime->buffer_changed) {
ret = create_page_table(component, substream, runtime->dma_area, ret = create_page_table(component, substream, runtime->dma_area,
runtime->dma_bytes); runtime->dma_bytes);
if (ret < 0) if (ret < 0)
return ret; return ret;
} }
/* number of pages should be rounded up */ ret = snd_sof_pcm_platform_hw_params(sdev, substream, params, &platform_params);
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) { if (ret < 0) {
dev_err(component->dev, "error: platform hw params failed\n"); dev_err(component->dev, "platform hw params failed\n");
return ret; return ret;
} }
/* Update the IPC message with information from the platform */ if (pcm_ops->hw_params) {
pcm.params.stream_tag = platform_params.stream_tag; ret = pcm_ops->hw_params(component, substream, params, &platform_params);
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);
if (ret < 0) if (ret < 0)
return ret; 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));
if (ret < 0) {
dev_err(component->dev, "error: 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;
}
spcm->prepared[substream->stream] = true; spcm->prepared[substream->stream] = true;
/* save pcm hw_params */ /* save pcm hw_params */
memcpy(&spcm->params[substream->stream], params, sizeof(*params)); memcpy(&spcm->params[substream->stream], params, sizeof(*params));
return ret; return 0;
} }
static int sof_pcm_hw_free(struct snd_soc_component *component, 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, ...@@ -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_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); 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 snd_sof_pcm *spcm;
int ret, err = 0; int ret, err = 0;
...@@ -305,10 +207,13 @@ static int sof_pcm_hw_free(struct snd_soc_component *component, ...@@ -305,10 +207,13 @@ static int sof_pcm_hw_free(struct snd_soc_component *component,
spcm->pcm.pcm_id, substream->stream); spcm->pcm.pcm_id, substream->stream);
/* free PCM in the DSP */ /* free PCM in the DSP */
ret = sof_pcm_dsp_pcm_free(substream, sdev, spcm); if (pcm_ops->hw_free && spcm->prepared[substream->stream]) {
if (ret < 0) ret = pcm_ops->hw_free(component, substream);
err = ret; if (ret < 0)
err = ret;
spcm->prepared[substream->stream] = false;
}
/* stop DMA */ /* stop DMA */
ret = snd_sof_pcm_platform_hw_free(sdev, substream); ret = snd_sof_pcm_platform_hw_free(sdev, substream);
...@@ -369,13 +274,12 @@ static int sof_pcm_trigger(struct snd_soc_component *component, ...@@ -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_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); 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 snd_sof_pcm *spcm;
struct sof_ipc_stream stream;
struct sof_ipc_reply reply;
bool reset_hw_params = false; bool reset_hw_params = false;
bool free_widget_list = false; bool free_widget_list = false;
bool ipc_first = false; bool ipc_first = false;
int ret; int ret = 0;
/* nothing to do for BE */ /* nothing to do for BE */
if (rtd->dai_link->no_pcm) if (rtd->dai_link->no_pcm)
...@@ -388,17 +292,11 @@ static int sof_pcm_trigger(struct snd_soc_component *component, ...@@ -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", dev_dbg(component->dev, "pcm: trigger stream %d dir %d cmd %d\n",
spcm->pcm.pcm_id, substream->stream, cmd); 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) { switch (cmd) {
case SNDRV_PCM_TRIGGER_PAUSE_PUSH: case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_PAUSE;
ipc_first = true; ipc_first = true;
break; break;
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_RELEASE;
break; break;
case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_START:
if (spcm->stream[substream->stream].suspend_ignored) { if (spcm->stream[substream->stream].suspend_ignored) {
...@@ -410,7 +308,6 @@ static int sof_pcm_trigger(struct snd_soc_component *component, ...@@ -410,7 +308,6 @@ static int sof_pcm_trigger(struct snd_soc_component *component,
spcm->stream[substream->stream].suspend_ignored = false; spcm->stream[substream->stream].suspend_ignored = false;
return 0; return 0;
} }
stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_START;
break; break;
case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_SUSPEND:
if (sdev->system_suspend_target == SOF_SUSPEND_S0IX && if (sdev->system_suspend_target == SOF_SUSPEND_S0IX &&
...@@ -427,13 +324,11 @@ static int sof_pcm_trigger(struct snd_soc_component *component, ...@@ -427,13 +324,11 @@ static int sof_pcm_trigger(struct snd_soc_component *component,
free_widget_list = true; free_widget_list = true;
fallthrough; fallthrough;
case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_STOP:
stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_STOP;
ipc_first = true; ipc_first = true;
reset_hw_params = true; reset_hw_params = true;
break; break;
default: default:
dev_err(component->dev, "error: unhandled trigger cmd %d\n", dev_err(component->dev, "Unhandled trigger cmd %d\n", cmd);
cmd);
return -EINVAL; return -EINVAL;
} }
...@@ -444,11 +339,10 @@ static int sof_pcm_trigger(struct snd_soc_component *component, ...@@ -444,11 +339,10 @@ static int sof_pcm_trigger(struct snd_soc_component *component,
if (!ipc_first) if (!ipc_first)
snd_sof_pcm_platform_trigger(sdev, substream, cmd); snd_sof_pcm_platform_trigger(sdev, substream, cmd);
/* send IPC to the DSP */ if (pcm_ops->trigger)
ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream, ret = pcm_ops->trigger(component, substream, cmd);
sizeof(stream), &reply, sizeof(reply));
/* need to STOP DMA even if STOP IPC failed */ /* need to STOP DMA even if trigger IPC failed */
if (ipc_first) if (ipc_first)
snd_sof_pcm_platform_trigger(sdev, substream, cmd); snd_sof_pcm_platform_trigger(sdev, substream, cmd);
...@@ -662,33 +556,6 @@ static int sof_pcm_new(struct snd_soc_component *component, ...@@ -662,33 +556,6 @@ static int sof_pcm_new(struct snd_soc_component *component,
return 0; 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 */ /* 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) 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 ...@@ -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 = struct snd_sof_dai *dai =
snd_sof_find_dai(component, (char *)rtd->dai_link->name); snd_sof_find_dai(component, (char *)rtd->dai_link->name);
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component); struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
struct sof_dai_private_data *private = dai->private; const struct sof_ipc_pcm_ops *pcm_ops = sdev->ipc->ops->pcm;
struct snd_soc_dpcm *dpcm;
/* no topology exists for this BE, try a common configuration */ /* no topology exists for this BE, try a common configuration */
if (!dai) { if (!dai) {
...@@ -724,148 +590,8 @@ int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_pa ...@@ -724,148 +590,8 @@ int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_pa
return 0; return 0;
} }
/* read format from topology */ if (pcm_ops->dai_link_fixup)
snd_mask_none(fmt); return pcm_ops->dai_link_fixup(rtd, params);
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;
}
return 0; return 0;
} }
......
...@@ -48,22 +48,6 @@ static u32 snd_sof_dsp_power_target(struct snd_sof_dev *sdev) ...@@ -48,22 +48,6 @@ static u32 snd_sof_dsp_power_target(struct snd_sof_dev *sdev)
return target_dsp_state; 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) #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE)
static void sof_cache_debugfs(struct snd_sof_dev *sdev) static void sof_cache_debugfs(struct snd_sof_dev *sdev)
{ {
...@@ -86,6 +70,8 @@ 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) static int sof_resume(struct device *dev, bool runtime_resume)
{ {
struct snd_sof_dev *sdev = dev_get_drvdata(dev); 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; u32 old_state = sdev->dsp_power_state.state;
int ret; int ret;
...@@ -159,23 +145,23 @@ static int sof_resume(struct device *dev, bool runtime_resume) ...@@ -159,23 +145,23 @@ static int sof_resume(struct device *dev, bool runtime_resume)
} }
/* restore pipelines */ /* restore pipelines */
ret = sof_set_up_pipelines(sdev, false); if (tplg_ops->set_up_all_pipelines) {
if (ret < 0) { ret = tplg_ops->set_up_all_pipelines(sdev, false);
dev_err(sdev->dev, if (ret < 0) {
"error: failed to restore pipeline after resume %d\n", dev_err(sdev->dev, "Failed to restore pipeline after resume %d\n", ret);
ret); return ret;
return ret; }
} }
/* Notify clients not managed by pm framework about core resume */ /* Notify clients not managed by pm framework about core resume */
sof_resume_clients(sdev); sof_resume_clients(sdev);
/* notify DSP of system resume */ /* notify DSP of system resume */
ret = sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_RESTORE); if (pm_ops && pm_ops->ctx_restore) {
if (ret < 0) ret = pm_ops->ctx_restore(sdev);
dev_err(sdev->dev, if (ret < 0)
"error: ctx_restore ipc error during resume %d\n", dev_err(sdev->dev, "ctx_restore IPC error during resume: %d\n", ret);
ret); }
return ret; return ret;
} }
...@@ -183,6 +169,8 @@ static int sof_resume(struct device *dev, bool runtime_resume) ...@@ -183,6 +169,8 @@ static int sof_resume(struct device *dev, bool runtime_resume)
static int sof_suspend(struct device *dev, bool runtime_suspend) static int sof_suspend(struct device *dev, bool runtime_suspend)
{ {
struct snd_sof_dev *sdev = dev_get_drvdata(dev); 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; pm_message_t pm_state;
u32 target_state = 0; u32 target_state = 0;
int ret; int ret;
...@@ -218,7 +206,8 @@ static int sof_suspend(struct device *dev, bool runtime_suspend) ...@@ -218,7 +206,8 @@ static int sof_suspend(struct device *dev, bool runtime_suspend)
goto 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 */ /* release trace */
snd_sof_release_trace(sdev); snd_sof_release_trace(sdev);
...@@ -232,21 +221,20 @@ static int sof_suspend(struct device *dev, bool runtime_suspend) ...@@ -232,21 +221,20 @@ static int sof_suspend(struct device *dev, bool runtime_suspend)
sof_cache_debugfs(sdev); sof_cache_debugfs(sdev);
#endif #endif
/* notify DSP of upcoming power down */ /* notify DSP of upcoming power down */
ret = sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_SAVE); if (pm_ops && pm_ops->ctx_save) {
if (ret == -EBUSY || ret == -EAGAIN) { ret = pm_ops->ctx_save(sdev);
/* if (ret == -EBUSY || ret == -EAGAIN) {
* runtime PM has logic to handle -EBUSY/-EAGAIN so /*
* pass these errors up * 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", dev_err(sdev->dev, "ctx_save IPC error during suspend: %d\n", ret);
ret); return ret;
return ret; } else if (ret < 0) {
} else if (ret < 0) { /* FW in unexpected state, continue to power down */
/* FW in unexpected state, continue to power down */ dev_warn(sdev->dev, "ctx_save IPC error: %d, proceeding with suspend\n",
dev_warn(sdev->dev, ret);
"ctx_save ipc error %d, proceeding with suspend\n", }
ret);
} }
suspend: suspend:
...@@ -278,9 +266,11 @@ static int sof_suspend(struct device *dev, bool runtime_suspend) ...@@ -278,9 +266,11 @@ static int sof_suspend(struct device *dev, bool runtime_suspend)
int snd_sof_dsp_power_down_notify(struct snd_sof_dev *sdev) 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 */ /* Notify DSP of upcoming power down */
if (sof_ops(sdev)->remove) if (sof_ops(sdev)->remove && pm_ops && pm_ops->ctx_save)
return sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_SAVE); return pm_ops->ctx_save(sdev);
return 0; return 0;
} }
......
...@@ -27,31 +27,6 @@ static int sof_kcontrol_setup(struct snd_sof_dev *sdev, struct snd_sof_control * ...@@ -27,31 +27,6 @@ static int sof_kcontrol_setup(struct snd_sof_dev *sdev, struct snd_sof_control *
return ret; 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) static int sof_widget_kcontrol_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
{ {
struct snd_sof_control *scontrol; struct snd_sof_control *scontrol;
...@@ -96,15 +71,9 @@ static void sof_reset_route_setup_status(struct snd_sof_dev *sdev, struct snd_so ...@@ -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) int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
{ {
struct sof_ipc_free ipc_free = { const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
.hdr = { int err = 0;
.size = sizeof(ipc_free), int ret;
.cmd = SOF_IPC_GLB_TPLG_MSG,
},
.id = swidget->comp_id,
};
struct sof_ipc_reply reply;
int ret, ret1;
if (!swidget->private) if (!swidget->private)
return 0; return 0;
...@@ -113,64 +82,46 @@ int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) ...@@ -113,64 +82,46 @@ int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
if (--swidget->use_count) if (--swidget->use_count)
return 0; 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 */ /* continue to disable core even if IPC fails */
ret = sof_ipc_tx_message(sdev->ipc, ipc_free.hdr.cmd, &ipc_free, sizeof(ipc_free), if (tplg_ops->widget_free)
&reply, sizeof(reply)); err = tplg_ops->widget_free(sdev, swidget);
if (ret < 0)
dev_err(sdev->dev, "error: failed to free widget %s\n", swidget->widget->name);
/* /*
* disable widget core. continue to route setup status and complete flag * disable widget core. continue to route setup status and complete flag
* even if this fails and return the appropriate error * even if this fails and return the appropriate error
*/ */
ret1 = snd_sof_dsp_core_put(sdev, swidget->core); ret = snd_sof_dsp_core_put(sdev, swidget->core);
if (ret1 < 0) { if (ret < 0) {
dev_err(sdev->dev, "error: failed to disable target core: %d for widget %s\n", dev_err(sdev->dev, "error: failed to disable target core: %d for widget %s\n",
swidget->core, swidget->widget->name); swidget->core, swidget->widget->name);
if (!ret) if (!err)
ret = ret1; err = ret;
} }
/* reset route setup status for all routes that contain this widget */ /* reset route setup status for all routes that contain this widget */
sof_reset_route_setup_status(sdev, swidget); sof_reset_route_setup_status(sdev, swidget);
swidget->complete = 0; 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); dev_dbg(sdev->dev, "widget %s freed\n", swidget->widget->name);
return ret; return err;
} }
EXPORT_SYMBOL(sof_widget_free); EXPORT_SYMBOL(sof_widget_free);
int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
{ {
struct sof_ipc_pipe_new *pipeline; const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
struct sof_ipc_comp_reply r;
struct sof_ipc_cmd_hdr *hdr;
struct sof_ipc_comp *comp;
struct snd_sof_dai *dai;
int ret; int ret;
/* skip if there is no private data */ /* 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) ...@@ -181,61 +132,50 @@ int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
if (++swidget->use_count > 1) if (++swidget->use_count > 1)
return 0; 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 */ /* enable widget core */
ret = snd_sof_dsp_core_get(sdev, swidget->core); ret = snd_sof_dsp_core_get(sdev, swidget->core);
if (ret < 0) { if (ret < 0) {
dev_err(sdev->dev, "error: failed to enable target core for widget %s\n", dev_err(sdev->dev, "error: failed to enable target core for widget %s\n",
swidget->widget->name); swidget->widget->name);
goto use_count_dec; goto pipe_widget_free;
} }
switch (swidget->id) { /* setup widget in the DSP */
case snd_soc_dapm_dai_in: if (tplg_ops->widget_setup) {
case snd_soc_dapm_dai_out: ret = tplg_ops->widget_setup(sdev, swidget);
{ if (ret < 0)
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);
goto core_put; goto core_put;
} }
ret = sof_dai_config_setup(sdev, dai); /* send config for DAI components */
if (ret < 0) { if (WIDGET_IS_DAI(swidget->id)) {
dev_err(sdev->dev, "error: failed to load dai config for DAI %s\n", unsigned int flags = SOF_DAI_CONFIG_FLAGS_NONE;
swidget->widget->name);
/* if (tplg_ops->dai_config) {
* widget use_count and core ref_count will both be decremented by ret = tplg_ops->dai_config(sdev, swidget, flags, NULL);
* sof_widget_free() if (ret < 0)
*/ goto 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;
} }
/* restore kcontrols for widget */ /* restore kcontrols for widget */
...@@ -243,28 +183,29 @@ int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget) ...@@ -243,28 +183,29 @@ int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
if (ret < 0) { if (ret < 0) {
dev_err(sdev->dev, "error: failed to restore kcontrols for widget %s\n", dev_err(sdev->dev, "error: failed to restore kcontrols for widget %s\n",
swidget->widget->name); swidget->widget->name);
/* goto widget_free;
* widget use_count and core ref_count will both be decremented by
* sof_widget_free()
*/
sof_widget_free(sdev, swidget);
return ret;
} }
dev_dbg(sdev->dev, "widget %s setup complete\n", swidget->widget->name); dev_dbg(sdev->dev, "widget %s setup complete\n", swidget->widget->name);
return 0; 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: core_put:
snd_sof_dsp_core_put(sdev, swidget->core); 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: use_count_dec:
swidget->use_count--; swidget->use_count--;
return ret; return ret;
} }
EXPORT_SYMBOL(sof_widget_setup); EXPORT_SYMBOL(sof_widget_setup);
static int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsource, int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsource,
struct snd_soc_dapm_widget *wsink) struct snd_soc_dapm_widget *wsink)
{ {
const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg; const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg;
struct snd_sof_widget *src_widget = wsource->dobj.private; 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 ...@@ -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 */ /* set up widgets in the list */
for_each_dapm_widgets(list, num_widgets, widget) { for_each_dapm_widgets(list, num_widgets, widget) {
struct snd_sof_widget *swidget = widget->dobj.private; struct snd_sof_widget *swidget = widget->dobj.private;
struct snd_sof_widget *pipe_widget;
if (!swidget) if (!swidget)
continue; 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 */ /* set up the widget */
ret = sof_widget_setup(sdev, swidget); ret = sof_widget_setup(sdev, swidget);
if (ret < 0) { if (ret < 0)
sof_widget_free(sdev, pipe_widget);
goto widget_free; goto widget_free;
}
} }
/* /*
...@@ -456,7 +375,6 @@ int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, in ...@@ -456,7 +375,6 @@ int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, in
break; break;
sof_widget_free(sdev, swidget); sof_widget_free(sdev, swidget);
sof_widget_free(sdev, swidget->pipe_widget);
} }
return ret; return ret;
...@@ -490,10 +408,6 @@ int sof_widget_list_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int ...@@ -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); ret = sof_widget_free(sdev, swidget);
if (ret < 0) if (ret < 0)
ret1 = ret; ret1 = ret;
ret = sof_widget_free(sdev, swidget->pipe_widget);
if (ret < 0)
ret1 = ret;
} }
snd_soc_dapm_dai_free_widgets(&list); snd_soc_dapm_dai_free_widgets(&list);
...@@ -584,105 +498,20 @@ int sof_set_hw_params_upon_resume(struct device *dev) ...@@ -584,105 +498,20 @@ int sof_set_hw_params_upon_resume(struct device *dev)
return snd_sof_dsp_hw_params_upon_resume(sdev); 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; const struct sof_ipc_pcm_ops *pcm_ops = sdev->ipc->ops->pcm;
struct sof_ipc_fw_version *v = &sdev->fw_ready.version;
struct snd_sof_widget *swidget;
struct snd_sof_route *sroute;
int ret; int ret;
/* restore pipeline components */ /* Send PCM_FREE IPC to reset pipeline */
list_for_each_entry(swidget, &sdev->widget_list, list) { if (pcm_ops->hw_free && spcm->prepared[substream->stream]) {
/* only set up the widgets belonging to static pipelines */ ret = pcm_ops->hw_free(sdev->component, substream);
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);
if (ret < 0) if (ret < 0)
return ret; return ret;
} }
/* restore pipeline connections */ spcm->prepared[substream->stream] = false;
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;
/* stop the DMA */ /* stop the DMA */
ret = snd_sof_pcm_platform_hw_free(sdev, substream); 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 ...@@ -699,102 +528,6 @@ int sof_pcm_stream_free(struct snd_sof_dev *sdev, struct snd_pcm_substream *subs
return ret; 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. * Generic object lookup APIs.
*/ */
...@@ -895,40 +628,23 @@ struct snd_sof_dai *snd_sof_find_dai(struct snd_soc_component *scomp, ...@@ -895,40 +628,23 @@ struct snd_sof_dai *snd_sof_find_dai(struct snd_soc_component *scomp,
return NULL; 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) static int sof_dai_get_clk(struct snd_soc_pcm_runtime *rtd, int clk_type)
{ {
struct snd_soc_component *component = struct snd_soc_component *component =
snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME); snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME);
struct snd_sof_dai *dai = struct snd_sof_dai *dai =
snd_sof_find_dai(component, (char *)rtd->dai_link->name); 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 */ /* use the tplg configured mclk if existed */
if (!dai || !private || !private->dai_config) if (!dai)
return 0; return 0;
switch (private->dai_config->type) { if (tplg_ops->dai_get_clk)
case SOF_DAI_INTEL_SSP: return tplg_ops->dai_get_clk(sdev, dai, clk_type);
switch (clk_type) {
case SOF_DAI_CLK_INTEL_SSP_MCLK: return 0;
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;
}
} }
/* /*
......
...@@ -30,6 +30,9 @@ ...@@ -30,6 +30,9 @@
#define WIDGET_IS_DAI(id) ((id) == snd_soc_dapm_dai_in || (id) == snd_soc_dapm_dai_out) #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 * Volume fractional word length define to 16 sets
* the volume linear gain value to use Qx.16 format * the volume linear gain value to use Qx.16 format
...@@ -39,6 +42,51 @@ ...@@ -39,6 +42,51 @@
struct snd_sof_widget; struct snd_sof_widget;
struct snd_sof_route; struct snd_sof_route;
struct snd_sof_control; 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 * struct sof_ipc_tplg_widget_ops - IPC-specific ops for topology widgets
...@@ -62,6 +110,7 @@ struct sof_ipc_tplg_widget_ops { ...@@ -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 * @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 * SND_SOF_DAPM_TYPE_COUNT i.e one per widget type. Unsupported widget types will be
* initialized to 0. * initialized to 0.
* @control: Pointer to the IPC-specific ops for topology kcontrol IO
* @route_setup: Function pointer for setting up pipeline connections * @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 * @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 * array should be SOF_TOKEN_COUNT. The unused elements in the array will be
...@@ -69,14 +118,28 @@ struct sof_ipc_tplg_widget_ops { ...@@ -69,14 +118,28 @@ struct sof_ipc_tplg_widget_ops {
* @control_setup: Function pointer for setting up kcontrol IPC-specific data * @control_setup: Function pointer for setting up kcontrol IPC-specific data
* @control_free: Function pointer for freeing kcontrol IPC-specific data * @control_free: Function pointer for freeing kcontrol IPC-specific data
* @pipeline_complete: Function pointer for pipeline complete IPC * @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 { struct sof_ipc_tplg_ops {
const struct sof_ipc_tplg_widget_ops *widget; 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); int (*route_setup)(struct snd_sof_dev *sdev, struct snd_sof_route *sroute);
const struct sof_token_info *token_list; const struct sof_token_info *token_list;
int (*control_setup)(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol); 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 (*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 (*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 /** struct snd_sof_tuple - Tuple info
...@@ -276,7 +339,6 @@ struct snd_sof_dai { ...@@ -276,7 +339,6 @@ struct snd_sof_dai {
int number_configs; int number_configs;
int current_config; int current_config;
bool configured; /* DAI configured during BE hw_params */
struct list_head list; /* list in sdev dai list */ struct list_head list; /* list in sdev dai list */
void *private; void *private;
}; };
...@@ -377,8 +439,6 @@ int snd_sof_ipc_set_get_comp_data(struct snd_sof_control *scontrol, bool set); ...@@ -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); int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params);
/* PM */ /* 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); int sof_set_hw_params_upon_resume(struct device *dev);
bool snd_sof_stream_suspend_ignored(struct snd_sof_dev *sdev); 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); 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); ...@@ -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_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_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 */ /* PCM */
int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir); 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); ...@@ -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, 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, struct snd_sof_tuple *tuples, int num_tuples,
size_t object_size, int token_instance_num); 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 #endif
...@@ -360,18 +360,31 @@ struct snd_sof_ipc_msg { ...@@ -360,18 +360,31 @@ struct snd_sof_ipc_msg {
bool ipc_complete; 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_tplg_ops;
struct sof_ipc_pcm_ops;
/** /**
* struct sof_ipc_ops - IPC-specific ops * struct sof_ipc_ops - IPC-specific ops
* @tplg: Pointer to IPC-specific topology ops * @tplg: Pointer to IPC-specific topology ops
* @pm: Pointer to PM ops
* @pcm: Pointer to PCM ops
*/ */
struct sof_ipc_ops { struct sof_ipc_ops {
const struct sof_ipc_tplg_ops *tplg; 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 */ /* SOF generic IPC data */
struct snd_sof_ipc { struct snd_sof_ipc {
struct snd_sof_dev *sdev; struct snd_sof_dev *sdev;
......
...@@ -1796,29 +1796,15 @@ static int sof_route_load(struct snd_soc_component *scomp, int index, ...@@ -1796,29 +1796,15 @@ static int sof_route_load(struct snd_soc_component *scomp, int index,
sink_swidget->id == snd_soc_dapm_output) sink_swidget->id == snd_soc_dapm_output)
goto err; goto err;
/* sroute->route = route;
* For virtual routes, both sink and source are not dobj->private = sroute;
* buffer. Since only buffer linked to component is supported by sroute->src_widget = source_swidget;
* FW, others are reported as error, add check in route function, sroute->sink_widget = sink_swidget;
* 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;
/* 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: err:
kfree(sroute); kfree(sroute);
return ret; return ret;
...@@ -1917,21 +1903,28 @@ static int sof_complete(struct snd_soc_component *scomp) ...@@ -1917,21 +1903,28 @@ static int sof_complete(struct snd_soc_component *scomp)
/* verify topology components loading including dynamic pipelines */ /* verify topology components loading including dynamic pipelines */
if (sof_debug_check_flag(SOF_DBG_VERIFY_TPLG)) { if (sof_debug_check_flag(SOF_DBG_VERIFY_TPLG)) {
ret = sof_set_up_pipelines(sdev, true); if (ipc_tplg_ops->set_up_all_pipelines && ipc_tplg_ops->tear_down_all_pipelines) {
if (ret < 0) { ret = ipc_tplg_ops->set_up_all_pipelines(sdev, true);
dev_err(sdev->dev, "error: topology verification failed %d\n", ret); if (ret < 0) {
return ret; dev_err(sdev->dev, "Failed to set up all topology pipelines: %d\n",
} ret);
return ret;
}
ret = sof_tear_down_pipelines(sdev, true); ret = ipc_tplg_ops->tear_down_all_pipelines(sdev, true);
if (ret < 0) { if (ret < 0) {
dev_err(sdev->dev, "error: topology tear down pipelines failed %d\n", ret); dev_err(sdev->dev, "Failed to tear down topology pipelines: %d\n",
return ret; ret);
return ret;
}
} }
} }
/* set up static pipelines */ /* 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 */ /* manifest - optional to inform component of manifest */
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册