root/sound/soc/intel/avs/pcm.c
// SPDX-License-Identifier: GPL-2.0-only
//
// Copyright(c) 2021-2022 Intel Corporation
//
// Authors: Cezary Rojewski <cezary.rojewski@intel.com>
//          Amadeusz Slawinski <amadeuszx.slawinski@linux.intel.com>
//

#include <linux/debugfs.h>
#include <linux/device.h>
#include <sound/hda_register.h>
#include <sound/hdaudio_ext.h>
#include <sound/pcm_params.h>
#include <sound/soc-acpi.h>
#include <sound/soc-acpi-intel-match.h>
#include <sound/soc-component.h>
#include "avs.h"
#include "path.h"
#include "pcm.h"
#include "topology.h"
#include "utils.h"
#include "../../codecs/hda.h"

struct avs_dma_data {
        struct avs_tplg_path_template *template;
        struct avs_path *path;
        struct avs_dev *adev;

        /* LINK-stream utilized in BE operations while HOST in FE ones. */
        union {
                struct hdac_ext_stream *link_stream;
                struct hdac_ext_stream *host_stream;
        };

        struct snd_pcm_hw_constraint_list rate_list;
        struct snd_pcm_hw_constraint_list channels_list;
        struct snd_pcm_hw_constraint_list sample_bits_list;

        struct work_struct period_elapsed_work;
        struct hdac_ext_link *link;
        struct snd_pcm_substream *substream;
};

static struct avs_tplg_path_template *
avs_dai_find_path_template(struct snd_soc_dai *dai, bool is_fe, int direction)
{
        struct snd_soc_dapm_widget *dw = snd_soc_dai_get_widget(dai, direction);
        struct snd_soc_dapm_path *dp;
        enum snd_soc_dapm_direction dir;

        if (direction == SNDRV_PCM_STREAM_CAPTURE) {
                dir = is_fe ? SND_SOC_DAPM_DIR_OUT : SND_SOC_DAPM_DIR_IN;
        } else {
                dir = is_fe ? SND_SOC_DAPM_DIR_IN : SND_SOC_DAPM_DIR_OUT;
        }

        dp = list_first_entry_or_null(&dw->edges[dir], typeof(*dp), list_node[dir]);
        if (!dp)
                return NULL;

        /* Get the other widget, with actual path template data */
        dw = (dp->source == dw) ? dp->sink : dp->source;

        return dw->priv;
}

static void avs_period_elapsed_work(struct work_struct *work)
{
        struct avs_dma_data *data = container_of(work, struct avs_dma_data, period_elapsed_work);

        snd_pcm_period_elapsed(data->substream);
}

void avs_period_elapsed(struct snd_pcm_substream *substream)
{
        struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
        struct snd_soc_dai *dai = snd_soc_rtd_to_cpu(rtd, 0);
        struct avs_dma_data *data = snd_soc_dai_get_dma_data(dai, substream);

        schedule_work(&data->period_elapsed_work);
}

static int hw_rule_param_size(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule);
static int avs_hw_constraints_init(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
        struct snd_pcm_runtime *runtime = substream->runtime;
        struct snd_pcm_hw_constraint_list *r, *c, *s;
        struct avs_dma_data *data;
        int ret;

        ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
        if (ret < 0)
                return ret;

        data = snd_soc_dai_get_dma_data(dai, substream);
        r = &(data->rate_list);
        c = &(data->channels_list);
        s = &(data->sample_bits_list);

        ret = avs_path_set_constraint(data->adev, data->template, r, c, s);
        if (ret <= 0)
                return ret;

        ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, r);
        if (ret < 0)
                return ret;

        ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, c);
        if (ret < 0)
                return ret;

        ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, s);
        if (ret < 0)
                return ret;

        return 0;
}

static int avs_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
        struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
        struct avs_dev *adev = to_avs_dev(dai->component->dev);
        struct avs_tplg_path_template *template;
        struct avs_dma_data *data;

        template = avs_dai_find_path_template(dai, !rtd->dai_link->no_pcm, substream->stream);
        if (!template) {
                dev_err(dai->dev, "no %s path for dai %s, invalid tplg?\n",
                        snd_pcm_stream_str(substream), dai->name);
                return -EINVAL;
        }

        data = kzalloc_obj(*data);
        if (!data)
                return -ENOMEM;

        data->substream = substream;
        data->template = template;
        data->adev = adev;
        INIT_WORK(&data->period_elapsed_work, avs_period_elapsed_work);
        snd_soc_dai_set_dma_data(dai, substream, data);

        if (rtd->dai_link->ignore_suspend)
                adev->num_lp_paths++;

        return avs_hw_constraints_init(substream, dai);
}

static void avs_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
        struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
        struct avs_dma_data *data;

        data = snd_soc_dai_get_dma_data(dai, substream);

        if (rtd->dai_link->ignore_suspend)
                data->adev->num_lp_paths--;

        kfree(data->rate_list.list);
        kfree(data->channels_list.list);
        kfree(data->sample_bits_list.list);

        snd_soc_dai_set_dma_data(dai, substream, NULL);
        kfree(data);
}

static int avs_dai_hw_params(struct snd_pcm_substream *substream,
                             struct snd_pcm_hw_params *fe_hw_params,
                             struct snd_pcm_hw_params *be_hw_params, struct snd_soc_dai *dai,
                             int dma_id)
{
        struct avs_dma_data *data;
        struct avs_path *path;
        int ret;

        data = snd_soc_dai_get_dma_data(dai, substream);

        dev_dbg(dai->dev, "%s FE hw_params str %p rtd %p",
                __func__, substream, substream->runtime);
        dev_dbg(dai->dev, "rate %d chn %d vbd %d bd %d\n",
                params_rate(fe_hw_params), params_channels(fe_hw_params),
                params_width(fe_hw_params), params_physical_width(fe_hw_params));

        dev_dbg(dai->dev, "%s BE hw_params str %p rtd %p",
                __func__, substream, substream->runtime);
        dev_dbg(dai->dev, "rate %d chn %d vbd %d bd %d\n",
                params_rate(be_hw_params), params_channels(be_hw_params),
                params_width(be_hw_params), params_physical_width(be_hw_params));

        path = avs_path_create(data->adev, dma_id, data->template, fe_hw_params, be_hw_params);
        if (IS_ERR(path)) {
                ret = PTR_ERR(path);
                dev_err(dai->dev, "create path failed: %d\n", ret);
                return ret;
        }

        data->path = path;
        return 0;
}

static int avs_dai_be_hw_params(struct snd_pcm_substream *substream,
                                struct snd_pcm_hw_params *be_hw_params, struct snd_soc_dai *dai,
                                int dma_id)
{
        struct snd_pcm_hw_params *fe_hw_params = NULL;
        struct snd_soc_pcm_runtime *fe, *be;
        struct snd_soc_dpcm *dpcm;

        be = snd_soc_substream_to_rtd(substream);
        /* dpcm_fe_dai_open() guarantees the list is not empty at this point. */
        for_each_dpcm_fe(be, substream->stream, dpcm) {
                fe = dpcm->fe;
                fe_hw_params = &fe->dpcm[substream->stream].hw_params;
        }

        return avs_dai_hw_params(substream, fe_hw_params, be_hw_params, dai, dma_id);
}

static int avs_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
        struct avs_dma_data *data;
        int ret;

        data = snd_soc_dai_get_dma_data(dai, substream);
        if (!data->path)
                return 0;

        ret = avs_path_reset(data->path);
        if (ret < 0) {
                dev_err(dai->dev, "reset path failed: %d\n", ret);
                return ret;
        }

        ret = avs_path_pause(data->path);
        if (ret < 0)
                dev_err(dai->dev, "pause path failed: %d\n", ret);
        return ret;
}

static int avs_dai_nonhda_be_hw_params(struct snd_pcm_substream *substream,
                                       struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *dai)
{
        struct avs_dma_data *data;

        data = snd_soc_dai_get_dma_data(dai, substream);
        if (data->path)
                return 0;

        /* Actual port-id comes from topology. */
        return avs_dai_be_hw_params(substream, hw_params, dai, 0);
}

static int avs_dai_nonhda_be_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
        struct avs_dma_data *data;

        dev_dbg(dai->dev, "%s: %s\n", __func__, dai->name);

        data = snd_soc_dai_get_dma_data(dai, substream);
        if (data->path) {
                avs_path_free(data->path);
                data->path = NULL;
        }

        return 0;
}

static int avs_dai_nonhda_be_trigger(struct snd_pcm_substream *substream, int cmd,
                                     struct snd_soc_dai *dai)
{
        struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
        struct avs_dma_data *data;
        int ret = 0;

        data = snd_soc_dai_get_dma_data(dai, substream);

        switch (cmd) {
        case SNDRV_PCM_TRIGGER_RESUME:
                if (rtd->dai_link->ignore_suspend)
                        break;
                fallthrough;
        case SNDRV_PCM_TRIGGER_START:
        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
                ret = avs_path_pause(data->path);
                if (ret < 0) {
                        dev_err(dai->dev, "pause BE path failed: %d\n", ret);
                        break;
                }

                ret = avs_path_run(data->path, AVS_TPLG_TRIGGER_AUTO);
                if (ret < 0)
                        dev_err(dai->dev, "run BE path failed: %d\n", ret);
                break;

        case SNDRV_PCM_TRIGGER_SUSPEND:
                if (rtd->dai_link->ignore_suspend)
                        break;
                fallthrough;
        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
        case SNDRV_PCM_TRIGGER_STOP:
                ret = avs_path_pause(data->path);
                if (ret < 0)
                        dev_err(dai->dev, "pause BE path failed: %d\n", ret);

                ret = avs_path_reset(data->path);
                if (ret < 0)
                        dev_err(dai->dev, "reset BE path failed: %d\n", ret);
                break;

        default:
                ret = -EINVAL;
                break;
        }

        return ret;
}

static const struct snd_soc_dai_ops avs_dai_nonhda_be_ops = {
        .startup = avs_dai_startup,
        .shutdown = avs_dai_shutdown,
        .hw_params = avs_dai_nonhda_be_hw_params,
        .hw_free = avs_dai_nonhda_be_hw_free,
        .prepare = avs_dai_prepare,
        .trigger = avs_dai_nonhda_be_trigger,
};

static int __avs_dai_hda_be_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai,
                                    struct hdac_ext_link *link)
{
        struct hdac_ext_stream *link_stream;
        struct avs_dma_data *data;
        int ret;

        ret = avs_dai_startup(substream, dai);
        if (ret)
                return ret;

        data = snd_soc_dai_get_dma_data(dai, substream);
        link_stream = snd_hdac_ext_stream_assign(&data->adev->base.core, substream,
                                                 HDAC_EXT_STREAM_TYPE_LINK);
        if (!link_stream) {
                avs_dai_shutdown(substream, dai);
                return -EBUSY;
        }

        data->link_stream = link_stream;
        data->link = link;
        return 0;
}

static int avs_dai_hda_be_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
        struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
        struct hdac_ext_link *link;
        struct avs_dma_data *data;
        struct hda_codec *codec;
        int ret;

        codec = dev_to_hda_codec(snd_soc_rtd_to_codec(rtd, 0)->dev);

        link = snd_hdac_ext_bus_get_hlink_by_addr(&codec->bus->core, codec->core.addr);
        if (!link)
                return -EINVAL;

        ret = __avs_dai_hda_be_startup(substream, dai, link);
        if (!ret) {
                data = snd_soc_dai_get_dma_data(dai, substream);
                substream->runtime->private_data = data->link_stream;
        }

        return ret;
}

static int avs_dai_i2shda_be_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
        struct avs_dev *adev = to_avs_dev(dai->component->dev);
        struct hdac_ext_link *link;

        link = snd_hdac_ext_bus_get_hlink_by_id(&adev->base.core, AZX_REG_ML_LEPTR_ID_INTEL_SSP);
        if (!link)
                return -EINVAL;
        return __avs_dai_hda_be_startup(substream, dai, link);
}

static int avs_dai_dmichda_be_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
        struct avs_dev *adev = to_avs_dev(dai->component->dev);
        struct hdac_ext_link *link;

        link = snd_hdac_ext_bus_get_hlink_by_id(&adev->base.core, AZX_REG_ML_LEPTR_ID_INTEL_DMIC);
        if (!link)
                return -EINVAL;
        return __avs_dai_hda_be_startup(substream, dai, link);
}

static void avs_dai_hda_be_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
        struct avs_dma_data *data = snd_soc_dai_get_dma_data(dai, substream);

        snd_hdac_ext_stream_release(data->link_stream, HDAC_EXT_STREAM_TYPE_LINK);
        substream->runtime->private_data = NULL;
        avs_dai_shutdown(substream, dai);
}

static void avs_dai_althda_be_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
        struct avs_dma_data *data = snd_soc_dai_get_dma_data(dai, substream);

        snd_hdac_ext_stream_release(data->link_stream, HDAC_EXT_STREAM_TYPE_LINK);
        avs_dai_shutdown(substream, dai);
}

static int avs_dai_hda_be_hw_params(struct snd_pcm_substream *substream,
                                    struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *dai)
{
        struct avs_dma_data *data;

        data = snd_soc_dai_get_dma_data(dai, substream);
        if (data->path)
                return 0;

        return avs_dai_be_hw_params(substream, hw_params, dai,
                                    hdac_stream(data->link_stream)->stream_tag - 1);
}

static int avs_dai_hda_be_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
        struct hdac_ext_stream *link_stream;
        struct avs_dma_data *data;

        data = snd_soc_dai_get_dma_data(dai, substream);
        if (!data->path)
                return 0;

        link_stream = data->link_stream;
        link_stream->link_prepared = false;
        avs_path_free(data->path);
        data->path = NULL;

        /* clear link <-> stream mapping */
        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
                snd_hdac_ext_bus_link_clear_stream_id(data->link,
                                                      hdac_stream(link_stream)->stream_tag);

        return 0;
}

static int avs_dai_hda_be_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
        struct snd_soc_pcm_runtime *be = snd_soc_substream_to_rtd(substream);
        const struct snd_soc_pcm_stream *stream_info;
        struct hdac_ext_stream *link_stream;
        const struct snd_pcm_hw_params *p;
        struct avs_dma_data *data;
        unsigned int format_val;
        unsigned int bits;
        int ret;

        data = snd_soc_dai_get_dma_data(dai, substream);
        link_stream = data->link_stream;
        p = &be->dpcm[substream->stream].hw_params;

        if (link_stream->link_prepared)
                return 0;

        stream_info = snd_soc_dai_get_pcm_stream(dai, substream->stream);
        bits = snd_hdac_stream_format_bits(params_format(p), params_subformat(p),
                                           stream_info->sig_bits);
        format_val = snd_hdac_stream_format(params_channels(p), bits, params_rate(p));

        snd_hdac_ext_stream_decouple(&data->adev->base.core, link_stream, true);
        snd_hdac_ext_stream_reset(link_stream);
        snd_hdac_ext_stream_setup(link_stream, format_val);

        if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
                snd_hdac_ext_bus_link_set_stream_id(data->link,
                                                    hdac_stream(link_stream)->stream_tag);

        ret = avs_dai_prepare(substream, dai);
        if (ret)
                return ret;

        link_stream->link_prepared = true;
        return 0;
}

