* [PATCH v9 00/10] ASoC: mediatek: Add support for MT8196 SoC
From: Cyril Chao @ 2026-04-30 2:23 UTC (permalink / raw)
To: Liam Girdwood, Mark Brown, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Matthias Brugger, AngeloGioacchino Del Regno,
Jaroslav Kysela, Takashi Iwai
Cc: linux-sound, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek, Cyril Chao, Project_Global_Chrome_Upstream_Group,
Cyril Chao
This series of patches adds support for Mediatek AFE of MT8196 SoC.
Patches are based on broonie tree "for-next" branch.
This v9 series is based on the v8 version:
Link: https://patchwork.kernel.org/project/alsa-devel/patch/20260324015719.17543-2-Cyril.Chao@mediatek.com/
Changes since v8:
- Avoid divide-by-zero when computing CM update_val for mono.
Changes since v7:
- Fix sparse warning in mt8196-nau8825 machine driver
(reported by kernel test robot <lkp@intel.com>)
- Reorder DT bindings patches before the driver patches so that the
bindings land first and match the dependencies in the ASoC drivers
- Replace the deprecated pcm_construct/pcm_destruct callbacks with
pcm_new/pcm_free to follow the latest ASoC core API
Changes since v6:
- optimize mtk_afe_pcm_pointer interface and improved logic checks.
- update mt8196_afe_private structure by removing unused member variables.
- only reference clocks directly supplied to afe and set required frequencies directly.
- update adda driver according to reviewer suggestions.
- update i2s driver according to reviewer suggestions.
- update tdm driver according to reviewer suggestions.
- optimize platform driver and update widget rotues.
- move the sof route from platform driver to the machine driver, and moved pinctrl to platform driver.
- update the afe yaml file to remove unnecessary clock references.
Changes since v5:
- restore the commit message for mediatek,mt8196-afe.yaml and only remove the string document.
- add reviewed owner for mediatek,mt8196-nau8825.yaml.
- use SND_JACK_AVOUT as jack status.
- use GENMASK_ULL to support 64-bit address masks.
- modify the afe platform and i2s dai driver code based on reviewer's suggestions.
Changes since v4:
- modify the mediatek,mt8196-afe.yaml commit message and add reviewed owner.
- modify the mediatek,mt8196-nau8825.yaml commit message.
- modify the audio common code based on reviewer's suggestions.
- add reviewed and tested owners in the audio common code submission message.
- fix cm update cnt calculation issue.
Changes since v3:
- the AFE TOP CG index is added to the common header.
- remove the audsys clk register and directly read and write to the regmap of afe cg clk.
- modify the clk logic according to the suggestions.
- remove the macro definition of MTKAIF4
- remove the tdm cg event and directly read and write the tdm cg reg form the widget.
- remove the i2s and cm cg event and directly read and write cg reg.
- afe hopping and f26m clk cg are placed in remap_register_patch and enable.
- the yaml file is modified according to the suggestions.
- replace SND_SOC_DAIFMT_CBS_CFS with SND_SOC_DAIFMT_CBC_CFC.
Changes since v2:
- remove the mtk_memif_set_channel interface modify.
- remove duplicate definitions from the header file.
- move the afe gate clk to the audio driver for management and registration
and manage the afe clk gate in each dai driver.
- delete the useless clk source.
- the i2s driver adds i2s clk gate management, removes the additional dts
configuration of i2s4.
- the afe and i2s dai driver,memif and irq data structs are encapsulated using
macros to reduce the amount of code.
- the volatile reg is modified as suggested.
- mt6681 codec is not supported, the mt6681 keyword is removed.
- the name of the machine driver is changed from mt8196-mt6681.c to mt8196-nau8825.c
- remove the i2s4 configuration from mt8196-afe.yaml and make the modifications as suggested.
- change the mt8196-mt6681.yaml to mt8196-nau8825.yaml and make the modifications as suggested.
Changes since v1:
- modify mtk_memif_set_channel and mtk_afe_pcm_pointer interfaces
are improved to support mt8196.
- remove duplicate definitions in the mt8196 common header file.
- cm logic is merge into the afe platform driver.
- modify afe clk to return judgment logic and remove useless clk sources.
- refactor the mt8196 adda dai driver.
- remove the gpio module and use SND_SOC_DAPM_PINCTRL to manage it.
- removes CONNSYS_I2S related functions that are not supported in i2s dai driver.
- fixed mt8196-afe.yaml and mt8196-mt6681.yaml syntax issues.
- modify log printing in all modules.
- optimize the header file included for machine driver.
Darren Ye (10):
ASoC: mediatek: common: modify mtk afe platform driver for mt8196
ASoC: mediatek: mt8196: add common header
ASoC: mediatek: mt8196: support audio clock control
ASoC: mediatek: mt8196: support ADDA in platform driver
ASoC: mediatek: mt8196: support I2S in platform driver
ASoC: mediatek: mt8196: support TDM in platform driver
ASoC: dt-bindings: mediatek,mt8196-afe: add audio AFE
ASoC: mediatek: mt8196: add platform driver
ASoC: dt-bindings: mediatek,mt8196-nau8825: Add audio sound card
ASoC: mediatek: mt8196: add machine driver with nau8825
.../bindings/sound/mediatek,mt8196-afe.yaml | 113 +
.../sound/mediatek,mt8196-nau8825.yaml | 100 +
sound/soc/mediatek/Kconfig | 30 +
sound/soc/mediatek/Makefile | 1 +
.../mediatek/common/mtk-afe-platform-driver.c | 56 +-
.../mediatek/common/mtk-afe-platform-driver.h | 2 +
sound/soc/mediatek/mt8196/Makefile | 14 +
sound/soc/mediatek/mt8196/mt8196-afe-clk.c | 581 +
sound/soc/mediatek/mt8196/mt8196-afe-clk.h | 64 +
sound/soc/mediatek/mt8196/mt8196-afe-common.h | 205 +
sound/soc/mediatek/mt8196/mt8196-afe-pcm.c | 2503 ++++
sound/soc/mediatek/mt8196/mt8196-dai-adda.c | 845 ++
sound/soc/mediatek/mt8196/mt8196-dai-i2s.c | 2613 ++++
sound/soc/mediatek/mt8196/mt8196-dai-tdm.c | 675 +
.../mediatek/mt8196/mt8196-interconnection.h | 121 +
sound/soc/mediatek/mt8196/mt8196-nau8825.c | 870 ++
sound/soc/mediatek/mt8196/mt8196-reg.h | 12068 ++++++++++++++++
17 files changed, 20843 insertions(+), 18 deletions(-)
create mode 100644 Documentation/devicetree/bindings/sound/mediatek,mt8196-afe.yaml
create mode 100644 Documentation/devicetree/bindings/sound/mediatek,mt8196-nau8825.yaml
create mode 100644 sound/soc/mediatek/mt8196/Makefile
create mode 100644 sound/soc/mediatek/mt8196/mt8196-afe-clk.c
create mode 100644 sound/soc/mediatek/mt8196/mt8196-afe-clk.h
create mode 100644 sound/soc/mediatek/mt8196/mt8196-afe-common.h
create mode 100644 sound/soc/mediatek/mt8196/mt8196-afe-pcm.c
create mode 100644 sound/soc/mediatek/mt8196/mt8196-dai-adda.c
create mode 100644 sound/soc/mediatek/mt8196/mt8196-dai-i2s.c
create mode 100644 sound/soc/mediatek/mt8196/mt8196-dai-tdm.c
create mode 100644 sound/soc/mediatek/mt8196/mt8196-interconnection.h
create mode 100644 sound/soc/mediatek/mt8196/mt8196-nau8825.c
create mode 100644 sound/soc/mediatek/mt8196/mt8196-reg.h
--
2.45.2
^ permalink raw reply
* [PATCH v9 01/10] ASoC: mediatek: common: modify mtk afe platform driver for mt8196
From: Cyril Chao @ 2026-04-30 2:23 UTC (permalink / raw)
To: Liam Girdwood, Mark Brown, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Matthias Brugger, AngeloGioacchino Del Regno,
Jaroslav Kysela, Takashi Iwai
Cc: linux-sound, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek, Cyril Chao, Project_Global_Chrome_Upstream_Group,
Darren Ye, Louis-Alexis Eyraud, Cyril Chao
In-Reply-To: <20260430022417.32282-1-Cyril.Chao@mediatek.com>
From: Darren Ye <darren.ye@mediatek.com>
Mofify the pcm pointer interface to support 64-bit address access.
Signed-off-by: Darren Ye <darren.ye@mediatek.com>
Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Tested-by: Louis-Alexis Eyraud <louisalexis.eyraud@collabora.com>
Signed-off-by: Cyril Chao <Cyril.Chao@mediatek.com>
---
.../mediatek/common/mtk-afe-platform-driver.c | 56 +++++++++++++------
.../mediatek/common/mtk-afe-platform-driver.h | 2 +
2 files changed, 40 insertions(+), 18 deletions(-)
diff --git a/sound/soc/mediatek/common/mtk-afe-platform-driver.c b/sound/soc/mediatek/common/mtk-afe-platform-driver.c
index f2b39fc9ec81..477284e12396 100644
--- a/sound/soc/mediatek/common/mtk-afe-platform-driver.c
+++ b/sound/soc/mediatek/common/mtk-afe-platform-driver.c
@@ -87,29 +87,49 @@ snd_pcm_uframes_t mtk_afe_pcm_pointer(struct snd_soc_component *component,
const struct mtk_base_memif_data *memif_data = memif->data;
struct regmap *regmap = afe->regmap;
struct device *dev = afe->dev;
- int reg_ofs_base = memif_data->reg_ofs_base;
- int reg_ofs_cur = memif_data->reg_ofs_cur;
- unsigned int hw_ptr = 0, hw_base = 0;
- int ret, pcm_ptr_bytes;
-
- ret = regmap_read(regmap, reg_ofs_cur, &hw_ptr);
- if (ret || hw_ptr == 0) {
- dev_err(dev, "%s hw_ptr err\n", __func__);
- pcm_ptr_bytes = 0;
- goto POINTER_RETURN_FRAMES;
+ unsigned int hw_ptr_lower32 = 0, hw_ptr_upper32 = 0;
+ unsigned int hw_base_lower32 = 0, hw_base_upper32 = 0;
+ unsigned long long hw_ptr = 0, hw_base = 0;
+ int ret;
+ unsigned long long pcm_ptr_bytes = 0;
+
+ ret = regmap_read(regmap, memif_data->reg_ofs_cur, &hw_ptr_lower32);
+ if (ret) {
+ dev_err(dev, "%s hw_ptr_lower32 err\n", __func__);
+ return 0;
}
- ret = regmap_read(regmap, reg_ofs_base, &hw_base);
- if (ret || hw_base == 0) {
- dev_err(dev, "%s hw_ptr err\n", __func__);
- pcm_ptr_bytes = 0;
- goto POINTER_RETURN_FRAMES;
+ if (memif_data->reg_ofs_cur_msb) {
+ ret = regmap_read(regmap, memif_data->reg_ofs_cur_msb, &hw_ptr_upper32);
+ if (ret) {
+ dev_err(dev, "%s hw_ptr_upper32 err\n", __func__);
+ return 0;
+ }
}
- pcm_ptr_bytes = hw_ptr - hw_base;
+ ret = regmap_read(regmap, memif_data->reg_ofs_base, &hw_base_lower32);
+ if (ret) {
+ dev_err(dev, "%s hw_base_lower32 err\n", __func__);
+ return 0;
+ }
+ if (memif_data->reg_ofs_base_msb) {
+ ret = regmap_read(regmap, memif_data->reg_ofs_base_msb, &hw_base_upper32);
+ if (ret) {
+ dev_err(dev, "%s hw_base_upper32 err\n", __func__);
+ return 0;
+ }
+ }
+
+ hw_ptr = ((unsigned long long)hw_ptr_upper32 << 32) | hw_ptr_lower32;
+ hw_base = ((unsigned long long)hw_base_upper32 << 32) | hw_base_lower32;
+
+ if (!hw_ptr || !hw_base) {
+ dev_err(dev, "hw_ptr or hw_base = 0 err\n");
+ return 0;
+ }
-POINTER_RETURN_FRAMES:
- return bytes_to_frames(substream->runtime, pcm_ptr_bytes);
+ pcm_ptr_bytes = MTK_ALIGN_16BYTES(hw_ptr - hw_base);
+ return bytes_to_frames(substream->runtime, (ssize_t)pcm_ptr_bytes);
}
EXPORT_SYMBOL_GPL(mtk_afe_pcm_pointer);
diff --git a/sound/soc/mediatek/common/mtk-afe-platform-driver.h b/sound/soc/mediatek/common/mtk-afe-platform-driver.h
index fcc923b88f12..71070b26f8f8 100644
--- a/sound/soc/mediatek/common/mtk-afe-platform-driver.h
+++ b/sound/soc/mediatek/common/mtk-afe-platform-driver.h
@@ -12,6 +12,8 @@
#define AFE_PCM_NAME "mtk-afe-pcm"
extern const struct snd_soc_component_driver mtk_afe_pcm_platform;
+#define MTK_ALIGN_16BYTES(x) ((x) & GENMASK_ULL(39, 4))
+
struct mtk_base_afe;
struct snd_pcm;
struct snd_soc_component;
--
2.45.2
^ permalink raw reply related
* [PATCH v9 03/10] ASoC: mediatek: mt8196: support audio clock control
From: Cyril Chao @ 2026-04-30 2:23 UTC (permalink / raw)
To: Liam Girdwood, Mark Brown, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Matthias Brugger, AngeloGioacchino Del Regno,
Jaroslav Kysela, Takashi Iwai
Cc: linux-sound, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek, Cyril Chao, Project_Global_Chrome_Upstream_Group,
Darren Ye, Cyril Chao
In-Reply-To: <20260430022417.32282-1-Cyril.Chao@mediatek.com>
From: Darren Ye <darren.ye@mediatek.com>
Add audio clock wrapper and audio tuner control.
Signed-off-by: Darren Ye <darren.ye@mediatek.com>
Signed-off-by: Cyril Chao <Cyril.Chao@mediatek.com>
---
sound/soc/mediatek/mt8196/mt8196-afe-clk.c | 581 +++++++++++++++++++++
sound/soc/mediatek/mt8196/mt8196-afe-clk.h | 64 +++
2 files changed, 645 insertions(+)
create mode 100644 sound/soc/mediatek/mt8196/mt8196-afe-clk.c
create mode 100644 sound/soc/mediatek/mt8196/mt8196-afe-clk.h
diff --git a/sound/soc/mediatek/mt8196/mt8196-afe-clk.c b/sound/soc/mediatek/mt8196/mt8196-afe-clk.c
new file mode 100644
index 000000000000..286e39f53ae0
--- /dev/null
+++ b/sound/soc/mediatek/mt8196/mt8196-afe-clk.c
@@ -0,0 +1,581 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * mt8196-afe-clk.c -- Mediatek 8196 afe clock ctrl
+ *
+ * Copyright (c) 2025 MediaTek Inc.
+ * Author: Darren Ye <darren.ye@mediatek.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+
+#include "mt8196-afe-clk.h"
+#include "mt8196-afe-common.h"
+
+static const char *aud_clks[MT8196_CLK_NUM] = {
+ /* vlp clk */
+ [MT8196_CLK_VLP_MUX_AUDIOINTBUS] = "top_aud_intbus",
+ [MT8196_CLK_VLP_MUX_AUD_ENG1] = "top_aud_eng1",
+ [MT8196_CLK_VLP_MUX_AUD_ENG2] = "top_aud_eng2",
+ [MT8196_CLK_VLP_MUX_AUDIO_H] = "top_aud_h",
+ /* pll */
+ [MT8196_CLK_TOP_APLL1_CK] = "apll1",
+ [MT8196_CLK_TOP_APLL2_CK] = "apll2",
+ /* divider */
+ [MT8196_CLK_TOP_APLL12_DIV_I2SIN0] = "apll12_div_i2sin0",
+ [MT8196_CLK_TOP_APLL12_DIV_I2SIN1] = "apll12_div_i2sin1",
+ [MT8196_CLK_TOP_APLL12_DIV_FMI2S] = "apll12_div_fmi2s",
+ [MT8196_CLK_TOP_APLL12_DIV_TDMOUT_M] = "apll12_div_tdmout_m",
+ [MT8196_CLK_TOP_APLL12_DIV_TDMOUT_B] = "apll12_div_tdmout_b",
+ /* mux */
+ [MT8196_CLK_TOP_ADSP_SEL] = "top_adsp",
+};
+
+int mt8196_afe_enable_clk(struct mtk_base_afe *afe, struct clk *clk)
+{
+ int ret;
+
+ ret = clk_prepare_enable(clk);
+ if (ret) {
+ dev_err(afe->dev, "failed to enable clk\n");
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mt8196_afe_enable_clk);
+
+void mt8196_afe_disable_clk(struct mtk_base_afe *afe, struct clk *clk)
+{
+ if (clk)
+ clk_disable_unprepare(clk);
+ else
+ dev_err(afe->dev, "NULL clk\n");
+}
+EXPORT_SYMBOL_GPL(mt8196_afe_disable_clk);
+
+static int mt8196_afe_set_clk_rate(struct mtk_base_afe *afe, struct clk *clk,
+ unsigned int rate)
+{
+ int ret;
+
+ if (clk) {
+ ret = clk_set_rate(clk, rate);
+ if (ret) {
+ dev_err(afe->dev, "failed to set clk rate\n");
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static unsigned int get_top_cg_reg(unsigned int cg_type)
+{
+ switch (cg_type) {
+ case MT8196_AUDIO_26M_EN_ON:
+ case MT8196_AUDIO_F3P25M_EN_ON:
+ case MT8196_AUDIO_APLL1_EN_ON:
+ case MT8196_AUDIO_APLL2_EN_ON:
+ return AUDIO_ENGEN_CON0;
+ case MT8196_CG_AUDIO_HOPPING_CK:
+ case MT8196_CG_AUDIO_F26M_CK:
+ case MT8196_CG_APLL1_CK:
+ case MT8196_CG_APLL2_CK:
+ case MT8196_PDN_APLL_TUNER2:
+ case MT8196_PDN_APLL_TUNER1:
+ return AUDIO_TOP_CON4;
+ default:
+ return 0;
+ }
+}
+
+static unsigned int get_top_cg_mask(unsigned int cg_type)
+{
+ switch (cg_type) {
+ case MT8196_AUDIO_26M_EN_ON:
+ return AUDIO_26M_EN_ON_MASK_SFT;
+ case MT8196_AUDIO_F3P25M_EN_ON:
+ return AUDIO_F3P25M_EN_ON_MASK_SFT;
+ case MT8196_AUDIO_APLL1_EN_ON:
+ return AUDIO_APLL1_EN_ON_MASK_SFT;
+ case MT8196_AUDIO_APLL2_EN_ON:
+ return AUDIO_APLL2_EN_ON_MASK_SFT;
+ case MT8196_CG_AUDIO_HOPPING_CK:
+ return CG_AUDIO_HOPPING_CK_MASK_SFT;
+ case MT8196_CG_AUDIO_F26M_CK:
+ return CG_AUDIO_F26M_CK_MASK_SFT;
+ case MT8196_CG_APLL1_CK:
+ return CG_APLL1_CK_MASK_SFT;
+ case MT8196_CG_APLL2_CK:
+ return CG_APLL2_CK_MASK_SFT;
+ case MT8196_PDN_APLL_TUNER2:
+ return PDN_APLL_TUNER2_MASK_SFT;
+ case MT8196_PDN_APLL_TUNER1:
+ return PDN_APLL_TUNER1_MASK_SFT;
+ default:
+ return 0;
+ }
+}
+
+static unsigned int get_top_cg_on_val(unsigned int cg_type)
+{
+ switch (cg_type) {
+ case MT8196_AUDIO_26M_EN_ON:
+ case MT8196_AUDIO_F3P25M_EN_ON:
+ case MT8196_AUDIO_APLL1_EN_ON:
+ case MT8196_AUDIO_APLL2_EN_ON:
+ return get_top_cg_mask(cg_type);
+ case MT8196_CG_AUDIO_HOPPING_CK:
+ case MT8196_CG_AUDIO_F26M_CK:
+ case MT8196_CG_APLL1_CK:
+ case MT8196_CG_APLL2_CK:
+ case MT8196_PDN_APLL_TUNER2:
+ case MT8196_PDN_APLL_TUNER1:
+ return 0;
+ default:
+ return 0;
+ }
+}
+
+static unsigned int get_top_cg_off_val(unsigned int cg_type)
+{
+ switch (cg_type) {
+ case MT8196_AUDIO_26M_EN_ON:
+ case MT8196_AUDIO_F3P25M_EN_ON:
+ case MT8196_AUDIO_APLL1_EN_ON:
+ case MT8196_AUDIO_APLL2_EN_ON:
+ return 0;
+ case MT8196_CG_AUDIO_HOPPING_CK:
+ case MT8196_CG_AUDIO_F26M_CK:
+ case MT8196_CG_APLL1_CK:
+ case MT8196_CG_APLL2_CK:
+ case MT8196_PDN_APLL_TUNER2:
+ case MT8196_PDN_APLL_TUNER1:
+ return get_top_cg_mask(cg_type);
+ default:
+ return get_top_cg_mask(cg_type);
+ }
+}
+
+static int mt8196_afe_enable_top_cg(struct mtk_base_afe *afe, unsigned int cg_type)
+{
+ int ret;
+ unsigned int reg = get_top_cg_reg(cg_type);
+ unsigned int mask = get_top_cg_mask(cg_type);
+ unsigned int val = get_top_cg_on_val(cg_type);
+
+ if (!afe->regmap) {
+ dev_err(afe->dev, "afe regmap is null !!!\n");
+ return 0;
+ }
+
+ dev_dbg(afe->dev, "reg: 0x%x, mask: 0x%x, val: 0x%x\n", reg, mask, val);
+
+ ret = regmap_update_bits(afe->regmap, reg, mask, val);
+ if (ret)
+ dev_err(afe->dev, "regmap_update_bits failed: %d\n", ret);
+
+ return ret;
+}
+
+static int mt8196_afe_disable_top_cg(struct mtk_base_afe *afe, unsigned int cg_type)
+{
+ int ret;
+ unsigned int reg = get_top_cg_reg(cg_type);
+ unsigned int mask = get_top_cg_mask(cg_type);
+ unsigned int val = get_top_cg_off_val(cg_type);
+
+ if (!afe->regmap) {
+ dev_err(afe->dev, "afe regmap is null !!!\n");
+ return 0;
+ }
+
+ dev_dbg(afe->dev, "reg: 0x%x, mask: 0x%x, val: 0x%x\n", reg, mask, val);
+
+ ret = regmap_update_bits(afe->regmap, reg, mask, val);
+ if (ret)
+ dev_err(afe->dev, "regmap_update_bits failed: %d\n", ret);
+
+ return ret;
+}
+
+static int apll1_mux_setting(struct mtk_base_afe *afe, bool enable)
+{
+ struct mt8196_afe_private *afe_priv = afe->platform_priv;
+ int apll_rate;
+ int ret;
+
+ dev_dbg(afe->dev, "enable: %d\n", enable);
+
+ if (enable) {
+ apll_rate = mt8196_get_apll_rate(afe, MT8196_APLL1);
+
+ /* 180.6336 / 4 = 45.1584MHz */
+ ret = mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG1]);
+ if (ret)
+ return ret;
+
+ ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG1],
+ MT8196_AUD_ENG1_CLK);
+ if (ret)
+ return ret;
+
+ ret = mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]);
+ if (ret)
+ return ret;
+
+ ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H],
+ apll_rate);
+ if (ret)
+ return ret;
+ } else {
+ ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG1],
+ MT8196_AFE_26M);
+ if (ret)
+ return ret;
+
+ mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG1]);
+
+ ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H],
+ MT8196_AFE_26M);
+ if (ret)
+ return ret;
+
+ mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]);
+ }
+
+ return 0;
+}
+
+static int apll2_mux_setting(struct mtk_base_afe *afe, bool enable)
+{
+ struct mt8196_afe_private *afe_priv = afe->platform_priv;
+ int apll_rate;
+ int ret;
+
+ dev_dbg(afe->dev, "enable: %d\n", enable);
+
+ if (enable) {
+ apll_rate = mt8196_get_apll_rate(afe, MT8196_APLL2);
+
+ /* 196.608 / 4 = 49.152MHz */
+ ret = mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG2]);
+ if (ret)
+ return ret;
+
+ ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG2],
+ MT8196_AUD_ENG2_CLK);
+ if (ret)
+ return ret;
+
+ ret = mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]);
+ if (ret)
+ return ret;
+
+ ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H],
+ apll_rate);
+ if (ret)
+ return ret;
+ } else {
+ ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG2],
+ MT8196_AFE_26M);
+ if (ret)
+ return ret;
+
+ mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUD_ENG2]);
+
+ ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H],
+ MT8196_AFE_26M);
+ if (ret)
+ return ret;
+
+ mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]);
+ }
+
+ return 0;
+}
+
+int mt8196_apll1_enable(struct mtk_base_afe *afe)
+{
+ int ret;
+
+ /* setting for APLL */
+ apll1_mux_setting(afe, true);
+
+ ret = mt8196_afe_enable_top_cg(afe, MT8196_CG_APLL1_CK);
+ if (ret)
+ goto err_clk_apll1;
+
+ ret = mt8196_afe_enable_top_cg(afe, MT8196_PDN_APLL_TUNER1);
+ if (ret)
+ goto err_clk_apll1_tuner;
+
+ /* sel 44.1kHz:1, apll_div:7, upper bound:3 */
+ regmap_update_bits(afe->regmap, AFE_APLL1_TUNER_CFG,
+ XTAL_EN_128FS_SEL_MASK_SFT | APLL_DIV_MASK_SFT | UPPER_BOUND_MASK_SFT,
+ (0x1 << XTAL_EN_128FS_SEL_SFT) | (7 << APLL_DIV_SFT) |
+ (3 << UPPER_BOUND_SFT));
+
+ /* apll1 freq tuner enable */
+ regmap_update_bits(afe->regmap, AFE_APLL1_TUNER_CFG,
+ FREQ_TUNER_EN_MASK_SFT,
+ 0x1 << FREQ_TUNER_EN_SFT);
+
+ /* audio apll1 on */
+ mt8196_afe_enable_top_cg(afe, MT8196_AUDIO_APLL1_EN_ON);
+
+ return 0;
+
+err_clk_apll1_tuner:
+ mt8196_afe_disable_top_cg(afe, MT8196_PDN_APLL_TUNER1);
+err_clk_apll1:
+ mt8196_afe_disable_top_cg(afe, MT8196_CG_APLL1_CK);
+ return ret;
+}
+
+void mt8196_apll1_disable(struct mtk_base_afe *afe)
+{
+ /* audio apll1 off */
+ mt8196_afe_disable_top_cg(afe, MT8196_AUDIO_APLL1_EN_ON);
+
+ /* apll1 freq tuner disable */
+ regmap_update_bits(afe->regmap, AFE_APLL1_TUNER_CFG,
+ FREQ_TUNER_EN_MASK_SFT,
+ 0x0);
+
+ mt8196_afe_disable_top_cg(afe, MT8196_PDN_APLL_TUNER1);
+ mt8196_afe_disable_top_cg(afe, MT8196_CG_APLL1_CK);
+ apll1_mux_setting(afe, false);
+}
+
+int mt8196_apll2_enable(struct mtk_base_afe *afe)
+{
+ int ret;
+
+ /* setting for APLL */
+ apll2_mux_setting(afe, true);
+
+ ret = mt8196_afe_enable_top_cg(afe, MT8196_CG_APLL2_CK);
+ if (ret)
+ goto err_clk_apll2;
+
+ ret = mt8196_afe_enable_top_cg(afe, MT8196_PDN_APLL_TUNER2);
+ if (ret)
+ goto err_clk_apll2_tuner;
+
+ /* sel 48kHz: 2, apll_div: 7, upper bound: 3*/
+ regmap_update_bits(afe->regmap, AFE_APLL2_TUNER_CFG,
+ XTAL_EN_128FS_SEL_MASK_SFT | APLL_DIV_MASK_SFT | UPPER_BOUND_MASK_SFT,
+ (0x2 << XTAL_EN_128FS_SEL_SFT) | (7 << APLL_DIV_SFT) |
+ (3 << UPPER_BOUND_SFT));
+
+ /* apll2 freq tuner enable */
+ regmap_update_bits(afe->regmap, AFE_APLL2_TUNER_CFG,
+ FREQ_TUNER_EN_MASK_SFT,
+ 0x1 << FREQ_TUNER_EN_SFT);
+
+ /* audio apll2 on */
+ mt8196_afe_enable_top_cg(afe, MT8196_AUDIO_APLL2_EN_ON);
+ return 0;
+
+err_clk_apll2_tuner:
+ mt8196_afe_disable_top_cg(afe, MT8196_PDN_APLL_TUNER2);
+err_clk_apll2:
+ mt8196_afe_disable_top_cg(afe, MT8196_CG_APLL2_CK);
+ return 0;
+}
+
+void mt8196_apll2_disable(struct mtk_base_afe *afe)
+{
+ /* audio apll2 off */
+ mt8196_afe_disable_top_cg(afe, MT8196_AUDIO_APLL2_EN_ON);
+
+ /* apll2 freq tuner disable */
+ regmap_update_bits(afe->regmap, AFE_APLL2_TUNER_CFG,
+ FREQ_TUNER_EN_MASK_SFT,
+ 0x0);
+
+ mt8196_afe_disable_top_cg(afe, MT8196_PDN_APLL_TUNER2);
+ mt8196_afe_disable_top_cg(afe, MT8196_CG_APLL2_CK);
+ apll2_mux_setting(afe, false);
+}
+
+int mt8196_get_apll_rate(struct mtk_base_afe *afe, int apll)
+{
+ struct mt8196_afe_private *afe_priv = afe->platform_priv;
+ int clk_id = 0;
+
+ if (apll < MT8196_APLL1 || apll > MT8196_APLL2) {
+ dev_warn(afe->dev, "invalid clk id %d\n", apll);
+ return 0;
+ }
+
+ if (apll == MT8196_APLL1)
+ clk_id = MT8196_CLK_TOP_APLL1_CK;
+ else
+ clk_id = MT8196_CLK_TOP_APLL2_CK;
+
+ return clk_get_rate(afe_priv->clk[clk_id]);
+}
+
+/* 48K: select APLL2; 44.1k: select APLL1 */
+int mt8196_get_apll_by_rate(struct mtk_base_afe *afe, int rate)
+{
+ return (rate % 8000) ? MT8196_APLL1 : MT8196_APLL2;
+}
+
+int mt8196_get_apll_by_name(struct mtk_base_afe *afe, const char *name)
+{
+ if (strcmp(name, APLL1_W_NAME) == 0)
+ return MT8196_APLL1;
+
+ return MT8196_APLL2;
+}
+
+static const int mck_div[MT8196_MCK_NUM] = {
+ [MT8196_I2SIN0_MCK] = MT8196_CLK_TOP_APLL12_DIV_I2SIN0,
+ [MT8196_I2SIN1_MCK] = MT8196_CLK_TOP_APLL12_DIV_I2SIN1,
+ [MT8196_FMI2S_MCK] = MT8196_CLK_TOP_APLL12_DIV_FMI2S,
+ [MT8196_TDMOUT_MCK] = MT8196_CLK_TOP_APLL12_DIV_TDMOUT_M,
+ [MT8196_TDMOUT_BCK] = MT8196_CLK_TOP_APLL12_DIV_TDMOUT_B,
+};
+
+int mt8196_mck_enable(struct mtk_base_afe *afe, int mck_id, int rate)
+{
+ struct mt8196_afe_private *afe_priv = afe->platform_priv;
+ int div_clk_id;
+ int ret;
+
+ dev_dbg(afe->dev, "mck_id: %d, rate: %d\n", mck_id, rate);
+
+ if (mck_id >= MT8196_MCK_NUM || mck_id < 0)
+ return -EINVAL;
+
+ div_clk_id = mck_div[mck_id];
+
+ /* enable div, set rate */
+ if (div_clk_id < 0) {
+ dev_err(afe->dev, "invalid div_clk_id %d\n", div_clk_id);
+ return -EINVAL;
+ }
+
+ if (div_clk_id == MT8196_CLK_TOP_APLL12_DIV_TDMOUT_B)
+ rate *= 16;
+
+ ret = mt8196_afe_enable_clk(afe, afe_priv->clk[div_clk_id]);
+ if (ret)
+ return ret;
+
+ ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[div_clk_id], rate);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+int mt8196_mck_disable(struct mtk_base_afe *afe, int mck_id)
+{
+ struct mt8196_afe_private *afe_priv = afe->platform_priv;
+ int div_clk_id;
+ int ret;
+
+ dev_dbg(afe->dev, "mck_id: %d.\n", mck_id);
+
+ if (mck_id < 0) {
+ dev_err(afe->dev, "mck_id = %d < 0\n", mck_id);
+ return -EINVAL;
+ }
+
+ div_clk_id = mck_div[mck_id];
+
+ if (div_clk_id < 0) {
+ dev_err(afe->dev, "div_clk_id = %d < 0\n",
+ div_clk_id);
+ return -EINVAL;
+ }
+
+ ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[div_clk_id], MT8196_AFE_26M);
+ if (ret)
+ return ret;
+
+ mt8196_afe_disable_clk(afe, afe_priv->clk[div_clk_id]);
+
+ return 0;
+}
+
+int mt8196_afe_enable_reg_rw_clk(struct mtk_base_afe *afe)
+{
+ struct mt8196_afe_private *afe_priv = afe->platform_priv;
+ int ret;
+
+ /* bus clock for AFE external access, like DRAM */
+ mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_TOP_ADSP_SEL]);
+
+ /* bus clock for AFE internal access, like AFE SRAM */
+ mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIOINTBUS]);
+ ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIOINTBUS],
+ MT8196_AFE_26M);
+ if (ret)
+ return ret;
+
+ /* enable audio h clock */
+ mt8196_afe_enable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]);
+ ret = mt8196_afe_set_clk_rate(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H],
+ MT8196_AFE_26M);
+ if (ret)
+ return ret;
+
+ /* AFE hw clock */
+ /* IPM2.0: USE HOPPING & 26M */
+ /* set in the regmap_register_patch */
+ return 0;
+}
+
+int mt8196_afe_disable_reg_rw_clk(struct mtk_base_afe *afe)
+{
+ struct mt8196_afe_private *afe_priv = afe->platform_priv;
+
+ /* IPM2.0: Use HOPPING & 26M */
+ /* set in the regmap_register_patch */
+
+ mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIO_H]);
+ mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_VLP_MUX_AUDIOINTBUS]);
+ mt8196_afe_disable_clk(afe, afe_priv->clk[MT8196_CLK_TOP_ADSP_SEL]);
+ return 0;
+}
+
+int mt8196_afe_enable_main_clock(struct mtk_base_afe *afe)
+{
+ mt8196_afe_enable_top_cg(afe, MT8196_AUDIO_26M_EN_ON);
+ return 0;
+}
+
+int mt8196_afe_disable_main_clock(struct mtk_base_afe *afe)
+{
+ mt8196_afe_disable_top_cg(afe, MT8196_AUDIO_26M_EN_ON);
+ return 0;
+}
+
+int mt8196_init_clock(struct mtk_base_afe *afe)
+{
+ struct mt8196_afe_private *afe_priv = afe->platform_priv;
+ int i;
+
+ afe_priv->clk = devm_kcalloc(afe->dev, MT8196_CLK_NUM, sizeof(*afe_priv->clk),
+ GFP_KERNEL);
+ if (!afe_priv->clk)
+ return -ENOMEM;
+
+ for (i = 0; i < MT8196_CLK_NUM; i++) {
+ afe_priv->clk[i] = devm_clk_get(afe->dev, aud_clks[i]);
+ if (IS_ERR(afe_priv->clk[i])) {
+ dev_err(afe->dev, "devm_clk_get %s fail\n", aud_clks[i]);
+ return PTR_ERR(afe_priv->clk[i]);
+ }
+ }
+
+ return 0;
+}
+
diff --git a/sound/soc/mediatek/mt8196/mt8196-afe-clk.h b/sound/soc/mediatek/mt8196/mt8196-afe-clk.h
new file mode 100644
index 000000000000..7d47dcff768b
--- /dev/null
+++ b/sound/soc/mediatek/mt8196/mt8196-afe-clk.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * mt8196-afe-clk.h -- Mediatek MT8196 AFE Clock Control definitions
+ *
+ * Copyright (c) 2025 MediaTek Inc.
+ * Author: Darren Ye <darren.ye@mediatek.com>
+ */
+
+#ifndef _MT8196_AFE_CLOCK_CTRL_H_
+#define _MT8196_AFE_CLOCK_CTRL_H_
+
+#define MT8196_AFE_26M 26000000
+#define MT8196_AUD_ENG1_CLK 45158400
+#define MT8196_AUD_ENG2_CLK 49152000
+
+/* APLL */
+#define APLL1_W_NAME "APLL1"
+#define APLL2_W_NAME "APLL2"
+
+enum {
+ MT8196_APLL1 = 0,
+ MT8196_APLL2,
+};
+
+enum {
+ /* vlp clk */
+ MT8196_CLK_VLP_MUX_AUDIOINTBUS,
+ MT8196_CLK_VLP_MUX_AUD_ENG1,
+ MT8196_CLK_VLP_MUX_AUD_ENG2,
+ MT8196_CLK_VLP_MUX_AUDIO_H,
+ /* pll */
+ MT8196_CLK_TOP_APLL1_CK,
+ MT8196_CLK_TOP_APLL2_CK,
+ /* divider */
+ MT8196_CLK_TOP_APLL12_DIV_I2SIN0,
+ MT8196_CLK_TOP_APLL12_DIV_I2SIN1,
+ MT8196_CLK_TOP_APLL12_DIV_FMI2S,
+ MT8196_CLK_TOP_APLL12_DIV_TDMOUT_M,
+ MT8196_CLK_TOP_APLL12_DIV_TDMOUT_B,
+ /* mux */
+ MT8196_CLK_TOP_ADSP_SEL,
+ MT8196_CLK_NUM,
+};
+
+struct mtk_base_afe;
+
+int mt8196_mck_enable(struct mtk_base_afe *afe, int mck_id, int rate);
+int mt8196_mck_disable(struct mtk_base_afe *afe, int mck_id);
+int mt8196_get_apll_rate(struct mtk_base_afe *afe, int apll);
+int mt8196_get_apll_by_rate(struct mtk_base_afe *afe, int rate);
+int mt8196_get_apll_by_name(struct mtk_base_afe *afe, const char *name);
+int mt8196_init_clock(struct mtk_base_afe *afe);
+int mt8196_afe_enable_clk(struct mtk_base_afe *afe, struct clk *clk);
+void mt8196_afe_disable_clk(struct mtk_base_afe *afe, struct clk *clk);
+int mt8196_apll1_enable(struct mtk_base_afe *afe);
+void mt8196_apll1_disable(struct mtk_base_afe *afe);
+int mt8196_apll2_enable(struct mtk_base_afe *afe);
+void mt8196_apll2_disable(struct mtk_base_afe *afe);
+int mt8196_afe_enable_main_clock(struct mtk_base_afe *afe);
+int mt8196_afe_disable_main_clock(struct mtk_base_afe *afe);
+int mt8196_afe_enable_reg_rw_clk(struct mtk_base_afe *afe);
+int mt8196_afe_disable_reg_rw_clk(struct mtk_base_afe *afe);
+
+#endif
--
2.45.2
^ permalink raw reply related
* [PATCH v9 06/10] ASoC: mediatek: mt8196: support TDM in platform driver
From: Cyril Chao @ 2026-04-30 2:23 UTC (permalink / raw)
To: Liam Girdwood, Mark Brown, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Matthias Brugger, AngeloGioacchino Del Regno,
Jaroslav Kysela, Takashi Iwai
Cc: linux-sound, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek, Cyril Chao, Project_Global_Chrome_Upstream_Group,
Darren Ye, Cyril Chao
In-Reply-To: <20260430022417.32282-1-Cyril.Chao@mediatek.com>
From: Darren Ye <darren.ye@mediatek.com>
Add mt8196 TDM DAI driver support.
Signed-off-by: Darren Ye <darren.ye@mediatek.com>
Signed-off-by: Cyril Chao <Cyril.Chao@mediatek.com>
---
sound/soc/mediatek/mt8196/mt8196-dai-tdm.c | 675 +++++++++++++++++++++
1 file changed, 675 insertions(+)
create mode 100644 sound/soc/mediatek/mt8196/mt8196-dai-tdm.c
diff --git a/sound/soc/mediatek/mt8196/mt8196-dai-tdm.c b/sound/soc/mediatek/mt8196/mt8196-dai-tdm.c
new file mode 100644
index 000000000000..b7aeee939d88
--- /dev/null
+++ b/sound/soc/mediatek/mt8196/mt8196-dai-tdm.c
@@ -0,0 +1,675 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * MediaTek ALSA SoC Audio DAI TDM Control
+ *
+ * Copyright (c) 2025 MediaTek Inc.
+ * Author: Darren Ye <darren.ye@mediatek.com>
+ */
+
+#include <linux/regmap.h>
+#include <sound/pcm_params.h>
+
+#include "mt8196-afe-clk.h"
+#include "mt8196-afe-common.h"
+#include "mt8196-interconnection.h"
+
+struct mtk_afe_tdm_priv {
+ int bck_id;
+ int bck_rate;
+
+ int mclk_id;
+ int mclk_multiple; /* according to sample rate */
+ int mclk_rate;
+ int mclk_apll;
+};
+
+enum {
+ TDM_WLEN_8_BIT,
+ TDM_WLEN_16_BIT,
+ TDM_WLEN_24_BIT,
+ TDM_WLEN_32_BIT,
+};
+
+enum {
+ TDM_CHANNEL_BCK_16,
+ TDM_CHANNEL_BCK_24,
+ TDM_CHANNEL_BCK_32,
+};
+
+enum {
+ TDM_CHANNEL_NUM_2,
+ TDM_CHANNEL_NUM_4,
+ TDM_CHANNEL_NUM_8,
+};
+
+enum {
+ TDM_CH_START_O30_O31,
+ TDM_CH_START_O32_O33,
+ TDM_CH_START_O34_O35,
+ TDM_CH_START_O36_O37,
+ TDM_CH_ZERO,
+};
+
+enum {
+ DPTX_CHANNEL_2,
+ DPTX_CHANNEL_8,
+};
+
+enum {
+ DPTX_WLEN_24_BIT,
+ DPTX_WLEN_16_BIT,
+};
+
+#define DPTX_CH_EN_MASK_2CH (0x3)
+#define DPTX_CH_EN_MASK_4CH (0xf)
+#define DPTX_CH_EN_MASK_6CH (0x3f)
+#define DPTX_CH_EN_MASK_8CH (0xff)
+
+static unsigned int get_tdm_wlen(snd_pcm_format_t format)
+{
+ return (snd_pcm_format_physical_width(format) / 8) - 1;
+}
+
+static unsigned int get_tdm_channel_bck(snd_pcm_format_t format)
+{
+ return snd_pcm_format_physical_width(format) <= 16 ?
+ TDM_CHANNEL_BCK_16 : TDM_CHANNEL_BCK_32;
+}
+
+static unsigned int get_tdm_lrck_width(snd_pcm_format_t format)
+{
+ return snd_pcm_format_physical_width(format) - 1;
+}
+
+static unsigned int get_tdm_ch(unsigned int ch)
+{
+ switch (ch) {
+ case 1:
+ case 2:
+ return TDM_CHANNEL_NUM_2;
+ case 3:
+ case 4:
+ return TDM_CHANNEL_NUM_4;
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ default:
+ return TDM_CHANNEL_NUM_8;
+ }
+}
+
+static unsigned int get_dptx_ch_enable_mask(struct device *dev, unsigned int ch)
+{
+ switch (ch) {
+ case 1:
+ case 2:
+ return DPTX_CH_EN_MASK_2CH;
+ case 3:
+ case 4:
+ return DPTX_CH_EN_MASK_4CH;
+ case 5:
+ case 6:
+ return DPTX_CH_EN_MASK_6CH;
+ case 7:
+ case 8:
+ return DPTX_CH_EN_MASK_8CH;
+ default:
+ dev_warn(dev, "invalid channel num, default use 2ch\n");
+ return DPTX_CH_EN_MASK_2CH;
+ }
+}
+
+static unsigned int get_dptx_ch(unsigned int ch)
+{
+ if (ch == 2)
+ return DPTX_CHANNEL_2;
+ else
+ return DPTX_CHANNEL_8;
+}
+
+static unsigned int get_dptx_wlen(snd_pcm_format_t format)
+{
+ return snd_pcm_format_physical_width(format) <= 16 ?
+ DPTX_WLEN_16_BIT : DPTX_WLEN_24_BIT;
+}
+
+/* interconnection */
+enum {
+ HDMI_CONN_CH0,
+ HDMI_CONN_CH1,
+ HDMI_CONN_CH2,
+ HDMI_CONN_CH3,
+ HDMI_CONN_CH4,
+ HDMI_CONN_CH5,
+ HDMI_CONN_CH6,
+ HDMI_CONN_CH7,
+};
+
+static const char *const hdmi_conn_mux_map[] = {
+ "CH0", "CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7",
+};
+
+static int hdmi_conn_mux_map_value[] = {
+ HDMI_CONN_CH0, HDMI_CONN_CH1, HDMI_CONN_CH2, HDMI_CONN_CH3,
+ HDMI_CONN_CH4, HDMI_CONN_CH5, HDMI_CONN_CH6, HDMI_CONN_CH7,
+};
+
+static SOC_VALUE_ENUM_SINGLE_DECL(hdmi_ch0_mux_map_enum,
+ AFE_HDMI_CONN0, HDMI_O_0_SFT, HDMI_O_0_MASK,
+ hdmi_conn_mux_map, hdmi_conn_mux_map_value);
+
+static SOC_VALUE_ENUM_SINGLE_DECL(hdmi_ch1_mux_map_enum,
+ AFE_HDMI_CONN0,
+ HDMI_O_1_SFT,
+ HDMI_O_1_MASK,
+ hdmi_conn_mux_map,
+ hdmi_conn_mux_map_value);
+
+static SOC_VALUE_ENUM_SINGLE_DECL(hdmi_ch2_mux_map_enum,
+ AFE_HDMI_CONN0,
+ HDMI_O_2_SFT,
+ HDMI_O_2_MASK,
+ hdmi_conn_mux_map,
+ hdmi_conn_mux_map_value);
+
+static SOC_VALUE_ENUM_SINGLE_DECL(hdmi_ch3_mux_map_enum,
+ AFE_HDMI_CONN0,
+ HDMI_O_3_SFT,
+ HDMI_O_3_MASK,
+ hdmi_conn_mux_map,
+ hdmi_conn_mux_map_value);
+
+static SOC_VALUE_ENUM_SINGLE_DECL(hdmi_ch4_mux_map_enum,
+ AFE_HDMI_CONN0,
+ HDMI_O_4_SFT,
+ HDMI_O_4_MASK,
+ hdmi_conn_mux_map,
+ hdmi_conn_mux_map_value);
+
+static SOC_VALUE_ENUM_SINGLE_DECL(hdmi_ch5_mux_map_enum,
+ AFE_HDMI_CONN0,
+ HDMI_O_5_SFT,
+ HDMI_O_5_MASK,
+ hdmi_conn_mux_map,
+ hdmi_conn_mux_map_value);
+
+static SOC_VALUE_ENUM_SINGLE_DECL(hdmi_ch6_mux_map_enum,
+ AFE_HDMI_CONN0,
+ HDMI_O_6_SFT,
+ HDMI_O_6_MASK,
+ hdmi_conn_mux_map,
+ hdmi_conn_mux_map_value);
+
+static SOC_VALUE_ENUM_SINGLE_DECL(hdmi_ch7_mux_map_enum,
+ AFE_HDMI_CONN0,
+ HDMI_O_7_SFT,
+ HDMI_O_7_MASK,
+ hdmi_conn_mux_map,
+ hdmi_conn_mux_map_value);
+
+static const struct snd_kcontrol_new mtk_dai_tdm_controls[] = {
+ SOC_ENUM("HDMI_CH0_MUX", hdmi_ch0_mux_map_enum),
+ SOC_ENUM("HDMI_CH1_MUX", hdmi_ch1_mux_map_enum),
+ SOC_ENUM("HDMI_CH2_MUX", hdmi_ch2_mux_map_enum),
+ SOC_ENUM("HDMI_CH3_MUX", hdmi_ch3_mux_map_enum),
+ SOC_ENUM("HDMI_CH4_MUX", hdmi_ch4_mux_map_enum),
+ SOC_ENUM("HDMI_CH5_MUX", hdmi_ch5_mux_map_enum),
+ SOC_ENUM("HDMI_CH6_MUX", hdmi_ch6_mux_map_enum),
+ SOC_ENUM("HDMI_CH7_MUX", hdmi_ch7_mux_map_enum),
+};
+
+static const char *const tdm_out_demux_texts[] = {
+ "NONE", "TDMOUT", "DPTXOUT",
+};
+
+static SOC_ENUM_SINGLE_DECL(tdm_out_demux_enum,
+ SND_SOC_NOPM,
+ 0,
+ tdm_out_demux_texts);
+static const struct snd_kcontrol_new tdm_out_demux_control =
+ SOC_DAPM_ENUM("TDM DEMUX ROUTE", tdm_out_demux_enum);
+
+enum {
+ SUPPLY_SEQ_APLL,
+ SUPPLY_SEQ_TDM_MCK_EN,
+ SUPPLY_SEQ_TDM_BCK_EN,
+ SUPPLY_SEQ_TDM_DPTX_MCK_EN,
+ SUPPLY_SEQ_TDM_DPTX_BCK_EN,
+ SUPPLY_SEQ_TDM_CG_EN,
+};
+
+static int get_tdm_id_by_name(const char *name)
+{
+ if (strstr(name, "DPTX"))
+ return MT8196_DAI_TDM_DPTX;
+ else
+ return MT8196_DAI_TDM;
+}
+
+static int mtk_tdm_bck_en_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol,
+ int event)
+{
+ struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm);
+ struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt);
+ struct mt8196_afe_private *afe_priv = afe->platform_priv;
+ int dai_id = get_tdm_id_by_name(w->name);
+ struct mtk_afe_tdm_priv *tdm_priv = afe_priv->dai_priv[dai_id];
+
+ dev_dbg(cmpnt->dev, "name %s, event 0x%x, dai_id %d\n",
+ w->name, event, dai_id);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ mt8196_mck_enable(afe, tdm_priv->bck_id, tdm_priv->bck_rate);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ mt8196_mck_disable(afe, tdm_priv->bck_id);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int mtk_tdm_mck_en_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol,
+ int event)
+{
+ struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm);
+ struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt);
+ struct mt8196_afe_private *afe_priv = afe->platform_priv;
+ int dai_id = get_tdm_id_by_name(w->name);
+ struct mtk_afe_tdm_priv *tdm_priv = afe_priv->dai_priv[dai_id];
+
+ dev_dbg(cmpnt->dev, "name %s, event 0x%x, dai_id %d\n",
+ w->name, event, dai_id);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ mt8196_mck_enable(afe, tdm_priv->mclk_id, tdm_priv->mclk_rate);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ tdm_priv->mclk_rate = 0;
+ mt8196_mck_disable(afe, tdm_priv->mclk_id);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget mtk_dai_tdm_widgets[] = {
+ SND_SOC_DAPM_DEMUX("TDM_DEMUX", SND_SOC_NOPM, 0, 0,
+ &tdm_out_demux_control),
+
+ SND_SOC_DAPM_SUPPLY_S("TDM_BCK", SUPPLY_SEQ_TDM_BCK_EN,
+ SND_SOC_NOPM, 0, 0,
+ mtk_tdm_bck_en_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_SUPPLY_S("TDM_MCK", SUPPLY_SEQ_TDM_MCK_EN,
+ SND_SOC_NOPM, 0, 0,
+ mtk_tdm_mck_en_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_SUPPLY_S("TDM_DPTX_BCK", SUPPLY_SEQ_TDM_DPTX_BCK_EN,
+ SND_SOC_NOPM, 0, 0,
+ mtk_tdm_bck_en_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_SUPPLY_S("TDM_DPTX_MCK", SUPPLY_SEQ_TDM_DPTX_MCK_EN,
+ SND_SOC_NOPM, 0, 0,
+ mtk_tdm_mck_en_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+ /* cg */
+ SND_SOC_DAPM_SUPPLY_S("TDM_CG", SUPPLY_SEQ_TDM_CG_EN,
+ AUDIO_TOP_CON2, PDN_TDM_OUT_SFT, 1,
+ NULL, 0),
+};
+
+static int mtk_afe_tdm_apll_connect(struct snd_soc_dapm_widget *source,
+ struct snd_soc_dapm_widget *sink)
+{
+ struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(sink->dapm);
+ struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt);
+ struct mt8196_afe_private *afe_priv = afe->platform_priv;
+ int dai_id = get_tdm_id_by_name(sink->name);
+ struct mtk_afe_tdm_priv *tdm_priv = afe_priv->dai_priv[dai_id];
+ int cur_apll;
+
+ /* which apll */
+ cur_apll = mt8196_get_apll_by_name(afe, source->name);
+
+ return (tdm_priv->mclk_apll == cur_apll) ? 1 : 0;
+}
+
+static const struct snd_soc_dapm_route mtk_dai_tdm_routes[] = {
+ {"TDM_DEMUX", NULL, "HDMI"},
+
+ {"TDM", "TDMOUT", "TDM_DEMUX"},
+ {"TDM", NULL, "TDM_BCK"},
+ {"TDM", NULL, "TDM_CG"},
+
+ {"TDM_DPTX", "DPTXOUT", "TDM_DEMUX"},
+ {"TDM_DPTX", NULL, "TDM_DPTX_BCK"},
+ {"TDM_DPTX", NULL, "TDM_CG"},
+
+ {"TDM_BCK", NULL, "TDM_MCK"},
+ {"TDM_DPTX_BCK", NULL, "TDM_DPTX_MCK"},
+ {"TDM_MCK", NULL, APLL1_W_NAME, mtk_afe_tdm_apll_connect},
+ {"TDM_MCK", NULL, APLL2_W_NAME, mtk_afe_tdm_apll_connect},
+ {"TDM_DPTX_MCK", NULL, APLL1_W_NAME, mtk_afe_tdm_apll_connect},
+ {"TDM_DPTX_MCK", NULL, APLL2_W_NAME, mtk_afe_tdm_apll_connect},
+};
+
+/* dai ops */
+static int mtk_dai_tdm_cal_mclk(struct mtk_base_afe *afe,
+ struct mtk_afe_tdm_priv *tdm_priv,
+ int freq)
+{
+ int apll;
+ int apll_rate;
+
+ apll = mt8196_get_apll_by_rate(afe, freq);
+ apll_rate = mt8196_get_apll_rate(afe, apll);
+
+ if (freq > apll_rate)
+ return -EINVAL;
+
+ if (apll_rate % freq != 0)
+ return -EINVAL;
+
+ tdm_priv->mclk_rate = freq;
+ tdm_priv->mclk_apll = apll;
+
+ return 0;
+}
+
+static int mtk_dai_tdm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
+ struct mt8196_afe_private *afe_priv = afe->platform_priv;
+ int tdm_id = dai->id;
+ struct mtk_afe_tdm_priv *tdm_priv;
+ unsigned int rate = params_rate(params);
+ unsigned int channels = params_channels(params);
+ snd_pcm_format_t format = params_format(params);
+ unsigned int tdm_con = 0;
+
+ if (tdm_id >= MT8196_DAI_NUM || tdm_id < 0)
+ return -EINVAL;
+
+ tdm_priv = afe_priv->dai_priv[tdm_id];
+
+ if (!tdm_priv)
+ return -EINVAL;
+
+ /* calculate mclk_rate, if not set explicitly */
+ if (!tdm_priv->mclk_rate) {
+ tdm_priv->mclk_rate = rate * tdm_priv->mclk_multiple;
+ mtk_dai_tdm_cal_mclk(afe,
+ tdm_priv,
+ tdm_priv->mclk_rate);
+ }
+
+ /* calculate bck */
+ tdm_priv->bck_rate = rate *
+ channels *
+ snd_pcm_format_physical_width(format);
+
+ if (tdm_priv->bck_rate > tdm_priv->mclk_rate)
+ return -EINVAL;
+
+ if (tdm_priv->mclk_rate % tdm_priv->bck_rate != 0)
+ return -EINVAL;
+
+ dev_dbg(afe->dev, "id %d, rate %d, ch %d, fmt %d, mclk %d, bck %d\n",
+ tdm_id, rate, channels, format, tdm_priv->mclk_rate, tdm_priv->bck_rate);
+
+ /* set tdm */
+ tdm_con = 0 << BCK_INVERSE_SFT;
+ tdm_con |= 0 << LRCK_INVERSE_SFT;
+ tdm_con |= 0 << DELAY_DATA_SFT;
+ tdm_con |= 1 << LEFT_ALIGN_SFT;
+ tdm_con |= get_tdm_wlen(format) << WLEN_SFT;
+ tdm_con |= get_tdm_ch(channels) << CHANNEL_NUM_SFT;
+ tdm_con |= get_tdm_channel_bck(format) << CHANNEL_BCK_CYCLES_SFT;
+ tdm_con |= get_tdm_lrck_width(format) << LRCK_TDM_WIDTH_SFT;
+ regmap_write(afe->regmap, AFE_TDM_CON1, tdm_con);
+
+ /* set dptx */
+ if (tdm_id == MT8196_DAI_TDM_DPTX) {
+ regmap_update_bits(afe->regmap, AFE_DPTX_CON,
+ DPTX_CHANNEL_ENABLE_MASK_SFT,
+ get_dptx_ch_enable_mask(afe->dev, channels) <<
+ DPTX_CHANNEL_ENABLE_SFT);
+ regmap_update_bits(afe->regmap, AFE_DPTX_CON,
+ DPTX_CHANNEL_NUMBER_MASK_SFT,
+ get_dptx_ch(channels) <<
+ DPTX_CHANNEL_NUMBER_SFT);
+ regmap_update_bits(afe->regmap, AFE_DPTX_CON,
+ DPTX_16BIT_MASK_SFT,
+ get_dptx_wlen(format) << DPTX_16BIT_SFT);
+ }
+ switch (channels) {
+ case 1:
+ case 2:
+ tdm_con = TDM_CH_START_O30_O31 << ST_CH_PAIR_SOUT0_SFT;
+ tdm_con |= TDM_CH_ZERO << ST_CH_PAIR_SOUT1_SFT;
+ tdm_con |= TDM_CH_ZERO << ST_CH_PAIR_SOUT2_SFT;
+ tdm_con |= TDM_CH_ZERO << ST_CH_PAIR_SOUT3_SFT;
+ break;
+ case 3:
+ case 4:
+ tdm_con = TDM_CH_START_O30_O31 << ST_CH_PAIR_SOUT0_SFT;
+ tdm_con |= TDM_CH_START_O32_O33 << ST_CH_PAIR_SOUT1_SFT;
+ tdm_con |= TDM_CH_ZERO << ST_CH_PAIR_SOUT2_SFT;
+ tdm_con |= TDM_CH_ZERO << ST_CH_PAIR_SOUT3_SFT;
+ break;
+ case 5:
+ case 6:
+ tdm_con = TDM_CH_START_O30_O31 << ST_CH_PAIR_SOUT0_SFT;
+ tdm_con |= TDM_CH_START_O32_O33 << ST_CH_PAIR_SOUT1_SFT;
+ tdm_con |= TDM_CH_START_O34_O35 << ST_CH_PAIR_SOUT2_SFT;
+ tdm_con |= TDM_CH_ZERO << ST_CH_PAIR_SOUT3_SFT;
+ break;
+ case 7:
+ case 8:
+ tdm_con = TDM_CH_START_O30_O31 << ST_CH_PAIR_SOUT0_SFT;
+ tdm_con |= TDM_CH_START_O32_O33 << ST_CH_PAIR_SOUT1_SFT;
+ tdm_con |= TDM_CH_START_O34_O35 << ST_CH_PAIR_SOUT2_SFT;
+ tdm_con |= TDM_CH_START_O36_O37 << ST_CH_PAIR_SOUT3_SFT;
+ break;
+ default:
+ tdm_con = 0;
+ }
+
+ regmap_write(afe->regmap, AFE_TDM_CON2, tdm_con);
+ regmap_update_bits(afe->regmap, AFE_HDMI_OUT_CON0,
+ HDMI_CH_NUM_MASK_SFT,
+ channels << HDMI_CH_NUM_SFT);
+
+ return 0;
+}
+
+static int mtk_dai_tdm_trigger(struct snd_pcm_substream *substream,
+ int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
+ int tdm_id = dai->id;
+
+ dev_dbg(afe->dev, "cmd %d, tdm_id %d\n", cmd, tdm_id);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ /* enable Out control */
+ regmap_update_bits(afe->regmap, AFE_HDMI_OUT_CON0,
+ HDMI_OUT_ON_MASK_SFT,
+ 0x1 << HDMI_OUT_ON_SFT);
+
+ /* enable dptx */
+ if (tdm_id == MT8196_DAI_TDM_DPTX) {
+ regmap_update_bits(afe->regmap, AFE_DPTX_CON,
+ DPTX_ON_MASK_SFT, 0x1 <<
+ DPTX_ON_SFT);
+ }
+
+ /* enable tdm */
+ regmap_update_bits(afe->regmap, AFE_TDM_CON1,
+ TDM_EN_MASK_SFT, 0x1 << TDM_EN_SFT);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ /* disable tdm */
+ regmap_update_bits(afe->regmap, AFE_TDM_CON1,
+ TDM_EN_MASK_SFT, 0);
+
+ /* disable dptx */
+ if (tdm_id == MT8196_DAI_TDM_DPTX) {
+ regmap_update_bits(afe->regmap, AFE_DPTX_CON,
+ DPTX_ON_MASK_SFT, 0);
+ }
+
+ /* disable Out control */
+ regmap_update_bits(afe->regmap, AFE_HDMI_OUT_CON0,
+ HDMI_OUT_ON_MASK_SFT, 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mtk_dai_tdm_set_sysclk(struct snd_soc_dai *dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct mtk_base_afe *afe = dev_get_drvdata(dai->dev);
+ struct mt8196_afe_private *afe_priv = afe->platform_priv;
+ struct mtk_afe_tdm_priv *tdm_priv;
+
+ if (dai->id >= MT8196_DAI_NUM || dai->id < 0)
+ return -EINVAL;
+
+ tdm_priv = afe_priv->dai_priv[dai->id];
+
+ if (!tdm_priv)
+ return -EINVAL;
+
+ if (dir != SND_SOC_CLOCK_OUT)
+ return -EINVAL;
+
+ dev_dbg(afe->dev, "freq %d\n", freq);
+
+ return mtk_dai_tdm_cal_mclk(afe, tdm_priv, freq);
+}
+
+static const struct snd_soc_dai_ops mtk_dai_tdm_ops = {
+ .hw_params = mtk_dai_tdm_hw_params,
+ .trigger = mtk_dai_tdm_trigger,
+ .set_sysclk = mtk_dai_tdm_set_sysclk,
+};
+
+/* dai driver */
+#define MTK_TDM_RATES (SNDRV_PCM_RATE_8000_48000 |\
+ SNDRV_PCM_RATE_88200 |\
+ SNDRV_PCM_RATE_96000 |\
+ SNDRV_PCM_RATE_176400 |\
+ SNDRV_PCM_RATE_192000)
+
+#define MTK_TDM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
+ SNDRV_PCM_FMTBIT_S24_LE |\
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_driver mtk_dai_tdm_driver[] = {
+ {
+ .name = "TDM",
+ .id = MT8196_DAI_TDM,
+ .playback = {
+ .stream_name = "TDM",
+ .channels_min = 2,
+ .channels_max = 8,
+ .rates = MTK_TDM_RATES,
+ .formats = MTK_TDM_FORMATS,
+ },
+ .ops = &mtk_dai_tdm_ops,
+ },
+ {
+ .name = "TDM_DPTX",
+ .id = MT8196_DAI_TDM_DPTX,
+ .playback = {
+ .stream_name = "TDM_DPTX",
+ .channels_min = 2,
+ .channels_max = 8,
+ .rates = MTK_TDM_RATES,
+ .formats = MTK_TDM_FORMATS,
+ },
+ .ops = &mtk_dai_tdm_ops,
+ },
+};
+
+static struct mtk_afe_tdm_priv *init_tdm_priv_data(struct mtk_base_afe *afe,
+ int id)
+{
+ struct mtk_afe_tdm_priv *tdm_priv;
+
+ tdm_priv = devm_kzalloc(afe->dev, sizeof(struct mtk_afe_tdm_priv),
+ GFP_KERNEL);
+ if (!tdm_priv)
+ return NULL;
+
+ if (id == MT8196_DAI_TDM_DPTX)
+ tdm_priv->mclk_multiple = 256;
+ else
+ tdm_priv->mclk_multiple = 128;
+
+ tdm_priv->bck_id = MT8196_TDMOUT_BCK;
+ tdm_priv->mclk_id = MT8196_TDMOUT_MCK;
+
+ return tdm_priv;
+}
+
+int mt8196_dai_tdm_register(struct mtk_base_afe *afe)
+{
+ struct mt8196_afe_private *afe_priv = afe->platform_priv;
+ struct mtk_afe_tdm_priv *tdm_priv, *tdm_dptx_priv;
+ struct mtk_base_afe_dai *dai;
+
+ dai = devm_kzalloc(afe->dev, sizeof(*dai), GFP_KERNEL);
+ if (!dai)
+ return -ENOMEM;
+
+ dai->dai_drivers = mtk_dai_tdm_driver;
+ dai->num_dai_drivers = ARRAY_SIZE(mtk_dai_tdm_driver);
+ dai->controls = mtk_dai_tdm_controls;
+ dai->num_controls = ARRAY_SIZE(mtk_dai_tdm_controls);
+ dai->dapm_widgets = mtk_dai_tdm_widgets;
+ dai->num_dapm_widgets = ARRAY_SIZE(mtk_dai_tdm_widgets);
+ dai->dapm_routes = mtk_dai_tdm_routes;
+ dai->num_dapm_routes = ARRAY_SIZE(mtk_dai_tdm_routes);
+
+ tdm_priv = init_tdm_priv_data(afe, MT8196_DAI_TDM);
+ if (!tdm_priv)
+ return -ENOMEM;
+
+ tdm_dptx_priv = init_tdm_priv_data(afe, MT8196_DAI_TDM_DPTX);
+ if (!tdm_dptx_priv)
+ return -ENOMEM;
+
+ list_add(&dai->list, &afe->sub_dais);
+
+ afe_priv->dai_priv[MT8196_DAI_TDM] = tdm_priv;
+ afe_priv->dai_priv[MT8196_DAI_TDM_DPTX] = tdm_dptx_priv;
+
+ return 0;
+}
+
--
2.45.2
^ permalink raw reply related
* [PATCH v9 07/10] ASoC: dt-bindings: mediatek,mt8196-afe: add audio AFE
From: Cyril Chao @ 2026-04-30 2:23 UTC (permalink / raw)
To: Liam Girdwood, Mark Brown, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Matthias Brugger, AngeloGioacchino Del Regno,
Jaroslav Kysela, Takashi Iwai
Cc: linux-sound, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek, Cyril Chao, Project_Global_Chrome_Upstream_Group,
Darren Ye, Krzysztof Kozlowski, Cyril Chao
In-Reply-To: <20260430022417.32282-1-Cyril.Chao@mediatek.com>
From: Darren Ye <darren.ye@mediatek.com>
Add mt8196 audio AFE.
Signed-off-by: Darren Ye <darren.ye@mediatek.com>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Signed-off-by: Cyril Chao <Cyril.Chao@mediatek.com>
---
.../bindings/sound/mediatek,mt8196-afe.yaml | 113 ++++++++++++++++++
1 file changed, 113 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/mediatek,mt8196-afe.yaml
diff --git a/Documentation/devicetree/bindings/sound/mediatek,mt8196-afe.yaml b/Documentation/devicetree/bindings/sound/mediatek,mt8196-afe.yaml
new file mode 100644
index 000000000000..949f8622baf9
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/mediatek,mt8196-afe.yaml
@@ -0,0 +1,113 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/mediatek,mt8196-afe.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: MediaTek Audio Front End PCM controller for MT8196
+
+maintainers:
+ - Darren Ye <darren.ye@mediatek.com>
+
+properties:
+ compatible:
+ const: mediatek,mt8196-afe
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ memory-region:
+ maxItems: 1
+
+ power-domains:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: mux for audio intbus
+ - description: mux for audio engen1
+ - description: mux for audio engen2
+ - description: mux for audio h
+ - description: audio apll1 clock
+ - description: audio apll2 clock
+ - description: audio apll12 divide for i2sin0
+ - description: audio apll12 divide for i2sin1
+ - description: audio apll12 divide for fmi2s
+ - description: audio apll12 divide for tdmout mck
+ - description: audio apll12 divide for tdmout bck
+ - description: mux for adsp clock
+
+ clock-names:
+ items:
+ - const: top_aud_intbus
+ - const: top_aud_eng1
+ - const: top_aud_eng2
+ - const: top_aud_h
+ - const: apll1
+ - const: apll2
+ - const: apll12_div_i2sin0
+ - const: apll12_div_i2sin1
+ - const: apll12_div_fmi2s
+ - const: apll12_div_tdmout_m
+ - const: apll12_div_tdmout_b
+ - const: top_adsp
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - memory-region
+ - power-domains
+ - clocks
+ - clock-names
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ soc {
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ afe@1a110000 {
+ compatible = "mediatek,mt8196-afe";
+ reg = <0 0x1a110000 0 0x9000>;
+ interrupts = <GIC_SPI 351 IRQ_TYPE_LEVEL_HIGH 0>;
+ memory-region = <&afe_dma_mem_reserved>;
+ power-domains = <&scpsys 14>; //MT8196_POWER_DOMAIN_AUDIO
+ pinctrl-names = "default";
+ pinctrl-0 = <&aud_pins_default>;
+ clocks = <&vlp_cksys_clk 40>, //CLK_VLP_CK_AUD_INTBUS_SEL
+ <&vlp_cksys_clk 38>, //CLK_VLP_CK_AUD_ENGEN1_SEL
+ <&vlp_cksys_clk 39>, //CLK_VLP_CK_AUD_ENGEN2_SEL
+ <&vlp_cksys_clk 37>, //CLK_VLP_CK_AUDIO_H_SEL
+ <&vlp_cksys_clk 0>, //CLK_VLP_CK_VLP_APLL1
+ <&vlp_cksys_clk 1>, //CLK_VLP_CK_VLP_APLL2
+ <&cksys_clk 80>, //CLK_CK_APLL12_CK_DIV_I2SIN0
+ <&cksys_clk 81>, //CLK_CK_APLL12_CK_DIV_I2SIN1
+ <&cksys_clk 92>, //CLK_CK_APLL12_CK_DIV_FMI2S
+ <&cksys_clk 93>, //CLK_CK_APLL12_CK_DIV_TDMOUT_M
+ <&cksys_clk 94>, //CLK_CK_APLL12_CK_DIV_TDMOUT_B
+ <&cksys_clk 45>; //CLK_CK_ADSP_SEL
+ clock-names = "top_aud_intbus",
+ "top_aud_eng1",
+ "top_aud_eng2",
+ "top_aud_h",
+ "apll1",
+ "apll2",
+ "apll12_div_i2sin0",
+ "apll12_div_i2sin1",
+ "apll12_div_fmi2s",
+ "apll12_div_tdmout_m",
+ "apll12_div_tdmout_b",
+ "top_adsp";
+ };
+ };
+
+...
--
2.45.2
^ permalink raw reply related
* [PATCH v9 09/10] ASoC: dt-bindings: mediatek,mt8196-nau8825: Add audio sound card
From: Cyril Chao @ 2026-04-30 2:23 UTC (permalink / raw)
To: Liam Girdwood, Mark Brown, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Matthias Brugger, AngeloGioacchino Del Regno,
Jaroslav Kysela, Takashi Iwai
Cc: linux-sound, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek, Cyril Chao, Project_Global_Chrome_Upstream_Group,
Darren Ye, Krzysztof Kozlowski, Cyril Chao
In-Reply-To: <20260430022417.32282-1-Cyril.Chao@mediatek.com>
From: Darren Ye <darren.ye@mediatek.com>
Add soundcard bindings for the MT8196 SoC with the NAU8825 audio codec.
Signed-off-by: Darren Ye <darren.ye@mediatek.com>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Signed-off-by: Cyril Chao <Cyril.Chao@mediatek.com>
---
.../sound/mediatek,mt8196-nau8825.yaml | 100 ++++++++++++++++++
1 file changed, 100 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/mediatek,mt8196-nau8825.yaml
diff --git a/Documentation/devicetree/bindings/sound/mediatek,mt8196-nau8825.yaml b/Documentation/devicetree/bindings/sound/mediatek,mt8196-nau8825.yaml
new file mode 100644
index 000000000000..83350faa1e29
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/mediatek,mt8196-nau8825.yaml
@@ -0,0 +1,100 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/mediatek,mt8196-nau8825.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: MediaTek MT8196 ASoC sound card
+
+maintainers:
+ - Darren Ye <darren.ye@mediatek.com>
+
+allOf:
+ - $ref: sound-card-common.yaml#
+
+properties:
+ compatible:
+ enum:
+ - mediatek,mt8196-nau8825-sound
+ - mediatek,mt8196-rt5682s-sound
+ - mediatek,mt8196-rt5650-sound
+
+ mediatek,platform:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description: The phandle of MT8188 ASoC platform.
+
+patternProperties:
+ "^dai-link-[0-9]+$":
+ type: object
+ description:
+ Container for dai-link level properties and CODEC sub-nodes.
+
+ properties:
+ link-name:
+ description:
+ This property corresponds to the name of the BE dai-link to which
+ we are going to update parameters in this node.
+ items:
+ enum:
+ - TDM_DPTX_BE
+ - I2SOUT6_BE
+ - I2SIN6_BE
+ - I2SOUT4_BE
+ - I2SOUT3_BE
+
+ codec:
+ description: Holds subnode which indicates codec dai.
+ type: object
+ additionalProperties: false
+ properties:
+ sound-dai:
+ minItems: 1
+ maxItems: 2
+ required:
+ - sound-dai
+
+ dai-format:
+ description: audio format.
+ items:
+ enum:
+ - i2s
+ - right_j
+ - left_j
+ - dsp_a
+ - dsp_b
+
+ mediatek,clk-provider:
+ $ref: /schemas/types.yaml#/definitions/string
+ description: Indicates dai-link clock master.
+ enum:
+ - cpu
+ - codec
+
+ additionalProperties: false
+
+ required:
+ - link-name
+
+required:
+ - compatible
+ - mediatek,platform
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ sound {
+ compatible = "mediatek,mt8196-nau8825-sound";
+ model = "mt8196-nau8825";
+ mediatek,platform = <&afe>;
+ dai-link-0 {
+ link-name = "I2SOUT6_BE";
+ dai-format = "i2s";
+ mediatek,clk-provider = "cpu";
+ codec {
+ sound-dai = <&nau8825>;
+ };
+ };
+ };
+
+...
--
2.45.2
^ permalink raw reply related
* [PATCH v9 04/10] ASoC: mediatek: mt8196: support ADDA in platform driver
From: Cyril Chao @ 2026-04-30 2:23 UTC (permalink / raw)
To: Liam Girdwood, Mark Brown, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Matthias Brugger, AngeloGioacchino Del Regno,
Jaroslav Kysela, Takashi Iwai
Cc: linux-sound, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek, Cyril Chao, Project_Global_Chrome_Upstream_Group,
Darren Ye, Cyril Chao
In-Reply-To: <20260430022417.32282-1-Cyril.Chao@mediatek.com>
From: Darren Ye <darren.ye@mediatek.com>
Add mt8196 ADDA DAI driver support.
Signed-off-by: Darren Ye <darren.ye@mediatek.com>
Signed-off-by: Cyril Chao <Cyril.Chao@mediatek.com>
---
sound/soc/mediatek/mt8196/mt8196-dai-adda.c | 845 ++++++++++++++++++++
1 file changed, 845 insertions(+)
create mode 100644 sound/soc/mediatek/mt8196/mt8196-dai-adda.c
diff --git a/sound/soc/mediatek/mt8196/mt8196-dai-adda.c b/sound/soc/mediatek/mt8196/mt8196-dai-adda.c
new file mode 100644
index 000000000000..9a91db4e79ae
--- /dev/null
+++ b/sound/soc/mediatek/mt8196/mt8196-dai-adda.c
@@ -0,0 +1,845 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * MediaTek ALSA SoC Audio DAI ADDA Control
+ *
+ * Copyright (c) 2025 MediaTek Inc.
+ * Author: Darren Ye <darren.ye@mediatek.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/regmap.h>
+
+#include "mt8196-afe-clk.h"
+#include "mt8196-afe-common.h"
+#include "mt8196-interconnection.h"
+
+enum {
+ UL_IIR_SW,
+ UL_IIR_5HZ,
+ UL_IIR_10HZ,
+ UL_IIR_25HZ,
+ UL_IIR_50HZ,
+ UL_IIR_75HZ,
+};
+
+enum {
+ MTK_AFE_ADDA_UL_RATE_8K,
+ MTK_AFE_ADDA_UL_RATE_16K,
+ MTK_AFE_ADDA_UL_RATE_32K,
+ MTK_AFE_ADDA_UL_RATE_48K,
+ MTK_AFE_ADDA_UL_RATE_96K,
+ MTK_AFE_ADDA_UL_RATE_192K,
+ MTK_AFE_ADDA_UL_RATE_48K_HD,
+};
+
+enum {
+ MTK_AFE_MTKAIF_RATE_8K,
+ MTK_AFE_MTKAIF_RATE_12K,
+ MTK_AFE_MTKAIF_RATE_16K,
+ MTK_AFE_MTKAIF_RATE_24K,
+ MTK_AFE_MTKAIF_RATE_32K,
+ MTK_AFE_MTKAIF_RATE_48K,
+ MTK_AFE_MTKAIF_RATE_64K,
+ MTK_AFE_MTKAIF_RATE_96K,
+ MTK_AFE_MTKAIF_RATE_128K,
+ MTK_AFE_MTKAIF_RATE_192K,
+ MTK_AFE_MTKAIF_RATE_256K,
+ MTK_AFE_MTKAIF_RATE_384K,
+ MTK_AFE_MTKAIF_RATE_11K = 0x10,
+ MTK_AFE_MTKAIF_RATE_22K,
+ MTK_AFE_MTKAIF_RATE_44K,
+ MTK_AFE_MTKAIF_RATE_88K,
+ MTK_AFE_MTKAIF_RATE_176K,
+ MTK_AFE_MTKAIF_RATE_352K,
+};
+
+struct mtk_afe_adda_priv {
+ int dl_rate;
+ int ul_rate;
+};
+
+static unsigned int adda_ul_rate_transform(struct mtk_base_afe *afe,
+ unsigned int rate)
+{
+ switch (rate) {
+ case 8000:
+ return MTK_AFE_ADDA_UL_RATE_8K;
+ case 16000:
+ return MTK_AFE_ADDA_UL_RATE_16K;
+ case 32000:
+ return MTK_AFE_ADDA_UL_RATE_32K;
+ case 48000:
+ return MTK_AFE_ADDA_UL_RATE_48K;
+ case 96000:
+ return MTK_AFE_ADDA_UL_RATE_96K;
+ case 192000:
+ return MTK_AFE_ADDA_UL_RATE_192K;
+ default:
+ dev_warn(afe->dev, "rate %d invalid, use 48kHz!!!\n", rate);
+ return MTK_AFE_ADDA_UL_RATE_48K;
+ }
+}
+
+static unsigned int mtkaif_rate_transform(struct mtk_base_afe *afe,
+ unsigned int rate)
+{
+ switch (rate) {
+ case 8000:
+ return MTK_AFE_MTKAIF_RATE_8K;
+ case 11025:
+ return MTK_AFE_MTKAIF_RATE_11K;
+ case 12000:
+ return MTK_AFE_MTKAIF_RATE_12K;
+ case 16000:
+ return MTK_AFE_MTKAIF_RATE_16K;
+ case 22050:
+ return MTK_AFE_MTKAIF_RATE_22K;
+ case 24000:
+ return MTK_AFE_MTKAIF_RATE_24K;
+ case 32000:
+ return MTK_AFE_MTKAIF_RATE_32K;
+ case 44100:
+ return MTK_AFE_MTKAIF_RATE_44K;
+ case 48000:
+ return MTK_AFE_MTKAIF_RATE_48K;
+ case 96000:
+ return MTK_AFE_MTKAIF_RATE_96K;
+ case 192000:
+ return MTK_AFE_MTKAIF_RATE_192K;
+ default:
+ dev_warn(afe->dev, "rate %d invalid, use 48kHz!!!\n", rate);
+ return MTK_AFE_MTKAIF_RATE_48K;
+ }
+}
+
+enum {
+ SUPPLY_SEQ_ADDA_AFE_ON,
+ SUPPLY_SEQ_ADDA_FIFO,
+ SUPPLY_SEQ_ADDA_AP_DMIC,
+ SUPPLY_SEQ_ADDA_UL_ON,
+};
+
+static int mtk_adda_ul_src_set_dmic_phase_sync(struct mtk_base_afe *afe)
+{
+ dev_dbg(afe->dev, "set dmic phase sync\n");
+ // ul0~1
+ regmap_update_bits(afe->regmap, AFE_ADDA_ULSRC_PHASE_CON1,
+ UL0_PHASE_SYNC_HCLK_SET_MASK_SFT,
+ 0x1 << UL0_PHASE_SYNC_HCLK_SET_SFT);
+ regmap_update_bits(afe->regmap, AFE_ADDA_ULSRC_PHASE_CON1,
+ UL0_PHASE_SYNC_FCLK_SET_MASK_SFT,
+ 0x1 << UL0_PHASE_SYNC_FCLK_SET_SFT);
+ regmap_update_bits(afe->regmap, AFE_ADDA_ULSRC_PHASE_CON1,
+ UL1_PHASE_SYNC_HCLK_SET_MASK_SFT,
+ 0x1 << UL1_PHASE_SYNC_HCLK_SET_SFT);
+ regmap_update_bits(afe->regmap, AFE_ADDA_ULSRC_PHASE_CON1,
+ UL1_PHASE_SYNC_FCLK_SET_MASK_SFT,
+ 0x1 << UL1_PHASE_SYNC_FCLK_SET_SFT);
+ // dmic 0
+ regmap_update_bits(afe->regmap, AFE_ADDA_ULSRC_PHASE_CON1,
+ DMIC0_PHASE_SYNC_FCLK_SET_MASK_SFT,
+ 0x1 << DMIC0_PHASE_SYNC_FCLK_SET_SFT);
+ regmap_update_bits(afe->regmap, AFE_ADDA_ULSRC_PHASE_CON1,
+ DMIC0_PHASE_SYNC_HCLK_SET_MASK_SFT,
+ 0x1 << DMIC0_PHASE_SYNC_HCLK_SET_SFT);
+ // dmic 1
+ regmap_update_bits(afe->regmap, AFE_ADDA_ULSRC_PHASE_CON1,
+ DMIC1_PHASE_SYNC_FCLK_SET_MASK_SFT,
+ 0x1 << DMIC1_PHASE_SYNC_FCLK_SET_SFT);
+ regmap_update_bits(afe->regmap, AFE_ADDA_ULSRC_PHASE_CON1,
+ DMIC1_PHASE_SYNC_HCLK_SET_MASK_SFT,
+ 0x1 << DMIC1_PHASE_SYNC_HCLK_SET_SFT);
+ // ul0~1 phase sync clock
+ regmap_update_bits(afe->regmap, AFE_ADDA_ULSRC_PHASE_CON0,
+ DMIC1_PHASE_HCLK_SEL_MASK_SFT,
+ 0x1 << DMIC1_PHASE_HCLK_SEL_SFT);
+ regmap_update_bits(afe->regmap, AFE_ADDA_ULSRC_PHASE_CON0,
+ DMIC1_PHASE_FCLK_SEL_MASK_SFT,
+ 0x1 << DMIC1_PHASE_FCLK_SEL_SFT);
+ regmap_update_bits(afe->regmap, AFE_ADDA_ULSRC_PHASE_CON0,
+ DMIC0_PHASE_HCLK_SEL_MASK_SFT,
+ 0x1 << DMIC0_PHASE_HCLK_SEL_SFT);
+ regmap_update_bits(afe->regmap, AFE_ADDA_ULSRC_PHASE_CON0,
+ DMIC0_PHASE_FCLK_SEL_MASK_SFT,
+ 0x1 << DMIC0_PHASE_FCLK_SEL_SFT);
+ // dmic 0
+ regmap_update_bits(afe->regmap, AFE_ADDA_ULSRC_PHASE_CON0,
+ UL1_PHASE_HCLK_SEL_MASK_SFT,
+ 0x2 << UL1_PHASE_HCLK_SEL_SFT);
+ regmap_update_bits(afe->regmap, AFE_ADDA_ULSRC_PHASE_CON0,
+ UL1_PHASE_FCLK_SEL_MASK_SFT,
+ 0x2 << UL1_PHASE_FCLK_SEL_SFT);
+ // dmic 1
+ regmap_update_bits(afe->regmap, AFE_ADDA_ULSRC_PHASE_CON0,
+ UL0_PHASE_HCLK_SEL_MASK_SFT,
+ 0x2 << UL0_PHASE_HCLK_SEL_SFT);
+ regmap_update_bits(afe->regmap, AFE_ADDA_ULSRC_PHASE_CON0,
+ UL0_PHASE_FCLK_SEL_MASK_SFT,
+ 0x2 << UL0_PHASE_FCLK_SEL_SFT);
+
+ return 0;
+}
+
+static int mtk_adda_ul_src_set_dmic_phase_sync_clock(struct mtk_base_afe *afe)
+{
+ dev_dbg(afe->dev, "dmic turn on phase sync clk\n");
+ regmap_update_bits(afe->regmap, AFE_ADDA_ULSRC_PHASE_CON0,
+ UL_PHASE_SYNC_HCLK_1_ON_MASK_SFT,
+ 0x1 << UL_PHASE_SYNC_HCLK_1_ON_SFT);
+ regmap_update_bits(afe->regmap, AFE_ADDA_ULSRC_PHASE_CON0,
+ UL_PHASE_SYNC_HCLK_0_ON_MASK_SFT,
+ 0x1 << UL_PHASE_SYNC_HCLK_0_ON_SFT);
+
+ regmap_update_bits(afe->regmap, AFE_ADDA_ULSRC_PHASE_CON0,
+ UL_PHASE_SYNC_FCLK_1_ON_MASK_SFT,
+ 0x1 << UL_PHASE_SYNC_FCLK_1_ON_SFT);
+ regmap_update_bits(afe->regmap, AFE_ADDA_ULSRC_PHASE_CON0,
+ UL_PHASE_SYNC_FCLK_0_ON_MASK_SFT,
+ 0x1 << UL_PHASE_SYNC_FCLK_0_ON_SFT);
+
+ return 0;
+}
+
+static int mtk_adda_ul_src_enable_dmic(struct mtk_base_afe *afe, int id)
+{
+ unsigned int reg_con0 = 0, reg_con1 = 0;
+
+ dev_dbg(afe->dev, "id: %d\n", id);
+
+ switch (id) {
+ case MT8196_DAI_ADDA:
+ case MT8196_DAI_AP_DMIC:
+ reg_con0 = AFE_ADDA_UL0_SRC_CON0;
+ reg_con1 = AFE_ADDA_UL0_SRC_CON1;
+ break;
+ case MT8196_DAI_ADDA_CH34:
+ case MT8196_DAI_AP_DMIC_CH34:
+ reg_con0 = AFE_ADDA_UL1_SRC_CON0;
+ reg_con1 = AFE_ADDA_UL1_SRC_CON1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (id) {
+ case MT8196_DAI_AP_DMIC:
+ dev_dbg(afe->dev, "clear mtkaifv4 ul ch1ch2 mux\n");
+ regmap_update_bits(afe->regmap, AFE_ADDA_MTKAIFV4_RX_CFG0,
+ MTKAIFV4_UL_CH1CH2_IN_EN_SEL_MASK_SFT,
+ 0x0 << MTKAIFV4_UL_CH1CH2_IN_EN_SEL_SFT);
+ break;
+ case MT8196_DAI_AP_DMIC_CH34:
+ dev_dbg(afe->dev, "clear mtkaifv4 ul ch3ch4 mux\n");
+ regmap_update_bits(afe->regmap, AFE_ADDA_MTKAIFV4_RX_CFG0,
+ MTKAIFV4_UL_CH3CH4_IN_EN_SEL_MASK_SFT,
+ 0x0 << MTKAIFV4_UL_CH3CH4_IN_EN_SEL_SFT);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* choose Phase */
+ regmap_update_bits(afe->regmap, reg_con0,
+ UL_DMIC_PHASE_SEL_CH1_MASK_SFT,
+ 0x0 << UL_DMIC_PHASE_SEL_CH1_SFT);
+ regmap_update_bits(afe->regmap, reg_con0,
+ UL_DMIC_PHASE_SEL_CH2_MASK_SFT,
+ 0x4 << UL_DMIC_PHASE_SEL_CH2_SFT);
+
+ /* dmic mode, 3.25M*/
+ regmap_update_bits(afe->regmap, reg_con0,
+ DIGMIC_3P25M_1P625M_SEL_CTL_MASK_SFT,
+ 0x0);
+ regmap_update_bits(afe->regmap, reg_con0,
+ DMIC_LOW_POWER_MODE_CTL_MASK_SFT,
+ 0x0);
+
+ /* turn on dmic, ch1, ch2 */
+ regmap_update_bits(afe->regmap, reg_con0,
+ UL_SDM_3_LEVEL_CTL_MASK_SFT,
+ 0x1 << UL_SDM_3_LEVEL_CTL_SFT);
+ regmap_update_bits(afe->regmap, reg_con0,
+ UL_MODE_3P25M_CH1_CTL_MASK_SFT,
+ 0x1 << UL_MODE_3P25M_CH1_CTL_SFT);
+ regmap_update_bits(afe->regmap, reg_con0,
+ UL_MODE_3P25M_CH2_CTL_MASK_SFT,
+ 0x1 << UL_MODE_3P25M_CH2_CTL_SFT);
+
+ /* ul gain: gain = 0x7fff/positive_gain = 0x0/gain_mode = 0x10 */
+ regmap_update_bits(afe->regmap, reg_con1,
+ ADDA_UL_GAIN_VALUE_MASK_SFT,
+ 0x7fff << ADDA_UL_GAIN_VALUE_SFT);
+ regmap_update_bits(afe->regmap, reg_con1,
+ ADDA_UL_POSTIVEGAIN_MASK_SFT,
+ 0x0 << ADDA_UL_POSTIVEGAIN_SFT);
+ /* gain_mode = 0x10: Add 0.5 gain at CIC output */
+ regmap_update_bits(afe->regmap, reg_con1,
+ GAIN_MODE_MASK_SFT,
+ 0x02 << GAIN_MODE_SFT);
+ return 0;
+}
+
+static int mtk_adda_sleep_on_pmd_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm);
+ struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt);
+
+ dev_dbg(afe->dev, "name %s, event 0x%x\n", w->name, event);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ /* should delayed 1/fs(smallest is 8k) = 125us before afe off */
+ usleep_range(120, 130);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/* ADDA UL MUX */
+#define ADDA_UL_MUX_MASK 0x3
+enum {
+ ADDA_UL_MUX_MTKAIF = 0,
+ ADDA_UL_MUX_AP_DMIC,
+ ADDA_UL_MUX_AP_DMIC_MULTICH,
+};
+
+static const char *const adda_ul_mux_map[] = {
+ "MTKAIF", "AP_DMIC", "AP_DMIC_MULTI_CH",
+};
+
+static int adda_ul_map_value[] = {
+ ADDA_UL_MUX_MTKAIF,
+ ADDA_UL_MUX_AP_DMIC,
+ ADDA_UL_MUX_AP_DMIC_MULTICH,
+};
+
+static SOC_VALUE_ENUM_SINGLE_DECL(adda_ul_mux_map_enum,
+ SND_SOC_NOPM,
+ 0,
+ ADDA_UL_MUX_MASK,
+ adda_ul_mux_map,
+ adda_ul_map_value);
+
+static const struct snd_kcontrol_new adda_ul_mux_control =
+ SOC_DAPM_ENUM("ADDA_UL_MUX Select", adda_ul_mux_map_enum);
+
+static const struct snd_kcontrol_new adda_ch34_ul_mux_control =
+ SOC_DAPM_ENUM("ADDA_CH34_UL_MUX Select", adda_ul_mux_map_enum);
+
+static const struct snd_soc_dapm_widget mtk_dai_adda_widgets[] = {
+ /* inter-connections */
+ SND_SOC_DAPM_SUPPLY_S("ADDA Enable", SUPPLY_SEQ_ADDA_AFE_ON,
+ AUDIO_ENGEN_CON0, AUDIO_F3P25M_EN_ON_SFT, 0,
+ NULL, 0),
+ SND_SOC_DAPM_SUPPLY_S("ADDA Capture Enable", SUPPLY_SEQ_ADDA_UL_ON,
+ AFE_ADDA_UL0_SRC_CON0,
+ UL_SRC_ON_TMP_CTL_SFT, 0,
+ mtk_adda_sleep_on_pmd_event,
+ SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_SUPPLY_S("ADDA CH34 Capture Enable", SUPPLY_SEQ_ADDA_UL_ON,
+ AFE_ADDA_UL1_SRC_CON0,
+ UL_SRC_ON_TMP_CTL_SFT, 0,
+ mtk_adda_sleep_on_pmd_event,
+ SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_SUPPLY_S("AP_DMIC_EN", SUPPLY_SEQ_ADDA_AP_DMIC,
+ AFE_ADDA_UL0_SRC_CON0,
+ UL_AP_DMIC_ON_SFT, 0,
+ mtk_adda_sleep_on_pmd_event,
+ SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_SUPPLY_S("AP_DMIC_CH34_EN", SUPPLY_SEQ_ADDA_AP_DMIC,
+ AFE_ADDA_UL1_SRC_CON0,
+ UL_AP_DMIC_ON_SFT, 0,
+ mtk_adda_sleep_on_pmd_event,
+ SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_SUPPLY_S("ADDA_FIFO", SUPPLY_SEQ_ADDA_FIFO,
+ AFE_ADDA_UL0_SRC_CON1,
+ FIFO_SOFT_RST_SFT, 1,
+ NULL, 0),
+ SND_SOC_DAPM_SUPPLY_S("ADDA_CH34_FIFO", SUPPLY_SEQ_ADDA_FIFO,
+ AFE_ADDA_UL1_SRC_CON1,
+ FIFO_SOFT_RST_SFT, 1,
+ NULL, 0),
+ SND_SOC_DAPM_MUX("ADDA_UL_Mux", SND_SOC_NOPM, 0, 0,
+ &adda_ul_mux_control),
+ SND_SOC_DAPM_MUX("ADDA_CH34_UL_Mux", SND_SOC_NOPM, 0, 0,
+ &adda_ch34_ul_mux_control),
+ SND_SOC_DAPM_INPUT("AP_DMIC_INPUT"),
+};
+
+static const struct snd_soc_dapm_route mtk_dai_adda_routes[] = {
+ /* capture */
+ {"ADDA_UL_Mux", "MTKAIF", "ADDA Capture"},
+ {"ADDA_UL_Mux", "AP_DMIC", "AP DMIC Capture"},
+ {"ADDA_UL_Mux", "AP_DMIC_MULTI_CH", "AP DMIC MULTICH Capture"},
+
+ {"ADDA_CH34_UL_Mux", "MTKAIF", "ADDA CH34 Capture"},
+ {"ADDA_CH34_UL_Mux", "AP_DMIC", "AP DMIC CH34 Capture"},
+ {"ADDA_CH34_UL_Mux", "AP_DMIC_MULTI_CH", "AP DMIC MULTICH Capture"},
+
+ {"AP DMIC Capture", NULL, "ADDA Enable"},
+ {"AP DMIC Capture", NULL, "ADDA Capture Enable"},
+ {"AP DMIC Capture", NULL, "ADDA_FIFO"},
+ {"AP DMIC Capture", NULL, "AP_DMIC_EN"},
+
+ {"AP DMIC CH34 Capture", NULL, "ADDA Enable"},
+ {"AP DMIC CH34 Capture", NULL, "ADDA CH34 Capture Enable"},
+ {"AP DMIC CH34 Capture", NULL, "ADDA_CH34_FIFO"},
+ {"AP DMIC CH34 Capture", NULL, "AP_DMIC_CH34_EN"},
+
+ {"AP DMIC MULTICH Capture", NULL, "ADDA Enable"},
+ {"AP DMIC MULTICH Capture", NULL, "ADDA Capture Enable"},
+ {"AP DMIC MULTICH Capture", NULL, "ADDA CH34 Capture Enable"},
+ {"AP DMIC MULTICH Capture", NULL, "ADDA_FIFO"},
+ {"AP DMIC MULTICH Capture", NULL, "ADDA_CH34_FIFO"},
+ {"AP DMIC MULTICH Capture", NULL, "AP_DMIC_EN"},
+ {"AP DMIC MULTICH Capture", NULL, "AP_DMIC_CH34_EN"},
+
+ {"AP DMIC Capture", NULL, "AP_DMIC_INPUT"},
+ {"AP DMIC CH34 Capture", NULL, "AP_DMIC_INPUT"},
+ {"AP DMIC MULTICH Capture", NULL, "AP_DMIC_INPUT"},
+};
+
+/* dai ops */
+static int set_playback_hw_params(struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
+ struct mt8196_afe_private *afe_priv = afe->platform_priv;
+ unsigned int rate = params_rate(params);
+ struct mtk_afe_adda_priv *adda_priv;
+ unsigned int mtkaif_rate = 0;
+ int id = dai->id;
+
+ adda_priv = afe_priv->dai_priv[id];
+ if (!adda_priv)
+ return -EINVAL;
+
+ adda_priv->dl_rate = rate;
+
+ /* get mtkaif dl rate */
+ mtkaif_rate = mtkaif_rate_transform(afe, adda_priv->dl_rate);
+
+ if (id == MT8196_DAI_ADDA) {
+ /* MTKAIF sample rate config */
+ regmap_update_bits(afe->regmap, AFE_ADDA_MTKAIFV4_TX_CFG0,
+ MTKAIFV4_TXIF_INPUT_MODE_MASK_SFT,
+ mtkaif_rate << MTKAIFV4_TXIF_INPUT_MODE_SFT);
+ /* AFE_ADDA_MTKAIFV4_TX_CFG0 */
+ regmap_update_bits(afe->regmap, AFE_ADDA_MTKAIFV4_TX_CFG0,
+ MTKAIFV4_TXIF_FOUR_CHANNEL_MASK_SFT,
+ 0x0 << MTKAIFV4_TXIF_FOUR_CHANNEL_SFT);
+ regmap_update_bits(afe->regmap, AFE_ADDA_MTKAIFV4_TX_CFG0,
+ MTKAIFV4_ADDA_OUT_EN_SEL_MASK_SFT,
+ 0x1 << MTKAIFV4_ADDA_OUT_EN_SEL_SFT);
+ regmap_update_bits(afe->regmap, AFE_ADDA_MTKAIFV4_TX_CFG0,
+ MTKAIFV4_ADDA6_OUT_EN_SEL_MASK_SFT,
+ 0x1 << MTKAIFV4_ADDA6_OUT_EN_SEL_SFT);
+ regmap_update_bits(afe->regmap, AFE_ADDA_MTKAIFV4_TX_CFG0,
+ MTKAIFV4_TXIF_V4_MASK_SFT,
+ 0x1 << MTKAIFV4_TXIF_V4_SFT);
+ regmap_update_bits(afe->regmap, AFE_ADDA_MTKAIFV4_TX_CFG0,
+ MTKAIFV4_TXIF_EN_SEL_MASK_SFT,
+ 0x0 << MTKAIFV4_TXIF_EN_SEL_SFT);
+ /* clean predistortion */
+ } else {
+ /* MTKAIF sample rate config */
+ regmap_update_bits(afe->regmap, AFE_ADDA6_MTKAIFV4_TX_CFG0,
+ ADDA6_MTKAIFV4_TXIF_INPUT_MODE_MASK_SFT,
+ mtkaif_rate << ADDA6_MTKAIFV4_TXIF_INPUT_MODE_SFT);
+ /* AFE_ADDA6_MTKAIFV4_TX_CFG0 */
+ regmap_update_bits(afe->regmap, AFE_ADDA6_MTKAIFV4_TX_CFG0,
+ ADDA6_MTKAIFV4_TXIF_FOUR_CHANNEL_MASK_SFT,
+ 0x0 << ADDA6_MTKAIFV4_TXIF_FOUR_CHANNEL_SFT);
+ regmap_update_bits(afe->regmap, AFE_ADDA6_MTKAIFV4_TX_CFG0,
+ ADDA6_MTKAIFV4_TXIF_EN_SEL_MASK_SFT,
+ 0x1 << ADDA6_MTKAIFV4_TXIF_EN_SEL_SFT);
+ }
+
+ return 0;
+};
+
+static int set_capture_hw_params(struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
+ struct mt8196_afe_private *afe_priv = afe->platform_priv;
+ unsigned int rate = params_rate(params);
+ struct mtk_afe_adda_priv *adda_priv;
+ unsigned int voice_mode = 0;
+ unsigned int ul_src_con0 = 0;
+ unsigned int mtkaif_rate = 0;
+ int id = dai->id;
+
+ adda_priv = afe_priv->dai_priv[id];
+ if (!adda_priv)
+ return -EINVAL;
+
+ adda_priv->ul_rate = rate;
+
+ /* get mtkaif dl rate */
+ mtkaif_rate = mtkaif_rate_transform(afe, adda_priv->ul_rate);
+
+ voice_mode = adda_ul_rate_transform(afe, rate);
+
+ ul_src_con0 |= (voice_mode << 17) & (0x7 << 17);
+
+ /* enable iir */
+ ul_src_con0 |= (1 << UL_IIR_ON_TMP_CTL_SFT) &
+ UL_IIR_ON_TMP_CTL_MASK_SFT;
+ ul_src_con0 |= (UL_IIR_SW << UL_IIRMODE_CTL_SFT) &
+ UL_IIRMODE_CTL_MASK_SFT;
+
+ regmap_update_bits(afe->regmap, AFE_ADDA_MTKAIFV4_RX_CFG0,
+ MTKAIFV4_RXIF_INPUT_MODE_MASK_SFT,
+ mtkaif_rate << MTKAIFV4_RXIF_INPUT_MODE_SFT);
+
+ regmap_update_bits(afe->regmap, AFE_ADDA6_MTKAIFV4_RX_CFG0,
+ ADDA6_MTKAIFV4_RXIF_INPUT_MODE_MASK_SFT,
+ mtkaif_rate << ADDA6_MTKAIFV4_RXIF_INPUT_MODE_SFT);
+
+ switch (id) {
+ case MT8196_DAI_ADDA:
+ case MT8196_DAI_AP_DMIC:
+ case MT8196_DAI_AP_DMIC_MULTICH:
+ regmap_update_bits(afe->regmap, AFE_ADDA_MTKAIFV4_RX_CFG0,
+ MTKAIFV4_RXIF_INPUT_MODE_MASK_SFT,
+ mtkaif_rate << MTKAIFV4_RXIF_INPUT_MODE_SFT);
+ /* AFE_ADDA_MTKAIFV4_RX_CFG0 */
+ regmap_update_bits(afe->regmap, AFE_ADDA_MTKAIFV4_RX_CFG0,
+ MTKAIFV4_RXIF_FOUR_CHANNEL_MASK_SFT,
+ 0x1 << MTKAIFV4_RXIF_FOUR_CHANNEL_SFT);
+ regmap_update_bits(afe->regmap, AFE_ADDA_MTKAIFV4_RX_CFG0,
+ MTKAIFV4_RXIF_EN_SEL_MASK_SFT,
+ 0x0 << MTKAIFV4_RXIF_EN_SEL_SFT);
+ /* [28] loopback mode
+ * 0: loopback adda tx to adda rx
+ * 1: loopback adda6 tx to adda rx
+ */
+ regmap_update_bits(afe->regmap, AFE_ADDA_MTKAIFV4_RX_CFG0,
+ MTKAIFV4_TXIF_EN_SEL_MASK_SFT,
+ 0x0 << MTKAIFV4_TXIF_EN_SEL_SFT);
+
+ regmap_update_bits(afe->regmap, AFE_ADDA_MTKAIFV4_RX_CFG0,
+ MTKAIFV4_UL_CH1CH2_IN_EN_SEL_MASK_SFT,
+ 0x1 << MTKAIFV4_UL_CH1CH2_IN_EN_SEL_SFT);
+ regmap_update_bits(afe->regmap, AFE_ADDA_MTKAIFV4_RX_CFG0,
+ MTKAIFV4_UL_CH3CH4_IN_EN_SEL_MASK_SFT,
+ 0x1 << MTKAIFV4_UL_CH3CH4_IN_EN_SEL_SFT);
+
+ /* 35Hz @ 48k */
+ regmap_write(afe->regmap,
+ AFE_ADDA_UL1_IIR_COEF_02_01, 0x00000000);
+ regmap_write(afe->regmap,
+ AFE_ADDA_UL1_IIR_COEF_04_03, 0x00003FB8);
+ regmap_write(afe->regmap,
+ AFE_ADDA_UL1_IIR_COEF_06_05, 0x3FB80000);
+ regmap_write(afe->regmap,
+ AFE_ADDA_UL1_IIR_COEF_08_07, 0x3FB80000);
+ regmap_write(afe->regmap,
+ AFE_ADDA_UL1_IIR_COEF_10_09, 0x0000C048);
+
+ regmap_write(afe->regmap,
+ AFE_ADDA_UL1_SRC_CON0, ul_src_con0);
+
+ /* mtkaif_rxif_data_mode = 0, amic */
+ regmap_update_bits(afe->regmap,
+ AFE_MTKAIF1_RX_CFG0,
+ 0x1 << 0,
+ 0x0 << 0);
+
+ /* 35Hz @ 48k */
+ regmap_write(afe->regmap,
+ AFE_ADDA_UL0_IIR_COEF_02_01, 0x00000000);
+ regmap_write(afe->regmap,
+ AFE_ADDA_UL0_IIR_COEF_04_03, 0x00003FB8);
+ regmap_write(afe->regmap,
+ AFE_ADDA_UL0_IIR_COEF_06_05, 0x3FB80000);
+ regmap_write(afe->regmap,
+ AFE_ADDA_UL0_IIR_COEF_08_07, 0x3FB80000);
+ regmap_write(afe->regmap,
+ AFE_ADDA_UL0_IIR_COEF_10_09, 0x0000C048);
+
+ regmap_write(afe->regmap,
+ AFE_ADDA_UL0_SRC_CON0, ul_src_con0);
+
+ /* mtkaif_rxif_data_mode = 0, amic */
+ regmap_update_bits(afe->regmap,
+ AFE_MTKAIF0_RX_CFG0,
+ 0x1 << 0,
+ 0x0 << 0);
+ break;
+ case MT8196_DAI_ADDA_CH34:
+ case MT8196_DAI_AP_DMIC_CH34:
+ /* AFE_ADDA_MTKAIFV4_RX_CFG0 */
+ regmap_update_bits(afe->regmap, AFE_ADDA_MTKAIFV4_RX_CFG0,
+ MTKAIFV4_RXIF_FOUR_CHANNEL_MASK_SFT,
+ 0x1 << MTKAIFV4_RXIF_FOUR_CHANNEL_SFT);
+ regmap_update_bits(afe->regmap, AFE_ADDA_MTKAIFV4_RX_CFG0,
+ MTKAIFV4_RXIF_EN_SEL_MASK_SFT,
+ 0x0 << MTKAIFV4_RXIF_EN_SEL_SFT);
+
+ regmap_update_bits(afe->regmap, AFE_ADDA_MTKAIFV4_RX_CFG0,
+ MTKAIFV4_UL_CH1CH2_IN_EN_SEL_MASK_SFT,
+ 0x1 << MTKAIFV4_UL_CH1CH2_IN_EN_SEL_SFT);
+ regmap_update_bits(afe->regmap, AFE_ADDA_MTKAIFV4_RX_CFG0,
+ MTKAIFV4_UL_CH3CH4_IN_EN_SEL_MASK_SFT,
+ 0x1 << MTKAIFV4_UL_CH3CH4_IN_EN_SEL_SFT);
+
+ /* 35Hz @ 48k */
+ regmap_write(afe->regmap,
+ AFE_ADDA_UL1_IIR_COEF_02_01, 0x00000000);
+ regmap_write(afe->regmap,
+ AFE_ADDA_UL1_IIR_COEF_04_03, 0x00003FB8);
+ regmap_write(afe->regmap,
+ AFE_ADDA_UL1_IIR_COEF_06_05, 0x3FB80000);
+ regmap_write(afe->regmap,
+ AFE_ADDA_UL1_IIR_COEF_08_07, 0x3FB80000);
+ regmap_write(afe->regmap,
+ AFE_ADDA_UL1_IIR_COEF_10_09, 0x0000C048);
+
+ regmap_write(afe->regmap,
+ AFE_ADDA_UL1_SRC_CON0, ul_src_con0);
+
+ /* mtkaif_rxif_data_mode = 0, amic */
+ regmap_update_bits(afe->regmap,
+ AFE_MTKAIF1_RX_CFG0,
+ 0x1 << 0,
+ 0x0 << 0);
+ break;
+ case MT8196_DAI_ADDA_CH56:
+ regmap_update_bits(afe->regmap, AFE_ADDA6_MTKAIFV4_RX_CFG0,
+ ADDA6_MTKAIFV4_RXIF_INPUT_MODE_MASK_SFT,
+ mtkaif_rate << ADDA6_MTKAIFV4_RXIF_INPUT_MODE_SFT);
+ /* AFE_ADDA6_MTKAIFV4_RX_CFG0 */
+ regmap_update_bits(afe->regmap, AFE_ADDA6_MTKAIFV4_RX_CFG0,
+ ADDA6_MTKAIFV4_RXIF_FOUR_CHANNEL_MASK_SFT,
+ 0x1 << ADDA6_MTKAIFV4_RXIF_FOUR_CHANNEL_SFT);
+ regmap_update_bits(afe->regmap, AFE_ADDA_MTKAIFV4_RX_CFG0,
+ MTKAIFV4_UL_CH5CH6_IN_EN_SEL_MASK_SFT,
+ 0x1 << MTKAIFV4_UL_CH5CH6_IN_EN_SEL_SFT);
+ regmap_update_bits(afe->regmap, AFE_ADDA6_MTKAIFV4_RX_CFG0,
+ ADDA6_MTKAIFV4_RXIF_EN_SEL_MASK_SFT,
+ 0x1 << ADDA6_MTKAIFV4_RXIF_EN_SEL_SFT);
+ break;
+ default:
+ break;
+ }
+
+ /* ap dmic */
+ switch (id) {
+ case MT8196_DAI_AP_DMIC:
+ case MT8196_DAI_AP_DMIC_CH34:
+ mtk_adda_ul_src_enable_dmic(afe, id);
+ break;
+ case MT8196_DAI_AP_DMIC_MULTICH:
+ regmap_update_bits(afe->regmap, AFE_ADDA_ULSRC_PHASE_CON1,
+ DMIC_CLK_PHASE_SYNC_SET_MASK_SFT,
+ 0x1 << DMIC_CLK_PHASE_SYNC_SET_SFT);
+ mtk_adda_ul_src_set_dmic_phase_sync(afe);
+ mtk_adda_ul_src_enable_dmic(afe, MT8196_DAI_AP_DMIC);
+ mtk_adda_ul_src_enable_dmic(afe, MT8196_DAI_AP_DMIC_CH34);
+ mtk_adda_ul_src_set_dmic_phase_sync_clock(afe);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+};
+
+static int mtk_dai_adda_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
+ int id = dai->id;
+
+ if (id >= MT8196_DAI_NUM || id < 0)
+ return -EINVAL;
+
+ dev_dbg(afe->dev, "id %d, stream %d, rate %d\n",
+ id, substream->stream, params_rate(params));
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ return set_playback_hw_params(params, dai);
+ else
+ return set_capture_hw_params(params, dai);
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops mtk_dai_adda_ops = {
+ .hw_params = mtk_dai_adda_hw_params,
+};
+
+/* dai driver */
+#define MTK_ADDA_PLAYBACK_RATES (SNDRV_PCM_RATE_8000_48000 |\
+ SNDRV_PCM_RATE_96000 |\
+ SNDRV_PCM_RATE_192000)
+
+#define MTK_ADDA_CAPTURE_RATES (SNDRV_PCM_RATE_8000 |\
+ SNDRV_PCM_RATE_16000 |\
+ SNDRV_PCM_RATE_32000 |\
+ SNDRV_PCM_RATE_48000 |\
+ SNDRV_PCM_RATE_96000 |\
+ SNDRV_PCM_RATE_192000)
+
+#define MTK_ADDA_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
+ SNDRV_PCM_FMTBIT_S24_LE |\
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_driver mtk_dai_adda_driver[] = {
+ {
+ .name = "ADDA",
+ .id = MT8196_DAI_ADDA,
+ .playback = {
+ .stream_name = "ADDA Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = MTK_ADDA_PLAYBACK_RATES,
+ .formats = MTK_ADDA_FORMATS,
+ },
+ .capture = {
+ .stream_name = "ADDA Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = MTK_ADDA_CAPTURE_RATES,
+ .formats = MTK_ADDA_FORMATS,
+ },
+ .ops = &mtk_dai_adda_ops,
+ },
+ {
+ .name = "ADDA_CH34",
+ .id = MT8196_DAI_ADDA_CH34,
+ .playback = {
+ .stream_name = "ADDA CH34 Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = MTK_ADDA_PLAYBACK_RATES,
+ .formats = MTK_ADDA_FORMATS,
+ },
+ .capture = {
+ .stream_name = "ADDA CH34 Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = MTK_ADDA_CAPTURE_RATES,
+ .formats = MTK_ADDA_FORMATS,
+ },
+ .ops = &mtk_dai_adda_ops,
+ },
+ {
+ .name = "ADDA_CH56",
+ .id = MT8196_DAI_ADDA_CH56,
+ .capture = {
+ .stream_name = "ADDA CH56 Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = MTK_ADDA_CAPTURE_RATES,
+ .formats = MTK_ADDA_FORMATS,
+ },
+ .ops = &mtk_dai_adda_ops,
+ },
+ {
+ .name = "AP_DMIC",
+ .id = MT8196_DAI_AP_DMIC,
+ .capture = {
+ .stream_name = "AP DMIC Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = MTK_ADDA_CAPTURE_RATES,
+ .formats = MTK_ADDA_FORMATS,
+ },
+ .ops = &mtk_dai_adda_ops,
+ },
+ {
+ .name = "AP_DMIC_CH34",
+ .id = MT8196_DAI_AP_DMIC_CH34,
+ .capture = {
+ .stream_name = "AP DMIC CH34 Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = MTK_ADDA_CAPTURE_RATES,
+ .formats = MTK_ADDA_FORMATS,
+ },
+ .ops = &mtk_dai_adda_ops,
+ },
+ /*
+ * Multich DMIC combines two DMIC controllers for use together,
+ * so AP_DMIC and Multich DMIC cannot be used at the same time.
+ */
+ {
+ .name = "AP_DMIC_MULTICH",
+ .id = MT8196_DAI_AP_DMIC_MULTICH,
+ .capture = {
+ .stream_name = "AP DMIC MULTICH Capture",
+ .channels_min = 1,
+ .channels_max = 4,
+ .rates = MTK_ADDA_CAPTURE_RATES,
+ .formats = MTK_ADDA_FORMATS,
+ },
+ .ops = &mtk_dai_adda_ops,
+ },
+};
+
+static int init_adda_priv_data(struct mtk_base_afe *afe)
+{
+ struct mt8196_afe_private *afe_priv = afe->platform_priv;
+ struct mtk_afe_adda_priv *adda_priv;
+ static const int adda_dai_list[] = {
+ MT8196_DAI_ADDA,
+ MT8196_DAI_ADDA_CH34,
+ MT8196_DAI_ADDA_CH56,
+ MT8196_DAI_AP_DMIC_MULTICH
+ };
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(adda_dai_list); i++) {
+ adda_priv = devm_kzalloc(afe->dev,
+ sizeof(struct mtk_afe_adda_priv),
+ GFP_KERNEL);
+ if (!adda_priv)
+ return -ENOMEM;
+
+ afe_priv->dai_priv[adda_dai_list[i]] = adda_priv;
+ }
+
+ /* ap dmic priv share with adda */
+ afe_priv->dai_priv[MT8196_DAI_AP_DMIC] =
+ afe_priv->dai_priv[MT8196_DAI_ADDA];
+ afe_priv->dai_priv[MT8196_DAI_AP_DMIC_CH34] =
+ afe_priv->dai_priv[MT8196_DAI_ADDA_CH34];
+
+ return 0;
+}
+
+int mt8196_dai_adda_register(struct mtk_base_afe *afe)
+{
+ struct mtk_base_afe_dai *dai;
+ int ret;
+
+ dai = devm_kzalloc(afe->dev, sizeof(*dai), GFP_KERNEL);
+ if (!dai)
+ return -ENOMEM;
+
+ 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);
+
+ ret = init_adda_priv_data(afe);
+ if (ret)
+ return ret;
+
+ list_add(&dai->list, &afe->sub_dais);
+
+ return 0;
+}
+
--
2.45.2
^ permalink raw reply related
* [PATCH v9 10/10] ASoC: mediatek: mt8196: add machine driver with nau8825
From: Cyril Chao @ 2026-04-30 2:23 UTC (permalink / raw)
To: Liam Girdwood, Mark Brown, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Matthias Brugger, AngeloGioacchino Del Regno,
Jaroslav Kysela, Takashi Iwai
Cc: linux-sound, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek, Cyril Chao, Project_Global_Chrome_Upstream_Group,
Darren Ye, Cyril Chao
In-Reply-To: <20260430022417.32282-1-Cyril.Chao@mediatek.com>
From: Darren Ye <darren.ye@mediatek.com>
Add support for mt8196 board with nau8825.
Signed-off-by: Darren Ye <darren.ye@mediatek.com>
Signed-off-by: Cyril Chao <Cyril.Chao@mediatek.com>
---
sound/soc/mediatek/Kconfig | 20 +
sound/soc/mediatek/mt8196/Makefile | 2 +
sound/soc/mediatek/mt8196/mt8196-nau8825.c | 870 +++++++++++++++++++++
3 files changed, 892 insertions(+)
create mode 100644 sound/soc/mediatek/mt8196/mt8196-nau8825.c
diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig
index dc41e5860eb0..202fe637aaf9 100644
--- a/sound/soc/mediatek/Kconfig
+++ b/sound/soc/mediatek/Kconfig
@@ -363,4 +363,24 @@ config SND_SOC_MT8196
Select Y if you have such device.
If unsure select "N".
+config SND_SOC_MT8196_NAU8825
+ tristate "ASoc Audio driver for MT8196 with NAU8825 and I2S codec"
+ depends on SND_SOC_MT8196
+ depends on I2C
+ select SND_SOC_HDMI_CODEC
+ select SND_SOC_DMIC
+ select SND_SOC_NAU8315
+ select SND_SOC_NAU8825
+ select SND_SOC_RT5645
+ select SND_SOC_RT5682_I2C
+ select SND_SOC_RT5682S
+ select SND_SOC_TAS2781_COMLIB
+ select SND_SOC_TAS2781_FMWLIB
+ select SND_SOC_TAS2781_I2C
+ help
+ This adds support for ASoC machine driver for MediaTek MT8196
+ boards with the NAU8825 and other I2S audio codecs.
+ Select Y if you have such device.
+ If unsure select "N".
+
endmenu
diff --git a/sound/soc/mediatek/mt8196/Makefile b/sound/soc/mediatek/mt8196/Makefile
index 0c6a018190a2..91de200071d7 100644
--- a/sound/soc/mediatek/mt8196/Makefile
+++ b/sound/soc/mediatek/mt8196/Makefile
@@ -10,3 +10,5 @@ snd-soc-mt8196-afe-objs += \
obj-$(CONFIG_SND_SOC_MT8196) += snd-soc-mt8196-afe.o
+# machine driver
+obj-$(CONFIG_SND_SOC_MT8196_NAU8825) += mt8196-nau8825.o
diff --git a/sound/soc/mediatek/mt8196/mt8196-nau8825.c b/sound/soc/mediatek/mt8196/mt8196-nau8825.c
new file mode 100644
index 000000000000..c9424786c53d
--- /dev/null
+++ b/sound/soc/mediatek/mt8196/mt8196-nau8825.c
@@ -0,0 +1,870 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * mt8196-nau8825.c -- mt8196 nau8825 ALSA SoC machine driver
+ *
+ * Copyright (c) 2025 MediaTek Inc.
+ * Author: Darren Ye <darren.ye@mediatek.com>
+ */
+
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/pcm_params.h>
+
+#include "mt8196-afe-common.h"
+
+#include "../../codecs/nau8825.h"
+#include "../../codecs/rt5682s.h"
+
+#include "../common/mtk-soc-card.h"
+#include "../common/mtk-dsp-sof-common.h"
+#include "../common/mtk-soundcard-driver.h"
+#include "../common/mtk-afe-platform-driver.h"
+
+#define NAU8825_HS_PRESENT BIT(0)
+#define RT5682S_HS_PRESENT BIT(1)
+#define RT5650_HS_PRESENT BIT(2)
+
+/*
+ * Nau88l25
+ */
+#define NAU8825_CODEC_DAI "nau8825-hifi"
+
+/*
+ * Rt5682s
+ */
+#define RT5682S_CODEC_DAI "rt5682s-aif1"
+
+/*
+ * Rt5650
+ */
+#define RT5650_CODEC_DAI "rt5645-aif1"
+
+#define SOF_DMA_DL1 "SOF_DMA_DL1"
+#define SOF_DMA_DL_24CH "SOF_DMA_DL_24CH"
+#define SOF_DMA_UL0 "SOF_DMA_UL0"
+#define SOF_DMA_UL1 "SOF_DMA_UL1"
+#define SOF_DMA_UL2 "SOF_DMA_UL2"
+
+enum mt8196_jacks {
+ MT8196_JACK_HEADSET,
+ MT8196_JACK_DP,
+ MT8196_JACK_HDMI,
+ MT8196_JACK_MAX,
+};
+
+static struct snd_soc_jack_pin mt8196_dp_jack_pins[] = {
+ {
+ .pin = "DP",
+ .mask = SND_JACK_AVOUT,
+ },
+};
+
+static struct snd_soc_jack_pin mt8196_hdmi_jack_pins[] = {
+ {
+ .pin = "HDMI",
+ .mask = SND_JACK_AVOUT,
+ },
+};
+
+static struct snd_soc_jack_pin nau8825_jack_pins[] = {
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+static const struct snd_kcontrol_new mt8196_dumb_spk_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Ext Spk"),
+};
+
+static const struct snd_soc_dapm_widget mt8196_dumb_spk_widgets[] = {
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+};
+
+static const struct snd_soc_dapm_widget mt8196_nau8825_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+ SND_SOC_DAPM_SINK("DP"),
+};
+
+static const struct snd_kcontrol_new mt8196_nau8825_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+};
+
+#define EXT_SPK_AMP_W_NAME "Ext_Speaker_Amp"
+
+static struct snd_soc_card mt8196_nau8825_soc_card;
+
+static const struct snd_soc_dapm_widget mt8196_nau8825_card_widgets[] = {
+ /* SOF Uplink */
+ SND_SOC_DAPM_MIXER("SOF_DMA_UL0", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("SOF_DMA_UL1", SND_SOC_NOPM, 0, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("SOF_DMA_UL2", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ /*
+ * SOF Downlink
+ * the widgets on the machine driver cannot use the parameter with kcontrol
+ * because the widget domain is its platform driver. so sof downlink route
+ * is written in the i2s dai driver.
+ */
+};
+
+static const struct snd_soc_dapm_route mt8196_nau8825_card_routes[] = {
+ /* SOF Uplink */
+ {"SOF_DMA_UL0", NULL, "UL0_CH1"},
+ {"SOF_DMA_UL0", NULL, "UL0_CH2"},
+ /* SOF Uplink */
+ {"SOF_DMA_UL1", NULL, "UL1_CH1"},
+ {"SOF_DMA_UL1", NULL, "UL1_CH2"},
+ /* SOF Uplink */
+ {"SOF_DMA_UL2", NULL, "UL2_CH1"},
+ {"SOF_DMA_UL2", NULL, "UL2_CH2"},
+};
+
+static const struct snd_kcontrol_new mt8196_nau8825_card_controls[] = {
+ SOC_DAPM_PIN_SWITCH(EXT_SPK_AMP_W_NAME),
+};
+
+/*
+ * define mtk_spk_i2s_mck node in dts when need mclk,
+ * BE i2s need assign snd_soc_ops = mt8196_nau8825_i2s_ops
+ */
+static int mt8196_nau8825_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ unsigned int rate = params_rate(params);
+ unsigned int mclk_fs_ratio = 128;
+ unsigned int mclk_fs = rate * mclk_fs_ratio;
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+ return snd_soc_dai_set_sysclk(cpu_dai,
+ 0, mclk_fs, SND_SOC_CLOCK_OUT);
+}
+
+static const struct snd_soc_ops mt8196_nau8825_i2s_ops = {
+ .hw_params = mt8196_nau8825_i2s_hw_params,
+};
+
+static int mt8196_dptx_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ unsigned int rate = params_rate(params);
+ unsigned int mclk_fs_ratio = 256;
+ unsigned int mclk_fs = rate * mclk_fs_ratio;
+ struct snd_soc_dai *dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+ return snd_soc_dai_set_sysclk(dai, 0, mclk_fs, SND_SOC_CLOCK_OUT);
+}
+
+static const struct snd_soc_ops mt8196_dptx_ops = {
+ .hw_params = mt8196_dptx_hw_params,
+};
+
+static int mt8196_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ dev_info(rtd->dev, "fix format to 32bit\n");
+
+ /* fix BE i2s format to 32bit, clean param mask first */
+ snd_mask_reset_range(hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT),
+ 0, (__force unsigned int)SNDRV_PCM_FORMAT_LAST);
+
+ params_set_format(params, SNDRV_PCM_FORMAT_S32_LE);
+ return 0;
+}
+
+static int mt8196_sof_be_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_component *cmpnt_afe = NULL;
+ struct snd_soc_pcm_runtime *runtime;
+
+ /* find afe component */
+ for_each_card_rtds(rtd->card, runtime) {
+ cmpnt_afe = snd_soc_rtdcom_lookup(runtime, AFE_PCM_NAME);
+ if (cmpnt_afe) {
+ dev_info(rtd->dev, "component->name: %s\n", cmpnt_afe->name);
+ break;
+ }
+ }
+
+ if (cmpnt_afe && !pm_runtime_active(cmpnt_afe->dev)) {
+ dev_err(rtd->dev, "afe pm runtime is not active!!\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_ops mt8196_sof_be_ops = {
+ .hw_params = mt8196_sof_be_hw_params,
+};
+
+static const struct sof_conn_stream g_sof_conn_streams[] = {
+ {
+ .sof_link = "AFE_SOF_DL1",
+ .sof_dma = SOF_DMA_DL1,
+ .stream_dir = SNDRV_PCM_STREAM_PLAYBACK
+ },
+ {
+ .sof_link = "AFE_SOF_DL_24CH",
+ .sof_dma = SOF_DMA_DL_24CH,
+ .stream_dir = SNDRV_PCM_STREAM_PLAYBACK
+ },
+ {
+ .sof_link = "AFE_SOF_UL0",
+ .sof_dma = SOF_DMA_UL0,
+ .stream_dir = SNDRV_PCM_STREAM_CAPTURE
+ },
+ {
+ .sof_link = "AFE_SOF_UL1",
+ .sof_dma = SOF_DMA_UL1,
+ .stream_dir = SNDRV_PCM_STREAM_CAPTURE
+ },
+ {
+ .sof_link = "AFE_SOF_UL2",
+ .sof_dma = SOF_DMA_UL2,
+ .stream_dir = SNDRV_PCM_STREAM_CAPTURE
+ },
+};
+
+/* FE */
+SND_SOC_DAILINK_DEFS(playback1,
+ DAILINK_COMP_ARRAY(COMP_CPU("DL1")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+SND_SOC_DAILINK_DEFS(playback_24ch,
+ DAILINK_COMP_ARRAY(COMP_CPU("DL_24CH")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+SND_SOC_DAILINK_DEFS(capture0,
+ DAILINK_COMP_ARRAY(COMP_CPU("UL0")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+SND_SOC_DAILINK_DEFS(capture1,
+ DAILINK_COMP_ARRAY(COMP_CPU("UL1")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+SND_SOC_DAILINK_DEFS(capture2,
+ DAILINK_COMP_ARRAY(COMP_CPU("UL2")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+SND_SOC_DAILINK_DEFS(playback_hdmi,
+ DAILINK_COMP_ARRAY(COMP_CPU("HDMI")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+SND_SOC_DAILINK_DEFS(playback2,
+ DAILINK_COMP_ARRAY(COMP_CPU("DL2")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+SND_SOC_DAILINK_DEFS(capture_cm0,
+ DAILINK_COMP_ARRAY(COMP_CPU("UL_CM0")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+/* BE */
+SND_SOC_DAILINK_DEFS(ap_dmic,
+ DAILINK_COMP_ARRAY(COMP_CPU("AP_DMIC")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+SND_SOC_DAILINK_DEFS(ap_dmic_ch34,
+ DAILINK_COMP_ARRAY(COMP_CPU("AP_DMIC_CH34")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+SND_SOC_DAILINK_DEFS(ap_dmic_multich,
+ DAILINK_COMP_ARRAY(COMP_CPU("AP_DMIC_MULTICH")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+SND_SOC_DAILINK_DEFS(i2sin6,
+ DAILINK_COMP_ARRAY(COMP_CPU("I2SIN6")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+SND_SOC_DAILINK_DEFS(i2sout3,
+ DAILINK_COMP_ARRAY(COMP_CPU("I2SOUT3")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+SND_SOC_DAILINK_DEFS(i2sout4,
+ DAILINK_COMP_ARRAY(COMP_CPU("I2SOUT4")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+SND_SOC_DAILINK_DEFS(i2sout6,
+ DAILINK_COMP_ARRAY(COMP_CPU("I2SOUT6")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+SND_SOC_DAILINK_DEFS(tdm_dptx,
+ DAILINK_COMP_ARRAY(COMP_CPU("TDM_DPTX")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+SND_SOC_DAILINK_DEFS(AFE_SOF_DL_24CH,
+ DAILINK_COMP_ARRAY(COMP_CPU("SOF_DL_24CH")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+SND_SOC_DAILINK_DEFS(AFE_SOF_DL1,
+ DAILINK_COMP_ARRAY(COMP_CPU("SOF_DL1")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+SND_SOC_DAILINK_DEFS(AFE_SOF_UL0,
+ DAILINK_COMP_ARRAY(COMP_CPU("SOF_UL0")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+SND_SOC_DAILINK_DEFS(AFE_SOF_UL1,
+ DAILINK_COMP_ARRAY(COMP_CPU("SOF_UL1")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+SND_SOC_DAILINK_DEFS(AFE_SOF_UL2,
+ DAILINK_COMP_ARRAY(COMP_CPU("SOF_UL2")),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link mt8196_nau8825_dai_links[] = {
+ /*
+ * The SOF topology expects PCM streams 0~4 to be available
+ * for the SOF PCM streams. Put the SOF BE definitions here
+ * so that the PCM device numbers are skipped over.
+ * (BE dailinks do not have PCM devices created.)
+ */
+ {
+ .name = "AFE_SOF_DL_24CH",
+ .no_pcm = 1,
+ .playback_only = 1,
+ .ops = &mt8196_sof_be_ops,
+ SND_SOC_DAILINK_REG(AFE_SOF_DL_24CH),
+ },
+ {
+ .name = "AFE_SOF_DL1",
+ .no_pcm = 1,
+ .playback_only = 1,
+ .ops = &mt8196_sof_be_ops,
+ SND_SOC_DAILINK_REG(AFE_SOF_DL1),
+ },
+ {
+ .name = "AFE_SOF_UL0",
+ .no_pcm = 1,
+ .capture_only = 1,
+ .ops = &mt8196_sof_be_ops,
+ SND_SOC_DAILINK_REG(AFE_SOF_UL0),
+ },
+ {
+ .name = "AFE_SOF_UL1",
+ .no_pcm = 1,
+ .capture_only = 1,
+ .ops = &mt8196_sof_be_ops,
+ SND_SOC_DAILINK_REG(AFE_SOF_UL1),
+ },
+ {
+ .name = "AFE_SOF_UL2",
+ .no_pcm = 1,
+ .capture_only = 1,
+ .ops = &mt8196_sof_be_ops,
+ SND_SOC_DAILINK_REG(AFE_SOF_UL2),
+ },
+ /* Front End DAI links */
+ {
+ .name = "HDMI_FE",
+ .stream_name = "HDMI Playback",
+ .trigger = {SND_SOC_DPCM_TRIGGER_PRE,
+ SND_SOC_DPCM_TRIGGER_PRE},
+ .dynamic = 1,
+ .playback_only = 1,
+ SND_SOC_DAILINK_REG(playback_hdmi),
+ },
+ {
+ .name = "DL2_FE",
+ .stream_name = "DL2 Playback",
+ .trigger = {SND_SOC_DPCM_TRIGGER_PRE,
+ SND_SOC_DPCM_TRIGGER_PRE},
+ .dynamic = 1,
+ .playback_only = 1,
+ SND_SOC_DAILINK_REG(playback2),
+ },
+ {
+ .name = "UL_CM0_FE",
+ .stream_name = "UL_CM0 Capture",
+ .trigger = {SND_SOC_DPCM_TRIGGER_PRE,
+ SND_SOC_DPCM_TRIGGER_PRE},
+ .dynamic = 1,
+ .capture_only = 1,
+ SND_SOC_DAILINK_REG(capture_cm0),
+ },
+ {
+ .name = "DL_24CH_FE",
+ .stream_name = "DL_24CH Playback",
+ .trigger = {SND_SOC_DPCM_TRIGGER_PRE,
+ SND_SOC_DPCM_TRIGGER_PRE},
+ .dynamic = 1,
+ .playback_only = 1,
+ SND_SOC_DAILINK_REG(playback_24ch),
+ },
+ {
+ .name = "DL1_FE",
+ .stream_name = "DL1 Playback",
+ .trigger = {SND_SOC_DPCM_TRIGGER_PRE,
+ SND_SOC_DPCM_TRIGGER_PRE},
+ .dynamic = 1,
+ .playback_only = 1,
+ SND_SOC_DAILINK_REG(playback1),
+ },
+ {
+ .name = "UL0_FE",
+ .stream_name = "UL0 Capture",
+ .trigger = {SND_SOC_DPCM_TRIGGER_PRE,
+ SND_SOC_DPCM_TRIGGER_PRE},
+ .dynamic = 1,
+ .capture_only = 1,
+ SND_SOC_DAILINK_REG(capture0),
+ },
+ {
+ .name = "UL1_FE",
+ .stream_name = "UL1 Capture",
+ .trigger = {SND_SOC_DPCM_TRIGGER_PRE,
+ SND_SOC_DPCM_TRIGGER_PRE},
+ .dynamic = 1,
+ .capture_only = 1,
+ SND_SOC_DAILINK_REG(capture1),
+ },
+ {
+ .name = "UL2_FE",
+ .stream_name = "UL2 Capture",
+ .trigger = {SND_SOC_DPCM_TRIGGER_PRE,
+ SND_SOC_DPCM_TRIGGER_PRE},
+ .dynamic = 1,
+ .capture_only = 1,
+ SND_SOC_DAILINK_REG(capture2),
+ },
+ /* Back End DAI links */
+ {
+ .name = "I2SIN6_BE",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBC_CFC
+ | SND_SOC_DAIFMT_GATED,
+ .ops = &mt8196_nau8825_i2s_ops,
+ .no_pcm = 1,
+ .capture_only = 1,
+ .ignore_suspend = 1,
+ .be_hw_params_fixup = mt8196_hw_params_fixup,
+ SND_SOC_DAILINK_REG(i2sin6),
+ },
+ {
+ .name = "I2SOUT4_BE",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBC_CFC
+ | SND_SOC_DAIFMT_GATED,
+ .ops = &mt8196_nau8825_i2s_ops,
+ .no_pcm = 1,
+ .playback_only = 1,
+ .ignore_suspend = 1,
+ .ignore_pmdown_time = 1,
+ .be_hw_params_fixup = mt8196_hw_params_fixup,
+ SND_SOC_DAILINK_REG(i2sout4),
+ },
+ {
+ .name = "I2SOUT6_BE",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBC_CFC
+ | SND_SOC_DAIFMT_GATED,
+ .ops = &mt8196_nau8825_i2s_ops,
+ .no_pcm = 1,
+ .playback_only = 1,
+ .ignore_suspend = 1,
+ .be_hw_params_fixup = mt8196_hw_params_fixup,
+ SND_SOC_DAILINK_REG(i2sout6),
+ },
+ {
+ .name = "AP_DMIC_BE",
+ .no_pcm = 1,
+ .capture_only = 1,
+ .ignore_suspend = 1,
+ SND_SOC_DAILINK_REG(ap_dmic),
+ },
+ {
+ .name = "AP_DMIC_CH34_BE",
+ .no_pcm = 1,
+ .capture_only = 1,
+ .ignore_suspend = 1,
+ SND_SOC_DAILINK_REG(ap_dmic_ch34),
+ },
+ {
+ .name = "AP_DMIC_MULTICH_BE",
+ .no_pcm = 1,
+ .capture_only = 1,
+ .ignore_suspend = 1,
+ SND_SOC_DAILINK_REG(ap_dmic_multich),
+ },
+ {
+ .name = "TDM_DPTX_BE",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBC_CFC
+ | SND_SOC_DAIFMT_GATED,
+ .ops = &mt8196_dptx_ops,
+ .be_hw_params_fixup = mt8196_hw_params_fixup,
+ .no_pcm = 1,
+ .playback_only = 1,
+ .ignore_suspend = 1,
+ SND_SOC_DAILINK_REG(tdm_dptx),
+ },
+ {
+ .name = "I2SOUT3_BE",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBC_CFC
+ | SND_SOC_DAIFMT_GATED,
+ .ops = &mt8196_nau8825_i2s_ops,
+ .no_pcm = 1,
+ .playback_only = 1,
+ .ignore_suspend = 1,
+ SND_SOC_DAILINK_REG(i2sout3),
+ },
+};
+
+static int mt8196_dumb_amp_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card);
+ int ret = 0;
+
+ ret = snd_soc_dapm_new_controls(dapm, mt8196_dumb_spk_widgets,
+ ARRAY_SIZE(mt8196_dumb_spk_widgets));
+ if (ret) {
+ dev_err(rtd->dev, "unable to add Dumb Speaker dapm, ret %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_add_card_controls(card, mt8196_dumb_spk_controls,
+ ARRAY_SIZE(mt8196_dumb_spk_controls));
+ if (ret) {
+ dev_err(rtd->dev, "unable to add Dumb card controls, ret %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mt8196_dptx_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct mtk_soc_card_data *soc_card_data = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_jack *jack = &soc_card_data->card_data->jacks[MT8196_JACK_DP];
+ struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+ int ret = 0;
+
+ ret = snd_soc_card_jack_new_pins(rtd->card, "DP Jack", SND_JACK_AVOUT,
+ jack, mt8196_dp_jack_pins,
+ ARRAY_SIZE(mt8196_dp_jack_pins));
+ if (ret) {
+ dev_err(rtd->dev, "new jack failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_component_set_jack(component, jack, NULL);
+ if (ret) {
+ dev_err(rtd->dev, "set jack failed on %s (ret=%d)\n",
+ component->name, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mt8196_hdmi_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct mtk_soc_card_data *soc_card_data = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_jack *jack = &soc_card_data->card_data->jacks[MT8196_JACK_HDMI];
+ struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+ int ret = 0;
+
+ ret = snd_soc_card_jack_new_pins(rtd->card, "HDMI Jack", SND_JACK_AVOUT,
+ jack, mt8196_hdmi_jack_pins,
+ ARRAY_SIZE(mt8196_hdmi_jack_pins));
+ if (ret) {
+ dev_err(rtd->dev, "new jack failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_component_set_jack(component, jack, NULL);
+ if (ret) {
+ dev_err(rtd->dev, "set jack failed on %s (ret=%d)\n",
+ component->name, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mt8196_headset_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card);
+ struct mtk_soc_card_data *soc_card_data = snd_soc_card_get_drvdata(card);
+ struct snd_soc_jack *jack = &soc_card_data->card_data->jacks[MT8196_JACK_HEADSET];
+ struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+ int ret;
+ int type;
+
+ ret = snd_soc_dapm_new_controls(dapm, mt8196_nau8825_widgets,
+ ARRAY_SIZE(mt8196_nau8825_widgets));
+ if (ret) {
+ dev_err(rtd->dev, "unable to add nau8825 card widget, ret %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_add_card_controls(card, mt8196_nau8825_controls,
+ ARRAY_SIZE(mt8196_nau8825_controls));
+ if (ret) {
+ dev_err(rtd->dev, "unable to add nau8825 card controls, ret %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack",
+ SND_JACK_HEADSET | SND_JACK_BTN_0 |
+ SND_JACK_BTN_1 | SND_JACK_BTN_2 |
+ SND_JACK_BTN_3,
+ jack,
+ nau8825_jack_pins,
+ ARRAY_SIZE(nau8825_jack_pins));
+ if (ret) {
+ dev_err(rtd->dev, "Headset Jack creation failed: %d\n", ret);
+ return ret;
+ }
+
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
+
+ type = SND_JACK_HEADSET | SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2 | SND_JACK_BTN_3;
+ ret = snd_soc_component_set_jack(component, jack, (void *)&type);
+
+ if (ret) {
+ dev_err(rtd->dev, "Headset Jack call-back failed: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+};
+
+static void mt8196_headset_codec_exit(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+
+ snd_soc_component_set_jack(component, NULL, NULL);
+}
+
+static int mt8196_nau8825_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+ unsigned int rate = params_rate(params);
+ unsigned int bit_width = params_width(params);
+ int clk_freq, ret;
+
+ clk_freq = rate * 2 * bit_width;
+
+ /* Configure clock for codec */
+ ret = snd_soc_dai_set_sysclk(codec_dai, NAU8825_CLK_FLL_BLK, 0,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(codec_dai->dev, "can't set BCLK clock %d\n", ret);
+ return ret;
+ }
+
+ /* Configure pll for codec */
+ ret = snd_soc_dai_set_pll(codec_dai, 0, 0, clk_freq,
+ params_rate(params) * 256);
+ if (ret < 0) {
+ dev_err(codec_dai->dev, "can't set BCLK: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_ops mt8196_nau8825_ops = {
+ .hw_params = mt8196_nau8825_hw_params,
+};
+
+static int mt8196_rt5682s_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_card *card = rtd->card;
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+ unsigned int rate = params_rate(params);
+ int bitwidth;
+ int ret;
+
+ bitwidth = snd_pcm_format_width(params_format(params));
+ if (bitwidth < 0) {
+ dev_err(card->dev, "invalid bit width: %d\n", bitwidth);
+ return bitwidth;
+ }
+
+ ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x00, 0x0, 0x2, bitwidth);
+ if (ret) {
+ dev_err(card->dev, "failed to set tdm slot\n");
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_pll(codec_dai, RT5682S_PLL1, RT5682S_PLL_S_BCLK1,
+ rate * 32, rate * 512);
+ if (ret) {
+ dev_err(card->dev, "failed to set pll\n");
+ return ret;
+ }
+
+ dev_info(card->dev, "%s set mclk rate: %d\n", __func__, rate * 512);
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, RT5682S_SCLK_S_MCLK,
+ rate * 512, SND_SOC_CLOCK_IN);
+ if (ret) {
+ dev_err(card->dev, "failed to set sysclk\n");
+ return ret;
+ }
+
+ return snd_soc_dai_set_sysclk(cpu_dai, 0, rate * 512,
+ SND_SOC_CLOCK_OUT);
+}
+
+static const struct snd_soc_ops mt8196_rt5682s_i2s_ops = {
+ .hw_params = mt8196_rt5682s_i2s_hw_params,
+};
+
+static int mt8196_nau8825_soc_card_probe(struct mtk_soc_card_data *soc_card_data, bool legacy)
+{
+ struct snd_soc_card *card = soc_card_data->card_data->card;
+ struct snd_soc_dai_link *dai_link;
+ bool init_nau8825 = false;
+ bool init_rt5682s = false;
+ bool init_rt5650 = false;
+ bool init_dumb = false;
+ int i;
+
+ dev_info(card->dev, "legacy: %d\n", legacy);
+
+ for_each_card_prelinks(card, i, dai_link) {
+ if (strcmp(dai_link->name, "TDM_DPTX_BE") == 0) {
+ if (dai_link->num_codecs &&
+ strcmp(dai_link->codecs->dai_name, "snd-soc-dummy-dai"))
+ dai_link->init = mt8196_dptx_codec_init;
+ } else if (strcmp(dai_link->name, "I2SOUT3_BE") == 0) {
+ if (dai_link->num_codecs &&
+ strcmp(dai_link->codecs->dai_name, "snd-soc-dummy-dai"))
+ dai_link->init = mt8196_hdmi_codec_init;
+ } else if (strcmp(dai_link->name, "I2SOUT6_BE") == 0 ||
+ strcmp(dai_link->name, "I2SIN6_BE") == 0) {
+ if (!strcmp(dai_link->codecs->dai_name, NAU8825_CODEC_DAI)) {
+ dai_link->ops = &mt8196_nau8825_ops;
+ if (!init_nau8825) {
+ dai_link->init = mt8196_headset_codec_init;
+ dai_link->exit = mt8196_headset_codec_exit;
+ init_nau8825 = true;
+ }
+ } else if (!strcmp(dai_link->codecs->dai_name, RT5682S_CODEC_DAI)) {
+ dai_link->ops = &mt8196_rt5682s_i2s_ops;
+ if (!init_rt5682s) {
+ dai_link->init = mt8196_headset_codec_init;
+ dai_link->exit = mt8196_headset_codec_exit;
+ init_rt5682s = true;
+ }
+ } else if (!strcmp(dai_link->codecs->dai_name, RT5650_CODEC_DAI)) {
+ dai_link->ops = &mt8196_rt5682s_i2s_ops;
+ if (!init_rt5650) {
+ dai_link->init = mt8196_headset_codec_init;
+ dai_link->exit = mt8196_headset_codec_exit;
+ init_rt5650 = true;
+ }
+ } else {
+ if (strcmp(dai_link->codecs->dai_name, "snd-soc-dummy-dai")) {
+ if (!init_dumb) {
+ dai_link->init = mt8196_dumb_amp_init;
+ init_dumb = true;
+ }
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+static const struct mtk_sof_priv mt8196_sof_priv = {
+ .conn_streams = g_sof_conn_streams,
+ .num_streams = ARRAY_SIZE(g_sof_conn_streams),
+};
+
+static struct snd_soc_card mt8196_nau8825_soc_card = {
+ .owner = THIS_MODULE,
+ .dai_link = mt8196_nau8825_dai_links,
+ .num_links = ARRAY_SIZE(mt8196_nau8825_dai_links),
+ .dapm_widgets = mt8196_nau8825_card_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(mt8196_nau8825_card_widgets),
+ .dapm_routes = mt8196_nau8825_card_routes,
+ .num_dapm_routes = ARRAY_SIZE(mt8196_nau8825_card_routes),
+ .controls = mt8196_nau8825_card_controls,
+ .num_controls = ARRAY_SIZE(mt8196_nau8825_card_controls),
+};
+
+static const struct mtk_soundcard_pdata mt8196_nau8825_card = {
+ .card_name = "mt8196_nau8825",
+ .card_data = &(struct mtk_platform_card_data) {
+ .card = &mt8196_nau8825_soc_card,
+ .num_jacks = MT8196_JACK_MAX,
+ .flags = NAU8825_HS_PRESENT
+ },
+ .sof_priv = &mt8196_sof_priv,
+ .soc_probe = mt8196_nau8825_soc_card_probe,
+};
+
+static const struct mtk_soundcard_pdata mt8196_rt5682s_card = {
+ .card_name = "mt8196_rt5682s",
+ .card_data = &(struct mtk_platform_card_data) {
+ .card = &mt8196_nau8825_soc_card,
+ .num_jacks = MT8196_JACK_MAX,
+ .flags = RT5682S_HS_PRESENT
+ },
+ .sof_priv = &mt8196_sof_priv,
+ .soc_probe = mt8196_nau8825_soc_card_probe,
+};
+
+static const struct mtk_soundcard_pdata mt8196_rt5650_card = {
+ .card_name = "mt8196_rt5650",
+ .card_data = &(struct mtk_platform_card_data) {
+ .card = &mt8196_nau8825_soc_card,
+ .num_jacks = MT8196_JACK_MAX,
+ .flags = RT5650_HS_PRESENT
+ },
+ .sof_priv = &mt8196_sof_priv,
+ .soc_probe = mt8196_nau8825_soc_card_probe,
+};
+
+static const struct of_device_id mt8196_nau8825_dt_match[] = {
+ {.compatible = "mediatek,mt8196-nau8825-sound", .data = &mt8196_nau8825_card,},
+ {.compatible = "mediatek,mt8196-rt5682s-sound", .data = &mt8196_rt5682s_card,},
+ {.compatible = "mediatek,mt8196-rt5650-sound", .data = &mt8196_rt5650_card,},
+ {}
+};
+MODULE_DEVICE_TABLE(of, mt8196_nau8825_dt_match);
+
+static struct platform_driver mt8196_nau8825_driver = {
+ .driver = {
+ .name = "mt8196-nau8825",
+ .of_match_table = mt8196_nau8825_dt_match,
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = mtk_soundcard_common_probe,
+};
+module_platform_driver(mt8196_nau8825_driver);
+
+/* Module information */
+MODULE_DESCRIPTION("MT8196 nau8825 ALSA SoC machine driver");
+MODULE_AUTHOR("Darren Ye <darren.ye@mediatek.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("mt8196 nau8825 soc card");
+
--
2.45.2
^ permalink raw reply related
* [PATCH v9 08/10] ASoC: mediatek: mt8196: add platform driver
From: Cyril Chao @ 2026-04-30 2:23 UTC (permalink / raw)
To: Liam Girdwood, Mark Brown, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Matthias Brugger, AngeloGioacchino Del Regno,
Jaroslav Kysela, Takashi Iwai
Cc: linux-sound, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek, Cyril Chao, Project_Global_Chrome_Upstream_Group,
Darren Ye, Cyril Chao
In-Reply-To: <20260430022417.32282-1-Cyril.Chao@mediatek.com>
From: Darren Ye <darren.ye@mediatek.com>
Add mt8196 platform driver.
Signed-off-by: Darren Ye <darren.ye@mediatek.com>
Signed-off-by: Cyril Chao <Cyril.Chao@mediatek.com>
---
sound/soc/mediatek/Kconfig | 10 +
sound/soc/mediatek/Makefile | 1 +
sound/soc/mediatek/mt8196/Makefile | 12 +
sound/soc/mediatek/mt8196/mt8196-afe-pcm.c | 2503 ++++++++++++++++++++
4 files changed, 2526 insertions(+)
create mode 100644 sound/soc/mediatek/mt8196/Makefile
create mode 100644 sound/soc/mediatek/mt8196/mt8196-afe-pcm.c
diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig
index 3a1e1fa3fe5c..dc41e5860eb0 100644
--- a/sound/soc/mediatek/Kconfig
+++ b/sound/soc/mediatek/Kconfig
@@ -353,4 +353,14 @@ config SND_SOC_MT8365_MT6357
Select Y if you have such device.
If unsure select "N".
+config SND_SOC_MT8196
+ tristate "ASoC support for Mediatek MT8196 chip"
+ depends on ARCH_MEDIATEK
+ select SND_SOC_MEDIATEK
+ help
+ This adds ASoC driver for Mediatek MT8196 boards
+ that can be used with other codecs.
+ Select Y if you have such device.
+ If unsure select "N".
+
endmenu
diff --git a/sound/soc/mediatek/Makefile b/sound/soc/mediatek/Makefile
index 7cd67bce92e9..a6815a3c5988 100644
--- a/sound/soc/mediatek/Makefile
+++ b/sound/soc/mediatek/Makefile
@@ -11,3 +11,4 @@ obj-$(CONFIG_SND_SOC_MT8192) += mt8192/
obj-$(CONFIG_SND_SOC_MT8195) += mt8195/
obj-$(CONFIG_SND_SOC_MT8365) += mt8365/
obj-$(CONFIG_SND_SOC_MT8189) += mt8189/
+obj-$(CONFIG_SND_SOC_MT8196) += mt8196/
diff --git a/sound/soc/mediatek/mt8196/Makefile b/sound/soc/mediatek/mt8196/Makefile
new file mode 100644
index 000000000000..0c6a018190a2
--- /dev/null
+++ b/sound/soc/mediatek/mt8196/Makefile
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0
+
+# platform driver
+snd-soc-mt8196-afe-objs += \
+ mt8196-afe-pcm.o \
+ mt8196-afe-clk.o \
+ mt8196-dai-adda.o \
+ mt8196-dai-i2s.o \
+ mt8196-dai-tdm.o
+
+obj-$(CONFIG_SND_SOC_MT8196) += snd-soc-mt8196-afe.o
+
diff --git a/sound/soc/mediatek/mt8196/mt8196-afe-pcm.c b/sound/soc/mediatek/mt8196/mt8196-afe-pcm.c
new file mode 100644
index 000000000000..511e888567be
--- /dev/null
+++ b/sound/soc/mediatek/mt8196/mt8196-afe-pcm.c
@@ -0,0 +1,2503 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Mediatek ALSA SoC AFE platform driver for 8196
+ *
+ * Copyright (c) 2025 MediaTek Inc.
+ * Author: Darren Ye <darren.ye@mediatek.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include "mt8196-afe-clk.h"
+#include "mt8196-afe-common.h"
+#include "mt8196-interconnection.h"
+
+#include "../common/mtk-afe-fe-dai.h"
+#include "../common/mtk-afe-platform-driver.h"
+
+static const struct snd_pcm_hardware mt8196_afe_hardware = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_NO_PERIOD_WAKEUP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = (SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE),
+ .period_bytes_min = 96,
+ .period_bytes_max = 4 * 48 * 1024,
+ .periods_min = 2,
+ .periods_max = 256,
+ .buffer_bytes_max = 256 * 1024,
+ .fifo_size = 0,
+};
+
+static unsigned int mt8196_rate_transform(struct device *dev,
+ unsigned int rate)
+{
+ switch (rate) {
+ case 8000:
+ return MTK_AFE_IPM2P0_RATE_8K;
+ case 11025:
+ return MTK_AFE_IPM2P0_RATE_11K;
+ case 12000:
+ return MTK_AFE_IPM2P0_RATE_12K;
+ case 16000:
+ return MTK_AFE_IPM2P0_RATE_16K;
+ case 22050:
+ return MTK_AFE_IPM2P0_RATE_22K;
+ case 24000:
+ return MTK_AFE_IPM2P0_RATE_24K;
+ case 32000:
+ return MTK_AFE_IPM2P0_RATE_32K;
+ case 44100:
+ return MTK_AFE_IPM2P0_RATE_44K;
+ case 48000:
+ return MTK_AFE_IPM2P0_RATE_48K;
+ case 88200:
+ return MTK_AFE_IPM2P0_RATE_88K;
+ case 96000:
+ return MTK_AFE_IPM2P0_RATE_96K;
+ case 176400:
+ return MTK_AFE_IPM2P0_RATE_176K;
+ case 192000:
+ return MTK_AFE_IPM2P0_RATE_192K;
+ /* not support 260K */
+ case 352800:
+ return MTK_AFE_IPM2P0_RATE_352K;
+ case 384000:
+ return MTK_AFE_IPM2P0_RATE_384K;
+ default:
+ dev_err(dev, "rate %u invalid, use %d!!!\n",
+ rate, MTK_AFE_IPM2P0_RATE_48K);
+ return MTK_AFE_IPM2P0_RATE_48K;
+ }
+}
+
+static int mt8196_set_cm(struct mtk_base_afe *afe, int id,
+ bool update, bool swap, unsigned int ch)
+{
+ struct mt8196_afe_private *afe_priv = afe->platform_priv;
+ unsigned int rate = afe_priv->cm_rate[id];
+ unsigned int rate_val = mt8196_rate_transform(afe->dev, rate);
+ unsigned int ch_pair = ch / 2;
+ unsigned int update_val;
+ int reg = AFE_CM0_CON0 + 0x10 * id;
+
+ if (update && ch_pair)
+ update_val = (26000000 / rate - 10) / ch_pair - 1;
+ else
+ update_val = 0x64;
+
+ dev_dbg(afe->dev, "CM%d, rate %d, update %d, swap %d, ch %d, update_val: %d\n",
+ id, rate, update, swap, ch, update_val);
+
+ /* update cnt */
+ regmap_update_bits(afe->regmap, reg,
+ AFE_CM_UPDATE_CNT_MASK << AFE_CM_UPDATE_CNT_SFT,
+ update_val << AFE_CM_UPDATE_CNT_SFT);
+
+ /* rate */
+ regmap_update_bits(afe->regmap, reg,
+ AFE_CM_1X_EN_SEL_FS_MASK << AFE_CM_1X_EN_SEL_FS_SFT,
+ rate_val << AFE_CM_1X_EN_SEL_FS_SFT);
+
+ /* ch num */
+ ch = ch - 1;
+ regmap_update_bits(afe->regmap, reg,
+ AFE_CM_CH_NUM_MASK << AFE_CM_CH_NUM_SFT,
+ ch << AFE_CM_CH_NUM_SFT);
+
+ /* swap */
+ regmap_update_bits(afe->regmap, reg,
+ AFE_CM_BYTE_SWAP_MASK << AFE_CM_BYTE_SWAP_SFT,
+ swap << AFE_CM_BYTE_SWAP_SFT);
+
+ return 0;
+}
+
+static int mt8196_enable_cm_bypass(struct mtk_base_afe *afe, int id, bool en)
+{
+ return regmap_update_bits(afe->regmap,
+ AFE_CM0_CON0 + 0x10 * id,
+ AFE_CM_BYPASS_MODE_MASK << AFE_CM_BYPASS_MODE_SFT,
+ en << AFE_CM_BYPASS_MODE_SFT);
+}
+
+static int mt8196_fe_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ int memif_num = cpu_dai->id;
+ struct mtk_base_afe_memif *memif = &afe->memif[memif_num];
+ const struct snd_pcm_hardware *mtk_afe_hardware = afe->mtk_afe_hardware;
+ int ret;
+
+ dev_dbg(afe->dev, "memif_num: %d.\n", memif_num);
+
+ memif->substream = substream;
+
+ snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 16);
+
+ if (memif_num == MT8196_MEMIF_VUL_CM0)
+ snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 16);
+
+ snd_soc_set_runtime_hwparams(substream, mtk_afe_hardware);
+
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ dev_warn(afe->dev, "snd_pcm_hw_constraint_integer failed\n");
+
+ /* dynamic allocate irq to memif */
+ if (memif->irq_usage < 0) {
+ int irq_id = mtk_dynamic_irq_acquire(afe);
+
+ if (irq_id != afe->irqs_size) {
+ /* link */
+ memif->irq_usage = irq_id;
+ } else {
+ dev_err(afe->dev, "no more asys irq\n");
+ ret = -EBUSY;
+ }
+ }
+ return ret;
+}
+
+static void mt8196_fe_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
+ struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+ int memif_num = cpu_dai->id;
+ struct mtk_base_afe_memif *memif = &afe->memif[memif_num];
+ int irq_id = memif->irq_usage;
+
+ dev_dbg(afe->dev, "memif_num: %d.\n", memif_num);
+
+ memif->substream = NULL;
+
+ if (!memif->const_irq) {
+ mtk_dynamic_irq_release(afe, irq_id);
+ memif->irq_usage = -1;
+ memif->substream = NULL;
+ }
+}
+
+static int mt8196_fe_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
+ unsigned int channels = params_channels(params);
+ struct mt8196_afe_private *afe_priv = afe->platform_priv;
+ int id = snd_soc_rtd_to_cpu(rtd, 0)->id;
+ struct mtk_base_afe_memif *memif = &afe->memif[id];
+ const struct mtk_base_memif_data *data = memif->data;
+ int cm;
+
+ afe_priv->cm_channels = channels;
+
+ /* set channels */
+ if (data->ch_num_shift >= 0) {
+ regmap_update_bits(afe->regmap, data->ch_num_reg,
+ data->ch_num_maskbit << data->ch_num_shift,
+ channels << data->ch_num_shift);
+ }
+
+ switch (id) {
+ case MT8196_MEMIF_VUL8:
+ case MT8196_MEMIF_VUL_CM0:
+ cm = CM0;
+ break;
+ case MT8196_MEMIF_VUL9:
+ case MT8196_MEMIF_VUL_CM1:
+ cm = CM1;
+ break;
+ case MT8196_MEMIF_VUL10:
+ case MT8196_MEMIF_VUL_CM2:
+ cm = CM2;
+ break;
+ default:
+ cm = CM0;
+ break;
+ }
+
+ afe_priv->cm_rate[cm] = params_rate(params);
+
+ return mtk_afe_fe_hw_params(substream, params, dai);
+}
+
+static int mt8196_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 snd_pcm_runtime *const runtime = substream->runtime;
+ struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai);
+ int id = snd_soc_rtd_to_cpu(rtd, 0)->id;
+ struct mtk_base_afe_memif *memif = &afe->memif[id];
+ int irq_id = memif->irq_usage;
+ struct mtk_base_afe_irq *irqs = &afe->irqs[irq_id];
+ const struct mtk_base_irq_data *irq_data = irqs->irq_data;
+ unsigned int counter = runtime->period_size;
+ unsigned int rate = runtime->rate;
+ unsigned int tmp_reg;
+ int fs;
+ int ret;
+
+ dev_dbg(afe->dev, "%s cmd %d, irq_id %d\n", memif->data->name, cmd, irq_id);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ dev_dbg(afe->dev, "%s cmd %d, id %d\n", memif->data->name, cmd, id);
+
+ ret = mtk_memif_set_enable(afe, id);
+ if (ret) {
+ dev_err(afe->dev, "id %d, memif enable fail.\n", id);
+ return ret;
+ }
+
+ /*
+ * for small latency record
+ * ul memif need read some data before irq enable.
+ * the context of this ops triger is atmoic, so it cannot sleep.
+ */
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ if ((runtime->period_size * 1000) / rate <= 10)
+ udelay(300);
+
+ regmap_update_bits(afe->regmap,
+ irq_data->irq_cnt_reg,
+ irq_data->irq_cnt_maskbit << irq_data->irq_cnt_shift,
+ counter << irq_data->irq_cnt_shift);
+
+ /* set irq fs */
+ fs = afe->irq_fs(substream, rate);
+ if (fs < 0)
+ return -EINVAL;
+
+ if (irq_data->irq_fs_reg >= 0)
+ regmap_update_bits(afe->regmap,
+ irq_data->irq_fs_reg,
+ irq_data->irq_fs_maskbit << irq_data->irq_fs_shift,
+ fs << irq_data->irq_fs_shift);
+
+ /* enable interrupt */
+ regmap_update_bits(afe->regmap,
+ irq_data->irq_en_reg,
+ 1 << irq_data->irq_en_shift,
+ 1 << irq_data->irq_en_shift);
+
+ return 0;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ ret = mtk_memif_set_disable(afe, id);
+ if (ret)
+ dev_warn(afe->dev, "id %d, memif disable fail\n", id);
+
+ /* disable interrupt */
+ regmap_update_bits(afe->regmap,
+ irq_data->irq_en_reg,
+ 1 << irq_data->irq_en_shift,
+ 0 << irq_data->irq_en_shift);
+
+ /*
+ * clear pending IRQ, if the register read as one, there is no need to write
+ * one to clear operaton.
+ */
+ regmap_read(afe->regmap, irq_data->irq_clr_reg, &tmp_reg);
+ regmap_update_bits(afe->regmap, irq_data->irq_clr_reg,
+ AFE_IRQ_CLR_CFG_MASK_SFT | AFE_IRQ_MISS_FLAG_CLR_CFG_MASK_SFT,
+ tmp_reg ^ (AFE_IRQ_CLR_CFG_MASK_SFT |
+ AFE_IRQ_MISS_FLAG_CLR_CFG_MASK_SFT));
+
+ return ret;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mt8196_memif_fs(struct snd_pcm_substream *substream,
+ unsigned int rate)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_component *component =
+ snd_soc_rtdcom_lookup(rtd, AFE_PCM_NAME);
+ struct mtk_base_afe *afe = NULL;
+ unsigned int rate_reg;
+
+ if (!component)
+ return -EINVAL;
+
+ afe = snd_soc_component_get_drvdata(component);
+ if (!afe)
+ return -EINVAL;
+
+ rate_reg = mt8196_rate_transform(afe->dev, rate);
+
+ return rate_reg;
+}
+
+static int mt8196_get_dai_fs(struct mtk_base_afe *afe,
+ int dai_id, unsigned int rate)
+{
+ return mt8196_rate_transform(afe->dev, rate);
+}
+
+static int mt8196_irq_fs(struct snd_pcm_substream *substream, unsigned int rate)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_component *component =
+ snd_soc_rtdcom_lookup(rtd, AFE_PCM_NAME);
+ struct mtk_base_afe *afe = NULL;
+
+ if (!component)
+ return -EINVAL;
+ afe = snd_soc_component_get_drvdata(component);
+ return mt8196_rate_transform(afe->dev, rate);
+}
+
+static int mt8196_get_memif_pbuf_size(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ if ((runtime->period_size * 1000) / runtime->rate > 10)
+ return MT8196_MEMIF_PBUF_SIZE_256_BYTES;
+ else
+ return MT8196_MEMIF_PBUF_SIZE_32_BYTES;
+}
+
+/* FE DAIs */
+static const struct snd_soc_dai_ops mt8196_memif_dai_ops = {
+ .startup = mt8196_fe_startup,
+ .shutdown = mt8196_fe_shutdown,
+ .hw_params = mt8196_fe_hw_params,
+ .hw_free = mtk_afe_fe_hw_free,
+ .prepare = mtk_afe_fe_prepare,
+ .trigger = mt8196_fe_trigger,
+};
+
+#define MTK_PCM_RATES (SNDRV_PCM_RATE_8000_48000 |\
+ SNDRV_PCM_RATE_88200 |\
+ SNDRV_PCM_RATE_96000 |\
+ SNDRV_PCM_RATE_176400 |\
+ SNDRV_PCM_RATE_192000)
+
+#define MTK_PCM_DAI_RATES (SNDRV_PCM_RATE_8000 |\
+ SNDRV_PCM_RATE_16000 |\
+ SNDRV_PCM_RATE_32000 |\
+ SNDRV_PCM_RATE_48000)
+
+#define MTK_PCM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
+ SNDRV_PCM_FMTBIT_S24_LE |\
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+#define MT8196_FE_DAI(_name, _id, max_ch, dir) \
+{ \
+ .name = #_name, \
+ .id = _id, \
+ .dir = { \
+ .stream_name = #_name, \
+ .channels_min = 1, \
+ .channels_max = max_ch, \
+ .rates = MTK_PCM_RATES, \
+ .formats = MTK_PCM_FORMATS, \
+ }, \
+ .ops = &mt8196_memif_dai_ops, \
+}
+
+static struct snd_soc_dai_driver mt8196_memif_dai_driver[] = {
+ /* FE DAIs: memory intefaces to CPU */
+ /* Playback */
+ MT8196_FE_DAI(DL0, MT8196_MEMIF_DL0, 2, playback),
+ MT8196_FE_DAI(DL1, MT8196_MEMIF_DL1, 2, playback),
+ MT8196_FE_DAI(DL2, MT8196_MEMIF_DL2, 2, playback),
+ MT8196_FE_DAI(DL3, MT8196_MEMIF_DL3, 2, playback),
+ MT8196_FE_DAI(DL4, MT8196_MEMIF_DL4, 2, playback),
+ MT8196_FE_DAI(DL5, MT8196_MEMIF_DL5, 2, playback),
+ MT8196_FE_DAI(DL6, MT8196_MEMIF_DL6, 2, playback),
+ MT8196_FE_DAI(DL7, MT8196_MEMIF_DL7, 2, playback),
+ MT8196_FE_DAI(DL8, MT8196_MEMIF_DL8, 2, playback),
+ MT8196_FE_DAI(DL23, MT8196_MEMIF_DL23, 2, playback),
+ MT8196_FE_DAI(DL24, MT8196_MEMIF_DL24, 2, playback),
+ MT8196_FE_DAI(DL25, MT8196_MEMIF_DL25, 2, playback),
+ MT8196_FE_DAI(DL26, MT8196_MEMIF_DL26, 2, playback),
+ MT8196_FE_DAI(DL_4CH, MT8196_MEMIF_DL_4CH, 4, playback),
+ MT8196_FE_DAI(DL_24CH, MT8196_MEMIF_DL_24CH, 8, playback),
+ MT8196_FE_DAI(HDMI, MT8196_MEMIF_HDMI, 8, playback),
+ /* Capture */
+ MT8196_FE_DAI(UL0, MT8196_MEMIF_VUL0, 2, capture),
+ MT8196_FE_DAI(UL1, MT8196_MEMIF_VUL1, 2, capture),
+ MT8196_FE_DAI(UL2, MT8196_MEMIF_VUL2, 2, capture),
+ MT8196_FE_DAI(UL3, MT8196_MEMIF_VUL3, 2, capture),
+ MT8196_FE_DAI(UL4, MT8196_MEMIF_VUL4, 2, capture),
+ MT8196_FE_DAI(UL5, MT8196_MEMIF_VUL5, 2, capture),
+ MT8196_FE_DAI(UL6, MT8196_MEMIF_VUL6, 2, capture),
+ MT8196_FE_DAI(UL7, MT8196_MEMIF_VUL7, 2, capture),
+ MT8196_FE_DAI(UL8, MT8196_MEMIF_VUL8, 2, capture),
+ MT8196_FE_DAI(UL9, MT8196_MEMIF_VUL9, 16, capture),
+ MT8196_FE_DAI(UL10, MT8196_MEMIF_VUL10, 2, capture),
+ MT8196_FE_DAI(UL24, MT8196_MEMIF_VUL24, 2, capture),
+ MT8196_FE_DAI(UL25, MT8196_MEMIF_VUL25, 2, capture),
+ MT8196_FE_DAI(UL26, MT8196_MEMIF_VUL26, 2, capture),
+ MT8196_FE_DAI(UL_CM0, MT8196_MEMIF_VUL_CM0, 8, capture),
+ MT8196_FE_DAI(UL_CM1, MT8196_MEMIF_VUL_CM1, 16, capture),
+ MT8196_FE_DAI(UL_CM2, MT8196_MEMIF_VUL_CM2, 32, capture),
+ MT8196_FE_DAI(UL_ETDM_IN0, MT8196_MEMIF_ETDM_IN0, 2, capture),
+ MT8196_FE_DAI(UL_ETDM_IN1, MT8196_MEMIF_ETDM_IN1, 2, capture),
+ MT8196_FE_DAI(UL_ETDM_IN2, MT8196_MEMIF_ETDM_IN2, 2, capture),
+ MT8196_FE_DAI(UL_ETDM_IN3, MT8196_MEMIF_ETDM_IN3, 2, capture),
+ MT8196_FE_DAI(UL_ETDM_IN4, MT8196_MEMIF_ETDM_IN4, 2, capture),
+ MT8196_FE_DAI(UL_ETDM_IN6, MT8196_MEMIF_ETDM_IN6, 2, capture),
+};
+
+static int ul_cm0_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol,
+ int event)
+{
+ struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm);
+ struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt);
+ struct mt8196_afe_private *afe_priv = afe->platform_priv;
+ unsigned int channels = afe_priv->cm_channels;
+
+ dev_dbg(afe->dev, "event 0x%x, name %s, channels %u\n",
+ event, w->name, channels);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ mt8196_enable_cm_bypass(afe, CM0, false);
+ mt8196_set_cm(afe, CM0, true, false, channels);
+ regmap_update_bits(afe->regmap, AUDIO_TOP_CON0,
+ PDN_CM0_MASK_SFT, 0 << PDN_CM0_SFT);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ mt8196_enable_cm_bypass(afe, CM0, true);
+ regmap_update_bits(afe->regmap, AUDIO_TOP_CON0,
+ PDN_CM0_MASK_SFT, 1 << PDN_CM0_SFT);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int ul_cm1_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol,
+ int event)
+{
+ struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm);
+ struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt);
+ struct mt8196_afe_private *afe_priv = afe->platform_priv;
+ unsigned int channels = afe_priv->cm_channels;
+
+ dev_dbg(afe->dev, "event 0x%x, name %s, channels %u\n",
+ event, w->name, channels);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ mt8196_enable_cm_bypass(afe, CM1, false);
+ mt8196_set_cm(afe, CM1, true, false, channels);
+ regmap_update_bits(afe->regmap, AUDIO_TOP_CON0,
+ PDN_CM1_MASK_SFT, 0 << PDN_CM1_SFT);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ mt8196_enable_cm_bypass(afe, CM1, true);
+ regmap_update_bits(afe->regmap, AUDIO_TOP_CON0,
+ PDN_CM1_MASK_SFT, 1 << PDN_CM1_SFT);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int ul_cm2_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol,
+ int event)
+{
+ struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm);
+ struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt);
+ struct mt8196_afe_private *afe_priv = afe->platform_priv;
+ unsigned int channels = afe_priv->cm_channels;
+
+ dev_dbg(afe->dev, "event 0x%x, name %s, channels %u\n",
+ event, w->name, channels);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ mt8196_enable_cm_bypass(afe, CM2, false);
+ mt8196_set_cm(afe, CM2, true, false, channels);
+ regmap_update_bits(afe->regmap, AUDIO_TOP_CON0,
+ PDN_CM2_MASK_SFT, 0 << PDN_CM2_SFT);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ mt8196_enable_cm_bypass(afe, CM2, true);
+ regmap_update_bits(afe->regmap, AUDIO_TOP_CON0,
+ PDN_CM2_MASK_SFT, 1 << PDN_CM2_SFT);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * dma widget & routes
+ * The mixer controls and routes are by no means fully implemented,
+ * only the ones that are intended to be used are, as other wise a fully
+ * interconnected switch bar mixer would introduce way too many unused
+ * controls.
+ */
+static const struct snd_kcontrol_new memif_ul0_ch1_mix[] = {
+ /* Normal record */
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN018_0,
+ I_ADDA_UL_CH1, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul0_ch2_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN019_0,
+ I_ADDA_UL_CH2, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul1_ch1_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN4_CH1", AFE_CONN020_4,
+ I_I2SIN4_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN6_CH1", AFE_CONN020_5,
+ I_I2SIN6_CH1, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul1_ch2_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN4_CH2", AFE_CONN021_4,
+ I_I2SIN4_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN6_CH2", AFE_CONN021_5,
+ I_I2SIN6_CH2, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul2_ch1_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN022_0,
+ I_ADDA_UL_CH3, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul2_ch2_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN023_0,
+ I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul3_ch1_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN0_CH1", AFE_CONN024_4,
+ I_I2SIN0_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN1_CH1", AFE_CONN024_4,
+ I_I2SIN1_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN3_CH1", AFE_CONN024_4,
+ I_I2SIN3_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN4_CH1", AFE_CONN024_4,
+ I_I2SIN4_CH1, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul3_ch2_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN0_CH2", AFE_CONN025_4,
+ I_I2SIN0_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN1_CH2", AFE_CONN025_4,
+ I_I2SIN1_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN3_CH2", AFE_CONN025_4,
+ I_I2SIN3_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN4_CH2", AFE_CONN025_4,
+ I_I2SIN4_CH2, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul4_ch1_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN026_0,
+ I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("DL0_CH1", AFE_CONN026_1,
+ I_DL0_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("DL1_CH1", AFE_CONN026_1,
+ I_DL1_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("DL6_CH1", AFE_CONN026_1,
+ I_DL6_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("DL2_CH1", AFE_CONN026_1,
+ I_DL2_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("DL3_CH1", AFE_CONN026_1,
+ I_DL3_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("DL_24CH_CH1", AFE_CONN026_1,
+ I_DL_24CH_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN0_CH1", AFE_CONN026_4,
+ I_I2SIN0_CH1, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul4_ch2_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN027_0,
+ I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("DL0_CH2", AFE_CONN027_1,
+ I_DL0_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("DL1_CH2", AFE_CONN027_1,
+ I_DL1_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("DL6_CH2", AFE_CONN027_1,
+ I_DL6_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("DL2_CH2", AFE_CONN027_1,
+ I_DL2_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("DL3_CH2", AFE_CONN027_1,
+ I_DL3_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("DL_24CH_CH2", AFE_CONN027_1,
+ I_DL_24CH_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN0_CH2", AFE_CONN027_4,
+ I_I2SIN0_CH2, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul5_ch1_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN3_CH1", AFE_CONN028_4,
+ I_I2SIN3_CH1, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul5_ch2_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN3_CH2", AFE_CONN029_4,
+ I_I2SIN3_CH2, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul6_ch1_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN030_0,
+ I_ADDA_UL_CH1, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul6_ch2_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN031_0,
+ I_ADDA_UL_CH2, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul7_ch1_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN032_0,
+ I_ADDA_UL_CH1, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul7_ch2_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN033_0,
+ I_ADDA_UL_CH2, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul8_ch1_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN034_0,
+ I_ADDA_UL_CH1, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul8_ch2_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN035_0,
+ I_ADDA_UL_CH1, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul9_ch1_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN036_0,
+ I_ADDA_UL_CH1, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul9_ch2_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN037_0,
+ I_ADDA_UL_CH2, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul10_ch1_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN038_0,
+ I_ADDA_UL_CH1, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul10_ch2_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN039_0,
+ I_ADDA_UL_CH2, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul24_ch1_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN0_CH1", AFE_CONN066_4,
+ I_I2SIN0_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN6_CH1", AFE_CONN066_5,
+ I_I2SIN6_CH1, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul24_ch2_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN0_CH2", AFE_CONN067_4,
+ I_I2SIN0_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN6_CH2", AFE_CONN067_5,
+ I_I2SIN6_CH2, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul25_ch1_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN0_CH1", AFE_CONN068_4,
+ I_I2SIN0_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN6_CH1", AFE_CONN068_5,
+ I_I2SIN6_CH1, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul25_ch2_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN0_CH2", AFE_CONN069_4,
+ I_I2SIN0_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN6_CH2", AFE_CONN069_5,
+ I_I2SIN6_CH2, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul26_ch1_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN0_CH1", AFE_CONN070_4,
+ I_I2SIN0_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN6_CH1", AFE_CONN070_5,
+ I_I2SIN6_CH1, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul26_ch2_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN0_CH2", AFE_CONN071_4,
+ I_I2SIN0_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("I2SIN6_CH2", AFE_CONN071_5,
+ I_I2SIN6_CH2, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm0_ch1_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN040_0,
+ I_ADDA_UL_CH1, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm0_ch2_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN041_0,
+ I_ADDA_UL_CH2, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm0_ch3_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN042_0,
+ I_ADDA_UL_CH3, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm0_ch4_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN043_0,
+ I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm0_ch5_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN044_0,
+ I_ADDA_UL_CH1, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm0_ch6_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN045_0,
+ I_ADDA_UL_CH1, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm0_ch7_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN046_0,
+ I_ADDA_UL_CH1, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm0_ch8_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN047_0,
+ I_ADDA_UL_CH1, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm1_ch1_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN048_0,
+ I_ADDA_UL_CH1, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm1_ch2_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN049_0,
+ I_ADDA_UL_CH2, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm1_ch3_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN050_0,
+ I_ADDA_UL_CH3, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm1_ch4_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN051_0,
+ I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm1_ch5_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN052_0,
+ I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN052_0,
+ I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN052_0,
+ I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN052_0,
+ I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm1_ch6_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN053_0,
+ I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN053_0,
+ I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN053_0,
+ I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN053_0,
+ I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm1_ch7_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN054_0,
+ I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN054_0,
+ I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN054_0,
+ I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN054_0,
+ I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm1_ch8_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN055_0,
+ I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN055_0,
+ I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN055_0,
+ I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN055_0,
+ I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm1_ch9_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN056_0,
+ I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN056_0,
+ I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN056_0,
+ I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN056_0,
+ I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm1_ch10_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN057_0,
+ I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN057_0,
+ I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN057_0,
+ I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN057_0,
+ I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm1_ch11_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN058_0,
+ I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN058_0,
+ I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN058_0,
+ I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN058_0,
+ I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm1_ch12_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN059_0,
+ I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN059_0,
+ I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN059_0,
+ I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN059_0,
+ I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm1_ch13_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN060_0,
+ I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN060_0,
+ I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN060_0,
+ I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN060_0,
+ I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm1_ch14_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN061_0,
+ I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN061_0,
+ I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN061_0,
+ I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN061_0,
+ I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm1_ch15_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN062_0,
+ I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN062_0,
+ I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN062_0,
+ I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN062_0,
+ I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm1_ch16_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN063_0,
+ I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN063_0,
+ I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN063_0,
+ I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN063_0,
+ I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch1_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN064_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN064_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN064_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN064_0, I_ADDA_UL_CH4, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH5", AFE_CONN064_0, I_ADDA_UL_CH5, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH6", AFE_CONN064_0, I_ADDA_UL_CH6, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch2_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN065_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN065_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN065_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN065_0, I_ADDA_UL_CH4, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH5", AFE_CONN065_0, I_ADDA_UL_CH5, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH6", AFE_CONN065_0, I_ADDA_UL_CH6, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch3_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN066_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN066_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN066_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN066_0, I_ADDA_UL_CH4, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH5", AFE_CONN066_0, I_ADDA_UL_CH5, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH6", AFE_CONN066_0, I_ADDA_UL_CH6, 1, 0)
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch4_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN067_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN067_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN067_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN067_0, I_ADDA_UL_CH4, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH5", AFE_CONN067_0, I_ADDA_UL_CH5, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH6", AFE_CONN067_0, I_ADDA_UL_CH6, 1, 0)
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch5_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN068_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN068_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN068_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN068_0, I_ADDA_UL_CH4, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH5", AFE_CONN068_0, I_ADDA_UL_CH5, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH6", AFE_CONN068_0, I_ADDA_UL_CH6, 1, 0)
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch6_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN069_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN069_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN069_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN069_0, I_ADDA_UL_CH4, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH5", AFE_CONN069_0, I_ADDA_UL_CH5, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH6", AFE_CONN069_0, I_ADDA_UL_CH6, 1, 0)
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch7_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN070_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN070_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN070_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN070_0, I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch8_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN071_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN071_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN071_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN071_0, I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch9_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN072_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN072_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN072_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN072_0, I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch10_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN073_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN073_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN073_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN073_0, I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch11_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN074_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN074_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN074_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN074_0, I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch12_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN075_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN075_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN075_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN075_0, I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch13_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN076_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN076_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN076_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN076_0, I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch14_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN077_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN077_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN077_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN077_0, I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch15_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN078_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN078_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN078_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN078_0, I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch16_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN079_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN079_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN079_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN079_0, I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch17_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN080_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN080_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN080_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN080_0, I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch18_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN081_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN081_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN081_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN081_0, I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch19_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN082_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN082_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN082_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN082_0, I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch20_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN083_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN083_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN083_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN083_0, I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch21_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN084_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN084_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN084_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN084_0, I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch22_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN085_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN085_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN085_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN085_0, I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch23_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN086_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN086_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN086_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN086_0, I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch24_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN087_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN087_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN087_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN087_0, I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch25_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN088_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN088_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN088_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN088_0, I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch26_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN089_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN089_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN089_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN089_0, I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch27_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN090_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN090_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN090_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN090_0, I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch28_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN091_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN091_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN091_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN091_0, I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch29_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN092_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN092_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN092_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN092_0, I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch30_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN093_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN093_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN093_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN093_0, I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch31_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN094_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN094_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN094_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN094_0, I_ADDA_UL_CH4, 1, 0),
+};
+
+static const struct snd_kcontrol_new memif_ul_cm2_ch32_mix[] = {
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH1", AFE_CONN095_0, I_ADDA_UL_CH1, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH2", AFE_CONN095_0, I_ADDA_UL_CH2, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH3", AFE_CONN095_0, I_ADDA_UL_CH3, 1, 0),
+ SOC_DAPM_SINGLE_AUTODISABLE("ADDA_UL_CH4", AFE_CONN095_0, I_ADDA_UL_CH4, 1, 0),
+};
+
+static const char * const cm0_mux_map[] = {
+ "UL8_2CH_PATH",
+ "CM0_8CH_PATH",
+};
+
+static const char * const cm1_mux_map[] = {
+ "UL9_2CH_PATH",
+ "CM1_16CH_PATH",
+};
+
+static const char * const cm2_mux_map[] = {
+ "UL10_2CH_PATH",
+ "CM2_32CH_PATH",
+};
+
+static SOC_ENUM_SINGLE_DECL(ul_cm0_mux_map_enum, AFE_CM0_CON0,
+ AFE_CM0_OUTPUT_MUX_SFT, cm0_mux_map);
+
+static SOC_ENUM_SINGLE_DECL(ul_cm1_mux_map_enum, AFE_CM1_CON0,
+ AFE_CM1_OUTPUT_MUX_SFT, cm1_mux_map);
+
+static SOC_ENUM_SINGLE_DECL(ul_cm2_mux_map_enum, AFE_CM2_CON0,
+ AFE_CM2_OUTPUT_MUX_SFT, cm2_mux_map);
+
+static const struct snd_kcontrol_new ul_cm0_mux_control =
+ SOC_DAPM_ENUM("CM0_UL_MUX Route", ul_cm0_mux_map_enum);
+
+static const struct snd_kcontrol_new ul_cm1_mux_control =
+ SOC_DAPM_ENUM("CM1_UL_MUX Route", ul_cm1_mux_map_enum);
+
+static const struct snd_kcontrol_new ul_cm2_mux_control =
+ SOC_DAPM_ENUM("CM2_UL_MUX Route", ul_cm2_mux_map_enum);
+
+static const struct snd_soc_dapm_widget mt8196_memif_widgets[] = {
+ /* inter-connections */
+ SND_SOC_DAPM_MIXER("UL0_CH1", SND_SOC_NOPM, 0, 0,
+ memif_ul0_ch1_mix, ARRAY_SIZE(memif_ul0_ch1_mix)),
+ SND_SOC_DAPM_MIXER("UL0_CH2", SND_SOC_NOPM, 0, 0,
+ memif_ul0_ch2_mix, ARRAY_SIZE(memif_ul0_ch2_mix)),
+
+ SND_SOC_DAPM_MIXER("UL1_CH1", SND_SOC_NOPM, 0, 0,
+ memif_ul1_ch1_mix, ARRAY_SIZE(memif_ul1_ch1_mix)),
+ SND_SOC_DAPM_MIXER("UL1_CH2", SND_SOC_NOPM, 0, 0,
+ memif_ul1_ch2_mix, ARRAY_SIZE(memif_ul1_ch2_mix)),
+
+ SND_SOC_DAPM_MIXER("UL2_CH1", SND_SOC_NOPM, 0, 0,
+ memif_ul2_ch1_mix, ARRAY_SIZE(memif_ul2_ch1_mix)),
+ SND_SOC_DAPM_MIXER("UL2_CH2", SND_SOC_NOPM, 0, 0,
+ memif_ul2_ch2_mix, ARRAY_SIZE(memif_ul2_ch2_mix)),
+
+ SND_SOC_DAPM_MIXER("UL3_CH1", SND_SOC_NOPM, 0, 0,
+ memif_ul3_ch1_mix, ARRAY_SIZE(memif_ul3_ch1_mix)),
+ SND_SOC_DAPM_MIXER("UL3_CH2", SND_SOC_NOPM, 0, 0,
+ memif_ul3_ch2_mix, ARRAY_SIZE(memif_ul3_ch2_mix)),
+
+ SND_SOC_DAPM_MIXER("UL4_CH1", SND_SOC_NOPM, 0, 0,
+ memif_ul4_ch1_mix, ARRAY_SIZE(memif_ul4_ch1_mix)),
+ SND_SOC_DAPM_MIXER("UL4_CH2", SND_SOC_NOPM, 0, 0,
+ memif_ul4_ch2_mix, ARRAY_SIZE(memif_ul4_ch2_mix)),
+
+ SND_SOC_DAPM_MIXER("UL5_CH1", SND_SOC_NOPM, 0, 0,
+ memif_ul5_ch1_mix, ARRAY_SIZE(memif_ul5_ch1_mix)),
+ SND_SOC_DAPM_MIXER("UL5_CH2", SND_SOC_NOPM, 0, 0,
+ memif_ul5_ch2_mix, ARRAY_SIZE(memif_ul5_ch2_mix)),
+
+ SND_SOC_DAPM_MIXER("UL6_CH1", SND_SOC_NOPM, 0, 0,
+ memif_ul6_ch1_mix, ARRAY_SIZE(memif_ul6_ch1_mix)),
+ SND_SOC_DAPM_MIXER("UL6_CH2", SND_SOC_NOPM, 0, 0,
+ memif_ul6_ch2_mix, ARRAY_SIZE(memif_ul6_ch2_mix)),
+
+ SND_SOC_DAPM_MIXER("UL7_CH1", SND_SOC_NOPM, 0, 0,
+ memif_ul7_ch1_mix, ARRAY_SIZE(memif_ul7_ch1_mix)),
+ SND_SOC_DAPM_MIXER("UL7_CH2", SND_SOC_NOPM, 0, 0,
+ memif_ul7_ch2_mix, ARRAY_SIZE(memif_ul7_ch2_mix)),
+
+ SND_SOC_DAPM_MIXER("UL8_CH1", SND_SOC_NOPM, 0, 0,
+ memif_ul8_ch1_mix, ARRAY_SIZE(memif_ul8_ch1_mix)),
+ SND_SOC_DAPM_MIXER("UL8_CH2", SND_SOC_NOPM, 0, 0,
+ memif_ul8_ch2_mix, ARRAY_SIZE(memif_ul8_ch2_mix)),
+
+ SND_SOC_DAPM_MIXER("UL9_CH1", SND_SOC_NOPM, 0, 0,
+ memif_ul9_ch1_mix, ARRAY_SIZE(memif_ul9_ch1_mix)),
+ SND_SOC_DAPM_MIXER("UL9_CH2", SND_SOC_NOPM, 0, 0,
+ memif_ul9_ch2_mix, ARRAY_SIZE(memif_ul9_ch2_mix)),
+
+ SND_SOC_DAPM_MIXER("UL10_CH1", SND_SOC_NOPM, 0, 0,
+ memif_ul10_ch1_mix, ARRAY_SIZE(memif_ul10_ch1_mix)),
+ SND_SOC_DAPM_MIXER("UL10_CH2", SND_SOC_NOPM, 0, 0,
+ memif_ul10_ch2_mix, ARRAY_SIZE(memif_ul10_ch2_mix)),
+
+ SND_SOC_DAPM_MIXER("UL24_CH1", SND_SOC_NOPM, 0, 0,
+ memif_ul24_ch1_mix, ARRAY_SIZE(memif_ul24_ch1_mix)),
+ SND_SOC_DAPM_MIXER("UL24_CH2", SND_SOC_NOPM, 0, 0,
+ memif_ul24_ch2_mix, ARRAY_SIZE(memif_ul24_ch2_mix)),
+
+ SND_SOC_DAPM_MIXER("UL25_CH1", SND_SOC_NOPM, 0, 0,
+ memif_ul25_ch1_mix, ARRAY_SIZE(memif_ul25_ch1_mix)),
+ SND_SOC_DAPM_MIXER("UL25_CH2", SND_SOC_NOPM, 0, 0,
+ memif_ul25_ch2_mix, ARRAY_SIZE(memif_ul25_ch2_mix)),
+
+ SND_SOC_DAPM_MIXER("UL26_CH1", SND_SOC_NOPM, 0, 0,
+ memif_ul26_ch1_mix, ARRAY_SIZE(memif_ul26_ch1_mix)),
+ SND_SOC_DAPM_MIXER("UL26_CH2", SND_SOC_NOPM, 0, 0,
+ memif_ul26_ch2_mix, ARRAY_SIZE(memif_ul26_ch2_mix)),
+
+ SND_SOC_DAPM_MIXER("UL_CM0_CH1", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm0_ch1_mix, ARRAY_SIZE(memif_ul_cm0_ch1_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM0_CH2", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm0_ch2_mix, ARRAY_SIZE(memif_ul_cm0_ch2_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM0_CH3", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm0_ch3_mix, ARRAY_SIZE(memif_ul_cm0_ch3_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM0_CH4", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm0_ch4_mix, ARRAY_SIZE(memif_ul_cm0_ch4_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM0_CH5", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm0_ch5_mix, ARRAY_SIZE(memif_ul_cm0_ch5_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM0_CH6", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm0_ch6_mix, ARRAY_SIZE(memif_ul_cm0_ch6_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM0_CH7", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm0_ch7_mix, ARRAY_SIZE(memif_ul_cm0_ch7_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM0_CH8", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm0_ch8_mix, ARRAY_SIZE(memif_ul_cm0_ch8_mix)),
+ SND_SOC_DAPM_MUX("CM0_UL_MUX", SND_SOC_NOPM, 0, 0,
+ &ul_cm0_mux_control),
+
+ SND_SOC_DAPM_MIXER("UL_CM1_CH1", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm1_ch1_mix, ARRAY_SIZE(memif_ul_cm1_ch1_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM1_CH2", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm1_ch2_mix, ARRAY_SIZE(memif_ul_cm1_ch2_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM1_CH3", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm1_ch3_mix, ARRAY_SIZE(memif_ul_cm1_ch3_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM1_CH4", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm1_ch4_mix, ARRAY_SIZE(memif_ul_cm1_ch4_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM1_CH5", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm1_ch5_mix, ARRAY_SIZE(memif_ul_cm1_ch5_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM1_CH6", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm1_ch6_mix, ARRAY_SIZE(memif_ul_cm1_ch6_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM1_CH7", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm1_ch7_mix, ARRAY_SIZE(memif_ul_cm1_ch7_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM1_CH8", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm1_ch8_mix, ARRAY_SIZE(memif_ul_cm1_ch8_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM1_CH9", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm1_ch9_mix, ARRAY_SIZE(memif_ul_cm1_ch9_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM1_CH10", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm1_ch10_mix, ARRAY_SIZE(memif_ul_cm1_ch10_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM1_CH11", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm1_ch11_mix, ARRAY_SIZE(memif_ul_cm1_ch11_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM1_CH12", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm1_ch12_mix, ARRAY_SIZE(memif_ul_cm1_ch12_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM1_CH13", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm1_ch13_mix, ARRAY_SIZE(memif_ul_cm1_ch13_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM1_CH14", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm1_ch14_mix, ARRAY_SIZE(memif_ul_cm1_ch14_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM1_CH15", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm1_ch15_mix, ARRAY_SIZE(memif_ul_cm1_ch15_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM1_CH16", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm1_ch16_mix, ARRAY_SIZE(memif_ul_cm1_ch16_mix)),
+ SND_SOC_DAPM_MUX("CM1_UL_MUX", SND_SOC_NOPM, 0, 0,
+ &ul_cm1_mux_control),
+
+ SND_SOC_DAPM_MIXER("UL_CM2_CH1", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch1_mix, ARRAY_SIZE(memif_ul_cm2_ch1_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH2", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch2_mix, ARRAY_SIZE(memif_ul_cm2_ch2_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH3", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch3_mix, ARRAY_SIZE(memif_ul_cm2_ch3_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH4", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch4_mix, ARRAY_SIZE(memif_ul_cm2_ch4_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH5", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch5_mix, ARRAY_SIZE(memif_ul_cm2_ch5_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH6", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch6_mix, ARRAY_SIZE(memif_ul_cm2_ch6_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH7", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch7_mix, ARRAY_SIZE(memif_ul_cm2_ch7_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH8", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch8_mix, ARRAY_SIZE(memif_ul_cm2_ch8_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH9", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch9_mix, ARRAY_SIZE(memif_ul_cm2_ch9_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH10", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch10_mix, ARRAY_SIZE(memif_ul_cm2_ch10_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH11", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch11_mix, ARRAY_SIZE(memif_ul_cm2_ch11_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH12", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch12_mix, ARRAY_SIZE(memif_ul_cm2_ch12_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH13", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch13_mix, ARRAY_SIZE(memif_ul_cm2_ch13_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH14", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch14_mix, ARRAY_SIZE(memif_ul_cm2_ch14_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH15", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch15_mix, ARRAY_SIZE(memif_ul_cm2_ch15_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH16", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch16_mix, ARRAY_SIZE(memif_ul_cm2_ch16_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH17", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch17_mix, ARRAY_SIZE(memif_ul_cm2_ch17_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH18", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch18_mix, ARRAY_SIZE(memif_ul_cm2_ch18_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH19", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch19_mix, ARRAY_SIZE(memif_ul_cm2_ch19_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH20", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch20_mix, ARRAY_SIZE(memif_ul_cm2_ch20_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH21", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch21_mix, ARRAY_SIZE(memif_ul_cm2_ch21_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH22", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch22_mix, ARRAY_SIZE(memif_ul_cm2_ch22_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH23", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch23_mix, ARRAY_SIZE(memif_ul_cm2_ch23_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH24", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch24_mix, ARRAY_SIZE(memif_ul_cm2_ch24_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH25", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch25_mix, ARRAY_SIZE(memif_ul_cm2_ch25_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH26", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch26_mix, ARRAY_SIZE(memif_ul_cm2_ch26_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH27", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch27_mix, ARRAY_SIZE(memif_ul_cm2_ch27_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH28", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch28_mix, ARRAY_SIZE(memif_ul_cm2_ch28_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH29", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch29_mix, ARRAY_SIZE(memif_ul_cm2_ch29_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH30", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch30_mix, ARRAY_SIZE(memif_ul_cm2_ch30_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH31", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch31_mix, ARRAY_SIZE(memif_ul_cm2_ch31_mix)),
+ SND_SOC_DAPM_MIXER("UL_CM2_CH32", SND_SOC_NOPM, 0, 0,
+ memif_ul_cm2_ch32_mix, ARRAY_SIZE(memif_ul_cm2_ch32_mix)),
+ SND_SOC_DAPM_MUX("CM2_UL_MUX", SND_SOC_NOPM, 0, 0,
+ &ul_cm2_mux_control),
+
+ SND_SOC_DAPM_SUPPLY("CM0_Enable",
+ AFE_CM0_CON0, AFE_CM0_ON_SFT, 0,
+ ul_cm0_event,
+ SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_PRE_PMD),
+
+ SND_SOC_DAPM_SUPPLY("CM1_Enable",
+ AFE_CM1_CON0, AFE_CM1_ON_SFT, 0,
+ ul_cm1_event,
+ SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_PRE_PMD),
+
+ SND_SOC_DAPM_SUPPLY("CM2_Enable",
+ AFE_CM2_CON0, AFE_CM2_ON_SFT, 0,
+ ul_cm2_event,
+ SND_SOC_DAPM_PRE_PMU |
+ SND_SOC_DAPM_PRE_PMD),
+
+ /* dynamic pinctrl */
+ SND_SOC_DAPM_PINCTRL("I2S3_PIN", "aud-gpio-i2s3-on", "aud-gpio-i2s3-off"),
+ SND_SOC_DAPM_PINCTRL("I2S4_PIN", "aud-gpio-i2s4-on", "aud-gpio-i2s4-off"),
+ SND_SOC_DAPM_PINCTRL("I2S6_PIN", "aud-gpio-i2s6-on", "aud-gpio-i2s6-off"),
+ SND_SOC_DAPM_PINCTRL("AP_DMIC0_PIN", "aud-gpio-ap-dmic-on", "aud-gpio-ap-dmic-off"),
+ SND_SOC_DAPM_PINCTRL("AP_DMIC1_PIN", "aud-gpio-ap-dmic1-on", "aud-gpio-ap-dmic1-off"),
+};
+
+static const struct snd_soc_dapm_route mt8196_memif_routes[] = {
+ {"UL0", NULL, "UL0_CH1"},
+ {"UL0", NULL, "UL0_CH2"},
+ /* Normal record */
+ {"UL0_CH1", "ADDA_UL_CH1", "ADDA_UL_Mux"},
+ {"UL0_CH2", "ADDA_UL_CH2", "ADDA_UL_Mux"},
+
+ {"UL1", NULL, "UL1_CH1"},
+ {"UL1", NULL, "UL1_CH2"},
+ {"UL1_CH1", "I2SIN4_CH1", "I2SIN4"},
+ {"UL1_CH2", "I2SIN4_CH2", "I2SIN4"},
+ {"UL1_CH1", "I2SIN6_CH1", "I2SIN6"},
+ {"UL1_CH2", "I2SIN6_CH2", "I2SIN6"},
+
+ {"UL2", NULL, "UL2_CH1"},
+ {"UL2", NULL, "UL2_CH2"},
+ {"UL2_CH1", "ADDA_UL_CH3", "ADDA_CH34_UL_Mux"},
+ {"UL2_CH2", "ADDA_UL_CH4", "ADDA_CH34_UL_Mux"},
+
+ {"UL3", NULL, "UL3_CH1"},
+ {"UL3", NULL, "UL3_CH2"},
+ {"UL3_CH1", "I2SIN0_CH1", "I2SIN0"},
+ {"UL3_CH2", "I2SIN0_CH2", "I2SIN0"},
+ {"UL3_CH1", "I2SIN1_CH1", "I2SIN1"},
+ {"UL3_CH2", "I2SIN1_CH2", "I2SIN1"},
+ {"UL3_CH1", "I2SIN3_CH1", "I2SIN3"},
+ {"UL3_CH2", "I2SIN3_CH2", "I2SIN3"},
+ {"UL3_CH1", "I2SIN4_CH1", "I2SIN4"},
+ {"UL3_CH2", "I2SIN4_CH2", "I2SIN4"},
+
+ {"UL4", NULL, "UL4_CH1"},
+ {"UL4", NULL, "UL4_CH2"},
+ {"UL4_CH1", "ADDA_UL_CH1", "ADDA_UL_Mux"},
+ {"UL4_CH2", "ADDA_UL_CH2", "ADDA_UL_Mux"},
+ {"UL4_CH1", "I2SIN0_CH1", "I2SIN0"},
+ {"UL4_CH2", "I2SIN0_CH2", "I2SIN0"},
+
+ {"UL5", NULL, "UL5_CH1"},
+ {"UL5", NULL, "UL5_CH2"},
+ {"UL5_CH1", "I2SIN3_CH1", "I2SIN3"},
+ {"UL5_CH2", "I2SIN3_CH2", "I2SIN3"},
+
+ {"UL6", NULL, "UL6_CH1"},
+ {"UL6", NULL, "UL6_CH2"},
+ {"UL6_CH1", "ADDA_UL_CH1", "ADDA_UL_Mux"},
+ {"UL6_CH2", "ADDA_UL_CH2", "ADDA_UL_Mux"},
+
+ {"UL7", NULL, "UL7_CH1"},
+ {"UL7", NULL, "UL7_CH2"},
+ {"UL7_CH1", "ADDA_UL_CH1", "ADDA_UL_Mux"},
+ {"UL7_CH2", "ADDA_UL_CH2", "ADDA_UL_Mux"},
+
+ {"UL8", NULL, "CM0_UL_MUX"},
+ {"CM0_UL_MUX", "UL8_2CH_PATH", "UL8_CH1"},
+ {"CM0_UL_MUX", "UL8_2CH_PATH", "UL8_CH2"},
+ {"CM0_UL_MUX", "CM0_8CH_PATH", "UL_CM0_CH1"},
+ {"CM0_UL_MUX", "CM0_8CH_PATH", "UL_CM0_CH2"},
+ {"CM0_UL_MUX", "CM0_8CH_PATH", "UL_CM0_CH3"},
+ {"CM0_UL_MUX", "CM0_8CH_PATH", "UL_CM0_CH4"},
+ {"CM0_UL_MUX", "CM0_8CH_PATH", "UL_CM0_CH5"},
+ {"CM0_UL_MUX", "CM0_8CH_PATH", "UL_CM0_CH6"},
+ {"CM0_UL_MUX", "CM0_8CH_PATH", "UL_CM0_CH7"},
+ {"CM0_UL_MUX", "CM0_8CH_PATH", "UL_CM0_CH8"},
+
+ {"UL_CM0", NULL, "CM0_Enable"},
+
+ /* UL9 */
+ {"UL9", NULL, "CM1_UL_MUX"},
+ {"CM1_UL_MUX", "UL9_2CH_PATH", "UL9_CH1"},
+ {"CM1_UL_MUX", "UL9_2CH_PATH", "UL9_CH2"},
+ {"CM1_UL_MUX", "CM1_16CH_PATH", "UL_CM1_CH1"},
+ {"CM1_UL_MUX", "CM1_16CH_PATH", "UL_CM1_CH2"},
+ {"CM1_UL_MUX", "CM1_16CH_PATH", "UL_CM1_CH3"},
+ {"CM1_UL_MUX", "CM1_16CH_PATH", "UL_CM1_CH4"},
+ {"CM1_UL_MUX", "CM1_16CH_PATH", "UL_CM1_CH5"},
+ {"CM1_UL_MUX", "CM1_16CH_PATH", "UL_CM1_CH6"},
+ {"CM1_UL_MUX", "CM1_16CH_PATH", "UL_CM1_CH7"},
+ {"CM1_UL_MUX", "CM1_16CH_PATH", "UL_CM1_CH8"},
+ {"CM1_UL_MUX", "CM1_16CH_PATH", "UL_CM1_CH9"},
+ {"CM1_UL_MUX", "CM1_16CH_PATH", "UL_CM1_CH10"},
+ {"CM1_UL_MUX", "CM1_16CH_PATH", "UL_CM1_CH11"},
+ {"CM1_UL_MUX", "CM1_16CH_PATH", "UL_CM1_CH12"},
+ {"CM1_UL_MUX", "CM1_16CH_PATH", "UL_CM1_CH13"},
+ {"CM1_UL_MUX", "CM1_16CH_PATH", "UL_CM1_CH14"},
+ {"CM1_UL_MUX", "CM1_16CH_PATH", "UL_CM1_CH15"},
+ {"CM1_UL_MUX", "CM1_16CH_PATH", "UL_CM1_CH16"},
+
+ {"UL_CM1", NULL, "CM1_Enable"},
+
+ {"UL9_CH1", "ADDA_UL_CH1", "ADDA_UL_Mux"},
+ {"UL9_CH2", "ADDA_UL_CH2", "ADDA_UL_Mux"},
+
+ {"UL10", NULL, "CM2_UL_MUX"},
+ {"CM2_UL_MUX", "UL10_2CH_PATH", "UL10_CH1"},
+ {"CM2_UL_MUX", "UL10_2CH_PATH", "UL10_CH2"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH1"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH2"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH3"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH4"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH5"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH6"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH7"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH8"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH9"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH10"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH11"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH12"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH13"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH14"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH15"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH16"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH17"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH18"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH19"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH20"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH21"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH22"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH23"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH24"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH25"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH26"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH27"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH28"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH29"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH30"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH31"},
+ {"CM2_UL_MUX", "CM2_32CH_PATH", "UL_CM2_CH32"},
+
+ {"UL_CM2", NULL, "CM2_Enable"},
+
+ {"UL10_CH1", "ADDA_UL_CH1", "ADDA_UL_Mux"},
+ {"UL10_CH2", "ADDA_UL_CH2", "ADDA_UL_Mux"},
+
+ {"UL24", NULL, "UL24_CH1"},
+ {"UL24", NULL, "UL24_CH2"},
+ {"UL24_CH1", "I2SIN6_CH1", "I2SIN6"},
+ {"UL24_CH2", "I2SIN6_CH2", "I2SIN6"},
+ {"UL24_CH1", "I2SIN0_CH1", "I2SIN0"},
+ {"UL24_CH2", "I2SIN0_CH2", "I2SIN0"},
+
+ {"UL25", NULL, "UL25_CH1"},
+ {"UL25", NULL, "UL25_CH2"},
+ {"UL25_CH1", "I2SIN6_CH1", "I2SIN6"},
+ {"UL25_CH2", "I2SIN6_CH2", "I2SIN6"},
+ {"UL25_CH1", "I2SIN0_CH1", "I2SIN0"},
+ {"UL25_CH2", "I2SIN0_CH2", "I2SIN0"},
+
+ {"UL26", NULL, "UL26_CH1"},
+ {"UL26", NULL, "UL26_CH2"},
+ {"UL26_CH1", "I2SIN6_CH1", "I2SIN6"},
+ {"UL26_CH2", "I2SIN6_CH2", "I2SIN6"},
+ {"UL26_CH1", "I2SIN0_CH1", "I2SIN0"},
+ {"UL26_CH2", "I2SIN0_CH2", "I2SIN0"},
+
+ {"UL_CM0", NULL, "UL_CM0_CH1"},
+ {"UL_CM0", NULL, "UL_CM0_CH2"},
+ {"UL_CM0", NULL, "UL_CM0_CH3"},
+ {"UL_CM0", NULL, "UL_CM0_CH4"},
+ {"UL_CM0", NULL, "UL_CM0_CH5"},
+ {"UL_CM0", NULL, "UL_CM0_CH6"},
+ {"UL_CM0", NULL, "UL_CM0_CH7"},
+ {"UL_CM0", NULL, "UL_CM0_CH8"},
+ {"UL_CM0_CH1", "ADDA_UL_CH1", "ADDA_UL_Mux"},
+ {"UL_CM0_CH2", "ADDA_UL_CH2", "ADDA_UL_Mux"},
+ {"UL_CM0_CH3", "ADDA_UL_CH3", "ADDA_CH34_UL_Mux"},
+ {"UL_CM0_CH4", "ADDA_UL_CH4", "ADDA_CH34_UL_Mux"},
+
+ {"UL_CM1", NULL, "UL_CM1_CH1"},
+ {"UL_CM1", NULL, "UL_CM1_CH2"},
+ {"UL_CM1", NULL, "UL_CM1_CH3"},
+ {"UL_CM1", NULL, "UL_CM1_CH4"},
+ {"UL_CM1", NULL, "UL_CM1_CH5"},
+ {"UL_CM1", NULL, "UL_CM1_CH6"},
+ {"UL_CM1", NULL, "UL_CM1_CH7"},
+ {"UL_CM1", NULL, "UL_CM1_CH8"},
+ {"UL_CM1", NULL, "UL_CM1_CH9"},
+ {"UL_CM1", NULL, "UL_CM1_CH10"},
+ {"UL_CM1", NULL, "UL_CM1_CH11"},
+ {"UL_CM1", NULL, "UL_CM1_CH12"},
+ {"UL_CM1", NULL, "UL_CM1_CH13"},
+ {"UL_CM1", NULL, "UL_CM1_CH14"},
+ {"UL_CM1", NULL, "UL_CM1_CH15"},
+ {"UL_CM1", NULL, "UL_CM1_CH16"},
+ {"UL_CM1_CH1", "ADDA_UL_CH1", "ADDA_UL_Mux"},
+ {"UL_CM1_CH2", "ADDA_UL_CH2", "ADDA_UL_Mux"},
+ {"UL_CM1_CH3", "ADDA_UL_CH3", "ADDA_CH34_UL_Mux"},
+ {"UL_CM1_CH4", "ADDA_UL_CH4", "ADDA_CH34_UL_Mux"},
+
+ {"UL_CM2", NULL, "UL_CM2_CH1"},
+ {"UL_CM2", NULL, "UL_CM2_CH2"},
+ {"UL_CM2", NULL, "UL_CM2_CH3"},
+ {"UL_CM2", NULL, "UL_CM2_CH4"},
+ {"UL_CM2", NULL, "UL_CM2_CH5"},
+ {"UL_CM2", NULL, "UL_CM2_CH6"},
+ {"UL_CM2", NULL, "UL_CM2_CH7"},
+ {"UL_CM2", NULL, "UL_CM2_CH8"},
+ {"UL_CM2", NULL, "UL_CM2_CH9"},
+ {"UL_CM2", NULL, "UL_CM2_CH10"},
+ {"UL_CM2", NULL, "UL_CM2_CH11"},
+ {"UL_CM2", NULL, "UL_CM2_CH12"},
+ {"UL_CM2", NULL, "UL_CM2_CH13"},
+ {"UL_CM2", NULL, "UL_CM2_CH14"},
+ {"UL_CM2", NULL, "UL_CM2_CH15"},
+ {"UL_CM2", NULL, "UL_CM2_CH16"},
+ {"UL_CM2", NULL, "UL_CM2_CH17"},
+ {"UL_CM2", NULL, "UL_CM2_CH18"},
+ {"UL_CM2", NULL, "UL_CM2_CH19"},
+ {"UL_CM2", NULL, "UL_CM2_CH20"},
+ {"UL_CM2", NULL, "UL_CM2_CH21"},
+ {"UL_CM2", NULL, "UL_CM2_CH22"},
+ {"UL_CM2", NULL, "UL_CM2_CH23"},
+ {"UL_CM2", NULL, "UL_CM2_CH24"},
+ {"UL_CM2", NULL, "UL_CM2_CH25"},
+ {"UL_CM2", NULL, "UL_CM2_CH26"},
+ {"UL_CM2", NULL, "UL_CM2_CH27"},
+ {"UL_CM2", NULL, "UL_CM2_CH28"},
+ {"UL_CM2", NULL, "UL_CM2_CH29"},
+ {"UL_CM2", NULL, "UL_CM2_CH30"},
+ {"UL_CM2", NULL, "UL_CM2_CH31"},
+ {"UL_CM2", NULL, "UL_CM2_CH32"},
+ {"UL_CM2_CH1", "ADDA_UL_CH1", "ADDA_UL_Mux"},
+ {"UL_CM2_CH2", "ADDA_UL_CH2", "ADDA_UL_Mux"},
+ {"UL_CM2_CH3", "ADDA_UL_CH3", "ADDA_CH34_UL_Mux"},
+ {"UL_CM2_CH4", "ADDA_UL_CH4", "ADDA_CH34_UL_Mux"},
+
+ /* Audio Pin */
+ {"I2SOUT4", NULL, "I2S4_PIN"},
+ {"I2SIN4", NULL, "I2S4_PIN"},
+ {"I2SOUT6", NULL, "I2S6_PIN"},
+ {"I2SIN6", NULL, "I2S6_PIN"},
+ {"I2SOUT3", NULL, "I2S3_PIN"},
+ {"I2SIN3", NULL, "I2S3_PIN"},
+ {"AP DMIC Capture", NULL, "AP_DMIC0_PIN"},
+ {"AP DMIC CH34 Capture", NULL, "AP_DMIC1_PIN"},
+};
+
+#define MT8196_DL_MEMIF(_id) \
+ [MT8196_MEMIF_##_id] = { \
+ .name = #_id, \
+ .id = MT8196_MEMIF_##_id, \
+ .reg_ofs_base = AFE_##_id##_BASE, \
+ .reg_ofs_cur = AFE_##_id##_CUR, \
+ .reg_ofs_end = AFE_##_id##_END, \
+ .reg_ofs_base_msb = AFE_##_id##_BASE_MSB, \
+ .reg_ofs_cur_msb = AFE_##_id##_CUR_MSB, \
+ .reg_ofs_end_msb = AFE_##_id##_END_MSB, \
+ .fs_reg = AFE_##_id##_CON0, \
+ .fs_shift = _id##_SEL_FS_SFT, \
+ .fs_maskbit = _id##_SEL_FS_MASK, \
+ .mono_reg = AFE_##_id##_CON0, \
+ .mono_shift = _id##_MONO_SFT, \
+ .enable_reg = AFE_##_id##_CON0, \
+ .enable_shift = _id##_ON_SFT, \
+ .hd_reg = AFE_##_id##_CON0, \
+ .hd_shift = _id##_HD_MODE_SFT, \
+ .hd_align_reg = AFE_##_id##_CON0, \
+ .hd_align_mshift = _id##_HALIGN_SFT, \
+ .agent_disable_reg = -1, \
+ .agent_disable_shift = -1, \
+ .msb_reg = -1, \
+ .msb_shift = -1, \
+ .pbuf_reg = AFE_##_id##_CON0, \
+ .pbuf_mask = _id##_PBUF_SIZE_MASK, \
+ .pbuf_shift = _id##_PBUF_SIZE_SFT, \
+ .minlen_reg = AFE_##_id##_CON0, \
+ .minlen_mask = _id##_MINLEN_MASK, \
+ .minlen_shift = _id##_MINLEN_SFT, \
+}
+
+#define MT8196_MULTI_DL_MEMIF(_id) \
+ [MT8196_MEMIF_##_id] = { \
+ .name = #_id, \
+ .id = MT8196_MEMIF_##_id, \
+ .reg_ofs_base = AFE_##_id##_BASE, \
+ .reg_ofs_cur = AFE_##_id##_CUR, \
+ .reg_ofs_end = AFE_##_id##_END, \
+ .reg_ofs_base_msb = AFE_##_id##_BASE_MSB, \
+ .reg_ofs_cur_msb = AFE_##_id##_CUR_MSB, \
+ .reg_ofs_end_msb = AFE_##_id##_END_MSB, \
+ .fs_reg = AFE_##_id##_CON0, \
+ .fs_shift = _id##_SEL_FS_SFT, \
+ .fs_maskbit = _id##_SEL_FS_MASK, \
+ .mono_reg = -1, \
+ .mono_shift = -1, \
+ .enable_reg = AFE_##_id##_CON0, \
+ .enable_shift = _id##_ON_SFT, \
+ .hd_reg = AFE_##_id##_CON0, \
+ .hd_shift = _id##_HD_MODE_SFT, \
+ .hd_align_reg = AFE_##_id##_CON0, \
+ .hd_align_mshift = _id##_HALIGN_SFT, \
+ .agent_disable_reg = -1, \
+ .agent_disable_shift = -1, \
+ .msb_reg = -1, \
+ .msb_shift = -1, \
+ .pbuf_reg = AFE_##_id##_CON0, \
+ .pbuf_mask = _id##_PBUF_SIZE_MASK, \
+ .pbuf_shift = _id##_PBUF_SIZE_SFT, \
+ .minlen_reg = AFE_##_id##_CON0, \
+ .minlen_mask = _id##_MINLEN_MASK, \
+ .minlen_shift = _id##_MINLEN_SFT, \
+ .ch_num_reg = AFE_##_id##_CON0, \
+ .ch_num_maskbit = _id##_NUM_MASK, \
+ .ch_num_shift = _id##_NUM_SFT, \
+}
+
+#define MT8196_UL_MEMIF(_id, _fs_shift, _fs_maskbit, _mono_shift) \
+ [MT8196_MEMIF_##_id] = { \
+ .name = #_id, \
+ .id = MT8196_MEMIF_##_id, \
+ .reg_ofs_base = AFE_##_id##_BASE, \
+ .reg_ofs_cur = AFE_##_id##_CUR, \
+ .reg_ofs_end = AFE_##_id##_END, \
+ .reg_ofs_base_msb = AFE_##_id##_BASE_MSB, \
+ .reg_ofs_cur_msb = AFE_##_id##_CUR_MSB, \
+ .reg_ofs_end_msb = AFE_##_id##_END_MSB, \
+ .fs_reg = AFE_##_id##_CON0, \
+ .fs_shift = _fs_shift, \
+ .fs_maskbit = _fs_maskbit, \
+ .mono_reg = AFE_##_id##_CON0, \
+ .mono_shift = _mono_shift, \
+ .enable_reg = AFE_##_id##_CON0, \
+ .enable_shift = _id##_ON_SFT, \
+ .hd_reg = AFE_##_id##_CON0, \
+ .hd_shift = _id##_HD_MODE_SFT, \
+ .hd_align_reg = AFE_##_id##_CON0, \
+ .hd_align_mshift = _id##_HALIGN_SFT, \
+ .agent_disable_reg = -1, \
+ .agent_disable_shift = -1, \
+ .msb_reg = -1, \
+ .msb_shift = -1, \
+ }
+
+/* For convenience with macros: missing register fields */
+#define HDMI_SEL_FS_SFT -1
+#define HDMI_SEL_FS_MASK -1
+
+/* For convenience with macros: register name differences */
+#define AFE_HDMI_BASE AFE_HDMI_OUT_BASE
+#define AFE_HDMI_CUR AFE_HDMI_OUT_CUR
+#define AFE_HDMI_END AFE_HDMI_OUT_END
+#define AFE_HDMI_BASE_MSB AFE_HDMI_OUT_BASE_MSB
+#define AFE_HDMI_CUR_MSB AFE_HDMI_OUT_CUR_MSB
+#define AFE_HDMI_END_MSB AFE_HDMI_OUT_END_MSB
+#define AFE_HDMI_CON0 AFE_HDMI_OUT_CON0
+#define HDMI_ON_SFT HDMI_OUT_ON_SFT
+#define HDMI_HD_MODE_SFT HDMI_OUT_HD_MODE_SFT
+#define HDMI_HALIGN_SFT HDMI_OUT_HALIGN_SFT
+#define HDMI_PBUF_SIZE_MASK HDMI_OUT_PBUF_SIZE_MASK
+#define HDMI_PBUF_SIZE_SFT HDMI_OUT_PBUF_SIZE_SFT
+#define HDMI_MINLEN_MASK HDMI_OUT_MINLEN_MASK
+#define HDMI_MINLEN_SFT HDMI_OUT_MINLEN_SFT
+#define HDMI_NUM_MASK HDMI_CH_NUM_MASK
+#define HDMI_NUM_SFT HDMI_CH_NUM_SFT
+
+static const struct mtk_base_memif_data memif_data[MT8196_MEMIF_NUM] = {
+ MT8196_DL_MEMIF(DL0),
+ MT8196_DL_MEMIF(DL1),
+ MT8196_DL_MEMIF(DL2),
+ MT8196_DL_MEMIF(DL3),
+ MT8196_DL_MEMIF(DL4),
+ MT8196_DL_MEMIF(DL5),
+ MT8196_DL_MEMIF(DL6),
+ MT8196_DL_MEMIF(DL7),
+ MT8196_DL_MEMIF(DL8),
+ MT8196_DL_MEMIF(DL23),
+ MT8196_DL_MEMIF(DL24),
+ MT8196_DL_MEMIF(DL25),
+ MT8196_DL_MEMIF(DL26),
+ MT8196_MULTI_DL_MEMIF(DL_4CH),
+ MT8196_MULTI_DL_MEMIF(DL_24CH),
+ MT8196_MULTI_DL_MEMIF(HDMI),
+ MT8196_UL_MEMIF(VUL0, VUL0_SEL_FS_SFT, VUL0_SEL_FS_MASK, VUL0_MONO_SFT),
+ MT8196_UL_MEMIF(VUL1, VUL1_SEL_FS_SFT, VUL1_SEL_FS_MASK, VUL1_MONO_SFT),
+ MT8196_UL_MEMIF(VUL2, VUL2_SEL_FS_SFT, VUL2_SEL_FS_MASK, VUL2_MONO_SFT),
+ MT8196_UL_MEMIF(VUL3, VUL3_SEL_FS_SFT, VUL3_SEL_FS_MASK, VUL3_MONO_SFT),
+ MT8196_UL_MEMIF(VUL4, VUL4_SEL_FS_SFT, VUL4_SEL_FS_MASK, VUL4_MONO_SFT),
+ MT8196_UL_MEMIF(VUL5, VUL5_SEL_FS_SFT, VUL5_SEL_FS_MASK, VUL5_MONO_SFT),
+ MT8196_UL_MEMIF(VUL6, VUL6_SEL_FS_SFT, VUL6_SEL_FS_MASK, VUL6_MONO_SFT),
+ MT8196_UL_MEMIF(VUL7, VUL7_SEL_FS_SFT, VUL7_SEL_FS_MASK, VUL7_MONO_SFT),
+ MT8196_UL_MEMIF(VUL8, VUL8_SEL_FS_SFT, VUL8_SEL_FS_MASK, VUL8_MONO_SFT),
+ MT8196_UL_MEMIF(VUL9, VUL9_SEL_FS_SFT, VUL9_SEL_FS_MASK, VUL9_MONO_SFT),
+ MT8196_UL_MEMIF(VUL10, VUL10_SEL_FS_SFT, VUL10_SEL_FS_MASK, VUL10_MONO_SFT),
+ MT8196_UL_MEMIF(VUL24, VUL24_SEL_FS_SFT, VUL24_SEL_FS_MASK, VUL24_MONO_SFT),
+ MT8196_UL_MEMIF(VUL25, VUL25_SEL_FS_SFT, VUL25_SEL_FS_MASK, VUL25_MONO_SFT),
+ MT8196_UL_MEMIF(VUL26, VUL26_SEL_FS_SFT, VUL26_SEL_FS_MASK, VUL26_MONO_SFT),
+ MT8196_UL_MEMIF(VUL_CM0, -1, -1, -1),
+ MT8196_UL_MEMIF(VUL_CM1, -1, -1, -1),
+ MT8196_UL_MEMIF(VUL_CM2, -1, -1, -1),
+ MT8196_UL_MEMIF(ETDM_IN0, REG_FS_TIMING_SEL_SFT, REG_FS_TIMING_SEL_MASK, -1),
+ MT8196_UL_MEMIF(ETDM_IN1, REG_FS_TIMING_SEL_SFT, REG_FS_TIMING_SEL_MASK, -1),
+ MT8196_UL_MEMIF(ETDM_IN2, REG_FS_TIMING_SEL_SFT, REG_FS_TIMING_SEL_MASK, -1),
+ MT8196_UL_MEMIF(ETDM_IN3, REG_FS_TIMING_SEL_SFT, REG_FS_TIMING_SEL_MASK, -1),
+ MT8196_UL_MEMIF(ETDM_IN4, REG_FS_TIMING_SEL_SFT, REG_FS_TIMING_SEL_MASK, -1),
+ MT8196_UL_MEMIF(ETDM_IN6, REG_FS_TIMING_SEL_SFT, REG_FS_TIMING_SEL_MASK, -1),
+};
+
+#define MT8196_AFE_IRQ(_id) \
+ [MT8196_IRQ_##_id] = { \
+ .id = MT8196_IRQ_##_id, \
+ .irq_cnt_reg = AFE_IRQ##_id##_MCU_CFG1, \
+ .irq_cnt_shift = AFE_IRQ_CNT_SHIFT, \
+ .irq_cnt_maskbit = AFE_IRQ_CNT_MASK, \
+ .irq_fs_reg = AFE_IRQ##_id##_MCU_CFG0, \
+ .irq_fs_shift = AFE_IRQ##_id##_MCU_FS_SFT, \
+ .irq_fs_maskbit = AFE_IRQ##_id##_MCU_FS_MASK, \
+ .irq_en_reg = AFE_IRQ##_id##_MCU_CFG0, \
+ .irq_en_shift = AFE_IRQ##_id##_MCU_ON_SFT, \
+ .irq_clr_reg = AFE_IRQ##_id##_MCU_CFG1, \
+ .irq_clr_shift = AFE_IRQ##_id##_CLR_CFG_SFT, \
+ }
+
+#define MT8196_AFE_TDM_IRQ(_id) \
+ [MT8196_IRQ_##_id] = { \
+ .id = MT8196_CUS_IRQ_TDM, \
+ .irq_cnt_reg = AFE_CUSTOM_IRQ0_MCU_CFG1, \
+ .irq_cnt_shift = AFE_CUSTOM_IRQ0_MCU_CNT_SFT, \
+ .irq_cnt_maskbit = AFE_CUSTOM_IRQ0_MCU_CNT_MASK, \
+ .irq_fs_reg = -1, \
+ .irq_fs_shift = -1, \
+ .irq_fs_maskbit = -1, \
+ .irq_en_reg = AFE_CUSTOM_IRQ0_MCU_CFG0, \
+ .irq_en_shift = AFE_CUSTOM_IRQ0_MCU_ON_SFT, \
+ .irq_clr_reg = AFE_CUSTOM_IRQ0_MCU_CFG1, \
+ .irq_clr_shift = AFE_CUSTOM_IRQ0_CLR_CFG_SFT, \
+ }
+
+static const struct mtk_base_irq_data irq_data[MT8196_IRQ_NUM] = {
+ MT8196_AFE_IRQ(0),
+ MT8196_AFE_IRQ(1),
+ MT8196_AFE_IRQ(2),
+ MT8196_AFE_IRQ(3),
+ MT8196_AFE_IRQ(4),
+ MT8196_AFE_IRQ(5),
+ MT8196_AFE_IRQ(6),
+ MT8196_AFE_IRQ(7),
+ MT8196_AFE_IRQ(8),
+ MT8196_AFE_IRQ(9),
+ MT8196_AFE_IRQ(10),
+ MT8196_AFE_IRQ(11),
+ MT8196_AFE_IRQ(12),
+ MT8196_AFE_IRQ(13),
+ MT8196_AFE_IRQ(14),
+ MT8196_AFE_IRQ(15),
+ MT8196_AFE_IRQ(16),
+ MT8196_AFE_IRQ(17),
+ MT8196_AFE_IRQ(18),
+ MT8196_AFE_IRQ(19),
+ MT8196_AFE_IRQ(20),
+ MT8196_AFE_IRQ(21),
+ MT8196_AFE_IRQ(22),
+ MT8196_AFE_IRQ(23),
+ MT8196_AFE_IRQ(24),
+ MT8196_AFE_IRQ(25),
+ MT8196_AFE_IRQ(26),
+ MT8196_AFE_TDM_IRQ(31),
+};
+
+static const int memif_irq_usage[MT8196_MEMIF_NUM] = {
+ /* TODO: verify each memif & irq */
+ [MT8196_MEMIF_DL0] = MT8196_IRQ_0,
+ [MT8196_MEMIF_DL1] = MT8196_IRQ_1,
+ [MT8196_MEMIF_DL2] = MT8196_IRQ_2,
+ [MT8196_MEMIF_DL3] = MT8196_IRQ_3,
+ [MT8196_MEMIF_DL4] = MT8196_IRQ_4,
+ [MT8196_MEMIF_DL5] = MT8196_IRQ_5,
+ [MT8196_MEMIF_DL6] = MT8196_IRQ_6,
+ [MT8196_MEMIF_DL7] = MT8196_IRQ_7,
+ [MT8196_MEMIF_DL8] = MT8196_IRQ_8,
+ [MT8196_MEMIF_DL23] = MT8196_IRQ_9,
+ [MT8196_MEMIF_DL24] = MT8196_IRQ_10,
+ [MT8196_MEMIF_DL25] = MT8196_IRQ_11,
+ [MT8196_MEMIF_DL26] = MT8196_IRQ_0,
+ [MT8196_MEMIF_DL_4CH] = MT8196_IRQ_0,
+ [MT8196_MEMIF_DL_24CH] = MT8196_IRQ_12,
+ [MT8196_MEMIF_VUL0] = MT8196_IRQ_13,
+ [MT8196_MEMIF_VUL1] = MT8196_IRQ_14,
+ [MT8196_MEMIF_VUL2] = MT8196_IRQ_15,
+ [MT8196_MEMIF_VUL3] = MT8196_IRQ_16,
+ [MT8196_MEMIF_VUL4] = MT8196_IRQ_17,
+ [MT8196_MEMIF_VUL5] = MT8196_IRQ_18,
+ [MT8196_MEMIF_VUL6] = MT8196_IRQ_19,
+ [MT8196_MEMIF_VUL7] = MT8196_IRQ_20,
+ [MT8196_MEMIF_VUL8] = MT8196_IRQ_21,
+ [MT8196_MEMIF_VUL9] = MT8196_IRQ_22,
+ [MT8196_MEMIF_VUL10] = MT8196_IRQ_23,
+ [MT8196_MEMIF_VUL24] = MT8196_IRQ_24,
+ [MT8196_MEMIF_VUL25] = MT8196_IRQ_25,
+ [MT8196_MEMIF_VUL26] = MT8196_IRQ_0,
+ [MT8196_MEMIF_VUL_CM0] = MT8196_IRQ_26,
+ [MT8196_MEMIF_VUL_CM1] = MT8196_IRQ_0,
+ [MT8196_MEMIF_VUL_CM2] = MT8196_IRQ_0,
+ [MT8196_MEMIF_ETDM_IN0] = MT8196_IRQ_0,
+ [MT8196_MEMIF_ETDM_IN1] = MT8196_IRQ_0,
+ [MT8196_MEMIF_ETDM_IN2] = MT8196_IRQ_0,
+ [MT8196_MEMIF_ETDM_IN3] = MT8196_IRQ_0,
+ [MT8196_MEMIF_ETDM_IN4] = MT8196_IRQ_0,
+ [MT8196_MEMIF_ETDM_IN6] = MT8196_IRQ_0,
+ [MT8196_MEMIF_HDMI] = MT8196_IRQ_31
+};
+
+static bool mt8196_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+ /* these auto-gen reg has read-only bit, so put it as volatile */
+ /* volatile reg cannot be cached, so cannot be set when power off */
+ switch (reg) {
+ case AUDIO_TOP_CON0 ... AUDIO_TOP_CON4:
+ case AFE_APLL1_TUNER_MON0:
+ case AFE_APLL2_TUNER_MON0:
+ case AFE_SPM_CONTROL_ACK:
+ case AUDIO_TOP_IP_VERSION:
+ case AUDIO_ENGEN_CON0_MON:
+ case AUD_TOP_MON_RG:
+ case AFE_CONNSYS_I2S_IPM_VER_MON:
+ case AFE_CONNSYS_I2S_MON:
+ case AFE_PCM_INTF_MON:
+ case AFE_PCM_TOP_IP_VERSION:
+ case AFE_IRQ_MCU_STATUS:
+ case AFE_CUSTOM_IRQ_MCU_STATUS:
+ case AFE_IRQ_MCU_MON0 ... AFE_IRQ26_CNT_MON:
+ case AFE_CUSTOM_IRQ0_CNT_MON:
+ case AFE_STF_MON:
+ case AFE_STF_IP_VERSION:
+ case AFE_CM0_MON:
+ case AFE_CM0_IP_VERSION:
+ case AFE_CM1_MON:
+ case AFE_CM1_IP_VERSION:
+ case AFE_ADDA_UL0_SRC_DEBUG_MON0 ... AFE_ADDA_UL0_SRC_MON1:
+ case AFE_ADDA_UL0_IP_VERSION:
+ case AFE_ADDA_UL1_SRC_DEBUG_MON0 ... AFE_ADDA_UL1_SRC_MON1:
+ case AFE_ADDA_UL1_IP_VERSION:
+ case AFE_MTKAIF_IPM_VER_MON:
+ case AFE_MTKAIF_MON:
+ case AFE_AUD_PAD_TOP_MON:
+ case AFE_ADDA_MTKAIFV4_MON0 ... AFE_ADDA6_MTKAIFV4_MON0:
+ case ETDM_IN0_MON:
+ case ETDM_IN1_MON:
+ case ETDM_IN2_MON:
+ case ETDM_IN4_MON:
+ case ETDM_IN6_MON:
+ case ETDM_OUT0_MON:
+ case ETDM_OUT1_MON:
+ case ETDM_OUT2_MON:
+ case ETDM_OUT4_MON:
+ case ETDM_OUT6_MON:
+ case AFE_DPTX_MON:
+ case AFE_TDM_TOP_IP_VERSION:
+ case AFE_CONN_MON0 ... AFE_CONN_MON5:
+ case AFE_CBIP_SLV_DECODER_MON0 ... AFE_CBIP_SLV_MUX_MON1:
+ case AFE_DL0_CUR_MSB ... AFE_DL0_CUR:
+ case AFE_DL0_RCH_MON ... AFE_DL0_LCH_MON:
+ case AFE_DL1_CUR_MSB ... AFE_DL1_CUR:
+ case AFE_DL1_RCH_MON ... AFE_DL1_LCH_MON:
+ case AFE_DL2_CUR_MSB ... AFE_DL2_CUR:
+ case AFE_DL2_RCH_MON ... AFE_DL2_LCH_MON:
+ case AFE_DL3_CUR_MSB ... AFE_DL3_CUR:
+ case AFE_DL3_RCH_MON ... AFE_DL3_LCH_MON:
+ case AFE_DL4_CUR_MSB ... AFE_DL4_CUR:
+ case AFE_DL4_RCH_MON ... AFE_DL4_LCH_MON:
+ case AFE_DL5_CUR_MSB ... AFE_DL5_CUR:
+ case AFE_DL5_RCH_MON ... AFE_DL5_LCH_MON:
+ case AFE_DL6_CUR_MSB ... AFE_DL6_CUR:
+ case AFE_DL6_RCH_MON ... AFE_DL6_LCH_MON:
+ case AFE_DL7_CUR_MSB ... AFE_DL7_CUR:
+ case AFE_DL7_RCH_MON ... AFE_DL7_LCH_MON:
+ case AFE_DL8_CUR_MSB ... AFE_DL8_CUR:
+ case AFE_DL8_RCH_MON ... AFE_DL8_LCH_MON:
+ case AFE_DL_24CH_CUR_MSB ... AFE_DL_24CH_CUR:
+ case AFE_DL_4CH_CUR_MSB ... AFE_DL_4CH_CUR:
+ case AFE_DL23_CUR_MSB ... AFE_DL23_CUR:
+ case AFE_DL23_RCH_MON ... AFE_DL23_LCH_MON:
+ case AFE_DL24_CUR_MSB ... AFE_DL24_CUR:
+ case AFE_DL24_RCH_MON ... AFE_DL24_LCH_MON:
+ case AFE_DL25_CUR_MSB ... AFE_DL25_CUR:
+ case AFE_DL25_RCH_MON ... AFE_DL25_LCH_MON:
+ case AFE_DL26_CUR_MSB ... AFE_DL26_CUR:
+ case AFE_DL26_RCH_MON ... AFE_DL26_LCH_MON:
+ case AFE_VUL0_CUR_MSB ... AFE_VUL0_CUR:
+ case AFE_VUL1_CUR_MSB ... AFE_VUL1_CUR:
+ case AFE_VUL2_CUR_MSB ... AFE_VUL2_CUR:
+ case AFE_VUL3_CUR_MSB ... AFE_VUL3_CUR:
+ case AFE_VUL4_CUR_MSB ... AFE_VUL4_CUR:
+ case AFE_VUL5_CUR_MSB ... AFE_VUL5_CUR:
+ case AFE_VUL6_CUR_MSB ... AFE_VUL6_CUR:
+ case AFE_VUL7_CUR_MSB ... AFE_VUL7_CUR:
+ case AFE_VUL8_CUR_MSB ... AFE_VUL8_CUR:
+ case AFE_VUL9_CUR_MSB ... AFE_VUL9_CUR:
+ case AFE_VUL10_CUR_MSB ... AFE_VUL10_CUR:
+ case AFE_VUL24_CUR_MSB ... AFE_VUL24_CUR:
+ case AFE_VUL25_CUR_MSB ... AFE_VUL25_CUR:
+ case AFE_VUL25_RCH_MON ... AFE_VUL25_LCH_MON:
+ case AFE_VUL26_CUR_MSB ... AFE_VUL26_CUR:
+ case AFE_VUL_CM0_BASE_MSB ... AFE_VUL_CM0_CON0:
+ case AFE_VUL_CM1_CUR_MSB ... AFE_VUL_CM1_CUR:
+ case AFE_VUL_CM2_CUR_MSB ... AFE_VUL_CM2_CUR:
+ case AFE_ETDM_IN0_CUR_MSB ... AFE_ETDM_IN0_CUR:
+ case AFE_ETDM_IN1_CUR_MSB ... AFE_ETDM_IN1_CUR:
+ case AFE_ETDM_IN2_CUR_MSB ... AFE_ETDM_IN2_CUR:
+ case AFE_ETDM_IN3_CUR_MSB ... AFE_ETDM_IN3_CUR:
+ case AFE_ETDM_IN4_CUR_MSB ... AFE_ETDM_IN4_CUR:
+ case AFE_ETDM_IN6_CUR_MSB ... AFE_ETDM_IN6_CUR:
+ case AFE_HDMI_OUT_CUR_MSB ... AFE_HDMI_OUT_CUR:
+ case AFE_HDMI_OUT_END:
+ case AFE_PROT_SIDEBAND0_MON ... AFE_DOMAIN_SIDEBAND9_MON:
+ case AFE_PCM0_INTF_CON1_MASK_MON ... AFE_ADDA_UL1_SRC_CON0_MASK_MON:
+ case AFE_IRQ_MCU_EN ... AFE_IRQ_MCU_DSP2_EN:
+ case AFE_CUSTOM_IRQ_MCU_EN:
+ case AFE_DL5_CON0:
+ case AFE_DL6_CON0:
+ case AFE_DL23_CON0:
+ case AFE_DL_24CH_CON0:
+ case AFE_VUL1_CON0:
+ case AFE_VUL3_CON0:
+ case AFE_VUL4_CON0:
+ case AFE_VUL5_CON0:
+ case AFE_VUL9_CON0:
+ case AFE_VUL25_CON0:
+ case AFE_IRQ0_MCU_CFG0 ... AFE_IRQ26_MCU_CFG1:
+ return true;
+ default:
+ return false;
+ };
+}
+
+static const struct regmap_config mt8196_afe_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+
+ .volatile_reg = mt8196_is_volatile_reg,
+
+ .max_register = AFE_MAX_REGISTER,
+ .num_reg_defaults_raw = AFE_MAX_REGISTER,
+
+ .cache_type = REGCACHE_FLAT,
+};
+
+static irqreturn_t mt8196_afe_irq_handler(int irq_id, void *dev)
+{
+ struct mtk_base_afe *afe = dev;
+ struct mtk_base_afe_irq *irq;
+ u32 status;
+ u32 status_mcu;
+ u32 mcu_en;
+ u32 cus_status;
+ u32 cus_status_mcu;
+ u32 cus_mcu_en;
+ u32 tmp_reg;
+ int ret, cus_ret;
+ int i;
+ struct timespec64 ts64;
+ u64 t1, t2;
+ /* one interrupt period = 5ms */
+ const u64 timeout_limit = 5000000;
+
+ /* get irq that is sent to MCU */
+ regmap_read(afe->regmap, AFE_IRQ_MCU_EN, &mcu_en);
+ regmap_read(afe->regmap, AFE_CUSTOM_IRQ_MCU_EN, &cus_mcu_en);
+
+ ret = regmap_read(afe->regmap, AFE_IRQ_MCU_STATUS, &status);
+ cus_ret = regmap_read(afe->regmap, AFE_CUSTOM_IRQ_MCU_STATUS, &cus_status);
+ /* only care IRQ which is sent to MCU */
+ status_mcu = status & mcu_en & AFE_IRQ_STATUS_BITS;
+ cus_status_mcu = cus_status & cus_mcu_en & AFE_IRQ_STATUS_BITS;
+ if ((ret || !status_mcu) && (cus_ret || !cus_status_mcu)) {
+ dev_err(afe->dev, "ret %d, sat 0x%x, en 0x%x,csat 0x%x, cen 0x%x\n",
+ ret, status, mcu_en, cus_status_mcu, cus_mcu_en);
+ return IRQ_NONE;
+ }
+
+ ktime_get_ts64(&ts64);
+ t1 = ktime_get_ns();
+
+ for (i = 0; i < MT8196_MEMIF_NUM; i++) {
+ struct mtk_base_afe_memif *memif = &afe->memif[i];
+
+ if (!memif->substream)
+ continue;
+
+ if (memif->irq_usage < 0)
+ continue;
+ irq = &afe->irqs[memif->irq_usage];
+
+ if (i == MT8196_MEMIF_HDMI) {
+ if (cus_status_mcu & BIT(irq->irq_data->id))
+ snd_pcm_period_elapsed(memif->substream);
+ } else {
+ if (status_mcu & BIT(irq->irq_data->id))
+ snd_pcm_period_elapsed(memif->substream);
+ }
+ }
+
+ ktime_get_ts64(&ts64);
+ t2 = ktime_get_ns();
+ t2 = t2 - t1; /* in ns (10^9) */
+
+ if (t2 > timeout_limit)
+ dev_warn(afe->dev, "IRQ handler exceeded time limit by %llu ns\n",
+ t2 - timeout_limit);
+
+ /* clear irq */
+ for (i = 0; i < MT8196_IRQ_NUM; ++i) {
+ /* cus_status_mcu only bit0 is used for TDM */
+ if ((status_mcu & BIT(i)) || (cus_status_mcu & 0x1)) {
+ regmap_read(afe->regmap, irq_data[i].irq_clr_reg, &tmp_reg);
+ regmap_update_bits(afe->regmap, irq_data[i].irq_clr_reg,
+ AFE_IRQ_CLR_CFG_MASK_SFT |
+ AFE_IRQ_MISS_FLAG_CLR_CFG_MASK_SFT,
+ tmp_reg ^ (AFE_IRQ_CLR_CFG_MASK_SFT |
+ AFE_IRQ_MISS_FLAG_CLR_CFG_MASK_SFT));
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int mt8196_afe_runtime_suspend(struct device *dev)
+{
+ struct mtk_base_afe *afe = dev_get_drvdata(dev);
+ unsigned int value;
+ unsigned int tmp_reg;
+ int ret, i;
+
+ if (!afe->regmap) {
+ dev_err(afe->dev, "skip regmap\n");
+ goto skip_regmap;
+ }
+
+ /* disable AFE */
+ mt8196_afe_disable_main_clock(afe);
+
+ ret = regmap_read_poll_timeout(afe->regmap,
+ AUDIO_ENGEN_CON0_MON,
+ value,
+ (value & AUDIO_ENGEN_MON_SFT) == 0,
+ 20,
+ 1 * 1000 * 1000);
+ dev_dbg(afe->dev, "read_poll ret %d\n", ret);
+ if (ret)
+ dev_warn(afe->dev, "ret %d\n", ret);
+
+ /* make sure all irq status are cleared */
+ for (i = 0; i < MT8196_IRQ_NUM; ++i) {
+ regmap_read(afe->regmap, irq_data[i].irq_clr_reg, &tmp_reg);
+ regmap_update_bits(afe->regmap, irq_data[i].irq_clr_reg,
+ AFE_IRQ_CLR_CFG_MASK_SFT | AFE_IRQ_MISS_FLAG_CLR_CFG_MASK_SFT,
+ tmp_reg ^ (AFE_IRQ_CLR_CFG_MASK_SFT |
+ AFE_IRQ_MISS_FLAG_CLR_CFG_MASK_SFT));
+ }
+
+ /* reset audio 26M request */
+ regmap_update_bits(afe->regmap,
+ AFE_SPM_CONTROL_REQ, 0x1, 0x0);
+
+ /* cache only */
+ regcache_cache_only(afe->regmap, true);
+ regcache_mark_dirty(afe->regmap);
+
+skip_regmap:
+ mt8196_afe_disable_reg_rw_clk(afe);
+ return 0;
+}
+
+static int mt8196_afe_runtime_resume(struct device *dev)
+{
+ struct mtk_base_afe *afe = dev_get_drvdata(dev);
+ int ret = 0;
+
+ ret = mt8196_afe_enable_reg_rw_clk(afe);
+ if (ret)
+ return ret;
+
+ if (!afe->regmap) {
+ dev_warn(afe->dev, "skip regmap\n");
+ goto skip_regmap;
+ }
+ regcache_cache_only(afe->regmap, false);
+ regcache_sync(afe->regmap);
+
+ /* set audio 26M request */
+ regmap_update_bits(afe->regmap, AFE_SPM_CONTROL_REQ, 0x1, 0x1);
+ regmap_update_bits(afe->regmap, AFE_CBIP_CFG0, 0x1, 0x1);
+
+ /* force cpu use 8_24 format when writing 32bit data */
+ regmap_update_bits(afe->regmap, AFE_MEMIF_CON0,
+ CPU_HD_ALIGN_MASK_SFT, 0 << CPU_HD_ALIGN_SFT);
+
+ /* enable AFE */
+ mt8196_afe_enable_main_clock(afe);
+
+skip_regmap:
+ return 0;
+}
+
+static int mt8196_afe_component_probe(struct snd_soc_component *component)
+{
+ struct mtk_base_afe *afe = snd_soc_component_get_drvdata(component);
+
+ if (component) {
+ /* enable clock for regcache get default value from hw */
+ pm_runtime_get_sync(afe->dev);
+ mtk_afe_add_sub_dai_control(component);
+ pm_runtime_put_sync(afe->dev);
+ }
+ return 0;
+}
+
+static int mt8196_afe_pcm_open(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ /* set the wait_for_avail to 2 sec*/
+ substream->wait_time = msecs_to_jiffies(2 * 1000);
+
+ return 0;
+}
+
+static void mt8196_afe_pcm_free(struct snd_soc_component *component, struct snd_pcm *pcm)
+{
+ snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static const struct snd_soc_component_driver mt8196_afe_component = {
+ .name = AFE_PCM_NAME,
+ .probe = mt8196_afe_component_probe,
+ .pcm_new = mtk_afe_pcm_new,
+ .pcm_free = mt8196_afe_pcm_free,
+ .open = mt8196_afe_pcm_open,
+ .pointer = mtk_afe_pcm_pointer,
+};
+
+static int mt8196_dai_memif_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 = mt8196_memif_dai_driver;
+ dai->num_dai_drivers = ARRAY_SIZE(mt8196_memif_dai_driver);
+ dai->dapm_widgets = mt8196_memif_widgets;
+ dai->num_dapm_widgets = ARRAY_SIZE(mt8196_memif_widgets);
+ dai->dapm_routes = mt8196_memif_routes;
+ dai->num_dapm_routes = ARRAY_SIZE(mt8196_memif_routes);
+ return 0;
+}
+
+typedef int (*dai_register_cb)(struct mtk_base_afe *);
+static const dai_register_cb dai_register_cbs[] = {
+ mt8196_dai_adda_register,
+ mt8196_dai_i2s_register,
+ mt8196_dai_tdm_register,
+ mt8196_dai_memif_register,
+};
+
+static const struct reg_sequence mt8196_cg_patch[] = {
+ { AUDIO_TOP_CON4, 0x361c },
+};
+
+static int mt8196_afe_pcm_dev_probe(struct platform_device *pdev)
+{
+ int ret, i;
+ unsigned int tmp_reg;
+ int irq_id;
+ struct mtk_base_afe *afe;
+ struct mt8196_afe_private *afe_priv;
+ struct device *dev = &pdev->dev;
+
+ ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(34));
+ if (ret)
+ return ret;
+
+ ret = of_reserved_mem_device_init(dev);
+ if (ret)
+ dev_err(dev, "failed to assign memory region: %d\n", ret);
+
+ afe = devm_kzalloc(dev, sizeof(*afe), GFP_KERNEL);
+ if (!afe)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, afe);
+
+ afe->platform_priv = devm_kzalloc(dev, sizeof(*afe_priv),
+ GFP_KERNEL);
+ if (!afe->platform_priv)
+ return -ENOMEM;
+
+ afe_priv = afe->platform_priv;
+ afe->dev = dev;
+
+ afe->base_addr = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(afe->base_addr))
+ return dev_err_probe(dev, PTR_ERR(afe->base_addr),
+ "AFE base_addr not found\n");
+
+ /* init audio related clock */
+ ret = mt8196_init_clock(afe);
+ if (ret)
+ return dev_err_probe(dev, ret, "init clock error.\n");
+
+ /* init memif */
+ /* IPM2.0 no need banding */
+ afe->memif_32bit_supported = 1;
+ afe->memif_size = MT8196_MEMIF_NUM;
+ afe->memif = devm_kcalloc(dev, afe->memif_size, sizeof(*afe->memif),
+ GFP_KERNEL);
+
+ if (!afe->memif)
+ return -ENOMEM;
+
+ for (i = 0; i < afe->memif_size; i++) {
+ afe->memif[i].data = &memif_data[i];
+ afe->memif[i].irq_usage = memif_irq_usage[i];
+ afe->memif[i].const_irq = 1;
+ }
+
+ mutex_init(&afe->irq_alloc_lock);
+
+ /* init irq */
+ afe->irqs_size = MT8196_IRQ_NUM;
+ afe->irqs = devm_kcalloc(dev, afe->irqs_size, sizeof(*afe->irqs),
+ GFP_KERNEL);
+
+ if (!afe->irqs)
+ return -ENOMEM;
+
+ for (i = 0; i < afe->irqs_size; i++)
+ afe->irqs[i].irq_data = &irq_data[i];
+
+ /* request irq */
+ irq_id = platform_get_irq(pdev, 0);
+ if (irq_id < 0)
+ return dev_err_probe(dev, irq_id, "no irq found");
+
+ ret = devm_request_irq(dev, irq_id, mt8196_afe_irq_handler,
+ IRQF_TRIGGER_NONE,
+ "Afe_ISR_Handle", afe);
+ if (ret)
+ return dev_err_probe(dev, ret, "could not request_irq for Afe_ISR_Handle\n");
+
+ /* init sub_dais */
+ INIT_LIST_HEAD(&afe->sub_dais);
+
+ for (i = 0; i < ARRAY_SIZE(dai_register_cbs); i++) {
+ ret = dai_register_cbs[i](afe);
+ if (ret)
+ return dev_err_probe(dev, ret, "dai register i %d fail\n", i);
+ }
+
+ /* init dai_driver and component_driver */
+ ret = mtk_afe_combine_sub_dai(afe);
+ if (ret)
+ return dev_err_probe(dev, ret, "mtk_afe_combine_sub_dai fail\n");
+
+ /* others */
+ afe->mtk_afe_hardware = &mt8196_afe_hardware;
+ afe->memif_fs = mt8196_memif_fs;
+ afe->irq_fs = mt8196_irq_fs;
+ afe->get_dai_fs = mt8196_get_dai_fs;
+ afe->get_memif_pbuf_size = mt8196_get_memif_pbuf_size;
+
+ afe->runtime_resume = mt8196_afe_runtime_resume;
+ afe->runtime_suspend = mt8196_afe_runtime_suspend;
+
+ ret = devm_pm_runtime_enable(dev);
+ if (ret)
+ return ret;
+
+/*
+ * Audio device is part of genpd. Registering it as a syscore device ensure
+ * the proper power-on sequence of the AFE device.
+ */
+ dev_pm_syscore_device(dev, true);
+
+ /* enable clock for regcache get default value from hw */
+ pm_runtime_get_sync(dev);
+
+ afe->regmap = devm_regmap_init_mmio(dev, afe->base_addr,
+ &mt8196_afe_regmap_config);
+ if (IS_ERR(afe->regmap))
+ return PTR_ERR(afe->regmap);
+
+ ret = regmap_register_patch(afe->regmap, mt8196_cg_patch,
+ ARRAY_SIZE(mt8196_cg_patch));
+ if (ret < 0) {
+ dev_err(dev, "Failed to apply cg patch\n");
+ goto err_pm_disable;
+ }
+
+ regmap_read(afe->regmap, AFE_IRQ_MCU_EN, &tmp_reg);
+ regmap_write(afe->regmap, AFE_IRQ_MCU_EN, 0xffffffff);
+ regmap_read(afe->regmap, AFE_IRQ_MCU_EN, &tmp_reg);
+
+ pm_runtime_put_sync(dev);
+
+ regcache_cache_only(afe->regmap, true);
+ regcache_mark_dirty(afe->regmap);
+
+ /* register component */
+ ret = devm_snd_soc_register_component(dev,
+ &mt8196_afe_component,
+ afe->dai_drivers,
+ afe->num_dai_drivers);
+ if (ret) {
+ dev_err(dev, "afe component err\n");
+ goto err_pm_disable;
+ }
+
+ return 0;
+
+err_pm_disable:
+ pm_runtime_put_sync(dev);
+ return ret;
+}
+
+static void mt8196_afe_pcm_dev_remove(struct platform_device *pdev)
+{
+ struct mtk_base_afe *afe = platform_get_drvdata(pdev);
+ struct device *dev = &pdev->dev;
+
+ pm_runtime_put_sync(dev);
+ if (!pm_runtime_status_suspended(dev))
+ mt8196_afe_runtime_suspend(dev);
+
+ mt8196_afe_disable_main_clock(afe);
+ /* disable afe clock */
+ mt8196_afe_disable_reg_rw_clk(afe);
+}
+
+static const struct of_device_id mt8196_afe_pcm_dt_match[] = {
+ { .compatible = "mediatek,mt8196-afe", },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, mt8196_afe_pcm_dt_match);
+
+static const struct dev_pm_ops mt8196_afe_pm_ops = {
+ SET_RUNTIME_PM_OPS(mt8196_afe_runtime_suspend,
+ mt8196_afe_runtime_resume, NULL)
+};
+
+static struct platform_driver mt8196_afe_pcm_driver = {
+ .driver = {
+ .name = "mt8196-afe",
+ .of_match_table = mt8196_afe_pcm_dt_match,
+ .pm = &mt8196_afe_pm_ops,
+ },
+ .probe = mt8196_afe_pcm_dev_probe,
+ .remove = mt8196_afe_pcm_dev_remove,
+};
+module_platform_driver(mt8196_afe_pcm_driver);
+
+MODULE_DESCRIPTION("Mediatek ALSA SoC AFE platform driver for 8196");
+MODULE_AUTHOR("Darren Ye <darren.ye@mediatek.com>");
+MODULE_LICENSE("GPL");
--
2.45.2
^ permalink raw reply related
* [PATCH v5 net-next 00/15] Add preliminary NETC switch support for i.MX94
From: Wei Fang @ 2026-04-30 2:49 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
i.MX94 NETC (v4.3) integrates 802.1Q Ethernet switch functionality, the
switch provides advanced QoS with 8 traffic classes and a full range of
TSN standards capabilities. It has 3 user ports and 1 CPU port, and the
CPU port is connected to an internal ENETC through the pseduo link, so
instead of a back-to-back MAC, the lightweight "pseudo MAC" is used at
both ends of the pseudo link to transfer Ethernet frames. The pseudo
link provides a zero-copy interface (no serialization delay) and lower
power (less logic and memory).
Like most Ethernet switches, the NETC switch also supports a proprietary
switch tag, is used to carry in-band metadata information about frames.
This in-band metadata information can include the source port from which
the frame was received, what was the reason why this frame got forwarded
to the entity, and for the entity to indicate the precise destination
port of a frame. The NETC switch tag is added to frames after the source
MAC address. There are three types of switch tags, and each type has 1
to 4 subtypes, more details are as follows.
Forward switch tag (Type = 0): Represents forwarded frames.
- SubType = 0 - Normal frame processing.
To_Port switch tag (Type = 1): Represents frames that are to be sent to
a specific switch port.
- SubType = 0. No request to perform timestamping.
- SubType = 1. Request to perform one-step timestamping.
- SubType = 2. Request to perform two-step timestamping.
- SubType = 3. Request to perform both one-step timestamping and
two-step timestamping.
To_Host switch tag (Type = 2): Represents frames redirected or copied to
the switch management port.
- SubType = 0. Received frames redirected or copied to the switch
management port.
- SubType = 1. Received frames redirected or copied to the switch
management port with captured timestamp at the switch port where
the frame was received.
- SubType = 2. Transmit timestamp response (two-step timestamping).
Currently, this patch set supports Forward tag, SubType 0 of To_Port tag
and SubType 0 of To_Host tag. More tags will be supported in the future.
In addition, the switch supports NETC Table Management Protocol (NTMP),
some switch functionality is controlled using control messages sent to
the hardware using BD ring interface with 32B descriptors similar to the
packet Transmit BD ring used on ENETC. This interface is referred to as
the command BD ring. This is used to configure functionality where the
underlying resources may be shared between different entities or being
too large to configure using direct registers.
For this patch set, we have supported the following tables through the
command BD ring interface.
FDB Table: It contains forwarding and/or filtering information about MAC
addresses. The FDB table is used for MAC learning lookups and MAC
forwarding lookups.
VLAN Filter Table: It contains configuration and control information for
each VLAN configured on the switch.
Buffer Pool Table: It contains buffer pool configuration and operational
information. Each entry corresponds to a buffer pool. Currently, we use
this table to implement flow control feature on each port.
Ingress Port Filter Table: It contains a set of filters each capable of
classifying incoming traffic using a mix of L2, L3, and L4 parsed and
arbitrary field data. We use this table to implement host flood support
to the switch port.
The switch also supports other tables, and we will add more advanced
features through them in the future.
---
v5:
1. Move '$ref: dsa.yaml#' under the 'allOf'
2. Change '^(ethernet-)?ports$' to 'ethernet-ports'
3. Change '^(ethernet-)?port@[0-9a-f]$' to '^ethernet-port@[0-9a-f]'
4. Update node names in the DT example
5. Change port_id type from int to u32
6. Remove netc_get_switch_ports()
7. Remove unused inline functions from ntmp.h
8. Refactor the implemention of new tables due to ntmp.c is updated
8. Correct the definition of IPFT_DSCP_MASK
9. Check the return value of netdev_txq_to_tc()
10. Use pcim_* functions for automatic resource cleanup and remove
netc_switch_pci_destroy()
11. Simplify error handling paths in probe function
12. Remove PHY_INTERFACE_MODE_1000BASEX from the driver
13. Add vid check to netc_port_fdb_add/del()
14. Move netc_mac_port_rmw from patch 10 to patch 11
15. Delete the old host flood entry after adding a new one to avoid
losing the configuration if the new rule fails
16. Add netc_get_switch_capabilities() to get the FDB table capability
17. Add a maximum query count check to netc_port_fdb_dump() to prevent
infinite loops caused by hardware malfunctions.
18. Remove netc_get_buffer_pool_num(), the logic is moved to
netc_get_switch_capabilities()
19. Remove enum netc_port_mac
20. Move some register definitions from patch 14 to patch 15, as they
are not used in patch 14.
21. Patch 15 is a new patch
22. Update commit message, correct typos and add some comments
v4 link: https://lore.kernel.org/imx/20260331113025.1566878-1-wei.fang@nxp.com/
v3 link: https://lore.kernel.org/imx/20260326062917.3552334-1-wei.fang@nxp.com/
v2 link: https://lore.kernel.org/imx/20260323060752.1157031-1-wei.fang@nxp.com/
v1 link: https://lore.kernel.org/imx/20260316094152.1558671-1-wei.fang@nxp.com/
---
Wei Fang (15):
dt-bindings: net: dsa: update the description of 'dsa,member' property
dt-bindings: net: dsa: add NETC switch
net: enetc: add pre-boot initialization for i.MX94 switch
net: enetc: add basic operations to the FDB table
net: enetc: add support for the "Add" operation to VLAN filter table
net: enetc: add support for the "Update" operation to buffer pool
table
net: enetc: add support for "Add" and "Delete" operations to IPFT
net: enetc: add multiple command BD rings support
net: dsa: add NETC switch tag support
net: dsa: netc: introduce NXP NETC switch driver for i.MX94
net: dsa: netc: add phylink MAC operations
net: dsa: netc: add FDB, STP, MTU, port setup and host flooding
support
net: dsa: netc: initialize buffer pool table and implement
flow-control
net: dsa: netc: add support for the standardized counters
net: dsa: netc: add support for ethtool private statistics
.../devicetree/bindings/net/dsa/dsa.yaml | 6 +-
.../bindings/net/dsa/nxp,netc-switch.yaml | 127 ++
MAINTAINERS | 11 +
drivers/net/dsa/Kconfig | 2 +
drivers/net/dsa/Makefile | 1 +
drivers/net/dsa/netc/Kconfig | 14 +
drivers/net/dsa/netc/Makefile | 3 +
drivers/net/dsa/netc/netc_ethtool.c | 297 ++++
drivers/net/dsa/netc/netc_main.c | 1566 +++++++++++++++++
drivers/net/dsa/netc/netc_platform.c | 87 +
drivers/net/dsa/netc/netc_switch.h | 172 ++
drivers/net/dsa/netc/netc_switch_hw.h | 361 ++++
.../ethernet/freescale/enetc/netc_blk_ctrl.c | 185 +-
drivers/net/ethernet/freescale/enetc/ntmp.c | 381 +++-
.../ethernet/freescale/enetc/ntmp_private.h | 122 +-
include/linux/dsa/tag_netc.h | 14 +
include/linux/fsl/netc_global.h | 6 +
include/linux/fsl/ntmp.h | 187 +-
include/net/dsa.h | 2 +
include/uapi/linux/if_ether.h | 1 +
net/dsa/Kconfig | 10 +
net/dsa/Makefile | 1 +
net/dsa/tag_netc.c | 193 ++
23 files changed, 3719 insertions(+), 30 deletions(-)
create mode 100644 Documentation/devicetree/bindings/net/dsa/nxp,netc-switch.yaml
create mode 100644 drivers/net/dsa/netc/Kconfig
create mode 100644 drivers/net/dsa/netc/Makefile
create mode 100644 drivers/net/dsa/netc/netc_ethtool.c
create mode 100644 drivers/net/dsa/netc/netc_main.c
create mode 100644 drivers/net/dsa/netc/netc_platform.c
create mode 100644 drivers/net/dsa/netc/netc_switch.h
create mode 100644 drivers/net/dsa/netc/netc_switch_hw.h
create mode 100644 include/linux/dsa/tag_netc.h
create mode 100644 net/dsa/tag_netc.c
--
2.34.1
^ permalink raw reply
* [PATCH v5 net-next 01/15] dt-bindings: net: dsa: update the description of 'dsa,member' property
From: Wei Fang @ 2026-04-30 2:49 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260430024945.3413973-1-wei.fang@nxp.com>
The current description indicates that the 'dsa,member' property cannot
be set for a switch that is not part of any cluster. Vladimir thinks
that this is a case where the actual technical limitation was poorly
transposed into words when this restriction was first documented, in
commit 8c5ad1d6179d ("net: dsa: Document new binding").
The true technical limitation is that many DSA tagging protocols are
topology-unaware, and always call dsa_conduit_find_user() with a
switch_id of 0. Specifying a custom "dsa,member" property with a
non-zero switch_id would break them.
Therefore, for topology-aware switches, it is fine to specify this
property for them, even if they are not part of any cluster. Our NETC
switch is a good example which is topology-aware, the switch_id is
carried in the switch tag, but the switch_id 0 is reserved for VEPA
switch and cannot be used, so we need to use this property to assign
a non-zero switch_id for it.
Suggested-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: Wei Fang <wei.fang@nxp.com>
Acked-by: Rob Herring (Arm) <robh@kernel.org>
---
Documentation/devicetree/bindings/net/dsa/dsa.yaml | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/net/dsa/dsa.yaml b/Documentation/devicetree/bindings/net/dsa/dsa.yaml
index 2abd036578d1..801e1411e5c2 100644
--- a/Documentation/devicetree/bindings/net/dsa/dsa.yaml
+++ b/Documentation/devicetree/bindings/net/dsa/dsa.yaml
@@ -28,7 +28,11 @@ properties:
A two element list indicates which DSA cluster, and position within the
cluster a switch takes. <0 0> is cluster 0, switch 0. <0 1> is cluster 0,
switch 1. <1 0> is cluster 1, switch 0. A switch not part of any cluster
- (single device hanging off a CPU port) must not specify this property
+ (single device hanging off a CPU port) does not usually need to specify
+ this property, and then it becomes cluster 0, switch 0. For a topology
+ aware switch, its switch index can be specified through this property,
+ even if it is not part of any cluster. Also, topology-unaware switches
+ must always be defined as index 0 of their cluster.
$ref: /schemas/types.yaml#/definitions/uint32-array
additionalProperties: true
--
2.34.1
^ permalink raw reply related
* [PATCH v5 net-next 02/15] dt-bindings: net: dsa: add NETC switch
From: Wei Fang @ 2026-04-30 2:49 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260430024945.3413973-1-wei.fang@nxp.com>
Add bindings for NETC switch. This switch is a PCIe function of NETC IP,
it supports advanced QoS with 8 traffic classes and 4 drop resilience
levels, and a full range of TSN standards capabilities. The switch CPU
port connects to an internal ENETC port, which is also a PCIe function
of NETC IP. So these two ports use a light-weight "pseudo MAC" instead
of a back-to-back MAC, because the "pseudo MAC" provides the delineation
between switch and ENETC, this translates to lower power (less logic and
memory) and lower delay (as there is no serialization delay across this
link).
Signed-off-by: Wei Fang <wei.fang@nxp.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
---
.../bindings/net/dsa/nxp,netc-switch.yaml | 127 ++++++++++++++++++
1 file changed, 127 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/dsa/nxp,netc-switch.yaml
diff --git a/Documentation/devicetree/bindings/net/dsa/nxp,netc-switch.yaml b/Documentation/devicetree/bindings/net/dsa/nxp,netc-switch.yaml
new file mode 100644
index 000000000000..988688bf4467
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/dsa/nxp,netc-switch.yaml
@@ -0,0 +1,127 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/dsa/nxp,netc-switch.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NETC Switch family
+
+description: >
+ The NETC presents itself as a multi-function PCIe Root Complex Integrated
+ Endpoint (RCiEP) and provides full 802.1Q Ethernet switch functionality,
+ advanced QoS with 8 traffic classes and 4 drop resilience levels, and a
+ full range of TSN standards capabilities.
+
+ The CPU port of the switch connects to an internal ENETC. The switch and
+ the internal ENETC are fully integrated into the NETC IP, a back-to-back
+ MAC is not required. Instead, a light-weight "pseudo MAC" provides the
+ delineation between the switch and ENETC. This translates to lower power
+ (less logic and memory) and lower delay (as there is no serialization
+ delay across this link).
+
+maintainers:
+ - Wei Fang <wei.fang@nxp.com>
+
+properties:
+ compatible:
+ enum:
+ - pci1131,eef2
+
+ reg:
+ maxItems: 1
+
+ dsa,member:
+ description: >
+ The property indicates DSA cluster and switch index. For NETC switch,
+ the valid range of the switch index is 1 ~ 7, the index is reflected
+ in the switch tag as an indication of the switch ID where the frame
+ originated. The value 0 is reserved for ENETC VEPA switch, whose ID
+ is hardwired to zero.
+
+ ethernet-ports:
+ type: object
+ patternProperties:
+ "^ethernet-port@[0-9a-f]$":
+ type: object
+ $ref: dsa-port.yaml#
+
+ properties:
+ clocks:
+ items:
+ - description: MAC transmit/receive reference clock.
+
+ clock-names:
+ items:
+ - const: ref
+
+ mdio:
+ $ref: /schemas/net/mdio.yaml#
+ unevaluatedProperties: false
+ description:
+ Optional child node for switch port, otherwise use NETC EMDIO.
+
+ unevaluatedProperties: false
+
+required:
+ - compatible
+ - reg
+ - dsa,member
+ - ethernet-ports
+
+allOf:
+ - $ref: /schemas/pci/pci-device.yaml
+ - $ref: dsa.yaml#
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ pcie {
+ #address-cells = <3>;
+ #size-cells = <2>;
+
+ ethernet-switch@0,2 {
+ compatible = "pci1131,eef2";
+ reg = <0x200 0 0 0 0>;
+ dsa,member = <0 1>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_switch>;
+
+ ethernet-ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ethernet-port@0 {
+ reg = <0>;
+ phy-handle = <ðphy0>;
+ phy-mode = "mii";
+ };
+
+ ethernet-port@1 {
+ reg = <1>;
+ phy-handle = <ðphy1>;
+ phy-mode = "mii";
+ };
+
+ ethernet-port@2 {
+ reg = <2>;
+ clocks = <&scmi_clk 103>;
+ clock-names = "ref";
+ phy-handle = <ðphy2>;
+ phy-mode = "rgmii-id";
+ };
+
+ ethernet-port@3 {
+ reg = <3>;
+ ethernet = <&enetc3>;
+ phy-mode = "internal";
+
+ fixed-link {
+ speed = <2500>;
+ full-duplex;
+ pause;
+ };
+ };
+ };
+ };
+ };
--
2.34.1
^ permalink raw reply related
* [PATCH v5 net-next 03/15] net: enetc: add pre-boot initialization for i.MX94 switch
From: Wei Fang @ 2026-04-30 2:49 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260430024945.3413973-1-wei.fang@nxp.com>
Before probing the NETC switch driver, some pre-initialization needs to
be set in NETCMIX and IERB to ensure that the switch can work properly.
For example, i.MX94 NETC switch has three external ports and each port
is bound to a link. And each link needs to be configured so that it can
work properly, such as I/O variant and MII protocol.
In addition, the switch port 2 (MAC 2) and ENETC 0 (MAC 3) share the same
parallel interface, they cannot be used at the same time due to the SoC
constraint. And the MAC selection is controlled by the mac2_mac3_sel bit
of EXT_PIN_CONTROL register. Currently, the interface is set for ENETC 0
by default unless the switch port 2 is enabled in the DT node.
Like ENETC, each external port of the NETC switch can manage its external
PHY through its port MDIO registers. And the port can only access its own
external PHY by setting the PHY address to the LaBCR[MDIO_PHYAD_PRTAD].
If the accessed PHY address is not equal to LaBCR[MDIO_PHYAD_PRTAD], then
the MDIO access initiated by port MDIO will be invalid.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
.../ethernet/freescale/enetc/netc_blk_ctrl.c | 185 +++++++++++++++---
1 file changed, 163 insertions(+), 22 deletions(-)
diff --git a/drivers/net/ethernet/freescale/enetc/netc_blk_ctrl.c b/drivers/net/ethernet/freescale/enetc/netc_blk_ctrl.c
index 92a0f824dae7..c7eb0234c785 100644
--- a/drivers/net/ethernet/freescale/enetc/netc_blk_ctrl.c
+++ b/drivers/net/ethernet/freescale/enetc/netc_blk_ctrl.c
@@ -261,40 +261,108 @@ static int imx94_link_config(struct netc_blk_ctrl *priv,
}
static int imx94_enetc_link_config(struct netc_blk_ctrl *priv,
- struct device_node *np)
+ struct device_node *np,
+ bool *enetc0_en)
{
int link_id = imx94_enetc_get_link_id(np);
if (link_id < 0)
return link_id;
+ if (link_id == IMX94_ENETC0_LINK && of_device_is_available(np))
+ *enetc0_en = true;
+
return imx94_link_config(priv, np, link_id);
}
+static int imx94_switch_link_config(struct netc_blk_ctrl *priv,
+ struct device_node *np,
+ bool *swp2_en)
+{
+ struct device_node *ports;
+ u32 port_id;
+ int err = 0;
+
+ ports = of_get_child_by_name(np, "ethernet-ports");
+ if (!ports)
+ return -ENODEV;
+
+ /* The switch may be owned by a guest OS, in this case, the switch
+ * node in the host OS will be disabled, but we still hope that the
+ * host OS could do some configurations for the switch, as the
+ * netc_blk_ctrl is owned by host OS. So of_device_is_available()
+ * is not needed here.
+ */
+ for_each_available_child_of_node_scoped(ports, child) {
+ if (of_property_read_u32(child, "reg", &port_id) < 0) {
+ err = -ENODEV;
+ goto end;
+ }
+
+ switch (port_id) {
+ case 0 ... 2: /* External ports */
+ err = imx94_link_config(priv, child, port_id);
+ if (err)
+ goto end;
+
+ if (port_id == 2)
+ *swp2_en = true;
+
+ break;
+ case 3: /* CPU port */
+ break;
+ default:
+ err = -EINVAL;
+ goto end;
+ }
+ }
+
+end:
+ of_node_put(ports);
+
+ return err;
+}
+
static int imx94_netcmix_init(struct platform_device *pdev)
{
struct netc_blk_ctrl *priv = platform_get_drvdata(pdev);
struct device_node *np = pdev->dev.of_node;
+ bool enetc0_en = false, swp2_en = false;
u32 val;
int err;
for_each_child_of_node_scoped(np, child) {
for_each_child_of_node_scoped(child, gchild) {
- if (!of_device_is_compatible(gchild, "pci1131,e101"))
- continue;
-
- err = imx94_enetc_link_config(priv, gchild);
- if (err)
- return err;
+ if (of_device_is_compatible(gchild, "pci1131,e101")) {
+ err = imx94_enetc_link_config(priv, gchild,
+ &enetc0_en);
+ if (err)
+ return err;
+ } else if (of_device_is_compatible(gchild,
+ "pci1131,eef2")) {
+ err = imx94_switch_link_config(priv, gchild,
+ &swp2_en);
+ if (err)
+ return err;
+ }
}
}
- /* ENETC 0 and switch port 2 share the same parallel interface.
- * Currently, the switch is not supported, so this interface is
- * used by ENETC 0 by default.
+ if (enetc0_en && swp2_en) {
+ dev_err(&pdev->dev,
+ "Cannot enable swp2 and enetc0 at the same time\n");
+ return -EINVAL;
+ }
+
+ /* ENETC 0 and switch port 2 share the same parallel interface, they
+ * cannot be enabled at the same time. The interface is set for the
+ * ENETC 0 by default unless the switch port 2 is enabled in the DTS.
*/
val = netc_reg_read(priv->netcmix, IMX94_EXT_PIN_CONTROL);
- val |= MAC2_MAC3_SEL;
+ if (!swp2_en)
+ val |= MAC2_MAC3_SEL;
+ else
+ val &= ~MAC2_MAC3_SEL;
netc_reg_write(priv->netcmix, IMX94_EXT_PIN_CONTROL, val);
return 0;
@@ -610,6 +678,78 @@ static int imx94_enetc_mdio_phyaddr_config(struct netc_blk_ctrl *priv,
return 0;
}
+static int imx94_ierb_enetc_init(struct netc_blk_ctrl *priv,
+ struct device_node *np,
+ u32 phy_mask)
+{
+ int err;
+
+ err = imx94_enetc_update_tid(priv, np);
+ if (err)
+ return err;
+
+ return imx94_enetc_mdio_phyaddr_config(priv, np, phy_mask);
+}
+
+static int imx94_switch_mdio_phyaddr_config(struct netc_blk_ctrl *priv,
+ struct device_node *np,
+ u32 port_id, u32 phy_mask)
+{
+ int addr;
+
+ /* The switch has 3 external ports at most */
+ if (port_id > 2)
+ return 0;
+
+ addr = netc_get_phy_addr(np);
+ if (addr < 0) {
+ if (addr == -ENODEV)
+ return 0;
+
+ return addr;
+ }
+
+ if (phy_mask & BIT(addr)) {
+ dev_err(&priv->pdev->dev,
+ "Found same PHY address in EMDIO and switch node\n");
+ return -EINVAL;
+ }
+
+ netc_reg_write(priv->ierb, IERB_LBCR(port_id),
+ LBCR_MDIO_PHYAD_PRTAD(addr));
+
+ return 0;
+}
+
+static int imx94_ierb_switch_init(struct netc_blk_ctrl *priv,
+ struct device_node *np,
+ u32 phy_mask)
+{
+ struct device_node *ports;
+ u32 port_id;
+ int err = 0;
+
+ ports = of_get_child_by_name(np, "ethernet-ports");
+ if (!ports)
+ return -ENODEV;
+
+ for_each_available_child_of_node_scoped(ports, child) {
+ err = of_property_read_u32(child, "reg", &port_id);
+ if (err)
+ goto end;
+
+ err = imx94_switch_mdio_phyaddr_config(priv, child,
+ port_id, phy_mask);
+ if (err)
+ goto end;
+ }
+
+end:
+ of_node_put(ports);
+
+ return err;
+}
+
static int imx94_ierb_init(struct platform_device *pdev)
{
struct netc_blk_ctrl *priv = platform_get_drvdata(pdev);
@@ -625,17 +765,18 @@ static int imx94_ierb_init(struct platform_device *pdev)
for_each_child_of_node_scoped(np, child) {
for_each_child_of_node_scoped(child, gchild) {
- if (!of_device_is_compatible(gchild, "pci1131,e101"))
- continue;
-
- err = imx94_enetc_update_tid(priv, gchild);
- if (err)
- return err;
-
- err = imx94_enetc_mdio_phyaddr_config(priv, gchild,
- phy_mask);
- if (err)
- return err;
+ if (of_device_is_compatible(gchild, "pci1131,e101")) {
+ err = imx94_ierb_enetc_init(priv, gchild,
+ phy_mask);
+ if (err)
+ return err;
+ } else if (of_device_is_compatible(gchild,
+ "pci1131,eef2")) {
+ err = imx94_ierb_switch_init(priv, gchild,
+ phy_mask);
+ if (err)
+ return err;
+ }
}
}
--
2.34.1
^ permalink raw reply related
* [PATCH v5 net-next 04/15] net: enetc: add basic operations to the FDB table
From: Wei Fang @ 2026-04-30 2:49 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260430024945.3413973-1-wei.fang@nxp.com>
The FDB table is used for MAC learning lookups and MAC forwarding lookups.
Each table entry includes information such as a FID and MAC address that
may be unicast or multicast and a forwarding destination field containing
a port bitmap identifying the associated port(s) with the MAC address.
FDB table entries can be static or dynamic. Static entries are added from
software whereby dynamic entries are added either by software or by the
hardware as MAC addresses are learned in the datapath.
The FDB table can only be managed by the command BD ring using table
management protocol version 2.0. Table management command operations Add,
Delete, Update and Query are supported. And the FDB table supports three
access methods: Entry ID, Exact Match Key Element and Search. This patch
adds the following basic supports to the FDB table.
ntmp_fdbt_update_entry() - update the configuration element data of a
specified FDB entry
ntmp_fdbt_delete_entry() - delete a specified FDB entry
ntmp_fdbt_add_entry() - add an entry into the FDB table
ntmp_fdbt_search_port_entry() - Search the FDB entry on the specified
port based on RESUME_ENTRY_ID.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/ethernet/freescale/enetc/ntmp.c | 203 +++++++++++++++++-
.../ethernet/freescale/enetc/ntmp_private.h | 61 +++++-
include/linux/fsl/ntmp.h | 44 +++-
3 files changed, 305 insertions(+), 3 deletions(-)
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c
index c94a928622fd..4ed8d783a9a2 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp.c
+++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
/*
* NETC NTMP (NETC Table Management Protocol) 2.0 Library
- * Copyright 2025 NXP
+ * Copyright 2025-2026 NXP
*/
#include <linux/dma-mapping.h>
@@ -21,11 +21,15 @@
/* Define NTMP Table ID */
#define NTMP_MAFT_ID 1
#define NTMP_RSST_ID 3
+#define NTMP_FDBT_ID 15
/* Generic Update Actions for most tables */
#define NTMP_GEN_UA_CFGEU BIT(0)
#define NTMP_GEN_UA_STSEU BIT(1)
+/* Query Action: 0: Full query, 1: Only query entry ID */
+#define NTMP_QA_ENTRY_ID 1
+
#define NTMP_ENTRY_ID_SIZE 4
#define RSST_ENTRY_NUM 64
#define RSST_STSE_DATA_SIZE(n) ((n) * 8)
@@ -260,6 +264,8 @@ static const char *ntmp_table_name(int tbl_id)
return "MAC Address Filter Table";
case NTMP_RSST_ID:
return "RSS Table";
+ case NTMP_FDBT_ID:
+ return "FDB Table";
default:
return "Unknown Table";
}
@@ -496,5 +502,200 @@ int ntmp_rsst_query_entry(struct ntmp_user *user, u32 *table, int count)
}
EXPORT_SYMBOL_GPL(ntmp_rsst_query_entry);
+/**
+ * ntmp_fdbt_add_entry - add an entry into the FDB table
+ * @user: target ntmp_user struct
+ * @entry_id: returned value, the entry ID of the new added entry
+ * @keye: key element data
+ * @cfge: configuration element data
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_fdbt_add_entry(struct ntmp_user *user, u32 *entry_id,
+ const struct fdbt_keye_data *keye,
+ const struct fdbt_cfge_data *cfge)
+{
+ struct fdbt_resp_query *resp;
+ struct fdbt_req_ua *req;
+ struct netc_swcbd swcbd;
+ struct netc_cbdr *cbdr;
+ union netc_cbd cbd;
+ u32 len;
+ int err;
+
+ swcbd.size = sizeof(*req);
+ err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req);
+ if (err)
+ return err;
+
+ /* Request data */
+ ntmp_fill_crd(&req->crd, user->tbl.fdbt_ver, NTMP_QA_ENTRY_ID,
+ NTMP_GEN_UA_CFGEU);
+ req->ak.exact.keye = *keye;
+ req->cfge = *cfge;
+
+ len = NTMP_LEN(swcbd.size, sizeof(*resp));
+ /* The entry ID is allotted by hardware, so we need to perform
+ * a query action after the add action to get the entry ID from
+ * hardware.
+ */
+ ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_FDBT_ID,
+ NTMP_CMD_AQ, NTMP_AM_EXACT_KEY);
+
+ ntmp_select_and_lock_cbdr(user, &cbdr);
+ err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd);
+ if (err) {
+ dev_err(user->dev, "Failed to add %s entry, err: %pe\n",
+ ntmp_table_name(NTMP_FDBT_ID), ERR_PTR(err));
+ goto unlock_cbdr;
+ }
+
+ if (entry_id) {
+ resp = (struct fdbt_resp_query *)req;
+ *entry_id = le32_to_cpu(resp->entry_id);
+ }
+
+unlock_cbdr:
+ ntmp_unlock_cbdr(cbdr);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_fdbt_add_entry);
+
+/**
+ * ntmp_fdbt_update_entry - update the configuration element data of the
+ * specified FDB entry
+ * @user: target ntmp_user struct
+ * @entry_id: the specified entry ID of the FDB table
+ * @cfge: configuration element data
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_fdbt_update_entry(struct ntmp_user *user, u32 entry_id,
+ const struct fdbt_cfge_data *cfge)
+{
+ struct fdbt_req_ua *req;
+ struct netc_swcbd swcbd;
+ struct netc_cbdr *cbdr;
+ union netc_cbd cbd;
+ u32 len;
+ int err;
+
+ swcbd.size = sizeof(*req);
+ err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req);
+ if (err)
+ return err;
+
+ /* Request data */
+ ntmp_fill_crd(&req->crd, user->tbl.fdbt_ver, 0, NTMP_GEN_UA_CFGEU);
+ req->ak.eid.entry_id = cpu_to_le32(entry_id);
+ req->cfge = *cfge;
+
+ /* Request header */
+ len = NTMP_LEN(swcbd.size, NTMP_STATUS_RESP_LEN);
+ ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_FDBT_ID,
+ NTMP_CMD_UPDATE, NTMP_AM_ENTRY_ID);
+
+ ntmp_select_and_lock_cbdr(user, &cbdr);
+ err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd);
+ if (err)
+ dev_err(user->dev, "Failed to update %s entry, err: %pe\n",
+ ntmp_table_name(NTMP_FDBT_ID), ERR_PTR(err));
+
+ ntmp_unlock_cbdr(cbdr);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_fdbt_update_entry);
+
+/**
+ * ntmp_fdbt_delete_entry - delete the specified FDB entry
+ * @user: target ntmp_user struct
+ * @entry_id: the specified ID of the FDB entry
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_fdbt_delete_entry(struct ntmp_user *user, u32 entry_id)
+{
+ u32 req_len = sizeof(struct fdbt_req_qd);
+
+ return ntmp_delete_entry_by_id(user, NTMP_FDBT_ID,
+ user->tbl.fdbt_ver,
+ entry_id, req_len,
+ NTMP_STATUS_RESP_LEN);
+}
+EXPORT_SYMBOL_GPL(ntmp_fdbt_delete_entry);
+
+/**
+ * ntmp_fdbt_search_port_entry - Search the FDB entry on the specified
+ * port based on RESUME_ENTRY_ID
+ * @user: target ntmp_user struct
+ * @port: the specified switch port ID
+ * @resume_entry_id: it is both an input and an output. As an input, it
+ * represents the FDB entry ID to be searched. If it is a NULL entry ID,
+ * it indicates that the first FDB entry for that port is being searched.
+ * As an output, it represents the next FDB entry ID to be searched.
+ * @entry: returned value, the response data of the searched FDB entry
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_fdbt_search_port_entry(struct ntmp_user *user, int port,
+ u32 *resume_entry_id,
+ struct fdbt_entry_data *entry)
+{
+ struct fdbt_resp_query *resp;
+ struct fdbt_req_qd *req;
+ struct netc_swcbd swcbd;
+ struct netc_cbdr *cbdr;
+ union netc_cbd cbd;
+ u32 len;
+ int err;
+
+ swcbd.size = sizeof(*req);
+ err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req);
+ if (err)
+ return err;
+
+ /* Request data */
+ ntmp_fill_crd(&req->crd, user->tbl.fdbt_ver, 0, 0);
+ req->ak.search.resume_eid = cpu_to_le32(*resume_entry_id);
+ req->ak.search.cfge.port_bitmap = cpu_to_le32(BIT(port));
+ /* Match CFGE_DATA[PORT_BITMAP] field */
+ req->ak.search.cfge_mc = FDBT_CFGE_MC_PORT_BITMAP;
+
+ /* Request header */
+ len = NTMP_LEN(swcbd.size, sizeof(*resp));
+ ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_FDBT_ID,
+ NTMP_CMD_QUERY, NTMP_AM_SEARCH);
+
+ ntmp_select_and_lock_cbdr(user, &cbdr);
+ err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd);
+ if (err) {
+ dev_err(user->dev,
+ "Failed to search %s entry on port %d, err: %pe\n",
+ ntmp_table_name(NTMP_FDBT_ID), port, ERR_PTR(err));
+ goto unlock_cbdr;
+ }
+
+ if (!cbd.resp_hdr.num_matched) {
+ entry->entry_id = NTMP_NULL_ENTRY_ID;
+ *resume_entry_id = NTMP_NULL_ENTRY_ID;
+ goto unlock_cbdr;
+ }
+
+ resp = (struct fdbt_resp_query *)req;
+ *resume_entry_id = le32_to_cpu(resp->status);
+ entry->entry_id = le32_to_cpu(resp->entry_id);
+ entry->keye = resp->keye;
+ entry->cfge = resp->cfge;
+ entry->acte = resp->acte;
+
+unlock_cbdr:
+ ntmp_unlock_cbdr(cbdr);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_fdbt_search_port_entry);
+
MODULE_DESCRIPTION("NXP NETC Library");
MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp_private.h b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
index f8dff3ba2c28..b0b5805ac4f6 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp_private.h
+++ b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
/*
* NTMP table request and response data buffer formats
- * Copyright 2025 NXP
+ * Copyright 2025-2026 NXP
*/
#ifndef __NTMP_PRIVATE_H
@@ -11,6 +11,7 @@
#include <linux/fsl/ntmp.h>
#define NTMP_EID_REQ_LEN 8
+#define NTMP_STATUS_RESP_LEN 4
#define NETC_CBDR_BD_NUM 256
#define NETC_CBDRCIR_INDEX GENMASK(9, 0)
#define NETC_CBDRCIR_SBE BIT(31)
@@ -30,6 +31,7 @@ union netc_cbd {
#define NTMP_CMD_QUERY BIT(2)
#define NTMP_CMD_ADD BIT(3)
#define NTMP_CMD_QU (NTMP_CMD_QUERY | NTMP_CMD_UPDATE)
+#define NTMP_CMD_AQ (NTMP_CMD_ADD | NTMP_CMD_QUERY)
u8 access_method;
#define NTMP_ACCESS_METHOD GENMASK(7, 4)
#define NTMP_AM_ENTRY_ID 0
@@ -97,4 +99,61 @@ struct rsst_req_update {
u8 groups[];
};
+/* Access Key Format of FDB Table */
+struct fdbt_ak_eid {
+ __le32 entry_id;
+ __le32 resv[7];
+};
+
+struct fdbt_ak_exact {
+ struct fdbt_keye_data keye;
+ __le32 resv[5];
+};
+
+struct fdbt_ak_search {
+ __le32 resume_eid;
+ struct fdbt_keye_data keye;
+ struct fdbt_cfge_data cfge;
+ u8 acte;
+ u8 keye_mc;
+#define FDBT_KEYE_MAC GENMASK(1, 0)
+ u8 cfge_mc;
+#define FDBT_CFGE_MC GENMASK(2, 0)
+#define FDBT_CFGE_MC_ANY 0
+#define FDBT_CFGE_MC_DYNAMIC 1
+#define FDBT_CFGE_MC_PORT_BITMAP 2
+#define FDBT_CFGE_MC_DYNAMIC_AND_PORT_BITMAP 3
+ u8 acte_mc;
+#define FDBT_ACTE_MC BIT(0)
+};
+
+union fdbt_access_key {
+ struct fdbt_ak_eid eid;
+ struct fdbt_ak_exact exact;
+ struct fdbt_ak_search search;
+};
+
+/* FDB Table Request Data Buffer Format of Update and Add actions */
+struct fdbt_req_ua {
+ struct ntmp_cmn_req_data crd;
+ union fdbt_access_key ak;
+ struct fdbt_cfge_data cfge;
+};
+
+/* FDB Table Request Data Buffer Format of Query and Delete actions */
+struct fdbt_req_qd {
+ struct ntmp_cmn_req_data crd;
+ union fdbt_access_key ak;
+};
+
+/* FDB Table Response Data Buffer Format of Query action */
+struct fdbt_resp_query {
+ __le32 status;
+ __le32 entry_id;
+ struct fdbt_keye_data keye;
+ struct fdbt_cfge_data cfge;
+ u8 acte;
+ u8 resv[3];
+};
+
#endif
diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h
index 83a449b4d6ec..4cfff835954e 100644
--- a/include/linux/fsl/ntmp.h
+++ b/include/linux/fsl/ntmp.h
@@ -1,11 +1,13 @@
/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
-/* Copyright 2025 NXP */
+/* Copyright 2025-2026 NXP */
#ifndef __NETC_NTMP_H
#define __NETC_NTMP_H
#include <linux/bitops.h>
#include <linux/if_ether.h>
+#define NTMP_NULL_ENTRY_ID 0xffffffffU
+
struct maft_keye_data {
u8 mac_addr[ETH_ALEN];
__le16 resv;
@@ -29,6 +31,7 @@ struct netc_cbdr_regs {
struct netc_tbl_vers {
u8 maft_ver;
u8 rsst_ver;
+ u8 fdbt_ver;
};
struct netc_swcbd {
@@ -68,6 +71,36 @@ struct maft_entry_data {
struct maft_cfge_data cfge;
};
+struct fdbt_keye_data {
+ u8 mac_addr[ETH_ALEN]; /* big-endian */
+ __le16 resv0;
+ __le16 fid;
+#define FDBT_FID GENMASK(11, 0)
+ __le16 resv1;
+};
+
+struct fdbt_cfge_data {
+ __le32 port_bitmap;
+#define FDBT_PORT_BITMAP GENMASK(23, 0)
+ __le32 cfg;
+#define FDBT_OETEID GENMASK(1, 0)
+#define FDBT_EPORT GENMASK(6, 2)
+#define FDBT_IMIRE BIT(7)
+#define FDBT_CTD GENMASK(10, 9)
+#define FDBT_DYNAMIC BIT(11)
+#define FDBT_TIMECAPE BIT(12)
+ __le32 et_eid;
+};
+
+struct fdbt_entry_data {
+ u32 entry_id;
+ struct fdbt_keye_data keye;
+ struct fdbt_cfge_data cfge;
+ u8 acte;
+#define FDBT_ACT_CNT GENMASK(6, 0)
+#define FDBT_ACT_FLAG BIT(7)
+};
+
#if IS_ENABLED(CONFIG_NXP_NETC_LIB)
int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev,
const struct netc_cbdr_regs *regs);
@@ -83,6 +116,15 @@ int ntmp_rsst_update_entry(struct ntmp_user *user, const u32 *table,
int count);
int ntmp_rsst_query_entry(struct ntmp_user *user,
u32 *table, int count);
+int ntmp_fdbt_add_entry(struct ntmp_user *user, u32 *entry_id,
+ const struct fdbt_keye_data *keye,
+ const struct fdbt_cfge_data *cfge);
+int ntmp_fdbt_update_entry(struct ntmp_user *user, u32 entry_id,
+ const struct fdbt_cfge_data *cfge);
+int ntmp_fdbt_delete_entry(struct ntmp_user *user, u32 entry_id);
+int ntmp_fdbt_search_port_entry(struct ntmp_user *user, int port,
+ u32 *resume_entry_id,
+ struct fdbt_entry_data *entry);
#else
static inline int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev,
const struct netc_cbdr_regs *regs)
--
2.34.1
^ permalink raw reply related
* [PATCH v5 net-next 05/15] net: enetc: add support for the "Add" operation to VLAN filter table
From: Wei Fang @ 2026-04-30 2:49 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260430024945.3413973-1-wei.fang@nxp.com>
The VLAN filter table contains configuration and control information for
each VLAN configured on the switch. Each VLAN entry includes the VLAN
port membership, which FID to use in the FDB lookup, which spanning tree
group to use, the egress frame modification actions to apply to a frame
exiting form this VLAN, and various configuration and control parameters
for this VLAN.
The VLAN filter table can only be managed by the command BD ring using
table management protocol version 2.0. The table supports Add, Delete,
Update and Query operations. And the table supports 3 access methods:
Entry ID, Exact Match Key Element and Search. But currently we only add
the ntmp_vft_add_entry() helper to support the upcoming switch driver to
add an entry to the VLAN filter table. Other interfaces will be added in
the future.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/ethernet/freescale/enetc/ntmp.c | 50 +++++++++++++++++++
.../ethernet/freescale/enetc/ntmp_private.h | 19 +++++++
include/linux/fsl/ntmp.h | 24 +++++++++
3 files changed, 93 insertions(+)
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c
index 4ed8d783a9a2..31e8945de6cb 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp.c
+++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
@@ -22,6 +22,7 @@
#define NTMP_MAFT_ID 1
#define NTMP_RSST_ID 3
#define NTMP_FDBT_ID 15
+#define NTMP_VFT_ID 18
/* Generic Update Actions for most tables */
#define NTMP_GEN_UA_CFGEU BIT(0)
@@ -266,6 +267,8 @@ static const char *ntmp_table_name(int tbl_id)
return "RSS Table";
case NTMP_FDBT_ID:
return "FDB Table";
+ case NTMP_VFT_ID:
+ return "VLAN Filter Table";
default:
return "Unknown Table";
}
@@ -697,5 +700,52 @@ int ntmp_fdbt_search_port_entry(struct ntmp_user *user, int port,
}
EXPORT_SYMBOL_GPL(ntmp_fdbt_search_port_entry);
+/**
+ * ntmp_vft_add_entry - add an entry into the VLAN filter table
+ * @user: target ntmp_user struct
+ * @vid: VLAN ID
+ * @cfge: configuration element data
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid,
+ const struct vft_cfge_data *cfge)
+{
+ struct netc_swcbd swcbd;
+ struct vft_req_ua *req;
+ struct netc_cbdr *cbdr;
+ union netc_cbd cbd;
+ u32 len;
+ int err;
+
+ swcbd.size = sizeof(*req);
+ err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req);
+ if (err)
+ return err;
+
+ /* Request data */
+ ntmp_fill_crd(&req->crd, user->tbl.vft_ver, 0,
+ NTMP_GEN_UA_CFGEU);
+ req->ak.exact.vid = cpu_to_le16(vid);
+ req->cfge = *cfge;
+
+ /* Request header */
+ len = NTMP_LEN(swcbd.size, NTMP_STATUS_RESP_LEN);
+ ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_VFT_ID,
+ NTMP_CMD_ADD, NTMP_AM_EXACT_KEY);
+
+ ntmp_select_and_lock_cbdr(user, &cbdr);
+ err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd);
+ if (err)
+ dev_err(user->dev,
+ "Failed to add %s entry, vid: %u, err: %pe\n",
+ ntmp_table_name(NTMP_VFT_ID), vid, ERR_PTR(err));
+
+ ntmp_unlock_cbdr(cbdr);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_vft_add_entry);
+
MODULE_DESCRIPTION("NXP NETC Library");
MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp_private.h b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
index b0b5805ac4f6..575ee783be47 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp_private.h
+++ b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
@@ -156,4 +156,23 @@ struct fdbt_resp_query {
u8 resv[3];
};
+/* Access Key Format of VLAN Filter Table */
+struct vft_ak_exact {
+ __le16 vid; /* bit0~11: VLAN ID, other bits are reserved */
+ __le16 resv;
+};
+
+union vft_access_key {
+ __le32 entry_id; /* entry_id match */
+ struct vft_ak_exact exact;
+ __le32 resume_entry_id; /* search */
+};
+
+/* VLAN Filter Table Request Data Buffer Format of Update and Add actions */
+struct vft_req_ua {
+ struct ntmp_cmn_req_data crd;
+ union vft_access_key ak;
+ struct vft_cfge_data cfge;
+};
+
#endif
diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h
index 4cfff835954e..3672e0dc7726 100644
--- a/include/linux/fsl/ntmp.h
+++ b/include/linux/fsl/ntmp.h
@@ -32,6 +32,7 @@ struct netc_tbl_vers {
u8 maft_ver;
u8 rsst_ver;
u8 fdbt_ver;
+ u8 vft_ver;
};
struct netc_swcbd {
@@ -101,6 +102,27 @@ struct fdbt_entry_data {
#define FDBT_ACT_FLAG BIT(7)
};
+struct vft_cfge_data {
+ __le32 bitmap_stg;
+#define VFT_PORT_MEMBERSHIP GENMASK(23, 0)
+#define VFT_STG_ID_MASK GENMASK(27, 24)
+#define VFT_STG_ID(g) FIELD_PREP(VFT_STG_ID_MASK, (g))
+ __le16 fid;
+#define VFT_FID GENMASK(11, 0)
+ __le16 cfg;
+#define VFT_MLO GENMASK(2, 0)
+#define VFT_MFO GENMASK(4, 3)
+#define VFT_IPMFE BIT(6)
+#define VFT_IPMFLE BIT(7)
+#define VFT_PGA BIT(8)
+#define VFT_SFDA BIT(10)
+#define VFT_OSFDA BIT(11)
+#define VFT_FDBAFSS BIT(12)
+ __le32 eta_port_bitmap;
+#define VFT_ETA_PORT_BITMAP GENMASK(23, 0)
+ __le32 et_eid;
+};
+
#if IS_ENABLED(CONFIG_NXP_NETC_LIB)
int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev,
const struct netc_cbdr_regs *regs);
@@ -125,6 +147,8 @@ int ntmp_fdbt_delete_entry(struct ntmp_user *user, u32 entry_id);
int ntmp_fdbt_search_port_entry(struct ntmp_user *user, int port,
u32 *resume_entry_id,
struct fdbt_entry_data *entry);
+int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid,
+ const struct vft_cfge_data *cfge);
#else
static inline int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev,
const struct netc_cbdr_regs *regs)
--
2.34.1
^ permalink raw reply related
* [PATCH v5 net-next 06/15] net: enetc: add support for the "Update" operation to buffer pool table
From: Wei Fang @ 2026-04-30 2:49 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260430024945.3413973-1-wei.fang@nxp.com>
The buffer pool table contains buffer pool configuration and operational
information. Each entry corresponds to a buffer pool. The Entry ID value
represents the buffer pool ID to access.
The buffer pool table is a static bounded index table, buffer pools are
always present and enabled. It only supports Update and Query operations,
This patch only adds ntmp_bpt_update_entry() helper to support updating
the specified entry of the buffer pool table. Query action to the table
will be added in the future.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/ethernet/freescale/enetc/ntmp.c | 39 +++++++++++++++++++
.../ethernet/freescale/enetc/ntmp_private.h | 6 +++
include/linux/fsl/ntmp.h | 26 +++++++++++++
3 files changed, 71 insertions(+)
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c
index 31e8945de6cb..39d7a14a8bef 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp.c
+++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
@@ -23,11 +23,15 @@
#define NTMP_RSST_ID 3
#define NTMP_FDBT_ID 15
#define NTMP_VFT_ID 18
+#define NTMP_BPT_ID 41
/* Generic Update Actions for most tables */
#define NTMP_GEN_UA_CFGEU BIT(0)
#define NTMP_GEN_UA_STSEU BIT(1)
+/* Specific Update Actions for some tables */
+#define BPT_UA_BPSEU BIT(1)
+
/* Query Action: 0: Full query, 1: Only query entry ID */
#define NTMP_QA_ENTRY_ID 1
@@ -269,6 +273,8 @@ static const char *ntmp_table_name(int tbl_id)
return "FDB Table";
case NTMP_VFT_ID:
return "VLAN Filter Table";
+ case NTMP_BPT_ID:
+ return "Buffer Pool Table";
default:
return "Unknown Table";
}
@@ -747,5 +753,38 @@ int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid,
}
EXPORT_SYMBOL_GPL(ntmp_vft_add_entry);
+int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id,
+ const struct bpt_cfge_data *cfge)
+{
+ struct bpt_req_update *req;
+ struct netc_swcbd swcbd;
+ struct netc_cbdr *cbdr;
+ union netc_cbd cbd;
+ int err;
+
+ swcbd.size = sizeof(*req);
+ err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req);
+ if (err)
+ return err;
+
+ ntmp_fill_crd_eid(&req->rbe, user->tbl.bpt_ver, 0,
+ NTMP_GEN_UA_CFGEU | BPT_UA_BPSEU, entry_id);
+ req->cfge = *cfge;
+ ntmp_fill_request_hdr(&cbd, swcbd.dma, NTMP_LEN(swcbd.size, 0),
+ NTMP_BPT_ID, NTMP_CMD_UPDATE, NTMP_AM_ENTRY_ID);
+
+ ntmp_select_and_lock_cbdr(user, &cbdr);
+ err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd);
+ if (err)
+ dev_err(user->dev,
+ "Failed to update %s entry 0x%x, err: %pe\n",
+ ntmp_table_name(NTMP_BPT_ID), entry_id, ERR_PTR(err));
+
+ ntmp_unlock_cbdr(cbdr);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_bpt_update_entry);
+
MODULE_DESCRIPTION("NXP NETC Library");
MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp_private.h b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
index 575ee783be47..64df49e9a3ef 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp_private.h
+++ b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
@@ -175,4 +175,10 @@ struct vft_req_ua {
struct vft_cfge_data cfge;
};
+/* Buffer Pool Table Request Data Buffer Format of Update action */
+struct bpt_req_update {
+ struct ntmp_req_by_eid rbe;
+ struct bpt_cfge_data cfge;
+};
+
#endif
diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h
index 3672e0dc7726..d74714a402f6 100644
--- a/include/linux/fsl/ntmp.h
+++ b/include/linux/fsl/ntmp.h
@@ -33,6 +33,7 @@ struct netc_tbl_vers {
u8 rsst_ver;
u8 fdbt_ver;
u8 vft_ver;
+ u8 bpt_ver;
};
struct netc_swcbd {
@@ -123,6 +124,29 @@ struct vft_cfge_data {
__le32 et_eid;
};
+struct bpt_bpse_data {
+ __le32 amount_used;
+ __le32 amount_used_hwm;
+ u8 bpd_fc_state;
+#define BPT_FC_STATE BIT(0)
+#define BPT_BPD BIT(1)
+} __packed;
+
+struct bpt_cfge_data {
+ u8 fccfg_sbpen;
+#define BPT_SBP_EN BIT(0)
+#define BPT_FC_CFG GENMASK(2, 1)
+#define BPT_FC_CFG_EN_BPFC 1
+ u8 pfc_vector;
+ __le16 max_thresh;
+ __le16 fc_on_thresh;
+ __le16 fc_off_thresh;
+ __le16 sbp_thresh;
+ __le16 resv;
+ __le32 sbp_eid;
+ __le32 fc_ports;
+};
+
#if IS_ENABLED(CONFIG_NXP_NETC_LIB)
int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev,
const struct netc_cbdr_regs *regs);
@@ -149,6 +173,8 @@ int ntmp_fdbt_search_port_entry(struct ntmp_user *user, int port,
struct fdbt_entry_data *entry);
int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid,
const struct vft_cfge_data *cfge);
+int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id,
+ const struct bpt_cfge_data *cfge);
#else
static inline int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev,
const struct netc_cbdr_regs *regs)
--
2.34.1
^ permalink raw reply related
* [PATCH v5 net-next 07/15] net: enetc: add support for "Add" and "Delete" operations to IPFT
From: Wei Fang @ 2026-04-30 2:49 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260430024945.3413973-1-wei.fang@nxp.com>
The ingress port filter table (IPFT )contains a set of filters each
capable of classifying incoming traffic using a mix of L2, L3, and L4
parsed and arbitrary field data. As a result of a filter match, several
actions can be specified such as on whether to deny or allow a frame,
overriding internal QoS attributes associated with the frame and setting
parameters for the subsequent frame processing functions, such as stream
identification, policing, ingress mirroring. Each entry corresponds to a
filter. The ingress port filter entries are added using a precedence
value. If a frame matches multiple entries, the entry with the higher
precedence is used. Currently, this patch only adds "Add" and "Delete"
operations to the ingress port filter table. These two interfaces will
be used by both ENETC driver and NETC switch driver.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/ethernet/freescale/enetc/ntmp.c | 76 +++++++++++++++
.../ethernet/freescale/enetc/ntmp_private.h | 36 +++++++
include/linux/fsl/ntmp.h | 93 +++++++++++++++++++
3 files changed, 205 insertions(+)
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c
index 39d7a14a8bef..add8e30a6061 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp.c
+++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
@@ -21,6 +21,7 @@
/* Define NTMP Table ID */
#define NTMP_MAFT_ID 1
#define NTMP_RSST_ID 3
+#define NTMP_IPFT_ID 13
#define NTMP_FDBT_ID 15
#define NTMP_VFT_ID 18
#define NTMP_BPT_ID 41
@@ -269,6 +270,8 @@ static const char *ntmp_table_name(int tbl_id)
return "MAC Address Filter Table";
case NTMP_RSST_ID:
return "RSS Table";
+ case NTMP_IPFT_ID:
+ return "Ingress Port Filter Table";
case NTMP_FDBT_ID:
return "FDB Table";
case NTMP_VFT_ID:
@@ -511,6 +514,79 @@ int ntmp_rsst_query_entry(struct ntmp_user *user, u32 *table, int count)
}
EXPORT_SYMBOL_GPL(ntmp_rsst_query_entry);
+/**
+ * ntmp_ipft_add_entry - add an entry into the ingress port filter table
+ * @user: target ntmp_user struct
+ * @entry: the entry data, entry->cfge (configuration element data) and
+ * entry->keye (key element data) are used as input. Since the entry ID
+ * is assigned by the hardware, so entry->entry_id is a returned value
+ * for the driver to use, the driver can update/delete/query the entry
+ * based on the entry_id.
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_ipft_add_entry(struct ntmp_user *user,
+ struct ipft_entry_data *entry)
+{
+ struct ipft_resp_query *resp;
+ struct ipft_req_ua *req;
+ struct netc_swcbd swcbd;
+ struct netc_cbdr *cbdr;
+ union netc_cbd cbd;
+ u32 len;
+ int err;
+
+ swcbd.size = sizeof(*resp);
+ err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req);
+ if (err)
+ return err;
+
+ ntmp_fill_crd(&req->crd, user->tbl.ipft_ver, NTMP_QA_ENTRY_ID,
+ NTMP_GEN_UA_CFGEU | NTMP_GEN_UA_STSEU);
+ req->ak.keye = entry->keye;
+ req->cfge = entry->cfge;
+
+ len = NTMP_LEN(sizeof(*req), swcbd.size);
+ ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_IPFT_ID,
+ NTMP_CMD_AQ, NTMP_AM_TERNARY_KEY);
+
+ ntmp_select_and_lock_cbdr(user, &cbdr);
+ err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd);
+ if (err) {
+ dev_err(user->dev, "Failed to add %s entry, err: %pe\n",
+ ntmp_table_name(NTMP_IPFT_ID), ERR_PTR(err));
+
+ goto unlock_cbdr;
+ }
+
+ resp = (struct ipft_resp_query *)req;
+ entry->entry_id = le32_to_cpu(resp->entry_id);
+
+unlock_cbdr:
+ ntmp_unlock_cbdr(cbdr);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(ntmp_ipft_add_entry);
+
+/**
+ * ntmp_ipft_delete_entry - delete a specified ingress port filter table entry
+ * @user: target ntmp_user struct
+ * @entry_id: the specified ID of the ingress port filter table entry
+ *
+ * Return: 0 on success, otherwise a negative error code
+ */
+int ntmp_ipft_delete_entry(struct ntmp_user *user, u32 entry_id)
+{
+ u32 req_len = sizeof(struct ipft_req_qd);
+
+ return ntmp_delete_entry_by_id(user, NTMP_IPFT_ID,
+ user->tbl.ipft_ver,
+ entry_id, req_len,
+ NTMP_STATUS_RESP_LEN);
+}
+EXPORT_SYMBOL_GPL(ntmp_ipft_delete_entry);
+
/**
* ntmp_fdbt_add_entry - add an entry into the FDB table
* @user: target ntmp_user struct
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp_private.h b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
index 64df49e9a3ef..0a9b87286105 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp_private.h
+++ b/drivers/net/ethernet/freescale/enetc/ntmp_private.h
@@ -99,6 +99,42 @@ struct rsst_req_update {
u8 groups[];
};
+/* Ingress Port Filter Table Response Data Buffer Format of Query action */
+struct ipft_resp_query {
+ __le32 status;
+ __le32 entry_id;
+ struct ipft_keye_data keye;
+ __le64 match_count; /* STSE_DATA */
+ struct ipft_cfge_data cfge;
+} __packed;
+
+struct ipft_ak_eid {
+ __le32 entry_id;
+ __le32 resv[52];
+};
+
+union ipft_access_key {
+ struct ipft_ak_eid eid;
+ struct ipft_keye_data keye;
+};
+
+/* Ingress Port Filter Table Request Data Buffer Format of Update and
+ * Add actions
+ */
+struct ipft_req_ua {
+ struct ntmp_cmn_req_data crd;
+ union ipft_access_key ak;
+ struct ipft_cfge_data cfge;
+};
+
+/* Ingress Port Filter Table Request Data Buffer Format of Query and
+ * Delete actions
+ */
+struct ipft_req_qd {
+ struct ntmp_req_by_eid rbe;
+ __le32 resv[52];
+};
+
/* Access Key Format of FDB Table */
struct fdbt_ak_eid {
__le32 entry_id;
diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h
index d74714a402f6..f68551045b60 100644
--- a/include/linux/fsl/ntmp.h
+++ b/include/linux/fsl/ntmp.h
@@ -7,6 +7,7 @@
#include <linux/if_ether.h>
#define NTMP_NULL_ENTRY_ID 0xffffffffU
+#define IPFT_MAX_PLD_LEN 24
struct maft_keye_data {
u8 mac_addr[ETH_ALEN];
@@ -34,6 +35,7 @@ struct netc_tbl_vers {
u8 fdbt_ver;
u8 vft_ver;
u8 bpt_ver;
+ u8 ipft_ver;
};
struct netc_swcbd {
@@ -73,6 +75,94 @@ struct maft_entry_data {
struct maft_cfge_data cfge;
};
+struct ipft_pld_byte {
+ u8 data;
+ u8 mask;
+};
+
+struct ipft_keye_data {
+ __le16 precedence;
+ __le16 resv0[3];
+ __le16 frm_attr_flags;
+#define IPFT_FAF_OVLAN BIT(2)
+#define IPFT_FAF_IVLAN BIT(3)
+#define IPFT_FAF_IP_HDR BIT(7)
+#define IPFT_FAF_IP_VER6 BIT(8)
+#define IPFT_FAF_L4_CODE GENMASK(11, 10)
+#define IPFT_FAF_TCP_HDR 1
+#define IPFT_FAF_UDP_HDR 2
+#define IPFT_FAF_SCTP_HDR 3
+#define IPFT_FAF_WOL_MAGIC BIT(12)
+ __le16 frm_attr_flags_mask;
+ __le16 dscp;
+#define IPFT_DSCP GENMASK(5, 0)
+#define IPFT_DSCP_MASK GENMASK(11, 6)
+#define IPFT_DSCP_MASK_ALL 0x3f
+ __le16 src_port; /* This field is reserved for ENETC */
+#define IPFT_SRC_PORT GENMASK(4, 0)
+#define IPFT_SRC_PORT_MASK GENMASK(9, 5)
+#define IPFT_SRC_PORT_MASK_ALL 0x1f
+ __be16 outer_vlan_tci;
+ __be16 outer_vlan_tci_mask;
+ u8 dmac[ETH_ALEN];
+ u8 dmac_mask[ETH_ALEN];
+ u8 smac[ETH_ALEN];
+ u8 smac_mask[ETH_ALEN];
+ __be16 inner_vlan_tci;
+ __be16 inner_vlan_tci_mask;
+ __be16 ethertype;
+ __be16 ethertype_mask;
+ u8 ip_protocol;
+ u8 ip_protocol_mask;
+ __le16 resv1[7];
+ __be32 ip_src[4];
+ __le32 resv2[2];
+ __be32 ip_src_mask[4];
+ __be16 l4_src_port;
+ __be16 l4_src_port_mask;
+ __le32 resv3;
+ __be32 ip_dst[4];
+ __le32 resv4[2];
+ __be32 ip_dst_mask[4];
+ __be16 l4_dst_port;
+ __be16 l4_dst_port_mask;
+ __le32 resv5;
+ struct ipft_pld_byte byte[IPFT_MAX_PLD_LEN];
+};
+
+struct ipft_cfge_data {
+ __le32 cfg;
+#define IPFT_IPV GENMASK(3, 0)
+#define IPFT_OIPV BIT(4)
+#define IPFT_DR GENMASK(6, 5)
+#define IPFT_ODR BIT(7)
+#define IPFT_FLTFA GENMASK(10, 8)
+#define IPFT_FLTFA_DISCARD 0
+#define IPFT_FLTFA_PERMIT 1
+/* Redirect is only for switch */
+#define IPFT_FLTFA_REDIRECT 2
+#define IPFT_IMIRE BIT(11)
+#define IPFT_WOLTE BIT(12)
+#define IPFT_FLTA GENMASK(14, 13)
+#define IPFT_FLTA_RP 1
+#define IPFT_FLTA_IS 2
+#define IPFT_FLTA_SI_BITMAP 3
+#define IPFT_RPR GENMASK(16, 15)
+#define IPFT_CTD BIT(17)
+#define IPFT_HR GENMASK(21, 18)
+#define IPFT_TIMECAPE BIT(22)
+#define IPFT_RRT BIT(23)
+#define IPFT_BL2F BIT(24)
+#define IPFT_EVMEID GENMASK(31, 28)
+ __le32 flta_tgt;
+};
+
+struct ipft_entry_data {
+ u32 entry_id; /* hardware assigns entry ID */
+ struct ipft_keye_data keye;
+ struct ipft_cfge_data cfge;
+};
+
struct fdbt_keye_data {
u8 mac_addr[ETH_ALEN]; /* big-endian */
__le16 resv0;
@@ -162,6 +252,9 @@ int ntmp_rsst_update_entry(struct ntmp_user *user, const u32 *table,
int count);
int ntmp_rsst_query_entry(struct ntmp_user *user,
u32 *table, int count);
+int ntmp_ipft_add_entry(struct ntmp_user *user,
+ struct ipft_entry_data *entry);
+int ntmp_ipft_delete_entry(struct ntmp_user *user, u32 entry_id);
int ntmp_fdbt_add_entry(struct ntmp_user *user, u32 *entry_id,
const struct fdbt_keye_data *keye,
const struct fdbt_cfge_data *cfge);
--
2.34.1
^ permalink raw reply related
* [PATCH v5 net-next 08/15] net: enetc: add multiple command BD rings support
From: Wei Fang @ 2026-04-30 2:49 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260430024945.3413973-1-wei.fang@nxp.com>
All the tables of NETC switch are managed through the command BD ring,
but unlike ENETC, the switch has two command BD rings, if the current
ring is busy, the switch driver can switch to another ring to manage
the table. Currently, the NTMP driver does not support multiple rings.
Therefore, update ntmp_select_and_lock_cbdr() to select a appropriate
ring to execute the command for the switch.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/ethernet/freescale/enetc/ntmp.c | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c
index add8e30a6061..4bb7d06e5dfd 100644
--- a/drivers/net/ethernet/freescale/enetc/ntmp.c
+++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
@@ -144,11 +144,16 @@ static void ntmp_clean_cbdr(struct netc_cbdr *cbdr)
static void ntmp_select_and_lock_cbdr(struct ntmp_user *user,
struct netc_cbdr **cbdr)
{
- /* Currently only ENETC is supported, and it has only one command
- * BD ring.
- */
- *cbdr = &user->ring[0];
+ for (int i = 0; i < user->cbdr_num; i++) {
+ *cbdr = &user->ring[i];
+ if (mutex_trylock(&(*cbdr)->ring_lock))
+ return;
+ }
+ /* If all command BD rings are locked, we need to select one of
+ * them and wait for it.
+ */
+ *cbdr = &user->ring[raw_smp_processor_id() % user->cbdr_num];
mutex_lock(&(*cbdr)->ring_lock);
}
--
2.34.1
^ permalink raw reply related
* [PATCH v5 net-next 09/15] net: dsa: add NETC switch tag support
From: Wei Fang @ 2026-04-30 2:49 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260430024945.3413973-1-wei.fang@nxp.com>
The NXP NETC switch tag is a proprietary header added to frames after the
source MAC address. The switch tag has 3 types, and each type has 1 ~ 4
subtypes, the details are as follows.
Forward NXP switch tag (Type=0): Represents forwarded frames.
- SubType = 0 - Normal frame processing.
To_Port NXP switch tag (Type=1): Represents frames that are to be sent
to a specific switch port.
- SubType = 0. No request to perform timestamping.
- SubType = 1. Request to perform one-step timestamping.
- SubType = 2. Request to perform two-step timestamping.
- SubType = 3. Request to perform both one-step timestamping and
two-step timestamping.
To_Host NXP switch tag (Type=2): Represents frames redirected or copied
to the switch management port.
- SubType = 0. Received frames redirected or copied to the switch
management port.
- SubType = 1. Received frames redirected or copied to the switch
management port with captured timestamp at the switch port where
the frame was received.
- SubType = 2. Transmit timestamp response (two-step timestamping).
In addition, the length of different type switch tag is different, the
minimum length is 6 bytes, the maximum length is 14 bytes. Currently,
Forward tag, SubType 0 of To_Port tag and Subtype 0 of To_Host tag are
supported. More tags will be supported in the future.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
include/linux/dsa/tag_netc.h | 14 +++
include/net/dsa.h | 2 +
include/uapi/linux/if_ether.h | 1 +
net/dsa/Kconfig | 10 ++
net/dsa/Makefile | 1 +
net/dsa/tag_netc.c | 193 ++++++++++++++++++++++++++++++++++
6 files changed, 221 insertions(+)
create mode 100644 include/linux/dsa/tag_netc.h
create mode 100644 net/dsa/tag_netc.c
diff --git a/include/linux/dsa/tag_netc.h b/include/linux/dsa/tag_netc.h
new file mode 100644
index 000000000000..fe964722e5b0
--- /dev/null
+++ b/include/linux/dsa/tag_netc.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright 2025-2026 NXP
+ */
+
+#ifndef __NET_DSA_TAG_NETC_H
+#define __NET_DSA_TAG_NETC_H
+
+#include <linux/skbuff.h>
+#include <net/dsa.h>
+
+#define NETC_TAG_MAX_LEN 14
+
+#endif
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 8b6d34e8a6f0..f199781594db 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -58,6 +58,7 @@ struct tc_action;
#define DSA_TAG_PROTO_YT921X_VALUE 30
#define DSA_TAG_PROTO_MXL_GSW1XX_VALUE 31
#define DSA_TAG_PROTO_MXL862_VALUE 32
+#define DSA_TAG_PROTO_NETC_VALUE 33
enum dsa_tag_protocol {
DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE,
@@ -93,6 +94,7 @@ enum dsa_tag_protocol {
DSA_TAG_PROTO_YT921X = DSA_TAG_PROTO_YT921X_VALUE,
DSA_TAG_PROTO_MXL_GSW1XX = DSA_TAG_PROTO_MXL_GSW1XX_VALUE,
DSA_TAG_PROTO_MXL862 = DSA_TAG_PROTO_MXL862_VALUE,
+ DSA_TAG_PROTO_NETC = DSA_TAG_PROTO_NETC_VALUE,
};
struct dsa_switch;
diff --git a/include/uapi/linux/if_ether.h b/include/uapi/linux/if_ether.h
index df9d44a11540..fb5efc8e06cc 100644
--- a/include/uapi/linux/if_ether.h
+++ b/include/uapi/linux/if_ether.h
@@ -123,6 +123,7 @@
#define ETH_P_DSA_A5PSW 0xE001 /* A5PSW Tag Value [ NOT AN OFFICIALLY REGISTERED ID ] */
#define ETH_P_IFE 0xED3E /* ForCES inter-FE LFB type */
#define ETH_P_AF_IUCV 0xFBFB /* IBM af_iucv [ NOT AN OFFICIALLY REGISTERED ID ] */
+#define ETH_P_NXP_NETC 0xFD3A /* NXP NETC DSA [ NOT AN OFFICIALLY REGISTERED ID ] */
#define ETH_P_802_3_MIN 0x0600 /* If the value in the ethernet type is more than this value
* then the frame is Ethernet II. Else it is 802.3 */
diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig
index 5ed8c704636d..d5e725b90d78 100644
--- a/net/dsa/Kconfig
+++ b/net/dsa/Kconfig
@@ -125,6 +125,16 @@ config NET_DSA_TAG_KSZ
Say Y if you want to enable support for tagging frames for the
Microchip 8795/937x/9477/9893 families of switches.
+config NET_DSA_TAG_NETC
+ tristate "Tag driver for NXP NETC switches"
+ help
+ Say Y or M if you want to enable support for the NXP Switch Tag (NST),
+ as implemented by NXP NETC switches having version 4.3 or later. The
+ switch tag is a proprietary header added to frames after the source
+ MAC address, it has 3 types and each type has different subtypes, so
+ its length depends on the type and subtype of the tag, the maximum
+ length is 14 bytes.
+
config NET_DSA_TAG_OCELOT
tristate "Tag driver for Ocelot family of switches, using NPI port"
select PACKING
diff --git a/net/dsa/Makefile b/net/dsa/Makefile
index bf7247759a64..b8c2667cd14a 100644
--- a/net/dsa/Makefile
+++ b/net/dsa/Makefile
@@ -30,6 +30,7 @@ obj-$(CONFIG_NET_DSA_TAG_LAN9303) += tag_lan9303.o
obj-$(CONFIG_NET_DSA_TAG_MTK) += tag_mtk.o
obj-$(CONFIG_NET_DSA_TAG_MXL_862XX) += tag_mxl862xx.o
obj-$(CONFIG_NET_DSA_TAG_MXL_GSW1XX) += tag_mxl-gsw1xx.o
+obj-$(CONFIG_NET_DSA_TAG_NETC) += tag_netc.o
obj-$(CONFIG_NET_DSA_TAG_NONE) += tag_none.o
obj-$(CONFIG_NET_DSA_TAG_OCELOT) += tag_ocelot.o
obj-$(CONFIG_NET_DSA_TAG_OCELOT_8021Q) += tag_ocelot_8021q.o
diff --git a/net/dsa/tag_netc.c b/net/dsa/tag_netc.c
new file mode 100644
index 000000000000..c2e4aa7e5ec2
--- /dev/null
+++ b/net/dsa/tag_netc.c
@@ -0,0 +1,193 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2025-2026 NXP
+ */
+
+#include <linux/dsa/tag_netc.h>
+
+#include "tag.h"
+
+#define NETC_NAME "nxp_netc"
+
+/* Forward NXP switch tag */
+#define NETC_TAG_FORWARD 0
+
+/* To_Port NXP switch tag */
+#define NETC_TAG_TO_PORT 1
+/* SubType0: No request to perform timestamping */
+#define NETC_TAG_TP_SUBTYPE0 0
+
+/* To_Host NXP switch tag */
+#define NETC_TAG_TO_HOST 2
+/* SubType0: frames redirected or copied to CPU port */
+#define NETC_TAG_TH_SUBTYPE0 0
+/* SubType1: frames redirected or copied to CPU port with timestamp */
+#define NETC_TAG_TH_SUBTYPE1 1
+/* SubType2: Transmit timestamp response (two-step timestamping) */
+#define NETC_TAG_TH_SUBTYPE2 2
+
+/* NETC switch tag lengths */
+#define NETC_TAG_FORWARD_LEN 6
+#define NETC_TAG_TP_SUBTYPE0_LEN 6
+#define NETC_TAG_TH_SUBTYPE0_LEN 6
+#define NETC_TAG_TH_SUBTYPE1_LEN 14
+#define NETC_TAG_TH_SUBTYPE2_LEN 14
+#define NETC_TAG_CMN_LEN 5
+
+#define NETC_TAG_SUBTYPE GENMASK(3, 0)
+#define NETC_TAG_TYPE GENMASK(7, 4)
+#define NETC_TAG_QV BIT(0)
+#define NETC_TAG_IPV GENMASK(4, 2)
+#define NETC_TAG_SWITCH GENMASK(2, 0)
+#define NETC_TAG_PORT GENMASK(7, 3)
+
+struct netc_tag_cmn {
+ __be16 tpid;
+ u8 type;
+ u8 qos;
+ u8 switch_port;
+} __packed;
+
+static void netc_fill_common_tag(struct netc_tag_cmn *tag, u8 type,
+ u8 subtype, u8 sw_id, u8 port, u8 ipv)
+{
+ tag->tpid = htons(ETH_P_NXP_NETC);
+ tag->type = FIELD_PREP(NETC_TAG_TYPE, type) |
+ FIELD_PREP(NETC_TAG_SUBTYPE, subtype);
+ tag->qos = NETC_TAG_QV | FIELD_PREP(NETC_TAG_IPV, ipv);
+ tag->switch_port = FIELD_PREP(NETC_TAG_SWITCH, sw_id) |
+ FIELD_PREP(NETC_TAG_PORT, port);
+}
+
+static void *netc_fill_common_tp_tag(struct sk_buff *skb,
+ struct net_device *ndev,
+ u8 subtype, int tag_len)
+{
+ struct dsa_port *dp = dsa_user_to_port(ndev);
+ u16 queue = skb_get_queue_mapping(skb);
+ s8 ipv = netdev_txq_to_tc(ndev, queue);
+ void *tag;
+
+ if (unlikely(ipv < 0))
+ ipv = 0;
+
+ skb_push(skb, tag_len);
+ dsa_alloc_etype_header(skb, tag_len);
+
+ tag = dsa_etype_header_pos_tx(skb);
+ memset(tag + NETC_TAG_CMN_LEN, 0, tag_len - NETC_TAG_CMN_LEN);
+ /* As 'dsa,member' is a required property for NETC switch, the
+ * member is used to specify the switch ID (thus the hardware
+ * switch ID and the software switch ID are consistent), which
+ * is a non-zero value, so dp->ds->index will not 0 here.
+ */
+ netc_fill_common_tag(tag, NETC_TAG_TO_PORT, subtype,
+ dp->ds->index, dp->index, ipv);
+
+ return tag;
+}
+
+static void netc_fill_tp_tag_subtype0(struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ netc_fill_common_tp_tag(skb, ndev, NETC_TAG_TP_SUBTYPE0,
+ NETC_TAG_TP_SUBTYPE0_LEN);
+}
+
+/* Currently only support To_Port tag, subtype 0 */
+static struct sk_buff *netc_xmit(struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ netc_fill_tp_tag_subtype0(skb, ndev);
+
+ return skb;
+}
+
+static int netc_get_rx_tag_len(int rx_type)
+{
+ int type = FIELD_GET(NETC_TAG_TYPE, rx_type);
+
+ if (type == NETC_TAG_TO_HOST) {
+ u8 subtype = rx_type & NETC_TAG_SUBTYPE;
+
+ if (subtype == NETC_TAG_TH_SUBTYPE1)
+ return NETC_TAG_TH_SUBTYPE1_LEN;
+ else if (subtype == NETC_TAG_TH_SUBTYPE2)
+ return NETC_TAG_TH_SUBTYPE2_LEN;
+ else
+ return NETC_TAG_TH_SUBTYPE0_LEN;
+ }
+
+ return NETC_TAG_FORWARD_LEN;
+}
+
+static struct sk_buff *netc_rcv(struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ struct netc_tag_cmn *tag_cmn;
+ int tag_len, sw_id, port;
+
+ if (unlikely(!pskb_may_pull(skb, NETC_TAG_MAX_LEN)))
+ return NULL;
+
+ tag_cmn = dsa_etype_header_pos_rx(skb);
+ tag_len = netc_get_rx_tag_len(tag_cmn->type);
+
+ if (ntohs(tag_cmn->tpid) != ETH_P_NXP_NETC) {
+ dev_warn_ratelimited(&ndev->dev, "Unknown TPID 0x%04x\n",
+ ntohs(tag_cmn->tpid));
+
+ return NULL;
+ }
+
+ if (tag_cmn->qos & NETC_TAG_QV)
+ skb->priority = FIELD_GET(NETC_TAG_IPV, tag_cmn->qos);
+
+ sw_id = FIELD_GET(NETC_TAG_SWITCH, tag_cmn->switch_port);
+ /* ENETC VEPA switch ID (0) is not supported yet */
+ if (!sw_id) {
+ dev_warn_ratelimited(&ndev->dev,
+ "VEPA switch ID is not supported yet\n");
+
+ return NULL;
+ }
+
+ port = FIELD_GET(NETC_TAG_PORT, tag_cmn->switch_port);
+ skb->dev = dsa_conduit_find_user(ndev, sw_id, port);
+ if (!skb->dev)
+ return NULL;
+
+ if (tag_cmn->type == NETC_TAG_FORWARD)
+ dsa_default_offload_fwd_mark(skb);
+
+ /* Remove Switch tag from the frame */
+ skb_pull_rcsum(skb, tag_len);
+ dsa_strip_etype_header(skb, tag_len);
+
+ return skb;
+}
+
+static void netc_flow_dissect(const struct sk_buff *skb, __be16 *proto,
+ int *offset)
+{
+ struct netc_tag_cmn *tag_cmn = (struct netc_tag_cmn *)(skb->data - 2);
+ int tag_len = netc_get_rx_tag_len(tag_cmn->type);
+
+ *offset = tag_len;
+ *proto = ((__be16 *)skb->data)[(tag_len / 2) - 1];
+}
+
+static const struct dsa_device_ops netc_netdev_ops = {
+ .name = NETC_NAME,
+ .proto = DSA_TAG_PROTO_NETC,
+ .xmit = netc_xmit,
+ .rcv = netc_rcv,
+ .needed_headroom = NETC_TAG_MAX_LEN,
+ .flow_dissect = netc_flow_dissect,
+};
+
+MODULE_DESCRIPTION("DSA tag driver for NXP NETC switch family");
+MODULE_LICENSE("GPL");
+
+MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_NETC, NETC_NAME);
+module_dsa_tag_driver(netc_netdev_ops);
--
2.34.1
^ permalink raw reply related
* [PATCH v5 net-next 10/15] net: dsa: netc: introduce NXP NETC switch driver for i.MX94
From: Wei Fang @ 2026-04-30 2:49 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260430024945.3413973-1-wei.fang@nxp.com>
For i.MX94 series, the NETC IP provides full 802.1Q Ethernet switch
functionality, advanced QoS with 8 traffic classes, and a full range of
TSN standards capabilities. The switch has 3 user ports and 1 CPU port,
the CPU port is connected to an internal ENETC. Since the switch and the
internal ENETC are fully integrated within the NETC IP, no back-to-back
MAC connection is required. Instead, a light-weight "pseudo MAC" is used
between the switch and the ENETC. This translates to lower power (less
logic and memory) and lower delay (as there is no serialization delay
across this link).
Introduce the initial NETC switch driver with basic probe and remove
functionality. More features will be added in subsequent patches.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
MAINTAINERS | 11 +
drivers/net/dsa/Kconfig | 2 +
drivers/net/dsa/Makefile | 1 +
drivers/net/dsa/netc/Kconfig | 14 +
drivers/net/dsa/netc/Makefile | 3 +
drivers/net/dsa/netc/netc_main.c | 615 ++++++++++++++++++++++++++
drivers/net/dsa/netc/netc_platform.c | 49 ++
drivers/net/dsa/netc/netc_switch.h | 92 ++++
drivers/net/dsa/netc/netc_switch_hw.h | 133 ++++++
9 files changed, 920 insertions(+)
create mode 100644 drivers/net/dsa/netc/Kconfig
create mode 100644 drivers/net/dsa/netc/Makefile
create mode 100644 drivers/net/dsa/netc/netc_main.c
create mode 100644 drivers/net/dsa/netc/netc_platform.c
create mode 100644 drivers/net/dsa/netc/netc_switch.h
create mode 100644 drivers/net/dsa/netc/netc_switch_hw.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 2fb1c75afd16..d880b3222d45 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19251,6 +19251,17 @@ F: Documentation/devicetree/bindings/clock/*imx*
F: drivers/clk/imx/
F: include/dt-bindings/clock/*imx*
+NXP NETC ETHERNET SWITCH DRIVER
+M: Wei Fang <wei.fang@nxp.com>
+R: Clark Wang <xiaoning.wang@nxp.com>
+L: imx@lists.linux.dev
+L: netdev@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/net/dsa/nxp,netc-switch.yaml
+F: drivers/net/dsa/netc/
+F: include/linux/dsa/tag_netc.h
+F: net/dsa/tag_netc.c
+
NXP NETC TIMER PTP CLOCK DRIVER
M: Wei Fang <wei.fang@nxp.com>
M: Clark Wang <xiaoning.wang@nxp.com>
diff --git a/drivers/net/dsa/Kconfig b/drivers/net/dsa/Kconfig
index 39fb8ead16b5..4ab567c5bbaf 100644
--- a/drivers/net/dsa/Kconfig
+++ b/drivers/net/dsa/Kconfig
@@ -76,6 +76,8 @@ source "drivers/net/dsa/mv88e6xxx/Kconfig"
source "drivers/net/dsa/mxl862xx/Kconfig"
+source "drivers/net/dsa/netc/Kconfig"
+
source "drivers/net/dsa/ocelot/Kconfig"
source "drivers/net/dsa/qca/Kconfig"
diff --git a/drivers/net/dsa/Makefile b/drivers/net/dsa/Makefile
index f5a463b87ec2..d2975badffc0 100644
--- a/drivers/net/dsa/Makefile
+++ b/drivers/net/dsa/Makefile
@@ -21,6 +21,7 @@ obj-y += lantiq/
obj-y += microchip/
obj-y += mv88e6xxx/
obj-y += mxl862xx/
+obj-y += netc/
obj-y += ocelot/
obj-y += qca/
obj-y += realtek/
diff --git a/drivers/net/dsa/netc/Kconfig b/drivers/net/dsa/netc/Kconfig
new file mode 100644
index 000000000000..8824d30ed3ea
--- /dev/null
+++ b/drivers/net/dsa/netc/Kconfig
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config NET_DSA_NETC_SWITCH
+ tristate "NXP NETC Ethernet switch support"
+ depends on NET_DSA && PCI
+ select NET_DSA_TAG_NETC
+ select FSL_ENETC_MDIO
+ select NXP_NTMP
+ select NXP_NETC_LIB
+ help
+ This driver supports the NXP NETC Ethernet switch, which is embedded
+ as a PCIe function of the NXP NETC IP. But note that this driver does
+ only support switch versions greater than or equal to NETC v4.3.
+
+ If compiled as module (M), the module name is nxp-netc-switch.
diff --git a/drivers/net/dsa/netc/Makefile b/drivers/net/dsa/netc/Makefile
new file mode 100644
index 000000000000..4a5767562574
--- /dev/null
+++ b/drivers/net/dsa/netc/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_NET_DSA_NETC_SWITCH) += nxp-netc-switch.o
+nxp-netc-switch-objs := netc_main.o netc_platform.o
diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c
new file mode 100644
index 000000000000..90a2d8cfd3d2
--- /dev/null
+++ b/drivers/net/dsa/netc/netc_main.c
@@ -0,0 +1,615 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
+/*
+ * NXP NETC switch driver
+ * Copyright 2025-2026 NXP
+ */
+
+#include <linux/clk.h>
+#include <linux/etherdevice.h>
+#include <linux/fsl/enetc_mdio.h>
+#include <linux/if_vlan.h>
+#include <linux/of_mdio.h>
+
+#include "netc_switch.h"
+
+static enum dsa_tag_protocol
+netc_get_tag_protocol(struct dsa_switch *ds, int port,
+ enum dsa_tag_protocol mprot)
+{
+ return DSA_TAG_PROTO_NETC;
+}
+
+static void netc_port_rmw(struct netc_port *np, u32 reg,
+ u32 mask, u32 val)
+{
+ u32 old, new;
+
+ WARN_ON((mask | val) != mask);
+
+ old = netc_port_rd(np, reg);
+ new = (old & ~mask) | val;
+ if (new == old)
+ return;
+
+ netc_port_wr(np, reg, new);
+}
+
+static void netc_mac_port_wr(struct netc_port *np, u32 reg, u32 val)
+{
+ if (is_netc_pseudo_port(np))
+ return;
+
+ netc_port_wr(np, reg, val);
+ if (np->caps.pmac)
+ netc_port_wr(np, reg + NETC_PMAC_OFFSET, val);
+}
+
+static void netc_port_get_capability(struct netc_port *np)
+{
+ u32 val;
+
+ val = netc_port_rd(np, NETC_PMCAPR);
+ if (val & PMCAPR_HD)
+ np->caps.half_duplex = true;
+
+ if (FIELD_GET(PMCAPR_FP, val) == FP_SUPPORT)
+ np->caps.pmac = true;
+
+ val = netc_port_rd(np, NETC_PCAPR);
+ if (val & PCAPR_LINK_TYPE)
+ np->caps.pseudo_link = true;
+}
+
+static int netc_port_get_info_from_dt(struct netc_port *np,
+ struct device_node *node,
+ struct device *dev)
+{
+ if (of_find_property(node, "clock-names", NULL)) {
+ np->ref_clk = devm_get_clk_from_child(dev, node, "ref");
+ if (IS_ERR(np->ref_clk)) {
+ dev_err(dev, "Port %d cannot get reference clock\n",
+ np->dp->index);
+ return PTR_ERR(np->ref_clk);
+ }
+ }
+
+ return 0;
+}
+
+static int netc_port_create_emdio_bus(struct netc_port *np,
+ struct device_node *node)
+{
+ struct netc_switch *priv = np->switch_priv;
+ struct enetc_mdio_priv *mdio_priv;
+ struct device *dev = priv->dev;
+ struct enetc_hw *hw;
+ struct mii_bus *bus;
+ int err;
+
+ hw = enetc_hw_alloc(dev, np->iobase);
+ if (IS_ERR(hw))
+ return dev_err_probe(dev, PTR_ERR(hw),
+ "Failed to allocate enetc_hw\n");
+
+ bus = devm_mdiobus_alloc_size(dev, sizeof(*mdio_priv));
+ if (!bus)
+ return -ENOMEM;
+
+ bus->name = "NXP NETC switch external MDIO Bus";
+ bus->read = enetc_mdio_read_c22;
+ bus->write = enetc_mdio_write_c22;
+ bus->read_c45 = enetc_mdio_read_c45;
+ bus->write_c45 = enetc_mdio_write_c45;
+ bus->parent = dev;
+ mdio_priv = bus->priv;
+ mdio_priv->hw = hw;
+ mdio_priv->mdio_base = NETC_EMDIO_BASE;
+ snprintf(bus->id, MII_BUS_ID_SIZE, "%s-p%d-emdio",
+ dev_name(dev), np->dp->index);
+
+ err = devm_of_mdiobus_register(dev, bus, node);
+ if (err)
+ return dev_err_probe(dev, err,
+ "Cannot register EMDIO bus\n");
+
+ np->emdio = bus;
+
+ return 0;
+}
+
+static int netc_port_create_mdio_bus(struct netc_port *np,
+ struct device_node *node)
+{
+ struct device_node *mdio_node;
+ int err;
+
+ mdio_node = of_get_child_by_name(node, "mdio");
+ if (mdio_node) {
+ err = netc_port_create_emdio_bus(np, mdio_node);
+ of_node_put(mdio_node);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int netc_init_switch_id(struct netc_switch *priv)
+{
+ struct netc_switch_regs *regs = &priv->regs;
+ struct dsa_switch *ds = priv->ds;
+
+ /* The value of 0 is reserved for the VEPA switch and cannot
+ * be used. So 'dsa,member' is a required property for NETC
+ * switch, the member is used to specify the switch ID, which
+ * cannot be zero. This way, the hardware switch ID and the
+ * software switch ID are consistent.
+ */
+ if (ds->index > FIELD_MAX(SWCR_SWID) || !ds->index) {
+ dev_err(priv->dev, "Switch index %d out of range\n",
+ ds->index);
+ return -ERANGE;
+ }
+
+ netc_base_wr(regs, NETC_SWCR, ds->index);
+
+ return 0;
+}
+
+static int netc_init_all_ports(struct netc_switch *priv)
+{
+ struct device *dev = priv->dev;
+ struct netc_port *np;
+ struct dsa_port *dp;
+ int err;
+
+ priv->ports = devm_kcalloc(dev, priv->info->num_ports,
+ sizeof(struct netc_port *),
+ GFP_KERNEL);
+ if (!priv->ports)
+ return -ENOMEM;
+
+ /* Some DSA interfaces may set the port even it is disabled, such
+ * as .port_disable(), .port_stp_state_set() and so on. To avoid
+ * crash caused by accessing NULL port pointer, each port is
+ * allocated its own memory. Otherwise, we need to check whether
+ * the port pointer is NULL in these interfaces. The latter is
+ * difficult for us to cover.
+ */
+ for (int i = 0; i < priv->info->num_ports; i++) {
+ np = devm_kzalloc(dev, sizeof(*np), GFP_KERNEL);
+ if (!np)
+ return -ENOMEM;
+
+ np->switch_priv = priv;
+ np->iobase = priv->regs.port + PORT_IOBASE(i);
+ netc_port_get_capability(np);
+ priv->ports[i] = np;
+ }
+
+ dsa_switch_for_each_available_port(dp, priv->ds) {
+ np = priv->ports[dp->index];
+ np->dp = dp;
+ err = netc_port_get_info_from_dt(np, dp->dn, dev);
+ if (err)
+ return err;
+
+ if (dsa_port_is_user(dp)) {
+ err = netc_port_create_mdio_bus(np, dp->dn);
+ if (err) {
+ dev_err(dev, "Failed to create MDIO bus\n");
+ return err;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static void netc_init_ntmp_tbl_versions(struct netc_switch *priv)
+{
+ struct ntmp_user *ntmp = &priv->ntmp;
+
+ /* All tables default to version 0 */
+ memset(&ntmp->tbl, 0, sizeof(ntmp->tbl));
+}
+
+static int netc_init_all_cbdrs(struct netc_switch *priv)
+{
+ struct netc_switch_regs *regs = &priv->regs;
+ struct ntmp_user *ntmp = &priv->ntmp;
+ int i, err;
+
+ ntmp->cbdr_num = NETC_CBDR_NUM;
+ ntmp->dev = priv->dev;
+ ntmp->ring = devm_kcalloc(ntmp->dev, ntmp->cbdr_num,
+ sizeof(struct netc_cbdr),
+ GFP_KERNEL);
+ if (!ntmp->ring)
+ return -ENOMEM;
+
+ for (i = 0; i < ntmp->cbdr_num; i++) {
+ struct netc_cbdr *cbdr = &ntmp->ring[i];
+ struct netc_cbdr_regs cbdr_regs;
+
+ cbdr_regs.pir = regs->base + NETC_CBDRPIR(i);
+ cbdr_regs.cir = regs->base + NETC_CBDRCIR(i);
+ cbdr_regs.mr = regs->base + NETC_CBDRMR(i);
+ cbdr_regs.bar0 = regs->base + NETC_CBDRBAR0(i);
+ cbdr_regs.bar1 = regs->base + NETC_CBDRBAR1(i);
+ cbdr_regs.lenr = regs->base + NETC_CBDRLENR(i);
+
+ err = ntmp_init_cbdr(cbdr, ntmp->dev, &cbdr_regs);
+ if (err)
+ goto free_cbdrs;
+ }
+
+ return 0;
+
+free_cbdrs:
+ for (i--; i >= 0; i--)
+ ntmp_free_cbdr(&ntmp->ring[i]);
+
+ return err;
+}
+
+static void netc_remove_all_cbdrs(struct netc_switch *priv)
+{
+ struct ntmp_user *ntmp = &priv->ntmp;
+
+ for (int i = 0; i < NETC_CBDR_NUM; i++)
+ ntmp_free_cbdr(&ntmp->ring[i]);
+}
+
+static int netc_init_ntmp_user(struct netc_switch *priv)
+{
+ netc_init_ntmp_tbl_versions(priv);
+
+ return netc_init_all_cbdrs(priv);
+}
+
+static void netc_free_ntmp_user(struct netc_switch *priv)
+{
+ netc_remove_all_cbdrs(priv);
+}
+
+static void netc_switch_dos_default_config(struct netc_switch *priv)
+{
+ struct netc_switch_regs *regs = &priv->regs;
+ u32 val;
+
+ val = DOSL2CR_SAMEADDR | DOSL2CR_MSAMCC;
+ netc_base_wr(regs, NETC_DOSL2CR, val);
+
+ val = DOSL3CR_SAMEADDR | DOSL3CR_IPSAMCC;
+ netc_base_wr(regs, NETC_DOSL3CR, val);
+}
+
+static void netc_switch_vfht_default_config(struct netc_switch *priv)
+{
+ struct netc_switch_regs *regs = &priv->regs;
+ u32 val;
+
+ val = netc_base_rd(regs, NETC_VFHTDECR2);
+
+ /* If no match is found in the VLAN Filter table, then VFHTDECR2[MLO]
+ * will take effect. VFHTDECR2[MLO] is set to "Software MAC learning
+ * secure" by default. Notice BPCR[MLO] will override VFHTDECR2[MLO]
+ * if its value is not zero.
+ */
+ val = u32_replace_bits(val, MLO_SW_SEC, VFHTDECR2_MLO);
+ val = u32_replace_bits(val, MFO_NO_MATCH_DISCARD, VFHTDECR2_MFO);
+ netc_base_wr(regs, NETC_VFHTDECR2, val);
+}
+
+static void netc_port_set_max_frame_size(struct netc_port *np,
+ u32 max_frame_size)
+{
+ netc_mac_port_wr(np, NETC_PM_MAXFRM(0),
+ max_frame_size & PM_MAXFRAM);
+}
+
+static void netc_switch_fixed_config(struct netc_switch *priv)
+{
+ netc_switch_dos_default_config(priv);
+ netc_switch_vfht_default_config(priv);
+}
+
+static void netc_port_set_tc_max_sdu(struct netc_port *np,
+ int tc, u32 max_sdu)
+{
+ u32 val = FIELD_PREP(PTCTMSDUR_MAXSDU, max_sdu) |
+ FIELD_PREP(PTCTMSDUR_SDU_TYPE, SDU_TYPE_MPDU);
+
+ netc_port_wr(np, NETC_PTCTMSDUR(tc), val);
+}
+
+static void netc_port_set_all_tc_msdu(struct netc_port *np)
+{
+ for (int tc = 0; tc < NETC_TC_NUM; tc++)
+ netc_port_set_tc_max_sdu(np, tc, NETC_MAX_FRAME_LEN);
+}
+
+static void netc_port_set_mlo(struct netc_port *np, enum netc_mlo mlo)
+{
+ netc_port_rmw(np, NETC_BPCR, BPCR_MLO, FIELD_PREP(BPCR_MLO, mlo));
+}
+
+static void netc_port_fixed_config(struct netc_port *np)
+{
+ /* Default IPV and DR setting */
+ netc_port_rmw(np, NETC_PQOSMR, PQOSMR_VS | PQOSMR_VE,
+ PQOSMR_VS | PQOSMR_VE);
+
+ /* Enable L2 and L3 DOS */
+ netc_port_rmw(np, NETC_PCR, PCR_L2DOSE | PCR_L3DOSE,
+ PCR_L2DOSE | PCR_L3DOSE);
+}
+
+static void netc_port_default_config(struct netc_port *np)
+{
+ netc_port_fixed_config(np);
+
+ /* Default VLAN unaware */
+ netc_port_rmw(np, NETC_BPDVR, BPDVR_RXVAM, BPDVR_RXVAM);
+
+ if (dsa_port_is_cpu(np->dp))
+ /* For CPU port, source port pruning is disabled */
+ netc_port_rmw(np, NETC_BPCR, BPCR_SRCPRND, BPCR_SRCPRND);
+ else
+ netc_port_set_mlo(np, MLO_DISABLE);
+
+ netc_port_set_max_frame_size(np, NETC_MAX_FRAME_LEN);
+ netc_port_set_all_tc_msdu(np);
+}
+
+static int netc_setup(struct dsa_switch *ds)
+{
+ struct netc_switch *priv = ds->priv;
+ struct dsa_port *dp;
+ int err;
+
+ err = netc_init_switch_id(priv);
+ if (err)
+ return err;
+
+ err = netc_init_all_ports(priv);
+ if (err)
+ return err;
+
+ err = netc_init_ntmp_user(priv);
+ if (err)
+ return err;
+
+ netc_switch_fixed_config(priv);
+
+ /* default setting for ports */
+ dsa_switch_for_each_available_port(dp, ds)
+ netc_port_default_config(priv->ports[dp->index]);
+
+ return 0;
+}
+
+static void netc_teardown(struct dsa_switch *ds)
+{
+ struct netc_switch *priv = ds->priv;
+
+ netc_free_ntmp_user(priv);
+}
+
+static bool netc_port_is_emdio_consumer(struct device_node *node)
+{
+ struct device_node *mdio_node;
+
+ /* If the port node has phy-handle property and it does
+ * not contain a mdio child node, then the port is the
+ * EMDIO consumer.
+ */
+ mdio_node = of_get_child_by_name(node, "mdio");
+ if (!mdio_node)
+ return true;
+
+ of_node_put(mdio_node);
+
+ return false;
+}
+
+/* Currently, phylink_of_phy_connect() is called by dsa_user_create(),
+ * so if the switch uses the external MDIO controller (like the EMDIO
+ * function) to manage the external PHYs. The MDIO bus may not be
+ * created when phylink_of_phy_connect() is called, so it will return
+ * an error and cause the switch driver to fail to probe.
+ * This workaround can be removed when DSA phylink_of_phy_connect()
+ * calls are moved from probe() to ndo_open().
+ */
+static int netc_switch_check_emdio_is_ready(struct device *dev)
+{
+ struct device_node *ports, *phy_node;
+ struct phy_device *phydev;
+ int err = 0;
+
+ ports = of_get_child_by_name(dev->of_node, "ethernet-ports");
+ if (!ports) {
+ dev_err(dev, "Cannot find the ethernet-ports node\n");
+ return -EINVAL;
+ }
+
+ for_each_available_child_of_node_scoped(ports, child) {
+ /* If the node does not have phy-handle property, then the
+ * port does not connect to a PHY, so the port is not the
+ * EMDIO consumer.
+ */
+ phy_node = of_parse_phandle(child, "phy-handle", 0);
+ if (!phy_node)
+ continue;
+
+ /* Note that from the hardware perspective, the switch ports
+ * do not support sharing the MDIO bus defined under one port.
+ * Each port can only access its own external PHY through its
+ * port MDIO bus.
+ */
+ if (!netc_port_is_emdio_consumer(child)) {
+ of_node_put(phy_node);
+ continue;
+ }
+
+ phydev = of_phy_find_device(phy_node);
+ of_node_put(phy_node);
+ if (!phydev) {
+ err = -EPROBE_DEFER;
+ goto out;
+ }
+
+ put_device(&phydev->mdio.dev);
+ }
+
+out:
+ of_node_put(ports);
+
+ return err;
+}
+
+static int netc_switch_pci_init(struct pci_dev *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct netc_switch_regs *regs;
+ struct netc_switch *priv;
+ void __iomem *base;
+ int err;
+
+ pcie_flr(pdev);
+ err = pcim_enable_device(pdev);
+ if (err)
+ return dev_err_probe(dev, err, "Failed to enable device\n");
+
+ err = pcim_request_all_regions(pdev, KBUILD_MODNAME);
+ if (err)
+ return dev_err_probe(dev, err, "Failed to request regions\n");
+
+ /* The command BD rings and NTMP tables need DMA. No need to check
+ * the return value, because it never returns fail when the mask is
+ * DMA_BIT_MASK(64), see dma-api-howto.rst.
+ */
+ dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
+
+ base = pcim_iomap(pdev, NETC_REGS_BAR, 0);
+ if (!base)
+ return dev_err_probe(dev, -ENXIO, "pcim_iomap() failed\n");
+
+ pci_set_master(pdev);
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->pdev = pdev;
+ priv->dev = dev;
+
+ regs = &priv->regs;
+ regs->base = base;
+ regs->port = regs->base + NETC_REGS_PORT_BASE;
+ regs->global = regs->base + NETC_REGS_GLOBAL_BASE;
+ pci_set_drvdata(pdev, priv);
+
+ return 0;
+}
+
+static void netc_switch_get_ip_revision(struct netc_switch *priv)
+{
+ struct netc_switch_regs *regs = &priv->regs;
+ u32 val = netc_glb_rd(regs, NETC_IPBRR0);
+
+ priv->revision = FIELD_GET(IPBRR0_IP_REV, val);
+}
+
+static const struct dsa_switch_ops netc_switch_ops = {
+ .get_tag_protocol = netc_get_tag_protocol,
+ .setup = netc_setup,
+ .teardown = netc_teardown,
+};
+
+static int netc_switch_probe(struct pci_dev *pdev,
+ const struct pci_device_id *id)
+{
+ struct device_node *node = dev_of_node(&pdev->dev);
+ struct device *dev = &pdev->dev;
+ struct netc_switch *priv;
+ struct dsa_switch *ds;
+ int err;
+
+ if (!node)
+ return dev_err_probe(dev, -ENODEV,
+ "No DT bindings, skipping\n");
+
+ err = netc_switch_check_emdio_is_ready(dev);
+ if (err)
+ return err;
+
+ err = netc_switch_pci_init(pdev);
+ if (err)
+ return err;
+
+ priv = pci_get_drvdata(pdev);
+ netc_switch_get_ip_revision(priv);
+
+ err = netc_switch_platform_probe(priv);
+ if (err)
+ return err;
+
+ ds = devm_kzalloc(dev, sizeof(*ds), GFP_KERNEL);
+ if (!ds)
+ return -ENOMEM;
+
+ ds->dev = dev;
+ ds->num_ports = priv->info->num_ports;
+ ds->num_tx_queues = NETC_TC_NUM;
+ ds->ops = &netc_switch_ops;
+ ds->priv = priv;
+ priv->ds = ds;
+
+ err = dsa_register_switch(ds);
+ if (err)
+ return dev_err_probe(dev, err,
+ "Failed to register DSA switch\n");
+
+ return 0;
+}
+
+static void netc_switch_remove(struct pci_dev *pdev)
+{
+ struct netc_switch *priv = pci_get_drvdata(pdev);
+
+ if (!priv)
+ return;
+
+ dsa_unregister_switch(priv->ds);
+}
+
+static void netc_switch_shutdown(struct pci_dev *pdev)
+{
+ struct netc_switch *priv = pci_get_drvdata(pdev);
+
+ if (!priv)
+ return;
+
+ dsa_switch_shutdown(priv->ds);
+ pci_set_drvdata(pdev, NULL);
+}
+
+static const struct pci_device_id netc_switch_ids[] = {
+ { PCI_DEVICE(NETC_SWITCH_VENDOR_ID, NETC_SWITCH_DEVICE_ID) },
+ { }
+};
+MODULE_DEVICE_TABLE(pci, netc_switch_ids);
+
+static struct pci_driver netc_switch_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = netc_switch_ids,
+ .probe = netc_switch_probe,
+ .remove = netc_switch_remove,
+ .shutdown = netc_switch_shutdown,
+};
+module_pci_driver(netc_switch_driver);
+
+MODULE_DESCRIPTION("NXP NETC Switch driver");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/dsa/netc/netc_platform.c b/drivers/net/dsa/netc/netc_platform.c
new file mode 100644
index 000000000000..abd599ea9c8d
--- /dev/null
+++ b/drivers/net/dsa/netc/netc_platform.c
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
+/*
+ * NXP NETC switch driver
+ * Copyright 2025-2026 NXP
+ */
+
+#include "netc_switch.h"
+
+struct netc_switch_platform {
+ u16 revision;
+ const struct netc_switch_info *info;
+};
+
+static const struct netc_switch_info imx94_info = {
+ .num_ports = 4,
+};
+
+static const struct netc_switch_platform netc_platforms[] = {
+ { .revision = NETC_SWITCH_REV_4_3, .info = &imx94_info, },
+ { }
+};
+
+static const struct netc_switch_info *
+netc_switch_get_info(struct netc_switch *priv)
+{
+ int i;
+
+ /* Matching based on IP revision */
+ for (i = 0; i < ARRAY_SIZE(netc_platforms); i++) {
+ if (priv->revision == netc_platforms[i].revision)
+ return netc_platforms[i].info;
+ }
+
+ return NULL;
+}
+
+int netc_switch_platform_probe(struct netc_switch *priv)
+{
+ const struct netc_switch_info *info = netc_switch_get_info(priv);
+
+ if (!info) {
+ dev_err(priv->dev, "Cannot find switch platform info\n");
+ return -EINVAL;
+ }
+
+ priv->info = info;
+
+ return 0;
+}
diff --git a/drivers/net/dsa/netc/netc_switch.h b/drivers/net/dsa/netc/netc_switch.h
new file mode 100644
index 000000000000..dac19bfba02b
--- /dev/null
+++ b/drivers/net/dsa/netc/netc_switch.h
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
+/*
+ * Copyright 2025-2026 NXP
+ */
+
+#ifndef _NETC_SWITCH_H
+#define _NETC_SWITCH_H
+
+#include <linux/dsa/tag_netc.h>
+#include <linux/fsl/netc_global.h>
+#include <linux/fsl/ntmp.h>
+#include <linux/of_device.h>
+#include <linux/of_net.h>
+#include <linux/pci.h>
+
+#include "netc_switch_hw.h"
+
+#define NETC_REGS_BAR 0
+#define NETC_MSIX_TBL_BAR 2
+#define NETC_REGS_PORT_BASE 0x4000
+/* register block size per port */
+#define NETC_REGS_PORT_SIZE 0x4000
+#define PORT_IOBASE(p) (NETC_REGS_PORT_SIZE * (p))
+#define NETC_REGS_GLOBAL_BASE 0x70000
+
+#define NETC_SWITCH_REV_4_3 0x0403
+
+#define NETC_TC_NUM 8
+#define NETC_CBDR_NUM 2
+
+#define NETC_MAX_FRAME_LEN 9600
+
+struct netc_switch;
+
+struct netc_switch_info {
+ u32 num_ports;
+};
+
+struct netc_port_caps {
+ u32 half_duplex:1; /* indicates whether the port support half-duplex */
+ u32 pmac:1; /* indicates whether the port has preemption MAC */
+ u32 pseudo_link:1;
+};
+
+struct netc_port {
+ void __iomem *iobase;
+ struct netc_switch *switch_priv;
+ struct netc_port_caps caps;
+ struct dsa_port *dp;
+ struct clk *ref_clk; /* RGMII/RMII reference clock */
+ struct mii_bus *emdio;
+};
+
+struct netc_switch_regs {
+ void __iomem *base;
+ void __iomem *port;
+ void __iomem *global;
+};
+
+struct netc_switch {
+ struct pci_dev *pdev;
+ struct device *dev;
+ struct dsa_switch *ds;
+ u16 revision;
+
+ const struct netc_switch_info *info;
+ struct netc_switch_regs regs;
+ struct netc_port **ports;
+
+ struct ntmp_user ntmp;
+};
+
+/* Write/Read Switch base registers */
+#define netc_base_rd(r, o) netc_read((r)->base + (o))
+#define netc_base_wr(r, o, v) netc_write((r)->base + (o), v)
+
+/* Write/Read registers of Switch Port (including pseudo MAC port) */
+#define netc_port_rd(p, o) netc_read((p)->iobase + (o))
+#define netc_port_wr(p, o, v) netc_write((p)->iobase + (o), v)
+
+/* Write/Read Switch global registers */
+#define netc_glb_rd(r, o) netc_read((r)->global + (o))
+#define netc_glb_wr(r, o, v) netc_write((r)->global + (o), v)
+
+static inline bool is_netc_pseudo_port(struct netc_port *np)
+{
+ return np->caps.pseudo_link;
+}
+
+int netc_switch_platform_probe(struct netc_switch *priv);
+
+#endif
diff --git a/drivers/net/dsa/netc/netc_switch_hw.h b/drivers/net/dsa/netc/netc_switch_hw.h
new file mode 100644
index 000000000000..0419f7f9207e
--- /dev/null
+++ b/drivers/net/dsa/netc/netc_switch_hw.h
@@ -0,0 +1,133 @@
+/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
+/*
+ * Copyright 2025-2026 NXP
+ */
+
+#ifndef _NETC_SWITCH_HW_H
+#define _NETC_SWITCH_HW_H
+
+#include <linux/bitops.h>
+
+#define NETC_SWITCH_VENDOR_ID 0x1131
+#define NETC_SWITCH_DEVICE_ID 0xeef2
+
+/* Definition of Switch base registers */
+#define NETC_CBDRMR(a) (0x0800 + (a) * 0x30)
+#define NETC_CBDRBAR0(a) (0x0810 + (a) * 0x30)
+#define NETC_CBDRBAR1(a) (0x0814 + (a) * 0x30)
+#define NETC_CBDRPIR(a) (0x0818 + (a) * 0x30)
+#define NETC_CBDRCIR(a) (0x081c + (a) * 0x30)
+#define NETC_CBDRLENR(a) (0x0820 + (a) * 0x30)
+
+#define NETC_SWCR 0x1018
+#define SWCR_SWID GENMASK(2, 0)
+
+#define NETC_DOSL2CR 0x1220
+#define DOSL2CR_SAMEADDR BIT(0)
+#define DOSL2CR_MSAMCC BIT(1)
+
+#define NETC_DOSL3CR 0x1224
+#define DOSL3CR_SAMEADDR BIT(0)
+#define DOSL3CR_IPSAMCC BIT(1)
+
+#define NETC_VFHTDECR1 0x2014
+#define NETC_VFHTDECR2 0x2018
+#define VFHTDECR2_ET_PORT(a) BIT((a))
+#define VFHTDECR2_MLO GENMASK(26, 24)
+#define VFHTDECR2_MFO GENMASK(28, 27)
+
+/* Definition of Switch port registers */
+#define NETC_PCAPR 0x0000
+#define PCAPR_LINK_TYPE BIT(4)
+#define PCAPR_NUM_TC GENMASK(15, 12)
+#define PCAPR_NUM_Q GENMASK(19, 16)
+#define PCAPR_NUM_CG GENMASK(27, 24)
+#define PCAPR_TGS BIT(28)
+#define PCAPR_CBS BIT(29)
+
+#define NETC_PMCAPR 0x0004
+#define PMCAPR_HD BIT(8)
+#define PMCAPR_FP GENMASK(10, 9)
+#define FP_SUPPORT 2
+
+#define NETC_PCR 0x0010
+#define PCR_HDR_FMT BIT(0)
+#define PCR_NS_TAG_PORT BIT(3)
+#define PCR_L2DOSE BIT(4)
+#define PCR_L3DOSE BIT(5)
+#define PCR_TIMER_CS BIT(8)
+#define PCR_PSPEED GENMASK(29, 16)
+#define PSPEED_SET_VAL(s) FIELD_PREP(PCR_PSPEED, ((s) / 10 - 1))
+
+#define NETC_PQOSMR 0x0054
+#define PQOSMR_VS BIT(0)
+#define PQOSMR_VE BIT(1)
+#define PQOSMR_DDR GENMASK(3, 2)
+#define PQOSMR_DIPV GENMASK(6, 4)
+#define PQOSMR_VQMP GENMASK(19, 16)
+#define PQOSMR_QVMP GENMASK(23, 20)
+
+#define NETC_PTCTMSDUR(a) (0x208 + (a) * 0x20)
+#define PTCTMSDUR_MAXSDU GENMASK(15, 0)
+#define PTCTMSDUR_SDU_TYPE GENMASK(17, 16)
+#define SDU_TYPE_PPDU 0
+#define SDU_TYPE_MPDU 1
+#define SDU_TYPE_MSDU 2
+
+#define NETC_BPCR 0x500
+#define BPCR_DYN_LIMIT GENMASK(15, 0)
+#define BPCR_MLO GENMASK(22, 20)
+#define BPCR_UUCASTE BIT(24)
+#define BPCR_UMCASTE BIT(25)
+#define BPCR_MCASTE BIT(26)
+#define BPCR_BCASTE BIT(27)
+#define BPCR_STAMVD BIT(28)
+#define BPCR_SRCPRND BIT(29)
+
+/* MAC learning options, see BPCR[MLO], VFHTDECR2[MLO] and
+ * VLAN Filter Table CFGE_DATA[MLO]
+ */
+enum netc_mlo {
+ MLO_NOT_OVERRIDE = 0,
+ MLO_DISABLE,
+ MLO_HW,
+ MLO_SW_SEC,
+ MLO_SW_UNSEC,
+ MLO_DISABLE_SMAC,
+};
+
+/* MAC forwarding options, see VFHTDECR2[MFO] and VLAN
+ * Filter Table CFGE_DATA[MFO]
+ */
+enum netc_mfo {
+ MFO_NO_FDB_LOOKUP = 1,
+ MFO_NO_MATCH_FLOOD,
+ MFO_NO_MATCH_DISCARD,
+};
+
+#define NETC_BPDVR 0x510
+#define BPDVR_VID GENMASK(11, 0)
+#define BPDVR_DEI BIT(12)
+#define BPDVR_PCP GENMASK(15, 13)
+#define BPDVR_TPID BIT(16)
+#define BPDVR_RXTAGA GENMASK(23, 20)
+#define BPDVR_RXVAM BIT(24)
+#define BPDVR_TXTAGA GENMASK(26, 25)
+
+/* Definition of Switch ethernet MAC port registers */
+#define NETC_PMAC_OFFSET 0x400
+#define NETC_PM_CMD_CFG(a) (0x1008 + (a) * 0x400)
+#define PM_CMD_CFG_TX_EN BIT(0)
+#define PM_CMD_CFG_RX_EN BIT(1)
+
+#define NETC_PM_MAXFRM(a) (0x1014 + (a) * 0x400)
+#define PM_MAXFRAM GENMASK(15, 0)
+
+#define NETC_PEMDIOCR 0x1c00
+#define NETC_EMDIO_BASE NETC_PEMDIOCR
+
+/* Definition of global registers (read only) */
+#define NETC_IPBRR0 0x0bf8
+#define IPBRR0_IP_REV GENMASK(15, 0)
+
+#endif
--
2.34.1
^ permalink raw reply related
* [PATCH v5 net-next 11/15] net: dsa: netc: add phylink MAC operations
From: Wei Fang @ 2026-04-30 2:49 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260430024945.3413973-1-wei.fang@nxp.com>
Different versions of NETC switches have different numbers of ports and
MAC capabilities. Add .phylink_get_caps() to struct netc_switch_info,
allowing each NETC switch version to implement its own callback for
obtaining MAC capabilities.
Implement the phylink_mac_ops callbacks: .mac_config(), .mac_link_up(),
and .mac_link_down(). Note that flow-control configuration is not yet
supported in .mac_link_up(), but will be implemented in a subsequent
patch.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/dsa/netc/netc_main.c | 243 ++++++++++++++++++++++++++
drivers/net/dsa/netc/netc_platform.c | 38 ++++
drivers/net/dsa/netc/netc_switch.h | 4 +
drivers/net/dsa/netc/netc_switch_hw.h | 26 +++
4 files changed, 311 insertions(+)
diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c
index 90a2d8cfd3d2..edf50cb32cb6 100644
--- a/drivers/net/dsa/netc/netc_main.c
+++ b/drivers/net/dsa/netc/netc_main.c
@@ -44,6 +44,26 @@ static void netc_mac_port_wr(struct netc_port *np, u32 reg, u32 val)
netc_port_wr(np, reg + NETC_PMAC_OFFSET, val);
}
+static void netc_mac_port_rmw(struct netc_port *np, u32 reg,
+ u32 mask, u32 val)
+{
+ u32 old, new;
+
+ if (is_netc_pseudo_port(np))
+ return;
+
+ WARN_ON((mask | val) != mask);
+
+ old = netc_port_rd(np, reg);
+ new = (old & ~mask) | val;
+ if (new == old)
+ return;
+
+ netc_port_wr(np, reg, new);
+ if (np->caps.pmac)
+ netc_port_wr(np, reg + NETC_PMAC_OFFSET, new);
+}
+
static void netc_port_get_capability(struct netc_port *np)
{
u32 val;
@@ -522,10 +542,232 @@ static void netc_switch_get_ip_revision(struct netc_switch *priv)
priv->revision = FIELD_GET(IPBRR0_IP_REV, val);
}
+static void netc_phylink_get_caps(struct dsa_switch *ds, int port,
+ struct phylink_config *config)
+{
+ struct netc_switch *priv = ds->priv;
+
+ priv->info->phylink_get_caps(port, config);
+}
+
+static void netc_port_set_mac_mode(struct netc_port *np,
+ unsigned int mode,
+ phy_interface_t phy_mode)
+{
+ u32 mask = PM_IF_MODE_IFMODE | PM_IF_MODE_REVMII;
+ u32 val = 0;
+
+ switch (phy_mode) {
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ val |= IFMODE_RGMII;
+ break;
+ case PHY_INTERFACE_MODE_RMII:
+ val |= IFMODE_RMII;
+ break;
+ case PHY_INTERFACE_MODE_REVMII:
+ val |= PM_IF_MODE_REVMII;
+ fallthrough;
+ case PHY_INTERFACE_MODE_MII:
+ val |= IFMODE_MII;
+ break;
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_2500BASEX:
+ val |= IFMODE_SGMII;
+ break;
+ default:
+ break;
+ }
+
+ netc_mac_port_rmw(np, NETC_PM_IF_MODE(0), mask, val);
+}
+
+static void netc_mac_config(struct phylink_config *config, unsigned int mode,
+ const struct phylink_link_state *state)
+{
+ struct dsa_port *dp = dsa_phylink_to_port(config);
+
+ netc_port_set_mac_mode(NETC_PORT(dp->ds, dp->index), mode,
+ state->interface);
+}
+
+static void netc_port_set_speed(struct netc_port *np, int speed)
+{
+ netc_port_rmw(np, NETC_PCR, PCR_PSPEED, PSPEED_SET_VAL(speed));
+}
+
+static void netc_port_set_rgmii_mac(struct netc_port *np,
+ int speed, int duplex)
+{
+ u32 mask, val;
+
+ mask = PM_IF_MODE_SSP | PM_IF_MODE_HD | PM_IF_MODE_M10;
+
+ switch (speed) {
+ default:
+ case SPEED_1000:
+ val = FIELD_PREP(PM_IF_MODE_SSP, SSP_1G);
+ break;
+ case SPEED_100:
+ val = FIELD_PREP(PM_IF_MODE_SSP, SSP_100M);
+ break;
+ case SPEED_10:
+ val = FIELD_PREP(PM_IF_MODE_SSP, SSP_10M);
+ break;
+ }
+
+ if (duplex != DUPLEX_FULL)
+ val |= PM_IF_MODE_HD;
+
+ netc_mac_port_rmw(np, NETC_PM_IF_MODE(0), mask, val);
+}
+
+static void netc_port_set_rmii_mii_mac(struct netc_port *np,
+ int speed, int duplex)
+{
+ u32 mask, val = 0;
+
+ mask = PM_IF_MODE_SSP | PM_IF_MODE_HD | PM_IF_MODE_M10;
+
+ if (speed == SPEED_10)
+ val |= PM_IF_MODE_M10;
+
+ if (duplex != DUPLEX_FULL)
+ val |= PM_IF_MODE_HD;
+
+ netc_mac_port_rmw(np, NETC_PM_IF_MODE(0), mask, val);
+}
+
+static void netc_port_mac_rx_enable(struct netc_port *np)
+{
+ netc_port_rmw(np, NETC_POR, POR_RXDIS, 0);
+ netc_mac_port_rmw(np, NETC_PM_CMD_CFG(0), PM_CMD_CFG_RX_EN,
+ PM_CMD_CFG_RX_EN);
+}
+
+static void netc_port_wait_rx_empty(struct netc_port *np, int mac)
+{
+ u32 val;
+
+ /* PM_IEVENT_RX_EMPTY is a read-only bit, it is automatically set by
+ * hardware if RX FIFO is empty and no RX packet receive in process.
+ * And it is automatically cleared if RX FIFO is not empty or RX
+ * packet receive in process.
+ */
+ if (read_poll_timeout(netc_port_rd, val, val & PM_IEVENT_RX_EMPTY,
+ 100, 10000, false, np, NETC_PM_IEVENT(mac)))
+ dev_warn(np->switch_priv->dev,
+ "swp%d MAC%d: RX is not idle\n", np->dp->index, mac);
+}
+
+static void netc_port_mac_rx_graceful_stop(struct netc_port *np)
+{
+ u32 val;
+
+ if (is_netc_pseudo_port(np))
+ goto rx_disable;
+
+ if (np->caps.pmac) {
+ netc_port_rmw(np, NETC_PM_CMD_CFG(1), PM_CMD_CFG_RX_EN, 0);
+ netc_port_wait_rx_empty(np, 1);
+ }
+
+ netc_port_rmw(np, NETC_PM_CMD_CFG(0), PM_CMD_CFG_RX_EN, 0);
+ netc_port_wait_rx_empty(np, 0);
+
+ if (read_poll_timeout(netc_port_rd, val, !(val & PSR_RX_BUSY),
+ 100, 10000, false, np, NETC_PSR))
+ dev_warn(np->switch_priv->dev, "swp%d RX is busy\n",
+ np->dp->index);
+
+rx_disable:
+ netc_port_rmw(np, NETC_POR, POR_RXDIS, POR_RXDIS);
+}
+
+static void netc_port_mac_tx_enable(struct netc_port *np)
+{
+ netc_mac_port_rmw(np, NETC_PM_CMD_CFG(0), PM_CMD_CFG_TX_EN,
+ PM_CMD_CFG_TX_EN);
+ netc_port_rmw(np, NETC_POR, POR_TXDIS, 0);
+}
+
+static void netc_port_wait_tx_empty(struct netc_port *np, int mac)
+{
+ u32 val;
+
+ /* PM_IEVENT_TX_EMPTY is a read-only bit, it is automatically set by
+ * hardware if TX FIFO is empty. And it is automatically cleared if
+ * TX FIFO is not empty.
+ */
+ if (read_poll_timeout(netc_port_rd, val, val & PM_IEVENT_TX_EMPTY,
+ 100, 10000, false, np, NETC_PM_IEVENT(mac)))
+ dev_warn(np->switch_priv->dev,
+ "swp%d MAC%d: TX FIFO is not empty\n",
+ np->dp->index, mac);
+}
+
+static void netc_port_mac_tx_graceful_stop(struct netc_port *np)
+{
+ netc_port_rmw(np, NETC_POR, POR_TXDIS, POR_TXDIS);
+
+ if (is_netc_pseudo_port(np))
+ return;
+
+ netc_port_wait_tx_empty(np, 0);
+ if (np->caps.pmac)
+ netc_port_wait_tx_empty(np, 1);
+
+ netc_mac_port_rmw(np, NETC_PM_CMD_CFG(0), PM_CMD_CFG_TX_EN, 0);
+}
+
+static void netc_mac_link_up(struct phylink_config *config,
+ struct phy_device *phy, unsigned int mode,
+ phy_interface_t interface, int speed,
+ int duplex, bool tx_pause, bool rx_pause)
+{
+ struct dsa_port *dp = dsa_phylink_to_port(config);
+ struct netc_port *np;
+
+ np = NETC_PORT(dp->ds, dp->index);
+ netc_port_set_speed(np, speed);
+
+ if (phy_interface_mode_is_rgmii(interface))
+ netc_port_set_rgmii_mac(np, speed, duplex);
+
+ if (interface == PHY_INTERFACE_MODE_RMII ||
+ interface == PHY_INTERFACE_MODE_REVMII ||
+ interface == PHY_INTERFACE_MODE_MII)
+ netc_port_set_rmii_mii_mac(np, speed, duplex);
+
+ netc_port_mac_tx_enable(np);
+ netc_port_mac_rx_enable(np);
+}
+
+static void netc_mac_link_down(struct phylink_config *config,
+ unsigned int mode,
+ phy_interface_t interface)
+{
+ struct dsa_port *dp = dsa_phylink_to_port(config);
+ struct netc_port *np;
+
+ np = NETC_PORT(dp->ds, dp->index);
+ netc_port_mac_rx_graceful_stop(np);
+ netc_port_mac_tx_graceful_stop(np);
+}
+
+static const struct phylink_mac_ops netc_phylink_mac_ops = {
+ .mac_config = netc_mac_config,
+ .mac_link_up = netc_mac_link_up,
+ .mac_link_down = netc_mac_link_down,
+};
+
static const struct dsa_switch_ops netc_switch_ops = {
.get_tag_protocol = netc_get_tag_protocol,
.setup = netc_setup,
.teardown = netc_teardown,
+ .phylink_get_caps = netc_phylink_get_caps,
};
static int netc_switch_probe(struct pci_dev *pdev,
@@ -564,6 +806,7 @@ static int netc_switch_probe(struct pci_dev *pdev,
ds->num_ports = priv->info->num_ports;
ds->num_tx_queues = NETC_TC_NUM;
ds->ops = &netc_switch_ops;
+ ds->phylink_mac_ops = &netc_phylink_mac_ops;
ds->priv = priv;
priv->ds = ds;
diff --git a/drivers/net/dsa/netc/netc_platform.c b/drivers/net/dsa/netc/netc_platform.c
index abd599ea9c8d..bb4f92d238cb 100644
--- a/drivers/net/dsa/netc/netc_platform.c
+++ b/drivers/net/dsa/netc/netc_platform.c
@@ -11,8 +11,46 @@ struct netc_switch_platform {
const struct netc_switch_info *info;
};
+static void imx94_switch_phylink_get_caps(int port,
+ struct phylink_config *config)
+{
+ config->mac_capabilities = MAC_1000FD;
+
+ switch (port) {
+ case 0 ... 1:
+ __set_bit(PHY_INTERFACE_MODE_SGMII,
+ config->supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX,
+ config->supported_interfaces);
+ config->mac_capabilities |= MAC_2500FD;
+ fallthrough;
+ case 2:
+ config->mac_capabilities |= MAC_10 | MAC_100;
+ __set_bit(PHY_INTERFACE_MODE_MII,
+ config->supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_RMII,
+ config->supported_interfaces);
+ /* Port 0 and 1 do not support REVMII */
+ if (port == 2)
+ __set_bit(PHY_INTERFACE_MODE_REVMII,
+ config->supported_interfaces);
+
+ phy_interface_set_rgmii(config->supported_interfaces);
+ break;
+ case 3: /* CPU port */
+ __set_bit(PHY_INTERFACE_MODE_INTERNAL,
+ config->supported_interfaces);
+ config->mac_capabilities |= MAC_10FD | MAC_100FD |
+ MAC_2500FD;
+ break;
+ default:
+ break;
+ }
+}
+
static const struct netc_switch_info imx94_info = {
.num_ports = 4,
+ .phylink_get_caps = imx94_switch_phylink_get_caps,
};
static const struct netc_switch_platform netc_platforms[] = {
diff --git a/drivers/net/dsa/netc/netc_switch.h b/drivers/net/dsa/netc/netc_switch.h
index dac19bfba02b..eb65c36ecead 100644
--- a/drivers/net/dsa/netc/netc_switch.h
+++ b/drivers/net/dsa/netc/netc_switch.h
@@ -34,6 +34,7 @@ struct netc_switch;
struct netc_switch_info {
u32 num_ports;
+ void (*phylink_get_caps)(int port, struct phylink_config *config);
};
struct netc_port_caps {
@@ -70,6 +71,9 @@ struct netc_switch {
struct ntmp_user ntmp;
};
+#define NETC_PRIV(ds) ((struct netc_switch *)((ds)->priv))
+#define NETC_PORT(ds, port_id) (NETC_PRIV(ds)->ports[(port_id)])
+
/* Write/Read Switch base registers */
#define netc_base_rd(r, o) netc_read((r)->base + (o))
#define netc_base_wr(r, o, v) netc_write((r)->base + (o), v)
diff --git a/drivers/net/dsa/netc/netc_switch_hw.h b/drivers/net/dsa/netc/netc_switch_hw.h
index 0419f7f9207e..7d9afb493053 100644
--- a/drivers/net/dsa/netc/netc_switch_hw.h
+++ b/drivers/net/dsa/netc/netc_switch_hw.h
@@ -67,6 +67,14 @@
#define PQOSMR_VQMP GENMASK(19, 16)
#define PQOSMR_QVMP GENMASK(23, 20)
+#define NETC_POR 0x100
+#define POR_TXDIS BIT(0)
+#define POR_RXDIS BIT(1)
+
+#define NETC_PSR 0x104
+#define PSR_TX_BUSY BIT(0)
+#define PSR_RX_BUSY BIT(1)
+
#define NETC_PTCTMSDUR(a) (0x208 + (a) * 0x20)
#define PTCTMSDUR_MAXSDU GENMASK(15, 0)
#define PTCTMSDUR_SDU_TYPE GENMASK(17, 16)
@@ -123,6 +131,24 @@ enum netc_mfo {
#define NETC_PM_MAXFRM(a) (0x1014 + (a) * 0x400)
#define PM_MAXFRAM GENMASK(15, 0)
+#define NETC_PM_IEVENT(a) (0x1040 + (a) * 0x400)
+#define PM_IEVENT_TX_EMPTY BIT(5)
+#define PM_IEVENT_RX_EMPTY BIT(6)
+
+#define NETC_PM_IF_MODE(a) (0x1300 + (a) * 0x400)
+#define PM_IF_MODE_IFMODE GENMASK(2, 0)
+#define IFMODE_MII 1
+#define IFMODE_RMII 3
+#define IFMODE_RGMII 4
+#define IFMODE_SGMII 5
+#define PM_IF_MODE_REVMII BIT(3)
+#define PM_IF_MODE_M10 BIT(4)
+#define PM_IF_MODE_HD BIT(6)
+#define PM_IF_MODE_SSP GENMASK(14, 13)
+#define SSP_100M 0
+#define SSP_10M 1
+#define SSP_1G 2
+
#define NETC_PEMDIOCR 0x1c00
#define NETC_EMDIO_BASE NETC_PEMDIOCR
--
2.34.1
^ permalink raw reply related
* [PATCH v5 net-next 12/15] net: dsa: netc: add FDB, STP, MTU, port setup and host flooding support
From: Wei Fang @ 2026-04-30 2:49 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260430024945.3413973-1-wei.fang@nxp.com>
Expand the NETC switch driver with several foundational features:
- FDB and MDB management
- STP state handling
- MTU configuration
- Port setup/teardown
- Host flooding support
At this stage, the driver operates only in standalone port mode. Each
port uses VLAN 0 as its PVID, meaning ingress frames are internally
assigned VID 0 regardless of whether they arrive tagged or untagged.
Note that this does not inject a VLAN 0 header into the frame, the VID
is used purely for subsequent VLAN processing within the switch.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/dsa/netc/netc_main.c | 573 ++++++++++++++++++++++++++
drivers/net/dsa/netc/netc_switch.h | 36 ++
drivers/net/dsa/netc/netc_switch_hw.h | 14 +
3 files changed, 623 insertions(+)
diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c
index edf50cb32cb6..7f1ab1fbb6fb 100644
--- a/drivers/net/dsa/netc/netc_main.c
+++ b/drivers/net/dsa/netc/netc_main.c
@@ -7,11 +7,36 @@
#include <linux/clk.h>
#include <linux/etherdevice.h>
#include <linux/fsl/enetc_mdio.h>
+#include <linux/if_bridge.h>
#include <linux/if_vlan.h>
#include <linux/of_mdio.h>
#include "netc_switch.h"
+static struct netc_fdb_entry *
+netc_lookup_fdb_entry(struct netc_switch *priv,
+ const unsigned char *addr,
+ u16 vid)
+{
+ struct netc_fdb_entry *entry;
+
+ hlist_for_each_entry(entry, &priv->fdb_list, node)
+ if (ether_addr_equal(entry->keye.mac_addr, addr) &&
+ le16_to_cpu(entry->keye.fid) == vid)
+ return entry;
+
+ return NULL;
+}
+
+static void netc_destroy_fdb_list(struct netc_switch *priv)
+{
+ struct netc_fdb_entry *entry;
+ struct hlist_node *tmp;
+
+ hlist_for_each_entry_safe(entry, tmp, &priv->fdb_list, node)
+ netc_del_fdb_entry(entry);
+}
+
static enum dsa_tag_protocol
netc_get_tag_protocol(struct dsa_switch *ds, int port,
enum dsa_tag_protocol mprot)
@@ -176,6 +201,15 @@ static int netc_init_switch_id(struct netc_switch *priv)
return 0;
}
+static void netc_get_switch_capabilities(struct netc_switch *priv)
+{
+ struct netc_switch_regs *regs = &priv->regs;
+ u32 val;
+
+ val = netc_base_rd(regs, NETC_FDBHTCAPR);
+ priv->num_fdb_gmac = FIELD_GET(FDBHTCAPR_NUM_GMAC, val);
+}
+
static int netc_init_all_ports(struct netc_switch *priv)
{
struct device *dev = priv->dev;
@@ -383,6 +417,206 @@ static void netc_port_default_config(struct netc_port *np)
netc_port_set_all_tc_msdu(np);
}
+static u32 netc_available_port_bitmap(struct netc_switch *priv)
+{
+ struct dsa_port *dp;
+ u32 bitmap = 0;
+
+ dsa_switch_for_each_available_port(dp, priv->ds)
+ bitmap |= BIT(dp->index);
+
+ return bitmap;
+}
+
+static int netc_add_standalone_vlan_entry(struct netc_switch *priv)
+{
+ u32 bitmap_stg = VFT_STG_ID(0) | netc_available_port_bitmap(priv);
+ struct vft_cfge_data *cfge;
+ u16 cfg;
+ int err;
+
+ cfge = kzalloc_obj(*cfge);
+ if (!cfge)
+ return -ENOMEM;
+
+ cfge->bitmap_stg = cpu_to_le32(bitmap_stg);
+ cfge->et_eid = cpu_to_le32(NTMP_NULL_ENTRY_ID);
+ cfge->fid = cpu_to_le16(NETC_STANDALONE_PVID);
+
+ /* For standalone ports, MAC learning needs to be disabled, so frames
+ * from other user ports will not be forwarded to the standalone ports,
+ * because there are no FDB entries on the standalone ports. Also, the
+ * frames received by the standalone ports cannot be flooded to other
+ * ports, so MAC forwarding option needs to be set to
+ * MFO_NO_MATCH_DISCARD, so the frames will discarded rather than
+ * flooding to other ports.
+ */
+ cfg = FIELD_PREP(VFT_MLO, MLO_DISABLE) |
+ FIELD_PREP(VFT_MFO, MFO_NO_MATCH_DISCARD);
+ cfge->cfg = cpu_to_le16(cfg);
+
+ err = ntmp_vft_add_entry(&priv->ntmp, NETC_STANDALONE_PVID, cfge);
+ if (err)
+ dev_err(priv->dev,
+ "Failed to add standalone VLAN entry\n");
+
+ kfree(cfge);
+
+ return err;
+}
+
+static int netc_port_add_fdb_entry(struct netc_port *np,
+ const unsigned char *addr, u16 vid)
+{
+ struct netc_switch *priv = np->switch_priv;
+ struct netc_fdb_entry *entry;
+ struct fdbt_keye_data *keye;
+ struct fdbt_cfge_data *cfge;
+ int port = np->dp->index;
+ u32 cfg = 0;
+ int err;
+
+ entry = kzalloc_obj(*entry);
+ if (!entry)
+ return -ENOMEM;
+
+ keye = &entry->keye;
+ cfge = &entry->cfge;
+ ether_addr_copy(keye->mac_addr, addr);
+ keye->fid = cpu_to_le16(vid);
+
+ cfge->port_bitmap = cpu_to_le32(BIT(port));
+ cfge->cfg = cpu_to_le32(cfg);
+ cfge->et_eid = cpu_to_le32(NTMP_NULL_ENTRY_ID);
+
+ err = ntmp_fdbt_add_entry(&priv->ntmp, &entry->entry_id, keye, cfge);
+ if (err) {
+ kfree(entry);
+
+ return err;
+ }
+
+ netc_add_fdb_entry(priv, entry);
+
+ return 0;
+}
+
+static int netc_port_set_fdb_entry(struct netc_port *np,
+ const unsigned char *addr, u16 vid)
+{
+ struct netc_switch *priv = np->switch_priv;
+ struct netc_fdb_entry *entry;
+ struct fdbt_cfge_data *cfge;
+ int port = np->dp->index;
+ int err = 0;
+
+ mutex_lock(&priv->fdbt_lock);
+
+ entry = netc_lookup_fdb_entry(priv, addr, vid);
+ if (!entry) {
+ err = netc_port_add_fdb_entry(np, addr, vid);
+ if (err)
+ dev_err(priv->dev,
+ "Failed to add FDB entry on port %d\n",
+ port);
+
+ goto unlock_fdbt;
+ }
+
+ cfge = &entry->cfge;
+ /* If the entry already exists on the port, return 0 directly */
+ if (unlikely(cfge->port_bitmap & cpu_to_le32(BIT(port))))
+ goto unlock_fdbt;
+
+ /* If the entry already exists, but not on this port, we need to
+ * update the port bitmap. In general, it should only be valid
+ * for multicast or broadcast address.
+ */
+ cfge->port_bitmap |= cpu_to_le32(BIT(port));
+ err = ntmp_fdbt_update_entry(&priv->ntmp, entry->entry_id, cfge);
+ if (err) {
+ cfge->port_bitmap &= cpu_to_le32(~BIT(port));
+ dev_err(priv->dev, "Failed to set FDB entry on port %d\n",
+ port);
+ }
+
+unlock_fdbt:
+ mutex_unlock(&priv->fdbt_lock);
+
+ return err;
+}
+
+static int netc_port_del_fdb_entry(struct netc_port *np,
+ const unsigned char *addr, u16 vid)
+{
+ struct netc_switch *priv = np->switch_priv;
+ struct ntmp_user *ntmp = &priv->ntmp;
+ struct netc_fdb_entry *entry;
+ struct fdbt_cfge_data *cfge;
+ int port = np->dp->index;
+ int err = 0;
+
+ mutex_lock(&priv->fdbt_lock);
+
+ entry = netc_lookup_fdb_entry(priv, addr, vid);
+ if (unlikely(!entry))
+ goto unlock_fdbt;
+
+ cfge = &entry->cfge;
+ if (unlikely(!(cfge->port_bitmap & cpu_to_le32(BIT(port)))))
+ goto unlock_fdbt;
+
+ if (cfge->port_bitmap != cpu_to_le32(BIT(port))) {
+ /* If the entry also exists on other ports, we need to
+ * update the entry in the FDB table.
+ */
+ cfge->port_bitmap &= cpu_to_le32(~BIT(port));
+ err = ntmp_fdbt_update_entry(ntmp, entry->entry_id, cfge);
+ if (err) {
+ cfge->port_bitmap |= cpu_to_le32(BIT(port));
+ goto unlock_fdbt;
+ }
+ } else {
+ /* If the entry only exists on this port, just delete
+ * it from the FDB table.
+ */
+ err = ntmp_fdbt_delete_entry(ntmp, entry->entry_id);
+ if (err)
+ goto unlock_fdbt;
+
+ netc_del_fdb_entry(entry);
+ }
+
+unlock_fdbt:
+ mutex_unlock(&priv->fdbt_lock);
+
+ return err;
+}
+
+static int netc_add_standalone_fdb_bcast_entry(struct netc_switch *priv)
+{
+ const u8 bcast[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+ struct dsa_port *dp, *cpu_dp = NULL;
+
+ dsa_switch_for_each_cpu_port(dp, priv->ds) {
+ cpu_dp = dp;
+ break;
+ }
+
+ if (!cpu_dp)
+ return -ENODEV;
+
+ /* If the user port acts as a standalone port, then its PVID is 0,
+ * MLO is set to "disable MAC learning" and MFO is set to "discard
+ * frames if no matching entry found in FDB table". Therefore, we
+ * need to add a broadcast FDB entry on the CPU port so that the
+ * broadcast frames received on the user port can be forwarded to
+ * the CPU port.
+ */
+ return netc_port_set_fdb_entry(NETC_PORT(priv->ds, cpu_dp->index),
+ bcast, NETC_STANDALONE_PVID);
+}
+
static int netc_setup(struct dsa_switch *ds)
{
struct netc_switch *priv = ds->priv;
@@ -393,6 +627,8 @@ static int netc_setup(struct dsa_switch *ds)
if (err)
return err;
+ netc_get_switch_capabilities(priv);
+
err = netc_init_all_ports(priv);
if (err)
return err;
@@ -401,19 +637,61 @@ static int netc_setup(struct dsa_switch *ds)
if (err)
return err;
+ INIT_HLIST_HEAD(&priv->fdb_list);
+ mutex_init(&priv->fdbt_lock);
+
netc_switch_fixed_config(priv);
/* default setting for ports */
dsa_switch_for_each_available_port(dp, ds)
netc_port_default_config(priv->ports[dp->index]);
+ err = netc_add_standalone_vlan_entry(priv);
+ if (err)
+ goto free_lock_and_ntmp_user;
+
+ err = netc_add_standalone_fdb_bcast_entry(priv);
+ if (err)
+ goto free_lock_and_ntmp_user;
+
return 0;
+
+free_lock_and_ntmp_user:
+ mutex_destroy(&priv->fdbt_lock);
+ netc_free_ntmp_user(priv);
+
+ return err;
+}
+
+static void netc_destroy_all_lists(struct netc_switch *priv)
+{
+ netc_destroy_fdb_list(priv);
+ mutex_destroy(&priv->fdbt_lock);
+}
+
+static void netc_free_host_flood_rules(struct netc_switch *priv)
+{
+ struct dsa_port *dp;
+
+ dsa_switch_for_each_user_port(dp, priv->ds) {
+ struct netc_port *np = priv->ports[dp->index];
+
+ /* No need to clear the hardware IPFT entry. Because PCIe
+ * FLR will be performed when the switch is re-registered,
+ * it will reset hardware state. So only need to free the
+ * memory to avoid memory leak.
+ */
+ kfree(np->host_flood);
+ np->host_flood = NULL;
+ }
}
static void netc_teardown(struct dsa_switch *ds)
{
struct netc_switch *priv = ds->priv;
+ netc_destroy_all_lists(priv);
+ netc_free_host_flood_rules(priv);
netc_free_ntmp_user(priv);
}
@@ -542,6 +820,289 @@ static void netc_switch_get_ip_revision(struct netc_switch *priv)
priv->revision = FIELD_GET(IPBRR0_IP_REV, val);
}
+static int netc_port_enable(struct dsa_switch *ds, int port,
+ struct phy_device *phy)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ int err;
+
+ if (np->enable)
+ return 0;
+
+ err = clk_prepare_enable(np->ref_clk);
+ if (err) {
+ dev_err(ds->dev,
+ "Failed to enable enet_ref_clk of port %d\n", port);
+ return err;
+ }
+
+ np->enable = true;
+
+ return 0;
+}
+
+static void netc_port_disable(struct dsa_switch *ds, int port)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+
+ /* When .port_disable() is called, .port_enable() may not have been
+ * called. In this case, both the prepare_count and enable_count of
+ * clock are 0. Calling clk_disable_unprepare() at this time will
+ * cause warnings.
+ */
+ if (!np->enable)
+ return;
+
+ clk_disable_unprepare(np->ref_clk);
+ np->enable = false;
+}
+
+static void netc_port_stp_state_set(struct dsa_switch *ds,
+ int port, u8 state)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ u32 val;
+
+ switch (state) {
+ case BR_STATE_DISABLED:
+ case BR_STATE_LISTENING:
+ case BR_STATE_BLOCKING:
+ val = NETC_STG_STATE_DISABLED;
+ break;
+ case BR_STATE_LEARNING:
+ val = NETC_STG_STATE_LEARNING;
+ break;
+ case BR_STATE_FORWARDING:
+ val = NETC_STG_STATE_FORWARDING;
+ break;
+ default:
+ return;
+ }
+
+ netc_port_wr(np, NETC_BPSTGSR, val);
+}
+
+static int netc_port_change_mtu(struct dsa_switch *ds,
+ int port, int mtu)
+{
+ u32 max_frame_size = mtu + VLAN_ETH_HLEN + ETH_FCS_LEN;
+ struct netc_port *np = NETC_PORT(ds, port);
+
+ /* dsa_user_change_mtu() does not add the switch tag overhead
+ * for the CPU port, so we need to add this overhead for the
+ * CPU port here.
+ */
+ if (dsa_is_cpu_port(ds, port)) {
+ max_frame_size += NETC_TAG_MAX_LEN;
+ if (max_frame_size > NETC_MAX_FRAME_LEN)
+ max_frame_size = NETC_MAX_FRAME_LEN;
+ }
+
+ netc_port_set_max_frame_size(np, max_frame_size);
+
+ return 0;
+}
+
+static int netc_port_max_mtu(struct dsa_switch *ds, int port)
+{
+ return NETC_MAX_FRAME_LEN - VLAN_ETH_HLEN - ETH_FCS_LEN;
+}
+
+static int netc_port_fdb_add(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid,
+ struct dsa_db db)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+
+ /* Currently, only support standalone port mode, so only
+ * NETC_STANDALONE_PVID (= 0) is supported here.
+ */
+ if (vid != NETC_STANDALONE_PVID)
+ return -EOPNOTSUPP;
+
+ return netc_port_set_fdb_entry(np, addr, vid);
+}
+
+static int netc_port_fdb_del(struct dsa_switch *ds, int port,
+ const unsigned char *addr, u16 vid,
+ struct dsa_db db)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+
+ if (vid != NETC_STANDALONE_PVID)
+ return -EOPNOTSUPP;
+
+ return netc_port_del_fdb_entry(np, addr, vid);
+}
+
+static int netc_port_fdb_dump(struct dsa_switch *ds, int port,
+ dsa_fdb_dump_cb_t *cb, void *data)
+{
+ struct netc_switch *priv = ds->priv;
+ u32 resume_eid = NTMP_NULL_ENTRY_ID;
+ struct fdbt_entry_data *entry;
+ struct fdbt_keye_data *keye;
+ struct fdbt_cfge_data *cfge;
+ u32 cfg, cnt = 0;
+ bool is_static;
+ int err;
+ u16 vid;
+
+ entry = kmalloc_obj(*entry);
+ if (!entry)
+ return -ENOMEM;
+
+ keye = &entry->keye;
+ cfge = &entry->cfge;
+ mutex_lock(&priv->fdbt_lock);
+
+ do {
+ memset(entry, 0, sizeof(*entry));
+ err = ntmp_fdbt_search_port_entry(&priv->ntmp, port,
+ &resume_eid, entry);
+ if (err || entry->entry_id == NTMP_NULL_ENTRY_ID)
+ break;
+
+ cfg = le32_to_cpu(cfge->cfg);
+ is_static = (cfg & FDBT_DYNAMIC) ? false : true;
+ vid = le16_to_cpu(keye->fid);
+
+ err = cb(keye->mac_addr, vid, is_static, data);
+ if (err)
+ break;
+
+ /* To prevent hardware malfunctions from causing an
+ * infinite loop.
+ */
+ if (++cnt >= priv->num_fdb_gmac)
+ break;
+ } while (resume_eid != NTMP_NULL_ENTRY_ID);
+
+ mutex_unlock(&priv->fdbt_lock);
+ kfree(entry);
+
+ return err;
+}
+
+static int netc_port_mdb_add(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct dsa_db db)
+{
+ return netc_port_fdb_add(ds, port, mdb->addr, mdb->vid, db);
+}
+
+static int netc_port_mdb_del(struct dsa_switch *ds, int port,
+ const struct switchdev_obj_port_mdb *mdb,
+ struct dsa_db db)
+{
+ return netc_port_fdb_del(ds, port, mdb->addr, mdb->vid, db);
+}
+
+static int netc_port_add_host_flood_rule(struct netc_port *np,
+ bool uc, bool mc)
+{
+ const u8 dmac_mask[ETH_ALEN] = {0x1, 0, 0, 0, 0, 0};
+ struct netc_switch *priv = np->switch_priv;
+ struct ipft_entry_data *host_flood;
+ struct ipft_keye_data *keye;
+ struct ipft_cfge_data *cfge;
+ u16 src_port;
+ u32 cfg;
+ int err;
+
+ if (!uc && !mc) {
+ /* Disable ingress port filter table lookup */
+ netc_port_wr(np, NETC_PIPFCR, 0);
+ np->uc = false;
+ np->mc = false;
+
+ return 0;
+ }
+
+ host_flood = kzalloc_obj(*host_flood);
+ if (!host_flood)
+ return -ENOMEM;
+
+ keye = &host_flood->keye;
+ cfge = &host_flood->cfge;
+
+ src_port = FIELD_PREP(IPFT_SRC_PORT, np->dp->index);
+ src_port |= IPFT_SRC_PORT_MASK;
+ keye->src_port = cpu_to_le16(src_port);
+
+ /* If either only unicast or only multicast need to be flooded
+ * to the host, we always set the mask that tests the first MAC
+ * DA octet. The value should be 0 for the first bit (if unicast
+ * has to be flooded) or 1 (if multicast). If both unicast and
+ * multicast have to be flooded, we leave the key mask empty, so
+ * it matches everything.
+ */
+ if (uc && !mc)
+ ether_addr_copy(keye->dmac_mask, dmac_mask);
+
+ if (!uc && mc) {
+ ether_addr_copy(keye->dmac, dmac_mask);
+ ether_addr_copy(keye->dmac_mask, dmac_mask);
+ }
+
+ cfg = FIELD_PREP(IPFT_FLTFA, IPFT_FLTFA_REDIRECT);
+ cfg |= FIELD_PREP(IPFT_HR, NETC_HR_HOST_FLOOD);
+ cfge->cfg = cpu_to_le32(cfg);
+
+ err = ntmp_ipft_add_entry(&priv->ntmp, host_flood);
+ if (err) {
+ kfree(host_flood);
+ return err;
+ }
+
+ np->uc = uc;
+ np->mc = mc;
+ np->host_flood = host_flood;
+ /* Enable ingress port filter table lookup */
+ netc_port_wr(np, NETC_PIPFCR, PIPFCR_EN);
+
+ return 0;
+}
+
+static void netc_port_remove_host_flood(struct netc_port *np,
+ struct ipft_entry_data *host_flood)
+{
+ struct netc_switch *priv = np->switch_priv;
+
+ if (!host_flood)
+ return;
+
+ ntmp_ipft_delete_entry(&priv->ntmp, host_flood->entry_id);
+ kfree(host_flood);
+}
+
+static void netc_port_set_host_flood(struct dsa_switch *ds, int port,
+ bool uc, bool mc)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ struct ipft_entry_data *old_host_flood;
+
+ if (np->uc == uc && np->mc == mc)
+ return;
+
+ /* IPFT does not support in-place updates to the KEYE element,
+ * we need to add a new entry and then delete the old one. So
+ * save the old entry first.
+ */
+ old_host_flood = np->host_flood;
+ np->host_flood = NULL;
+
+ if (netc_port_add_host_flood_rule(np, uc, mc)) {
+ np->host_flood = old_host_flood;
+ dev_err(ds->dev, "Failed to add host flood rule on port %d\n",
+ port);
+ return;
+ }
+
+ /* Remove the old host flood entry */
+ netc_port_remove_host_flood(np, old_host_flood);
+}
+
static void netc_phylink_get_caps(struct dsa_switch *ds, int port,
struct phylink_config *config)
{
@@ -768,6 +1329,17 @@ static const struct dsa_switch_ops netc_switch_ops = {
.setup = netc_setup,
.teardown = netc_teardown,
.phylink_get_caps = netc_phylink_get_caps,
+ .port_enable = netc_port_enable,
+ .port_disable = netc_port_disable,
+ .port_stp_state_set = netc_port_stp_state_set,
+ .port_change_mtu = netc_port_change_mtu,
+ .port_max_mtu = netc_port_max_mtu,
+ .port_fdb_add = netc_port_fdb_add,
+ .port_fdb_del = netc_port_fdb_del,
+ .port_fdb_dump = netc_port_fdb_dump,
+ .port_mdb_add = netc_port_mdb_add,
+ .port_mdb_del = netc_port_mdb_del,
+ .port_set_host_flood = netc_port_set_host_flood,
};
static int netc_switch_probe(struct pci_dev *pdev,
@@ -807,6 +1379,7 @@ static int netc_switch_probe(struct pci_dev *pdev,
ds->num_tx_queues = NETC_TC_NUM;
ds->ops = &netc_switch_ops;
ds->phylink_mac_ops = &netc_phylink_mac_ops;
+ ds->fdb_isolation = true;
ds->priv = priv;
priv->ds = ds;
diff --git a/drivers/net/dsa/netc/netc_switch.h b/drivers/net/dsa/netc/netc_switch.h
index eb65c36ecead..3efe37fca390 100644
--- a/drivers/net/dsa/netc/netc_switch.h
+++ b/drivers/net/dsa/netc/netc_switch.h
@@ -30,6 +30,8 @@
#define NETC_MAX_FRAME_LEN 9600
+#define NETC_STANDALONE_PVID 0
+
struct netc_switch;
struct netc_switch_info {
@@ -43,6 +45,11 @@ struct netc_port_caps {
u32 pseudo_link:1;
};
+enum netc_host_reason {
+ /* Software defined host reasons */
+ NETC_HR_HOST_FLOOD = 8,
+};
+
struct netc_port {
void __iomem *iobase;
struct netc_switch *switch_priv;
@@ -50,6 +57,11 @@ struct netc_port {
struct dsa_port *dp;
struct clk *ref_clk; /* RGMII/RMII reference clock */
struct mii_bus *emdio;
+
+ u16 enable:1;
+ u16 uc:1;
+ u16 mc:1;
+ struct ipft_entry_data *host_flood;
};
struct netc_switch_regs {
@@ -58,6 +70,13 @@ struct netc_switch_regs {
void __iomem *global;
};
+struct netc_fdb_entry {
+ u32 entry_id;
+ struct fdbt_cfge_data cfge;
+ struct fdbt_keye_data keye;
+ struct hlist_node node;
+};
+
struct netc_switch {
struct pci_dev *pdev;
struct device *dev;
@@ -69,6 +88,11 @@ struct netc_switch {
struct netc_port **ports;
struct ntmp_user ntmp;
+ struct hlist_head fdb_list;
+ struct mutex fdbt_lock; /* FDB table lock */
+
+ /* Switch hardware capabilities */
+ u32 num_fdb_gmac;
};
#define NETC_PRIV(ds) ((struct netc_switch *)((ds)->priv))
@@ -91,6 +115,18 @@ static inline bool is_netc_pseudo_port(struct netc_port *np)
return np->caps.pseudo_link;
}
+static inline void netc_add_fdb_entry(struct netc_switch *priv,
+ struct netc_fdb_entry *entry)
+{
+ hlist_add_head(&entry->node, &priv->fdb_list);
+}
+
+static inline void netc_del_fdb_entry(struct netc_fdb_entry *entry)
+{
+ hlist_del(&entry->node);
+ kfree(entry);
+}
+
int netc_switch_platform_probe(struct netc_switch *priv);
#endif
diff --git a/drivers/net/dsa/netc/netc_switch_hw.h b/drivers/net/dsa/netc/netc_switch_hw.h
index 7d9afb493053..b04e9866d72a 100644
--- a/drivers/net/dsa/netc/netc_switch_hw.h
+++ b/drivers/net/dsa/netc/netc_switch_hw.h
@@ -36,6 +36,9 @@
#define VFHTDECR2_MLO GENMASK(26, 24)
#define VFHTDECR2_MFO GENMASK(28, 27)
+#define NETC_FDBHTCAPR 0x2020
+#define FDBHTCAPR_NUM_GMAC GENMASK(8, 0)
+
/* Definition of Switch port registers */
#define NETC_PCAPR 0x0000
#define PCAPR_LINK_TYPE BIT(4)
@@ -67,6 +70,9 @@
#define PQOSMR_VQMP GENMASK(19, 16)
#define PQOSMR_QVMP GENMASK(23, 20)
+#define NETC_PIPFCR 0x0084
+#define PIPFCR_EN BIT(0)
+
#define NETC_POR 0x100
#define POR_TXDIS BIT(0)
#define POR_RXDIS BIT(1)
@@ -122,6 +128,14 @@ enum netc_mfo {
#define BPDVR_RXVAM BIT(24)
#define BPDVR_TXTAGA GENMASK(26, 25)
+#define NETC_BPSTGSR 0x520
+
+enum netc_stg_stage {
+ NETC_STG_STATE_DISABLED = 0,
+ NETC_STG_STATE_LEARNING,
+ NETC_STG_STATE_FORWARDING,
+};
+
/* Definition of Switch ethernet MAC port registers */
#define NETC_PMAC_OFFSET 0x400
#define NETC_PM_CMD_CFG(a) (0x1008 + (a) * 0x400)
--
2.34.1
^ permalink raw reply related
* [PATCH v5 net-next 13/15] net: dsa: netc: initialize buffer pool table and implement flow-control
From: Wei Fang @ 2026-04-30 2:49 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260430024945.3413973-1-wei.fang@nxp.com>
The buffer pool is a quantity of memory available for buffering a group
of flows (e.g. frames having the same priority, frames received from the
same port), while waiting to be transmitted on a port. The buffer pool
tracks internal memory consumption with upper bound limits and optionally
a non-shared portion when associated with a shared buffer pool. Currently
the shared buffer pool is not supported, it will be added in the future.
For i.MX94, the switch has 4 ports and 8 buffer pools, so each port is
allocated two buffer pools. For frames with priorities of 0 to 3, they
will be mapped to the first buffer pool; For frames with priorities of
4 to 7, they will be mapped to the second buffer pool. Each buffer pool
has a flow control on threshold and a flow control off threshold. By
setting these threshold, add the flow control support to each port.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/dsa/netc/netc_main.c | 128 ++++++++++++++++++++++++++
drivers/net/dsa/netc/netc_platform.c | 2 +-
drivers/net/dsa/netc/netc_switch.h | 19 ++++
drivers/net/dsa/netc/netc_switch_hw.h | 10 ++
4 files changed, 158 insertions(+), 1 deletion(-)
diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c
index 7f1ab1fbb6fb..fd02a943824d 100644
--- a/drivers/net/dsa/netc/netc_main.c
+++ b/drivers/net/dsa/netc/netc_main.c
@@ -208,6 +208,9 @@ static void netc_get_switch_capabilities(struct netc_switch *priv)
val = netc_base_rd(regs, NETC_FDBHTCAPR);
priv->num_fdb_gmac = FIELD_GET(FDBHTCAPR_NUM_GMAC, val);
+
+ val = netc_base_rd(regs, NETC_BPCAPR);
+ priv->num_bp = FIELD_GET(BPCAPR_NUM_BP, val);
}
static int netc_init_all_ports(struct netc_switch *priv)
@@ -398,6 +401,15 @@ static void netc_port_fixed_config(struct netc_port *np)
/* Enable L2 and L3 DOS */
netc_port_rmw(np, NETC_PCR, PCR_L2DOSE | PCR_L3DOSE,
PCR_L2DOSE | PCR_L3DOSE);
+
+ /* Set the quanta value of TX PAUSE frame */
+ netc_mac_port_wr(np, NETC_PM_PAUSE_QUANTA(0), NETC_PAUSE_QUANTA);
+
+ /* When a quanta timer counts down and reaches this value,
+ * the MAC sends a refresh PAUSE frame with the programmed
+ * full quanta value if a pause condition still exists.
+ */
+ netc_mac_port_wr(np, NETC_PM_PAUSE_THRESH(0), NETC_PAUSE_THRESH);
}
static void netc_port_default_config(struct netc_port *np)
@@ -617,6 +629,77 @@ static int netc_add_standalone_fdb_bcast_entry(struct netc_switch *priv)
bcast, NETC_STANDALONE_PVID);
}
+static void netc_port_set_pbpmcr(struct netc_port *np, u64 mapping)
+{
+ u32 pbpmcr0 = lower_32_bits(mapping);
+ u32 pbpmcr1 = upper_32_bits(mapping);
+
+ netc_port_wr(np, NETC_PBPMCR0, pbpmcr0);
+ netc_port_wr(np, NETC_PBPMCR1, pbpmcr1);
+}
+
+static void netc_ipv_to_buffer_pool_mapping(struct netc_switch *priv)
+{
+ int bp_per_port = priv->num_bp / priv->info->num_ports;
+ int q = NETC_IPV_NUM / bp_per_port;
+ int r = NETC_IPV_NUM % bp_per_port;
+ int num = q + r;
+
+ /* IPV-to–buffer-pool mapping per port:
+ * Each port is allocated 'bp_per_port' buffer pools and supports 8
+ * IPVs, where a higher IPV indicates a higher frame priority. Each
+ * IPV can be mapped to only one buffer pool.
+ *
+ * The mapping rule is as follows:
+ * - The first 'num' IPVs share the port's first buffer pool (index
+ * 'base_id').
+ * - After that, every 'q' IPVs share one buffer pool, with pool
+ * indices increasing sequentially.
+ */
+ for (int i = 0; i < priv->info->num_ports; i++) {
+ u32 base_id = i * bp_per_port;
+ u32 bp_id = base_id;
+ u64 mapping = 0;
+
+ for (int ipv = 0; ipv < NETC_IPV_NUM; ipv++) {
+ /* Update the buffer pool index */
+ if (ipv >= num)
+ bp_id = base_id + ((ipv - num) / q) + 1;
+
+ mapping |= (u64)bp_id << (ipv * 8);
+ }
+
+ netc_port_set_pbpmcr(priv->ports[i], mapping);
+ }
+}
+
+static int netc_switch_bpt_default_config(struct netc_switch *priv)
+{
+ if (priv->num_bp < priv->info->num_ports)
+ return -EINVAL;
+
+ priv->bpt_list = devm_kcalloc(priv->dev, priv->num_bp,
+ sizeof(struct bpt_cfge_data),
+ GFP_KERNEL);
+ if (!priv->bpt_list)
+ return -ENOMEM;
+
+ /* Initialize the maximum threshold of each buffer pool entry */
+ for (int i = 0; i < priv->num_bp; i++) {
+ struct bpt_cfge_data *cfge = &priv->bpt_list[i];
+ int err;
+
+ cfge->max_thresh = cpu_to_le16(NETC_BP_THRESH);
+ err = ntmp_bpt_update_entry(&priv->ntmp, i, cfge);
+ if (err)
+ return err;
+ }
+
+ netc_ipv_to_buffer_pool_mapping(priv);
+
+ return 0;
+}
+
static int netc_setup(struct dsa_switch *ds)
{
struct netc_switch *priv = ds->priv;
@@ -646,6 +729,10 @@ static int netc_setup(struct dsa_switch *ds)
dsa_switch_for_each_available_port(dp, ds)
netc_port_default_config(priv->ports[dp->index]);
+ err = netc_switch_bpt_default_config(priv);
+ if (err)
+ goto free_lock_and_ntmp_user;
+
err = netc_add_standalone_vlan_entry(priv);
if (err)
goto free_lock_and_ntmp_user;
@@ -1201,6 +1288,45 @@ static void netc_port_set_rmii_mii_mac(struct netc_port *np,
netc_mac_port_rmw(np, NETC_PM_IF_MODE(0), mask, val);
}
+static void netc_port_set_tx_pause(struct netc_port *np, bool tx_pause)
+{
+ struct netc_switch *priv = np->switch_priv;
+ int port = np->dp->index;
+ int i, j, num_bp;
+
+ num_bp = priv->num_bp / priv->info->num_ports;
+ for (i = 0, j = port * num_bp; i < num_bp; i++, j++) {
+ struct bpt_cfge_data *cfge = &priv->bpt_list[j];
+ struct bpt_cfge_data old_cfge = *cfge;
+
+ if (tx_pause) {
+ cfge->fc_on_thresh = cpu_to_le16(NETC_FC_THRESH_ON);
+ cfge->fc_off_thresh = cpu_to_le16(NETC_FC_THRESH_OFF);
+ cfge->fccfg_sbpen = FIELD_PREP(BPT_FC_CFG,
+ BPT_FC_CFG_EN_BPFC);
+ cfge->fc_ports = cpu_to_le32(BIT(port));
+ } else {
+ cfge->fc_on_thresh = cpu_to_le16(0);
+ cfge->fc_off_thresh = cpu_to_le16(0);
+ cfge->fccfg_sbpen = 0;
+ cfge->fc_ports = cpu_to_le32(0);
+ }
+
+ if (ntmp_bpt_update_entry(&priv->ntmp, j, cfge)) {
+ *cfge = old_cfge;
+ dev_warn(priv->dev,
+ "Failed to %s TX pause of buffer pool %d (swp%d)\n",
+ tx_pause ? "enable" : "disable", j, port);
+ }
+ }
+}
+
+static void netc_port_set_rx_pause(struct netc_port *np, bool rx_pause)
+{
+ netc_mac_port_rmw(np, NETC_PM_CMD_CFG(0), PM_CMD_CFG_PAUSE_IGN,
+ rx_pause ? 0 : PM_CMD_CFG_PAUSE_IGN);
+}
+
static void netc_port_mac_rx_enable(struct netc_port *np)
{
netc_port_rmw(np, NETC_POR, POR_RXDIS, 0);
@@ -1302,6 +1428,8 @@ static void netc_mac_link_up(struct phylink_config *config,
interface == PHY_INTERFACE_MODE_MII)
netc_port_set_rmii_mii_mac(np, speed, duplex);
+ netc_port_set_tx_pause(np, tx_pause);
+ netc_port_set_rx_pause(np, rx_pause);
netc_port_mac_tx_enable(np);
netc_port_mac_rx_enable(np);
}
diff --git a/drivers/net/dsa/netc/netc_platform.c b/drivers/net/dsa/netc/netc_platform.c
index bb4f92d238cb..34aeb6fceb3c 100644
--- a/drivers/net/dsa/netc/netc_platform.c
+++ b/drivers/net/dsa/netc/netc_platform.c
@@ -14,7 +14,7 @@ struct netc_switch_platform {
static void imx94_switch_phylink_get_caps(int port,
struct phylink_config *config)
{
- config->mac_capabilities = MAC_1000FD;
+ config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE | MAC_1000FD;
switch (port) {
case 0 ... 1:
diff --git a/drivers/net/dsa/netc/netc_switch.h b/drivers/net/dsa/netc/netc_switch.h
index 3efe37fca390..9fea3d4287a3 100644
--- a/drivers/net/dsa/netc/netc_switch.h
+++ b/drivers/net/dsa/netc/netc_switch.h
@@ -27,11 +27,27 @@
#define NETC_TC_NUM 8
#define NETC_CBDR_NUM 2
+#define NETC_IPV_NUM 8
#define NETC_MAX_FRAME_LEN 9600
#define NETC_STANDALONE_PVID 0
+/* Threshold format: MANT (bits 11:4) * 2^EXP (bits 3:0)
+ * Unit: Memory words (average of 20 bytes each)
+ * NETC_BP_THRESH = 0x334, MANT = 0x33, EXP = 4. Threshold: 816 words
+ * NETC_FC_THRESH_ON = 0x533, MANT = 0x53, EXP = 3. Threshold: 664 words
+ * NETC_FC_THRESH_OFF = 0x3c3, MANT = 0x3c, EXP = 3. Threshold: 480 words
+ */
+#define NETC_BP_THRESH 0x334
+#define NETC_FC_THRESH_ON 0x533
+#define NETC_FC_THRESH_OFF 0x3c3
+
+/* PAUSE quanta: 0xFFFF = 65535 quanta (each quanta = 512 bit times) */
+#define NETC_PAUSE_QUANTA 0xFFFF
+/* PAUSE refresh threshold: send refresh when timer reaches this value */
+#define NETC_PAUSE_THRESH 0xFF00
+
struct netc_switch;
struct netc_switch_info {
@@ -93,6 +109,9 @@ struct netc_switch {
/* Switch hardware capabilities */
u32 num_fdb_gmac;
+ u32 num_bp;
+
+ struct bpt_cfge_data *bpt_list;
};
#define NETC_PRIV(ds) ((struct netc_switch *)((ds)->priv))
diff --git a/drivers/net/dsa/netc/netc_switch_hw.h b/drivers/net/dsa/netc/netc_switch_hw.h
index b04e9866d72a..caf5977c5100 100644
--- a/drivers/net/dsa/netc/netc_switch_hw.h
+++ b/drivers/net/dsa/netc/netc_switch_hw.h
@@ -12,6 +12,12 @@
#define NETC_SWITCH_DEVICE_ID 0xeef2
/* Definition of Switch base registers */
+#define NETC_BPCAPR 0x0008
+#define BPCAPR_NUM_BP GENMASK(7, 0)
+
+#define NETC_PBPMCR0 0x0400
+#define NETC_PBPMCR1 0x0404
+
#define NETC_CBDRMR(a) (0x0800 + (a) * 0x30)
#define NETC_CBDRBAR0(a) (0x0810 + (a) * 0x30)
#define NETC_CBDRBAR1(a) (0x0814 + (a) * 0x30)
@@ -141,6 +147,7 @@ enum netc_stg_stage {
#define NETC_PM_CMD_CFG(a) (0x1008 + (a) * 0x400)
#define PM_CMD_CFG_TX_EN BIT(0)
#define PM_CMD_CFG_RX_EN BIT(1)
+#define PM_CMD_CFG_PAUSE_IGN BIT(8)
#define NETC_PM_MAXFRM(a) (0x1014 + (a) * 0x400)
#define PM_MAXFRAM GENMASK(15, 0)
@@ -149,6 +156,9 @@ enum netc_stg_stage {
#define PM_IEVENT_TX_EMPTY BIT(5)
#define PM_IEVENT_RX_EMPTY BIT(6)
+#define NETC_PM_PAUSE_QUANTA(a) (0x1054 + (a) * 0x400)
+#define NETC_PM_PAUSE_THRESH(a) (0x1064 + (a) * 0x400)
+
#define NETC_PM_IF_MODE(a) (0x1300 + (a) * 0x400)
#define PM_IF_MODE_IFMODE GENMASK(2, 0)
#define IFMODE_MII 1
--
2.34.1
^ permalink raw reply related
* [PATCH v5 net-next 14/15] net: dsa: netc: add support for the standardized counters
From: Wei Fang @ 2026-04-30 2:49 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260430024945.3413973-1-wei.fang@nxp.com>
Each user port of the NETC switch supports 802.3 basic and mandatory
managed objects statistic counters and IETF Management Information
Database (MIB) package (RFC2665) and Remote Network Monitoring (RMON)
counters. And all of these counters are 64-bit registers. In addition,
some user ports support preemption, so these ports have two MACs, MAC
0 is the express MAC (eMAC), MAC 1 is the preemptible MAC (pMAC). So
for ports that support preemption, the statistics are the sum of the
pMAC and eMAC statistics.
Note that the current switch driver does not support preemption, all
frames are sent and received via the eMAC by default. The statistics
read from the pMAC should be zero.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/dsa/netc/Makefile | 2 +-
drivers/net/dsa/netc/netc_ethtool.c | 190 ++++++++++++++++++++++++++
drivers/net/dsa/netc/netc_main.c | 4 +
drivers/net/dsa/netc/netc_switch.h | 12 ++
drivers/net/dsa/netc/netc_switch_hw.h | 120 ++++++++++++++++
include/linux/fsl/netc_global.h | 6 +
6 files changed, 333 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/dsa/netc/netc_ethtool.c
diff --git a/drivers/net/dsa/netc/Makefile b/drivers/net/dsa/netc/Makefile
index 4a5767562574..f40b13c702e0 100644
--- a/drivers/net/dsa/netc/Makefile
+++ b/drivers/net/dsa/netc/Makefile
@@ -1,3 +1,3 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_NET_DSA_NETC_SWITCH) += nxp-netc-switch.o
-nxp-netc-switch-objs := netc_main.o netc_platform.o
+nxp-netc-switch-objs := netc_main.o netc_platform.o netc_ethtool.o
diff --git a/drivers/net/dsa/netc/netc_ethtool.c b/drivers/net/dsa/netc/netc_ethtool.c
new file mode 100644
index 000000000000..ac8940b5a85c
--- /dev/null
+++ b/drivers/net/dsa/netc/netc_ethtool.c
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
+/*
+ * NXP NETC switch driver
+ * Copyright 2025-2026 NXP
+ */
+
+#include <linux/ethtool_netlink.h>
+
+#include "netc_switch.h"
+
+static const struct ethtool_rmon_hist_range netc_rmon_ranges[] = {
+ { 64, 64 },
+ { 65, 127 },
+ { 128, 255 },
+ { 256, 511 },
+ { 512, 1023 },
+ { 1024, 1522 },
+ { 1523, NETC_MAX_FRAME_LEN },
+ { }
+};
+
+static void netc_port_pause_stats(struct netc_port *np, int mac,
+ struct ethtool_pause_stats *stats)
+{
+ if (mac && !np->caps.pmac)
+ return;
+
+ stats->tx_pause_frames = netc_port_rd64(np, NETC_PM_TXPF(mac));
+ stats->rx_pause_frames = netc_port_rd64(np, NETC_PM_RXPF(mac));
+}
+
+void netc_port_get_pause_stats(struct dsa_switch *ds, int port,
+ struct ethtool_pause_stats *pause_stats)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ struct net_device *ndev;
+
+ switch (pause_stats->src) {
+ case ETHTOOL_MAC_STATS_SRC_EMAC:
+ netc_port_pause_stats(np, 0, pause_stats);
+ break;
+ case ETHTOOL_MAC_STATS_SRC_PMAC:
+ netc_port_pause_stats(np, 1, pause_stats);
+ break;
+ case ETHTOOL_MAC_STATS_SRC_AGGREGATE:
+ ndev = dsa_to_port(ds, port)->user;
+ ethtool_aggregate_pause_stats(ndev, pause_stats);
+ break;
+ }
+}
+
+static void netc_port_rmon_stats(struct netc_port *np, int mac,
+ struct ethtool_rmon_stats *stats)
+{
+ if (mac && !np->caps.pmac)
+ return;
+
+ stats->undersize_pkts = netc_port_rd64(np, NETC_PM_RUND(mac));
+ stats->oversize_pkts = netc_port_rd64(np, NETC_PM_ROVR(mac));
+ stats->fragments = netc_port_rd64(np, NETC_PM_RFRG(mac));
+ stats->jabbers = netc_port_rd64(np, NETC_PM_RJBR(mac));
+
+ stats->hist[0] = netc_port_rd64(np, NETC_PM_R64(mac));
+ stats->hist[1] = netc_port_rd64(np, NETC_PM_R127(mac));
+ stats->hist[2] = netc_port_rd64(np, NETC_PM_R255(mac));
+ stats->hist[3] = netc_port_rd64(np, NETC_PM_R511(mac));
+ stats->hist[4] = netc_port_rd64(np, NETC_PM_R1023(mac));
+ stats->hist[5] = netc_port_rd64(np, NETC_PM_R1522(mac));
+ stats->hist[6] = netc_port_rd64(np, NETC_PM_R1523X(mac));
+
+ stats->hist_tx[0] = netc_port_rd64(np, NETC_PM_T64(mac));
+ stats->hist_tx[1] = netc_port_rd64(np, NETC_PM_T127(mac));
+ stats->hist_tx[2] = netc_port_rd64(np, NETC_PM_T255(mac));
+ stats->hist_tx[3] = netc_port_rd64(np, NETC_PM_T511(mac));
+ stats->hist_tx[4] = netc_port_rd64(np, NETC_PM_T1023(mac));
+ stats->hist_tx[5] = netc_port_rd64(np, NETC_PM_T1522(mac));
+ stats->hist_tx[6] = netc_port_rd64(np, NETC_PM_T1523X(mac));
+}
+
+void netc_port_get_rmon_stats(struct dsa_switch *ds, int port,
+ struct ethtool_rmon_stats *rmon_stats,
+ const struct ethtool_rmon_hist_range **ranges)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ struct net_device *ndev;
+
+ *ranges = netc_rmon_ranges;
+
+ switch (rmon_stats->src) {
+ case ETHTOOL_MAC_STATS_SRC_EMAC:
+ netc_port_rmon_stats(np, 0, rmon_stats);
+ break;
+ case ETHTOOL_MAC_STATS_SRC_PMAC:
+ netc_port_rmon_stats(np, 1, rmon_stats);
+ break;
+ case ETHTOOL_MAC_STATS_SRC_AGGREGATE:
+ ndev = dsa_to_port(ds, port)->user;
+ ethtool_aggregate_rmon_stats(ndev, rmon_stats);
+ break;
+ }
+}
+
+static void netc_port_ctrl_stats(struct netc_port *np, int mac,
+ struct ethtool_eth_ctrl_stats *stats)
+{
+ if (mac && !np->caps.pmac)
+ return;
+
+ stats->MACControlFramesTransmitted =
+ netc_port_rd64(np, NETC_PM_TCNP(mac));
+ stats->MACControlFramesReceived =
+ netc_port_rd64(np, NETC_PM_RCNP(mac));
+}
+
+void netc_port_get_eth_ctrl_stats(struct dsa_switch *ds, int port,
+ struct ethtool_eth_ctrl_stats *ctrl_stats)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ struct net_device *ndev;
+
+ switch (ctrl_stats->src) {
+ case ETHTOOL_MAC_STATS_SRC_EMAC:
+ netc_port_ctrl_stats(np, 0, ctrl_stats);
+ break;
+ case ETHTOOL_MAC_STATS_SRC_PMAC:
+ netc_port_ctrl_stats(np, 1, ctrl_stats);
+ break;
+ case ETHTOOL_MAC_STATS_SRC_AGGREGATE:
+ ndev = dsa_to_port(ds, port)->user;
+ ethtool_aggregate_ctrl_stats(ndev, ctrl_stats);
+ break;
+ }
+}
+
+static void netc_port_mac_stats(struct netc_port *np, int mac,
+ struct ethtool_eth_mac_stats *stats)
+{
+ if (mac && !np->caps.pmac)
+ return;
+
+ stats->FramesTransmittedOK = netc_port_rd64(np, NETC_PM_TFRM(mac));
+ stats->SingleCollisionFrames = netc_port_rd64(np, NETC_PM_TSCOL(mac));
+ stats->MultipleCollisionFrames =
+ netc_port_rd64(np, NETC_PM_TMCOL(mac));
+ stats->FramesReceivedOK = netc_port_rd64(np, NETC_PM_RFRM(mac));
+ stats->FrameCheckSequenceErrors =
+ netc_port_rd64(np, NETC_PM_RFCS(mac));
+ stats->AlignmentErrors = netc_port_rd64(np, NETC_PM_RALN(mac));
+ stats->OctetsTransmittedOK = netc_port_rd64(np, NETC_PM_TEOCT(mac));
+ stats->FramesWithDeferredXmissions =
+ netc_port_rd64(np, NETC_PM_TDFR(mac));
+ stats->LateCollisions = netc_port_rd64(np, NETC_PM_TLCOL(mac));
+ stats->FramesAbortedDueToXSColls =
+ netc_port_rd64(np, NETC_PM_TECOL(mac));
+ stats->FramesLostDueToIntMACXmitError =
+ netc_port_rd64(np, NETC_PM_TERR(mac));
+ stats->OctetsReceivedOK = netc_port_rd64(np, NETC_PM_REOCT(mac));
+ stats->FramesLostDueToIntMACRcvError =
+ netc_port_rd64(np, NETC_PM_RDRNTP(mac));
+ stats->MulticastFramesXmittedOK =
+ netc_port_rd64(np, NETC_PM_TMCA(mac));
+ stats->BroadcastFramesXmittedOK =
+ netc_port_rd64(np, NETC_PM_TBCA(mac));
+ stats->MulticastFramesReceivedOK =
+ netc_port_rd64(np, NETC_PM_RMCA(mac));
+ stats->BroadcastFramesReceivedOK =
+ netc_port_rd64(np, NETC_PM_RBCA(mac));
+ stats->FramesWithExcessiveDeferral =
+ netc_port_rd64(np, NETC_PM_TEDFR(mac));
+}
+
+void netc_port_get_eth_mac_stats(struct dsa_switch *ds, int port,
+ struct ethtool_eth_mac_stats *mac_stats)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ struct net_device *ndev;
+
+ switch (mac_stats->src) {
+ case ETHTOOL_MAC_STATS_SRC_EMAC:
+ netc_port_mac_stats(np, 0, mac_stats);
+ break;
+ case ETHTOOL_MAC_STATS_SRC_PMAC:
+ netc_port_mac_stats(np, 1, mac_stats);
+ break;
+ case ETHTOOL_MAC_STATS_SRC_AGGREGATE:
+ ndev = dsa_to_port(ds, port)->user;
+ ethtool_aggregate_mac_stats(ndev, mac_stats);
+ break;
+ }
+}
diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c
index fd02a943824d..ae8f28b82b72 100644
--- a/drivers/net/dsa/netc/netc_main.c
+++ b/drivers/net/dsa/netc/netc_main.c
@@ -1468,6 +1468,10 @@ static const struct dsa_switch_ops netc_switch_ops = {
.port_mdb_add = netc_port_mdb_add,
.port_mdb_del = netc_port_mdb_del,
.port_set_host_flood = netc_port_set_host_flood,
+ .get_pause_stats = netc_port_get_pause_stats,
+ .get_rmon_stats = netc_port_get_rmon_stats,
+ .get_eth_ctrl_stats = netc_port_get_eth_ctrl_stats,
+ .get_eth_mac_stats = netc_port_get_eth_mac_stats,
};
static int netc_switch_probe(struct pci_dev *pdev,
diff --git a/drivers/net/dsa/netc/netc_switch.h b/drivers/net/dsa/netc/netc_switch.h
index 9fea3d4287a3..d477b0cd2157 100644
--- a/drivers/net/dsa/netc/netc_switch.h
+++ b/drivers/net/dsa/netc/netc_switch.h
@@ -123,6 +123,7 @@ struct netc_switch {
/* Write/Read registers of Switch Port (including pseudo MAC port) */
#define netc_port_rd(p, o) netc_read((p)->iobase + (o))
+#define netc_port_rd64(p, o) netc_read64((p)->iobase + (o))
#define netc_port_wr(p, o, v) netc_write((p)->iobase + (o), v)
/* Write/Read Switch global registers */
@@ -148,4 +149,15 @@ static inline void netc_del_fdb_entry(struct netc_fdb_entry *entry)
int netc_switch_platform_probe(struct netc_switch *priv);
+/* ethtool APIs */
+void netc_port_get_pause_stats(struct dsa_switch *ds, int port,
+ struct ethtool_pause_stats *pause_stats);
+void netc_port_get_rmon_stats(struct dsa_switch *ds, int port,
+ struct ethtool_rmon_stats *rmon_stats,
+ const struct ethtool_rmon_hist_range **ranges);
+void netc_port_get_eth_ctrl_stats(struct dsa_switch *ds, int port,
+ struct ethtool_eth_ctrl_stats *ctrl_stats);
+void netc_port_get_eth_mac_stats(struct dsa_switch *ds, int port,
+ struct ethtool_eth_mac_stats *mac_stats);
+
#endif
diff --git a/drivers/net/dsa/netc/netc_switch_hw.h b/drivers/net/dsa/netc/netc_switch_hw.h
index caf5977c5100..f8d436ad9623 100644
--- a/drivers/net/dsa/netc/netc_switch_hw.h
+++ b/drivers/net/dsa/netc/netc_switch_hw.h
@@ -173,6 +173,126 @@ enum netc_stg_stage {
#define SSP_10M 1
#define SSP_1G 2
+/* Port MAC 0/1 Receive Ethernet Octets Counter */
+#define NETC_PM_REOCT(a) (0x1100 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Alignment Error Counter Register */
+#define NETC_PM_RALN(a) (0x1110 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Valid Pause Frame Counter */
+#define NETC_PM_RXPF(a) (0x1118 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Frame Counter */
+#define NETC_PM_RFRM(a) (0x1120 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Frame Check Sequence Error Counter */
+#define NETC_PM_RFCS(a) (0x1128 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Multicast Frame Counter */
+#define NETC_PM_RMCA(a) (0x1148 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Broadcast Frame Counter */
+#define NETC_PM_RBCA(a) (0x1150 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Undersized Packet Counter */
+#define NETC_PM_RUND(a) (0x1168 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive 64-Octet Packet Counter */
+#define NETC_PM_R64(a) (0x1170 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive 65 to 127-Octet Packet Counter */
+#define NETC_PM_R127(a) (0x1178 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive 128 to 255-Octet Packet Counter */
+#define NETC_PM_R255(a) (0x1180 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive 256 to 511-Octet Packet Counter */
+#define NETC_PM_R511(a) (0x1188 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive 512 to 1023-Octet Packet Counter */
+#define NETC_PM_R1023(a) (0x1190 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive 1024 to 1522-Octet Packet Counter */
+#define NETC_PM_R1522(a) (0x1198 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive 1523 to Max-Octet Packet Counter */
+#define NETC_PM_R1523X(a) (0x11a0 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Oversized Packet Counter */
+#define NETC_PM_ROVR(a) (0x11a8 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Jabber Packet Counter */
+#define NETC_PM_RJBR(a) (0x11b0 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Fragment Packet Counter */
+#define NETC_PM_RFRG(a) (0x11b8 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Control Packet Counter */
+#define NETC_PM_RCNP(a) (0x11c0 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Dropped Not Truncated Packets Counter */
+#define NETC_PM_RDRNTP(a) (0x11c8 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Ethernet Octets Counter */
+#define NETC_PM_TEOCT(a) (0x1200 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Excessive Deferral Packet Counter */
+#define NETC_PM_TEDFR(a) (0x1210 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Valid Pause Frame Counter */
+#define NETC_PM_TXPF(a) (0x1218 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Frame Counter */
+#define NETC_PM_TFRM(a) (0x1220 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Frame Error Counter */
+#define NETC_PM_TERR(a) (0x1238 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Multicast Frame Counter */
+#define NETC_PM_TMCA(a) (0x1248 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Broadcast Frame Counter */
+#define NETC_PM_TBCA(a) (0x1250 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit 64-Octet Packet Counter */
+#define NETC_PM_T64(a) (0x1270 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit 65 to 127-Octet Packet Counter */
+#define NETC_PM_T127(a) (0x1278 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit 128 to 255-Octet Packet Counter */
+#define NETC_PM_T255(a) (0x1280 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit 256 to 511-Octet Packet Counter */
+#define NETC_PM_T511(a) (0x1288 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit 512 to 1023-Octet Packet Counter */
+#define NETC_PM_T1023(a) (0x1290 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit 1024 to 1522-Octet Packet Counter */
+#define NETC_PM_T1522(a) (0x1298 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit 1523 to TX_MTU-Octet Packet Counter */
+#define NETC_PM_T1523X(a) (0x12a0 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Control Packet Counter */
+#define NETC_PM_TCNP(a) (0x12c0 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Deferred Packet Counter */
+#define NETC_PM_TDFR(a) (0x12d0 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Multiple Collisions Counter */
+#define NETC_PM_TMCOL(a) (0x12d8 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Single Collision */
+#define NETC_PM_TSCOL(a) (0x12e0 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Late Collision Counter */
+#define NETC_PM_TLCOL(a) (0x12e8 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Excessive Collisions Counter */
+#define NETC_PM_TECOL(a) (0x12f0 + (a) * 0x400)
+
#define NETC_PEMDIOCR 0x1c00
#define NETC_EMDIO_BASE NETC_PEMDIOCR
diff --git a/include/linux/fsl/netc_global.h b/include/linux/fsl/netc_global.h
index fdecca8c90f0..5b8ff528d369 100644
--- a/include/linux/fsl/netc_global.h
+++ b/include/linux/fsl/netc_global.h
@@ -5,6 +5,7 @@
#define __NETC_GLOBAL_H
#include <linux/io.h>
+#include <linux/io-64-nonatomic-lo-hi.h>
static inline u32 netc_read(void __iomem *reg)
{
@@ -16,4 +17,9 @@ static inline void netc_write(void __iomem *reg, u32 val)
iowrite32(val, reg);
}
+static inline u64 netc_read64(void __iomem *reg)
+{
+ return ioread64(reg);
+}
+
#endif
--
2.34.1
^ permalink raw reply related
* [PATCH v5 net-next 15/15] net: dsa: netc: add support for ethtool private statistics
From: Wei Fang @ 2026-04-30 2:49 UTC (permalink / raw)
To: claudiu.manoil, vladimir.oltean, xiaoning.wang, andrew+netdev,
davem, edumazet, kuba, pabeni, robh, krzk+dt, conor+dt,
f.fainelli, frank.li, chleroy, horms, linux
Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
imx
In-Reply-To: <20260430024945.3413973-1-wei.fang@nxp.com>
Implement the ethtool private statistics interface to expose additional
port-level and MAC-level counters that are not covered by the standard
IEEE 802.3 statistics. The pMAC counters are only reported when the port
supports Frame Preemption (802.1Qbu/802.3br).
Signed-off-by: Wei Fang <wei.fang@nxp.com>
---
drivers/net/dsa/netc/netc_ethtool.c | 107 ++++++++++++++++++++++++++
drivers/net/dsa/netc/netc_main.c | 3 +
drivers/net/dsa/netc/netc_switch.h | 9 +++
drivers/net/dsa/netc/netc_switch_hw.h | 58 ++++++++++++++
4 files changed, 177 insertions(+)
diff --git a/drivers/net/dsa/netc/netc_ethtool.c b/drivers/net/dsa/netc/netc_ethtool.c
index ac8940b5a85c..8d04db534347 100644
--- a/drivers/net/dsa/netc/netc_ethtool.c
+++ b/drivers/net/dsa/netc/netc_ethtool.c
@@ -19,6 +19,56 @@ static const struct ethtool_rmon_hist_range netc_rmon_ranges[] = {
{ }
};
+static const struct netc_port_stat netc_port_counters[] = {
+ { NETC_PTGSLACR, "port gate late arrival frames" },
+ { NETC_PSDFTCR, "port SDF transmit frames" },
+ { NETC_PSDFDDCR, "port SDF drop duplicate frames" },
+ { NETC_PRXDCR, "port rx discard frames" },
+ { NETC_PRXDCRRR, "port rx discard read-reset" },
+ { NETC_PRXDCRR0, "port rx discard reason 0" },
+ { NETC_PRXDCRR1, "port rx discard reason 1" },
+ { NETC_PTXDCR, "port tx discard frames" },
+ { NETC_PTXDCRRR, "port tx discard read-reset" },
+ { NETC_PTXDCRR0, "port tx discard reason 0" },
+ { NETC_PTXDCRR1, "port tx discard reason 1" },
+ { NETC_BPDCR, "bridge port discard frames" },
+ { NETC_BPDCRRR, "bridge port discard read-reset" },
+ { NETC_BPDCRR0, "bridge port discard reason 0" },
+ { NETC_BPDCRR1, "bridge port discard reason 1" },
+};
+
+static const struct netc_port_stat netc_emac_counters[] = {
+ { NETC_PM_ROCT(0), "eMAC rx octets" },
+ { NETC_PM_RVLAN(0), "eMAC rx VLAN frames" },
+ { NETC_PM_RERR(0), "eMAC rx frame errors" },
+ { NETC_PM_RUCA(0), "eMAC rx unicast frames" },
+ { NETC_PM_RDRP(0), "eMAC rx dropped packets" },
+ { NETC_PM_RPKT(0), "eMAC rx packets" },
+ { NETC_PM_TOCT(0), "eMAC tx octets" },
+ { NETC_PM_TVLAN(0), "eMAC tx VLAN frames" },
+ { NETC_PM_TFCS(0), "eMAC tx FCS errors" },
+ { NETC_PM_TUCA(0), "eMAC tx unicast frames" },
+ { NETC_PM_TPKT(0), "eMAC tx packets" },
+ { NETC_PM_TUND(0), "eMAC tx undersized packets" },
+ { NETC_PM_TIOCT(0), "eMAC tx invalid octets" },
+};
+
+static const struct netc_port_stat netc_pmac_counters[] = {
+ { NETC_PM_ROCT(1), "pMAC rx octets" },
+ { NETC_PM_RVLAN(1), "pMAC rx VLAN frames" },
+ { NETC_PM_RERR(1), "pMAC rx frame errors" },
+ { NETC_PM_RUCA(1), "pMAC rx unicast frames" },
+ { NETC_PM_RDRP(1), "pMAC rx dropped packets" },
+ { NETC_PM_RPKT(1), "pMAC rx packets" },
+ { NETC_PM_TOCT(1), "pMAC tx octets" },
+ { NETC_PM_TVLAN(1), "pMAC tx VLAN frames" },
+ { NETC_PM_TFCS(1), "pMAC tx FCS errors" },
+ { NETC_PM_TUCA(1), "pMAC tx unicast frames" },
+ { NETC_PM_TPKT(1), "pMAC tx packets" },
+ { NETC_PM_TUND(1), "pMAC tx undersized packets" },
+ { NETC_PM_TIOCT(1), "pMAC tx invalid octets" },
+};
+
static void netc_port_pause_stats(struct netc_port *np, int mac,
struct ethtool_pause_stats *stats)
{
@@ -188,3 +238,60 @@ void netc_port_get_eth_mac_stats(struct dsa_switch *ds, int port,
break;
}
}
+
+int netc_port_get_sset_count(struct dsa_switch *ds, int port, int sset)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ int size;
+
+ if (sset != ETH_SS_STATS)
+ return -EOPNOTSUPP;
+
+ size = ARRAY_SIZE(netc_port_counters) +
+ ARRAY_SIZE(netc_emac_counters);
+
+ if (np->caps.pmac)
+ size += ARRAY_SIZE(netc_pmac_counters);
+
+ return size;
+}
+
+void netc_port_get_strings(struct dsa_switch *ds, int port,
+ u32 sset, u8 *data)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ int i;
+
+ if (sset != ETH_SS_STATS)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(netc_port_counters); i++)
+ ethtool_cpy(&data, netc_port_counters[i].name);
+
+ for (i = 0; i < ARRAY_SIZE(netc_emac_counters); i++)
+ ethtool_cpy(&data, netc_emac_counters[i].name);
+
+ if (!np->caps.pmac)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(netc_pmac_counters); i++)
+ ethtool_cpy(&data, netc_pmac_counters[i].name);
+}
+
+void netc_port_get_ethtool_stats(struct dsa_switch *ds, int port, u64 *data)
+{
+ struct netc_port *np = NETC_PORT(ds, port);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(netc_port_counters); i++)
+ *data++ = netc_port_rd(np, netc_port_counters[i].reg);
+
+ for (i = 0; i < ARRAY_SIZE(netc_emac_counters); i++)
+ *data++ = netc_port_rd64(np, netc_emac_counters[i].reg);
+
+ if (!np->caps.pmac)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(netc_pmac_counters); i++)
+ *data++ = netc_port_rd64(np, netc_pmac_counters[i].reg);
+}
diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c
index ae8f28b82b72..803582d939b0 100644
--- a/drivers/net/dsa/netc/netc_main.c
+++ b/drivers/net/dsa/netc/netc_main.c
@@ -1472,6 +1472,9 @@ static const struct dsa_switch_ops netc_switch_ops = {
.get_rmon_stats = netc_port_get_rmon_stats,
.get_eth_ctrl_stats = netc_port_get_eth_ctrl_stats,
.get_eth_mac_stats = netc_port_get_eth_mac_stats,
+ .get_sset_count = netc_port_get_sset_count,
+ .get_strings = netc_port_get_strings,
+ .get_ethtool_stats = netc_port_get_ethtool_stats,
};
static int netc_switch_probe(struct pci_dev *pdev,
diff --git a/drivers/net/dsa/netc/netc_switch.h b/drivers/net/dsa/netc/netc_switch.h
index d477b0cd2157..90d750331fb9 100644
--- a/drivers/net/dsa/netc/netc_switch.h
+++ b/drivers/net/dsa/netc/netc_switch.h
@@ -93,6 +93,11 @@ struct netc_fdb_entry {
struct hlist_node node;
};
+struct netc_port_stat {
+ int reg;
+ char name[ETH_GSTRING_LEN] __nonstring;
+};
+
struct netc_switch {
struct pci_dev *pdev;
struct device *dev;
@@ -159,5 +164,9 @@ void netc_port_get_eth_ctrl_stats(struct dsa_switch *ds, int port,
struct ethtool_eth_ctrl_stats *ctrl_stats);
void netc_port_get_eth_mac_stats(struct dsa_switch *ds, int port,
struct ethtool_eth_mac_stats *mac_stats);
+int netc_port_get_sset_count(struct dsa_switch *ds, int port, int sset);
+void netc_port_get_strings(struct dsa_switch *ds, int port,
+ u32 sset, u8 *data);
+void netc_port_get_ethtool_stats(struct dsa_switch *ds, int port, u64 *data);
#endif
diff --git a/drivers/net/dsa/netc/netc_switch_hw.h b/drivers/net/dsa/netc/netc_switch_hw.h
index f8d436ad9623..1b016e7dd03e 100644
--- a/drivers/net/dsa/netc/netc_switch_hw.h
+++ b/drivers/net/dsa/netc/netc_switch_hw.h
@@ -87,6 +87,17 @@
#define PSR_TX_BUSY BIT(0)
#define PSR_RX_BUSY BIT(1)
+#define NETC_PTGSLACR 0x130
+
+#define NETC_PRXDCR 0x1c0
+#define NETC_PRXDCRRR 0x1c4
+#define NETC_PRXDCRR0 0x1c8
+#define NETC_PRXDCRR1 0x1cc
+#define NETC_PTXDCR 0x1e0
+#define NETC_PTXDCRRR 0x1e4
+#define NETC_PTXDCRR0 0x1e8
+#define NETC_PTXDCRR1 0x1ec
+
#define NETC_PTCTMSDUR(a) (0x208 + (a) * 0x20)
#define PTCTMSDUR_MAXSDU GENMASK(15, 0)
#define PTCTMSDUR_SDU_TYPE GENMASK(17, 16)
@@ -94,6 +105,9 @@
#define SDU_TYPE_MPDU 1
#define SDU_TYPE_MSDU 2
+#define NETC_PSDFTCR 0x4c4
+#define NETC_PSDFDDCR 0x4c8
+
#define NETC_BPCR 0x500
#define BPCR_DYN_LIMIT GENMASK(15, 0)
#define BPCR_MLO GENMASK(22, 20)
@@ -142,6 +156,11 @@ enum netc_stg_stage {
NETC_STG_STATE_FORWARDING,
};
+#define NETC_BPDCR 0x580
+#define NETC_BPDCRRR 0x584
+#define NETC_BPDCRR0 0x588
+#define NETC_BPDCRR1 0x58c
+
/* Definition of Switch ethernet MAC port registers */
#define NETC_PMAC_OFFSET 0x400
#define NETC_PM_CMD_CFG(a) (0x1008 + (a) * 0x400)
@@ -176,6 +195,9 @@ enum netc_stg_stage {
/* Port MAC 0/1 Receive Ethernet Octets Counter */
#define NETC_PM_REOCT(a) (0x1100 + (a) * 0x400)
+/* Port MAC 0/1 Receive Octets Counter */
+#define NETC_PM_ROCT(a) (0x1108 + (a) * 0x400)
+
/* Port MAC 0/1 Receive Alignment Error Counter Register */
#define NETC_PM_RALN(a) (0x1110 + (a) * 0x400)
@@ -188,12 +210,27 @@ enum netc_stg_stage {
/* Port MAC 0/1 Receive Frame Check Sequence Error Counter */
#define NETC_PM_RFCS(a) (0x1128 + (a) * 0x400)
+/* Port MAC 0/1 Receive VLAN Frame Counter */
+#define NETC_PM_RVLAN(a) (0x1130 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Frame Error Counter */
+#define NETC_PM_RERR(a) (0x1138 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Unicast Frame Counter */
+#define NETC_PM_RUCA(a) (0x1140 + (a) * 0x400)
+
/* Port MAC 0/1 Receive Multicast Frame Counter */
#define NETC_PM_RMCA(a) (0x1148 + (a) * 0x400)
/* Port MAC 0/1 Receive Broadcast Frame Counter */
#define NETC_PM_RBCA(a) (0x1150 + (a) * 0x400)
+/* Port MAC 0/1 Receive Dropped Packets Counter */
+#define NETC_PM_RDRP(a) (0x1158 + (a) * 0x400)
+
+/* Port MAC 0/1 Receive Packets Counter */
+#define NETC_PM_RPKT(a) (0x1160 + (a) * 0x400)
+
/* Port MAC 0/1 Receive Undersized Packet Counter */
#define NETC_PM_RUND(a) (0x1168 + (a) * 0x400)
@@ -236,6 +273,9 @@ enum netc_stg_stage {
/* Port MAC 0/1 Transmit Ethernet Octets Counter */
#define NETC_PM_TEOCT(a) (0x1200 + (a) * 0x400)
+/* Port MAC 0/1 Transmit Octets Counter */
+#define NETC_PM_TOCT(a) (0x1208 + (a) * 0x400)
+
/* Port MAC 0/1 Transmit Excessive Deferral Packet Counter */
#define NETC_PM_TEDFR(a) (0x1210 + (a) * 0x400)
@@ -245,15 +285,30 @@ enum netc_stg_stage {
/* Port MAC 0/1 Transmit Frame Counter */
#define NETC_PM_TFRM(a) (0x1220 + (a) * 0x400)
+/* Port MAC 0/1 Transmit Frame Check Sequence Error Counter */
+#define NETC_PM_TFCS(a) (0x1228 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit VLAN Frame Counter */
+#define NETC_PM_TVLAN(a) (0x1230 + (a) * 0x400)
+
/* Port MAC 0/1 Transmit Frame Error Counter */
#define NETC_PM_TERR(a) (0x1238 + (a) * 0x400)
+/* Port MAC 0/1 Transmit Unicast Frame Counter */
+#define NETC_PM_TUCA(a) (0x1240 + (a) * 0x400)
+
/* Port MAC 0/1 Transmit Multicast Frame Counter */
#define NETC_PM_TMCA(a) (0x1248 + (a) * 0x400)
/* Port MAC 0/1 Transmit Broadcast Frame Counter */
#define NETC_PM_TBCA(a) (0x1250 + (a) * 0x400)
+/* Port MAC 0/1 Transmit Packets Counter */
+#define NETC_PM_TPKT(a) (0x1260 + (a) * 0x400)
+
+/* Port MAC 0/1 Transmit Undersized Packet Counter */
+#define NETC_PM_TUND(a) (0x1268 + (a) * 0x400)
+
/* Port MAC 0/1 Transmit 64-Octet Packet Counter */
#define NETC_PM_T64(a) (0x1270 + (a) * 0x400)
@@ -293,6 +348,9 @@ enum netc_stg_stage {
/* Port MAC 0/1 Transmit Excessive Collisions Counter */
#define NETC_PM_TECOL(a) (0x12f0 + (a) * 0x400)
+/* Port MAC 0/1 Transmit Invalid Octets Counter */
+#define NETC_PM_TIOCT(a) (0x12f8 + (a) * 0x400)
+
#define NETC_PEMDIOCR 0x1c00
#define NETC_EMDIO_BASE NETC_PEMDIOCR
--
2.34.1
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox