// SPDX-License-Identifier: GPL-2.0 /* * MediaTek 8365 ALSA SoC Audio DAI ADDA Control * * Copyright (c) 2024 MediaTek Inc. * Authors: Jia Zeng * Alexandre Mergnat */ #include #include #include #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; }