static int avs_dai_hda_be_trigger(struct snd_pcm_substream *substream, int cmd,
                                  struct snd_soc_dai *dai)
{
        struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
        struct avs_dma_data *data;
        int ret = 0;

        dev_dbg(dai->dev, "entry %s cmd=%d\n", __func__, cmd);

        data = snd_soc_dai_get_dma_data(dai, substream);

        switch (cmd) {
        case SNDRV_PCM_TRIGGER_RESUME:
                if (rtd->dai_link->ignore_suspend)
                        break;
                fallthrough;
        case SNDRV_PCM_TRIGGER_START:
        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
                snd_hdac_ext_stream_start(data->link_stream);

                ret = avs_path_pause(data->path);
                if (ret < 0) {
                        dev_err(dai->dev, "pause BE path failed: %d\n", ret);
                        break;
                }

                ret = avs_path_run(data->path, AVS_TPLG_TRIGGER_AUTO);
                if (ret < 0)
                        dev_err(dai->dev, "run BE path failed: %d\n", ret);
                break;

        case SNDRV_PCM_TRIGGER_SUSPEND:
                if (rtd->dai_link->ignore_suspend)
                        break;
                fallthrough;
        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
        case SNDRV_PCM_TRIGGER_STOP:
                ret = avs_path_pause(data->path);
                if (ret < 0)
                        dev_err(dai->dev, "pause BE path failed: %d\n", ret);

                snd_hdac_ext_stream_clear(data->link_stream);

                ret = avs_path_reset(data->path);
                if (ret < 0)
                        dev_err(dai->dev, "reset BE path failed: %d\n", ret);
                break;

        default:
                ret = -EINVAL;
                break;
        }

        return ret;
}

static const struct snd_soc_dai_ops avs_dai_hda_be_ops = {
        .startup = avs_dai_hda_be_startup,
        .shutdown = avs_dai_hda_be_shutdown,
        .hw_params = avs_dai_hda_be_hw_params,
        .hw_free = avs_dai_hda_be_hw_free,
        .prepare = avs_dai_hda_be_prepare,
        .trigger = avs_dai_hda_be_trigger,
};

static const struct snd_soc_dai_ops avs_dai_i2shda_be_ops = {
        .startup = avs_dai_i2shda_be_startup,
        .shutdown = avs_dai_althda_be_shutdown,
        .hw_params = avs_dai_hda_be_hw_params,
        .hw_free = avs_dai_hda_be_hw_free,
        .prepare = avs_dai_hda_be_prepare,
        .trigger = avs_dai_hda_be_trigger,
};

static const struct snd_soc_dai_ops avs_dai_dmichda_be_ops = {
        .startup = avs_dai_dmichda_be_startup,
        .shutdown = avs_dai_althda_be_shutdown,
        .hw_params = avs_dai_hda_be_hw_params,
        .hw_free = avs_dai_hda_be_hw_free,
        .prepare = avs_dai_hda_be_prepare,
        .trigger = avs_dai_hda_be_trigger,
};

static int hw_rule_param_size(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule)
{
        struct snd_interval *interval = hw_param_interval(params, rule->var);
        struct snd_interval to;

        snd_interval_any(&to);
        to.integer = interval->integer;
        to.max = interval->max;
        /*
         * Commonly 2ms buffer size is used in HDA scenarios whereas 4ms is used
         * when streaming through GPDMA. Align to the latter to account for both.
         */
        to.min = params_rate(params) / 1000 * 4;

        if (rule->var == SNDRV_PCM_HW_PARAM_PERIOD_SIZE)
                to.min /= params_periods(params);

        return snd_interval_refine(interval, &to);
}

static int avs_pcm_hw_constraints_init(struct snd_pcm_substream *substream)
{
        struct snd_pcm_runtime *runtime = substream->runtime;
        int ret;

        ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
        if (ret < 0)
                return ret;

        /* Avoid wrap-around with wall-clock. */
        ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_TIME, 20, 178000000);
        if (ret < 0)
                return ret;

        /* Adjust buffer and period size based on the audio format. */
        snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, hw_rule_param_size, NULL,
                            SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_HW_PARAM_CHANNELS,
                            SNDRV_PCM_HW_PARAM_RATE, -1);
        snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, hw_rule_param_size, NULL,
                            SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_HW_PARAM_CHANNELS,
                            SNDRV_PCM_HW_PARAM_RATE, -1);

        return 0;
}

static int avs_dai_fe_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
        struct hdac_ext_stream *host_stream;
        struct avs_dma_data *data;
        struct hdac_bus *bus;
        int ret;

        ret = avs_pcm_hw_constraints_init(substream);
        if (ret)
                return ret;

        ret = avs_dai_startup(substream, dai);
        if (ret)
                return ret;

        data = snd_soc_dai_get_dma_data(dai, substream);
        bus = &data->adev->base.core;

        host_stream = snd_hdac_ext_stream_assign(bus, substream, HDAC_EXT_STREAM_TYPE_HOST);
        if (!host_stream) {
                avs_dai_shutdown(substream, dai);
                return -EBUSY;
        }

        data->host_stream = host_stream;
        snd_pcm_set_sync(substream);

        dev_dbg(dai->dev, "%s fe STARTUP tag %d str %p",
                __func__, hdac_stream(host_stream)->stream_tag, substream);

        return 0;
}

static void avs_dai_fe_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
        struct avs_dma_data *data;

        data = snd_soc_dai_get_dma_data(dai, substream);

        disable_work_sync(&data->period_elapsed_work);
        snd_hdac_ext_stream_release(data->host_stream, HDAC_EXT_STREAM_TYPE_HOST);
        avs_dai_shutdown(substream, dai);
}

static int avs_dai_fe_hw_params(struct snd_pcm_substream *substream,
                                struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *dai)
{
        struct snd_pcm_hw_params *be_hw_params = NULL;
        struct snd_soc_pcm_runtime *fe, *be;
        struct snd_soc_dpcm *dpcm;
        struct avs_dma_data *data;
        struct hdac_ext_stream *host_stream;
        int ret;

        data = snd_soc_dai_get_dma_data(dai, substream);
        if (data->path)
                return 0;

        host_stream = data->host_stream;

        hdac_stream(host_stream)->bufsize = 0;
        hdac_stream(host_stream)->period_bytes = 0;
        hdac_stream(host_stream)->format_val = 0;

        fe = snd_soc_substream_to_rtd(substream);
        /* dpcm_fe_dai_open() guarantees the list is not empty at this point. */
        for_each_dpcm_be(fe, substream->stream, dpcm) {
                be = dpcm->be;
                be_hw_params = &be->dpcm[substream->stream].hw_params;
        }

        ret = avs_dai_hw_params(substream, hw_params, be_hw_params, dai,
                                hdac_stream(host_stream)->stream_tag - 1);
        if (ret)
                goto create_err;

        ret = avs_path_bind(data->path);
        if (ret < 0) {
                dev_err(dai->dev, "bind FE <-> BE failed: %d\n", ret);
                goto bind_err;
        }

        return 0;

bind_err:
        avs_path_free(data->path);
        data->path = NULL;
create_err:
        snd_pcm_lib_free_pages(substream);
        return ret;
}

