312 lines
8.8 KiB
C
312 lines
8.8 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
/*
|
||
|
* MediaTek 8365 ALSA SoC Audio DAI ADDA 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"
|
||
|
#include "../common/mtk-dai-adda-common.h"
|
||
|
|
||
|
static int adda_afe_on_ref_cnt;
|
||
|
|
||
|
/* DAI Drivers */
|
||
|
|
||
|
static int mt8365_dai_set_adda_out(struct mtk_base_afe *afe, unsigned int rate)
|
||
|
{
|
||
|
unsigned int val;
|
||
|
|
||
|
if (rate == 8000 || rate == 16000)
|
||
|
val = AFE_ADDA_DL_VOICE_DATA;
|
||
|
else
|
||
|
val = 0;
|
||
|
|
||
|
val |= FIELD_PREP(AFE_ADDA_DL_SAMPLING_RATE,
|
||
|
mtk_adda_dl_rate_transform(afe, rate));
|
||
|
val |= AFE_ADDA_DL_8X_UPSAMPLE |
|
||
|
AFE_ADDA_DL_MUTE_OFF_CH1 |
|
||
|
AFE_ADDA_DL_MUTE_OFF_CH2 |
|
||
|
AFE_ADDA_DL_DEGRADE_GAIN;
|
||
|
|
||
|
regmap_update_bits(afe->regmap, AFE_ADDA_PREDIS_CON0, 0xffffffff, 0);
|
||
|
regmap_update_bits(afe->regmap, AFE_ADDA_PREDIS_CON1, 0xffffffff, 0);
|
||
|
regmap_update_bits(afe->regmap, AFE_ADDA_DL_SRC2_CON0, 0xffffffff, val);
|
||
|
/* SA suggest apply -0.3db to audio/speech path */
|
||
|
regmap_update_bits(afe->regmap, AFE_ADDA_DL_SRC2_CON1,
|
||
|
0xffffffff, 0xf74f0000);
|
||
|
/* SA suggest use default value for sdm */
|
||
|
regmap_update_bits(afe->regmap, AFE_ADDA_DL_SDM_DCCOMP_CON,
|
||
|
0xffffffff, 0x0700701e);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int mt8365_dai_set_adda_in(struct mtk_base_afe *afe, unsigned int rate)
|
||
|
{
|
||
|
unsigned int val;
|
||
|
|
||
|
val = FIELD_PREP(AFE_ADDA_UL_SAMPLING_RATE,
|
||
|
mtk_adda_ul_rate_transform(afe, rate));
|
||
|
regmap_update_bits(afe->regmap, AFE_ADDA_UL_SRC_CON0,
|
||
|
AFE_ADDA_UL_SAMPLING_RATE, val);
|
||
|
/* Using Internal ADC */
|
||
|
regmap_update_bits(afe->regmap, AFE_ADDA_TOP_CON0, 0x1, 0x0);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int mt8365_dai_enable_adda_on(struct mtk_base_afe *afe)
|
||
|
{
|
||
|
unsigned long flags;
|
||
|
struct mt8365_afe_private *afe_priv = afe->platform_priv;
|
||
|
|
||
|
spin_lock_irqsave(&afe_priv->afe_ctrl_lock, flags);
|
||
|
|
||
|
adda_afe_on_ref_cnt++;
|
||
|
if (adda_afe_on_ref_cnt == 1)
|
||
|
regmap_update_bits(afe->regmap, AFE_ADDA_UL_DL_CON0,
|
||
|
AFE_ADDA_UL_DL_ADDA_AFE_ON,
|
||
|
AFE_ADDA_UL_DL_ADDA_AFE_ON);
|
||
|
|
||
|
spin_unlock_irqrestore(&afe_priv->afe_ctrl_lock, flags);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int mt8365_dai_disable_adda_on(struct mtk_base_afe *afe)
|
||
|
{
|
||
|
unsigned long flags;
|
||
|
struct mt8365_afe_private *afe_priv = afe->platform_priv;
|
||
|
|
||
|
spin_lock_irqsave(&afe_priv->afe_ctrl_lock, flags);
|
||
|
|
||
|
adda_afe_on_ref_cnt--;
|
||
|
if (adda_afe_on_ref_cnt == 0)
|
||
|
regmap_update_bits(afe->regmap, AFE_ADDA_UL_DL_CON0,
|
||
|
AFE_ADDA_UL_DL_ADDA_AFE_ON,
|
||
|
~AFE_ADDA_UL_DL_ADDA_AFE_ON);
|
||
|
else if (adda_afe_on_ref_cnt < 0) {
|
||
|
adda_afe_on_ref_cnt = 0;
|
||
|
dev_warn(afe->dev, "Abnormal adda_on ref count. Force it to 0\n");
|
||
|
}
|
||
|
|
||
|
spin_unlock_irqrestore(&afe_priv->afe_ctrl_lock, flags);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void mt8365_dai_set_adda_out_enable(struct mtk_base_afe *afe,
|
||
|
bool enable)
|
||
|
{
|
||
|
regmap_update_bits(afe->regmap, AFE_ADDA_DL_SRC2_CON0, 0x1, enable);
|
||
|
|
||
|
if (enable)
|
||
|
mt8365_dai_enable_adda_on(afe);
|
||
|
else
|
||
|
mt8365_dai_disable_adda_on(afe);
|
||
|
}
|
||
|
|
||
|
static void mt8365_dai_set_adda_in_enable(struct mtk_base_afe *afe, bool enable)
|
||
|
{
|
||
|
if (enable) {
|
||
|
regmap_update_bits(afe->regmap, AFE_ADDA_UL_SRC_CON0, 0x1, 0x1);
|
||
|
mt8365_dai_enable_adda_on(afe);
|
||
|
/* enable aud_pad_top fifo */
|
||
|
regmap_update_bits(afe->regmap, AFE_AUD_PAD_TOP,
|
||
|
0xffffffff, 0x31);
|
||
|
} else {
|
||
|
/* disable aud_pad_top fifo */
|
||
|
regmap_update_bits(afe->regmap, AFE_AUD_PAD_TOP,
|
||
|
0xffffffff, 0x30);
|
||
|
regmap_update_bits(afe->regmap, AFE_ADDA_UL_SRC_CON0, 0x1, 0x0);
|
||
|
/* de suggest disable ADDA_UL_SRC at least wait 125us */
|
||
|
usleep_range(150, 300);
|
||
|
mt8365_dai_disable_adda_on(afe);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int mt8365_dai_int_adda_startup(struct snd_pcm_substream *substream,
|
||
|
struct snd_soc_dai *dai)
|
||
|
{
|
||
|
struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
|
||
|
unsigned int stream = substream->stream;
|
||
|
|
||
|
mt8365_afe_enable_main_clk(afe);
|
||
|
|
||
|
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||
|
mt8365_afe_enable_top_cg(afe, MT8365_TOP_CG_DAC);
|
||
|
mt8365_afe_enable_top_cg(afe, MT8365_TOP_CG_DAC_PREDIS);
|
||
|
} else if (stream == SNDRV_PCM_STREAM_CAPTURE) {
|
||
|
mt8365_afe_enable_top_cg(afe, MT8365_TOP_CG_ADC);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void mt8365_dai_int_adda_shutdown(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_be_dai_data *be =
|
||
|
&afe_priv->be_data[dai->id - MT8365_AFE_BACKEND_BASE];
|
||
|
unsigned int stream = substream->stream;
|
||
|
|
||
|
if (be->prepared[stream]) {
|
||
|
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||
|
mt8365_dai_set_adda_out_enable(afe, false);
|
||
|
mt8365_afe_set_i2s_out_enable(afe, false);
|
||
|
} else {
|
||
|
mt8365_dai_set_adda_in_enable(afe, false);
|
||
|
}
|
||
|
be->prepared[stream] = false;
|
||
|
}
|
||
|
|
||
|
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||
|
mt8365_afe_disable_top_cg(afe, MT8365_TOP_CG_DAC_PREDIS);
|
||
|
mt8365_afe_disable_top_cg(afe, MT8365_TOP_CG_DAC);
|
||
|
} else if (stream == SNDRV_PCM_STREAM_CAPTURE) {
|
||
|
mt8365_afe_disable_top_cg(afe, MT8365_TOP_CG_ADC);
|
||
|
}
|
||
|
|
||
|
mt8365_afe_disable_main_clk(afe);
|
||
|
}
|
||
|
|
||
|
static int mt8365_dai_int_adda_prepare(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_be_dai_data *be =
|
||
|
&afe_priv->be_data[dai->id - MT8365_AFE_BACKEND_BASE];
|
||
|
unsigned int rate = substream->runtime->rate;
|
||
|
int bit_width = snd_pcm_format_width(substream->runtime->format);
|
||
|
int ret;
|
||
|
|
||
|
dev_info(afe->dev, "%s '%s' rate = %u\n", __func__,
|
||
|
snd_pcm_stream_str(substream), rate);
|
||
|
|
||
|
if (be->prepared[substream->stream]) {
|
||
|
dev_info(afe->dev, "%s '%s' prepared already\n",
|
||
|
__func__, snd_pcm_stream_str(substream));
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||
|
ret = mt8365_dai_set_adda_out(afe, rate);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
ret = mt8365_afe_set_i2s_out(afe, rate, bit_width);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
mt8365_dai_set_adda_out_enable(afe, true);
|
||
|
mt8365_afe_set_i2s_out_enable(afe, true);
|
||
|
} else {
|
||
|
ret = mt8365_dai_set_adda_in(afe, rate);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
mt8365_dai_set_adda_in_enable(afe, true);
|
||
|
}
|
||
|
be->prepared[substream->stream] = true;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct snd_soc_dai_ops mt8365_afe_int_adda_ops = {
|
||
|
.startup = mt8365_dai_int_adda_startup,
|
||
|
.shutdown = mt8365_dai_int_adda_shutdown,
|
||
|
.prepare = mt8365_dai_int_adda_prepare,
|
||
|
};
|
||
|
|
||
|
static struct snd_soc_dai_driver mtk_dai_adda_driver[] = {
|
||
|
{
|
||
|
.name = "INT ADDA",
|
||
|
.id = MT8365_AFE_IO_INT_ADDA,
|
||
|
.playback = {
|
||
|
.stream_name = "INT ADDA Playback",
|
||
|
.channels_min = 1,
|
||
|
.channels_max = 2,
|
||
|
.rates = SNDRV_PCM_RATE_8000_48000,
|
||
|
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||
|
},
|
||
|
.capture = {
|
||
|
.stream_name = "INT ADDA Capture",
|
||
|
.channels_min = 1,
|
||
|
.channels_max = 2,
|
||
|
.rates = 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_afe_int_adda_ops,
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/* DAI Controls */
|
||
|
|
||
|
static const struct snd_kcontrol_new mtk_adda_dl_ch1_mix[] = {
|
||
|
SOC_DAPM_SINGLE_AUTODISABLE("GAIN1_OUT_CH1 Switch", AFE_CONN3,
|
||
|
10, 1, 0),
|
||
|
};
|
||
|
|
||
|
static const struct snd_kcontrol_new mtk_adda_dl_ch2_mix[] = {
|
||
|
SOC_DAPM_SINGLE_AUTODISABLE("GAIN1_OUT_CH2 Switch", AFE_CONN4,
|
||
|
11, 1, 0),
|
||
|
};
|
||
|
|
||
|
static const struct snd_kcontrol_new int_adda_o03_o04_enable_ctl =
|
||
|
SOC_DAPM_SINGLE_VIRT("Switch", 1);
|
||
|
|
||
|
/* DAI widget */
|
||
|
|
||
|
static const struct snd_soc_dapm_widget mtk_dai_adda_widgets[] = {
|
||
|
SND_SOC_DAPM_SWITCH("INT ADDA O03_O04", SND_SOC_NOPM, 0, 0,
|
||
|
&int_adda_o03_o04_enable_ctl),
|
||
|
/* inter-connections */
|
||
|
SND_SOC_DAPM_MIXER("ADDA_DL_CH1", SND_SOC_NOPM, 0, 0,
|
||
|
mtk_adda_dl_ch1_mix,
|
||
|
ARRAY_SIZE(mtk_adda_dl_ch1_mix)),
|
||
|
SND_SOC_DAPM_MIXER("ADDA_DL_CH2", SND_SOC_NOPM, 0, 0,
|
||
|
mtk_adda_dl_ch2_mix,
|
||
|
ARRAY_SIZE(mtk_adda_dl_ch2_mix)),
|
||
|
};
|
||
|
|
||
|
/* DAI route */
|
||
|
|
||
|
static const struct snd_soc_dapm_route mtk_dai_adda_routes[] = {
|
||
|
{"INT ADDA O03_O04", "Switch", "O03"},
|
||
|
{"INT ADDA O03_O04", "Switch", "O04"},
|
||
|
{"INT ADDA Playback", NULL, "INT ADDA O03_O04"},
|
||
|
{"INT ADDA Playback", NULL, "ADDA_DL_CH1"},
|
||
|
{"INT ADDA Playback", NULL, "ADDA_DL_CH2"},
|
||
|
{"AIN Mux", "INT ADC", "INT ADDA Capture"},
|
||
|
{"ADDA_DL_CH1", "GAIN1_OUT_CH1", "Hostless FM DL"},
|
||
|
{"ADDA_DL_CH2", "GAIN1_OUT_CH2", "Hostless FM DL"},
|
||
|
};
|
||
|
|
||
|
int mt8365_dai_adda_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_adda_driver;
|
||
|
dai->num_dai_drivers = ARRAY_SIZE(mtk_dai_adda_driver);
|
||
|
dai->dapm_widgets = mtk_dai_adda_widgets;
|
||
|
dai->num_dapm_widgets = ARRAY_SIZE(mtk_dai_adda_widgets);
|
||
|
dai->dapm_routes = mtk_dai_adda_routes;
|
||
|
dai->num_dapm_routes = ARRAY_SIZE(mtk_dai_adda_routes);
|
||
|
return 0;
|
||
|
}
|