forked from Minki/linux
Make the SOF control, PCM and PM code IPC agnostic
Merge series from Ranjani Sridharan <ranjani.sridharan@linux.intel.com>: This series is a continuation to the SOF IPC abstraction work to support the new IPC version introduced in the SOF firmware. It makes the top-level control IO, PCM and PM code IPC-agnostic. Other than the first patch, the rest are purely for abstraction and include no changes in functionality.
This commit is contained in:
commit
49a24e9d9c
@ -2,7 +2,7 @@
|
||||
|
||||
snd-sof-objs := core.o ops.o loader.o ipc.o pcm.o pm.o debug.o topology.o\
|
||||
control.o trace.o iomem-utils.o sof-audio.o stream-ipc.o\
|
||||
ipc3-topology.o
|
||||
ipc3-topology.o ipc3.o ipc3-control.o ipc3-pcm.o
|
||||
ifneq ($(CONFIG_SND_SOC_SOF_CLIENT),)
|
||||
snd-sof-objs += sof-client.o
|
||||
endif
|
||||
|
@ -45,68 +45,17 @@ static void update_mute_led(struct snd_sof_control *scontrol,
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline u32 mixer_to_ipc(unsigned int value, u32 *volume_map, int size)
|
||||
{
|
||||
if (value >= size)
|
||||
return volume_map[size - 1];
|
||||
|
||||
return volume_map[value];
|
||||
}
|
||||
|
||||
static inline u32 ipc_to_mixer(u32 value, u32 *volume_map, int size)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < size; i++) {
|
||||
if (volume_map[i] >= value)
|
||||
return i;
|
||||
}
|
||||
|
||||
return i - 1;
|
||||
}
|
||||
|
||||
static void snd_sof_refresh_control(struct snd_sof_control *scontrol)
|
||||
{
|
||||
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
|
||||
struct snd_soc_component *scomp = scontrol->scomp;
|
||||
int ret;
|
||||
|
||||
if (!scontrol->comp_data_dirty)
|
||||
return;
|
||||
|
||||
if (!pm_runtime_active(scomp->dev))
|
||||
return;
|
||||
|
||||
/* set the ABI header values */
|
||||
cdata->data->magic = SOF_ABI_MAGIC;
|
||||
cdata->data->abi = SOF_ABI_VERSION;
|
||||
|
||||
/* refresh the component data from DSP */
|
||||
scontrol->comp_data_dirty = false;
|
||||
ret = snd_sof_ipc_set_get_comp_data(scontrol, false);
|
||||
if (ret < 0) {
|
||||
dev_err(scomp->dev, "error: failed to get control data: %d\n", ret);
|
||||
/* Set the flag to re-try next time to get the data */
|
||||
scontrol->comp_data_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
int snd_sof_volume_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct soc_mixer_control *sm =
|
||||
(struct soc_mixer_control *)kcontrol->private_value;
|
||||
struct soc_mixer_control *sm = (struct soc_mixer_control *)kcontrol->private_value;
|
||||
struct snd_sof_control *scontrol = sm->dobj.private;
|
||||
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
|
||||
unsigned int i, channels = scontrol->num_channels;
|
||||
struct snd_soc_component *scomp = scontrol->scomp;
|
||||
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
|
||||
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
|
||||
|
||||
snd_sof_refresh_control(scontrol);
|
||||
|
||||
/* read back each channel */
|
||||
for (i = 0; i < channels; i++)
|
||||
ucontrol->value.integer.value[i] =
|
||||
ipc_to_mixer(cdata->chanv[i].value,
|
||||
scontrol->volume_table, sm->max + 1);
|
||||
if (tplg_ops->control->volume_get)
|
||||
return tplg_ops->control->volume_get(scontrol, ucontrol);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -114,28 +63,16 @@ int snd_sof_volume_get(struct snd_kcontrol *kcontrol,
|
||||
int snd_sof_volume_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct soc_mixer_control *sm =
|
||||
(struct soc_mixer_control *)kcontrol->private_value;
|
||||
struct soc_mixer_control *sm = (struct soc_mixer_control *)kcontrol->private_value;
|
||||
struct snd_sof_control *scontrol = sm->dobj.private;
|
||||
struct snd_soc_component *scomp = scontrol->scomp;
|
||||
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
|
||||
unsigned int i, channels = scontrol->num_channels;
|
||||
bool change = false;
|
||||
u32 value;
|
||||
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
|
||||
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
|
||||
|
||||
/* update each channel */
|
||||
for (i = 0; i < channels; i++) {
|
||||
value = mixer_to_ipc(ucontrol->value.integer.value[i],
|
||||
scontrol->volume_table, sm->max + 1);
|
||||
change = change || (value != cdata->chanv[i].value);
|
||||
cdata->chanv[i].channel = i;
|
||||
cdata->chanv[i].value = value;
|
||||
}
|
||||
if (tplg_ops->control->volume_put)
|
||||
return tplg_ops->control->volume_put(scontrol, ucontrol);
|
||||
|
||||
/* notify DSP of mixer updates */
|
||||
if (pm_runtime_active(scomp->dev))
|
||||
snd_sof_ipc_set_get_comp_data(scontrol, true);
|
||||
return change;
|
||||
return false;
|
||||
}
|
||||
|
||||
int snd_sof_volume_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
|
||||
@ -163,17 +100,14 @@ int snd_sof_volume_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info
|
||||
int snd_sof_switch_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct soc_mixer_control *sm =
|
||||
(struct soc_mixer_control *)kcontrol->private_value;
|
||||
struct soc_mixer_control *sm = (struct soc_mixer_control *)kcontrol->private_value;
|
||||
struct snd_sof_control *scontrol = sm->dobj.private;
|
||||
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
|
||||
unsigned int i, channels = scontrol->num_channels;
|
||||
struct snd_soc_component *scomp = scontrol->scomp;
|
||||
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
|
||||
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
|
||||
|
||||
snd_sof_refresh_control(scontrol);
|
||||
|
||||
/* read back each channel */
|
||||
for (i = 0; i < channels; i++)
|
||||
ucontrol->value.integer.value[i] = cdata->chanv[i].value;
|
||||
if (tplg_ops->control->switch_get)
|
||||
return tplg_ops->control->switch_get(scontrol, ucontrol);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -181,47 +115,32 @@ int snd_sof_switch_get(struct snd_kcontrol *kcontrol,
|
||||
int snd_sof_switch_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct soc_mixer_control *sm =
|
||||
(struct soc_mixer_control *)kcontrol->private_value;
|
||||
struct soc_mixer_control *sm = (struct soc_mixer_control *)kcontrol->private_value;
|
||||
struct snd_sof_control *scontrol = sm->dobj.private;
|
||||
struct snd_soc_component *scomp = scontrol->scomp;
|
||||
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
|
||||
unsigned int i, channels = scontrol->num_channels;
|
||||
bool change = false;
|
||||
u32 value;
|
||||
|
||||
/* update each channel */
|
||||
for (i = 0; i < channels; i++) {
|
||||
value = ucontrol->value.integer.value[i];
|
||||
change = change || (value != cdata->chanv[i].value);
|
||||
cdata->chanv[i].channel = i;
|
||||
cdata->chanv[i].value = value;
|
||||
}
|
||||
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
|
||||
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
|
||||
|
||||
if (scontrol->led_ctl.use_led)
|
||||
update_mute_led(scontrol, kcontrol, ucontrol);
|
||||
|
||||
/* notify DSP of mixer updates */
|
||||
if (pm_runtime_active(scomp->dev))
|
||||
snd_sof_ipc_set_get_comp_data(scontrol, true);
|
||||
if (tplg_ops->control->switch_put)
|
||||
return tplg_ops->control->switch_put(scontrol, ucontrol);
|
||||
|
||||
return change;
|
||||
return false;
|
||||
}
|
||||
|
||||
int snd_sof_enum_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct soc_enum *se =
|
||||
(struct soc_enum *)kcontrol->private_value;
|
||||
struct soc_enum *se = (struct soc_enum *)kcontrol->private_value;
|
||||
struct snd_sof_control *scontrol = se->dobj.private;
|
||||
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
|
||||
unsigned int i, channels = scontrol->num_channels;
|
||||
struct snd_soc_component *scomp = scontrol->scomp;
|
||||
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
|
||||
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
|
||||
|
||||
snd_sof_refresh_control(scontrol);
|
||||
|
||||
/* read back each channel */
|
||||
for (i = 0; i < channels; i++)
|
||||
ucontrol->value.enumerated.item[i] = cdata->chanv[i].value;
|
||||
if (tplg_ops->control->enum_get)
|
||||
return tplg_ops->control->enum_get(scontrol, ucontrol);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -229,62 +148,29 @@ int snd_sof_enum_get(struct snd_kcontrol *kcontrol,
|
||||
int snd_sof_enum_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct soc_enum *se =
|
||||
(struct soc_enum *)kcontrol->private_value;
|
||||
struct soc_enum *se = (struct soc_enum *)kcontrol->private_value;
|
||||
struct snd_sof_control *scontrol = se->dobj.private;
|
||||
struct snd_soc_component *scomp = scontrol->scomp;
|
||||
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
|
||||
unsigned int i, channels = scontrol->num_channels;
|
||||
bool change = false;
|
||||
u32 value;
|
||||
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
|
||||
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
|
||||
|
||||
/* update each channel */
|
||||
for (i = 0; i < channels; i++) {
|
||||
value = ucontrol->value.enumerated.item[i];
|
||||
change = change || (value != cdata->chanv[i].value);
|
||||
cdata->chanv[i].channel = i;
|
||||
cdata->chanv[i].value = value;
|
||||
}
|
||||
if (tplg_ops->control->enum_put)
|
||||
return tplg_ops->control->enum_put(scontrol, ucontrol);
|
||||
|
||||
/* notify DSP of enum updates */
|
||||
if (pm_runtime_active(scomp->dev))
|
||||
snd_sof_ipc_set_get_comp_data(scontrol, true);
|
||||
|
||||
return change;
|
||||
return false;
|
||||
}
|
||||
|
||||
int snd_sof_bytes_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct soc_bytes_ext *be =
|
||||
(struct soc_bytes_ext *)kcontrol->private_value;
|
||||
struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value;
|
||||
struct snd_sof_control *scontrol = be->dobj.private;
|
||||
struct snd_soc_component *scomp = scontrol->scomp;
|
||||
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
|
||||
struct sof_abi_hdr *data = cdata->data;
|
||||
size_t size;
|
||||
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
|
||||
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
|
||||
|
||||
snd_sof_refresh_control(scontrol);
|
||||
|
||||
if (be->max > sizeof(ucontrol->value.bytes.data)) {
|
||||
dev_err_ratelimited(scomp->dev,
|
||||
"error: data max %d exceeds ucontrol data array size\n",
|
||||
be->max);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* be->max has been verified to be >= sizeof(struct sof_abi_hdr) */
|
||||
if (data->size > be->max - sizeof(*data)) {
|
||||
dev_err_ratelimited(scomp->dev,
|
||||
"error: %u bytes of control data is invalid, max is %zu\n",
|
||||
data->size, be->max - sizeof(*data));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
size = data->size + sizeof(*data);
|
||||
|
||||
/* copy back to kcontrol */
|
||||
memcpy(ucontrol->value.bytes.data, data, size);
|
||||
if (tplg_ops->control->bytes_get)
|
||||
return tplg_ops->control->bytes_get(scontrol, ucontrol);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -292,37 +178,14 @@ int snd_sof_bytes_get(struct snd_kcontrol *kcontrol,
|
||||
int snd_sof_bytes_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct soc_bytes_ext *be =
|
||||
(struct soc_bytes_ext *)kcontrol->private_value;
|
||||
struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value;
|
||||
struct snd_sof_control *scontrol = be->dobj.private;
|
||||
struct snd_soc_component *scomp = scontrol->scomp;
|
||||
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
|
||||
struct sof_abi_hdr *data = cdata->data;
|
||||
size_t size;
|
||||
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
|
||||
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
|
||||
|
||||
if (be->max > sizeof(ucontrol->value.bytes.data)) {
|
||||
dev_err_ratelimited(scomp->dev,
|
||||
"error: data max %d exceeds ucontrol data array size\n",
|
||||
be->max);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* be->max has been verified to be >= sizeof(struct sof_abi_hdr) */
|
||||
if (data->size > be->max - sizeof(*data)) {
|
||||
dev_err_ratelimited(scomp->dev,
|
||||
"error: data size too big %u bytes max is %zu\n",
|
||||
data->size, be->max - sizeof(*data));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
size = data->size + sizeof(*data);
|
||||
|
||||
/* copy from kcontrol */
|
||||
memcpy(data, ucontrol->value.bytes.data, size);
|
||||
|
||||
/* notify DSP of byte control updates */
|
||||
if (pm_runtime_active(scomp->dev))
|
||||
snd_sof_ipc_set_get_comp_data(scontrol, true);
|
||||
if (tplg_ops->control->bytes_put)
|
||||
return tplg_ops->control->bytes_put(scontrol, ucontrol);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -331,74 +194,18 @@ int snd_sof_bytes_ext_put(struct snd_kcontrol *kcontrol,
|
||||
const unsigned int __user *binary_data,
|
||||
unsigned int size)
|
||||
{
|
||||
struct soc_bytes_ext *be =
|
||||
(struct soc_bytes_ext *)kcontrol->private_value;
|
||||
struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value;
|
||||
struct snd_sof_control *scontrol = be->dobj.private;
|
||||
struct snd_soc_component *scomp = scontrol->scomp;
|
||||
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
|
||||
struct snd_ctl_tlv header;
|
||||
const struct snd_ctl_tlv __user *tlvd =
|
||||
(const struct snd_ctl_tlv __user *)binary_data;
|
||||
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
|
||||
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
|
||||
|
||||
/* make sure we have at least a header */
|
||||
if (size < sizeof(struct snd_ctl_tlv))
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* The beginning of bytes data contains a header from where
|
||||
* the length (as bytes) is needed to know the correct copy
|
||||
* length of data from tlvd->tlv.
|
||||
*/
|
||||
if (copy_from_user(&header, tlvd, sizeof(struct snd_ctl_tlv)))
|
||||
return -EFAULT;
|
||||
|
||||
/* make sure TLV info is consistent */
|
||||
if (header.length + sizeof(struct snd_ctl_tlv) > size) {
|
||||
dev_err_ratelimited(scomp->dev, "error: inconsistent TLV, data %d + header %zu > %d\n",
|
||||
header.length, sizeof(struct snd_ctl_tlv), size);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* be->max is coming from topology */
|
||||
if (header.length > be->max) {
|
||||
dev_err_ratelimited(scomp->dev, "error: Bytes data size %d exceeds max %d.\n",
|
||||
header.length, be->max);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Check that header id matches the command */
|
||||
if (header.numid != cdata->cmd) {
|
||||
dev_err_ratelimited(scomp->dev,
|
||||
"error: incorrect numid %d\n",
|
||||
header.numid);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (copy_from_user(cdata->data, tlvd->tlv, header.length))
|
||||
return -EFAULT;
|
||||
|
||||
if (cdata->data->magic != SOF_ABI_MAGIC) {
|
||||
dev_err_ratelimited(scomp->dev,
|
||||
"error: Wrong ABI magic 0x%08x.\n",
|
||||
cdata->data->magic);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, cdata->data->abi)) {
|
||||
dev_err_ratelimited(scomp->dev, "error: Incompatible ABI version 0x%08x.\n",
|
||||
cdata->data->abi);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* be->max has been verified to be >= sizeof(struct sof_abi_hdr) */
|
||||
if (cdata->data->size > be->max - sizeof(struct sof_abi_hdr)) {
|
||||
dev_err_ratelimited(scomp->dev, "error: Mismatch in ABI data size (truncated?).\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* notify DSP of byte control updates */
|
||||
if (pm_runtime_active(scomp->dev))
|
||||
snd_sof_ipc_set_get_comp_data(scontrol, true);
|
||||
if (tplg_ops->control->bytes_ext_put)
|
||||
return tplg_ops->control->bytes_ext_put(scontrol, binary_data, size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -409,67 +216,24 @@ int snd_sof_bytes_ext_volatile_get(struct snd_kcontrol *kcontrol, unsigned int _
|
||||
struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value;
|
||||
struct snd_sof_control *scontrol = be->dobj.private;
|
||||
struct snd_soc_component *scomp = scontrol->scomp;
|
||||
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
|
||||
struct snd_ctl_tlv header;
|
||||
struct snd_ctl_tlv __user *tlvd = (struct snd_ctl_tlv __user *)binary_data;
|
||||
size_t data_size;
|
||||
int ret;
|
||||
int err;
|
||||
|
||||
/*
|
||||
* Decrement the limit by ext bytes header size to
|
||||
* ensure the user space buffer is not exceeded.
|
||||
*/
|
||||
if (size < sizeof(struct snd_ctl_tlv))
|
||||
return -ENOSPC;
|
||||
size -= sizeof(struct snd_ctl_tlv);
|
||||
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
|
||||
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
|
||||
int ret, err;
|
||||
|
||||
ret = pm_runtime_get_sync(scomp->dev);
|
||||
if (ret < 0 && ret != -EACCES) {
|
||||
dev_err_ratelimited(scomp->dev, "error: bytes_ext get failed to resume %d\n", ret);
|
||||
dev_err_ratelimited(scomp->dev, "%s: failed to resume %d\n", __func__, ret);
|
||||
pm_runtime_put_noidle(scomp->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* set the ABI header values */
|
||||
cdata->data->magic = SOF_ABI_MAGIC;
|
||||
cdata->data->abi = SOF_ABI_VERSION;
|
||||
/* get all the component data from DSP */
|
||||
ret = snd_sof_ipc_set_get_comp_data(scontrol, false);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
if (tplg_ops->control->bytes_ext_volatile_get)
|
||||
ret = tplg_ops->control->bytes_ext_volatile_get(scontrol, binary_data, size);
|
||||
|
||||
/* check data size doesn't exceed max coming from topology */
|
||||
if (cdata->data->size > be->max - sizeof(struct sof_abi_hdr)) {
|
||||
dev_err_ratelimited(scomp->dev, "error: user data size %d exceeds max size %zu.\n",
|
||||
cdata->data->size,
|
||||
be->max - sizeof(struct sof_abi_hdr));
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
data_size = cdata->data->size + sizeof(struct sof_abi_hdr);
|
||||
|
||||
/* make sure we don't exceed size provided by user space for data */
|
||||
if (data_size > size) {
|
||||
ret = -ENOSPC;
|
||||
goto out;
|
||||
}
|
||||
|
||||
header.numid = cdata->cmd;
|
||||
header.length = data_size;
|
||||
if (copy_to_user(tlvd, &header, sizeof(struct snd_ctl_tlv))) {
|
||||
ret = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (copy_to_user(tlvd->tlv, cdata->data, data_size))
|
||||
ret = -EFAULT;
|
||||
out:
|
||||
pm_runtime_mark_last_busy(scomp->dev);
|
||||
err = pm_runtime_put_autosuspend(scomp->dev);
|
||||
if (err < 0)
|
||||
dev_err_ratelimited(scomp->dev, "error: bytes_ext get failed to idle %d\n", err);
|
||||
dev_err_ratelimited(scomp->dev, "%s: failed to idle %d\n", __func__, err);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -478,195 +242,14 @@ int snd_sof_bytes_ext_get(struct snd_kcontrol *kcontrol,
|
||||
unsigned int __user *binary_data,
|
||||
unsigned int size)
|
||||
{
|
||||
struct soc_bytes_ext *be =
|
||||
(struct soc_bytes_ext *)kcontrol->private_value;
|
||||
struct soc_bytes_ext *be = (struct soc_bytes_ext *)kcontrol->private_value;
|
||||
struct snd_sof_control *scontrol = be->dobj.private;
|
||||
struct snd_soc_component *scomp = scontrol->scomp;
|
||||
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
|
||||
struct snd_ctl_tlv header;
|
||||
struct snd_ctl_tlv __user *tlvd =
|
||||
(struct snd_ctl_tlv __user *)binary_data;
|
||||
size_t data_size;
|
||||
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp);
|
||||
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
|
||||
|
||||
snd_sof_refresh_control(scontrol);
|
||||
|
||||
/*
|
||||
* Decrement the limit by ext bytes header size to
|
||||
* ensure the user space buffer is not exceeded.
|
||||
*/
|
||||
if (size < sizeof(struct snd_ctl_tlv))
|
||||
return -ENOSPC;
|
||||
size -= sizeof(struct snd_ctl_tlv);
|
||||
|
||||
/* set the ABI header values */
|
||||
cdata->data->magic = SOF_ABI_MAGIC;
|
||||
cdata->data->abi = SOF_ABI_VERSION;
|
||||
|
||||
/* check data size doesn't exceed max coming from topology */
|
||||
if (cdata->data->size > be->max - sizeof(struct sof_abi_hdr)) {
|
||||
dev_err_ratelimited(scomp->dev, "error: user data size %d exceeds max size %zu.\n",
|
||||
cdata->data->size,
|
||||
be->max - sizeof(struct sof_abi_hdr));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
data_size = cdata->data->size + sizeof(struct sof_abi_hdr);
|
||||
|
||||
/* make sure we don't exceed size provided by user space for data */
|
||||
if (data_size > size)
|
||||
return -ENOSPC;
|
||||
|
||||
header.numid = cdata->cmd;
|
||||
header.length = data_size;
|
||||
if (copy_to_user(tlvd, &header, sizeof(struct snd_ctl_tlv)))
|
||||
return -EFAULT;
|
||||
|
||||
if (copy_to_user(tlvd->tlv, cdata->data, data_size))
|
||||
return -EFAULT;
|
||||
if (tplg_ops->control->bytes_ext_get)
|
||||
return tplg_ops->control->bytes_ext_get(scontrol, binary_data, size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void snd_sof_update_control(struct snd_sof_control *scontrol,
|
||||
struct sof_ipc_ctrl_data *cdata)
|
||||
{
|
||||
struct snd_soc_component *scomp = scontrol->scomp;
|
||||
struct sof_ipc_ctrl_data *local_cdata;
|
||||
int i;
|
||||
|
||||
local_cdata = scontrol->ipc_control_data;
|
||||
|
||||
if (cdata->cmd == SOF_CTRL_CMD_BINARY) {
|
||||
if (cdata->num_elems != local_cdata->data->size) {
|
||||
dev_err(scomp->dev,
|
||||
"error: cdata binary size mismatch %u - %u\n",
|
||||
cdata->num_elems, local_cdata->data->size);
|
||||
return;
|
||||
}
|
||||
|
||||
/* copy the new binary data */
|
||||
memcpy(local_cdata->data, cdata->data, cdata->num_elems);
|
||||
} else if (cdata->num_elems != scontrol->num_channels) {
|
||||
dev_err(scomp->dev,
|
||||
"error: cdata channel count mismatch %u - %d\n",
|
||||
cdata->num_elems, scontrol->num_channels);
|
||||
} else {
|
||||
/* copy the new values */
|
||||
for (i = 0; i < cdata->num_elems; i++)
|
||||
local_cdata->chanv[i].value = cdata->chanv[i].value;
|
||||
}
|
||||
}
|
||||
|
||||
void snd_sof_control_notify(struct snd_sof_dev *sdev,
|
||||
struct sof_ipc_ctrl_data *cdata)
|
||||
{
|
||||
struct snd_soc_dapm_widget *widget;
|
||||
struct snd_sof_control *scontrol;
|
||||
struct snd_sof_widget *swidget;
|
||||
struct snd_kcontrol *kc = NULL;
|
||||
struct soc_mixer_control *sm;
|
||||
struct soc_bytes_ext *be;
|
||||
size_t expected_size;
|
||||
struct soc_enum *se;
|
||||
bool found = false;
|
||||
int i, type;
|
||||
|
||||
if (cdata->type == SOF_CTRL_TYPE_VALUE_COMP_GET ||
|
||||
cdata->type == SOF_CTRL_TYPE_VALUE_COMP_SET) {
|
||||
dev_err(sdev->dev,
|
||||
"Component data is not supported in control notification\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Find the swidget first */
|
||||
list_for_each_entry(swidget, &sdev->widget_list, list) {
|
||||
if (swidget->comp_id == cdata->comp_id) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
return;
|
||||
|
||||
/* Translate SOF cmd to TPLG type */
|
||||
switch (cdata->cmd) {
|
||||
case SOF_CTRL_CMD_VOLUME:
|
||||
case SOF_CTRL_CMD_SWITCH:
|
||||
type = SND_SOC_TPLG_TYPE_MIXER;
|
||||
break;
|
||||
case SOF_CTRL_CMD_BINARY:
|
||||
type = SND_SOC_TPLG_TYPE_BYTES;
|
||||
break;
|
||||
case SOF_CTRL_CMD_ENUM:
|
||||
type = SND_SOC_TPLG_TYPE_ENUM;
|
||||
break;
|
||||
default:
|
||||
dev_err(sdev->dev, "error: unknown cmd %u\n", cdata->cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
widget = swidget->widget;
|
||||
for (i = 0; i < widget->num_kcontrols; i++) {
|
||||
/* skip non matching types or non matching indexes within type */
|
||||
if (widget->dobj.widget.kcontrol_type[i] == type &&
|
||||
widget->kcontrol_news[i].index == cdata->index) {
|
||||
kc = widget->kcontrols[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!kc)
|
||||
return;
|
||||
|
||||
switch (cdata->cmd) {
|
||||
case SOF_CTRL_CMD_VOLUME:
|
||||
case SOF_CTRL_CMD_SWITCH:
|
||||
sm = (struct soc_mixer_control *)kc->private_value;
|
||||
scontrol = sm->dobj.private;
|
||||
break;
|
||||
case SOF_CTRL_CMD_BINARY:
|
||||
be = (struct soc_bytes_ext *)kc->private_value;
|
||||
scontrol = be->dobj.private;
|
||||
break;
|
||||
case SOF_CTRL_CMD_ENUM:
|
||||
se = (struct soc_enum *)kc->private_value;
|
||||
scontrol = se->dobj.private;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
expected_size = sizeof(struct sof_ipc_ctrl_data);
|
||||
switch (cdata->type) {
|
||||
case SOF_CTRL_TYPE_VALUE_CHAN_GET:
|
||||
case SOF_CTRL_TYPE_VALUE_CHAN_SET:
|
||||
expected_size += cdata->num_elems *
|
||||
sizeof(struct sof_ipc_ctrl_value_chan);
|
||||
break;
|
||||
case SOF_CTRL_TYPE_DATA_GET:
|
||||
case SOF_CTRL_TYPE_DATA_SET:
|
||||
expected_size += cdata->num_elems + sizeof(struct sof_abi_hdr);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (cdata->rhdr.hdr.size != expected_size) {
|
||||
dev_err(sdev->dev, "error: component notification size mismatch\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cdata->num_elems)
|
||||
/*
|
||||
* The message includes the updated value/data, update the
|
||||
* control's local cache using the received notification
|
||||
*/
|
||||
snd_sof_update_control(scontrol, cdata);
|
||||
else
|
||||
/* Mark the scontrol that the value/data is changed in SOF */
|
||||
scontrol->comp_data_dirty = true;
|
||||
|
||||
snd_ctl_notify_one(swidget->scomp->card->snd_card,
|
||||
SNDRV_CTL_EVENT_MASK_VALUE, kc, 0);
|
||||
}
|
||||
|
@ -162,58 +162,19 @@ static int hda_link_dma_params(struct hdac_ext_stream *hext_stream,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Update config for the DAI widget */
|
||||
static struct sof_ipc_dai_config *hda_dai_update_config(struct snd_soc_dapm_widget *w,
|
||||
int channel)
|
||||
{
|
||||
struct snd_sof_widget *swidget = w->dobj.private;
|
||||
struct sof_dai_private_data *private;
|
||||
struct sof_ipc_dai_config *config;
|
||||
struct snd_sof_dai *sof_dai;
|
||||
|
||||
if (!swidget)
|
||||
return NULL;
|
||||
|
||||
sof_dai = swidget->private;
|
||||
|
||||
if (!sof_dai || !sof_dai->private) {
|
||||
dev_err(swidget->scomp->dev, "%s: No private data for DAI %s\n", __func__,
|
||||
w->name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
private = sof_dai->private;
|
||||
if (!private->dai_config) {
|
||||
dev_err(swidget->scomp->dev, "%s: No config for DAI %s\n", __func__, w->name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
config = &private->dai_config[sof_dai->current_config];
|
||||
|
||||
/* update config with stream tag */
|
||||
config->hda.link_dma_ch = channel;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
static int hda_link_dai_widget_update(struct sof_intel_hda_stream *hda_stream,
|
||||
struct snd_soc_dapm_widget *w,
|
||||
int channel, bool widget_setup)
|
||||
{
|
||||
struct snd_sof_dev *sdev = hda_stream->sdev;
|
||||
struct sof_ipc_dai_config *config;
|
||||
struct snd_sof_dai_config_data data;
|
||||
|
||||
config = hda_dai_update_config(w, channel);
|
||||
if (!config) {
|
||||
dev_err(sdev->dev, "error: no config for DAI %s\n", w->name);
|
||||
return -ENOENT;
|
||||
}
|
||||
data.dai_data = channel;
|
||||
|
||||
/* set up/free DAI widget and send DAI_CONFIG IPC */
|
||||
if (widget_setup)
|
||||
return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_2_STEP_STOP);
|
||||
return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_2_STEP_STOP, &data);
|
||||
|
||||
return hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE);
|
||||
return hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE, &data);
|
||||
}
|
||||
|
||||
static int hda_link_hw_params(struct snd_pcm_substream *substream,
|
||||
@ -302,35 +263,16 @@ static int hda_link_dai_config_pause_push_ipc(struct snd_soc_dapm_widget *w)
|
||||
struct snd_sof_widget *swidget = w->dobj.private;
|
||||
struct snd_soc_component *component = swidget->scomp;
|
||||
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
|
||||
struct sof_dai_private_data *private;
|
||||
struct sof_ipc_dai_config *config;
|
||||
struct snd_sof_dai *sof_dai;
|
||||
struct sof_ipc_reply reply;
|
||||
int ret;
|
||||
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
|
||||
int ret = 0;
|
||||
|
||||
sof_dai = swidget->private;
|
||||
|
||||
if (!sof_dai || !sof_dai->private) {
|
||||
dev_err(sdev->dev, "%s: No private data for DAI %s\n", __func__, w->name);
|
||||
return -EINVAL;
|
||||
if (tplg_ops->dai_config) {
|
||||
ret = tplg_ops->dai_config(sdev, swidget, SOF_DAI_CONFIG_FLAGS_PAUSE, NULL);
|
||||
if (ret < 0)
|
||||
dev_err(sdev->dev, "%s: DAI config failed for widget %s\n", __func__,
|
||||
w->name);
|
||||
}
|
||||
|
||||
private = sof_dai->private;
|
||||
if (!private->dai_config) {
|
||||
dev_err(sdev->dev, "%s: No config for DAI %s\n", __func__, w->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
config = &private->dai_config[sof_dai->current_config];
|
||||
|
||||
/* set PAUSE command flag */
|
||||
config->flags = FIELD_PREP(SOF_DAI_CONFIG_FLAGS_CMD_MASK, SOF_DAI_CONFIG_FLAGS_PAUSE);
|
||||
|
||||
ret = sof_ipc_tx_message(sdev->ipc, config->hdr.cmd, config, config->hdr.size,
|
||||
&reply, sizeof(reply));
|
||||
if (ret < 0)
|
||||
dev_err(sdev->dev, "DAI config for %s failed during pause push\n", w->name);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -470,30 +412,17 @@ struct ssp_dai_dma_data {
|
||||
static int ssp_dai_setup_or_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai,
|
||||
bool setup)
|
||||
{
|
||||
struct snd_soc_component *component;
|
||||
struct snd_sof_widget *swidget;
|
||||
struct snd_soc_dapm_widget *w;
|
||||
struct sof_ipc_fw_version *v;
|
||||
struct snd_sof_dev *sdev;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
w = dai->playback_widget;
|
||||
else
|
||||
w = dai->capture_widget;
|
||||
|
||||
swidget = w->dobj.private;
|
||||
component = swidget->scomp;
|
||||
sdev = snd_soc_component_get_drvdata(component);
|
||||
v = &sdev->fw_ready.version;
|
||||
|
||||
/* DAI_CONFIG IPC during hw_params is not supported in older firmware */
|
||||
if (v->abi_version < SOF_ABI_VER(3, 18, 0))
|
||||
return 0;
|
||||
|
||||
if (setup)
|
||||
return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_NONE);
|
||||
return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_NONE, NULL);
|
||||
|
||||
return hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE);
|
||||
return hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE, NULL);
|
||||
}
|
||||
|
||||
static int ssp_dai_startup(struct snd_pcm_substream *substream,
|
||||
|
@ -41,114 +41,68 @@
|
||||
#define EXCEPT_MAX_HDR_SIZE 0x400
|
||||
#define HDA_EXT_ROM_STATUS_SIZE 8
|
||||
|
||||
int hda_ctrl_dai_widget_setup(struct snd_soc_dapm_widget *w, unsigned int quirk_flags)
|
||||
int hda_ctrl_dai_widget_setup(struct snd_soc_dapm_widget *w, unsigned int quirk_flags,
|
||||
struct snd_sof_dai_config_data *data)
|
||||
{
|
||||
struct snd_sof_widget *swidget = w->dobj.private;
|
||||
struct snd_soc_component *component = swidget->scomp;
|
||||
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
|
||||
struct sof_ipc_dai_config *config;
|
||||
struct sof_dai_private_data *private;
|
||||
struct snd_sof_dai *sof_dai;
|
||||
struct sof_ipc_reply reply;
|
||||
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
|
||||
struct snd_sof_dai *sof_dai = swidget->private;
|
||||
int ret;
|
||||
|
||||
sof_dai = swidget->private;
|
||||
|
||||
if (!sof_dai || !sof_dai->private) {
|
||||
dev_err(sdev->dev, "%s: No private data for DAI %s\n", __func__, w->name);
|
||||
if (!sof_dai) {
|
||||
dev_err(sdev->dev, "%s: No DAI for DAI widget %s\n", __func__, w->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
private = sof_dai->private;
|
||||
if (!private->dai_config) {
|
||||
dev_err(sdev->dev, "%s: No config for DAI %s\n", __func__, w->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (tplg_ops->dai_config) {
|
||||
unsigned int flags;
|
||||
|
||||
/* DAI already configured, reset it before reconfiguring it */
|
||||
if (sof_dai->configured) {
|
||||
ret = hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE);
|
||||
if (ret < 0)
|
||||
/* set HW_PARAMS flag along with quirks */
|
||||
flags = SOF_DAI_CONFIG_FLAGS_HW_PARAMS |
|
||||
quirk_flags << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT;
|
||||
|
||||
ret = tplg_ops->dai_config(sdev, swidget, flags, data);
|
||||
if (ret < 0) {
|
||||
dev_err(sdev->dev, "%s: DAI config failed for widget %s\n", __func__,
|
||||
w->name);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
config = &private->dai_config[sof_dai->current_config];
|
||||
|
||||
/*
|
||||
* For static pipelines, the DAI widget would already be set up and calling
|
||||
* sof_widget_setup() simply returns without doing anything.
|
||||
* For dynamic pipelines, the DAI widget will be set up now.
|
||||
*/
|
||||
ret = sof_widget_setup(sdev, swidget);
|
||||
if (ret < 0) {
|
||||
dev_err(sdev->dev, "error: failed setting up DAI widget %s\n", w->name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* set HW_PARAMS flag along with quirks */
|
||||
config->flags = SOF_DAI_CONFIG_FLAGS_HW_PARAMS |
|
||||
quirk_flags << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT;
|
||||
|
||||
|
||||
/* send DAI_CONFIG IPC */
|
||||
ret = sof_ipc_tx_message(sdev->ipc, config->hdr.cmd, config, config->hdr.size,
|
||||
&reply, sizeof(reply));
|
||||
if (ret < 0) {
|
||||
dev_err(sdev->dev, "error: failed setting DAI config for %s\n", w->name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
sof_dai->configured = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hda_ctrl_dai_widget_free(struct snd_soc_dapm_widget *w, unsigned int quirk_flags)
|
||||
int hda_ctrl_dai_widget_free(struct snd_soc_dapm_widget *w, unsigned int quirk_flags,
|
||||
struct snd_sof_dai_config_data *data)
|
||||
{
|
||||
struct snd_sof_widget *swidget = w->dobj.private;
|
||||
struct snd_soc_component *component = swidget->scomp;
|
||||
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
|
||||
struct sof_dai_private_data *private;
|
||||
struct sof_ipc_dai_config *config;
|
||||
struct snd_sof_dai *sof_dai;
|
||||
struct sof_ipc_reply reply;
|
||||
int ret;
|
||||
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
|
||||
struct snd_sof_dai *sof_dai = swidget->private;
|
||||
|
||||
sof_dai = swidget->private;
|
||||
|
||||
if (!sof_dai || !sof_dai->private) {
|
||||
dev_err(sdev->dev, "%s: No private data for DAI %s\n", __func__, w->name);
|
||||
if (!sof_dai) {
|
||||
dev_err(sdev->dev, "%s: No DAI for BE DAI widget %s\n", __func__, w->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
private = sof_dai->private;
|
||||
if (!private->dai_config) {
|
||||
dev_err(sdev->dev, "%s: No config for DAI %s\n", __func__, w->name);
|
||||
return -EINVAL;
|
||||
if (tplg_ops->dai_config) {
|
||||
unsigned int flags;
|
||||
int ret;
|
||||
|
||||
/* set HW_FREE flag along with any quirks */
|
||||
flags = SOF_DAI_CONFIG_FLAGS_HW_FREE |
|
||||
quirk_flags << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT;
|
||||
|
||||
ret = tplg_ops->dai_config(sdev, swidget, flags, data);
|
||||
if (ret < 0)
|
||||
dev_err(sdev->dev, "%s: DAI config failed for widget '%s'\n", __func__,
|
||||
w->name);
|
||||
}
|
||||
|
||||
/* nothing to do if hw_free() is called without restarting the stream after resume. */
|
||||
if (!sof_dai->configured)
|
||||
return 0;
|
||||
|
||||
config = &private->dai_config[sof_dai->current_config];
|
||||
|
||||
/* set HW_FREE flag along with any quirks */
|
||||
config->flags = SOF_DAI_CONFIG_FLAGS_HW_FREE |
|
||||
quirk_flags << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT;
|
||||
|
||||
ret = sof_ipc_tx_message(sdev->ipc, config->hdr.cmd, config, config->hdr.size,
|
||||
&reply, sizeof(reply));
|
||||
if (ret < 0)
|
||||
dev_err(sdev->dev, "error: failed resetting DAI config for %s\n", w->name);
|
||||
|
||||
/*
|
||||
* Reset the configured_flag and free the widget even if the IPC fails to keep
|
||||
* the widget use_count balanced
|
||||
*/
|
||||
sof_dai->configured = false;
|
||||
|
||||
return sof_widget_free(sdev, swidget);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE)
|
||||
@ -163,69 +117,34 @@ static int sdw_clock_stop_quirks = SDW_INTEL_CLK_STOP_BUS_RESET;
|
||||
module_param(sdw_clock_stop_quirks, int, 0444);
|
||||
MODULE_PARM_DESC(sdw_clock_stop_quirks, "SOF SoundWire clock stop quirks");
|
||||
|
||||
static int sdw_dai_config_ipc(struct snd_sof_dev *sdev,
|
||||
struct snd_soc_dapm_widget *w,
|
||||
int link_id, int alh_stream_id, int dai_id, bool setup)
|
||||
{
|
||||
struct snd_sof_widget *swidget = w->dobj.private;
|
||||
struct sof_dai_private_data *private;
|
||||
struct sof_ipc_dai_config *config;
|
||||
struct snd_sof_dai *sof_dai;
|
||||
|
||||
if (!swidget) {
|
||||
dev_err(sdev->dev, "error: No private data for widget %s\n", w->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
sof_dai = swidget->private;
|
||||
|
||||
if (!sof_dai || !sof_dai->private) {
|
||||
dev_err(sdev->dev, "%s: No private data for DAI %s\n", __func__, w->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
private = sof_dai->private;
|
||||
if (!private->dai_config) {
|
||||
dev_err(sdev->dev, "%s: No config for DAI %s\n", __func__, w->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
config = &private->dai_config[sof_dai->current_config];
|
||||
|
||||
/* update config with link and stream ID */
|
||||
config->dai_index = (link_id << 8) | dai_id;
|
||||
config->alh.stream_id = alh_stream_id;
|
||||
|
||||
if (setup)
|
||||
return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_NONE);
|
||||
|
||||
return hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE);
|
||||
}
|
||||
|
||||
static int sdw_params_stream(struct device *dev,
|
||||
struct sdw_intel_stream_params_data *params_data)
|
||||
{
|
||||
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
|
||||
struct snd_soc_dai *d = params_data->dai;
|
||||
struct snd_sof_dai_config_data data;
|
||||
struct snd_soc_dapm_widget *w;
|
||||
|
||||
w = snd_soc_dai_get_widget(d, params_data->stream);
|
||||
data.dai_index = (params_data->link_id << 8) | d->id;
|
||||
data.dai_data = params_data->alh_stream_id;
|
||||
|
||||
return sdw_dai_config_ipc(sdev, w, params_data->link_id, params_data->alh_stream_id,
|
||||
d->id, true);
|
||||
return hda_ctrl_dai_widget_setup(w, SOF_DAI_CONFIG_FLAGS_NONE, &data);
|
||||
}
|
||||
|
||||
static int sdw_free_stream(struct device *dev,
|
||||
struct sdw_intel_stream_free_data *free_data)
|
||||
{
|
||||
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
|
||||
struct snd_soc_dai *d = free_data->dai;
|
||||
struct snd_sof_dai_config_data data;
|
||||
struct snd_soc_dapm_widget *w;
|
||||
|
||||
w = snd_soc_dai_get_widget(d, free_data->stream);
|
||||
data.dai_index = (free_data->link_id << 8) | d->id;
|
||||
|
||||
/* send invalid stream_id */
|
||||
return sdw_dai_config_ipc(sdev, w, free_data->link_id, 0xFFFF, d->id, false);
|
||||
data.dai_data = 0xFFFF;
|
||||
|
||||
return hda_ctrl_dai_widget_free(w, SOF_DAI_CONFIG_FLAGS_NONE, &data);
|
||||
}
|
||||
|
||||
static const struct sdw_intel_ops sdw_callback = {
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include <sound/hda_codec.h>
|
||||
#include <sound/hdaudio_ext.h>
|
||||
#include "../sof-client-probes.h"
|
||||
#include "../sof-audio.h"
|
||||
#include "shim.h"
|
||||
|
||||
/* PCI registers */
|
||||
@ -730,8 +731,10 @@ int hda_pci_intel_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
|
||||
|
||||
struct snd_sof_dai;
|
||||
struct sof_ipc_dai_config;
|
||||
int hda_ctrl_dai_widget_setup(struct snd_soc_dapm_widget *w, unsigned int quirk_flags);
|
||||
int hda_ctrl_dai_widget_free(struct snd_soc_dapm_widget *w, unsigned int quirk_flags);
|
||||
int hda_ctrl_dai_widget_setup(struct snd_soc_dapm_widget *w, unsigned int quirk_flags,
|
||||
struct snd_sof_dai_config_data *data);
|
||||
int hda_ctrl_dai_widget_free(struct snd_soc_dapm_widget *w, unsigned int quirk_flags,
|
||||
struct snd_sof_dai_config_data *data);
|
||||
|
||||
#define SOF_HDA_POSITION_QUIRK_USE_SKYLAKE_LEGACY (0) /* previous implementation */
|
||||
#define SOF_HDA_POSITION_QUIRK_USE_DPIB_REGISTERS (1) /* recommended if VC0 only */
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "sof-priv.h"
|
||||
#include "sof-audio.h"
|
||||
#include "ops.h"
|
||||
#include "ipc3-ops.h"
|
||||
|
||||
typedef void (*ipc_rx_callback)(struct snd_sof_dev *sdev, void *msg_buf);
|
||||
|
||||
@ -469,6 +470,7 @@ EXPORT_SYMBOL(snd_sof_ipc_reply);
|
||||
|
||||
static void ipc_comp_notification(struct snd_sof_dev *sdev, void *msg_buf)
|
||||
{
|
||||
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
|
||||
struct sof_ipc_cmd_hdr *hdr = msg_buf;
|
||||
u32 msg_type = hdr->cmd & SOF_CMD_TYPE_MASK;
|
||||
|
||||
@ -481,7 +483,8 @@ static void ipc_comp_notification(struct snd_sof_dev *sdev, void *msg_buf)
|
||||
return;
|
||||
}
|
||||
|
||||
snd_sof_control_notify(sdev, msg_buf);
|
||||
if (tplg_ops->control->update)
|
||||
tplg_ops->control->update(sdev, msg_buf);
|
||||
}
|
||||
|
||||
/* DSP firmware has sent host a message */
|
||||
@ -1030,8 +1033,9 @@ struct snd_sof_ipc *snd_sof_ipc_init(struct snd_sof_dev *sdev)
|
||||
ipc->ops = &ipc3_ops;
|
||||
|
||||
/* check for mandatory ops */
|
||||
if (!ipc->ops->tplg || !ipc->ops->tplg->widget) {
|
||||
dev_err(sdev->dev, "Invalid topology IPC ops\n");
|
||||
if (!ipc->ops->pcm || !ipc->ops->tplg || !ipc->ops->tplg->widget ||
|
||||
!ipc->ops->tplg->control) {
|
||||
dev_err(sdev->dev, "Invalid IPC ops\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
594
sound/soc/sof/ipc3-control.c
Normal file
594
sound/soc/sof/ipc3-control.c
Normal file
@ -0,0 +1,594 @@
|
||||
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
|
||||
//
|
||||
// This file is provided under a dual BSD/GPLv2 license. When using or
|
||||
// redistributing this file, you may do so under either license.
|
||||
//
|
||||
// Copyright(c) 2021 Intel Corporation. All rights reserved.
|
||||
//
|
||||
//
|
||||
|
||||
#include "sof-priv.h"
|
||||
#include "sof-audio.h"
|
||||
#include "ipc3-ops.h"
|
||||
|
||||
static inline u32 mixer_to_ipc(unsigned int value, u32 *volume_map, int size)
|
||||
{
|
||||
if (value >= size)
|
||||
return volume_map[size - 1];
|
||||
|
||||
return volume_map[value];
|
||||
}
|
||||
|
||||
static inline u32 ipc_to_mixer(u32 value, u32 *volume_map, int size)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < size; i++) {
|
||||
if (volume_map[i] >= value)
|
||||
return i;
|
||||
}
|
||||
|
||||
return i - 1;
|
||||
}
|
||||
|
||||
static void snd_sof_refresh_control(struct snd_sof_control *scontrol)
|
||||
{
|
||||
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
|
||||
struct snd_soc_component *scomp = scontrol->scomp;
|
||||
int ret;
|
||||
|
||||
if (!scontrol->comp_data_dirty)
|
||||
return;
|
||||
|
||||
if (!pm_runtime_active(scomp->dev))
|
||||
return;
|
||||
|
||||
/* set the ABI header values */
|
||||
cdata->data->magic = SOF_ABI_MAGIC;
|
||||
cdata->data->abi = SOF_ABI_VERSION;
|
||||
|
||||
/* refresh the component data from DSP */
|
||||
scontrol->comp_data_dirty = false;
|
||||
ret = snd_sof_ipc_set_get_comp_data(scontrol, false);
|
||||
if (ret < 0) {
|
||||
dev_err(scomp->dev, "Failed to get control data: %d\n", ret);
|
||||
|
||||
/* Set the flag to re-try next time to get the data */
|
||||
scontrol->comp_data_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
static int sof_ipc3_volume_get(struct snd_sof_control *scontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
|
||||
unsigned int channels = scontrol->num_channels;
|
||||
unsigned int i;
|
||||
|
||||
snd_sof_refresh_control(scontrol);
|
||||
|
||||
/* read back each channel */
|
||||
for (i = 0; i < channels; i++)
|
||||
ucontrol->value.integer.value[i] = ipc_to_mixer(cdata->chanv[i].value,
|
||||
scontrol->volume_table,
|
||||
scontrol->max + 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool sof_ipc3_volume_put(struct snd_sof_control *scontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
|
||||
struct snd_soc_component *scomp = scontrol->scomp;
|
||||
unsigned int channels = scontrol->num_channels;
|
||||
unsigned int i;
|
||||
bool change = false;
|
||||
|
||||
/* update each channel */
|
||||
for (i = 0; i < channels; i++) {
|
||||
u32 value = mixer_to_ipc(ucontrol->value.integer.value[i],
|
||||
scontrol->volume_table, scontrol->max + 1);
|
||||
|
||||
change = change || (value != cdata->chanv[i].value);
|
||||
cdata->chanv[i].channel = i;
|
||||
cdata->chanv[i].value = value;
|
||||
}
|
||||
|
||||
/* notify DSP of mixer updates */
|
||||
if (pm_runtime_active(scomp->dev)) {
|
||||
int ret = snd_sof_ipc_set_get_comp_data(scontrol, true);
|
||||
|
||||
if (ret < 0) {
|
||||
dev_err(scomp->dev, "Failed to set mixer updates for %s\n",
|
||||
scontrol->name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return change;
|
||||
}
|
||||
|
||||
static int sof_ipc3_switch_get(struct snd_sof_control *scontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
|
||||
unsigned int channels = scontrol->num_channels;
|
||||
unsigned int i;
|
||||
|
||||
snd_sof_refresh_control(scontrol);
|
||||
|
||||
/* read back each channel */
|
||||
for (i = 0; i < channels; i++)
|
||||
ucontrol->value.integer.value[i] = cdata->chanv[i].value;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool sof_ipc3_switch_put(struct snd_sof_control *scontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
|
||||
struct snd_soc_component *scomp = scontrol->scomp;
|
||||
unsigned int channels = scontrol->num_channels;
|
||||
unsigned int i;
|
||||
bool change = false;
|
||||
u32 value;
|
||||
|
||||
/* update each channel */
|
||||
for (i = 0; i < channels; i++) {
|
||||
value = ucontrol->value.integer.value[i];
|
||||
change = change || (value != cdata->chanv[i].value);
|
||||
cdata->chanv[i].channel = i;
|
||||
cdata->chanv[i].value = value;
|
||||
}
|
||||
|
||||
/* notify DSP of mixer updates */
|
||||
if (pm_runtime_active(scomp->dev)) {
|
||||
int ret = snd_sof_ipc_set_get_comp_data(scontrol, true);
|
||||
|
||||
if (ret < 0) {
|
||||
dev_err(scomp->dev, "Failed to set mixer updates for %s\n",
|
||||
scontrol->name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return change;
|
||||
}
|
||||
|
||||
static int sof_ipc3_enum_get(struct snd_sof_control *scontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
|
||||
unsigned int channels = scontrol->num_channels;
|
||||
unsigned int i;
|
||||
|
||||
snd_sof_refresh_control(scontrol);
|
||||
|
||||
/* read back each channel */
|
||||
for (i = 0; i < channels; i++)
|
||||
ucontrol->value.enumerated.item[i] = cdata->chanv[i].value;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool sof_ipc3_enum_put(struct snd_sof_control *scontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
|
||||
struct snd_soc_component *scomp = scontrol->scomp;
|
||||
unsigned int channels = scontrol->num_channels;
|
||||
unsigned int i;
|
||||
bool change = false;
|
||||
u32 value;
|
||||
|
||||
/* update each channel */
|
||||
for (i = 0; i < channels; i++) {
|
||||
value = ucontrol->value.enumerated.item[i];
|
||||
change = change || (value != cdata->chanv[i].value);
|
||||
cdata->chanv[i].channel = i;
|
||||
cdata->chanv[i].value = value;
|
||||
}
|
||||
|
||||
/* notify DSP of enum updates */
|
||||
if (pm_runtime_active(scomp->dev)) {
|
||||
int ret = snd_sof_ipc_set_get_comp_data(scontrol, true);
|
||||
|
||||
if (ret < 0) {
|
||||
dev_err(scomp->dev, "Failed to set enum updates for %s\n",
|
||||
scontrol->name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return change;
|
||||
}
|
||||
|
||||
static int sof_ipc3_bytes_get(struct snd_sof_control *scontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
|
||||
struct snd_soc_component *scomp = scontrol->scomp;
|
||||
struct sof_abi_hdr *data = cdata->data;
|
||||
size_t size;
|
||||
|
||||
snd_sof_refresh_control(scontrol);
|
||||
|
||||
if (scontrol->max_size > sizeof(ucontrol->value.bytes.data)) {
|
||||
dev_err_ratelimited(scomp->dev, "data max %zu exceeds ucontrol data array size\n",
|
||||
scontrol->max_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* be->max has been verified to be >= sizeof(struct sof_abi_hdr) */
|
||||
if (data->size > scontrol->max_size - sizeof(*data)) {
|
||||
dev_err_ratelimited(scomp->dev,
|
||||
"%u bytes of control data is invalid, max is %zu\n",
|
||||
data->size, scontrol->max_size - sizeof(*data));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
size = data->size + sizeof(*data);
|
||||
|
||||
/* copy back to kcontrol */
|
||||
memcpy(ucontrol->value.bytes.data, data, size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sof_ipc3_bytes_put(struct snd_sof_control *scontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
|
||||
struct snd_soc_component *scomp = scontrol->scomp;
|
||||
struct sof_abi_hdr *data = cdata->data;
|
||||
size_t size;
|
||||
|
||||
if (scontrol->max_size > sizeof(ucontrol->value.bytes.data)) {
|
||||
dev_err_ratelimited(scomp->dev, "data max %zu exceeds ucontrol data array size\n",
|
||||
scontrol->max_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* scontrol->max_size has been verified to be >= sizeof(struct sof_abi_hdr) */
|
||||
if (data->size > scontrol->max_size - sizeof(*data)) {
|
||||
dev_err_ratelimited(scomp->dev, "data size too big %u bytes max is %zu\n",
|
||||
data->size, scontrol->max_size - sizeof(*data));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
size = data->size + sizeof(*data);
|
||||
|
||||
/* copy from kcontrol */
|
||||
memcpy(data, ucontrol->value.bytes.data, size);
|
||||
|
||||
/* notify DSP of byte control updates */
|
||||
if (pm_runtime_active(scomp->dev))
|
||||
return snd_sof_ipc_set_get_comp_data(scontrol, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sof_ipc3_bytes_ext_get(struct snd_sof_control *scontrol,
|
||||
const unsigned int __user *binary_data, unsigned int size)
|
||||
{
|
||||
struct snd_ctl_tlv __user *tlvd = (struct snd_ctl_tlv __user *)binary_data;
|
||||
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
|
||||
struct snd_soc_component *scomp = scontrol->scomp;
|
||||
struct snd_ctl_tlv header;
|
||||
size_t data_size;
|
||||
|
||||
snd_sof_refresh_control(scontrol);
|
||||
|
||||
/*
|
||||
* Decrement the limit by ext bytes header size to
|
||||
* ensure the user space buffer is not exceeded.
|
||||
*/
|
||||
if (size < sizeof(struct snd_ctl_tlv))
|
||||
return -ENOSPC;
|
||||
|
||||
size -= sizeof(struct snd_ctl_tlv);
|
||||
|
||||
/* set the ABI header values */
|
||||
cdata->data->magic = SOF_ABI_MAGIC;
|
||||
cdata->data->abi = SOF_ABI_VERSION;
|
||||
|
||||
/* check data size doesn't exceed max coming from topology */
|
||||
if (cdata->data->size > scontrol->max_size - sizeof(struct sof_abi_hdr)) {
|
||||
dev_err_ratelimited(scomp->dev, "User data size %d exceeds max size %zu\n",
|
||||
cdata->data->size,
|
||||
scontrol->max_size - sizeof(struct sof_abi_hdr));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
data_size = cdata->data->size + sizeof(struct sof_abi_hdr);
|
||||
|
||||
/* make sure we don't exceed size provided by user space for data */
|
||||
if (data_size > size)
|
||||
return -ENOSPC;
|
||||
|
||||
header.numid = cdata->cmd;
|
||||
header.length = data_size;
|
||||
if (copy_to_user(tlvd, &header, sizeof(struct snd_ctl_tlv)))
|
||||
return -EFAULT;
|
||||
|
||||
if (copy_to_user(tlvd->tlv, cdata->data, data_size))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sof_ipc3_bytes_ext_put(struct snd_sof_control *scontrol,
|
||||
const unsigned int __user *binary_data,
|
||||
unsigned int size)
|
||||
{
|
||||
const struct snd_ctl_tlv __user *tlvd = (const struct snd_ctl_tlv __user *)binary_data;
|
||||
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
|
||||
struct snd_soc_component *scomp = scontrol->scomp;
|
||||
struct snd_ctl_tlv header;
|
||||
|
||||
/*
|
||||
* The beginning of bytes data contains a header from where
|
||||
* the length (as bytes) is needed to know the correct copy
|
||||
* length of data from tlvd->tlv.
|
||||
*/
|
||||
if (copy_from_user(&header, tlvd, sizeof(struct snd_ctl_tlv)))
|
||||
return -EFAULT;
|
||||
|
||||
/* make sure TLV info is consistent */
|
||||
if (header.length + sizeof(struct snd_ctl_tlv) > size) {
|
||||
dev_err_ratelimited(scomp->dev, "Inconsistent TLV, data %d + header %zu > %d\n",
|
||||
header.length, sizeof(struct snd_ctl_tlv), size);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* be->max is coming from topology */
|
||||
if (header.length > scontrol->max_size) {
|
||||
dev_err_ratelimited(scomp->dev, "Bytes data size %d exceeds max %zu\n",
|
||||
header.length, scontrol->max_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Check that header id matches the command */
|
||||
if (header.numid != cdata->cmd) {
|
||||
dev_err_ratelimited(scomp->dev, "Incorrect command for bytes put %d\n",
|
||||
header.numid);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (copy_from_user(cdata->data, tlvd->tlv, header.length))
|
||||
return -EFAULT;
|
||||
|
||||
if (cdata->data->magic != SOF_ABI_MAGIC) {
|
||||
dev_err_ratelimited(scomp->dev, "Wrong ABI magic 0x%08x\n", cdata->data->magic);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, cdata->data->abi)) {
|
||||
dev_err_ratelimited(scomp->dev, "Incompatible ABI version 0x%08x\n",
|
||||
cdata->data->abi);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* be->max has been verified to be >= sizeof(struct sof_abi_hdr) */
|
||||
if (cdata->data->size > scontrol->max_size - sizeof(struct sof_abi_hdr)) {
|
||||
dev_err_ratelimited(scomp->dev, "Mismatch in ABI data size (truncated?)\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* notify DSP of byte control updates */
|
||||
if (pm_runtime_active(scomp->dev))
|
||||
return snd_sof_ipc_set_get_comp_data(scontrol, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sof_ipc3_bytes_ext_volatile_get(struct snd_sof_control *scontrol,
|
||||
const unsigned int __user *binary_data,
|
||||
unsigned int size)
|
||||
{
|
||||
struct snd_ctl_tlv __user *tlvd = (struct snd_ctl_tlv __user *)binary_data;
|
||||
struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data;
|
||||
struct snd_soc_component *scomp = scontrol->scomp;
|
||||
struct snd_ctl_tlv header;
|
||||
size_t data_size;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Decrement the limit by ext bytes header size to
|
||||
* ensure the user space buffer is not exceeded.
|
||||
*/
|
||||
if (size < sizeof(struct snd_ctl_tlv))
|
||||
return -ENOSPC;
|
||||
|
||||
size -= sizeof(struct snd_ctl_tlv);
|
||||
|
||||
/* set the ABI header values */
|
||||
cdata->data->magic = SOF_ABI_MAGIC;
|
||||
cdata->data->abi = SOF_ABI_VERSION;
|
||||
|
||||
/* get all the component data from DSP */
|
||||
ret = snd_sof_ipc_set_get_comp_data(scontrol, false);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* check data size doesn't exceed max coming from topology */
|
||||
if (cdata->data->size > scontrol->max_size - sizeof(struct sof_abi_hdr)) {
|
||||
dev_err_ratelimited(scomp->dev, "User data size %d exceeds max size %zu\n",
|
||||
cdata->data->size,
|
||||
scontrol->max_size - sizeof(struct sof_abi_hdr));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
data_size = cdata->data->size + sizeof(struct sof_abi_hdr);
|
||||
|
||||
/* make sure we don't exceed size provided by user space for data */
|
||||
if (data_size > size)
|
||||
return -ENOSPC;
|
||||
|
||||
header.numid = cdata->cmd;
|
||||
header.length = data_size;
|
||||
if (copy_to_user(tlvd, &header, sizeof(struct snd_ctl_tlv)))
|
||||
return -EFAULT;
|
||||
|
||||
if (copy_to_user(tlvd->tlv, cdata->data, data_size))
|
||||
return -EFAULT;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void snd_sof_update_control(struct snd_sof_control *scontrol,
|
||||
struct sof_ipc_ctrl_data *cdata)
|
||||
{
|
||||
struct snd_soc_component *scomp = scontrol->scomp;
|
||||
struct sof_ipc_ctrl_data *local_cdata;
|
||||
int i;
|
||||
|
||||
local_cdata = scontrol->ipc_control_data;
|
||||
|
||||
if (cdata->cmd == SOF_CTRL_CMD_BINARY) {
|
||||
if (cdata->num_elems != local_cdata->data->size) {
|
||||
dev_err(scomp->dev, "cdata binary size mismatch %u - %u\n",
|
||||
cdata->num_elems, local_cdata->data->size);
|
||||
return;
|
||||
}
|
||||
|
||||
/* copy the new binary data */
|
||||
memcpy(local_cdata->data, cdata->data, cdata->num_elems);
|
||||
} else if (cdata->num_elems != scontrol->num_channels) {
|
||||
dev_err(scomp->dev, "cdata channel count mismatch %u - %d\n",
|
||||
cdata->num_elems, scontrol->num_channels);
|
||||
} else {
|
||||
/* copy the new values */
|
||||
for (i = 0; i < cdata->num_elems; i++)
|
||||
local_cdata->chanv[i].value = cdata->chanv[i].value;
|
||||
}
|
||||
}
|
||||
|
||||
static void sof_ipc3_control_update(struct snd_sof_dev *sdev, void *ipc_control_message)
|
||||
{
|
||||
struct sof_ipc_ctrl_data *cdata = ipc_control_message;
|
||||
struct snd_soc_dapm_widget *widget;
|
||||
struct snd_sof_control *scontrol;
|
||||
struct snd_sof_widget *swidget;
|
||||
struct snd_kcontrol *kc = NULL;
|
||||
struct soc_mixer_control *sm;
|
||||
struct soc_bytes_ext *be;
|
||||
size_t expected_size;
|
||||
struct soc_enum *se;
|
||||
bool found = false;
|
||||
int i, type;
|
||||
|
||||
if (cdata->type == SOF_CTRL_TYPE_VALUE_COMP_GET ||
|
||||
cdata->type == SOF_CTRL_TYPE_VALUE_COMP_SET) {
|
||||
dev_err(sdev->dev, "Component data is not supported in control notification\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Find the swidget first */
|
||||
list_for_each_entry(swidget, &sdev->widget_list, list) {
|
||||
if (swidget->comp_id == cdata->comp_id) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
return;
|
||||
|
||||
/* Translate SOF cmd to TPLG type */
|
||||
switch (cdata->cmd) {
|
||||
case SOF_CTRL_CMD_VOLUME:
|
||||
case SOF_CTRL_CMD_SWITCH:
|
||||
type = SND_SOC_TPLG_TYPE_MIXER;
|
||||
break;
|
||||
case SOF_CTRL_CMD_BINARY:
|
||||
type = SND_SOC_TPLG_TYPE_BYTES;
|
||||
break;
|
||||
case SOF_CTRL_CMD_ENUM:
|
||||
type = SND_SOC_TPLG_TYPE_ENUM;
|
||||
break;
|
||||
default:
|
||||
dev_err(sdev->dev, "Unknown cmd %u in %s\n", cdata->cmd, __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
widget = swidget->widget;
|
||||
for (i = 0; i < widget->num_kcontrols; i++) {
|
||||
/* skip non matching types or non matching indexes within type */
|
||||
if (widget->dobj.widget.kcontrol_type[i] == type &&
|
||||
widget->kcontrol_news[i].index == cdata->index) {
|
||||
kc = widget->kcontrols[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!kc)
|
||||
return;
|
||||
|
||||
switch (cdata->cmd) {
|
||||
case SOF_CTRL_CMD_VOLUME:
|
||||
case SOF_CTRL_CMD_SWITCH:
|
||||
sm = (struct soc_mixer_control *)kc->private_value;
|
||||
scontrol = sm->dobj.private;
|
||||
break;
|
||||
case SOF_CTRL_CMD_BINARY:
|
||||
be = (struct soc_bytes_ext *)kc->private_value;
|
||||
scontrol = be->dobj.private;
|
||||
break;
|
||||
case SOF_CTRL_CMD_ENUM:
|
||||
se = (struct soc_enum *)kc->private_value;
|
||||
scontrol = se->dobj.private;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
expected_size = sizeof(struct sof_ipc_ctrl_data);
|
||||
switch (cdata->type) {
|
||||
case SOF_CTRL_TYPE_VALUE_CHAN_GET:
|
||||
case SOF_CTRL_TYPE_VALUE_CHAN_SET:
|
||||
expected_size += cdata->num_elems *
|
||||
sizeof(struct sof_ipc_ctrl_value_chan);
|
||||
break;
|
||||
case SOF_CTRL_TYPE_DATA_GET:
|
||||
case SOF_CTRL_TYPE_DATA_SET:
|
||||
expected_size += cdata->num_elems + sizeof(struct sof_abi_hdr);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (cdata->rhdr.hdr.size != expected_size) {
|
||||
dev_err(sdev->dev, "Component notification size mismatch\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cdata->num_elems)
|
||||
/*
|
||||
* The message includes the updated value/data, update the
|
||||
* control's local cache using the received notification
|
||||
*/
|
||||
snd_sof_update_control(scontrol, cdata);
|
||||
else
|
||||
/* Mark the scontrol that the value/data is changed in SOF */
|
||||
scontrol->comp_data_dirty = true;
|
||||
|
||||
snd_ctl_notify_one(swidget->scomp->card->snd_card, SNDRV_CTL_EVENT_MASK_VALUE, kc, 0);
|
||||
}
|
||||
|
||||
const struct sof_ipc_tplg_control_ops tplg_ipc3_control_ops = {
|
||||
.volume_put = sof_ipc3_volume_put,
|
||||
.volume_get = sof_ipc3_volume_get,
|
||||
.switch_put = sof_ipc3_switch_put,
|
||||
.switch_get = sof_ipc3_switch_get,
|
||||
.enum_put = sof_ipc3_enum_put,
|
||||
.enum_get = sof_ipc3_enum_get,
|
||||
.bytes_put = sof_ipc3_bytes_put,
|
||||
.bytes_get = sof_ipc3_bytes_get,
|
||||
.bytes_ext_put = sof_ipc3_bytes_ext_put,
|
||||
.bytes_ext_get = sof_ipc3_bytes_ext_get,
|
||||
.bytes_ext_volatile_get = sof_ipc3_bytes_ext_volatile_get,
|
||||
.update = sof_ipc3_control_update,
|
||||
};
|
21
sound/soc/sof/ipc3-ops.h
Normal file
21
sound/soc/sof/ipc3-ops.h
Normal file
@ -0,0 +1,21 @@
|
||||
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
|
||||
/*
|
||||
* This file is provided under a dual BSD/GPLv2 license. When using or
|
||||
* redistributing this file, you may do so under either license.
|
||||
*
|
||||
* Copyright(c) 2021 Intel Corporation. All rights reserved.
|
||||
*
|
||||
* Author: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
|
||||
*/
|
||||
|
||||
#ifndef __SOUND_SOC_SOF_IPC3_OPS_H
|
||||
#define __SOUND_SOC_SOF_IPC3_OPS_H
|
||||
|
||||
#include "sof-priv.h"
|
||||
|
||||
extern const struct sof_ipc_tplg_ops ipc3_tplg_ops;
|
||||
extern const struct sof_ipc_ops ipc3_ops;
|
||||
extern const struct sof_ipc_tplg_control_ops tplg_ipc3_control_ops;
|
||||
extern const struct sof_ipc_pcm_ops ipc3_pcm_ops;
|
||||
|
||||
#endif
|
372
sound/soc/sof/ipc3-pcm.c
Normal file
372
sound/soc/sof/ipc3-pcm.c
Normal file
@ -0,0 +1,372 @@
|
||||
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
|
||||
//
|
||||
// This file is provided under a dual BSD/GPLv2 license. When using or
|
||||
// redistributing this file, you may do so under either license.
|
||||
//
|
||||
// Copyright(c) 2021 Intel Corporation. All rights reserved.
|
||||
//
|
||||
//
|
||||
|
||||
#include <sound/pcm_params.h>
|
||||
#include "ipc3-ops.h"
|
||||
#include "ops.h"
|
||||
#include "sof-priv.h"
|
||||
#include "sof-audio.h"
|
||||
|
||||
static int sof_ipc3_pcm_hw_free(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
|
||||
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
||||
struct sof_ipc_stream stream;
|
||||
struct sof_ipc_reply reply;
|
||||
struct snd_sof_pcm *spcm;
|
||||
|
||||
spcm = snd_sof_find_spcm_dai(component, rtd);
|
||||
if (!spcm)
|
||||
return -EINVAL;
|
||||
|
||||
if (!spcm->prepared[substream->stream])
|
||||
return 0;
|
||||
|
||||
stream.hdr.size = sizeof(stream);
|
||||
stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_FREE;
|
||||
stream.comp_id = spcm->stream[substream->stream].comp_id;
|
||||
|
||||
/* send IPC to the DSP */
|
||||
return sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream,
|
||||
sizeof(stream), &reply, sizeof(reply));
|
||||
}
|
||||
|
||||
static int sof_ipc3_pcm_hw_params(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_sof_platform_stream_params *platform_params)
|
||||
{
|
||||
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
|
||||
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
||||
struct sof_ipc_fw_version *v = &sdev->fw_ready.version;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct sof_ipc_pcm_params_reply ipc_params_reply;
|
||||
struct sof_ipc_pcm_params pcm;
|
||||
struct snd_sof_pcm *spcm;
|
||||
int ret;
|
||||
|
||||
spcm = snd_sof_find_spcm_dai(component, rtd);
|
||||
if (!spcm)
|
||||
return -EINVAL;
|
||||
|
||||
memset(&pcm, 0, sizeof(pcm));
|
||||
|
||||
/* number of pages should be rounded up */
|
||||
pcm.params.buffer.pages = PFN_UP(runtime->dma_bytes);
|
||||
|
||||
/* set IPC PCM parameters */
|
||||
pcm.hdr.size = sizeof(pcm);
|
||||
pcm.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS;
|
||||
pcm.comp_id = spcm->stream[substream->stream].comp_id;
|
||||
pcm.params.hdr.size = sizeof(pcm.params);
|
||||
pcm.params.buffer.phy_addr = spcm->stream[substream->stream].page_table.addr;
|
||||
pcm.params.buffer.size = runtime->dma_bytes;
|
||||
pcm.params.direction = substream->stream;
|
||||
pcm.params.sample_valid_bytes = params_width(params) >> 3;
|
||||
pcm.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED;
|
||||
pcm.params.rate = params_rate(params);
|
||||
pcm.params.channels = params_channels(params);
|
||||
pcm.params.host_period_bytes = params_period_bytes(params);
|
||||
|
||||
/* container size */
|
||||
ret = snd_pcm_format_physical_width(params_format(params));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
pcm.params.sample_container_bytes = ret >> 3;
|
||||
|
||||
/* format */
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16:
|
||||
pcm.params.frame_fmt = SOF_IPC_FRAME_S16_LE;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24:
|
||||
pcm.params.frame_fmt = SOF_IPC_FRAME_S24_4LE;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S32:
|
||||
pcm.params.frame_fmt = SOF_IPC_FRAME_S32_LE;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_FLOAT:
|
||||
pcm.params.frame_fmt = SOF_IPC_FRAME_FLOAT;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Update the IPC message with information from the platform */
|
||||
pcm.params.stream_tag = platform_params->stream_tag;
|
||||
|
||||
if (platform_params->use_phy_address)
|
||||
pcm.params.buffer.phy_addr = platform_params->phy_addr;
|
||||
|
||||
if (platform_params->no_ipc_position) {
|
||||
/* For older ABIs set host_period_bytes to zero to inform
|
||||
* FW we don't want position updates. Newer versions use
|
||||
* no_stream_position for this purpose.
|
||||
*/
|
||||
if (v->abi_version < SOF_ABI_VER(3, 10, 0))
|
||||
pcm.params.host_period_bytes = 0;
|
||||
else
|
||||
pcm.params.no_stream_position = 1;
|
||||
}
|
||||
|
||||
dev_dbg(component->dev, "stream_tag %d", pcm.params.stream_tag);
|
||||
|
||||
/* send hw_params IPC to the DSP */
|
||||
ret = sof_ipc_tx_message(sdev->ipc, pcm.hdr.cmd, &pcm, sizeof(pcm),
|
||||
&ipc_params_reply, sizeof(ipc_params_reply));
|
||||
if (ret < 0) {
|
||||
dev_err(component->dev, "HW params ipc failed for stream %d\n",
|
||||
pcm.params.stream_tag);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_sof_set_stream_data_offset(sdev, substream, ipc_params_reply.posn_offset);
|
||||
if (ret < 0) {
|
||||
dev_err(component->dev, "%s: invalid stream data offset for PCM %d\n",
|
||||
__func__, spcm->pcm.pcm_id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sof_ipc3_pcm_trigger(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
||||
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
|
||||
struct sof_ipc_stream stream;
|
||||
struct sof_ipc_reply reply;
|
||||
struct snd_sof_pcm *spcm;
|
||||
|
||||
spcm = snd_sof_find_spcm_dai(component, rtd);
|
||||
if (!spcm)
|
||||
return -EINVAL;
|
||||
|
||||
stream.hdr.size = sizeof(stream);
|
||||
stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG;
|
||||
stream.comp_id = spcm->stream[substream->stream].comp_id;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_PAUSE;
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_RELEASE;
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_START;
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
fallthrough;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_STOP;
|
||||
break;
|
||||
default:
|
||||
dev_err(component->dev, "Unhandled trigger cmd %d\n", cmd);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* send IPC to the DSP */
|
||||
return sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream,
|
||||
sizeof(stream), &reply, sizeof(reply));
|
||||
}
|
||||
|
||||
static void ssp_dai_config_pcm_params_match(struct snd_sof_dev *sdev, const char *link_name,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct sof_ipc_dai_config *config;
|
||||
struct snd_sof_dai *dai;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* Search for all matching DAIs as we can have both playback and capture DAI
|
||||
* associated with the same link.
|
||||
*/
|
||||
list_for_each_entry(dai, &sdev->dai_list, list) {
|
||||
if (!dai->name || strcmp(link_name, dai->name))
|
||||
continue;
|
||||
for (i = 0; i < dai->number_configs; i++) {
|
||||
struct sof_dai_private_data *private = dai->private;
|
||||
|
||||
config = &private->dai_config[i];
|
||||
if (config->ssp.fsync_rate == params_rate(params)) {
|
||||
dev_dbg(sdev->dev, "DAI config %d matches pcm hw params\n", i);
|
||||
dai->current_config = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int sof_ipc3_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME);
|
||||
struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
|
||||
struct snd_sof_dai *dai = snd_sof_find_dai(component, (char *)rtd->dai_link->name);
|
||||
struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
|
||||
struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
|
||||
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
|
||||
struct sof_dai_private_data *private;
|
||||
struct snd_soc_dpcm *dpcm;
|
||||
|
||||
if (!dai) {
|
||||
dev_err(component->dev, "%s: No DAI found with name %s\n", __func__,
|
||||
rtd->dai_link->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
private = dai->private;
|
||||
if (!private) {
|
||||
dev_err(component->dev, "%s: No private data found for DAI %s\n", __func__,
|
||||
rtd->dai_link->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* read format from topology */
|
||||
snd_mask_none(fmt);
|
||||
|
||||
switch (private->comp_dai->config.frame_fmt) {
|
||||
case SOF_IPC_FRAME_S16_LE:
|
||||
snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE);
|
||||
break;
|
||||
case SOF_IPC_FRAME_S24_4LE:
|
||||
snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE);
|
||||
break;
|
||||
case SOF_IPC_FRAME_S32_LE:
|
||||
snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE);
|
||||
break;
|
||||
default:
|
||||
dev_err(component->dev, "No available DAI format!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* read rate and channels from topology */
|
||||
switch (private->dai_config->type) {
|
||||
case SOF_DAI_INTEL_SSP:
|
||||
/* search for config to pcm params match, if not found use default */
|
||||
ssp_dai_config_pcm_params_match(sdev, (char *)rtd->dai_link->name, params);
|
||||
|
||||
rate->min = private->dai_config[dai->current_config].ssp.fsync_rate;
|
||||
rate->max = private->dai_config[dai->current_config].ssp.fsync_rate;
|
||||
channels->min = private->dai_config[dai->current_config].ssp.tdm_slots;
|
||||
channels->max = private->dai_config[dai->current_config].ssp.tdm_slots;
|
||||
|
||||
dev_dbg(component->dev, "rate_min: %d rate_max: %d\n", rate->min, rate->max);
|
||||
dev_dbg(component->dev, "channels_min: %d channels_max: %d\n",
|
||||
channels->min, channels->max);
|
||||
|
||||
break;
|
||||
case SOF_DAI_INTEL_DMIC:
|
||||
/* DMIC only supports 16 or 32 bit formats */
|
||||
if (private->comp_dai->config.frame_fmt == SOF_IPC_FRAME_S24_4LE) {
|
||||
dev_err(component->dev, "Invalid fmt %d for DAI type %d\n",
|
||||
private->comp_dai->config.frame_fmt,
|
||||
private->dai_config->type);
|
||||
}
|
||||
break;
|
||||
case SOF_DAI_INTEL_HDA:
|
||||
/*
|
||||
* HDAudio does not follow the default trigger
|
||||
* sequence due to firmware implementation
|
||||
*/
|
||||
for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_PLAYBACK, dpcm) {
|
||||
struct snd_soc_pcm_runtime *fe = dpcm->fe;
|
||||
|
||||
fe->dai_link->trigger[SNDRV_PCM_STREAM_PLAYBACK] =
|
||||
SND_SOC_DPCM_TRIGGER_POST;
|
||||
}
|
||||
break;
|
||||
case SOF_DAI_INTEL_ALH:
|
||||
/*
|
||||
* Dai could run with different channel count compared with
|
||||
* front end, so get dai channel count from topology
|
||||
*/
|
||||
channels->min = private->dai_config->alh.channels;
|
||||
channels->max = private->dai_config->alh.channels;
|
||||
break;
|
||||
case SOF_DAI_IMX_ESAI:
|
||||
rate->min = private->dai_config->esai.fsync_rate;
|
||||
rate->max = private->dai_config->esai.fsync_rate;
|
||||
channels->min = private->dai_config->esai.tdm_slots;
|
||||
channels->max = private->dai_config->esai.tdm_slots;
|
||||
|
||||
dev_dbg(component->dev, "rate_min: %d rate_max: %d\n", rate->min, rate->max);
|
||||
dev_dbg(component->dev, "channels_min: %d channels_max: %d\n",
|
||||
channels->min, channels->max);
|
||||
break;
|
||||
case SOF_DAI_MEDIATEK_AFE:
|
||||
rate->min = private->dai_config->afe.rate;
|
||||
rate->max = private->dai_config->afe.rate;
|
||||
channels->min = private->dai_config->afe.channels;
|
||||
channels->max = private->dai_config->afe.channels;
|
||||
|
||||
dev_dbg(component->dev, "rate_min: %d rate_max: %d\n", rate->min, rate->max);
|
||||
dev_dbg(component->dev, "channels_min: %d channels_max: %d\n",
|
||||
channels->min, channels->max);
|
||||
break;
|
||||
case SOF_DAI_IMX_SAI:
|
||||
rate->min = private->dai_config->sai.fsync_rate;
|
||||
rate->max = private->dai_config->sai.fsync_rate;
|
||||
channels->min = private->dai_config->sai.tdm_slots;
|
||||
channels->max = private->dai_config->sai.tdm_slots;
|
||||
|
||||
dev_dbg(component->dev, "rate_min: %d rate_max: %d\n", rate->min, rate->max);
|
||||
dev_dbg(component->dev, "channels_min: %d channels_max: %d\n",
|
||||
channels->min, channels->max);
|
||||
break;
|
||||
case SOF_DAI_AMD_BT:
|
||||
rate->min = private->dai_config->acpbt.fsync_rate;
|
||||
rate->max = private->dai_config->acpbt.fsync_rate;
|
||||
channels->min = private->dai_config->acpbt.tdm_slots;
|
||||
channels->max = private->dai_config->acpbt.tdm_slots;
|
||||
|
||||
dev_dbg(component->dev,
|
||||
"AMD_BT rate_min: %d rate_max: %d\n", rate->min, rate->max);
|
||||
dev_dbg(component->dev, "AMD_BT channels_min: %d channels_max: %d\n",
|
||||
channels->min, channels->max);
|
||||
break;
|
||||
case SOF_DAI_AMD_SP:
|
||||
rate->min = private->dai_config->acpsp.fsync_rate;
|
||||
rate->max = private->dai_config->acpsp.fsync_rate;
|
||||
channels->min = private->dai_config->acpsp.tdm_slots;
|
||||
channels->max = private->dai_config->acpsp.tdm_slots;
|
||||
|
||||
dev_dbg(component->dev,
|
||||
"AMD_SP rate_min: %d rate_max: %d\n", rate->min, rate->max);
|
||||
dev_dbg(component->dev, "AMD_SP channels_min: %d channels_max: %d\n",
|
||||
channels->min, channels->max);
|
||||
break;
|
||||
case SOF_DAI_AMD_DMIC:
|
||||
rate->min = private->dai_config->acpdmic.fsync_rate;
|
||||
rate->max = private->dai_config->acpdmic.fsync_rate;
|
||||
channels->min = private->dai_config->acpdmic.tdm_slots;
|
||||
channels->max = private->dai_config->acpdmic.tdm_slots;
|
||||
|
||||
dev_dbg(component->dev,
|
||||
"AMD_DMIC rate_min: %d rate_max: %d\n", rate->min, rate->max);
|
||||
dev_dbg(component->dev, "AMD_DMIC channels_min: %d channels_max: %d\n",
|
||||
channels->min, channels->max);
|
||||
break;
|
||||
default:
|
||||
dev_err(component->dev, "Invalid DAI type %d\n", private->dai_config->type);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct sof_ipc_pcm_ops ipc3_pcm_ops = {
|
||||
.hw_params = sof_ipc3_pcm_hw_params,
|
||||
.hw_free = sof_ipc3_pcm_hw_free,
|
||||
.trigger = sof_ipc3_pcm_trigger,
|
||||
.dai_link_fixup = sof_ipc3_pcm_dai_link_fixup,
|
||||
};
|
@ -11,6 +11,7 @@
|
||||
#include <sound/pcm_params.h>
|
||||
#include "sof-priv.h"
|
||||
#include "sof-audio.h"
|
||||
#include "ipc3-ops.h"
|
||||
#include "ops.h"
|
||||
|
||||
/* Full volume for default values */
|
||||
@ -1909,6 +1910,376 @@ static int sof_ipc3_complete_pipeline(struct snd_sof_dev *sdev, struct snd_sof_w
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int sof_ipc3_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
|
||||
{
|
||||
struct sof_ipc_free ipc_free = {
|
||||
.hdr = {
|
||||
.size = sizeof(ipc_free),
|
||||
.cmd = SOF_IPC_GLB_TPLG_MSG,
|
||||
},
|
||||
.id = swidget->comp_id,
|
||||
};
|
||||
struct sof_ipc_reply reply;
|
||||
int ret;
|
||||
|
||||
if (!swidget->private)
|
||||
return 0;
|
||||
|
||||
switch (swidget->id) {
|
||||
case snd_soc_dapm_scheduler:
|
||||
{
|
||||
ipc_free.hdr.cmd |= SOF_IPC_TPLG_PIPE_FREE;
|
||||
break;
|
||||
}
|
||||
case snd_soc_dapm_buffer:
|
||||
ipc_free.hdr.cmd |= SOF_IPC_TPLG_BUFFER_FREE;
|
||||
break;
|
||||
default:
|
||||
ipc_free.hdr.cmd |= SOF_IPC_TPLG_COMP_FREE;
|
||||
break;
|
||||
}
|
||||
|
||||
ret = sof_ipc_tx_message(sdev->ipc, ipc_free.hdr.cmd, &ipc_free, sizeof(ipc_free),
|
||||
&reply, sizeof(reply));
|
||||
if (ret < 0)
|
||||
dev_err(sdev->dev, "failed to free widget %s\n", swidget->widget->name);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sof_ipc3_dai_config(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget,
|
||||
unsigned int flags, struct snd_sof_dai_config_data *data)
|
||||
{
|
||||
struct sof_ipc_fw_version *v = &sdev->fw_ready.version;
|
||||
struct snd_sof_dai *dai = swidget->private;
|
||||
struct sof_dai_private_data *private;
|
||||
struct sof_ipc_dai_config *config;
|
||||
struct sof_ipc_reply reply;
|
||||
int ret = 0;
|
||||
|
||||
if (!dai || !dai->private) {
|
||||
dev_err(sdev->dev, "No private data for DAI %s\n", swidget->widget->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
private = dai->private;
|
||||
if (!private->dai_config) {
|
||||
dev_err(sdev->dev, "No config for DAI %s\n", dai->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
config = &private->dai_config[dai->current_config];
|
||||
if (!config) {
|
||||
dev_err(sdev->dev, "Invalid current config for DAI %s\n", dai->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (config->type) {
|
||||
case SOF_DAI_INTEL_SSP:
|
||||
/*
|
||||
* DAI_CONFIG IPC during hw_params/hw_free for SSP DAI's is not supported in older
|
||||
* firmware
|
||||
*/
|
||||
if (v->abi_version < SOF_ABI_VER(3, 18, 0) &&
|
||||
((flags & SOF_DAI_CONFIG_FLAGS_HW_PARAMS) ||
|
||||
(flags & SOF_DAI_CONFIG_FLAGS_HW_FREE)))
|
||||
return 0;
|
||||
break;
|
||||
case SOF_DAI_INTEL_HDA:
|
||||
if (data)
|
||||
config->hda.link_dma_ch = data->dai_data;
|
||||
break;
|
||||
case SOF_DAI_INTEL_ALH:
|
||||
if (data) {
|
||||
config->dai_index = data->dai_index;
|
||||
config->alh.stream_id = data->dai_data;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
config->flags = flags;
|
||||
|
||||
/* only send the IPC if the widget is set up in the DSP */
|
||||
if (swidget->use_count > 0) {
|
||||
ret = sof_ipc_tx_message(sdev->ipc, config->hdr.cmd, config, config->hdr.size,
|
||||
&reply, sizeof(reply));
|
||||
if (ret < 0)
|
||||
dev_err(sdev->dev, "Failed to set dai config for %s\n", dai->name);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sof_ipc3_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
|
||||
{
|
||||
struct sof_ipc_comp_reply reply;
|
||||
int ret;
|
||||
|
||||
if (!swidget->private)
|
||||
return 0;
|
||||
|
||||
switch (swidget->id) {
|
||||
case snd_soc_dapm_dai_in:
|
||||
case snd_soc_dapm_dai_out:
|
||||
{
|
||||
struct snd_sof_dai *dai = swidget->private;
|
||||
struct sof_dai_private_data *dai_data = dai->private;
|
||||
struct sof_ipc_comp *comp = &dai_data->comp_dai->comp;
|
||||
|
||||
ret = sof_ipc_tx_message(sdev->ipc, comp->hdr.cmd, dai_data->comp_dai,
|
||||
comp->hdr.size, &reply, sizeof(reply));
|
||||
break;
|
||||
}
|
||||
case snd_soc_dapm_scheduler:
|
||||
{
|
||||
struct sof_ipc_pipe_new *pipeline;
|
||||
|
||||
pipeline = swidget->private;
|
||||
ret = sof_ipc_tx_message(sdev->ipc, pipeline->hdr.cmd, pipeline,
|
||||
sizeof(*pipeline), &reply, sizeof(reply));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
struct sof_ipc_cmd_hdr *hdr;
|
||||
|
||||
hdr = swidget->private;
|
||||
ret = sof_ipc_tx_message(sdev->ipc, hdr->cmd, swidget->private, hdr->size,
|
||||
&reply, sizeof(reply));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ret < 0)
|
||||
dev_err(sdev->dev, "Failed to setup widget %s\n", swidget->widget->name);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sof_ipc3_set_up_all_pipelines(struct snd_sof_dev *sdev, bool verify)
|
||||
{
|
||||
struct sof_ipc_fw_version *v = &sdev->fw_ready.version;
|
||||
struct snd_sof_widget *swidget;
|
||||
struct snd_sof_route *sroute;
|
||||
int ret;
|
||||
|
||||
/* restore pipeline components */
|
||||
list_for_each_entry(swidget, &sdev->widget_list, list) {
|
||||
/* only set up the widgets belonging to static pipelines */
|
||||
if (!verify && swidget->dynamic_pipeline_widget)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* For older firmware, skip scheduler widgets in this loop,
|
||||
* sof_widget_setup() will be called in the 'complete pipeline' loop
|
||||
*/
|
||||
if (v->abi_version < SOF_ABI_VER(3, 19, 0) &&
|
||||
swidget->id == snd_soc_dapm_scheduler)
|
||||
continue;
|
||||
|
||||
/* update DAI config. The IPC will be sent in sof_widget_setup() */
|
||||
if (WIDGET_IS_DAI(swidget->id)) {
|
||||
struct snd_sof_dai *dai = swidget->private;
|
||||
struct sof_dai_private_data *private;
|
||||
struct sof_ipc_dai_config *config;
|
||||
|
||||
if (!dai || !dai->private)
|
||||
continue;
|
||||
private = dai->private;
|
||||
if (!private->dai_config)
|
||||
continue;
|
||||
|
||||
config = private->dai_config;
|
||||
/*
|
||||
* The link DMA channel would be invalidated for running
|
||||
* streams but not for streams that were in the PAUSED
|
||||
* state during suspend. So invalidate it here before setting
|
||||
* the dai config in the DSP.
|
||||
*/
|
||||
if (config->type == SOF_DAI_INTEL_HDA)
|
||||
config->hda.link_dma_ch = DMA_CHAN_INVALID;
|
||||
}
|
||||
|
||||
ret = sof_widget_setup(sdev, swidget);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* restore pipeline connections */
|
||||
list_for_each_entry(sroute, &sdev->route_list, list) {
|
||||
/* only set up routes belonging to static pipelines */
|
||||
if (!verify && (sroute->src_widget->dynamic_pipeline_widget ||
|
||||
sroute->sink_widget->dynamic_pipeline_widget))
|
||||
continue;
|
||||
|
||||
/*
|
||||
* For virtual routes, both sink and source are not buffer. IPC3 only supports
|
||||
* connections between a buffer and a component. Ignore the rest.
|
||||
*/
|
||||
if (sroute->src_widget->id != snd_soc_dapm_buffer &&
|
||||
sroute->sink_widget->id != snd_soc_dapm_buffer)
|
||||
continue;
|
||||
|
||||
ret = sof_route_setup(sdev, sroute->src_widget->widget,
|
||||
sroute->sink_widget->widget);
|
||||
if (ret < 0) {
|
||||
dev_err(sdev->dev, "%s: route set up failed\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* complete pipeline */
|
||||
list_for_each_entry(swidget, &sdev->widget_list, list) {
|
||||
switch (swidget->id) {
|
||||
case snd_soc_dapm_scheduler:
|
||||
/* only complete static pipelines */
|
||||
if (!verify && swidget->dynamic_pipeline_widget)
|
||||
continue;
|
||||
|
||||
if (v->abi_version < SOF_ABI_VER(3, 19, 0)) {
|
||||
ret = sof_widget_setup(sdev, swidget);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
swidget->complete = sof_ipc3_complete_pipeline(sdev, swidget);
|
||||
if (swidget->complete < 0)
|
||||
return swidget->complete;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Free the PCM, its associated widgets and set the prepared flag to false for all PCMs that
|
||||
* did not get suspended(ex: paused streams) so the widgets can be set up again during resume.
|
||||
*/
|
||||
static int sof_tear_down_left_over_pipelines(struct snd_sof_dev *sdev)
|
||||
{
|
||||
struct snd_sof_widget *swidget;
|
||||
struct snd_sof_pcm *spcm;
|
||||
int dir, ret;
|
||||
|
||||
/*
|
||||
* free all PCMs and their associated DAPM widgets if their connected DAPM widget
|
||||
* list is not NULL. This should only be true for paused streams at this point.
|
||||
* This is equivalent to the handling of FE DAI suspend trigger for running streams.
|
||||
*/
|
||||
list_for_each_entry(spcm, &sdev->pcm_list, list) {
|
||||
for_each_pcm_streams(dir) {
|
||||
struct snd_pcm_substream *substream = spcm->stream[dir].substream;
|
||||
|
||||
if (!substream || !substream->runtime)
|
||||
continue;
|
||||
|
||||
if (spcm->stream[dir].list) {
|
||||
ret = sof_pcm_stream_free(sdev, substream, spcm, dir, true);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* free any left over DAI widgets. This is equivalent to the handling of suspend trigger
|
||||
* for the BE DAI for running streams.
|
||||
*/
|
||||
list_for_each_entry(swidget, &sdev->widget_list, list)
|
||||
if (WIDGET_IS_DAI(swidget->id) && swidget->use_count == 1) {
|
||||
ret = sof_widget_free(sdev, swidget);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* For older firmware, this function doesn't free widgets for static pipelines during suspend.
|
||||
* It only resets use_count for all widgets.
|
||||
*/
|
||||
static int sof_ipc3_tear_down_all_pipelines(struct snd_sof_dev *sdev, bool verify)
|
||||
{
|
||||
struct sof_ipc_fw_version *v = &sdev->fw_ready.version;
|
||||
struct snd_sof_widget *swidget;
|
||||
struct snd_sof_route *sroute;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* This function is called during suspend and for one-time topology verification during
|
||||
* first boot. In both cases, there is no need to protect swidget->use_count and
|
||||
* sroute->setup because during suspend all running streams are suspended and during
|
||||
* topology loading the sound card unavailable to open PCMs.
|
||||
*/
|
||||
list_for_each_entry(swidget, &sdev->widget_list, list) {
|
||||
if (swidget->dynamic_pipeline_widget)
|
||||
continue;
|
||||
|
||||
/* Do not free widgets for static pipelines with FW ABI older than 3.19 */
|
||||
if (!verify && !swidget->dynamic_pipeline_widget &&
|
||||
v->abi_version < SOF_ABI_VER(3, 19, 0)) {
|
||||
swidget->use_count = 0;
|
||||
swidget->complete = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = sof_widget_free(sdev, swidget);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Tear down all pipelines associated with PCMs that did not get suspended
|
||||
* and unset the prepare flag so that they can be set up again during resume.
|
||||
* Skip this step for older firmware.
|
||||
*/
|
||||
if (!verify && v->abi_version >= SOF_ABI_VER(3, 19, 0)) {
|
||||
ret = sof_tear_down_left_over_pipelines(sdev);
|
||||
if (ret < 0) {
|
||||
dev_err(sdev->dev, "failed to tear down paused pipelines\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
list_for_each_entry(sroute, &sdev->route_list, list)
|
||||
sroute->setup = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sof_ipc3_dai_get_clk(struct snd_sof_dev *sdev, struct snd_sof_dai *dai, int clk_type)
|
||||
{
|
||||
struct sof_dai_private_data *private = dai->private;
|
||||
|
||||
if (!private || !private->dai_config)
|
||||
return 0;
|
||||
|
||||
switch (private->dai_config->type) {
|
||||
case SOF_DAI_INTEL_SSP:
|
||||
switch (clk_type) {
|
||||
case SOF_DAI_CLK_INTEL_SSP_MCLK:
|
||||
return private->dai_config->ssp.mclk_rate;
|
||||
case SOF_DAI_CLK_INTEL_SSP_BCLK:
|
||||
return private->dai_config->ssp.bclk_rate;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
dev_err(sdev->dev, "fail to get SSP clk %d rate\n", clk_type);
|
||||
break;
|
||||
default:
|
||||
/* not yet implemented for platforms other than the above */
|
||||
dev_err(sdev->dev, "DAI type %d not supported yet!\n", private->dai_config->type);
|
||||
break;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* token list for each topology object */
|
||||
static enum sof_tokens host_token_list[] = {
|
||||
SOF_CORE_TOKENS,
|
||||
@ -2005,15 +2376,18 @@ static const struct sof_ipc_tplg_widget_ops tplg_ipc3_widget_ops[SND_SOC_DAPM_TY
|
||||
sof_ipc3_widget_bind_event},
|
||||
};
|
||||
|
||||
static const struct sof_ipc_tplg_ops ipc3_tplg_ops = {
|
||||
const struct sof_ipc_tplg_ops ipc3_tplg_ops = {
|
||||
.widget = tplg_ipc3_widget_ops,
|
||||
.control = &tplg_ipc3_control_ops,
|
||||
.route_setup = sof_ipc3_route_setup,
|
||||
.control_setup = sof_ipc3_control_setup,
|
||||
.control_free = sof_ipc3_control_free,
|
||||
.pipeline_complete = sof_ipc3_complete_pipeline,
|
||||
.token_list = ipc3_token_list,
|
||||
};
|
||||
|
||||
const struct sof_ipc_ops ipc3_ops = {
|
||||
.tplg = &ipc3_tplg_ops,
|
||||
.widget_free = sof_ipc3_widget_free,
|
||||
.widget_setup = sof_ipc3_widget_setup,
|
||||
.dai_config = sof_ipc3_dai_config,
|
||||
.dai_get_clk = sof_ipc3_dai_get_clk,
|
||||
.set_up_all_pipelines = sof_ipc3_set_up_all_pipelines,
|
||||
.tear_down_all_pipelines = sof_ipc3_tear_down_all_pipelines,
|
||||
};
|
||||
|
45
sound/soc/sof/ipc3.c
Normal file
45
sound/soc/sof/ipc3.c
Normal file
@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
|
||||
//
|
||||
// This file is provided under a dual BSD/GPLv2 license. When using or
|
||||
// redistributing this file, you may do so under either license.
|
||||
//
|
||||
// Copyright(c) 2021 Intel Corporation. All rights reserved.
|
||||
//
|
||||
//
|
||||
|
||||
#include "sof-priv.h"
|
||||
#include "ipc3-ops.h"
|
||||
|
||||
static int sof_ipc3_ctx_ipc(struct snd_sof_dev *sdev, int cmd)
|
||||
{
|
||||
struct sof_ipc_pm_ctx pm_ctx = {
|
||||
.hdr.size = sizeof(pm_ctx),
|
||||
.hdr.cmd = SOF_IPC_GLB_PM_MSG | cmd,
|
||||
};
|
||||
struct sof_ipc_reply reply;
|
||||
|
||||
/* send ctx save ipc to dsp */
|
||||
return sof_ipc_tx_message(sdev->ipc, pm_ctx.hdr.cmd, &pm_ctx,
|
||||
sizeof(pm_ctx), &reply, sizeof(reply));
|
||||
}
|
||||
|
||||
static int sof_ipc3_ctx_save(struct snd_sof_dev *sdev)
|
||||
{
|
||||
return sof_ipc3_ctx_ipc(sdev, SOF_IPC_PM_CTX_SAVE);
|
||||
}
|
||||
|
||||
static int sof_ipc3_ctx_restore(struct snd_sof_dev *sdev)
|
||||
{
|
||||
return sof_ipc3_ctx_ipc(sdev, SOF_IPC_PM_CTX_RESTORE);
|
||||
}
|
||||
|
||||
static const struct sof_ipc_pm_ops ipc3_pm_ops = {
|
||||
.ctx_save = sof_ipc3_ctx_save,
|
||||
.ctx_restore = sof_ipc3_ctx_restore,
|
||||
};
|
||||
|
||||
const struct sof_ipc_ops ipc3_ops = {
|
||||
.tplg = &ipc3_tplg_ops,
|
||||
.pm = &ipc3_pm_ops,
|
||||
.pcm = &ipc3_pcm_ops,
|
||||
};
|
@ -82,32 +82,8 @@ void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream)
|
||||
}
|
||||
EXPORT_SYMBOL(snd_sof_pcm_period_elapsed);
|
||||
|
||||
int sof_pcm_dsp_pcm_free(struct snd_pcm_substream *substream, struct snd_sof_dev *sdev,
|
||||
struct snd_sof_pcm *spcm)
|
||||
{
|
||||
struct sof_ipc_stream stream;
|
||||
struct sof_ipc_reply reply;
|
||||
int ret;
|
||||
|
||||
if (!spcm->prepared[substream->stream])
|
||||
return 0;
|
||||
|
||||
stream.hdr.size = sizeof(stream);
|
||||
stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_FREE;
|
||||
stream.comp_id = spcm->stream[substream->stream].comp_id;
|
||||
|
||||
/* send IPC to the DSP */
|
||||
ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream,
|
||||
sizeof(stream), &reply, sizeof(reply));
|
||||
if (!ret)
|
||||
spcm->prepared[substream->stream] = false;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sof_pcm_setup_connected_widgets(struct snd_sof_dev *sdev,
|
||||
struct snd_soc_pcm_runtime *rtd,
|
||||
struct snd_sof_pcm *spcm, int dir)
|
||||
int sof_pcm_setup_connected_widgets(struct snd_sof_dev *sdev, struct snd_soc_pcm_runtime *rtd,
|
||||
struct snd_sof_pcm *spcm, int dir)
|
||||
{
|
||||
struct snd_soc_dai *dai;
|
||||
int ret, j;
|
||||
@ -143,14 +119,12 @@ static int sof_pcm_hw_params(struct snd_soc_component *component,
|
||||
struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
|
||||
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
||||
struct snd_sof_platform_stream_params platform_params = { 0 };
|
||||
struct sof_ipc_fw_version *v = &sdev->fw_ready.version;
|
||||
const struct sof_ipc_pcm_ops *pcm_ops = sdev->ipc->ops->pcm;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct snd_sof_pcm *spcm;
|
||||
struct sof_ipc_pcm_params pcm;
|
||||
struct sof_ipc_pcm_params_reply ipc_params_reply;
|
||||
int ret;
|
||||
|
||||
/* nothing to do for BE */
|
||||
@ -165,94 +139,17 @@ static int sof_pcm_hw_params(struct snd_soc_component *component,
|
||||
* Handle repeated calls to hw_params() without free_pcm() in
|
||||
* between. At least ALSA OSS emulation depends on this.
|
||||
*/
|
||||
ret = sof_pcm_dsp_pcm_free(substream, sdev, spcm);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (pcm_ops->hw_free && spcm->prepared[substream->stream]) {
|
||||
ret = pcm_ops->hw_free(component, substream);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
spcm->prepared[substream->stream] = false;
|
||||
}
|
||||
|
||||
dev_dbg(component->dev, "pcm: hw params stream %d dir %d\n",
|
||||
spcm->pcm.pcm_id, substream->stream);
|
||||
|
||||
memset(&pcm, 0, sizeof(pcm));
|
||||
|
||||
/* create compressed page table for audio firmware */
|
||||
if (runtime->buffer_changed) {
|
||||
ret = create_page_table(component, substream, runtime->dma_area,
|
||||
runtime->dma_bytes);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* number of pages should be rounded up */
|
||||
pcm.params.buffer.pages = PFN_UP(runtime->dma_bytes);
|
||||
|
||||
/* set IPC PCM parameters */
|
||||
pcm.hdr.size = sizeof(pcm);
|
||||
pcm.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | SOF_IPC_STREAM_PCM_PARAMS;
|
||||
pcm.comp_id = spcm->stream[substream->stream].comp_id;
|
||||
pcm.params.hdr.size = sizeof(pcm.params);
|
||||
pcm.params.buffer.phy_addr =
|
||||
spcm->stream[substream->stream].page_table.addr;
|
||||
pcm.params.buffer.size = runtime->dma_bytes;
|
||||
pcm.params.direction = substream->stream;
|
||||
pcm.params.sample_valid_bytes = params_width(params) >> 3;
|
||||
pcm.params.buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED;
|
||||
pcm.params.rate = params_rate(params);
|
||||
pcm.params.channels = params_channels(params);
|
||||
pcm.params.host_period_bytes = params_period_bytes(params);
|
||||
|
||||
/* container size */
|
||||
ret = snd_pcm_format_physical_width(params_format(params));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
pcm.params.sample_container_bytes = ret >> 3;
|
||||
|
||||
/* format */
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16:
|
||||
pcm.params.frame_fmt = SOF_IPC_FRAME_S16_LE;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24:
|
||||
pcm.params.frame_fmt = SOF_IPC_FRAME_S24_4LE;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S32:
|
||||
pcm.params.frame_fmt = SOF_IPC_FRAME_S32_LE;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_FLOAT:
|
||||
pcm.params.frame_fmt = SOF_IPC_FRAME_FLOAT;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* firmware already configured host stream */
|
||||
ret = snd_sof_pcm_platform_hw_params(sdev,
|
||||
substream,
|
||||
params,
|
||||
&platform_params);
|
||||
if (ret < 0) {
|
||||
dev_err(component->dev, "error: platform hw params failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Update the IPC message with information from the platform */
|
||||
pcm.params.stream_tag = platform_params.stream_tag;
|
||||
|
||||
if (platform_params.use_phy_address)
|
||||
pcm.params.buffer.phy_addr = platform_params.phy_addr;
|
||||
|
||||
if (platform_params.no_ipc_position) {
|
||||
/* For older ABIs set host_period_bytes to zero to inform
|
||||
* FW we don't want position updates. Newer versions use
|
||||
* no_stream_position for this purpose.
|
||||
*/
|
||||
if (v->abi_version < SOF_ABI_VER(3, 10, 0))
|
||||
pcm.params.host_period_bytes = 0;
|
||||
else
|
||||
pcm.params.no_stream_position = 1;
|
||||
}
|
||||
|
||||
dev_dbg(component->dev, "stream_tag %d", pcm.params.stream_tag);
|
||||
|
||||
/* if this is a repeated hw_params without hw_free, skip setting up widgets */
|
||||
if (!spcm->stream[substream->stream].list) {
|
||||
ret = sof_pcm_setup_connected_widgets(sdev, rtd, spcm, substream->stream);
|
||||
@ -260,21 +157,25 @@ static int sof_pcm_hw_params(struct snd_soc_component *component,
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* send hw_params IPC to the DSP */
|
||||
ret = sof_ipc_tx_message(sdev->ipc, pcm.hdr.cmd, &pcm, sizeof(pcm),
|
||||
&ipc_params_reply, sizeof(ipc_params_reply));
|
||||
/* create compressed page table for audio firmware */
|
||||
if (runtime->buffer_changed) {
|
||||
ret = create_page_table(component, substream, runtime->dma_area,
|
||||
runtime->dma_bytes);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_sof_pcm_platform_hw_params(sdev, substream, params, &platform_params);
|
||||
if (ret < 0) {
|
||||
dev_err(component->dev, "error: hw params ipc failed for stream %d\n",
|
||||
pcm.params.stream_tag);
|
||||
dev_err(component->dev, "platform hw params failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_sof_set_stream_data_offset(sdev, substream,
|
||||
ipc_params_reply.posn_offset);
|
||||
if (ret < 0) {
|
||||
dev_err(component->dev, "%s: invalid stream data offset for PCM %d\n",
|
||||
__func__, spcm->pcm.pcm_id);
|
||||
return ret;
|
||||
if (pcm_ops->hw_params) {
|
||||
ret = pcm_ops->hw_params(component, substream, params, &platform_params);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
spcm->prepared[substream->stream] = true;
|
||||
@ -282,7 +183,7 @@ static int sof_pcm_hw_params(struct snd_soc_component *component,
|
||||
/* save pcm hw_params */
|
||||
memcpy(&spcm->params[substream->stream], params, sizeof(*params));
|
||||
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sof_pcm_hw_free(struct snd_soc_component *component,
|
||||
@ -290,6 +191,7 @@ static int sof_pcm_hw_free(struct snd_soc_component *component,
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
||||
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
|
||||
const struct sof_ipc_pcm_ops *pcm_ops = sdev->ipc->ops->pcm;
|
||||
struct snd_sof_pcm *spcm;
|
||||
int ret, err = 0;
|
||||
|
||||
@ -305,10 +207,13 @@ static int sof_pcm_hw_free(struct snd_soc_component *component,
|
||||
spcm->pcm.pcm_id, substream->stream);
|
||||
|
||||
/* free PCM in the DSP */
|
||||
ret = sof_pcm_dsp_pcm_free(substream, sdev, spcm);
|
||||
if (ret < 0)
|
||||
err = ret;
|
||||
if (pcm_ops->hw_free && spcm->prepared[substream->stream]) {
|
||||
ret = pcm_ops->hw_free(component, substream);
|
||||
if (ret < 0)
|
||||
err = ret;
|
||||
|
||||
spcm->prepared[substream->stream] = false;
|
||||
}
|
||||
|
||||
/* stop DMA */
|
||||
ret = snd_sof_pcm_platform_hw_free(sdev, substream);
|
||||
@ -369,13 +274,12 @@ static int sof_pcm_trigger(struct snd_soc_component *component,
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
||||
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
|
||||
const struct sof_ipc_pcm_ops *pcm_ops = sdev->ipc->ops->pcm;
|
||||
struct snd_sof_pcm *spcm;
|
||||
struct sof_ipc_stream stream;
|
||||
struct sof_ipc_reply reply;
|
||||
bool reset_hw_params = false;
|
||||
bool free_widget_list = false;
|
||||
bool ipc_first = false;
|
||||
int ret;
|
||||
int ret = 0;
|
||||
|
||||
/* nothing to do for BE */
|
||||
if (rtd->dai_link->no_pcm)
|
||||
@ -388,17 +292,11 @@ static int sof_pcm_trigger(struct snd_soc_component *component,
|
||||
dev_dbg(component->dev, "pcm: trigger stream %d dir %d cmd %d\n",
|
||||
spcm->pcm.pcm_id, substream->stream, cmd);
|
||||
|
||||
stream.hdr.size = sizeof(stream);
|
||||
stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG;
|
||||
stream.comp_id = spcm->stream[substream->stream].comp_id;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_PAUSE;
|
||||
ipc_first = true;
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_RELEASE;
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
if (spcm->stream[substream->stream].suspend_ignored) {
|
||||
@ -410,7 +308,6 @@ static int sof_pcm_trigger(struct snd_soc_component *component,
|
||||
spcm->stream[substream->stream].suspend_ignored = false;
|
||||
return 0;
|
||||
}
|
||||
stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_START;
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
if (sdev->system_suspend_target == SOF_SUSPEND_S0IX &&
|
||||
@ -427,13 +324,11 @@ static int sof_pcm_trigger(struct snd_soc_component *component,
|
||||
free_widget_list = true;
|
||||
fallthrough;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
stream.hdr.cmd |= SOF_IPC_STREAM_TRIG_STOP;
|
||||
ipc_first = true;
|
||||
reset_hw_params = true;
|
||||
break;
|
||||
default:
|
||||
dev_err(component->dev, "error: unhandled trigger cmd %d\n",
|
||||
cmd);
|
||||
dev_err(component->dev, "Unhandled trigger cmd %d\n", cmd);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@ -444,11 +339,10 @@ static int sof_pcm_trigger(struct snd_soc_component *component,
|
||||
if (!ipc_first)
|
||||
snd_sof_pcm_platform_trigger(sdev, substream, cmd);
|
||||
|
||||
/* send IPC to the DSP */
|
||||
ret = sof_ipc_tx_message(sdev->ipc, stream.hdr.cmd, &stream,
|
||||
sizeof(stream), &reply, sizeof(reply));
|
||||
if (pcm_ops->trigger)
|
||||
ret = pcm_ops->trigger(component, substream, cmd);
|
||||
|
||||
/* need to STOP DMA even if STOP IPC failed */
|
||||
/* need to STOP DMA even if trigger IPC failed */
|
||||
if (ipc_first)
|
||||
snd_sof_pcm_platform_trigger(sdev, substream, cmd);
|
||||
|
||||
@ -662,33 +556,6 @@ capture:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ssp_dai_config_pcm_params_match(struct snd_sof_dev *sdev, const char *link_name,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct sof_ipc_dai_config *config;
|
||||
struct snd_sof_dai *dai;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* Search for all matching DAIs as we can have both playback and capture DAI
|
||||
* associated with the same link.
|
||||
*/
|
||||
list_for_each_entry(dai, &sdev->dai_list, list) {
|
||||
if (!dai->name || strcmp(link_name, dai->name))
|
||||
continue;
|
||||
for (i = 0; i < dai->number_configs; i++) {
|
||||
struct sof_dai_private_data *private = dai->private;
|
||||
|
||||
config = &private->dai_config[i];
|
||||
if (config->ssp.fsync_rate == params_rate(params)) {
|
||||
dev_dbg(sdev->dev, "DAI config %d matches pcm hw params\n", i);
|
||||
dai->current_config = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* fixup the BE DAI link to match any values from topology */
|
||||
int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params)
|
||||
{
|
||||
@ -702,8 +569,7 @@ int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_pa
|
||||
struct snd_sof_dai *dai =
|
||||
snd_sof_find_dai(component, (char *)rtd->dai_link->name);
|
||||
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
|
||||
struct sof_dai_private_data *private = dai->private;
|
||||
struct snd_soc_dpcm *dpcm;
|
||||
const struct sof_ipc_pcm_ops *pcm_ops = sdev->ipc->ops->pcm;
|
||||
|
||||
/* no topology exists for this BE, try a common configuration */
|
||||
if (!dai) {
|
||||
@ -724,148 +590,8 @@ int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_pa
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* read format from topology */
|
||||
snd_mask_none(fmt);
|
||||
|
||||
switch (private->comp_dai->config.frame_fmt) {
|
||||
case SOF_IPC_FRAME_S16_LE:
|
||||
snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE);
|
||||
break;
|
||||
case SOF_IPC_FRAME_S24_4LE:
|
||||
snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE);
|
||||
break;
|
||||
case SOF_IPC_FRAME_S32_LE:
|
||||
snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S32_LE);
|
||||
break;
|
||||
default:
|
||||
dev_err(component->dev, "error: No available DAI format!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* read rate and channels from topology */
|
||||
switch (private->dai_config->type) {
|
||||
case SOF_DAI_INTEL_SSP:
|
||||
/* search for config to pcm params match, if not found use default */
|
||||
ssp_dai_config_pcm_params_match(sdev, (char *)rtd->dai_link->name, params);
|
||||
|
||||
rate->min = private->dai_config[dai->current_config].ssp.fsync_rate;
|
||||
rate->max = private->dai_config[dai->current_config].ssp.fsync_rate;
|
||||
channels->min = private->dai_config[dai->current_config].ssp.tdm_slots;
|
||||
channels->max = private->dai_config[dai->current_config].ssp.tdm_slots;
|
||||
|
||||
dev_dbg(component->dev,
|
||||
"rate_min: %d rate_max: %d\n", rate->min, rate->max);
|
||||
dev_dbg(component->dev,
|
||||
"channels_min: %d channels_max: %d\n",
|
||||
channels->min, channels->max);
|
||||
|
||||
break;
|
||||
case SOF_DAI_INTEL_DMIC:
|
||||
/* DMIC only supports 16 or 32 bit formats */
|
||||
if (private->comp_dai->config.frame_fmt == SOF_IPC_FRAME_S24_4LE) {
|
||||
dev_err(component->dev,
|
||||
"error: invalid fmt %d for DAI type %d\n",
|
||||
private->comp_dai->config.frame_fmt,
|
||||
private->dai_config->type);
|
||||
}
|
||||
break;
|
||||
case SOF_DAI_INTEL_HDA:
|
||||
/*
|
||||
* HDAudio does not follow the default trigger
|
||||
* sequence due to firmware implementation
|
||||
*/
|
||||
for_each_dpcm_fe(rtd, SNDRV_PCM_STREAM_PLAYBACK, dpcm) {
|
||||
struct snd_soc_pcm_runtime *fe = dpcm->fe;
|
||||
|
||||
fe->dai_link->trigger[SNDRV_PCM_STREAM_PLAYBACK] =
|
||||
SND_SOC_DPCM_TRIGGER_POST;
|
||||
}
|
||||
break;
|
||||
case SOF_DAI_INTEL_ALH:
|
||||
/*
|
||||
* Dai could run with different channel count compared with
|
||||
* front end, so get dai channel count from topology
|
||||
*/
|
||||
channels->min = private->dai_config->alh.channels;
|
||||
channels->max = private->dai_config->alh.channels;
|
||||
break;
|
||||
case SOF_DAI_IMX_ESAI:
|
||||
rate->min = private->dai_config->esai.fsync_rate;
|
||||
rate->max = private->dai_config->esai.fsync_rate;
|
||||
channels->min = private->dai_config->esai.tdm_slots;
|
||||
channels->max = private->dai_config->esai.tdm_slots;
|
||||
|
||||
dev_dbg(component->dev,
|
||||
"rate_min: %d rate_max: %d\n", rate->min, rate->max);
|
||||
dev_dbg(component->dev,
|
||||
"channels_min: %d channels_max: %d\n",
|
||||
channels->min, channels->max);
|
||||
break;
|
||||
case SOF_DAI_MEDIATEK_AFE:
|
||||
rate->min = private->dai_config->afe.rate;
|
||||
rate->max = private->dai_config->afe.rate;
|
||||
channels->min = private->dai_config->afe.channels;
|
||||
channels->max = private->dai_config->afe.channels;
|
||||
|
||||
dev_dbg(component->dev,
|
||||
"rate_min: %d rate_max: %d\n", rate->min, rate->max);
|
||||
dev_dbg(component->dev,
|
||||
"channels_min: %d channels_max: %d\n",
|
||||
channels->min, channels->max);
|
||||
break;
|
||||
case SOF_DAI_IMX_SAI:
|
||||
rate->min = private->dai_config->sai.fsync_rate;
|
||||
rate->max = private->dai_config->sai.fsync_rate;
|
||||
channels->min = private->dai_config->sai.tdm_slots;
|
||||
channels->max = private->dai_config->sai.tdm_slots;
|
||||
|
||||
dev_dbg(component->dev,
|
||||
"rate_min: %d rate_max: %d\n", rate->min, rate->max);
|
||||
dev_dbg(component->dev,
|
||||
"channels_min: %d channels_max: %d\n",
|
||||
channels->min, channels->max);
|
||||
break;
|
||||
case SOF_DAI_AMD_BT:
|
||||
rate->min = private->dai_config->acpbt.fsync_rate;
|
||||
rate->max = private->dai_config->acpbt.fsync_rate;
|
||||
channels->min = private->dai_config->acpbt.tdm_slots;
|
||||
channels->max = private->dai_config->acpbt.tdm_slots;
|
||||
|
||||
dev_dbg(component->dev,
|
||||
"AMD_BT rate_min: %d rate_max: %d\n", rate->min, rate->max);
|
||||
dev_dbg(component->dev,
|
||||
"AMD_BT channels_min: %d channels_max: %d\n",
|
||||
channels->min, channels->max);
|
||||
break;
|
||||
case SOF_DAI_AMD_SP:
|
||||
rate->min = private->dai_config->acpsp.fsync_rate;
|
||||
rate->max = private->dai_config->acpsp.fsync_rate;
|
||||
channels->min = private->dai_config->acpsp.tdm_slots;
|
||||
channels->max = private->dai_config->acpsp.tdm_slots;
|
||||
|
||||
dev_dbg(component->dev,
|
||||
"AMD_SP rate_min: %d rate_max: %d\n", rate->min, rate->max);
|
||||
dev_dbg(component->dev,
|
||||
"AMD_SP channels_min: %d channels_max: %d\n",
|
||||
channels->min, channels->max);
|
||||
break;
|
||||
case SOF_DAI_AMD_DMIC:
|
||||
rate->min = private->dai_config->acpdmic.fsync_rate;
|
||||
rate->max = private->dai_config->acpdmic.fsync_rate;
|
||||
channels->min = private->dai_config->acpdmic.tdm_slots;
|
||||
channels->max = private->dai_config->acpdmic.tdm_slots;
|
||||
|
||||
dev_dbg(component->dev,
|
||||
"AMD_DMIC rate_min: %d rate_max: %d\n", rate->min, rate->max);
|
||||
dev_dbg(component->dev,
|
||||
"AMD_DMIC channels_min: %d channels_max: %d\n",
|
||||
channels->min, channels->max);
|
||||
break;
|
||||
default:
|
||||
dev_err(component->dev, "error: invalid DAI type %d\n",
|
||||
private->dai_config->type);
|
||||
break;
|
||||
}
|
||||
if (pcm_ops->dai_link_fixup)
|
||||
return pcm_ops->dai_link_fixup(rtd, params);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -48,22 +48,6 @@ static u32 snd_sof_dsp_power_target(struct snd_sof_dev *sdev)
|
||||
return target_dsp_state;
|
||||
}
|
||||
|
||||
static int sof_send_pm_ctx_ipc(struct snd_sof_dev *sdev, int cmd)
|
||||
{
|
||||
struct sof_ipc_pm_ctx pm_ctx;
|
||||
struct sof_ipc_reply reply;
|
||||
|
||||
memset(&pm_ctx, 0, sizeof(pm_ctx));
|
||||
|
||||
/* configure ctx save ipc message */
|
||||
pm_ctx.hdr.size = sizeof(pm_ctx);
|
||||
pm_ctx.hdr.cmd = SOF_IPC_GLB_PM_MSG | cmd;
|
||||
|
||||
/* send ctx save ipc to dsp */
|
||||
return sof_ipc_tx_message(sdev->ipc, pm_ctx.hdr.cmd, &pm_ctx,
|
||||
sizeof(pm_ctx), &reply, sizeof(reply));
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE)
|
||||
static void sof_cache_debugfs(struct snd_sof_dev *sdev)
|
||||
{
|
||||
@ -86,6 +70,8 @@ static void sof_cache_debugfs(struct snd_sof_dev *sdev)
|
||||
static int sof_resume(struct device *dev, bool runtime_resume)
|
||||
{
|
||||
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
|
||||
const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm;
|
||||
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
|
||||
u32 old_state = sdev->dsp_power_state.state;
|
||||
int ret;
|
||||
|
||||
@ -159,23 +145,23 @@ static int sof_resume(struct device *dev, bool runtime_resume)
|
||||
}
|
||||
|
||||
/* restore pipelines */
|
||||
ret = sof_set_up_pipelines(sdev, false);
|
||||
if (ret < 0) {
|
||||
dev_err(sdev->dev,
|
||||
"error: failed to restore pipeline after resume %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
if (tplg_ops->set_up_all_pipelines) {
|
||||
ret = tplg_ops->set_up_all_pipelines(sdev, false);
|
||||
if (ret < 0) {
|
||||
dev_err(sdev->dev, "Failed to restore pipeline after resume %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* Notify clients not managed by pm framework about core resume */
|
||||
sof_resume_clients(sdev);
|
||||
|
||||
/* notify DSP of system resume */
|
||||
ret = sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_RESTORE);
|
||||
if (ret < 0)
|
||||
dev_err(sdev->dev,
|
||||
"error: ctx_restore ipc error during resume %d\n",
|
||||
ret);
|
||||
if (pm_ops && pm_ops->ctx_restore) {
|
||||
ret = pm_ops->ctx_restore(sdev);
|
||||
if (ret < 0)
|
||||
dev_err(sdev->dev, "ctx_restore IPC error during resume: %d\n", ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -183,6 +169,8 @@ static int sof_resume(struct device *dev, bool runtime_resume)
|
||||
static int sof_suspend(struct device *dev, bool runtime_suspend)
|
||||
{
|
||||
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
|
||||
const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm;
|
||||
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
|
||||
pm_message_t pm_state;
|
||||
u32 target_state = 0;
|
||||
int ret;
|
||||
@ -218,7 +206,8 @@ static int sof_suspend(struct device *dev, bool runtime_suspend)
|
||||
goto suspend;
|
||||
}
|
||||
|
||||
sof_tear_down_pipelines(sdev, false);
|
||||
if (tplg_ops->tear_down_all_pipelines)
|
||||
tplg_ops->tear_down_all_pipelines(sdev, false);
|
||||
|
||||
/* release trace */
|
||||
snd_sof_release_trace(sdev);
|
||||
@ -232,21 +221,20 @@ static int sof_suspend(struct device *dev, bool runtime_suspend)
|
||||
sof_cache_debugfs(sdev);
|
||||
#endif
|
||||
/* notify DSP of upcoming power down */
|
||||
ret = sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_SAVE);
|
||||
if (ret == -EBUSY || ret == -EAGAIN) {
|
||||
/*
|
||||
* runtime PM has logic to handle -EBUSY/-EAGAIN so
|
||||
* pass these errors up
|
||||
*/
|
||||
dev_err(sdev->dev,
|
||||
"error: ctx_save ipc error during suspend %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
} else if (ret < 0) {
|
||||
/* FW in unexpected state, continue to power down */
|
||||
dev_warn(sdev->dev,
|
||||
"ctx_save ipc error %d, proceeding with suspend\n",
|
||||
ret);
|
||||
if (pm_ops && pm_ops->ctx_save) {
|
||||
ret = pm_ops->ctx_save(sdev);
|
||||
if (ret == -EBUSY || ret == -EAGAIN) {
|
||||
/*
|
||||
* runtime PM has logic to handle -EBUSY/-EAGAIN so
|
||||
* pass these errors up
|
||||
*/
|
||||
dev_err(sdev->dev, "ctx_save IPC error during suspend: %d\n", ret);
|
||||
return ret;
|
||||
} else if (ret < 0) {
|
||||
/* FW in unexpected state, continue to power down */
|
||||
dev_warn(sdev->dev, "ctx_save IPC error: %d, proceeding with suspend\n",
|
||||
ret);
|
||||
}
|
||||
}
|
||||
|
||||
suspend:
|
||||
@ -278,9 +266,11 @@ suspend:
|
||||
|
||||
int snd_sof_dsp_power_down_notify(struct snd_sof_dev *sdev)
|
||||
{
|
||||
const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm;
|
||||
|
||||
/* Notify DSP of upcoming power down */
|
||||
if (sof_ops(sdev)->remove)
|
||||
return sof_send_pm_ctx_ipc(sdev, SOF_IPC_PM_CTX_SAVE);
|
||||
if (sof_ops(sdev)->remove && pm_ops && pm_ops->ctx_save)
|
||||
return pm_ops->ctx_save(sdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -27,31 +27,6 @@ static int sof_kcontrol_setup(struct snd_sof_dev *sdev, struct snd_sof_control *
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sof_dai_config_setup(struct snd_sof_dev *sdev, struct snd_sof_dai *dai)
|
||||
{
|
||||
struct sof_dai_private_data *private = dai->private;
|
||||
struct sof_ipc_dai_config *config;
|
||||
struct sof_ipc_reply reply;
|
||||
int ret;
|
||||
|
||||
config = &private->dai_config[dai->current_config];
|
||||
if (!config) {
|
||||
dev_err(sdev->dev, "error: no config for DAI %s\n", dai->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* set NONE flag to clear all previous settings */
|
||||
config->flags = SOF_DAI_CONFIG_FLAGS_NONE;
|
||||
|
||||
ret = sof_ipc_tx_message(sdev->ipc, config->hdr.cmd, config, config->hdr.size,
|
||||
&reply, sizeof(reply));
|
||||
|
||||
if (ret < 0)
|
||||
dev_err(sdev->dev, "error: failed to set dai config for %s\n", dai->name);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sof_widget_kcontrol_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
|
||||
{
|
||||
struct snd_sof_control *scontrol;
|
||||
@ -96,15 +71,9 @@ static void sof_reset_route_setup_status(struct snd_sof_dev *sdev, struct snd_so
|
||||
|
||||
int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
|
||||
{
|
||||
struct sof_ipc_free ipc_free = {
|
||||
.hdr = {
|
||||
.size = sizeof(ipc_free),
|
||||
.cmd = SOF_IPC_GLB_TPLG_MSG,
|
||||
},
|
||||
.id = swidget->comp_id,
|
||||
};
|
||||
struct sof_ipc_reply reply;
|
||||
int ret, ret1;
|
||||
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
|
||||
int err = 0;
|
||||
int ret;
|
||||
|
||||
if (!swidget->private)
|
||||
return 0;
|
||||
@ -113,64 +82,46 @@ int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
|
||||
if (--swidget->use_count)
|
||||
return 0;
|
||||
|
||||
switch (swidget->id) {
|
||||
case snd_soc_dapm_scheduler:
|
||||
{
|
||||
ipc_free.hdr.cmd |= SOF_IPC_TPLG_PIPE_FREE;
|
||||
break;
|
||||
}
|
||||
case snd_soc_dapm_buffer:
|
||||
ipc_free.hdr.cmd |= SOF_IPC_TPLG_BUFFER_FREE;
|
||||
break;
|
||||
case snd_soc_dapm_dai_in:
|
||||
case snd_soc_dapm_dai_out:
|
||||
{
|
||||
struct snd_sof_dai *dai = swidget->private;
|
||||
|
||||
dai->configured = false;
|
||||
fallthrough;
|
||||
}
|
||||
default:
|
||||
ipc_free.hdr.cmd |= SOF_IPC_TPLG_COMP_FREE;
|
||||
break;
|
||||
}
|
||||
|
||||
/* continue to disable core even if IPC fails */
|
||||
ret = sof_ipc_tx_message(sdev->ipc, ipc_free.hdr.cmd, &ipc_free, sizeof(ipc_free),
|
||||
&reply, sizeof(reply));
|
||||
if (ret < 0)
|
||||
dev_err(sdev->dev, "error: failed to free widget %s\n", swidget->widget->name);
|
||||
if (tplg_ops->widget_free)
|
||||
err = tplg_ops->widget_free(sdev, swidget);
|
||||
|
||||
/*
|
||||
* disable widget core. continue to route setup status and complete flag
|
||||
* even if this fails and return the appropriate error
|
||||
*/
|
||||
ret1 = snd_sof_dsp_core_put(sdev, swidget->core);
|
||||
if (ret1 < 0) {
|
||||
ret = snd_sof_dsp_core_put(sdev, swidget->core);
|
||||
if (ret < 0) {
|
||||
dev_err(sdev->dev, "error: failed to disable target core: %d for widget %s\n",
|
||||
swidget->core, swidget->widget->name);
|
||||
if (!ret)
|
||||
ret = ret1;
|
||||
if (!err)
|
||||
err = ret;
|
||||
}
|
||||
|
||||
/* reset route setup status for all routes that contain this widget */
|
||||
sof_reset_route_setup_status(sdev, swidget);
|
||||
swidget->complete = 0;
|
||||
|
||||
if (!ret)
|
||||
/*
|
||||
* free the scheduler widget (same as pipe_widget) associated with the current swidget.
|
||||
* skip for static pipelines
|
||||
*/
|
||||
if (swidget->dynamic_pipeline_widget && swidget->id != snd_soc_dapm_scheduler) {
|
||||
ret = sof_widget_free(sdev, swidget->pipe_widget);
|
||||
if (ret < 0 && !err)
|
||||
err = ret;
|
||||
}
|
||||
|
||||
if (!err)
|
||||
dev_dbg(sdev->dev, "widget %s freed\n", swidget->widget->name);
|
||||
|
||||
return ret;
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL(sof_widget_free);
|
||||
|
||||
int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
|
||||
{
|
||||
struct sof_ipc_pipe_new *pipeline;
|
||||
struct sof_ipc_comp_reply r;
|
||||
struct sof_ipc_cmd_hdr *hdr;
|
||||
struct sof_ipc_comp *comp;
|
||||
struct snd_sof_dai *dai;
|
||||
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
|
||||
int ret;
|
||||
|
||||
/* skip if there is no private data */
|
||||
@ -181,61 +132,50 @@ int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
|
||||
if (++swidget->use_count > 1)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* The scheduler widget for a pipeline is not part of the connected DAPM
|
||||
* widget list and it needs to be set up before the widgets in the pipeline
|
||||
* are set up. The use_count for the scheduler widget is incremented for every
|
||||
* widget in a given pipeline to ensure that it is freed only after the last
|
||||
* widget in the pipeline is freed. Skip setting up scheduler widget for static pipelines.
|
||||
*/
|
||||
if (swidget->dynamic_pipeline_widget && swidget->id != snd_soc_dapm_scheduler) {
|
||||
if (!swidget->pipe_widget) {
|
||||
dev_err(sdev->dev, "No scheduler widget set for %s\n",
|
||||
swidget->widget->name);
|
||||
ret = -EINVAL;
|
||||
goto use_count_dec;
|
||||
}
|
||||
|
||||
ret = sof_widget_setup(sdev, swidget->pipe_widget);
|
||||
if (ret < 0)
|
||||
goto use_count_dec;
|
||||
}
|
||||
|
||||
/* enable widget core */
|
||||
ret = snd_sof_dsp_core_get(sdev, swidget->core);
|
||||
if (ret < 0) {
|
||||
dev_err(sdev->dev, "error: failed to enable target core for widget %s\n",
|
||||
swidget->widget->name);
|
||||
goto use_count_dec;
|
||||
goto pipe_widget_free;
|
||||
}
|
||||
|
||||
switch (swidget->id) {
|
||||
case snd_soc_dapm_dai_in:
|
||||
case snd_soc_dapm_dai_out:
|
||||
{
|
||||
struct sof_dai_private_data *dai_data;
|
||||
|
||||
dai = swidget->private;
|
||||
dai_data = dai->private;
|
||||
comp = &dai_data->comp_dai->comp;
|
||||
dai->configured = false;
|
||||
|
||||
ret = sof_ipc_tx_message(sdev->ipc, comp->hdr.cmd, dai_data->comp_dai,
|
||||
comp->hdr.size, &r, sizeof(r));
|
||||
if (ret < 0) {
|
||||
dev_err(sdev->dev, "error: failed to load widget %s\n",
|
||||
swidget->widget->name);
|
||||
/* setup widget in the DSP */
|
||||
if (tplg_ops->widget_setup) {
|
||||
ret = tplg_ops->widget_setup(sdev, swidget);
|
||||
if (ret < 0)
|
||||
goto core_put;
|
||||
}
|
||||
|
||||
ret = sof_dai_config_setup(sdev, dai);
|
||||
if (ret < 0) {
|
||||
dev_err(sdev->dev, "error: failed to load dai config for DAI %s\n",
|
||||
swidget->widget->name);
|
||||
|
||||
/*
|
||||
* widget use_count and core ref_count will both be decremented by
|
||||
* sof_widget_free()
|
||||
*/
|
||||
sof_widget_free(sdev, swidget);
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case snd_soc_dapm_scheduler:
|
||||
pipeline = swidget->private;
|
||||
ret = sof_ipc_tx_message(sdev->ipc, pipeline->hdr.cmd, pipeline,
|
||||
sizeof(*pipeline), &r, sizeof(r));
|
||||
break;
|
||||
default:
|
||||
hdr = swidget->private;
|
||||
ret = sof_ipc_tx_message(sdev->ipc, hdr->cmd, swidget->private, hdr->size,
|
||||
&r, sizeof(r));
|
||||
break;
|
||||
}
|
||||
if (ret < 0) {
|
||||
dev_err(sdev->dev, "error: failed to load widget %s\n", swidget->widget->name);
|
||||
goto core_put;
|
||||
|
||||
/* send config for DAI components */
|
||||
if (WIDGET_IS_DAI(swidget->id)) {
|
||||
unsigned int flags = SOF_DAI_CONFIG_FLAGS_NONE;
|
||||
|
||||
if (tplg_ops->dai_config) {
|
||||
ret = tplg_ops->dai_config(sdev, swidget, flags, NULL);
|
||||
if (ret < 0)
|
||||
goto widget_free;
|
||||
}
|
||||
}
|
||||
|
||||
/* restore kcontrols for widget */
|
||||
@ -243,28 +183,29 @@ int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget)
|
||||
if (ret < 0) {
|
||||
dev_err(sdev->dev, "error: failed to restore kcontrols for widget %s\n",
|
||||
swidget->widget->name);
|
||||
/*
|
||||
* widget use_count and core ref_count will both be decremented by
|
||||
* sof_widget_free()
|
||||
*/
|
||||
sof_widget_free(sdev, swidget);
|
||||
return ret;
|
||||
goto widget_free;
|
||||
}
|
||||
|
||||
dev_dbg(sdev->dev, "widget %s setup complete\n", swidget->widget->name);
|
||||
|
||||
return 0;
|
||||
|
||||
widget_free:
|
||||
/* widget use_count and core ref_count will both be decremented by sof_widget_free() */
|
||||
sof_widget_free(sdev, swidget);
|
||||
core_put:
|
||||
snd_sof_dsp_core_put(sdev, swidget->core);
|
||||
pipe_widget_free:
|
||||
if (swidget->id != snd_soc_dapm_scheduler)
|
||||
sof_widget_free(sdev, swidget->pipe_widget);
|
||||
use_count_dec:
|
||||
swidget->use_count--;
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(sof_widget_setup);
|
||||
|
||||
static int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsource,
|
||||
struct snd_soc_dapm_widget *wsink)
|
||||
int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsource,
|
||||
struct snd_soc_dapm_widget *wsink)
|
||||
{
|
||||
const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg;
|
||||
struct snd_sof_widget *src_widget = wsource->dobj.private;
|
||||
@ -374,36 +315,14 @@ int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, in
|
||||
/* set up widgets in the list */
|
||||
for_each_dapm_widgets(list, num_widgets, widget) {
|
||||
struct snd_sof_widget *swidget = widget->dobj.private;
|
||||
struct snd_sof_widget *pipe_widget;
|
||||
|
||||
if (!swidget)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* The scheduler widget for a pipeline is not part of the connected DAPM
|
||||
* widget list and it needs to be set up before the widgets in the pipeline
|
||||
* are set up. The use_count for the scheduler widget is incremented for every
|
||||
* widget in a given pipeline to ensure that it is freed only after the last
|
||||
* widget in the pipeline is freed.
|
||||
*/
|
||||
pipe_widget = swidget->pipe_widget;
|
||||
if (!pipe_widget) {
|
||||
dev_err(sdev->dev, "error: no pipeline widget found for %s\n",
|
||||
swidget->widget->name);
|
||||
ret = -EINVAL;
|
||||
goto widget_free;
|
||||
}
|
||||
|
||||
ret = sof_widget_setup(sdev, pipe_widget);
|
||||
if (ret < 0)
|
||||
goto widget_free;
|
||||
|
||||
/* set up the widget */
|
||||
ret = sof_widget_setup(sdev, swidget);
|
||||
if (ret < 0) {
|
||||
sof_widget_free(sdev, pipe_widget);
|
||||
if (ret < 0)
|
||||
goto widget_free;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -456,7 +375,6 @@ widget_free:
|
||||
break;
|
||||
|
||||
sof_widget_free(sdev, swidget);
|
||||
sof_widget_free(sdev, swidget->pipe_widget);
|
||||
}
|
||||
|
||||
return ret;
|
||||
@ -490,10 +408,6 @@ int sof_widget_list_free(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int
|
||||
ret = sof_widget_free(sdev, swidget);
|
||||
if (ret < 0)
|
||||
ret1 = ret;
|
||||
|
||||
ret = sof_widget_free(sdev, swidget->pipe_widget);
|
||||
if (ret < 0)
|
||||
ret1 = ret;
|
||||
}
|
||||
|
||||
snd_soc_dapm_dai_free_widgets(&list);
|
||||
@ -584,105 +498,20 @@ int sof_set_hw_params_upon_resume(struct device *dev)
|
||||
return snd_sof_dsp_hw_params_upon_resume(sdev);
|
||||
}
|
||||
|
||||
int sof_set_up_pipelines(struct snd_sof_dev *sdev, bool verify)
|
||||
int sof_pcm_stream_free(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream,
|
||||
struct snd_sof_pcm *spcm, int dir, bool free_widget_list)
|
||||
{
|
||||
const struct sof_ipc_tplg_ops *ipc_tplg_ops = sdev->ipc->ops->tplg;
|
||||
struct sof_ipc_fw_version *v = &sdev->fw_ready.version;
|
||||
struct snd_sof_widget *swidget;
|
||||
struct snd_sof_route *sroute;
|
||||
const struct sof_ipc_pcm_ops *pcm_ops = sdev->ipc->ops->pcm;
|
||||
int ret;
|
||||
|
||||
/* restore pipeline components */
|
||||
list_for_each_entry(swidget, &sdev->widget_list, list) {
|
||||
/* only set up the widgets belonging to static pipelines */
|
||||
if (!verify && swidget->dynamic_pipeline_widget)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* For older firmware, skip scheduler widgets in this loop,
|
||||
* sof_widget_setup() will be called in the 'complete pipeline' loop
|
||||
*/
|
||||
if (v->abi_version < SOF_ABI_VER(3, 19, 0) &&
|
||||
swidget->id == snd_soc_dapm_scheduler)
|
||||
continue;
|
||||
|
||||
/* update DAI config. The IPC will be sent in sof_widget_setup() */
|
||||
if (WIDGET_IS_DAI(swidget->id)) {
|
||||
struct snd_sof_dai *dai = swidget->private;
|
||||
struct sof_dai_private_data *private = dai->private;
|
||||
struct sof_ipc_dai_config *config;
|
||||
|
||||
if (!dai || !private || !private->dai_config)
|
||||
continue;
|
||||
|
||||
config = private->dai_config;
|
||||
/*
|
||||
* The link DMA channel would be invalidated for running
|
||||
* streams but not for streams that were in the PAUSED
|
||||
* state during suspend. So invalidate it here before setting
|
||||
* the dai config in the DSP.
|
||||
*/
|
||||
if (config->type == SOF_DAI_INTEL_HDA)
|
||||
config->hda.link_dma_ch = DMA_CHAN_INVALID;
|
||||
}
|
||||
|
||||
ret = sof_widget_setup(sdev, swidget);
|
||||
/* Send PCM_FREE IPC to reset pipeline */
|
||||
if (pcm_ops->hw_free && spcm->prepared[substream->stream]) {
|
||||
ret = pcm_ops->hw_free(sdev->component, substream);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* restore pipeline connections */
|
||||
list_for_each_entry(sroute, &sdev->route_list, list) {
|
||||
|
||||
/* only set up routes belonging to static pipelines */
|
||||
if (!verify && (sroute->src_widget->dynamic_pipeline_widget ||
|
||||
sroute->sink_widget->dynamic_pipeline_widget))
|
||||
continue;
|
||||
|
||||
ret = ipc_tplg_ops->route_setup(sdev, sroute);
|
||||
if (ret < 0) {
|
||||
dev_err(sdev->dev, "%s: restore pipeline connections failed\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* complete pipeline */
|
||||
list_for_each_entry(swidget, &sdev->widget_list, list) {
|
||||
switch (swidget->id) {
|
||||
case snd_soc_dapm_scheduler:
|
||||
/* only complete static pipelines */
|
||||
if (!verify && swidget->dynamic_pipeline_widget)
|
||||
continue;
|
||||
|
||||
if (v->abi_version < SOF_ABI_VER(3, 19, 0)) {
|
||||
ret = sof_widget_setup(sdev, swidget);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ipc_tplg_ops->pipeline_complete) {
|
||||
swidget->complete = ipc_tplg_ops->pipeline_complete(sdev, swidget);
|
||||
if (swidget->complete < 0)
|
||||
return swidget->complete;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sof_pcm_stream_free(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream,
|
||||
struct snd_sof_pcm *spcm, int dir, bool free_widget_list)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Send PCM_FREE IPC to reset pipeline */
|
||||
ret = sof_pcm_dsp_pcm_free(substream, sdev, spcm);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
spcm->prepared[substream->stream] = false;
|
||||
|
||||
/* stop the DMA */
|
||||
ret = snd_sof_pcm_platform_hw_free(sdev, substream);
|
||||
@ -699,102 +528,6 @@ int sof_pcm_stream_free(struct snd_sof_dev *sdev, struct snd_pcm_substream *subs
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Free the PCM, its associated widgets and set the prepared flag to false for all PCMs that
|
||||
* did not get suspended(ex: paused streams) so the widgets can be set up again during resume.
|
||||
*/
|
||||
static int sof_tear_down_left_over_pipelines(struct snd_sof_dev *sdev)
|
||||
{
|
||||
struct snd_sof_widget *swidget;
|
||||
struct snd_sof_pcm *spcm;
|
||||
int dir, ret;
|
||||
|
||||
/*
|
||||
* free all PCMs and their associated DAPM widgets if their connected DAPM widget
|
||||
* list is not NULL. This should only be true for paused streams at this point.
|
||||
* This is equivalent to the handling of FE DAI suspend trigger for running streams.
|
||||
*/
|
||||
list_for_each_entry(spcm, &sdev->pcm_list, list)
|
||||
for_each_pcm_streams(dir) {
|
||||
struct snd_pcm_substream *substream = spcm->stream[dir].substream;
|
||||
|
||||
if (!substream || !substream->runtime)
|
||||
continue;
|
||||
|
||||
if (spcm->stream[dir].list) {
|
||||
ret = sof_pcm_stream_free(sdev, substream, spcm, dir, true);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* free any left over DAI widgets. This is equivalent to the handling of suspend trigger
|
||||
* for the BE DAI for running streams.
|
||||
*/
|
||||
list_for_each_entry(swidget, &sdev->widget_list, list)
|
||||
if (WIDGET_IS_DAI(swidget->id) && swidget->use_count == 1) {
|
||||
ret = sof_widget_free(sdev, swidget);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* For older firmware, this function doesn't free widgets for static pipelines during suspend.
|
||||
* It only resets use_count for all widgets.
|
||||
*/
|
||||
int sof_tear_down_pipelines(struct snd_sof_dev *sdev, bool verify)
|
||||
{
|
||||
struct sof_ipc_fw_version *v = &sdev->fw_ready.version;
|
||||
struct snd_sof_widget *swidget;
|
||||
struct snd_sof_route *sroute;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* This function is called during suspend and for one-time topology verification during
|
||||
* first boot. In both cases, there is no need to protect swidget->use_count and
|
||||
* sroute->setup because during suspend all running streams are suspended and during
|
||||
* topology loading the sound card unavailable to open PCMs.
|
||||
*/
|
||||
list_for_each_entry(swidget, &sdev->widget_list, list) {
|
||||
if (swidget->dynamic_pipeline_widget)
|
||||
continue;
|
||||
|
||||
/* Do not free widgets for static pipelines with FW ABI older than 3.19 */
|
||||
if (!verify && !swidget->dynamic_pipeline_widget &&
|
||||
v->abi_version < SOF_ABI_VER(3, 19, 0)) {
|
||||
swidget->use_count = 0;
|
||||
swidget->complete = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = sof_widget_free(sdev, swidget);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Tear down all pipelines associated with PCMs that did not get suspended
|
||||
* and unset the prepare flag so that they can be set up again during resume.
|
||||
* Skip this step for older firmware.
|
||||
*/
|
||||
if (!verify && v->abi_version >= SOF_ABI_VER(3, 19, 0)) {
|
||||
ret = sof_tear_down_left_over_pipelines(sdev);
|
||||
if (ret < 0) {
|
||||
dev_err(sdev->dev, "failed to tear down paused pipelines\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
list_for_each_entry(sroute, &sdev->route_list, list)
|
||||
sroute->setup = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Generic object lookup APIs.
|
||||
*/
|
||||
@ -895,40 +628,23 @@ struct snd_sof_dai *snd_sof_find_dai(struct snd_soc_component *scomp,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define SOF_DAI_CLK_INTEL_SSP_MCLK 0
|
||||
#define SOF_DAI_CLK_INTEL_SSP_BCLK 1
|
||||
|
||||
static int sof_dai_get_clk(struct snd_soc_pcm_runtime *rtd, int clk_type)
|
||||
{
|
||||
struct snd_soc_component *component =
|
||||
snd_soc_rtdcom_lookup(rtd, SOF_AUDIO_PCM_DRV_NAME);
|
||||
struct snd_sof_dai *dai =
|
||||
snd_sof_find_dai(component, (char *)rtd->dai_link->name);
|
||||
struct sof_dai_private_data *private = dai->private;
|
||||
struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(component);
|
||||
const struct sof_ipc_tplg_ops *tplg_ops = sdev->ipc->ops->tplg;
|
||||
|
||||
/* use the tplg configured mclk if existed */
|
||||
if (!dai || !private || !private->dai_config)
|
||||
if (!dai)
|
||||
return 0;
|
||||
|
||||
switch (private->dai_config->type) {
|
||||
case SOF_DAI_INTEL_SSP:
|
||||
switch (clk_type) {
|
||||
case SOF_DAI_CLK_INTEL_SSP_MCLK:
|
||||
return private->dai_config->ssp.mclk_rate;
|
||||
case SOF_DAI_CLK_INTEL_SSP_BCLK:
|
||||
return private->dai_config->ssp.bclk_rate;
|
||||
default:
|
||||
dev_err(rtd->dev, "fail to get SSP clk %d rate\n",
|
||||
clk_type);
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* not yet implemented for platforms other than the above */
|
||||
dev_err(rtd->dev, "DAI type %d not supported yet!\n",
|
||||
private->dai_config->type);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (tplg_ops->dai_get_clk)
|
||||
return tplg_ops->dai_get_clk(sdev, dai, clk_type);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -30,6 +30,9 @@
|
||||
|
||||
#define WIDGET_IS_DAI(id) ((id) == snd_soc_dapm_dai_in || (id) == snd_soc_dapm_dai_out)
|
||||
|
||||
#define SOF_DAI_CLK_INTEL_SSP_MCLK 0
|
||||
#define SOF_DAI_CLK_INTEL_SSP_BCLK 1
|
||||
|
||||
/*
|
||||
* Volume fractional word length define to 16 sets
|
||||
* the volume linear gain value to use Qx.16 format
|
||||
@ -39,6 +42,51 @@
|
||||
struct snd_sof_widget;
|
||||
struct snd_sof_route;
|
||||
struct snd_sof_control;
|
||||
struct snd_sof_dai;
|
||||
|
||||
struct snd_sof_dai_config_data {
|
||||
int dai_index;
|
||||
int dai_data; /* contains DAI-specific information */
|
||||
};
|
||||
|
||||
/**
|
||||
* struct sof_ipc_pcm_ops - IPC-specific PCM ops
|
||||
* @hw_params: Function pointer for hw_params
|
||||
* @hw_free: Function pointer for hw_free
|
||||
* @trigger: Function pointer for trigger
|
||||
* @dai_link_fixup: Function pointer for DAI link fixup
|
||||
*/
|
||||
struct sof_ipc_pcm_ops {
|
||||
int (*hw_params)(struct snd_soc_component *component, struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_sof_platform_stream_params *platform_params);
|
||||
int (*hw_free)(struct snd_soc_component *component, struct snd_pcm_substream *substream);
|
||||
int (*trigger)(struct snd_soc_component *component, struct snd_pcm_substream *substream,
|
||||
int cmd);
|
||||
int (*dai_link_fixup)(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct sof_ipc_tplg_control_ops - IPC-specific ops for topology kcontrol IO
|
||||
*/
|
||||
struct sof_ipc_tplg_control_ops {
|
||||
bool (*volume_put)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol);
|
||||
int (*volume_get)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol);
|
||||
bool (*switch_put)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol);
|
||||
int (*switch_get)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol);
|
||||
bool (*enum_put)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol);
|
||||
int (*enum_get)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol);
|
||||
int (*bytes_put)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol);
|
||||
int (*bytes_get)(struct snd_sof_control *scontrol, struct snd_ctl_elem_value *ucontrol);
|
||||
int (*bytes_ext_get)(struct snd_sof_control *scontrol,
|
||||
const unsigned int __user *binary_data, unsigned int size);
|
||||
int (*bytes_ext_volatile_get)(struct snd_sof_control *scontrol,
|
||||
const unsigned int __user *binary_data, unsigned int size);
|
||||
int (*bytes_ext_put)(struct snd_sof_control *scontrol,
|
||||
const unsigned int __user *binary_data, unsigned int size);
|
||||
/* update control data based on notification from the DSP */
|
||||
void (*update)(struct snd_sof_dev *sdev, void *ipc_control_message);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct sof_ipc_tplg_widget_ops - IPC-specific ops for topology widgets
|
||||
@ -62,6 +110,7 @@ struct sof_ipc_tplg_widget_ops {
|
||||
* @widget: Array of pointers to IPC-specific ops for widgets. This should always be of size
|
||||
* SND_SOF_DAPM_TYPE_COUNT i.e one per widget type. Unsupported widget types will be
|
||||
* initialized to 0.
|
||||
* @control: Pointer to the IPC-specific ops for topology kcontrol IO
|
||||
* @route_setup: Function pointer for setting up pipeline connections
|
||||
* @token_list: List of all tokens supported by the IPC version. The size of the token_list
|
||||
* array should be SOF_TOKEN_COUNT. The unused elements in the array will be
|
||||
@ -69,14 +118,28 @@ struct sof_ipc_tplg_widget_ops {
|
||||
* @control_setup: Function pointer for setting up kcontrol IPC-specific data
|
||||
* @control_free: Function pointer for freeing kcontrol IPC-specific data
|
||||
* @pipeline_complete: Function pointer for pipeline complete IPC
|
||||
* @widget_setup: Function pointer for setting up setup in the DSP
|
||||
* @widget_free: Function pointer for freeing widget in the DSP
|
||||
* @dai_config: Function pointer for sending DAI config IPC to the DSP
|
||||
* @dai_get_clk: Function pointer for getting the DAI clock setting
|
||||
* @set_up_all_pipelines: Function pointer for setting up all topology pipelines
|
||||
* @tear_down_all_pipelines: Function pointer for tearing down all topology pipelines
|
||||
*/
|
||||
struct sof_ipc_tplg_ops {
|
||||
const struct sof_ipc_tplg_widget_ops *widget;
|
||||
const struct sof_ipc_tplg_control_ops *control;
|
||||
int (*route_setup)(struct snd_sof_dev *sdev, struct snd_sof_route *sroute);
|
||||
const struct sof_token_info *token_list;
|
||||
int (*control_setup)(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol);
|
||||
int (*control_free)(struct snd_sof_dev *sdev, struct snd_sof_control *scontrol);
|
||||
int (*pipeline_complete)(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget);
|
||||
int (*widget_setup)(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget);
|
||||
int (*widget_free)(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget);
|
||||
int (*dai_config)(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget,
|
||||
unsigned int flags, struct snd_sof_dai_config_data *data);
|
||||
int (*dai_get_clk)(struct snd_sof_dev *sdev, struct snd_sof_dai *dai, int clk_type);
|
||||
int (*set_up_all_pipelines)(struct snd_sof_dev *sdev, bool verify);
|
||||
int (*tear_down_all_pipelines)(struct snd_sof_dev *sdev, bool verify);
|
||||
};
|
||||
|
||||
/** struct snd_sof_tuple - Tuple info
|
||||
@ -276,7 +339,6 @@ struct snd_sof_dai {
|
||||
|
||||
int number_configs;
|
||||
int current_config;
|
||||
bool configured; /* DAI configured during BE hw_params */
|
||||
struct list_head list; /* list in sdev dai list */
|
||||
void *private;
|
||||
};
|
||||
@ -377,8 +439,6 @@ int snd_sof_ipc_set_get_comp_data(struct snd_sof_control *scontrol, bool set);
|
||||
int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, struct snd_pcm_hw_params *params);
|
||||
|
||||
/* PM */
|
||||
int sof_set_up_pipelines(struct snd_sof_dev *sdev, bool verify);
|
||||
int sof_tear_down_pipelines(struct snd_sof_dev *sdev, bool verify);
|
||||
int sof_set_hw_params_upon_resume(struct device *dev);
|
||||
bool snd_sof_stream_suspend_ignored(struct snd_sof_dev *sdev);
|
||||
bool snd_sof_dsp_only_d0i3_compatible_stream_active(struct snd_sof_dev *sdev);
|
||||
@ -389,6 +449,8 @@ void sof_machine_unregister(struct snd_sof_dev *sdev, void *pdata);
|
||||
|
||||
int sof_widget_setup(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget);
|
||||
int sof_widget_free(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget);
|
||||
int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsource,
|
||||
struct snd_soc_dapm_widget *wsink);
|
||||
|
||||
/* PCM */
|
||||
int sof_widget_list_setup(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, int dir);
|
||||
@ -405,4 +467,6 @@ int get_token_uuid(void *elem, void *object, u32 offset);
|
||||
int sof_update_ipc_object(struct snd_soc_component *scomp, void *object, enum sof_tokens token_id,
|
||||
struct snd_sof_tuple *tuples, int num_tuples,
|
||||
size_t object_size, int token_instance_num);
|
||||
int sof_pcm_setup_connected_widgets(struct snd_sof_dev *sdev, struct snd_soc_pcm_runtime *rtd,
|
||||
struct snd_sof_pcm *spcm, int dir);
|
||||
#endif
|
||||
|
@ -360,18 +360,31 @@ struct snd_sof_ipc_msg {
|
||||
bool ipc_complete;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct sof_ipc_pm_ops - IPC-specific PM ops
|
||||
* @ctx_save: Function pointer for context save
|
||||
* @ctx_restore: Function pointer for context restore
|
||||
*/
|
||||
struct sof_ipc_pm_ops {
|
||||
int (*ctx_save)(struct snd_sof_dev *sdev);
|
||||
int (*ctx_restore)(struct snd_sof_dev *sdev);
|
||||
};
|
||||
|
||||
struct sof_ipc_tplg_ops;
|
||||
struct sof_ipc_pcm_ops;
|
||||
|
||||
/**
|
||||
* struct sof_ipc_ops - IPC-specific ops
|
||||
* @tplg: Pointer to IPC-specific topology ops
|
||||
* @pm: Pointer to PM ops
|
||||
* @pcm: Pointer to PCM ops
|
||||
*/
|
||||
struct sof_ipc_ops {
|
||||
const struct sof_ipc_tplg_ops *tplg;
|
||||
const struct sof_ipc_pm_ops *pm;
|
||||
const struct sof_ipc_pcm_ops *pcm;
|
||||
};
|
||||
|
||||
extern const struct sof_ipc_ops ipc3_ops;
|
||||
|
||||
/* SOF generic IPC data */
|
||||
struct snd_sof_ipc {
|
||||
struct snd_sof_dev *sdev;
|
||||
|
@ -1796,29 +1796,15 @@ static int sof_route_load(struct snd_soc_component *scomp, int index,
|
||||
sink_swidget->id == snd_soc_dapm_output)
|
||||
goto err;
|
||||
|
||||
/*
|
||||
* For virtual routes, both sink and source are not
|
||||
* buffer. Since only buffer linked to component is supported by
|
||||
* FW, others are reported as error, add check in route function,
|
||||
* do not send it to FW when both source and sink are not buffer
|
||||
*/
|
||||
if (source_swidget->id != snd_soc_dapm_buffer &&
|
||||
sink_swidget->id != snd_soc_dapm_buffer) {
|
||||
dev_dbg(scomp->dev, "warning: neither Linked source component %s nor sink component %s is of buffer type, ignoring link\n",
|
||||
route->source, route->sink);
|
||||
goto err;
|
||||
} else {
|
||||
sroute->route = route;
|
||||
dobj->private = sroute;
|
||||
sroute->src_widget = source_swidget;
|
||||
sroute->sink_widget = sink_swidget;
|
||||
sroute->route = route;
|
||||
dobj->private = sroute;
|
||||
sroute->src_widget = source_swidget;
|
||||
sroute->sink_widget = sink_swidget;
|
||||
|
||||
/* add route to route list */
|
||||
list_add(&sroute->list, &sdev->route_list);
|
||||
|
||||
return 0;
|
||||
}
|
||||
/* add route to route list */
|
||||
list_add(&sroute->list, &sdev->route_list);
|
||||
|
||||
return 0;
|
||||
err:
|
||||
kfree(sroute);
|
||||
return ret;
|
||||
@ -1917,21 +1903,28 @@ static int sof_complete(struct snd_soc_component *scomp)
|
||||
|
||||
/* verify topology components loading including dynamic pipelines */
|
||||
if (sof_debug_check_flag(SOF_DBG_VERIFY_TPLG)) {
|
||||
ret = sof_set_up_pipelines(sdev, true);
|
||||
if (ret < 0) {
|
||||
dev_err(sdev->dev, "error: topology verification failed %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
if (ipc_tplg_ops->set_up_all_pipelines && ipc_tplg_ops->tear_down_all_pipelines) {
|
||||
ret = ipc_tplg_ops->set_up_all_pipelines(sdev, true);
|
||||
if (ret < 0) {
|
||||
dev_err(sdev->dev, "Failed to set up all topology pipelines: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = sof_tear_down_pipelines(sdev, true);
|
||||
if (ret < 0) {
|
||||
dev_err(sdev->dev, "error: topology tear down pipelines failed %d\n", ret);
|
||||
return ret;
|
||||
ret = ipc_tplg_ops->tear_down_all_pipelines(sdev, true);
|
||||
if (ret < 0) {
|
||||
dev_err(sdev->dev, "Failed to tear down topology pipelines: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* set up static pipelines */
|
||||
return sof_set_up_pipelines(sdev, false);
|
||||
if (ipc_tplg_ops->set_up_all_pipelines)
|
||||
return ipc_tplg_ops->set_up_all_pipelines(sdev, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* manifest - optional to inform component of manifest */
|
||||
|
Loading…
Reference in New Issue
Block a user