static int __avs_dai_fe_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
        struct avs_dma_data *data;
        struct hdac_ext_stream *host_stream;
        int ret;

        dev_dbg(dai->dev, "%s fe HW_FREE str %p rtd %p",
                __func__, substream, substream->runtime);

        data = snd_soc_dai_get_dma_data(dai, substream);
        if (!data->path)
                return 0;

        host_stream = data->host_stream;

        ret = avs_path_unbind(data->path);
        if (ret < 0)
                dev_err(dai->dev, "unbind FE <-> BE failed: %d\n", ret);

        avs_path_free(data->path);
        data->path = NULL;
        snd_hdac_stream_cleanup(hdac_stream(host_stream));
        hdac_stream(host_stream)->prepared = false;

        return ret;
}

static int avs_dai_fe_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
        int ret;

        ret = __avs_dai_fe_hw_free(substream, dai);
        snd_pcm_lib_free_pages(substream);

        return ret;
}

static int avs_dai_fe_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
        struct snd_pcm_runtime *runtime = substream->runtime;
        const struct snd_soc_pcm_stream *stream_info;
        struct avs_dma_data *data;
        struct hdac_ext_stream *host_stream;
        unsigned int format_val;
        struct hdac_bus *bus;
        unsigned int bits;
        int ret;

        data = snd_soc_dai_get_dma_data(dai, substream);
        host_stream = data->host_stream;

        if (runtime->state == SNDRV_PCM_STATE_XRUN)
                hdac_stream(host_stream)->prepared = false;
        if (hdac_stream(host_stream)->prepared)
                return 0;

        bus = hdac_stream(host_stream)->bus;
        snd_hdac_ext_stream_decouple(bus, data->host_stream, true);
        snd_hdac_stream_reset(hdac_stream(host_stream));

        stream_info = snd_soc_dai_get_pcm_stream(dai, substream->stream);
        bits = snd_hdac_stream_format_bits(runtime->format, runtime->subformat,
                                           stream_info->sig_bits);
        format_val = snd_hdac_stream_format(runtime->channels, bits, runtime->rate);

        ret = snd_hdac_stream_set_params(hdac_stream(host_stream), format_val);
        if (ret < 0)
                return ret;

        ret = snd_hdac_ext_host_stream_setup(host_stream, false);
        if (ret < 0)
                return ret;

        ret = avs_dai_prepare(substream, dai);
        if (ret)
                return ret;

        hdac_stream(host_stream)->prepared = true;
        return 0;
}

static void avs_hda_stream_start(struct hdac_bus *bus, struct hdac_ext_stream *host_stream)
{
        struct hdac_stream *first_running = NULL;
        struct hdac_stream *pos;
        struct avs_dev *adev = hdac_to_avs(bus);

        list_for_each_entry(pos, &bus->stream_list, list) {
                if (pos->running) {
                        if (first_running)
                                break; /* more than one running */
                        first_running = pos;
                }
        }

        /*
         * If host_stream is a CAPTURE stream and will be the only one running,
         * disable L1SEN to avoid sound clipping.
         */
        if (!first_running) {
                if (hdac_stream(host_stream)->direction == SNDRV_PCM_STREAM_CAPTURE)
                        avs_hda_l1sen_enable(adev, false);
                snd_hdac_stream_start(hdac_stream(host_stream));
                return;
        }

        snd_hdac_stream_start(hdac_stream(host_stream));
        /*
         * If host_stream is the first stream to break the rule above,
         * re-enable L1SEN.
         */
        if (list_entry_is_head(pos, &bus->stream_list, list) &&
            first_running->direction == SNDRV_PCM_STREAM_CAPTURE)
                avs_hda_l1sen_enable(adev, true);
}

static void avs_hda_stream_stop(struct hdac_bus *bus, struct hdac_ext_stream *host_stream)
{
        struct hdac_stream *first_running = NULL;
        struct hdac_stream *pos;
        struct avs_dev *adev = hdac_to_avs(bus);

        list_for_each_entry(pos, &bus->stream_list, list) {
                if (pos == hdac_stream(host_stream))
                        continue; /* ignore stream that is about to be stopped */
                if (pos->running) {
                        if (first_running)
                                break; /* more than one running */
                        first_running = pos;
                }
        }

        /*
         * If host_stream is a CAPTURE stream and is the only one running,
         * re-enable L1SEN.
         */
        if (!first_running) {
                snd_hdac_stream_stop(hdac_stream(host_stream));
                if (hdac_stream(host_stream)->direction == SNDRV_PCM_STREAM_CAPTURE)
                        avs_hda_l1sen_enable(adev, true);
                return;
        }

        /*
         * If by stopping host_stream there is only a single, CAPTURE stream running
         * left, disable L1SEN to avoid sound clipping.
         */
        if (list_entry_is_head(pos, &bus->stream_list, list) &&
            first_running->direction == SNDRV_PCM_STREAM_CAPTURE)
                avs_hda_l1sen_enable(adev, false);

        snd_hdac_stream_stop(hdac_stream(host_stream));
}

static int avs_dai_fe_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai)
{
        struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
        struct avs_dma_data *data;
        struct hdac_ext_stream *host_stream;
        struct hdac_bus *bus;
        unsigned long flags;
        int ret = 0;

        data = snd_soc_dai_get_dma_data(dai, substream);
        host_stream = data->host_stream;
        bus = hdac_stream(host_stream)->bus;

        switch (cmd) {
        case SNDRV_PCM_TRIGGER_RESUME:
                if (rtd->dai_link->ignore_suspend)
                        break;
                fallthrough;
        case SNDRV_PCM_TRIGGER_START:
        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
                spin_lock_irqsave(&bus->reg_lock, flags);
                avs_hda_stream_start(bus, host_stream);
                spin_unlock_irqrestore(&bus->reg_lock, flags);

                /* Timeout on DRSM poll shall not stop the resume so ignore the result. */
                if (cmd == SNDRV_PCM_TRIGGER_RESUME)
                        snd_hdac_stream_wait_drsm(hdac_stream(host_stream));

                ret = avs_path_pause(data->path);
                if (ret < 0) {
                        dev_err(dai->dev, "pause FE path failed: %d\n", ret);
                        break;
                }

                ret = avs_path_run(data->path, AVS_TPLG_TRIGGER_AUTO);
                if (ret < 0)
                        dev_err(dai->dev, "run FE path failed: %d\n", ret);

                break;

        case SNDRV_PCM_TRIGGER_SUSPEND:
                if (rtd->dai_link->ignore_suspend)
                        break;
                fallthrough;
        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
        case SNDRV_PCM_TRIGGER_STOP:
                ret = avs_path_pause(data->path);
                if (ret < 0)
                        dev_err(dai->dev, "pause FE path failed: %d\n", ret);

                spin_lock_irqsave(&bus->reg_lock, flags);
                avs_hda_stream_stop(bus, host_stream);
                spin_unlock_irqrestore(&bus->reg_lock, flags);

                ret = avs_path_reset(data->path);
                if (ret < 0)
                        dev_err(dai->dev, "reset FE path failed: %d\n", ret);
                break;

        default:
                ret = -EINVAL;
                break;
        }

        return ret;
}

const struct snd_soc_dai_ops avs_dai_fe_ops = {
        .startup = avs_dai_fe_startup,
        .shutdown = avs_dai_fe_shutdown,
        .hw_params = avs_dai_fe_hw_params,
        .hw_free = avs_dai_fe_hw_free,
        .prepare = avs_dai_fe_prepare,
        .trigger = avs_dai_fe_trigger,
};

static ssize_t topology_name_read(struct file *file, char __user *user_buf, size_t count,
                                  loff_t *ppos)
{
        struct snd_soc_component *component = file->private_data;
        struct snd_soc_card *card = component->card;
        struct snd_soc_acpi_mach *mach = dev_get_platdata(card->dev);
        char buf[64];
        size_t len;

        len = scnprintf(buf, sizeof(buf), "%s/%s\n", component->driver->topology_name_prefix,
                        mach->tplg_filename);

        return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}

static const struct file_operations topology_name_fops = {
        .open = simple_open,
        .read = topology_name_read,
        .llseek = default_llseek,
};

static int avs_component_load_libraries(struct avs_soc_component *acomp)
{
        struct avs_tplg *tplg = acomp->tplg;
        struct avs_dev *adev = to_avs_dev(acomp->base.dev);
        int ret;

        if (!tplg->num_libs)
                return 0;

        /* Parent device may be asleep and library loading involves IPCs. */
        ret = pm_runtime_resume_and_get(adev->dev);
        if (ret < 0)
                return ret;

        avs_hda_power_gating_enable(adev, false);
        avs_hda_clock_gating_enable(adev, false);
        avs_hda_l1sen_enable(adev, false);

        ret = avs_dsp_load_libraries(adev, tplg->libs, tplg->num_libs);

        avs_hda_l1sen_enable(adev, true);
        avs_hda_clock_gating_enable(adev, true);
        avs_hda_power_gating_enable(adev, true);

        if (!ret)
                ret = avs_module_info_init(adev, false);

        pm_runtime_put_autosuspend(adev->dev);

        return ret;
}

static int avs_component_probe(struct snd_soc_component *component)
{
        struct snd_soc_card *card = component->card;
        struct snd_soc_acpi_mach *mach;
        struct avs_soc_component *acomp;
        struct avs_dev *adev;
        char *filename;
        int ret;

        dev_dbg(card->dev, "probing %s card %s\n", component->name, card->name);
        mach = dev_get_platdata(card->dev);
        acomp = to_avs_soc_component(component);
        adev = to_avs_dev(component->dev);

        acomp->tplg = avs_tplg_new(component);
        if (!acomp->tplg)
                return -ENOMEM;

        if (!mach->tplg_filename)
                goto finalize;

        /* Load specified topology and create debugfs for it. */
        filename = kasprintf(GFP_KERNEL, "%s/%s", component->driver->topology_name_prefix,
                             mach->tplg_filename);
        if (!filename)
                return -ENOMEM;

        ret = avs_load_topology(component, filename);
        kfree(filename);
        if (ret == -ENOENT && !strncmp(mach->tplg_filename, "hda-", 4)) {
                unsigned int vendor_id;

                if (sscanf(mach->tplg_filename, "hda-%08x-tplg.bin", &vendor_id) != 1)
                        return ret;

                if (((vendor_id >> 16) & 0xFFFF) == 0x8086)
                        mach->tplg_filename = devm_kasprintf(adev->dev, GFP_KERNEL,
                                                             "hda-8086-generic-tplg.bin");
                else
                        mach->tplg_filename = devm_kasprintf(adev->dev, GFP_KERNEL,
                                                             "hda-generic-tplg.bin");
                if (!mach->tplg_filename)
                        return -ENOMEM;
                filename = kasprintf(GFP_KERNEL, "%s/%s", component->driver->topology_name_prefix,
                                     mach->tplg_filename);
                if (!filename)
                        return -ENOMEM;

                dev_info(card->dev, "trying to load fallback topology %s\n", mach->tplg_filename);
                ret = avs_load_topology(component, filename);
                kfree(filename);
        }
        if (ret < 0)
                return ret;

        ret = avs_component_load_libraries(acomp);
        if (ret < 0) {
                dev_err(card->dev, "libraries loading failed: %d\n", ret);
                goto err_load_libs;
        }

finalize:
        debugfs_create_file("topology_name", 0444, component->debugfs_root, component,
                            &topology_name_fops);

        mutex_lock(&adev->comp_list_mutex);
        list_add_tail(&acomp->node, &adev->comp_list);
        mutex_unlock(&adev->comp_list_mutex);

        return 0;

err_load_libs:
        avs_remove_topology(component);
        return ret;
}

static void avs_component_remove(struct snd_soc_component *component)
{
        struct avs_soc_component *acomp = to_avs_soc_component(component);
        struct snd_soc_acpi_mach *mach;
        struct avs_dev *adev = to_avs_dev(component->dev);
        int ret;

        mach = dev_get_platdata(component->card->dev);

        mutex_lock(&adev->comp_list_mutex);
        list_del(&acomp->node);
        mutex_unlock(&adev->comp_list_mutex);

        if (mach->tplg_filename) {
                ret = avs_remove_topology(component);
                if (ret < 0)
                        dev_err(component->dev, "unload topology failed: %d\n", ret);
        }
}

static int avs_dai_resume_hw_params(struct snd_soc_dai *dai, struct avs_dma_data *data)
{
        struct snd_pcm_substream *substream;
        struct snd_soc_pcm_runtime *rtd;
        int ret;

        substream = data->substream;
        rtd = snd_soc_substream_to_rtd(substream);

        ret = dai->driver->ops->hw_params(substream, &rtd->dpcm[substream->stream].hw_params, dai);
        if (ret)
                dev_err(dai->dev, "hw_params on resume failed: %d\n", ret);

        return ret;
}

static int avs_dai_resume_fe_prepare(struct snd_soc_dai *dai, struct avs_dma_data *data)
{
        struct hdac_ext_stream *host_stream;
        struct hdac_stream *hstream;
        struct hdac_bus *bus;
        int ret;

        host_stream = data->host_stream;
        hstream = hdac_stream(host_stream);
        bus = hdac_stream(host_stream)->bus;

        /* Set DRSM before programming stream and position registers. */
        snd_hdac_stream_drsm_enable(bus, true, hstream->index);

        ret = dai->driver->ops->prepare(data->substream, dai);
        if (ret) {
                dev_err(dai->dev, "prepare FE on resume failed: %d\n", ret);
                return ret;
        }

        writel(host_stream->pphcllpl, host_stream->pphc_addr + AZX_REG_PPHCLLPL);
        writel(host_stream->pphcllpu, host_stream->pphc_addr + AZX_REG_PPHCLLPU);
        writel(host_stream->pphcldpl, host_stream->pphc_addr + AZX_REG_PPHCLDPL);
        writel(host_stream->pphcldpu, host_stream->pphc_addr + AZX_REG_PPHCLDPU);

        /* As per HW spec recommendation, program LPIB and DPIB to the same value. */
        snd_hdac_stream_set_lpib(hstream, hstream->lpib);
        snd_hdac_stream_set_dpibr(bus, hstream, hstream->lpib);

        return 0;
}

static int avs_dai_resume_be_prepare(struct snd_soc_dai *dai, struct avs_dma_data *data)
{
        int ret;

        ret = dai->driver->ops->prepare(data->substream, dai);
        if (ret)
                dev_err(dai->dev, "prepare BE on resume failed: %d\n", ret);

        return ret;
}

static int avs_dai_suspend_fe_hw_free(struct snd_soc_dai *dai, struct avs_dma_data *data)
{
        struct hdac_ext_stream *host_stream;
        int ret;

        host_stream = data->host_stream;

        /* Store position addresses so we can resume from them later on. */
        hdac_stream(host_stream)->lpib = snd_hdac_stream_get_pos_lpib(hdac_stream(host_stream));
        host_stream->pphcllpl = readl(host_stream->pphc_addr + AZX_REG_PPHCLLPL);
        host_stream->pphcllpu = readl(host_stream->pphc_addr + AZX_REG_PPHCLLPU);
        host_stream->pphcldpl = readl(host_stream->pphc_addr + AZX_REG_PPHCLDPL);
        host_stream->pphcldpu = readl(host_stream->pphc_addr + AZX_REG_PPHCLDPU);

        ret = __avs_dai_fe_hw_free(data->substream, dai);
        if (ret < 0)
                dev_err(dai->dev, "hw_free FE on suspend failed: %d\n", ret);

        return ret;
}

