LinuxPPC-Dev Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: Chancel Liu <chancel.liu@oss.nxp.com>
To: shengjiu.wang@gmail.com, Xiubo.Lee@gmail.com, festevam@gmail.com,
	nicoleotsuka@gmail.com, lgirdwood@gmail.com, broonie@kernel.org,
	perex@perex.cz, tiwai@suse.com
Cc: hui.wang@canonical.com, linuxppc-dev@lists.ozlabs.org,
	linux-sound@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH 3/3] ASoC: fsl-asoc-card: Move bound-component setup to late_probe
Date: Mon, 10 Aug 2026 18:35:31 +0900	[thread overview]
Message-ID: <20260810093834.1511749-4-chancel.liu@oss.nxp.com> (raw)
In-Reply-To: <20260810093834.1511749-1-chancel.liu@oss.nxp.com>

From: Chancel Liu <chancel.liu@nxp.com>

Move all operations that require bound codec and CPU DAI components out
of probe() and into late_probe(), which is the correct place for them
now that ASoC supports deferrable card binding. late_probe() may be
called multiple times after an unbind/rebind cycle, so every
initialization step is guarded accordingly.

Three new helpers are introduced:
- fsl_asoc_card_init_cpu()
  CPU DAI-specific setup. Previously done in probe() while CPU DAI
  component maybe not ready.

- fsl_asoc_card_init_codecs()
  Reads codec MCLK rates from the bound component devices, invokes the
  per-compatible pdata->codec_init callback if present.

- fsl_asoc_card_init_jack()
  Registers headphone and microphone jacks.

The call site of codec_init callbacks moves from probe() to
fsl_asoc_card_init_codecs(), which runs in late_probe() after the bound
codec device is known. This makes sure codecs can get proper MCLK.

The old card-name fallback depended on codec_dev_name[], which required
looking up the codec device in probe(). This is no longer valid under
deferrable card binding because the codec component may not have probed
yet. Since the DT binding requires "model", remove the fallback and fail
with a clear error.

Assisted-by: VeroCoder:claude-sonnet-4-6
Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
---
 sound/soc/fsl/fsl-asoc-card.c | 324 ++++++++++++++++++++--------------
 1 file changed, 191 insertions(+), 133 deletions(-)

diff --git a/sound/soc/fsl/fsl-asoc-card.c b/sound/soc/fsl/fsl-asoc-card.c
index 10e432c916b8..3532bf6d507e 100644
--- a/sound/soc/fsl/fsl-asoc-card.c
+++ b/sound/soc/fsl/fsl-asoc-card.c
@@ -658,8 +658,14 @@ static int fsl_asoc_card_cs42888_codec_init(struct fsl_asoc_card_priv *priv)
 {
 	unsigned long mclk_freq = priv->codec_priv[0].mclk_freq;
 
-	priv->cpu_priv.sysclk_freq[TX] = mclk_freq;
-	priv->cpu_priv.sysclk_freq[RX] = mclk_freq;
+	/*
+	 * Set CPU sysclk frequency from codec MCLK only if not already
+	 * set by the CPU DAI init (e.g. ESAI extal clock takes precedence).
+	 */
+	if (!priv->cpu_priv.sysclk_freq[TX])
+		priv->cpu_priv.sysclk_freq[TX] = mclk_freq;
+	if (!priv->cpu_priv.sysclk_freq[RX])
+		priv->cpu_priv.sysclk_freq[RX] = mclk_freq;
 
 	priv->constraint_channels = &cs42888_channel_constraints;
 	if (mclk_freq % 12288000 == 0)
@@ -868,17 +874,173 @@ static struct notifier_block mic_jack_nb = {
 	.notifier_call = mic_jack_event,
 };
 
-static int fsl_asoc_card_late_probe(struct snd_soc_card *card)
+/*
+ * fsl_asoc_card_init_cpu - configure CPU DAI-specific settings.
+ *
+ * Called from late_probe() when the CPU DAI component is guaranteed bound.
+ */
+static int fsl_asoc_card_init_cpu(struct snd_soc_card *card,
+				  struct snd_soc_pcm_runtime *rtd)
+{
+	struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card);
+	struct device_node *np = priv->pdev->dev.of_node;
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+	const char *comp_drv_name = cpu_dai->component->driver->name;
+	struct device *dev = card->dev;
+	int ret;
+
+	if (!strcmp(comp_drv_name, "fsl-ssi")) {
+		/* Only SSI needs to configure AUDMUX */
+		ret = fsl_asoc_card_audmux_init(np, priv);
+		if (ret) {
+			dev_err(dev, "failed to init audmux\n");
+			return ret;
+		}
+	} else if (!strcmp(comp_drv_name, "fsl-esai")) {
+		struct clk *esai_clk = clk_get(cpu_dai->dev, "extal");
+
+		if (!IS_ERR(esai_clk)) {
+			priv->cpu_priv.sysclk_freq[TX] = clk_get_rate(esai_clk);
+			priv->cpu_priv.sysclk_freq[RX] = clk_get_rate(esai_clk);
+			clk_put(esai_clk);
+		} else {
+			dev_warn(dev, "failed to get ESAI extal clock: %ld\n", PTR_ERR(esai_clk));
+		}
+
+		priv->cpu_priv.sysclk_id[TX] = ESAI_HCKT_EXTAL;
+		priv->cpu_priv.sysclk_id[RX] = ESAI_HCKR_EXTAL;
+	} else if (!strcmp(comp_drv_name, "fsl-sai")) {
+		priv->cpu_priv.sysclk_id[TX] = FSL_SAI_CLK_MAST1;
+		priv->cpu_priv.sysclk_id[RX] = FSL_SAI_CLK_MAST1;
+
+		if (priv->pdata->exclude_format)
+			priv->exclude_format = priv->pdata->exclude_format;
+	}
+
+	return 0;
+}
+
+/*
+ * fsl_asoc_card_init_codecs - read codec MCLK rates and set codec sysclk.
+ *
+ * Called from late_probe() after all components are bound.
+ */
+static int fsl_asoc_card_init_codecs(struct snd_soc_card *card,
+				     struct snd_soc_pcm_runtime *rtd)
 {
 	struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card);
