root/sound/soc/mediatek/mt8365/mt8365-dai-pcm.c
// SPDX-License-Identifier: GPL-2.0
/*
 * MediaTek 8365 ALSA SoC Audio DAI PCM Control
 *
 * Copyright (c) 2024 MediaTek Inc.
 * Authors: Jia Zeng <jia.zeng@mediatek.com>
 *          Alexandre Mergnat <amergnat@baylibre.com>
 */

#include <linux/bitops.h>
#include <linux/regmap.h>
#include <sound/pcm_params.h>
#include "mt8365-afe-clk.h"
#include "mt8365-afe-common.h"

struct mt8365_pcm_intf_data {
        bool slave_mode;
        bool lrck_inv;
        bool bck_inv;
        unsigned int format;
};

/* DAI Drivers */

static void mt8365_dai_enable_pcm1(struct mtk_base_afe *afe)
{
        regmap_update_bits(afe->regmap, PCM_INTF_CON1,
                           PCM_INTF_CON1_EN, PCM_INTF_CON1_EN);
}

static void mt8365_dai_disable_pcm1(struct mtk_base_afe *afe)
{
        regmap_update_bits(afe->regmap, PCM_INTF_CON1,
                           PCM_INTF_CON1_EN, 0x0);
}

static int mt8365_dai_configure_pcm1(struct snd_pcm_substream *substream,
                                     struct snd_soc_dai *dai)
{
        struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
        struct mt8365_afe_private *afe_priv = afe->platform_priv;
        struct mt8365_pcm_intf_data *pcm_priv = afe_priv->dai_priv[MT8365_AFE_IO_PCM1];
        bool slave_mode = pcm_priv->slave_mode;
        bool lrck_inv = pcm_priv->lrck_inv;
        bool bck_inv = pcm_priv->bck_inv;
        unsigned int fmt = pcm_priv->format;
        unsigned int bit_width = dai->symmetric_sample_bits;
        unsigned int val = 0;

        if (!slave_mode) {
                val |= PCM_INTF_CON1_MASTER_MODE |
                       PCM_INTF_CON1_BYPASS_ASRC;

                if (lrck_inv)
                        val |= PCM_INTF_CON1_SYNC_OUT_INV;
                if (bck_inv)
                        val |= PCM_INTF_CON1_BCLK_OUT_INV;
        } else {
                val |= PCM_INTF_CON1_SLAVE_MODE;

                if (lrck_inv)
                        val |= PCM_INTF_CON1_SYNC_IN_INV;
                if (bck_inv)
                        val |= PCM_INTF_CON1_BCLK_IN_INV;

                /* TODO: add asrc setting */
        }

        val |= FIELD_PREP(PCM_INTF_CON1_FORMAT_MASK, fmt);

        if (fmt == MT8365_PCM_FORMAT_PCMA ||
            fmt == MT8365_PCM_FORMAT_PCMB)
                val |= PCM_INTF_CON1_SYNC_LEN(1);
        else
                val |= PCM_INTF_CON1_SYNC_LEN(bit_width);

        switch (substream->runtime->rate) {
        case 48000:
                val |= PCM_INTF_CON1_FS_48K;
                break;
        case 32000:
                val |= PCM_INTF_CON1_FS_32K;
                break;
        case 16000:
                val |= PCM_INTF_CON1_FS_16K;
                break;
        case 8000:
                val |= PCM_INTF_CON1_FS_8K;
                break;
        default:
                return -EINVAL;
        }

        if (bit_width > 16)
                val |= PCM_INTF_CON1_24BIT | PCM_INTF_CON1_64BCK;
        else
                val |= PCM_INTF_CON1_16BIT | PCM_INTF_CON1_32BCK;

        val |= PCM_INTF_CON1_EXT_MODEM;

        regmap_update_bits(afe->regmap, PCM_INTF_CON1,
                           PCM_INTF_CON1_CONFIG_MASK, val);

        return 0;
}

static int mt8365_dai_pcm1_startup(struct snd_pcm_substream *substream,
                                   struct snd_soc_dai *dai)
{
        struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);

        if (snd_soc_dai_active(dai))
                return 0;

        mt8365_afe_enable_main_clk(afe);

        return 0;
}

static void mt8365_dai_pcm1_shutdown(struct snd_pcm_substream *substream,
                                     struct snd_soc_dai *dai)
{
        struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);

        if (snd_soc_dai_active(dai))
                return;

        mt8365_dai_disable_pcm1(afe);
        mt8365_afe_disable_main_clk(afe);
}

static int mt8365_dai_pcm1_prepare(struct snd_pcm_substream *substream,
                                   struct snd_soc_dai *dai)
{
        struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
        int ret;

        if ((snd_soc_dai_stream_active(dai, SNDRV_PCM_STREAM_PLAYBACK) +
            snd_soc_dai_stream_active(dai, SNDRV_PCM_STREAM_CAPTURE)) > 1) {
                dev_info(afe->dev, "%s '%s' active(%u-%u) already\n",
                         __func__, snd_pcm_stream_str(substream),
                         snd_soc_dai_stream_active(dai, SNDRV_PCM_STREAM_PLAYBACK),
                         snd_soc_dai_stream_active(dai, SNDRV_PCM_STREAM_CAPTURE));
                return 0;
        }

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

        mt8365_dai_enable_pcm1(afe);

        return 0;
}

static int mt8365_dai_pcm1_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
        struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
        struct mt8365_afe_private *afe_priv = afe->platform_priv;
        struct mt8365_pcm_intf_data *pcm_priv = afe_priv->dai_priv[MT8365_AFE_IO_PCM1];

        switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
        case SND_SOC_DAIFMT_I2S:
                pcm_priv->format = MT8365_PCM_FORMAT_I2S;
                break;
        default:
                return -EINVAL;
        }

        switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
        case SND_SOC_DAIFMT_NB_NF:
                pcm_priv->bck_inv = false;
                pcm_priv->lrck_inv = false;
                break;
        case SND_SOC_DAIFMT_NB_IF:
                pcm_priv->bck_inv = false;
                pcm_priv->lrck_inv = true;
                break;
        case SND_SOC_DAIFMT_IB_NF:
                pcm_priv->bck_inv = true;
                pcm_priv->lrck_inv = false;
                break;
        case SND_SOC_DAIFMT_IB_IF:
                pcm_priv->bck_inv = true;
                pcm_priv->lrck_inv = true;
                break;
        default:
                return -EINVAL;
        }

        switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
        case SND_SOC_DAIFMT_CBP_CFP:
                pcm_priv->slave_mode = true;
                break;
        case SND_SOC_DAIFMT_CBC_CFC:
                pcm_priv->slave_mode = false;
                break;
        default:
                return -EINVAL;
        }

        return 0;
}

static const struct snd_soc_dai_ops mt8365_dai_pcm1_ops = {
        .startup        = mt8365_dai_pcm1_startup,
        .shutdown       = mt8365_dai_pcm1_shutdown,
        .prepare        = mt8365_dai_pcm1_prepare,
        .set_fmt        = mt8365_dai_pcm1_set_fmt,
};

static struct snd_soc_dai_driver mtk_dai_pcm_driver[] = {
        {
                .name = "PCM1",
                .id = MT8365_AFE_IO_PCM1,
                .playback = {
                        .stream_name = "PCM1 Playback",
                        .channels_min = 1,
                        .channels_max = 2,
                        .rates = SNDRV_PCM_RATE_8000 |
                                 SNDRV_PCM_RATE_16000 |
                                 SNDRV_PCM_RATE_32000 |
                                 SNDRV_PCM_RATE_48000,
                        .formats = SNDRV_PCM_FMTBIT_S16_LE |
                                   SNDRV_PCM_FMTBIT_S32_LE,
                },
                .capture = {
                        .stream_name = "PCM1 Capture",
                        .channels_min = 1,
                        .channels_max = 2,
                        .rates = SNDRV_PCM_RATE_8000 |
                                 SNDRV_PCM_RATE_16000 |
                                 SNDRV_PCM_RATE_32000 |
                                 SNDRV_PCM_RATE_48000,
                        .formats = SNDRV_PCM_FMTBIT_S16_LE |
                                   SNDRV_PCM_FMTBIT_S32_LE,
                },
                .ops = &mt8365_dai_pcm1_ops,
                .symmetric_rate = 1,
                .symmetric_sample_bits = 1,
        }
};

/* DAI widget */

static const struct snd_soc_dapm_widget mtk_dai_pcm_widgets[] = {
        SND_SOC_DAPM_OUTPUT("PCM1 Out"),
        SND_SOC_DAPM_INPUT("PCM1 In"),
};

/* DAI route */

static const struct snd_soc_dapm_route mtk_dai_pcm_routes[] = {
        {"PCM1 Playback", NULL, "O07"},
        {"PCM1 Playback", NULL, "O08"},
        {"PCM1 Out", NULL, "PCM1 Playback"},

        {"I09", NULL, "PCM1 Capture"},
        {"I22", NULL, "PCM1 Capture"},
        {"PCM1 Capture", NULL, "PCM1 In"},
};

static int init_pcmif_priv_data(struct mtk_base_afe *afe)
{
        struct mt8365_afe_private *afe_priv = afe->platform_priv;
        struct mt8365_pcm_intf_data *pcmif_priv;

        pcmif_priv = devm_kzalloc(afe->dev, sizeof(struct mt8365_pcm_intf_data),
                                  GFP_KERNEL);
        if (!pcmif_priv)
                return -ENOMEM;

        afe_priv->dai_priv[MT8365_AFE_IO_PCM1] = pcmif_priv;
        return 0;
}

int mt8365_dai_pcm_register(struct mtk_base_afe *afe)
{
        struct mtk_base_afe_dai *dai;

        dai = devm_kzalloc(afe->dev, sizeof(*dai), GFP_KERNEL);
        if (!dai)
                return -ENOMEM;

        list_add(&dai->list, &afe->sub_dais);
        dai->dai_drivers = mtk_dai_pcm_driver;
        dai->num_dai_drivers = ARRAY_SIZE(mtk_dai_pcm_driver);
        dai->dapm_widgets = mtk_dai_pcm_widgets;
        dai->num_dapm_widgets = ARRAY_SIZE(mtk_dai_pcm_widgets);
        dai->dapm_routes = mtk_dai_pcm_routes;
        dai->num_dapm_routes = ARRAY_SIZE(mtk_dai_pcm_routes);
        return init_pcmif_priv_data(afe);
}