[RFC PATCH 0/3] Separate BE DAI HW constraints from FE ones
Pierre-Louis Bossart
pierre-louis.bossart at linux.intel.com
Tue Mar 23 20:25:53 CET 2021
On 3/23/21 6:43 AM, Codrin Ciubotariu wrote:
> HW constraints are needed to set limitations for HW parameters used to
> configure the DAIs. All DAIs on the same link must agree upon the HW
> parameters, so the parameters are affected by the DAIs' features and
> their limitations. In case of DPCM, the FE DAIs can be used to perform
> different kind of conversions, such as format or rate changing, bringing
> the audio stream to a configuration supported by all the DAIs of the BE's
> link. For this reason, the limitations of the BE DAIs are not always
> important for the HW parameters between user-space and FE, only for the
> paratemers between FE and BE DAI links. This brings us to this patch-set,
> which aims to separate the FE HW constraints from the BE HW constraints.
> This way, the FE can be used to perform more efficient HW conversions, on
> the initial audio stream parameters, to parameters supported by the BE
> DAIs.
> To achieve this, the first thing needed is to detect whether a HW
> constraint rule is enforced by a FE or a BE DAI. This means that
> snd_pcm_hw_rule_add() needs to be able to differentiate between the two
> type of DAIs. For this, the runtime pointer to struct snd_pcm_runtime is
> replaced with a pointer to struct snd_pcm_substream, to be able to reach
> substream->pcm->internal to differentiate between FE and BE DAIs.
> This change affects many sound drivers (and one gpu drm driver).
> All these changes are included in the first patch, to have a better
> overview of the implications created by this change.
> The second patch adds a new struct snd_pcm_hw_constraints under struct
> snd_soc_dpcm_runtime, which is used to store the HW constraint rules
> added by the BE DAIs. This structure is initialized with a subset of the
> runtime constraint rules which does not include the rules that affect
> the buffer or period size. snd_pcm_hw_rule_add() will add the BE rules
> to the new struct snd_pcm_hw_constraints.
> The third and last patch will apply the BE rule constraints, after the
> fixup callback. If the fixup HW parameters do not respect the BE
> constraint rules, the rules will exit with an error. The FE mask and
> interval constraints are copied to the BE ones, to satisfy the
> dai_link->dpcm_merged_* flags. The dai_link->dpcm_merged_* flags are
> used to know if the FE does format or sampling rate conversion.
>
> I tested with ad1934 and wm8731 codecs as BEs, with a not-yet-mainlined
> ASRC as FE, that can also do format conversion. I realize that the
> change to snd_pcm_hw_rule_add() has a big impact, even though all the
> drivers use snd_pcm_hw_rule_add() with substream->runtime, so passing
> substream instead of runtime is not that risky.
can you use the BE hw_params_fixup instead?
That's what we use for SOF.
The FE hw_params are propagated to the BE, and then the BE can update
the hw_params based on its own limitations and pass the result
downstream, e.g. to a codec.
I'll copy below my understanding of the flow, which we discussed
recently in the SOF team:
my understanding is that we start with the front-end hw_params as the
basis for the back-end hw_params.
static int dpcm_fe_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *fe = asoc_substream_to_rtd(substream);
int ret, stream = substream->stream;
mutex_lock_nested(&fe->card->mutex, SND_SOC_CARD_CLASS_RUNTIME);
dpcm_set_fe_update_state(fe, stream, SND_SOC_DPCM_UPDATE_FE);
memcpy(&fe->dpcm[stream].hw_params, params,
sizeof(struct snd_pcm_hw_params));
ret = dpcm_be_dai_hw_params(fe, stream);
<<< the BE is handled first.
if (ret < 0) {
dev_err(fe->dev,"ASoC: hw_params BE failed %d\n", ret);
goto out;
}
dev_dbg(fe->dev, "ASoC: hw_params FE %s rate %d chan %x fmt %d\n",
fe->dai_link->name, params_rate(params),
params_channels(params), params_format(params));
/* call hw_params on the frontend */
ret = soc_pcm_hw_params(substream, params);
then each BE will be configured
int dpcm_be_dai_hw_params(struct snd_soc_pcm_runtime *fe, int stream)
{
struct snd_soc_dpcm *dpcm;
int ret;
for_each_dpcm_be(fe, stream, dpcm) {
struct snd_soc_pcm_runtime *be = dpcm->be;
struct snd_pcm_substream *be_substream =
snd_soc_dpcm_get_substream(be, stream);
/* is this op for this BE ? */
if (!snd_soc_dpcm_be_can_update(fe, be, stream))
continue;
/* copy params for each dpcm */
memcpy(&dpcm->hw_params, &fe->dpcm[stream].hw_params,
sizeof(struct snd_pcm_hw_params));
/* perform any hw_params fixups */
ret = snd_soc_link_be_hw_params_fixup(be, &dpcm->hw_params);
The fixup is the key, in SOF this is where we are going to look for
information from the topology.
/* fixup the BE DAI link to match any values from topology */
int sof_pcm_dai_link_fixup(struct snd_soc_pcm_runtime *rtd, struct
snd_pcm_hw_params *params)
{
struct snd_interval *rate = hw_param_interval(params,
SNDRV_PCM_HW_PARAM_RATE);
struct snd_interval *channels = hw_param_interval(params,
SNDRV_PCM_HW_PARAM_CHANNELS);
struct snd_mask *fmt = hw_param_mask(params,
SNDRV_PCM_HW_PARAM_FORMAT);
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 snd_soc_dpcm *dpcm;
/* no topology exists for this BE, try a common configuration */
if (!dai) {
dev_warn(component->dev,
"warning: no topology found for BE DAI %s config\n",
rtd->dai_link->name);
/* set 48k, stereo, 16bits by default */
rate->min = 48000;
rate->max = 48000;
channels->min = 2;
channels->max = 2;
snd_mask_none(fmt);
snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE);
return 0;
}
/* read format from topology */
snd_mask_none(fmt);
switch (dai->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 (dai->dai_config->type) {
case SOF_DAI_INTEL_SSP:
rate->min = dai->dai_config->ssp.fsync_rate;
rate->max = dai->dai_config->ssp.fsync_rate;
channels->min = dai->dai_config->ssp.tdm_slots;
channels->max = dai->dai_config->ssp.tdm_slots;
More information about the Alsa-devel
mailing list