-	struct snd_soc_pcm_runtime *rtd = list_first_entry(
-			&card->rtd_list, struct snd_soc_pcm_runtime, list);
+	const struct fsl_asoc_card_pdata *pdata = priv->pdata;
 	struct snd_soc_dai *codec_dai;
 	struct codec_priv *codec_priv;
 	struct device *dev = card->dev;
 	int codec_idx;
 	int ret;
 
+	/* Read MCLK rate from each bound codec component */
+	for_each_rtd_codec_dais(rtd, codec_idx, codec_dai) {
+		struct clk *codec_clk = clk_get(codec_dai->component->dev, NULL);
+
+		codec_priv = &priv->codec_priv[codec_idx];
+		if (!IS_ERR(codec_clk)) {
+			codec_priv->mclk_freq = clk_get_rate(codec_clk);
+			clk_put(codec_clk);
+		}
+	}
+
+	if (pdata->codec_init) {
+		ret = pdata->codec_init(priv);
+		if (ret)
+			return ret;
+	}
+
+	for_each_rtd_codec_dais(rtd, codec_idx, codec_dai) {
+		codec_priv = &priv->codec_priv[codec_idx];
+
+		ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id,
+					     codec_priv->mclk_freq, SND_SOC_CLOCK_IN);
+		if (ret && ret != -ENOTSUPP) {
+			dev_err(dev, "failed to set sysclk in %s\n", __func__);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static void fsl_asoc_card_free_jack(struct snd_soc_card *card)
+{
+	struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card);
+
+	if (priv->hp_jack.gpio.desc) {
+		snd_soc_jack_notifier_unregister(&priv->hp_jack.jack, &hp_jack_nb);
+		snd_soc_jack_free_gpios(&priv->hp_jack.jack, 1, &priv->hp_jack.gpio);
+		priv->hp_jack.gpio.desc = NULL;
+	}
+
+	if (priv->mic_jack.gpio.desc) {
+		snd_soc_jack_notifier_unregister(&priv->mic_jack.jack, &mic_jack_nb);
+		snd_soc_jack_free_gpios(&priv->mic_jack.jack, 1, &priv->mic_jack.gpio);
+		priv->mic_jack.gpio.desc = NULL;
+	}
+}
+
+/*
+ * fsl_asoc_card_init_jack - register optional headphone and mic jacks.
+ *
+ * Called from late_probe() once per card bind cycle.
+ */
+static int fsl_asoc_card_init_jack(struct snd_soc_card *card)
+{
+	struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card);
+	struct device_node *np = priv->pdev->dev.of_node;
+	int ret;
+
+	/*
+	 * Properties "hp-det-gpios" and "mic-det-gpios" are optional.
+	 * simple_util_init_jack() checks for the GPIO property and
+	 * does nothing if it is absent.
+	 */
+	if (of_property_present(np, "hp-det-gpios") ||
+	    of_property_present(np, "hp-det-gpio") /* deprecated */) {
+		ret = simple_util_init_jack(card, &priv->hp_jack,
+					    1, NULL, "Headphone Jack");
+		if (ret)
+			return ret;
+
+		snd_soc_jack_notifier_register(&priv->hp_jack.jack, &hp_jack_nb);
+	}
+
+	if (of_property_present(np, "mic-det-gpios") ||
+	    of_property_present(np, "mic-det-gpio") /* deprecated */) {
+		ret = simple_util_init_jack(card, &priv->mic_jack,
+					    0, NULL, "Mic Jack");
+		if (ret)
+			return ret;
+
+		snd_soc_jack_notifier_register(&priv->mic_jack.jack, &mic_jack_nb);
+	}
+
+	return 0;
+}
+
+static int fsl_asoc_card_late_probe(struct snd_soc_card *card)
+{
+	struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card);
+	struct snd_soc_pcm_runtime *rtd;
+	int ret;
+
+	/* Use the first rtd which carries the CPU+codec DAIs */
+	rtd = list_first_entry(&card->rtd_list,
+			       struct snd_soc_pcm_runtime, list);
+
+	ret = fsl_asoc_card_init_jack(card);
+	if (ret)
+		goto jack_fail;
+
+	ret = fsl_asoc_card_init_cpu(card, rtd);
+	if (ret)
+		goto jack_fail;
+
 	if (fsl_asoc_card_is_ac97(priv)) {
 #if IS_ENABLED(CONFIG_SND_AC97_CODEC)
 		struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
@@ -896,16 +1058,20 @@ static int fsl_asoc_card_late_probe(struct snd_soc_card *card)
 		return 0;
 	}
 
-	for_each_rtd_codec_dais(rtd, codec_idx, codec_dai) {
-		codec_priv = &priv->codec_priv[codec_idx];
+	ret = fsl_asoc_card_init_codecs(card, rtd);
+	if (ret)
+		goto jack_fail;
 
-		ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id,
-					codec_priv->mclk_freq, SND_SOC_CLOCK_IN);
-		if (ret && ret != -ENOTSUPP) {
-			dev_err(dev, "failed to set sysclk in %s\n", __func__);
-			return ret;
-		}
-	}
+	return 0;
+
+jack_fail:
+	fsl_asoc_card_free_jack(card);
+	return ret;
+}
+
+static int fsl_asoc_card_card_remove(struct snd_soc_card *card)
+{
+	fsl_asoc_card_free_jack(card);
 
 	return 0;
 }
@@ -919,13 +1085,10 @@ static int fsl_asoc_card_probe(struct platform_device *pdev)
 	struct platform_device *asrc_pdev = NULL;
 	struct device_node *bitclkprovider = NULL;
 	struct device_node *frameprovider = NULL;
-	struct platform_device *cpu_pdev;
 	struct fsl_asoc_card_priv *priv;
 	const struct fsl_asoc_card_pdata *pdata;
-	struct device *codec_dev[2] = { NULL, NULL };
 	struct snd_soc_dai_link_component *dlc;
-	const char *codec_dai_name[2];
-	const char *codec_dev_name[2];
+	const char *codec_dai_name[2] = { NULL, NULL };
 	u32 asrc_fmt = 0;
 	int codec_idx;
 	u32 width;
@@ -938,8 +1101,10 @@ static int fsl_asoc_card_probe(struct platform_device *pdev)
 	priv->pdev = pdev;
 
 	pdata = of_device_get_match_data(&pdev->dev);
-	if (!pdata)
+	if (!pdata) {
+		dev_err(&pdev->dev, "unknown Device Tree compatible\n");
 		return -EINVAL;
+	}
 	priv->pdata = pdata;
 
 	cpu_np = of_parse_phandle(np, "audio-cpu", 0);
@@ -954,58 +1119,13 @@ static int fsl_asoc_card_probe(struct platform_device *pdev)
 		goto fail;
 	}
 
