From 3d6e3ee119d42eae33757a599835f248b0932e6a Mon Sep 17 00:00:00 2001 From: Custom Firmware Date: Tue, 9 Jun 2026 01:09:01 +0300 Subject: [PATCH 01/13] dt-bindings: mfd: mediatek,mt6397: add the MT6323 audio codec compatible Document mediatek,mt6323-sound in the audio-codec node, alongside the existing MT6358/MT6397 codecs. Signed-off-by: Custom Firmware --- Documentation/devicetree/bindings/mfd/mediatek,mt6397.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/mfd/mediatek,mt6397.yaml b/Documentation/devicetree/bindings/mfd/mediatek,mt6397.yaml index 05c121b0cb3d86..3d90cf2f32c06e 100644 --- a/Documentation/devicetree/bindings/mfd/mediatek,mt6397.yaml +++ b/Documentation/devicetree/bindings/mfd/mediatek,mt6397.yaml @@ -110,13 +110,14 @@ properties: audio-codec: type: object description: - Audio codec support with MT6358 and MT6397. + Audio codec support with MT6323, MT6358 and MT6397. additionalProperties: true properties: compatible: oneOf: - enum: + - mediatek,mt6323-sound - mediatek,mt6358-sound - mediatek,mt6359-codec - mediatek,mt6397-codec From 42e9de3f6d28ad7cf7eb60d48dd888f2bcb3bce8 Mon Sep 17 00:00:00 2001 From: Custom Firmware Date: Tue, 9 Jun 2026 01:09:01 +0300 Subject: [PATCH 02/13] dt-bindings: sound: add the MediaTek MT6572 AFE The MT6572 Audio Front-End: a DL1 playback memif streaming from the on-chip audio SRAM to the MT6323 PMIC codec. Signed-off-by: Custom Firmware --- .../bindings/sound/mediatek,mt6572-afe.yaml | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/mediatek,mt6572-afe.yaml diff --git a/Documentation/devicetree/bindings/sound/mediatek,mt6572-afe.yaml b/Documentation/devicetree/bindings/sound/mediatek,mt6572-afe.yaml new file mode 100644 index 00000000000000..35dbc17e9448bb --- /dev/null +++ b/Documentation/devicetree/bindings/sound/mediatek,mt6572-afe.yaml @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/mediatek,mt6572-afe.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MediaTek AFE PCM controller for MT6572 + +maintainers: + - Custom Firmware + +properties: + compatible: + const: mediatek,mt6572-audio + + clocks: + maxItems: 1 + + interrupts: + maxItems: 1 + + iram: + $ref: /schemas/types.yaml#/definitions/phandle + description: on-chip audio SRAM used for the DL1 playback buffers. + +required: + - compatible + - clocks + - interrupts + - iram + +additionalProperties: false From 95a81859375941fb9ada2a974a8dced48eee89f4 Mon Sep 17 00:00:00 2001 From: Custom Firmware Date: Tue, 9 Jun 2026 01:09:01 +0300 Subject: [PATCH 03/13] dt-bindings: sound: add the MediaTek MT6572 + MT6323 sound card The machine binding tying the MT6572 AFE to the MT6323 PMIC codec, with standard aux-devs/audio-routing, optional headphone-jack detection and a speaker amplifier. Signed-off-by: Custom Firmware --- .../sound/mediatek,mt6572-mt6323.yaml | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/mediatek,mt6572-mt6323.yaml diff --git a/Documentation/devicetree/bindings/sound/mediatek,mt6572-mt6323.yaml b/Documentation/devicetree/bindings/sound/mediatek,mt6572-mt6323.yaml new file mode 100644 index 00000000000000..0240afdd948673 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/mediatek,mt6572-mt6323.yaml @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/mediatek,mt6572-mt6323.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MediaTek MT6572 sound card with the MT6323 PMIC codec + +maintainers: + - Custom Firmware + +description: + MediaTek MT6572 SoC-based sound card using the MT6323 PMIC analog codec + for playback, with an optional external speaker amplifier. + +allOf: + - $ref: sound-card-common.yaml# + +properties: + compatible: + const: mediatek,mt6572-mt6323-sound + + mediatek,platform: + $ref: /schemas/types.yaml#/definitions/phandle + description: phandle to the MT6572 AFE platform. + + aux-devs: + $ref: /schemas/types.yaml#/definitions/phandle-array + description: + List of phandles pointing to auxiliary devices, such as an external + speaker amplifier, to be added to the sound card. + + hp-det-gpios: + maxItems: 1 + description: Optional headphone-jack plug-detect GPIO. + +required: + - compatible + - mediatek,platform + +unevaluatedProperties: false From b52fd10c65f1f25b51674eb5a5c81394d29d5b0b Mon Sep 17 00:00:00 2001 From: Custom Firmware Date: Tue, 9 Jun 2026 01:09:02 +0300 Subject: [PATCH 04/13] ASoC: codecs: add the MediaTek MT6323 PMIC audio codec DAC, headphone output, internal class-D speaker and volume controls on the MT6323 PMIC, reached over the parent MT6397 PMIC-wrapper regmap. The analog register fields are undocumented; the init values come from the stock power-on sequence. Signed-off-by: Custom Firmware --- sound/soc/codecs/Kconfig | 7 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/mt6323.c | 366 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 375 insertions(+) create mode 100644 sound/soc/codecs/mt6323.c diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index adb3fb923be394..3c455597d6b105 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -164,6 +164,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_MAX9877 imply SND_SOC_MC13783 imply SND_SOC_ML26124 + imply SND_SOC_MT6323 imply SND_SOC_MT6351 imply SND_SOC_MT6357 imply SND_SOC_MT6358 @@ -2756,6 +2757,12 @@ config SND_SOC_ML26124 tristate depends on I2C +config SND_SOC_MT6323 + tristate "MediaTek MT6323 Codec" + help + Enable support for the platform which uses MT6323 as + external codec device. + config SND_SOC_MT6351 tristate "MediaTek MT6351 Codec" diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 3ddee529872197..3450efcad30d4c 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -187,6 +187,7 @@ snd-soc-mc13783-y := mc13783.o snd-soc-ml26124-y := ml26124.o snd-soc-msm8916-analog-y := msm8916-wcd-analog.o snd-soc-msm8916-digital-y := msm8916-wcd-digital.o +snd-soc-mt6323-y := mt6323.o snd-soc-mt6351-y := mt6351.o snd-soc-mt6357-y := mt6357.o snd-soc-mt6358-y := mt6358.o @@ -620,6 +621,7 @@ obj-$(CONFIG_SND_SOC_MSM8916_WCD_ANALOG) +=snd-soc-msm8916-analog.o obj-$(CONFIG_SND_SOC_MSM8916_WCD_DIGITAL) +=snd-soc-msm8916-digital.o obj-$(CONFIG_SND_SOC_MT6351) += snd-soc-mt6351.o obj-$(CONFIG_SND_SOC_MT6357) += snd-soc-mt6357.o +obj-$(CONFIG_SND_SOC_MT6323) += snd-soc-mt6323.o obj-$(CONFIG_SND_SOC_MT6358) += snd-soc-mt6358.o obj-$(CONFIG_SND_SOC_MT6359) += snd-soc-mt6359.o obj-$(CONFIG_SND_SOC_MT6359_ACCDET) += mt6359-accdet.o diff --git a/sound/soc/codecs/mt6323.c b/sound/soc/codecs/mt6323.c new file mode 100644 index 00000000000000..dea1875e8e6a9e --- /dev/null +++ b/sound/soc/codecs/mt6323.c @@ -0,0 +1,366 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MT6323 PMIC analog audio codec driver + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define MT6323_CODEC_RATES SNDRV_PCM_RATE_8000_48000 +#define MT6323_CODEC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +#define ABB_AFE_CON(n) (0x4000 + (n) * 2) +#define AUDTOP_CON(n) (0x0700 + (n) * 2) +#define SPK_CON(n) (0x0052 + (n) * 2) +#define PMIC_RG_AUD_SPK_PDN 0x000e +#define AUDTOP_CON7_VBUF_GAIN GENMASK(7, 4) /* voice-buffer gain: 0x5=-11dB .. 0xb=+1dB */ +#define SPK_VBUF_GAIN_START 0x6 +#define SPK_VBUF_GAIN_END 0xb /* 0x35b0, final +1dB */ +#define ABB_AFE_UP8X_FIFO_CFG0 0x401e +#define ABB_AFE_PMIC_NEWIF_CFG0 0x4024 +#define ABB_AFE_PMIC_NEWIF_CFG1 0x4026 +#define ABB_AFE_PMIC_NEWIF_CFG2 0x4028 +#define ABB_AFE_PMIC_NEWIF_CFG3 0x402a + +/* MT6323 audio clocks (TOP_CKPDN). Atomic SET/CLR aliases. */ +#define MT6323_TOP_CKPDN0_SET 0x0104 +#define MT6323_TOP_CKPDN0_CLR 0x0106 +#define PMIC_RG_CLKSQ_EN_AUD BIT(0) + +#define MT6323_TOP_CKPDN1_SET 0x010a +#define MT6323_TOP_CKPDN1_CLR 0x010c +#define PMIC_RG_AUD_26M_PDN BIT(8) + +#define ZCD_CON1 0x0802 /* lineout L/R gain */ +#define ZCD_CON2 0x0804 /* headphone L/R gain */ +#define ZCD_GAIN_0DB 8 +#define ZCD_GAIN_N10DB 18 +#define ZCD_GAIN_CTL_MAX 0x12 /* +8dB(0) .. 0dB(8) .. -10dB(18), -1dB/step */ +#define ZCD_GAIN_REG(g) (((g) << 7) | (g)) /* L at [4:0], R at [11:7] */ + +struct mt6323_codec_priv { + struct device *dev; + /* borrowed from the parent MT6323 MFD */ + struct regmap *regmap; +}; + +static const struct reg_sequence mt6323_codec_init[] = { + { ABB_AFE_CON(1), 0x0009 }, + { ABB_AFE_CON(3), 0x0221 }, + { ABB_AFE_CON(4), 0x0255 }, + { ABB_AFE_CON(5), 0x0028 }, + { ABB_AFE_CON(6), 0x0218 }, + { ABB_AFE_CON(7), 0x0204 }, + { ABB_AFE_CON(10), 0x0001 }, + { AUDTOP_CON(0), 0x6010 }, + { AUDTOP_CON(1), 0x0140 }, + { AUDTOP_CON(2), 0x00c0 }, + { AUDTOP_CON(3), 0x0200 }, + { AUDTOP_CON(5), 0x0014 }, + { AUDTOP_CON(6), 0x37e2 }, + { AUDTOP_CON(8), 0x0200 }, + { AUDTOP_CON(9), 0x0008 }, + { ABB_AFE_UP8X_FIFO_CFG0, 0x0001 }, + { ABB_AFE_PMIC_NEWIF_CFG0, 0x7330 }, + { ABB_AFE_PMIC_NEWIF_CFG1, 0x0018 }, + { ABB_AFE_PMIC_NEWIF_CFG2, 0x302f }, + { ABB_AFE_PMIC_NEWIF_CFG3, 0xf872 }, + { ZCD_CON2, ZCD_GAIN_REG(ZCD_GAIN_0DB) }, + { ZCD_CON1, ZCD_GAIN_REG(ZCD_GAIN_N10DB) }, + { SPK_CON(9), 0x0400 }, +}; + +/* Audio clock supply (CLKSQ + 26 MHz), enabled while the stream is active. */ +static int mt6323_clk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct mt6323_codec_priv *priv = + snd_soc_component_get_drvdata(snd_soc_dapm_to_component(w->dapm)); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + regmap_write(priv->regmap, MT6323_TOP_CKPDN0_SET, PMIC_RG_CLKSQ_EN_AUD); + regmap_write(priv->regmap, MT6323_TOP_CKPDN1_CLR, PMIC_RG_AUD_26M_PDN); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_write(priv->regmap, MT6323_TOP_CKPDN1_SET, PMIC_RG_AUD_26M_PDN); + regmap_write(priv->regmap, MT6323_TOP_CKPDN0_CLR, PMIC_RG_CLKSQ_EN_AUD); + break; + } + return 0; +} + +/* AFE<->PMIC serial bridge (NEWIF) downlink enable. */ +static int mt6323_newif_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct mt6323_codec_priv *priv = + snd_soc_component_get_drvdata(snd_soc_dapm_to_component(w->dapm)); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* enable the ABB AFE bridge (CON0) and its clock/format config (CON11) */ + regmap_write(priv->regmap, ABB_AFE_CON(0), 0x0001); + regmap_write(priv->regmap, ABB_AFE_CON(11), 0x0303); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_write(priv->regmap, ABB_AFE_CON(11), 0x0000); + regmap_write(priv->regmap, ABB_AFE_CON(0), 0x0000); + break; + } + return 0; +} + +static int mt6323_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct mt6323_codec_priv *priv = + snd_soc_component_get_drvdata(snd_soc_dapm_to_component(w->dapm)); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* DAC output level */ + regmap_write(priv->regmap, AUDTOP_CON(5), 0x0014); + /* DAC enable */ + regmap_write(priv->regmap, AUDTOP_CON(0), 0x7010); + break; + case SND_SOC_DAPM_POST_PMD: + /* restore idle baseline */ + regmap_write(priv->regmap, AUDTOP_CON(0), 0x6010); + regmap_write(priv->regmap, AUDTOP_CON(5), 0x0014); + break; + } + return 0; +} + +static int mt6323_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct mt6323_codec_priv *priv = + snd_soc_component_get_drvdata(snd_soc_dapm_to_component(w->dapm)); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* HP drv + depop */ + regmap_write(priv->regmap, AUDTOP_CON(6), 0xf5ba); + /* HP enable */ + regmap_write(priv->regmap, AUDTOP_CON(4), 0x007c); + break; + case SND_SOC_DAPM_POST_PMD: + /* HP off */ + regmap_write(priv->regmap, AUDTOP_CON(4), 0x0000); + /* baseline */ + regmap_write(priv->regmap, AUDTOP_CON(6), 0x37e2); + break; + } + return 0; +} + +/* + * Internal class-D speaker (SPK_CON), fed by the AUDTOP voice/LCH DAC; the + * 1.35V CM buffer comes up with the shared "DAC" widget. + */ +static int mt6323_speaker_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct mt6323_codec_priv *priv = + snd_soc_component_get_drvdata(snd_soc_dapm_to_component(w->dapm)); + int i; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* voice buffer, min gain */ + regmap_write(priv->regmap, AUDTOP_CON(7), 0x2400); + /* HP input short, 2.4V, audio clk */ + regmap_write(priv->regmap, AUDTOP_CON(6), 0xb7f6); + /* audio bias + LCH DAC */ + regmap_write(priv->regmap, AUDTOP_CON(4), 0x0014); + /* bias/DAC settle */ + fsleep(10000); + /* connect voice buffer -> SPK amp */ + regmap_write(priv->regmap, AUDTOP_CON(7), 0x3550); + /* speaker clock on */ + regmap_write(priv->regmap, MT6323_TOP_CKPDN1_CLR, PMIC_RG_AUD_SPK_PDN); + /* class-AB OC protection */ + regmap_write(priv->regmap, SPK_CON(2), 0x0214); + /* enable amp, offset trim, class-D */ + regmap_write(priv->regmap, SPK_CON(0), 0x3008); + regmap_write(priv->regmap, SPK_CON(0), 0x3009); + /* amp power-up settle */ + fsleep(5000); + /* class-D, amp enable */ + regmap_write(priv->regmap, SPK_CON(0), 0x3001); + /* output stage enable */ + regmap_write(priv->regmap, SPK_CON(12), 0x0a00); + /* ramp the voice-buffer gain up to its final level, 1ms/step, to avoid a turn-on pop */ + for (i = SPK_VBUF_GAIN_START; i <= SPK_VBUF_GAIN_END; i++) { + fsleep(1000); + regmap_write(priv->regmap, AUDTOP_CON(7), + 0x3500 | FIELD_PREP(AUDTOP_CON7_VBUF_GAIN, i)); + } + break; + case SND_SOC_DAPM_POST_PMD: + /* mute + disable class-D amp */ + regmap_write(priv->regmap, SPK_CON(0), 0x0004); + /* output stage off */ + regmap_write(priv->regmap, SPK_CON(12), 0x0000); + /* speaker clock off */ + regmap_write(priv->regmap, MT6323_TOP_CKPDN1_SET, PMIC_RG_AUD_SPK_PDN); + /* voice buffer off */ + regmap_write(priv->regmap, AUDTOP_CON(7), 0x2400); + /* LCH DAC off */ + regmap_write(priv->regmap, AUDTOP_CON(4), 0x0000); + /* baseline */ + regmap_write(priv->regmap, AUDTOP_CON(6), 0x37e2); + break; + } + return 0; +} + +static const struct snd_soc_dapm_widget mt6323_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("AUDCLK", SND_SOC_NOPM, 0, 0, mt6323_clk_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("NEWIF", SND_SOC_NOPM, 0, 0, mt6323_newif_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_DAC_E("DAC", NULL, SND_SOC_NOPM, 0, 0, mt6323_dac_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_OUT_DRV_E("HP Driver", SND_SOC_NOPM, 0, 0, NULL, 0, + mt6323_hp_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_OUTPUT("Headphone"), + SND_SOC_DAPM_OUT_DRV_E("Speaker Driver", SND_SOC_NOPM, 0, 0, NULL, 0, + mt6323_speaker_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route mt6323_dapm_routes[] = { + { "DAC", NULL, "AIF1 Playback" }, + { "DAC", NULL, "AUDCLK" }, + { "DAC", NULL, "NEWIF" }, + { "HP Driver", NULL, "DAC" }, + { "Headphone", NULL, "HP Driver" }, + { "Speaker Driver", NULL, "DAC" }, +}; + +/* Per-output mute switches. */ +#define MT6323_PIN_SWITCH(xname) { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname " Switch", \ + .info = snd_soc_dapm_info_pin_switch, \ + .get = snd_soc_dapm_get_component_pin_switch, \ + .put = snd_soc_dapm_put_component_pin_switch, \ + .private_value = (unsigned long)xname } + +/* Output volume: -10dB .. +8dB in 1dB steps (the inverted ZCD gain field). */ +static const DECLARE_TLV_DB_SCALE(mt6323_dl_tlv, -1000, 100, 0); + +/* Class-D speaker PGA gain: analog, scales after the DAC. */ +static const DECLARE_TLV_DB_RANGE(mt6323_spk_tlv, + 0, 0, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1), + 1, 1, TLV_DB_SCALE_ITEM(0, 0, 0), + 2, 15, TLV_DB_SCALE_ITEM(400, 100, 0)); + +static const struct snd_kcontrol_new mt6323_snd_controls[] = { + MT6323_PIN_SWITCH("Headphone"), + SOC_DOUBLE_TLV("Headphone Volume", ZCD_CON2, 0, 7, ZCD_GAIN_CTL_MAX, 1, + mt6323_dl_tlv), + SOC_DOUBLE_TLV("Lineout Volume", ZCD_CON1, 0, 7, ZCD_GAIN_CTL_MAX, 1, + mt6323_dl_tlv), + SOC_SINGLE_TLV("Speaker Volume", SPK_CON(9), 8, 0x0f, 0, mt6323_spk_tlv), +}; + +static int mt6323_component_probe(struct snd_soc_component *component) +{ + struct mt6323_codec_priv *priv = snd_soc_component_get_drvdata(component); + + /* init the component regmap so mixer control writes reach the PMIC */ + snd_soc_component_init_regmap(component, priv->regmap); + return 0; +} + +static const struct snd_soc_component_driver mt6323_soc_component_driver = { + .probe = mt6323_component_probe, + .controls = mt6323_snd_controls, + .num_controls = ARRAY_SIZE(mt6323_snd_controls), + .dapm_widgets = mt6323_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(mt6323_dapm_widgets), + .dapm_routes = mt6323_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(mt6323_dapm_routes), + .endianness = 1, +}; + +static struct snd_soc_dai_driver mt6323_dai_driver[] = { + { + .name = "mt6323-snd-codec-aif1", + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = MT6323_CODEC_RATES, + .formats = MT6323_CODEC_FORMATS, + }, + }, +}; + +static int mt6323_codec_probe(struct platform_device *pdev) +{ + struct mt6397_chip *mt6397 = dev_get_drvdata(pdev->dev.parent); + struct mt6323_codec_priv *priv; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = &pdev->dev; + priv->regmap = mt6397->regmap; + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + platform_set_drvdata(pdev, priv); + + /* Analog + NEWIF idle baseline; DAPM powers the output path per-stream. */ + ret = regmap_multi_reg_write(priv->regmap, mt6323_codec_init, + ARRAY_SIZE(mt6323_codec_init)); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed to init analog codec\n"); + + ret = devm_snd_soc_register_component(&pdev->dev, + &mt6323_soc_component_driver, + mt6323_dai_driver, + ARRAY_SIZE(mt6323_dai_driver)); + if (ret) + return ret; + + return 0; +} + +static const struct of_device_id mt6323_codec_of_match[] = { + { .compatible = "mediatek,mt6323-sound", }, + { } +}; +MODULE_DEVICE_TABLE(of, mt6323_codec_of_match); + +static struct platform_driver mt6323_codec_driver = { + .driver = { + .name = "mt6323-sound", + .of_match_table = mt6323_codec_of_match, + }, + .probe = mt6323_codec_probe, +}; +module_platform_driver(mt6323_codec_driver); + +MODULE_DESCRIPTION("MediaTek MT6323 PMIC audio codec driver"); +MODULE_LICENSE("GPL"); From b5d97d063025e8d6d637c718982c4ff332639f4e Mon Sep 17 00:00:00 2001 From: Custom Firmware Date: Tue, 9 Jun 2026 01:09:02 +0300 Subject: [PATCH 05/13] ASoC: mediatek: add MT6572 AFE platform and MT6323 sound card AFE platform driver (DL1 playback memif + ADDA/PMIC link) plus the mt6572-mt6323 machine driver: a DL1 -> mt6323 dai-link, headphone-jack auto-routing between speaker and headphones, and an optional speaker amplifier via the standard aux-devs/audio-routing bindings. Earpiece and uplink/capture are not yet supported. Signed-off-by: Custom Firmware --- sound/soc/mediatek/Kconfig | 20 + sound/soc/mediatek/Makefile | 1 + sound/soc/mediatek/mt6572/Makefile | 3 + sound/soc/mediatek/mt6572/mt6572-afe-pcm.c | 443 +++++++++++++++++++++ sound/soc/mediatek/mt6572/mt6572-mt6323.c | 137 +++++++ 5 files changed, 604 insertions(+) create mode 100644 sound/soc/mediatek/mt6572/Makefile create mode 100644 sound/soc/mediatek/mt6572/mt6572-afe-pcm.c create mode 100644 sound/soc/mediatek/mt6572/mt6572-mt6323.c diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig index 3a1e1fa3fe5cc7..49ef0284ded21f 100644 --- a/sound/soc/mediatek/Kconfig +++ b/sound/soc/mediatek/Kconfig @@ -36,6 +36,26 @@ config SND_SOC_MT2701_WM8960 Select Y if you have such device. If unsure select "N". +config SND_SOC_MT6572 + tristate "ASoC support for the MediaTek MT6572 chip" + depends on ARCH_MEDIATEK || COMPILE_TEST + select SND_SOC_MEDIATEK + help + This adds the ASoC platform (AFE) driver for the MediaTek MT6572 + SoC. Select a machine driver below to build a sound card. + Select Y if you have such a device. If unsure select "N". + +config SND_SOC_MT6572_MT6323 + tristate "ASoC machine driver for MT6572 with the MT6323 codec" + depends on SND_SOC_MT6572 + select SND_SOC_MT6323 + imply SND_SOC_SIMPLE_AMPLIFIER + help + This adds the ASoC machine driver for a MediaTek MT6572 board with + the on-PMIC MT6323 analog audio codec: DL1 playback, headphone-jack + detection and an optional external speaker amplifier. + Select Y if you have such a device. If unsure select "N". + config SND_SOC_MT6797 tristate "ASoC support for Mediatek MT6797 chip" depends on ARCH_MEDIATEK diff --git a/sound/soc/mediatek/Makefile b/sound/soc/mediatek/Makefile index 7cd67bce92e9a4..fb19b582dc58a3 100644 --- a/sound/soc/mediatek/Makefile +++ b/sound/soc/mediatek/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_SND_SOC_MEDIATEK) += common/ obj-$(CONFIG_SND_SOC_MT2701) += mt2701/ +obj-$(CONFIG_SND_SOC_MT6572) += mt6572/ obj-$(CONFIG_SND_SOC_MT6797) += mt6797/ obj-$(CONFIG_SND_SOC_MT7986) += mt7986/ obj-$(CONFIG_SND_SOC_MT8173) += mt8173/ diff --git a/sound/soc/mediatek/mt6572/Makefile b/sound/soc/mediatek/mt6572/Makefile new file mode 100644 index 00000000000000..12f8e0e3858c26 --- /dev/null +++ b/sound/soc/mediatek/mt6572/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_SND_SOC_MT6572) += mt6572-afe-pcm.o +obj-$(CONFIG_SND_SOC_MT6572_MT6323) += mt6572-mt6323.o diff --git a/sound/soc/mediatek/mt6572/mt6572-afe-pcm.c b/sound/soc/mediatek/mt6572/mt6572-afe-pcm.c new file mode 100644 index 00000000000000..9401c91951fdd4 --- /dev/null +++ b/sound/soc/mediatek/mt6572/mt6572-afe-pcm.c @@ -0,0 +1,443 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MediaTek MT6572 AFE platform driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define AUDIO_TOP_CON0 0x0000 +#define AUDIO_TOP_CON0_AFE_ON 0x60004000 + +#define AFE_DAC_CON0 0x0010 +#define AFE_DAC_CON0_AFE_ON BIT(0) +#define AFE_DAC_CON0_DL1_ON BIT(1) +#define AFE_DAC_CON0_DL1_OUT BIT(10) + +#define AFE_DAC_CON1 0x0014 +#define AFE_DAC_CON1_DL1_RATE GENMASK(3, 0) + +#define AFE_DL1_BASE 0x0040 +#define AFE_DL1_CUR 0x0044 +/* ring end, inclusive */ +#define AFE_DL1_END 0x0048 + +#define AFE_MEMIF_MAXLEN 0x03d4 +#define AFE_MEMIF_MAXLEN_DL1 GENMASK(3, 0) + +#define AFE_MEMIF_PBUF_SIZE 0x03d8 +#define AFE_MEMIF_PBUF_SIZE_DL1 GENMASK(17, 16) + +#define AFE_IRQ_MCU_CON 0x03a0 +#define AFE_IRQ_MCU_CON_IRQ1_ON BIT(0) +#define AFE_IRQ_MCU_CON_IRQ1_RATE GENMASK(7, 4) + +#define AFE_IRQ_MCU_STATUS 0x03a4 +#define AFE_IRQ_MCU_STATUS_IRQ1 BIT(0) +#define AFE_IRQ_MCU_STATUS_MASK GENMASK(3, 0) + +#define AFE_IRQ_MCU_CLR 0x03a8 +#define AFE_IRQ_MCU_CLR_NOSTATUS (BIT(6) | BIT(1) | BIT(0)) + +#define AFE_IRQ_MCU_CNT1 0x03ac + +/* DL1 -> interconnect -> ADDA downlink SRC -> AFE<->PMIC link. */ +#define AFE_I2S_CON1 0x0034 +#define AFE_I2S_CON1_DAC_FORMAT 0x00000008 +#define AFE_I2S_CON1_RATE GENMASK(11, 8) +#define AFE_I2S_CON1_ON BIT(0) + +#define AFE_CONN1 0x0024 +#define AFE_CONN1_DL1_O3 BIT(21) /* DL1 ch1 -> interconnect out O3 */ + +#define AFE_CONN2 0x0028 +#define AFE_CONN2_DL1_O4 BIT(6) /* DL1 ch2 -> interconnect out O4 */ + +#define AFE_ADDA_DL_SRC2_CON0 0x0108 +#define AFE_ADDA_DL_SRC2_CON0_BASE 0x03001802 /* SRC-disabled base */ +#define AFE_ADDA_DL_SRC2_CON0_RATE GENMASK(31, 28) +#define AFE_ADDA_DL_SRC2_CON0_ON BIT(0) + +#define AFE_ADDA_DL_SRC2_CON1 0x010c +#define AFE_ADDA_DL_SRC2_CON1_GAIN GENMASK(31, 16) +#define AFE_DL_GAIN_DEFAULT 0x203b /* ~-18dB */ + +#define AFE_ADDA_UL_DL_CON0 0x0124 +#define AFE_ADDA_UL_DL_CON0_ON BIT(0) + +#define AFE_ADDA_PREDIS_CON0 0x0260 /* ADDA downlink pre-distortion */ +#define AFE_ADDA_PREDIS_CON1 0x0264 + +#define AFE_ADDA_NEWIF_CFG0 0x0138 /* AFE<->PMIC serial link (NEWIF) */ +#define AFE_ADDA_NEWIF_CFG0_VAL 0x03f87200 + +#define AFE_ADDA_NEWIF_CFG1 0x013c +#define AFE_ADDA_NEWIF_CFG1_VAL 0x03117180 + +static const struct regmap_config mt6572_afe_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .fast_io = true, + .max_register = 0x0ffc, +}; + +struct mt6572_afe { + struct device *dev; + struct regmap *regmap; + struct clk *clk; + struct snd_pcm_substream *dl1_substream; /* active DL1 stream */ + unsigned int dl_gain; /* "Playback Volume" */ +}; + +static int mt6572_afe_rate_code(unsigned int rate) +{ + switch (rate) { + case 8000: return 0; + case 11025: return 1; + case 12000: return 2; + case 16000: return 4; + case 22050: return 5; + case 24000: return 6; + case 32000: return 8; + case 44100: return 9; + case 48000: return 10; + default: return -EINVAL; + } +} + +static int mt6572_afe_adda_rate_code(unsigned int rate) +{ + switch (rate) { + case 8000: return 0; + case 11025: return 1; + case 12000: return 2; + case 16000: return 3; + case 22050: return 4; + case 24000: return 5; + case 32000: return 6; + case 44100: return 7; + case 48000: return 8; + default: return -EINVAL; + } +} + +static struct snd_soc_dai_driver mt6572_afe_dais[] = { + { + .name = "mt6572-afe-dl1", + .playback = { + .stream_name = "DL1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + }, +}; + +static const struct snd_pcm_hardware mt6572_afe_hardware = { + /* on-chip SRAM buffer, no mmap */ + .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .period_bytes_min = 1024, + .period_bytes_max = 8192, + .periods_min = 2, + .periods_max = 16, + .buffer_bytes_max = 16 * 1024, +}; + +static int mt6572_afe_pcm_open(struct snd_soc_component *comp, + struct snd_pcm_substream *substream) +{ + snd_soc_set_runtime_hwparams(substream, &mt6572_afe_hardware); + /* AFE_DL1_END[2:0] must be 7: keep the period (so the buffer) 8-byte aligned. */ + return snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 8); +} + +static int mt6572_afe_pcm_hw_params(struct snd_soc_component *comp, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct mt6572_afe *afe = snd_soc_component_get_drvdata(comp); + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int bytes = params_buffer_bytes(params); + u32 base = lower_32_bits(runtime->dma_addr); + + /* program the DL1 memif DMA ring (in the AFE on-chip SRAM) */ + regmap_write(afe->regmap, AFE_DL1_BASE, base); + regmap_write(afe->regmap, AFE_DL1_END, base + bytes - 1); + regmap_clear_bits(afe->regmap, AFE_MEMIF_MAXLEN, AFE_MEMIF_MAXLEN_DL1); + regmap_clear_bits(afe->regmap, AFE_MEMIF_PBUF_SIZE, AFE_MEMIF_PBUF_SIZE_DL1); + return 0; +} + +static int mt6572_afe_pcm_prepare(struct snd_soc_component *comp, + struct snd_pcm_substream *substream) +{ + struct mt6572_afe *afe = snd_soc_component_get_drvdata(comp); + struct snd_pcm_runtime *runtime = substream->runtime; + int adda_code = mt6572_afe_adda_rate_code(runtime->rate); + int rate_code = mt6572_afe_rate_code(runtime->rate); + + if (adda_code < 0 || rate_code < 0) + return -EINVAL; + + /* IRQ1 rate + per-period frame count (enabled in the trigger) */ + regmap_update_bits(afe->regmap, AFE_IRQ_MCU_CON, AFE_IRQ_MCU_CON_IRQ1_RATE, + FIELD_PREP(AFE_IRQ_MCU_CON_IRQ1_RATE, rate_code)); + regmap_write(afe->regmap, AFE_IRQ_MCU_CNT1, runtime->period_size); + + /* interconnect: DL1 ch1/ch2 -> O3/O4 */ + regmap_set_bits(afe->regmap, AFE_CONN1, AFE_CONN1_DL1_O3); + regmap_set_bits(afe->regmap, AFE_CONN2, AFE_CONN2_DL1_O4); + + regmap_set_bits(afe->regmap, AFE_DAC_CON0, AFE_DAC_CON0_DL1_OUT); + regmap_write(afe->regmap, AFE_ADDA_PREDIS_CON0, 0); + regmap_write(afe->regmap, AFE_ADDA_PREDIS_CON1, 0); + + /* enable ADDA downlink SRC + I2S */ + regmap_write(afe->regmap, AFE_ADDA_DL_SRC2_CON0, + AFE_ADDA_DL_SRC2_CON0_BASE | + FIELD_PREP(AFE_ADDA_DL_SRC2_CON0_RATE, adda_code) | + AFE_ADDA_DL_SRC2_CON0_ON); + regmap_write(afe->regmap, AFE_ADDA_DL_SRC2_CON1, + FIELD_PREP(AFE_ADDA_DL_SRC2_CON1_GAIN, afe->dl_gain)); + regmap_write(afe->regmap, AFE_I2S_CON1, + AFE_I2S_CON1_DAC_FORMAT | FIELD_PREP(AFE_I2S_CON1_RATE, rate_code)); + regmap_write(afe->regmap, AFE_ADDA_DL_SRC2_CON0, + AFE_ADDA_DL_SRC2_CON0_BASE | + FIELD_PREP(AFE_ADDA_DL_SRC2_CON0_RATE, adda_code) | + AFE_ADDA_DL_SRC2_CON0_ON); + regmap_set_bits(afe->regmap, AFE_I2S_CON1, AFE_I2S_CON1_ON); + regmap_write(afe->regmap, AFE_ADDA_DL_SRC2_CON0, + AFE_ADDA_DL_SRC2_CON0_BASE | + FIELD_PREP(AFE_ADDA_DL_SRC2_CON0_RATE, adda_code) | + AFE_ADDA_DL_SRC2_CON0_ON); + regmap_set_bits(afe->regmap, AFE_ADDA_UL_DL_CON0, AFE_ADDA_UL_DL_CON0_ON); + + regmap_set_bits(afe->regmap, AFE_DAC_CON0, AFE_DAC_CON0_AFE_ON); + regmap_update_bits(afe->regmap, AFE_DAC_CON1, AFE_DAC_CON1_DL1_RATE, + FIELD_PREP(AFE_DAC_CON1_DL1_RATE, rate_code)); + + return 0; +} + +static int mt6572_afe_pcm_trigger(struct snd_soc_component *comp, + struct snd_pcm_substream *substream, int cmd) +{ + struct mt6572_afe *afe = snd_soc_component_get_drvdata(comp); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + afe->dl1_substream = substream; + /* atomic: memif start + period IRQ only (HW-IRQ driven) */ + regmap_set_bits(afe->regmap, AFE_IRQ_MCU_CON, AFE_IRQ_MCU_CON_IRQ1_ON); + regmap_set_bits(afe->regmap, AFE_DAC_CON0, AFE_DAC_CON0_DL1_ON); + return 0; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + /* minimal stop: DL1 memif + period IRQ only; .prepare re-asserts the rest */ + regmap_clear_bits(afe->regmap, AFE_IRQ_MCU_CON, AFE_IRQ_MCU_CON_IRQ1_ON); + regmap_clear_bits(afe->regmap, AFE_DAC_CON0, AFE_DAC_CON0_DL1_ON); + afe->dl1_substream = NULL; + return 0; + default: + return -EINVAL; + } +} + +static snd_pcm_uframes_t mt6572_afe_pcm_pointer(struct snd_soc_component *comp, + struct snd_pcm_substream *substream) +{ + struct mt6572_afe *afe = snd_soc_component_get_drvdata(comp); + struct snd_pcm_runtime *runtime = substream->runtime; + u32 base = lower_32_bits(runtime->dma_addr); + unsigned int cur = 0; + + regmap_read(afe->regmap, AFE_DL1_CUR, &cur); + if (cur < base || cur >= base + runtime->dma_bytes) + return 0; + return bytes_to_frames(runtime, cur - base); +} + +static int mt6572_afe_pcm_construct(struct snd_soc_component *comp, + struct snd_soc_pcm_runtime *rtd) +{ + size_t size = mt6572_afe_hardware.buffer_bytes_max; + + snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV_IRAM, comp->dev, + size, size); + return 0; +} + +static const DECLARE_TLV_DB_LINEAR(dl_gain_tlv, TLV_DB_GAIN_MUTE, 0); + +static int mt6572_dl_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct mt6572_afe *afe = snd_soc_component_get_drvdata(comp); + + ucontrol->value.integer.value[0] = afe->dl_gain; + return 0; +} + +static int mt6572_dl_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct mt6572_afe *afe = snd_soc_component_get_drvdata(comp); + unsigned int gain = ucontrol->value.integer.value[0]; + + if (gain > 0xffff) + return -EINVAL; + if (gain == afe->dl_gain) + return 0; + + afe->dl_gain = gain; + regmap_update_bits(afe->regmap, AFE_ADDA_DL_SRC2_CON1, + AFE_ADDA_DL_SRC2_CON1_GAIN, + FIELD_PREP(AFE_ADDA_DL_SRC2_CON1_GAIN, gain)); + return 1; +} + +static const struct snd_kcontrol_new mt6572_afe_controls[] = { + SOC_SINGLE_EXT_TLV("Playback Volume", SND_SOC_NOPM, 0, 0xffff, 0, + mt6572_dl_gain_get, mt6572_dl_gain_put, dl_gain_tlv), +}; + +static const struct snd_soc_component_driver mt6572_afe_component = { + .name = "mt6572-afe-pcm", + .controls = mt6572_afe_controls, + .num_controls = ARRAY_SIZE(mt6572_afe_controls), + .open = mt6572_afe_pcm_open, + .hw_params = mt6572_afe_pcm_hw_params, + .prepare = mt6572_afe_pcm_prepare, + .trigger = mt6572_afe_pcm_trigger, + .pointer = mt6572_afe_pcm_pointer, + .pcm_construct = mt6572_afe_pcm_construct, +}; + +/* IRQ1 marks a DL1 period; hardirq, atomic PCM (fast_io regmap). */ +static irqreturn_t mt6572_afe_irq(int irq, void *dev_id) +{ + struct mt6572_afe *afe = dev_id; + unsigned int status; + + regmap_read(afe->regmap, AFE_IRQ_MCU_STATUS, &status); + status &= AFE_IRQ_MCU_STATUS_MASK; + if (!status) { + /* triggered with no status set: write the clear-mask (bit 6 clears all) to ack */ + regmap_write(afe->regmap, AFE_IRQ_MCU_CLR, AFE_IRQ_MCU_CLR_NOSTATUS); + return IRQ_HANDLED; + } + + if ((status & AFE_IRQ_MCU_STATUS_IRQ1) && afe->dl1_substream) + snd_pcm_period_elapsed(afe->dl1_substream); + + regmap_write(afe->regmap, AFE_IRQ_MCU_CLR, status); + return IRQ_HANDLED; +} + +static int mt6572_afe_pcm_dev_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mt6572_afe *afe; + struct resource res; + void __iomem *base; + int ret, irq; + + afe = devm_kzalloc(dev, sizeof(*afe), GFP_KERNEL); + if (!afe) + return -ENOMEM; + afe->dev = dev; + afe->dl_gain = AFE_DL_GAIN_DEFAULT; + platform_set_drvdata(pdev, afe); + + ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) + return dev_err_probe(dev, ret, "failed to set DMA mask\n"); + + afe->clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(afe->clk)) + return dev_err_probe(dev, PTR_ERR(afe->clk), + "failed to get/enable the audio clock\n"); + + /* The AFE registers are in the parent audsys syscon window. */ + ret = of_address_to_resource(dev->parent->of_node, 0, &res); + if (ret) + return dev_err_probe(dev, ret, "no AFE reg in parent syscon\n"); + base = devm_ioremap(dev, res.start, resource_size(&res)); + if (!base) + return dev_err_probe(dev, -ENOMEM, "failed to map AFE registers\n"); + afe->regmap = devm_regmap_init_mmio(dev, base, &mt6572_afe_regmap_config); + if (IS_ERR(afe->regmap)) + return dev_err_probe(dev, PTR_ERR(afe->regmap), + "failed to init AFE regmap\n"); + + /* power on the AFE top + the SoC side of the AFE<->PMIC link */ + regmap_write(afe->regmap, AUDIO_TOP_CON0, AUDIO_TOP_CON0_AFE_ON); + regmap_write(afe->regmap, AFE_ADDA_NEWIF_CFG0, AFE_ADDA_NEWIF_CFG0_VAL); + regmap_write(afe->regmap, AFE_ADDA_NEWIF_CFG1, AFE_ADDA_NEWIF_CFG1_VAL); + + /* mask all AFE IRQs + clear stale status before hooking the GIC */ + regmap_write(afe->regmap, AFE_IRQ_MCU_CON, 0); + regmap_write(afe->regmap, AFE_IRQ_MCU_CLR, AFE_IRQ_MCU_STATUS_MASK); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + ret = devm_request_irq(dev, irq, mt6572_afe_irq, 0, "mt6572-afe", afe); + if (ret) + return dev_err_probe(dev, ret, "failed to request AFE irq %d\n", irq); + + ret = devm_snd_soc_register_component(dev, &mt6572_afe_component, + mt6572_afe_dais, + ARRAY_SIZE(mt6572_afe_dais)); + if (ret) + return dev_err_probe(dev, ret, "failed to register AFE component\n"); + + return 0; +} + +static const struct of_device_id mt6572_afe_pcm_dt_match[] = { + { .compatible = "mediatek,mt6572-audio" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mt6572_afe_pcm_dt_match); + +static struct platform_driver mt6572_afe_pcm_driver = { + .driver = { + .name = "mt6572-afe-pcm", + .of_match_table = mt6572_afe_pcm_dt_match, + }, + .probe = mt6572_afe_pcm_dev_probe, +}; +module_platform_driver(mt6572_afe_pcm_driver); + +MODULE_DESCRIPTION("MediaTek MT6572 AFE platform driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/mediatek/mt6572/mt6572-mt6323.c b/sound/soc/mediatek/mt6572/mt6572-mt6323.c new file mode 100644 index 00000000000000..27c288de58747d --- /dev/null +++ b/sound/soc/mediatek/mt6572/mt6572-mt6323.c @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MediaTek MT6572 + MT6323 sound card + */ + +#include +#include +#include +#include + +#include +#include + +static const struct snd_soc_dapm_widget mt6572_mt6323_widgets[] = { + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +SND_SOC_DAILINK_DEFS(playback, + DAILINK_COMP_ARRAY(COMP_CPU("mt6572-afe-dl1")), + DAILINK_COMP_ARRAY(COMP_CODEC("mt6323-sound", "mt6323-snd-codec-aif1")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link mt6572_mt6323_dai_links[] = { + { + .name = "DL1", + .stream_name = "DL1 Playback", + SND_SOC_DAILINK_REG(playback), + }, +}; + +static struct snd_soc_jack mt6572_mt6323_hp_jack; + +static struct snd_soc_jack_pin mt6572_mt6323_jack_pins[] = { + { .pin = "Headphone", .mask = SND_JACK_HEADPHONE }, + { .pin = "Speaker", .mask = SND_JACK_HEADPHONE, .invert = 1 }, +}; + +static struct snd_soc_jack_gpio mt6572_mt6323_jack_gpio = { + .name = "hp-det", + .report = SND_JACK_HEADPHONE, + .debounce_time = 256, +}; + +static void mt6572_mt6323_jack_free(void *jack) +{ + snd_soc_jack_free_gpios(jack, 1, &mt6572_mt6323_jack_gpio); +} + +static int mt6572_mt6323_late_probe(struct snd_soc_card *card) +{ + int ret; + + if (!device_property_present(card->dev, "hp-det-gpios")) + return 0; + + ret = snd_soc_card_jack_new_pins(card, "Headphone Jack", SND_JACK_HEADPHONE, + &mt6572_mt6323_hp_jack, + mt6572_mt6323_jack_pins, + ARRAY_SIZE(mt6572_mt6323_jack_pins)); + if (ret) + return ret; + + mt6572_mt6323_jack_gpio.gpiod_dev = card->dev; + ret = snd_soc_jack_add_gpios(&mt6572_mt6323_hp_jack, 1, + &mt6572_mt6323_jack_gpio); + if (ret) + return ret; + + return devm_add_action_or_reset(card->dev, mt6572_mt6323_jack_free, + &mt6572_mt6323_hp_jack); +} + +static struct snd_soc_card mt6572_mt6323_card = { + .name = "mt6572-mt6323", + .owner = THIS_MODULE, + .dai_link = mt6572_mt6323_dai_links, + .num_links = ARRAY_SIZE(mt6572_mt6323_dai_links), + .dapm_widgets = mt6572_mt6323_widgets, + .num_dapm_widgets = ARRAY_SIZE(mt6572_mt6323_widgets), + .late_probe = mt6572_mt6323_late_probe, +}; + +static int mt6572_mt6323_dev_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &mt6572_mt6323_card; + struct device_node *platform_node; + struct snd_soc_dai_link *dai_link; + int i, ret; + + card->dev = &pdev->dev; + + platform_node = of_parse_phandle(pdev->dev.of_node, "mediatek,platform", 0); + if (!platform_node) + return dev_err_probe(&pdev->dev, -EINVAL, + "missing mediatek,platform\n"); + + /* The DL1 CPU DAI and the PCM platform both live on the AFE node. */ + for_each_card_prelinks(card, i, dai_link) { + dai_link->cpus->of_node = platform_node; + dai_link->platforms->of_node = platform_node; + } + + ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); + if (ret) + goto put_node; + + /* Optional external speaker amplifier(s) via "aux-devs". */ + ret = snd_soc_of_parse_aux_devs(card, "aux-devs"); + if (ret) + goto put_node; + + ret = devm_snd_soc_register_card(&pdev->dev, card); +put_node: + of_node_put(platform_node); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to set up sound card\n"); + + return 0; +} + +static const struct of_device_id mt6572_mt6323_dt_match[] = { + { .compatible = "mediatek,mt6572-mt6323-sound" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mt6572_mt6323_dt_match); + +static struct platform_driver mt6572_mt6323_driver = { + .driver = { + .name = "mt6572-mt6323", + .of_match_table = mt6572_mt6323_dt_match, + }, + .probe = mt6572_mt6323_dev_probe, +}; +module_platform_driver(mt6572_mt6323_driver); + +MODULE_DESCRIPTION("MediaTek MT6572 MT6323 sound card"); +MODULE_LICENSE("GPL"); From 702913d12b9c184072960cad050e95f705c80f03 Mon Sep 17 00:00:00 2001 From: Custom Firmware Date: Tue, 9 Jun 2026 01:09:02 +0300 Subject: [PATCH 06/13] mfd: mt6397: register the MT6323 audio codec cell Add an mt6323-sound MFD cell so the MT6323 ASoC codec driver probes. Signed-off-by: Custom Firmware --- drivers/mfd/mt6397-core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/mfd/mt6397-core.c b/drivers/mfd/mt6397-core.c index 061ac242f1f8ba..9eb77d16306804 100644 --- a/drivers/mfd/mt6397-core.c +++ b/drivers/mfd/mt6397-core.c @@ -151,6 +151,9 @@ static const struct mfd_cell mt6323_devs[] = { .num_resources = ARRAY_SIZE(mt6323_pwrc_resources), .resources = mt6323_pwrc_resources, .of_compatible = "mediatek,mt6323-pwrc" + }, { + .name = "mt6323-sound", + .of_compatible = "mediatek,mt6323-sound" }, { .name = "mt6323-thermal", .of_compatible = "mediatek,mt6323-thermal", From 0ae00fa879db987e65c4c214b4e44bbd4b959d4b Mon Sep 17 00:00:00 2001 From: Custom Firmware Date: Tue, 9 Jun 2026 01:09:02 +0300 Subject: [PATCH 07/13] pinctrl: mediatek: mt6572: map GPIO142 to EINT7 GPIO142 is the PAP5500 headphone-jack plug-detect line; give it its EINT7 mapping so gpiod_to_irq() works. Signed-off-by: Custom Firmware --- drivers/pinctrl/mediatek/pinctrl-mtk-mt6572.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pinctrl/mediatek/pinctrl-mtk-mt6572.h b/drivers/pinctrl/mediatek/pinctrl-mtk-mt6572.h index 350f2ba81e9533..872e6ca6d16051 100644 --- a/drivers/pinctrl/mediatek/pinctrl-mtk-mt6572.h +++ b/drivers/pinctrl/mediatek/pinctrl-mtk-mt6572.h @@ -1348,7 +1348,7 @@ static const struct mtk_pin_desc mtk_pins_mt6572[] = { ), MTK_PIN( 142, "GPIO142", - MTK_EINT_FUNCTION(NO_EINT_SUPPORT, NO_EINT_SUPPORT), + MTK_EINT_FUNCTION(3, 7), DRV_GRP2, MTK_FUNCTION(0, "GPIO142"), MTK_FUNCTION(1, "BPI_BUS12"), From e1ecda2fc9069001402cad2a50910779baaf200d Mon Sep 17 00:00:00 2001 From: Custom Firmware Date: Tue, 9 Jun 2026 01:09:03 +0300 Subject: [PATCH 08/13] ARM: dts: mediatek: mt6572: add the AFE audio-controller node Sub-node of the audsys syscon for the DL1 playback front-end + the on-chip audio SRAM. Signed-off-by: Custom Firmware --- arch/arm/boot/dts/mediatek/mt6572.dtsi | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/arch/arm/boot/dts/mediatek/mt6572.dtsi b/arch/arm/boot/dts/mediatek/mt6572.dtsi index 2042ebd69a7158..5fbfee78439ca9 100644 --- a/arch/arm/boot/dts/mediatek/mt6572.dtsi +++ b/arch/arm/boot/dts/mediatek/mt6572.dtsi @@ -530,6 +530,26 @@ status = "disabled"; }; + audsys: clock-controller@11140000 { + /* audio clock gates + AFE syscon; simple-mfd adds the AFE child */ + compatible = "mediatek,mt6572-audsys", "syscon", + "simple-mfd"; + reg = <0x11140000 0x1000>; + #clock-cells = <1>; + + afe: audio-controller { + compatible = "mediatek,mt6572-audio"; + clocks = <&topckgen CLK_TOP_AUDIO>; + iram = <&audio_sram>; + interrupts = ; + }; + }; + + audio_sram: sram@11141000 { + compatible = "mmio-sram"; + reg = <0x11141000 0x4000>; + }; + mfgcfg: syscon@13000000 { compatible = "mediatek,mt6572-mfgcfg", "syscon"; reg = <0x13000000 0x1000>; From 81b94d584062a40f542b4f37d8cd7cb42c517b53 Mon Sep 17 00:00:00 2001 From: Custom Firmware Date: Tue, 9 Jun 2026 01:09:03 +0300 Subject: [PATCH 09/13] ARM: dts: mediatek: mt6323: add the audio-codec node The MT6323 PMIC analog codec (mediatek,mt6323-sound). Signed-off-by: Custom Firmware --- arch/arm/boot/dts/mediatek/mt6323.dtsi | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm/boot/dts/mediatek/mt6323.dtsi b/arch/arm/boot/dts/mediatek/mt6323.dtsi index 78e370b7100736..31e571a58dfeb5 100644 --- a/arch/arm/boot/dts/mediatek/mt6323.dtsi +++ b/arch/arm/boot/dts/mediatek/mt6323.dtsi @@ -21,6 +21,10 @@ #io-channel-cells = <1>; }; + mt6323_codec: audio-codec { + compatible = "mediatek,mt6323-sound"; + }; + efuse { compatible = "mediatek,mt6323-efuse"; From d0d2e215ff23f32bbcfe8d461b587a6c3ca37ef2 Mon Sep 17 00:00:00 2001 From: Custom Firmware Date: Tue, 9 Jun 2026 01:09:03 +0300 Subject: [PATCH 10/13] ARM: dts: mediatek: pap5500-duo: wire up audio The sound card (mediatek,mt6572-mt6323-sound) with the AFE platform, an external speaker amplifier (simple-audio-amplifier on GPIO139) and headphone-jack detect (GPIO142/EINT7). Signed-off-by: Custom Firmware --- .../mediatek/mt6572-prestigio-pap5500-duo.dts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/arch/arm/boot/dts/mediatek/mt6572-prestigio-pap5500-duo.dts b/arch/arm/boot/dts/mediatek/mt6572-prestigio-pap5500-duo.dts index 6ca5c94debf302..47155e128ec059 100644 --- a/arch/arm/boot/dts/mediatek/mt6572-prestigio-pap5500-duo.dts +++ b/arch/arm/boot/dts/mediatek/mt6572-prestigio-pap5500-duo.dts @@ -219,6 +219,28 @@ }; }; +/ { + /* speaker amplifier (Yamaha YDA145), fed from the codec HP output */ + speaker_amp: audio-amplifier { + compatible = "simple-audio-amplifier"; + pinctrl-names = "default"; + pinctrl-0 = <&speaker_amplifier_pins>; + enable-gpios = <&pio 139 GPIO_ACTIVE_HIGH>; + sound-name-prefix = "Speaker Amp"; + }; + + sound { + compatible = "mediatek,mt6572-mt6323-sound"; + mediatek,platform = <&afe>; + aux-devs = <&speaker_amp>; + audio-routing = + "Speaker Amp INL", "HP Driver", + "Speaker Amp INR", "HP Driver", + "Speaker", "Speaker Amp OUTL", + "Speaker", "Speaker Amp OUTR"; + hp-det-gpios = <&pio 142 GPIO_ACTIVE_HIGH>; + }; +}; &pwm { pinctrl-0 = <&bl_pwm_pins>; @@ -293,6 +315,13 @@ }; }; + speaker_amplifier_pins: speaker-amplifier-pins { + pins { + pinmux = ; + output-low; + }; + }; + pmic_int_pins: pmic-int-pins { pins { pinmux = ; From 9606c76dd5050b8f6db8937e1d913ffa7e5c2543 Mon Sep 17 00:00:00 2001 From: Custom Firmware Date: Tue, 9 Jun 2026 01:09:03 +0300 Subject: [PATCH 11/13] README: document initial audio support Signed-off-by: Custom Firmware --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2d4c2bb29ecff3..84fc28dbebf29b 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ | power key: mediatek,mt6323-keys | 🟢 OK | 🟢 OK | 🔴 TBD | 🟢 OK | | haptics: regulator-haptic | 🟢 OK | 🔴 TBD | 🔴 TBD | 🟢 OK | | charger | 🔴 DEAD | 🔴 DEAD | 🔴 DEAD | 🔴 DEAD | +| audio (playback + headphone jack) | 🔴 TBD | 🟡 partial: playback + jack + speaker, no capture | 🔴 TBD | 🟡 partial: playback + jack + speaker, no capture | ### Per-device components | | JTY D101 | Lenovo A369i | Energy Phone Colors | Prestigio PAP5500 DUO | @@ -48,7 +49,7 @@ all of these need upstreaming | fhctl | subset of apmixed iirc | 🔴 DEAD | not sure if we really need it | | mmsys | mediatek,mt6572-mmsys | 🟡 partial | some dbi clocks from cg1 are missing | | mfgcfg | mediatek,mt6572-mfgcfg | 🟢 OK | | -| audio | | 🔴 DEAD | not needed for now, can't test | +| audio | mediatek,mt6572-audsys | 🟡 partial | used as a syscon; the AFE driver maps the regs directly, no dedicated clock driver yet | ### Pinctrl missing emmc r1r0 pins, needs upstreaming @@ -146,7 +147,13 @@ mostly useless i'd say, they're not full hw engines but rather only parts of the not existent in the upstream ### Audio -needs afe/i2s/etc drivers +| component | driver / binding | status | note | +|--------------------|------------------------------|---------|---------------------------------------| +| AFE (DL1 playback) | mediatek,mt6572-audio | 🟢 OK | DL1 playback front-end; no UL/capture | +| sound card | mediatek,mt6572-mt6323-sound | 🟢 OK | machine: routing, jack, speaker amp | +| analog codec | mediatek,mt6323-sound | 🟢 OK | MT6323 PMIC: DAC, headphone, volume | +| capture (mic) | | 🔴 DEAD | | +| i2s | | 🔴 DEAD | | ### Pericfg it seems to be clock + reset controller for NAND and USB From d98243b9e9e56e5567504edca9b37345f4c0bcb4 Mon Sep 17 00:00:00 2001 From: Custom Firmware Date: Thu, 18 Jun 2026 21:17:46 +0300 Subject: [PATCH 12/13] pinctrl: mediatek: mt6572: map GPIO136 to EINT2 GPIO136 is the Lenovo A369i headphone-jack plug-detect line; give it its EINT2 mapping so gpiod_to_irq() works. Signed-off-by: Custom Firmware --- drivers/pinctrl/mediatek/pinctrl-mtk-mt6572.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pinctrl/mediatek/pinctrl-mtk-mt6572.h b/drivers/pinctrl/mediatek/pinctrl-mtk-mt6572.h index 872e6ca6d16051..5b5d4612eb5b22 100644 --- a/drivers/pinctrl/mediatek/pinctrl-mtk-mt6572.h +++ b/drivers/pinctrl/mediatek/pinctrl-mtk-mt6572.h @@ -1284,7 +1284,7 @@ static const struct mtk_pin_desc mtk_pins_mt6572[] = { ), MTK_PIN( 136, "GPIO136", - MTK_EINT_FUNCTION(NO_EINT_SUPPORT, NO_EINT_SUPPORT), + MTK_EINT_FUNCTION(4, 2), DRV_GRP0, MTK_FUNCTION(0, "GPIO136"), MTK_FUNCTION(1, "BPI_BUS6"), From cfac629826d448baf99d67795cc1172d0a9271ce Mon Sep 17 00:00:00 2001 From: Custom Firmware Date: Thu, 18 Jun 2026 21:17:47 +0300 Subject: [PATCH 13/13] ARM: dts: mediatek: lenovo-a369i: wire up audio Add the MT6572 AFE + MT6323 sound card with the internal class-D speaker and headphone-jack detection on GPIO136. Signed-off-by: Custom Firmware --- arch/arm/boot/dts/mediatek/mt6572-lenovo-a369i.dts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/arch/arm/boot/dts/mediatek/mt6572-lenovo-a369i.dts b/arch/arm/boot/dts/mediatek/mt6572-lenovo-a369i.dts index 1b8d726561af69..110456a31f3fe0 100644 --- a/arch/arm/boot/dts/mediatek/mt6572-lenovo-a369i.dts +++ b/arch/arm/boot/dts/mediatek/mt6572-lenovo-a369i.dts @@ -60,6 +60,13 @@ min-microvolt = <1200000>; max-microvolt = <3300000>; }; + + sound { + compatible = "mediatek,mt6572-mt6323-sound"; + mediatek,platform = <&afe>; + audio-routing = "Speaker", "Speaker Driver"; + hp-det-gpios = <&pio 136 GPIO_ACTIVE_LOW>; + }; }; &bls {