static int avs_dai_suspend_be_hw_free(struct snd_soc_dai *dai, struct avs_dma_data *data)
{
        int ret;

        ret = dai->driver->ops->hw_free(data->substream, dai);
        if (ret < 0)
                dev_err(dai->dev, "hw_free BE on suspend failed: %d\n", ret);

        return ret;
}

static int avs_component_pm_op(struct snd_soc_component *component, bool be,
                               int (*op)(struct snd_soc_dai *, struct avs_dma_data *))
{
        struct snd_soc_pcm_runtime *rtd;
        struct avs_dma_data *data;
        struct snd_soc_dai *dai;
        int ret;

        for_each_component_dais(component, dai) {
                data = snd_soc_dai_dma_data_get_playback(dai);
                if (data) {
                        rtd = snd_soc_substream_to_rtd(data->substream);
                        if (rtd->dai_link->no_pcm == be && !rtd->dai_link->ignore_suspend) {
                                ret = op(dai, data);
                                if (ret < 0) {
                                        __snd_pcm_set_state(data->substream->runtime,
                                                            SNDRV_PCM_STATE_DISCONNECTED);
                                        return ret;
                                }
                        }
                }

                data = snd_soc_dai_dma_data_get_capture(dai);
                if (data) {
                        rtd = snd_soc_substream_to_rtd(data->substream);
                        if (rtd->dai_link->no_pcm == be && !rtd->dai_link->ignore_suspend) {
                                ret = op(dai, data);
                                if (ret < 0) {
                                        __snd_pcm_set_state(data->substream->runtime,
                                                            SNDRV_PCM_STATE_DISCONNECTED);
                                        return ret;
                                }
                        }
                }
        }

        return 0;
}

static int avs_component_resume_hw_params(struct snd_soc_component *component, bool be)
{
        return avs_component_pm_op(component, be, &avs_dai_resume_hw_params);
}

static int avs_component_resume_prepare(struct snd_soc_component *component, bool be)
{
        int (*prepare_cb)(struct snd_soc_dai *dai, struct avs_dma_data *data);

        if (be)
                prepare_cb = &avs_dai_resume_be_prepare;
        else
                prepare_cb = &avs_dai_resume_fe_prepare;

        return avs_component_pm_op(component, be, prepare_cb);
}

static int avs_component_suspend_hw_free(struct snd_soc_component *component, bool be)
{
        int (*hw_free_cb)(struct snd_soc_dai *dai, struct avs_dma_data *data);

        if (be)
                hw_free_cb = &avs_dai_suspend_be_hw_free;
        else
                hw_free_cb = &avs_dai_suspend_fe_hw_free;

        return avs_component_pm_op(component, be, hw_free_cb);
}

static int avs_component_suspend(struct snd_soc_component *component)
{
        int ret;

        /*
         * When freeing paths, FEs need to be first as they perform
         * path unbinding.
         */
        ret = avs_component_suspend_hw_free(component, false);
        if (ret)
                return ret;

        return avs_component_suspend_hw_free(component, true);
}

static int avs_component_resume(struct snd_soc_component *component)
{
        int ret;

        /*
         * When creating paths, FEs need to be last as they perform
         * path binding.
         */
        ret = avs_component_resume_hw_params(component, true);
        if (ret)
                return ret;

        ret = avs_component_resume_hw_params(component, false);
        if (ret)
                return ret;

        /* It is expected that the LINK stream is prepared first. */
        ret = avs_component_resume_prepare(component, true);
        if (ret)
                return ret;

        return avs_component_resume_prepare(component, false);
}

static const struct snd_pcm_hardware avs_pcm_hardware = {
        .info                   = SNDRV_PCM_INFO_MMAP |
                                  SNDRV_PCM_INFO_MMAP_VALID |
                                  SNDRV_PCM_INFO_INTERLEAVED |
                                  SNDRV_PCM_INFO_PAUSE |
                                  SNDRV_PCM_INFO_RESUME |
                                  SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
        .formats                = SNDRV_PCM_FMTBIT_S16_LE |
                                  SNDRV_PCM_FMTBIT_S32_LE,
        .subformats             = SNDRV_PCM_SUBFMTBIT_MSBITS_20 |
                                  SNDRV_PCM_SUBFMTBIT_MSBITS_24 |
                                  SNDRV_PCM_SUBFMTBIT_MSBITS_MAX,
        .buffer_bytes_max       = AZX_MAX_BUF_SIZE,
        .period_bytes_min       = 128,
        .period_bytes_max       = AZX_MAX_BUF_SIZE / 2,
        .periods_min            = 2,
        .periods_max            = AZX_MAX_FRAG,
        .fifo_size              = 0,
};

static int avs_component_open(struct snd_soc_component *component,
                              struct snd_pcm_substream *substream)
{
        struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);

        /* only FE DAI links are handled here */
        if (rtd->dai_link->no_pcm)
                return 0;

        return snd_soc_set_runtime_hwparams(substream, &avs_pcm_hardware);
}

static unsigned int avs_hda_stream_dpib_read(struct hdac_ext_stream *stream)
{
        return readl(hdac_stream(stream)->bus->remap_addr + AZX_REG_VS_SDXDPIB_XBASE +
                     (AZX_REG_VS_SDXDPIB_XINTERVAL * hdac_stream(stream)->index));
}

static snd_pcm_uframes_t
avs_component_pointer(struct snd_soc_component *component, struct snd_pcm_substream *substream)
{
        struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
        struct avs_dma_data *data;
        struct hdac_ext_stream *host_stream;
        unsigned int pos;

        data = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream);
        if (!data->host_stream)
                return 0;

        host_stream = data->host_stream;
        pos = avs_hda_stream_dpib_read(host_stream);

        if (pos >= hdac_stream(host_stream)->bufsize)
                pos = 0;

        return bytes_to_frames(substream->runtime, pos);
}

static int avs_component_mmap(struct snd_soc_component *component,
                              struct snd_pcm_substream *substream,
                              struct vm_area_struct *vma)
{
        return snd_pcm_lib_default_mmap(substream, vma);
}

#define MAX_PREALLOC_SIZE       (32 * 1024 * 1024)

static int avs_component_construct(struct snd_soc_component *component,
                                   struct snd_soc_pcm_runtime *rtd)
{
        struct snd_soc_dai *dai = snd_soc_rtd_to_cpu(rtd, 0);
        struct snd_pcm *pcm = rtd->pcm;

        if (dai->driver->playback.channels_min)
                snd_pcm_set_managed_buffer(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream,
                                           SNDRV_DMA_TYPE_DEV_SG, component->dev, 0,
                                           MAX_PREALLOC_SIZE);

        if (dai->driver->capture.channels_min)
                snd_pcm_set_managed_buffer(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream,
                                           SNDRV_DMA_TYPE_DEV_SG, component->dev, 0,
                                           MAX_PREALLOC_SIZE);

        return 0;
}

static struct snd_soc_component_driver avs_component_driver = {
        .name                   = "avs-pcm",
        .probe                  = avs_component_probe,
        .remove                 = avs_component_remove,
        .suspend                = avs_component_suspend,
        .resume                 = avs_component_resume,
        .open                   = avs_component_open,
        .pointer                = avs_component_pointer,
        .mmap                   = avs_component_mmap,
        .pcm_construct          = avs_component_construct,
        .module_get_upon_open   = 1, /* increment refcount when a pcm is opened */
        .topology_name_prefix   = "intel/avs",
};

int avs_register_component(struct device *dev, const char *name,
                           struct snd_soc_component_driver *drv,
                           struct snd_soc_dai_driver *cpu_dais, int num_cpu_dais)
{
        struct avs_soc_component *acomp;
        int ret;

        acomp = devm_kzalloc(dev, sizeof(*acomp), GFP_KERNEL);
        if (!acomp)
                return -ENOMEM;

        acomp->base.name = devm_kstrdup(dev, name, GFP_KERNEL);
        if (!acomp->base.name)
                return -ENOMEM;

        INIT_LIST_HEAD(&acomp->node);

        drv->use_dai_pcm_id = !obsolete_card_names;

        ret = snd_soc_component_initialize(&acomp->base, drv, dev);
        if (ret < 0)
                return ret;

        return snd_soc_add_component(&acomp->base, cpu_dais, num_cpu_dais);
}

static struct snd_soc_dai_driver dmic_cpu_dais[] = {
{
        .name = "DMIC Pin",
        .capture = {
                .stream_name    = "DMIC Rx",
                .channels_min   = 1,
                .channels_max   = 4,
                .rates          = SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_48000,
                .formats        = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
        },
},
{
        .name = "DMIC WoV Pin",
        .capture = {
                .stream_name    = "DMIC WoV Rx",
                .channels_min   = 1,
                .channels_max   = 4,
                .rates          = SNDRV_PCM_RATE_16000,
                .formats        = SNDRV_PCM_FMTBIT_S16_LE,
        },
},
};

int avs_register_dmic_component(struct avs_dev *adev, const char *name)
{
        const struct snd_soc_dai_ops *ops;

        if (avs_platattr_test(adev, ALTHDA))
                ops = &avs_dai_dmichda_be_ops;
        else
                ops = &avs_dai_nonhda_be_ops;

        dmic_cpu_dais[0].ops = ops;
        dmic_cpu_dais[1].ops = ops;
        return avs_register_component(adev->dev, name, &avs_component_driver, dmic_cpu_dais,
                                      ARRAY_SIZE(dmic_cpu_dais));
}

static const struct snd_soc_dai_driver i2s_dai_template = {
        .playback = {
                .channels_min   = 1,
                .channels_max   = AVS_CHANNELS_MAX,
                .rates          = SNDRV_PCM_RATE_8000_192000 |
                                  SNDRV_PCM_RATE_12000 |
                                  SNDRV_PCM_RATE_24000 |
                                  SNDRV_PCM_RATE_128000,
                .formats        = SNDRV_PCM_FMTBIT_S16_LE |
                                  SNDRV_PCM_FMTBIT_S32_LE,
                .subformats     = SNDRV_PCM_SUBFMTBIT_MSBITS_20 |
                                  SNDRV_PCM_SUBFMTBIT_MSBITS_24 |
                                  SNDRV_PCM_SUBFMTBIT_MSBITS_MAX,
        },
        .capture = {
                .channels_min   = 1,
                .channels_max   = AVS_CHANNELS_MAX,
                .rates          = SNDRV_PCM_RATE_8000_192000 |
                                  SNDRV_PCM_RATE_12000 |
                                  SNDRV_PCM_RATE_24000 |
                                  SNDRV_PCM_RATE_128000,
                .formats        = SNDRV_PCM_FMTBIT_S16_LE |
                                  SNDRV_PCM_FMTBIT_S32_LE,
                .subformats     = SNDRV_PCM_SUBFMTBIT_MSBITS_20 |
                                  SNDRV_PCM_SUBFMTBIT_MSBITS_24 |
                                  SNDRV_PCM_SUBFMTBIT_MSBITS_MAX,
        },
};

int avs_register_i2s_component(struct avs_dev *adev, const char *name, unsigned long port_mask,
                               unsigned long *tdms)
{
        struct snd_soc_dai_driver *cpus, *dai;
        const struct snd_soc_dai_ops *ops;
        size_t ssp_count, cpu_count;
        int i, j;

        ssp_count = adev->hw_cfg.i2s_caps.ctrl_count;
        if (avs_platattr_test(adev, ALTHDA))
                ops = &avs_dai_i2shda_be_ops;
        else
                ops = &avs_dai_nonhda_be_ops;

        cpu_count = 0;
        for_each_set_bit(i, &port_mask, ssp_count)
                if (!tdms || test_bit(0, &tdms[i]))
                        cpu_count++;
        if (tdms)
                for_each_set_bit(i, &port_mask, ssp_count)
                        cpu_count += hweight_long(tdms[i]);

        cpus = devm_kcalloc(adev->dev, cpu_count, sizeof(*cpus), GFP_KERNEL);
        if (!cpus)
                return -ENOMEM;

        dai = cpus;
        for_each_set_bit(i, &port_mask, ssp_count) {
                if (!tdms || test_bit(0, &tdms[i])) {
                        memcpy(dai, &i2s_dai_template, sizeof(*dai));

                        dai->name =
                                devm_kasprintf(adev->dev, GFP_KERNEL, "SSP%d Pin", i);
                        dai->playback.stream_name =
                                devm_kasprintf(adev->dev, GFP_KERNEL, "ssp%d Tx", i);
                        dai->capture.stream_name =
                                devm_kasprintf(adev->dev, GFP_KERNEL, "ssp%d Rx", i);

                        if (!dai->name || !dai->playback.stream_name || !dai->capture.stream_name)
                                return -ENOMEM;
                        dai->ops = ops;
                        dai++;
                }
        }

        if (!tdms)
                goto plat_register;

        for_each_set_bit(i, &port_mask, ssp_count) {
                for_each_set_bit(j, &tdms[i], AVS_CHANNELS_MAX) {
                        memcpy(dai, &i2s_dai_template, sizeof(*dai));

                        dai->name =
                                devm_kasprintf(adev->dev, GFP_KERNEL, "SSP%d:%d Pin", i, j);
                        dai->playback.stream_name =
                                devm_kasprintf(adev->dev, GFP_KERNEL, "ssp%d:%d Tx", i, j);
                        dai->capture.stream_name =
                                devm_kasprintf(adev->dev, GFP_KERNEL, "ssp%d:%d Rx", i, j);

                        if (!dai->name || !dai->playback.stream_name || !dai->capture.stream_name)
                                return -ENOMEM;
                        dai->ops = ops;
                        dai++;
                }
        }

plat_register:
        return avs_register_component(adev->dev, name, &avs_component_driver, cpus, cpu_count);
}

/* HD-Audio CPU DAI template */
static const struct snd_soc_dai_driver hda_cpu_dai = {
        .ops = &avs_dai_hda_be_ops,
        .playback = {
                .channels_min   = 1,
                .channels_max   = AVS_CHANNELS_MAX,
                .rates          = SNDRV_PCM_RATE_8000_192000,
                .formats        = SNDRV_PCM_FMTBIT_S16_LE |
                                  SNDRV_PCM_FMTBIT_S32_LE,
                .subformats     = SNDRV_PCM_SUBFMTBIT_MSBITS_20 |
                                  SNDRV_PCM_SUBFMTBIT_MSBITS_24 |
                                  SNDRV_PCM_SUBFMTBIT_MSBITS_MAX,
        },
        .capture = {
                .channels_min   = 1,
                .channels_max   = AVS_CHANNELS_MAX,
                .rates          = SNDRV_PCM_RATE_8000_192000,
                .formats        = SNDRV_PCM_FMTBIT_S16_LE |
                                  SNDRV_PCM_FMTBIT_S32_LE,
                .subformats     = SNDRV_PCM_SUBFMTBIT_MSBITS_20 |
                                  SNDRV_PCM_SUBFMTBIT_MSBITS_24 |
                                  SNDRV_PCM_SUBFMTBIT_MSBITS_MAX,
        },
};

static void avs_component_hda_unregister_dais(struct snd_soc_component *component)
{
        struct snd_soc_acpi_mach *mach;
        struct snd_soc_dai *dai, *save;
        struct avs_mach_pdata *pdata;
        struct hda_codec *codec;
        char name[32];

        mach = dev_get_platdata(component->card->dev);
        pdata = mach->pdata;
        codec = pdata->codec;
        snprintf(name, sizeof(name), "%s-cpu", dev_name(&codec->core.dev));

        for_each_component_dais_safe(component, dai, save) {
                int stream;

                if (!strstr(dai->driver->name, name))
                        continue;

                for_each_pcm_streams(stream)
                        snd_soc_dapm_free_widget(snd_soc_dai_get_widget(dai, stream));

                snd_soc_unregister_dai(dai);
        }
}

static int avs_component_hda_probe(struct snd_soc_component *component)
{
        struct snd_soc_dapm_context *dapm;
        struct snd_soc_dai_driver *dais;
        struct snd_soc_acpi_mach *mach;
        struct avs_mach_pdata *pdata;
        struct hda_codec *codec;
        struct hda_pcm *pcm;
        const char *cname;
        int pcm_count = 0, ret, i;

        mach = dev_get_platdata(component->card->dev);
        if (!mach)
                return -EINVAL;

        pdata = mach->pdata;
        codec = pdata->codec;
        if (list_empty(&codec->pcm_list_head))
                return -EINVAL;
        list_for_each_entry(pcm, &codec->pcm_list_head, list)
                pcm_count++;

        dais = devm_kcalloc(component->dev, pcm_count, sizeof(*dais),
                            GFP_KERNEL);
        if (!dais)
                return -ENOMEM;

        cname = dev_name(&codec->core.dev);
        dapm = snd_soc_component_to_dapm(component);
        pcm = list_first_entry(&codec->pcm_list_head, struct hda_pcm, list);

        for (i = 0; i < pcm_count; i++, pcm = list_next_entry(pcm, list)) {
                struct snd_soc_dai *dai;

                memcpy(&dais[i], &hda_cpu_dai, sizeof(*dais));
                dais[i].id = i;
                dais[i].name = devm_kasprintf(component->dev, GFP_KERNEL,
                                              "%s-cpu%d", cname, i);
                if (!dais[i].name) {
                        ret = -ENOMEM;
                        goto exit;
                }

                if (pcm->stream[0].substreams) {
                        dais[i].playback.stream_name =
                                devm_kasprintf(component->dev, GFP_KERNEL,
                                               "%s-cpu%d Tx", cname, i);
                        if (!dais[i].playback.stream_name) {
                                ret = -ENOMEM;
                                goto exit;
                        }

                        if (!hda_codec_is_display(codec)) {
                                dais[i].playback.formats = pcm->stream[0].formats;
                                dais[i].playback.subformats = pcm->stream[0].subformats;
                                dais[i].playback.rates = pcm->stream[0].rates;
                                dais[i].playback.channels_min = pcm->stream[0].channels_min;
                                dais[i].playback.channels_max = pcm->stream[0].channels_max;
                                dais[i].playback.sig_bits = pcm->stream[0].maxbps;
                        }
                }

                if (pcm->stream[1].substreams) {
                        dais[i].capture.stream_name =
                                devm_kasprintf(component->dev, GFP_KERNEL,
                                               "%s-cpu%d Rx", cname, i);
                        if (!dais[i].capture.stream_name) {
                                ret = -ENOMEM;
                                goto exit;
                        }

                        if (!hda_codec_is_display(codec)) {
                                dais[i].capture.formats = pcm->stream[1].formats;
                                dais[i].capture.subformats = pcm->stream[1].subformats;
                                dais[i].capture.rates = pcm->stream[1].rates;
                                dais[i].capture.channels_min = pcm->stream[1].channels_min;
                                dais[i].capture.channels_max = pcm->stream[1].channels_max;
                                dais[i].capture.sig_bits = pcm->stream[1].maxbps;
                        }
                }

                dai = snd_soc_register_dai(component, &dais[i], false);
                if (!dai) {
                        dev_err(component->dev, "register dai for %s failed\n",
                                pcm->name);
                        ret = -EINVAL;
                        goto exit;
                }

                ret = snd_soc_dapm_new_dai_widgets(dapm, dai);
                if (ret < 0) {
                        dev_err(component->dev, "create widgets failed: %d\n",
                                ret);
                        snd_soc_unregister_dai(dai);
                        goto exit;
                }
        }

        ret = avs_component_probe(component);
exit:
        if (ret)
                avs_component_hda_unregister_dais(component);

        return ret;
}

static void avs_component_hda_remove(struct snd_soc_component *component)
{
        avs_component_remove(component);
        avs_component_hda_unregister_dais(component);
}

static int avs_component_hda_open(struct snd_soc_component *component,
                                  struct snd_pcm_substream *substream)
{
        struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);

        if (!rtd->dai_link->no_pcm) {
                struct snd_pcm_hardware hwparams = avs_pcm_hardware;
                struct snd_soc_pcm_runtime *be;
                struct snd_soc_dpcm *dpcm;
                int dir = substream->stream;

                /*
                 * Support the DPCM reparenting while still fulfilling expectations of HDAudio
                 * common code - a valid stream pointer at substream->runtime->private_data -
                 * by having all FEs point to the same private data.
                 */
                for_each_dpcm_be(rtd, dir, dpcm) {
                        struct snd_pcm_substream *be_substream;

                        be = dpcm->be;
                        if (be->dpcm[dir].users == 1)
                                break;

                        be_substream = snd_soc_dpcm_get_substream(be, dir);
                        substream->runtime->private_data = be_substream->runtime->private_data;
                        break;
                }

                /* RESUME unsupported for de-coupled HD-Audio capture. */
                if (dir == SNDRV_PCM_STREAM_CAPTURE)
                        hwparams.info &= ~SNDRV_PCM_INFO_RESUME;

                return snd_soc_set_runtime_hwparams(substream, &hwparams);
        }

        return 0;
}

static struct snd_soc_component_driver avs_hda_component_driver = {
        .name                   = "avs-hda-pcm",
        .probe                  = avs_component_hda_probe,
        .remove                 = avs_component_hda_remove,
        .suspend                = avs_component_suspend,
        .resume                 = avs_component_resume,
        .open                   = avs_component_hda_open,
        .pointer                = avs_component_pointer,
        .mmap                   = avs_component_mmap,
        .pcm_construct          = avs_component_construct,
        /*
         * hda platform component's probe() is dependent on
         * codec->pcm_list_head, it needs to be initialized after codec
         * component. remove_order is here for completeness sake
         */
        .probe_order            = SND_SOC_COMP_ORDER_LATE,
        .remove_order           = SND_SOC_COMP_ORDER_EARLY,
        .module_get_upon_open   = 1,
        .topology_name_prefix   = "intel/avs",
};

int avs_register_hda_component(struct avs_dev *adev, const char *name)
{
        return avs_register_component(adev->dev, name, &avs_hda_component_driver, NULL, 0);
}