-	cpu_pdev = of_find_device_by_node(cpu_np);
-	if (!cpu_pdev) {
-		dev_err(&pdev->dev, "failed to find CPU DAI device\n");
-		ret = -EINVAL;
-		goto fail;
-	}
-
 	codec_np[0] = of_parse_phandle(np, "audio-codec", 0);
 	codec_np[1] = of_parse_phandle(np, "audio-codec", 1);
 
-	for (codec_idx = 0; codec_idx < 2; codec_idx++) {
-		if (codec_np[codec_idx]) {
-			struct platform_device *codec_pdev;
-			struct i2c_client *codec_i2c;
-
-			codec_i2c = of_find_i2c_device_by_node(codec_np[codec_idx]);
-			if (codec_i2c) {
-				codec_dev[codec_idx] = &codec_i2c->dev;
-				codec_dev_name[codec_idx] = codec_i2c->name;
-			}
-			if (!codec_dev[codec_idx]) {
-				codec_pdev = of_find_device_by_node(codec_np[codec_idx]);
-				if (codec_pdev) {
-					codec_dev[codec_idx] = &codec_pdev->dev;
-					codec_dev_name[codec_idx] = codec_pdev->name;
-				}
-			}
-		}
-	}
-
 	asrc_np = of_parse_phandle(np, "audio-asrc", 0);
 	if (asrc_np)
 		asrc_pdev = of_find_device_by_node(asrc_np);
 
-	/* Get the MCLK rate only, and leave it controlled by CODEC drivers */
-	for (codec_idx = 0; codec_idx < 2; codec_idx++) {
-		if (codec_dev[codec_idx]) {
-			struct clk *codec_clk = clk_get(codec_dev[codec_idx], NULL);
-
-			if (!IS_ERR(codec_clk)) {
-				priv->codec_priv[codec_idx].mclk_freq = clk_get_rate(codec_clk);
-				clk_put(codec_clk);
-			}
-		}
-	}
-
-	if (pdata->codec_init) {
-		ret = pdata->codec_init(priv);
-		if (ret)
-			goto asrc_fail;
-	}
-
 	/* Default sample rate and format, will be updated in hw_params() */
 	priv->sample_rate = 44100;
 	priv->sample_format = SNDRV_PCM_FORMAT_S16_LE;
@@ -1086,9 +1206,6 @@ static int fsl_asoc_card_probe(struct platform_device *pdev)
 	priv->card.dapm_routes = pdata->dapm_routes;
 	priv->card.num_dapm_routes = pdata->num_dapm_routes;
 
-	if (pdata->exclude_format && of_node_name_eq(cpu_np, "sai"))
-		priv->exclude_format = pdata->exclude_format;
-
 	if (pdata->probe_init) {
 		ret = pdata->probe_init(codec_np, cpu_np,
 					codec_dai_name, priv);
@@ -1139,51 +1256,21 @@ static int fsl_asoc_card_probe(struct platform_device *pdev)
 	of_node_put(bitclkprovider);
 	of_node_put(frameprovider);
 
-	if (!fsl_asoc_card_is_ac97(priv) && !codec_dev[0]
-	    && codec_dai_name[0] != snd_soc_dummy_dlc.dai_name) {
-		dev_dbg(&pdev->dev, "failed to find codec device\n");
-		ret = -EPROBE_DEFER;
-		goto asrc_fail;
-	}
-
-	/* Common settings for corresponding Freescale CPU DAI driver */
-	if (of_node_name_eq(cpu_np, "ssi")) {
-		/* Only SSI needs to configure AUDMUX */
-		ret = fsl_asoc_card_audmux_init(np, priv);
-		if (ret) {
-			dev_err(&pdev->dev, "failed to init audmux\n");
-			goto asrc_fail;
-		}
-	} else if (of_node_name_eq(cpu_np, "esai")) {
-		struct clk *esai_clk = clk_get(&cpu_pdev->dev, "extal");
-
-		if (!IS_ERR(esai_clk)) {
-			priv->cpu_priv.sysclk_freq[TX] = clk_get_rate(esai_clk);
-			priv->cpu_priv.sysclk_freq[RX] = clk_get_rate(esai_clk);
-			clk_put(esai_clk);
-		} else if (PTR_ERR(esai_clk) == -EPROBE_DEFER) {
-			ret = -EPROBE_DEFER;
-			goto asrc_fail;
-		}
-
-		priv->cpu_priv.sysclk_id[1] = ESAI_HCKT_EXTAL;
-		priv->cpu_priv.sysclk_id[0] = ESAI_HCKR_EXTAL;
-	} else if (of_node_name_eq(cpu_np, "sai")) {
-		priv->cpu_priv.sysclk_id[1] = FSL_SAI_CLK_MAST1;
-		priv->cpu_priv.sysclk_id[0] = FSL_SAI_CLK_MAST1;
-	}
-
 	/* Initialize sound card */
 	priv->card.dev = &pdev->dev;
 	priv->card.owner = THIS_MODULE;
 	ret = snd_soc_of_parse_card_name(&priv->card, "model");
 	if (ret) {
-		snprintf(priv->name, sizeof(priv->name), "%s-audio",
-			 fsl_asoc_card_is_ac97(priv) ? "ac97" : codec_dev_name[0]);
-		priv->card.name = priv->name;
+		/*
+		 * "model" is required by the DT binding. Enforce it here so
+		 * the driver fails with a clear message.
+		 */
+		dev_err(&pdev->dev, "Error parsing card name: %d\n", ret);
+		goto asrc_fail;
 	}
 	priv->card.dai_link = priv->dai_link;
 	priv->card.late_probe = fsl_asoc_card_late_probe;
+	priv->card.remove = fsl_asoc_card_card_remove;
 	priv->card.dapm_widgets = fsl_asoc_card_dapm_widgets;
 	priv->card.num_dapm_widgets = ARRAY_SIZE(fsl_asoc_card_dapm_widgets);
 
@@ -1290,39 +1377,10 @@ static int fsl_asoc_card_probe(struct platform_device *pdev)
 		goto asrc_fail;
 	}
 
-	/*
-	 * Properties "hp-det-gpios" and "mic-det-gpios" are optional, and
-	 * simple_util_init_jack() uses these properties for creating
-	 * Headphone Jack and Microphone Jack.
-	 *
-	 * The notifier is initialized in snd_soc_card_jack_new(), then
-	 * snd_soc_jack_notifier_register can be called.
-	 */
-	if (of_property_present(np, "hp-det-gpios") ||
-	    of_property_present(np, "hp-det-gpio") /* deprecated */) {
-		ret = simple_util_init_jack(&priv->card, &priv->hp_jack,
-					    1, NULL, "Headphone Jack");
-		if (ret)
-			goto asrc_fail;
-
-		snd_soc_jack_notifier_register(&priv->hp_jack.jack, &hp_jack_nb);
-	}
-
-	if (of_property_present(np, "mic-det-gpios") ||
-	    of_property_present(np, "mic-det-gpio") /* deprecated */) {
-		ret = simple_util_init_jack(&priv->card, &priv->mic_jack,
-					    0, NULL, "Mic Jack");
-		if (ret)
-			goto asrc_fail;
-
-		snd_soc_jack_notifier_register(&priv->mic_jack.jack, &mic_jack_nb);
-	}
-
 asrc_fail:
 	of_node_put(asrc_np);
 	of_node_put(codec_np[0]);
 	of_node_put(codec_np[1]);
-	put_device(&cpu_pdev->dev);
 fail:
 	of_node_put(cpu_np);
 
-- 
2.50.1



      parent reply	other threads:[~2026-08-10  9:39 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-10  9:35 [PATCH 0/3] ASoC: fsl-asoc-card: Restructure to support deferrable card binding Chancel Liu
2026-08-10  9:35 ` [PATCH 1/3] ASoC: fsl-asoc-card: Drop mclk management for nau8822 Chancel Liu
2026-08-10  9:35 ` [PATCH 2/3] ASoC: fsl-asoc-card: Move static compatible data to platform data Chancel Liu
2026-08-10  9:35 ` Chancel Liu [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260810093834.1511749-4-chancel.liu@oss.nxp.com \
    --to=chancel.liu@oss.nxp.com \
    --cc=Xiubo.Lee@gmail.com \
    --cc=broonie@kernel.org \
    --cc=festevam@gmail.com \
    --cc=hui.wang@canonical.com \
    --cc=lgirdwood@gmail.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-sound@vger.kernel.org \
    --cc=linuxppc-dev@lists.ozlabs.org \
    --cc=nicoleotsuka@gmail.com \
    --cc=perex@perex.cz \
    --cc=shengjiu.wang@gmail.com \
    --cc=tiwai@suse.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox