diff --git a/include/uapi/sound/sof-ipc.h b/include/uapi/sound/sof-ipc.h index e3c3de0a2d19f0..cb606d24c81320 100644 --- a/include/uapi/sound/sof-ipc.h +++ b/include/uapi/sound/sof-ipc.h @@ -649,6 +649,7 @@ struct sof_ipc_comp_mux { struct sof_ipc_comp_tone { struct sof_ipc_comp comp; struct sof_ipc_comp_config config; + int32_t sample_rate; int32_t frequency; int32_t amplitude; int32_t freq_mult; diff --git a/include/uapi/sound/sof-topology.h b/include/uapi/sound/sof-topology.h index 1a4957b7fd049d..5c40a897c44cf9 100644 --- a/include/uapi/sound/sof-topology.h +++ b/include/uapi/sound/sof-topology.h @@ -24,6 +24,7 @@ #define SOF_TPLG_KCTL_VOL_ID 256 #define SOF_TPLG_KCTL_ENUM_ID 257 #define SOF_TPLG_KCTL_BYTES_ID 258 +#define SOF_TPLG_KCTL_SWITCH_ID 259 /* * Tokens - must match values in topology configurations @@ -88,4 +89,7 @@ #define SOF_TKN_INTEL_DMIC_PDM_CLK_EDGE 705 #define SOF_TKN_INTEL_DMIC_PDM_SKEW 706 +/* Tone */ +#define SOF_TKN_TONE_SAMPLE_RATE 800 + #endif diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 6d54128e44b46b..04f84b2cc04f83 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -855,6 +855,7 @@ static int dapm_create_or_share_kcontrol(struct snd_soc_dapm_widget *w, kcname_in_long_name = true; } else { switch (w->id) { + case snd_soc_dapm_siggen: case snd_soc_dapm_switch: case snd_soc_dapm_mixer: case snd_soc_dapm_pga: @@ -3076,6 +3077,7 @@ int snd_soc_dapm_new_widgets(struct snd_soc_card *card) case snd_soc_dapm_demux: dapm_new_mux(w); break; + case snd_soc_dapm_siggen: case snd_soc_dapm_pga: case snd_soc_dapm_out_drv: dapm_new_pga(w); diff --git a/sound/soc/sof/control.c b/sound/soc/sof/control.c index db09ba63154b69..b912f8265a28b4 100644 --- a/sound/soc/sof/control.c +++ b/sound/soc/sof/control.c @@ -20,6 +20,101 @@ #include #include "sof-priv.h" +/* return the widget type of the comp the kcontrol is attached to */ +static int get_widget_type(struct snd_sof_dev *sdev, + struct snd_sof_control *scontrol) +{ + struct snd_sof_widget *swidget; + + list_for_each_entry(swidget, &sdev->widget_list, list) { + if (swidget->comp_id == scontrol->comp_id) + return swidget->id; + } + + /* standalone kcontrol */ + return -EINVAL; +} + +/* helper function to send pcm params ipc */ +static int siggen_pcm_params(struct snd_sof_control *scontrol, + struct snd_sof_dev *sdev) +{ + struct sof_ipc_pcm_params_reply ipc_params_reply; + struct sof_ipc_pcm_params pcm; + int ret = 0; + + memset(&pcm, 0, sizeof(pcm)); + + /* 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 = scontrol->comp_id; + pcm.params.channels = scontrol->num_channels; + pcm.params.direction = SOF_IPC_STREAM_PLAYBACK; + + /* send 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(sdev->dev, "error: setting pcm params for siggen\n"); + + return ret; +} + +/* helper function to send stream trigger ipc for siggen pipeline */ +static int siggen_trigger(struct snd_sof_control *scontrol, + struct snd_sof_dev *sdev, int cmd) +{ + struct sof_ipc_stream stream; + struct sof_ipc_reply reply; + int ret = 0; + + /* set IPC stream params */ + stream.hdr.size = sizeof(stream); + stream.hdr.cmd = SOF_IPC_GLB_STREAM_MSG | cmd; + stream.comp_id = scontrol->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 < 0) + dev_err(sdev->dev, "error: failed to trigger stream\n"); + + return ret; +} + +/* + * Helper function to send ipc's to trigger siggen pipeline + * The siggen pipeline is enabled/disabled only if the + * control values change from the old state. + */ +static int siggen_pipeline_trigger(struct snd_sof_control *scontrol, + struct snd_sof_dev *sdev, + struct snd_soc_card *card, + int new_state) +{ + int ret = 0; + + if (!new_state) { + + /* free pcm and reset pipeline */ + ret = siggen_trigger(scontrol, sdev, + SOF_IPC_STREAM_PCM_FREE); + } else { + + /* set pcm params */ + ret = siggen_pcm_params(scontrol, sdev); + if (ret < 0) + return ret; + + /* enable siggen */ + ret = siggen_trigger(scontrol, sdev, + SOF_IPC_STREAM_TRIG_START); + } + + return ret; +} + static inline u32 mixer_to_ipc(unsigned int value, u32 *volume_map, int size) { if (value >= size) @@ -195,3 +290,142 @@ int snd_sof_bytes_put(struct snd_kcontrol *kcontrol, pm_runtime_put_autosuspend(sdev->dev); return 0; } + +/* IO handlers for switch kcontrol handlers */ +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 snd_sof_control *scontrol = sm->dobj.private; + struct snd_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + unsigned int i, channels = scontrol->num_channels; + + pm_runtime_get_sync(sdev->dev); + + /* get all the mixer data from DSP */ + snd_sof_ipc_get_comp_data(sdev->ipc, scontrol, SOF_IPC_COMP_GET_VALUE, + SOF_CTRL_TYPE_VALUE_CHAN_GET, + SOF_CTRL_CMD_SWITCH); + + /* read back each channel */ + for (i = 0; i < channels; i++) + ucontrol->value.integer.value[i] = cdata->chanv[i].value; + + pm_runtime_mark_last_busy(sdev->dev); + pm_runtime_put_autosuspend(sdev->dev); + return 0; +} + +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 snd_sof_control *scontrol = sm->dobj.private; + struct snd_sof_dev *sdev = scontrol->sdev; + struct sof_ipc_ctrl_data *cdata = scontrol->control_data; + struct snd_soc_dapm_context *dapm = + snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_card *card = dapm->card; + unsigned int i, channels = scontrol->num_channels; + int ret = 0, new_state, old_state; + int changed = 0; + + pm_runtime_get_sync(sdev->dev); + + switch (get_widget_type(sdev, scontrol)) { + case snd_soc_dapm_pga: + + /* + * if the kcontrol is used for processing as in the case of pga, + * values are channel-specific + */ + for (i = 0; i < channels; i++) { + new_state = ucontrol->value.integer.value[i]; + old_state = cdata->chanv[i].value; + if (new_state != old_state) + changed = 1; + cdata->chanv[i].value = new_state; + cdata->chanv[i].channel = i; + } + + /* + * notify DSP of switch state update + * if the control values are different + */ + if (changed) + snd_sof_ipc_set_comp_data(sdev->ipc, scontrol, + SOF_IPC_COMP_SET_VALUE, + SOF_CTRL_TYPE_VALUE_CHAN_GET, + SOF_CTRL_CMD_SWITCH); + break; + case snd_soc_dapm_siggen: + + /* + * A siggen kcontrol is used as an ON/OFF switch, + * so all channel values are assumed to be identical + */ + for (i = 0; i < channels; i++) { + new_state = ucontrol->value.integer.value[0]; + old_state = cdata->chanv[0].value; + if (new_state != old_state) + changed = 1; + cdata->chanv[i].value = new_state; + cdata->chanv[i].channel = i; + } + + if (changed) { + + /* + * notify DSP of switch state update + * if the control values are different + */ + snd_sof_ipc_set_comp_data(sdev->ipc, scontrol, + SOF_IPC_COMP_SET_VALUE, + SOF_CTRL_TYPE_VALUE_CHAN_GET, + SOF_CTRL_CMD_SWITCH); + + /* trigger siggen pipeline */ + ret = siggen_pipeline_trigger(scontrol, sdev, + card, new_state); + if (ret < 0) { + dev_err(sdev->dev, + "error: triggering siggen pipeline\n"); + changed = ret; + } + } + break; + default: + + /* + * if the kcontrol is for routing or a standalone control, + * all channel values are assumed to be identical + */ + for (i = 0; i < channels; i++) { + new_state = ucontrol->value.integer.value[0]; + old_state = cdata->chanv[0].value; + if (new_state != old_state) + changed = 1; + cdata->chanv[i].value = new_state; + cdata->chanv[i].channel = i; + } + + /* + * notify DSP of switch state update + * if the control values are different + */ + if (changed) + snd_sof_ipc_set_comp_data(sdev->ipc, scontrol, + SOF_IPC_COMP_SET_VALUE, + SOF_CTRL_TYPE_VALUE_CHAN_GET, + SOF_CTRL_CMD_SWITCH); + + break; + } + + pm_runtime_mark_last_busy(sdev->dev); + pm_runtime_put_autosuspend(sdev->dev); + return changed; +} diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index fabfcb25180940..4a97bc73ff6146 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -454,6 +454,10 @@ int snd_sof_bytes_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); int snd_sof_bytes_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); +int snd_sof_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_sof_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); /* * DSP Architectures. diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index eb9e16f643b5a5..f0d968fadd3d8c 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -376,6 +376,8 @@ static const struct sof_topology_token src_tokens[] = { /* Tone */ static const struct sof_topology_token tone_tokens[] = { + {SOF_TKN_TONE_SAMPLE_RATE, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, + offsetof(struct sof_ipc_comp_tone, sample_rate), 0}, }; /* PCM */ @@ -1172,10 +1174,36 @@ static int sof_widget_load_siggen(struct snd_soc_component *scomp, int index, struct sof_ipc_comp_reply *r) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); + struct snd_soc_dapm_widget *widget = swidget->widget; struct snd_soc_tplg_private *private = &tw->priv; + const struct snd_kcontrol_new *kc = NULL; struct sof_ipc_comp_tone tone; + struct soc_mixer_control *sm; + struct snd_sof_control *scontrol; int ret; + /* siggen needs 1 mixer type control to act as a trigger */ + if (tw->num_kcontrols != 1) { + dev_err(sdev->dev, "error: invalid kcontrol count %d for siggen\n", + tw->num_kcontrols); + return -EINVAL; + } + + /* get mixer control */ + kc = &widget->kcontrol_news[0]; + sm = (struct soc_mixer_control *)kc->private_value; + scontrol = sm->dobj.private; + + /* + * siggen kcontrol needs only 2 values + * 0 for disabling and 1 for enabling the comp + */ + if (sm->max != 1) { + dev_err(sdev->dev, "error: invalid max %d for siggen control\n", + sm->max); + return -EINVAL; + } + /* configure mixer IPC message */ memset(&tone, 0, sizeof(tone)); tone.comp.hdr.size = sizeof(tone); @@ -1202,8 +1230,9 @@ static int sof_widget_load_siggen(struct snd_soc_component *scomp, int index, return ret; } - dev_dbg(sdev->dev, "tone %s: frequency %d amplitude %d\n", - swidget->widget->name, tone.frequency, tone.amplitude); + dev_dbg(sdev->dev, "tone %s: frequency %d amplitude %d sample rate %d\n", + swidget->widget->name, tone.frequency, tone.amplitude, + tone.sample_rate); sof_dbg_comp_config(scomp, &tone.config); return sof_ipc_tx_message(sdev->ipc, @@ -1932,6 +1961,7 @@ static const struct snd_soc_tplg_kcontrol_ops sof_io_ops[] = { {SOF_TPLG_KCTL_VOL_ID, snd_sof_volume_get, snd_sof_volume_put}, {SOF_TPLG_KCTL_ENUM_ID, snd_sof_enum_get, snd_sof_enum_put}, {SOF_TPLG_KCTL_BYTES_ID, snd_sof_bytes_get, snd_sof_bytes_put}, + {SOF_TPLG_KCTL_SWITCH_ID, snd_sof_switch_get, snd_sof_switch_put}, }; /* vendor specific bytes ext handlers available for binding */