* [PATCH v2 01/21] ASoC: codecs: cpcap: Fix devm resource leaks across card bind/unbind
2026-09-23 6:12 [PATCH v2 00/21] ASoC: codecs: Fix resource leaks across card bind/unbind Chancel Liu
@ 2026-09-23 6:12 ` Chancel Liu
2026-09-23 6:12 ` [PATCH v2 02/21] ASoC: codecs: da7218: " Chancel Liu
` (20 subsequent siblings)
21 siblings, 0 replies; 28+ messages in thread
From: Chancel Liu @ 2026-09-23 6:12 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Oder Chiou, Cezary Rojewski
Cc: Support Opensource, linux-sound, linux-kernel, patches
From: Chancel Liu <chancel.liu@nxp.com>
The ASoC component .probe/.remove are called on card bind/unbind, but
component->dev is the underlying platform device. Resources requested
with devm_*(component->dev, ...) or request_irq() in the component
.probe are only released when the platform device is removed, not on
card unbind, leaking on every bind/unbind cycle.
The VAUDIO regulator is a pure hardware resource that only depends on
the platform device, so acquire it with devm_regulator_get() in the
platform probe where its devres lifetime matches the device.
The headset/mic-button IRQs genuinely depend on the component, so
keep them in cpcap_soc_probe() but request them with the non-devm
request_irq() API and free them explicitly in cpcap_soc_remove().
Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
---
sound/soc/codecs/cpcap.c | 74 ++++++++++++++++++++++++----------------
1 file changed, 45 insertions(+), 29 deletions(-)
diff --git a/sound/soc/codecs/cpcap.c b/sound/soc/codecs/cpcap.c
index 0262900fbb7d..dda68165b817 100644
--- a/sound/soc/codecs/cpcap.c
+++ b/sound/soc/codecs/cpcap.c
@@ -1622,21 +1622,12 @@ static int cpcap_soc_probe(struct snd_soc_component *component)
{
struct platform_device *pdev = to_platform_device(component->dev);
struct snd_soc_card *card = component->card;
- struct cpcap_audio *cpcap;
+ struct cpcap_audio *cpcap = dev_get_drvdata(component->dev);
int err;
- cpcap = devm_kzalloc(component->dev, sizeof(*cpcap), GFP_KERNEL);
- if (!cpcap)
- return -ENOMEM;
-
snd_soc_component_set_drvdata(component, cpcap);
cpcap->component = component;
- cpcap->vaudio = devm_regulator_get(component->dev, "VAUDIO");
- if (IS_ERR(cpcap->vaudio))
- return dev_err_probe(component->dev, PTR_ERR(cpcap->vaudio),
- "Cannot get VAUDIO regulator\n");
-
err = snd_soc_card_jack_new(card, "Headphones",
SND_JACK_HEADSET | SND_JACK_BTN_0,
&cpcap->jack);
@@ -1660,13 +1651,13 @@ static int cpcap_soc_probe(struct snd_soc_component *component)
if (cpcap->hsirq < 0)
return cpcap->hsirq;
- err = devm_request_threaded_irq(component->dev, cpcap->hsirq, NULL,
- cpcap_hs_irq_thread,
- IRQF_TRIGGER_RISING |
- IRQF_TRIGGER_FALLING |
- IRQF_ONESHOT,
- "cpcap-codec-hs",
- component);
+ err = request_threaded_irq(cpcap->hsirq, NULL,
+ cpcap_hs_irq_thread,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
+ "cpcap-codec-hs",
+ component);
if (err) {
dev_warn(component->dev, "no HS irq%i: %i\n",
cpcap->hsirq, err);
@@ -1674,25 +1665,27 @@ static int cpcap_soc_probe(struct snd_soc_component *component)
}
cpcap->mb2irq = platform_get_irq_byname(pdev, "mb2");
- if (cpcap->mb2irq < 0)
- return cpcap->mb2irq;
-
- err = devm_request_threaded_irq(component->dev, cpcap->mb2irq, NULL,
- cpcap_mb2_irq_thread,
- IRQF_TRIGGER_RISING |
- IRQF_TRIGGER_FALLING |
- IRQF_ONESHOT,
- "cpcap-codec-mb2",
- component);
+ if (cpcap->mb2irq < 0) {
+ err = cpcap->mb2irq;
+ goto err_free_hsirq;
+ }
+
+ err = request_threaded_irq(cpcap->mb2irq, NULL,
+ cpcap_mb2_irq_thread,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
+ "cpcap-codec-mb2",
+ component);
if (err) {
dev_warn(component->dev, "no MB2 irq%i: %i\n",
cpcap->mb2irq, err);
- return err;
+ goto err_free_hsirq;
}
err = cpcap_audio_reset(component, false);
if (err)
- return err;
+ goto err_free_mb2irq;
cpcap_hs_irq_thread(cpcap->hsirq, component);
@@ -1700,6 +1693,13 @@ static int cpcap_soc_probe(struct snd_soc_component *component)
enable_irq_wake(cpcap->mb2irq);
return 0;
+
+err_free_mb2irq:
+ free_irq(cpcap->mb2irq, component);
+err_free_hsirq:
+ free_irq(cpcap->hsirq, component);
+
+ return err;
}
static void cpcap_soc_remove(struct snd_soc_component *component)
@@ -1708,6 +1708,9 @@ static void cpcap_soc_remove(struct snd_soc_component *component)
disable_irq_wake(cpcap->hsirq);
disable_irq_wake(cpcap->mb2irq);
+
+ free_irq(cpcap->mb2irq, component);
+ free_irq(cpcap->hsirq, component);
}
static int cpcap_set_bias_level(struct snd_soc_component *component,
@@ -1754,11 +1757,24 @@ static int cpcap_codec_probe(struct platform_device *pdev)
{
struct device_node *codec_node =
of_get_child_by_name(pdev->dev.parent->of_node, "audio-codec");
+ struct cpcap_audio *cpcap;
+
if (!codec_node)
return -ENODEV;
pdev->dev.of_node = codec_node;
+ cpcap = devm_kzalloc(&pdev->dev, sizeof(*cpcap), GFP_KERNEL);
+ if (!cpcap)
+ return -ENOMEM;
+
+ cpcap->vaudio = devm_regulator_get(&pdev->dev, "VAUDIO");
+ if (IS_ERR(cpcap->vaudio))
+ return dev_err_probe(&pdev->dev, PTR_ERR(cpcap->vaudio),
+ "Cannot get VAUDIO regulator\n");
+
+ platform_set_drvdata(pdev, cpcap);
+
return devm_snd_soc_register_component(&pdev->dev, &soc_codec_dev_cpcap,
cpcap_dai, ARRAY_SIZE(cpcap_dai));
}
--
2.50.1
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v2 02/21] ASoC: codecs: da7218: Fix devm resource leaks across card bind/unbind
2026-09-23 6:12 [PATCH v2 00/21] ASoC: codecs: Fix resource leaks across card bind/unbind Chancel Liu
2026-09-23 6:12 ` [PATCH v2 01/21] ASoC: codecs: cpcap: Fix devm " Chancel Liu
@ 2026-09-23 6:12 ` Chancel Liu
2026-09-23 6:12 ` [PATCH v2 03/21] ASoC: codecs: tlv320aic32x4: Fix clock leak from runtime callbacks Chancel Liu
` (19 subsequent siblings)
21 siblings, 0 replies; 28+ messages in thread
From: Chancel Liu @ 2026-09-23 6:12 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Oder Chiou, Cezary Rojewski
Cc: Support Opensource, linux-sound, linux-kernel, patches
From: Chancel Liu <chancel.liu@nxp.com>
component->dev is the underlying I2C device, so resources requested
with devm_*(component->dev, ...) in the component .probe are only
released when the I2C device is removed, not on card unbind. This
leaks the mclk clock, the regulator bulk, the platform data parsed
from the device tree and the IRQ on every card bind/unbind cycle.
Move the mclk clock and the regulator supplies (pure hardware
resources tied to the I2C device) to da7218_i2c_probe() using devm on
&i2c->dev. Likewise move the device tree parsing (da7218_of_to_pdata()),
which also allocates with devm, to da7218_i2c_probe() and store the
result in da7218->pdata; the OF helpers now take a struct device so
they no longer depend on the component. Keep the IRQ in the component
.probe (it feeds the component) but request it with the non-devm API
and free it in da7218_remove().
Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
---
sound/soc/codecs/da7218.c | 129 +++++++++++++++++++-------------------
1 file changed, 65 insertions(+), 64 deletions(-)
diff --git a/sound/soc/codecs/da7218.c b/sound/soc/codecs/da7218.c
index 361daf14152e..a538b3dd1635 100644
--- a/sound/soc/codecs/da7218.c
+++ b/sound/soc/codecs/da7218.c
@@ -2298,7 +2298,7 @@ static const struct of_device_id da7218_of_match[] = {
MODULE_DEVICE_TABLE(of, da7218_of_match);
static enum da7218_micbias_voltage
- da7218_of_micbias_lvl(struct snd_soc_component *component, u32 val)
+ da7218_of_micbias_lvl(struct device *dev, u32 val)
{
switch (val) {
case 1200:
@@ -2320,13 +2320,13 @@ static enum da7218_micbias_voltage
case 3000:
return DA7218_MICBIAS_3_0V;
default:
- dev_warn(component->dev, "Invalid micbias level");
+ dev_warn(dev, "Invalid micbias level");
return DA7218_MICBIAS_1_6V;
}
}
static enum da7218_mic_amp_in_sel
- da7218_of_mic_amp_in_sel(struct snd_soc_component *component, const char *str)
+ da7218_of_mic_amp_in_sel(struct device *dev, const char *str)
{
if (!strcmp(str, "diff")) {
return DA7218_MIC_AMP_IN_SEL_DIFF;
@@ -2335,39 +2335,39 @@ static enum da7218_mic_amp_in_sel
} else if (!strcmp(str, "se_n")) {
return DA7218_MIC_AMP_IN_SEL_SE_N;
} else {
- dev_warn(component->dev, "Invalid mic input type selection");
+ dev_warn(dev, "Invalid mic input type selection");
return DA7218_MIC_AMP_IN_SEL_DIFF;
}
}
static enum da7218_dmic_data_sel
- da7218_of_dmic_data_sel(struct snd_soc_component *component, const char *str)
+ da7218_of_dmic_data_sel(struct device *dev, const char *str)
{
if (!strcmp(str, "lrise_rfall")) {
return DA7218_DMIC_DATA_LRISE_RFALL;
} else if (!strcmp(str, "lfall_rrise")) {
return DA7218_DMIC_DATA_LFALL_RRISE;
} else {
- dev_warn(component->dev, "Invalid DMIC data type selection");
+ dev_warn(dev, "Invalid DMIC data type selection");
return DA7218_DMIC_DATA_LRISE_RFALL;
}
}
static enum da7218_dmic_samplephase
- da7218_of_dmic_samplephase(struct snd_soc_component *component, const char *str)
+ da7218_of_dmic_samplephase(struct device *dev, const char *str)
{
if (!strcmp(str, "on_clkedge")) {
return DA7218_DMIC_SAMPLE_ON_CLKEDGE;
} else if (!strcmp(str, "between_clkedge")) {
return DA7218_DMIC_SAMPLE_BETWEEN_CLKEDGE;
} else {
- dev_warn(component->dev, "Invalid DMIC sample phase");
+ dev_warn(dev, "Invalid DMIC sample phase");
return DA7218_DMIC_SAMPLE_ON_CLKEDGE;
}
}
static enum da7218_dmic_clk_rate
- da7218_of_dmic_clkrate(struct snd_soc_component *component, u32 val)
+ da7218_of_dmic_clkrate(struct device *dev, u32 val)
{
switch (val) {
case 1500000:
@@ -2375,13 +2375,13 @@ static enum da7218_dmic_clk_rate
case 3000000:
return DA7218_DMIC_CLK_3_0MHZ;
default:
- dev_warn(component->dev, "Invalid DMIC clock rate");
+ dev_warn(dev, "Invalid DMIC clock rate");
return DA7218_DMIC_CLK_3_0MHZ;
}
}
static enum da7218_hpldet_jack_rate
- da7218_of_jack_rate(struct snd_soc_component *component, u32 val)
+ da7218_of_jack_rate(struct device *dev, u32 val)
{
switch (val) {
case 5:
@@ -2401,13 +2401,13 @@ static enum da7218_hpldet_jack_rate
case 640:
return DA7218_HPLDET_JACK_RATE_640US;
default:
- dev_warn(component->dev, "Invalid jack detect rate");
+ dev_warn(dev, "Invalid jack detect rate");
return DA7218_HPLDET_JACK_RATE_40US;
}
}
static enum da7218_hpldet_jack_debounce
- da7218_of_jack_debounce(struct snd_soc_component *component, u32 val)
+ da7218_of_jack_debounce(struct device *dev, u32 val)
{
switch (val) {
case 0:
@@ -2419,13 +2419,13 @@ static enum da7218_hpldet_jack_debounce
case 4:
return DA7218_HPLDET_JACK_DEBOUNCE_4;
default:
- dev_warn(component->dev, "Invalid jack debounce");
+ dev_warn(dev, "Invalid jack debounce");
return DA7218_HPLDET_JACK_DEBOUNCE_2;
}
}
static enum da7218_hpldet_jack_thr
- da7218_of_jack_thr(struct snd_soc_component *component, u32 val)
+ da7218_of_jack_thr(struct device *dev, u32 val)
{
switch (val) {
case 84:
@@ -2437,76 +2437,76 @@ static enum da7218_hpldet_jack_thr
case 96:
return DA7218_HPLDET_JACK_THR_96PCT;
default:
- dev_warn(component->dev, "Invalid jack threshold level");
+ dev_warn(dev, "Invalid jack threshold level");
return DA7218_HPLDET_JACK_THR_84PCT;
}
}
-static struct da7218_pdata *da7218_of_to_pdata(struct snd_soc_component *component)
+static struct da7218_pdata *da7218_of_to_pdata(struct device *dev,
+ struct da7218_priv *da7218)
{
- struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component);
- struct device_node *np = component->dev->of_node;
+ struct device_node *np = dev->of_node;
struct device_node *hpldet_np;
struct da7218_pdata *pdata;
struct da7218_hpldet_pdata *hpldet_pdata;
const char *of_str;
u32 of_val32;
- pdata = devm_kzalloc(component->dev, sizeof(*pdata), GFP_KERNEL);
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return NULL;
if (of_property_read_u32(np, "dlg,micbias1-lvl-millivolt", &of_val32) >= 0)
- pdata->micbias1_lvl = da7218_of_micbias_lvl(component, of_val32);
+ pdata->micbias1_lvl = da7218_of_micbias_lvl(dev, of_val32);
else
pdata->micbias1_lvl = DA7218_MICBIAS_1_6V;
if (of_property_read_u32(np, "dlg,micbias2-lvl-millivolt", &of_val32) >= 0)
- pdata->micbias2_lvl = da7218_of_micbias_lvl(component, of_val32);
+ pdata->micbias2_lvl = da7218_of_micbias_lvl(dev, of_val32);
else
pdata->micbias2_lvl = DA7218_MICBIAS_1_6V;
if (!of_property_read_string(np, "dlg,mic1-amp-in-sel", &of_str))
pdata->mic1_amp_in_sel =
- da7218_of_mic_amp_in_sel(component, of_str);
+ da7218_of_mic_amp_in_sel(dev, of_str);
else
pdata->mic1_amp_in_sel = DA7218_MIC_AMP_IN_SEL_DIFF;
if (!of_property_read_string(np, "dlg,mic2-amp-in-sel", &of_str))
pdata->mic2_amp_in_sel =
- da7218_of_mic_amp_in_sel(component, of_str);
+ da7218_of_mic_amp_in_sel(dev, of_str);
else
pdata->mic2_amp_in_sel = DA7218_MIC_AMP_IN_SEL_DIFF;
if (!of_property_read_string(np, "dlg,dmic1-data-sel", &of_str))
- pdata->dmic1_data_sel = da7218_of_dmic_data_sel(component, of_str);
+ pdata->dmic1_data_sel = da7218_of_dmic_data_sel(dev, of_str);
else
pdata->dmic1_data_sel = DA7218_DMIC_DATA_LRISE_RFALL;
if (!of_property_read_string(np, "dlg,dmic1-samplephase", &of_str))
pdata->dmic1_samplephase =
- da7218_of_dmic_samplephase(component, of_str);
+ da7218_of_dmic_samplephase(dev, of_str);
else
pdata->dmic1_samplephase = DA7218_DMIC_SAMPLE_ON_CLKEDGE;
if (of_property_read_u32(np, "dlg,dmic1-clkrate-hz", &of_val32) >= 0)
- pdata->dmic1_clk_rate = da7218_of_dmic_clkrate(component, of_val32);
+ pdata->dmic1_clk_rate = da7218_of_dmic_clkrate(dev, of_val32);
else
pdata->dmic1_clk_rate = DA7218_DMIC_CLK_3_0MHZ;
if (!of_property_read_string(np, "dlg,dmic2-data-sel", &of_str))
- pdata->dmic2_data_sel = da7218_of_dmic_data_sel(component, of_str);
+ pdata->dmic2_data_sel = da7218_of_dmic_data_sel(dev, of_str);
else
pdata->dmic2_data_sel = DA7218_DMIC_DATA_LRISE_RFALL;
if (!of_property_read_string(np, "dlg,dmic2-samplephase", &of_str))
pdata->dmic2_samplephase =
- da7218_of_dmic_samplephase(component, of_str);
+ da7218_of_dmic_samplephase(dev, of_str);
else
pdata->dmic2_samplephase = DA7218_DMIC_SAMPLE_ON_CLKEDGE;
if (of_property_read_u32(np, "dlg,dmic2-clkrate-hz", &of_val32) >= 0)
- pdata->dmic2_clk_rate = da7218_of_dmic_clkrate(component, of_val32);
+ pdata->dmic2_clk_rate = da7218_of_dmic_clkrate(dev, of_val32);
else
pdata->dmic2_clk_rate = DA7218_DMIC_CLK_3_0MHZ;
@@ -2520,7 +2520,7 @@ static struct da7218_pdata *da7218_of_to_pdata(struct snd_soc_component *compone
if (!hpldet_np)
return pdata;
- hpldet_pdata = devm_kzalloc(component->dev, sizeof(*hpldet_pdata),
+ hpldet_pdata = devm_kzalloc(dev, sizeof(*hpldet_pdata),
GFP_KERNEL);
if (!hpldet_pdata) {
of_node_put(hpldet_np);
@@ -2531,14 +2531,14 @@ static struct da7218_pdata *da7218_of_to_pdata(struct snd_soc_component *compone
if (of_property_read_u32(hpldet_np, "dlg,jack-rate-us",
&of_val32) >= 0)
hpldet_pdata->jack_rate =
- da7218_of_jack_rate(component, of_val32);
+ da7218_of_jack_rate(dev, of_val32);
else
hpldet_pdata->jack_rate = DA7218_HPLDET_JACK_RATE_40US;
if (of_property_read_u32(hpldet_np, "dlg,jack-debounce",
&of_val32) >= 0)
hpldet_pdata->jack_debounce =
- da7218_of_jack_debounce(component, of_val32);
+ da7218_of_jack_debounce(dev, of_val32);
else
hpldet_pdata->jack_debounce =
DA7218_HPLDET_JACK_DEBOUNCE_2;
@@ -2546,7 +2546,7 @@ static struct da7218_pdata *da7218_of_to_pdata(struct snd_soc_component *compone
if (of_property_read_u32(hpldet_np, "dlg,jack-threshold-pct",
&of_val32) >= 0)
hpldet_pdata->jack_thr =
- da7218_of_jack_thr(component, of_val32);
+ da7218_of_jack_thr(dev, of_val32);
else
hpldet_pdata->jack_thr = DA7218_HPLDET_JACK_THR_84PCT;
@@ -2638,18 +2638,7 @@ static int da7218_handle_supplies(struct snd_soc_component *component)
struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component);
struct regulator *vddio;
u8 io_voltage_lvl = DA7218_IO_VOLTAGE_LEVEL_2_5V_3_6V;
- int i, ret;
-
- /* Get required supplies */
- for (i = 0; i < DA7218_NUM_SUPPLIES; ++i)
- da7218->supplies[i].supply = da7218_supply_names[i];
-
- ret = devm_regulator_bulk_get(component->dev, DA7218_NUM_SUPPLIES,
- da7218->supplies);
- if (ret) {
- dev_err(component->dev, "Failed to get supplies\n");
- return ret;
- }
+ int ret;
/* Determine VDDIO voltage provided */
vddio = da7218->supplies[DA7218_SUPPLY_VDDIO].consumer;
@@ -2887,21 +2876,8 @@ static int da7218_probe(struct snd_soc_component *component)
if (ret)
return ret;
- /* Handle DT/Platform data */
- if (component->dev->of_node)
- da7218->pdata = da7218_of_to_pdata(component);
- else
- da7218->pdata = dev_get_platdata(component->dev);
-
da7218_handle_pdata(component);
- /* Check if MCLK provided, if not the clock is NULL */
- da7218->mclk = devm_clk_get_optional(component->dev, "mclk");
- if (IS_ERR(da7218->mclk)) {
- ret = PTR_ERR(da7218->mclk);
- goto err_disable_reg;
- }
-
/* Default PC to free-running */
snd_soc_component_write(component, DA7218_PC_COUNT, DA7218_PC_FREERUN_MASK);
@@ -2965,10 +2941,10 @@ static int da7218_probe(struct snd_soc_component *component)
}
if (da7218->irq) {
- ret = devm_request_threaded_irq(component->dev, da7218->irq, NULL,
- da7218_irq_thread,
- IRQF_TRIGGER_LOW | IRQF_ONESHOT,
- "da7218", component);
+ ret = request_threaded_irq(da7218->irq, NULL,
+ da7218_irq_thread,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ "da7218", component);
if (ret != 0) {
dev_err(component->dev, "Failed to request IRQ %d: %d\n",
da7218->irq, ret);
@@ -2989,6 +2965,9 @@ static void da7218_remove(struct snd_soc_component *component)
{
struct da7218_priv *da7218 = snd_soc_component_get_drvdata(component);
+ if (da7218->irq)
+ free_irq(da7218->irq, component);
+
regulator_bulk_disable(DA7218_NUM_SUPPLIES, da7218->supplies);
}
@@ -3259,7 +3238,7 @@ static const struct regmap_config da7218_regmap_config = {
static int da7218_i2c_probe(struct i2c_client *i2c)
{
struct da7218_priv *da7218;
- int ret;
+ int i, ret;
da7218 = devm_kzalloc(&i2c->dev, sizeof(*da7218), GFP_KERNEL);
if (!da7218)
@@ -3284,6 +3263,28 @@ static int da7218_i2c_probe(struct i2c_client *i2c)
return ret;
}
+ /* Get required supplies */
+ for (i = 0; i < DA7218_NUM_SUPPLIES; ++i)
+ da7218->supplies[i].supply = da7218_supply_names[i];
+
+ ret = devm_regulator_bulk_get(&i2c->dev, DA7218_NUM_SUPPLIES,
+ da7218->supplies);
+ if (ret) {
+ dev_err(&i2c->dev, "Failed to get supplies\n");
+ return ret;
+ }
+
+ /* Check if MCLK provided, if not the clock is NULL */
+ da7218->mclk = devm_clk_get_optional(&i2c->dev, "mclk");
+ if (IS_ERR(da7218->mclk))
+ return PTR_ERR(da7218->mclk);
+
+ /* Handle DT/Platform data */
+ if (i2c->dev.of_node)
+ da7218->pdata = da7218_of_to_pdata(&i2c->dev, da7218);
+ else
+ da7218->pdata = dev_get_platdata(&i2c->dev);
+
ret = devm_snd_soc_register_component(&i2c->dev,
&soc_component_dev_da7218, &da7218_dai, 1);
if (ret < 0) {
--
2.50.1
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v2 03/21] ASoC: codecs: tlv320aic32x4: Fix clock leak from runtime callbacks
2026-09-23 6:12 [PATCH v2 00/21] ASoC: codecs: Fix resource leaks across card bind/unbind Chancel Liu
2026-09-23 6:12 ` [PATCH v2 01/21] ASoC: codecs: cpcap: Fix devm " Chancel Liu
2026-09-23 6:12 ` [PATCH v2 02/21] ASoC: codecs: da7218: " Chancel Liu
@ 2026-09-23 6:12 ` Chancel Liu
2026-09-23 6:12 ` [PATCH v2 04/21] ASoC: codecs: twl4030: Acquire board params and hs_extmute GPIO in the platform probe Chancel Liu
` (18 subsequent siblings)
21 siblings, 0 replies; 28+ messages in thread
From: Chancel Liu @ 2026-09-23 6:12 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Oder Chiou, Cezary Rojewski
Cc: Support Opensource, linux-sound, linux-kernel, patches
From: Chancel Liu <chancel.liu@nxp.com>
component->dev is the underlying I2C/SPI device. The driver acquired
its codec clocks with devm_clk_get()/devm_clk_bulk_get(component->dev,
...) from the DAI set_sysclk runtime callback, from set_bias_level and
from the component .probe. Because devres on component->dev is only
released when the platform device is removed, each card bind/unbind
(and, for set_sysclk, each runtime invocation) leaked a fresh clock
reference.
Acquire all codec clocks once in the bus probe with a single
devm_clk_bulk_get() into the private struct, and have the runtime
callbacks and both component probes reuse those stored references.
Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
---
sound/soc/codecs/tlv320aic32x4.c | 94 +++++++++++++++-----------------
1 file changed, 44 insertions(+), 50 deletions(-)
diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c
index af4be5bee723..7fec94768afb 100644
--- a/sound/soc/codecs/tlv320aic32x4.c
+++ b/sound/soc/codecs/tlv320aic32x4.c
@@ -48,8 +48,18 @@ struct aic32x4_priv {
enum aic32x4_type type;
unsigned int fmt;
+
+ struct clk_bulk_data clocks[7];
};
+#define AIC32X4_CLK_CODEC_CLKIN 0
+#define AIC32X4_CLK_PLL 1
+#define AIC32X4_CLK_NADC 2
+#define AIC32X4_CLK_MADC 3
+#define AIC32X4_CLK_NDAC 4
+#define AIC32X4_CLK_MDAC 5
+#define AIC32X4_CLK_BDIV 6
+
static int aic32x4_reset_adc(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
@@ -591,14 +601,10 @@ static int aic32x4_set_dai_sysclk(struct snd_soc_dai *codec_dai,
int clk_id, unsigned int freq, int dir)
{
struct snd_soc_component *component = codec_dai->component;
+ struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component);
struct clk *mclk;
- struct clk *pll;
-
- pll = devm_clk_get(component->dev, "pll");
- if (IS_ERR(pll))
- return PTR_ERR(pll);
- mclk = clk_get_parent(pll);
+ mclk = clk_get_parent(aic32x4->clocks[AIC32X4_CLK_PLL].clk);
return clk_set_rate(mclk, freq);
}
@@ -750,17 +756,7 @@ static int aic32x4_setup_clocks(struct snd_soc_component *component,
unsigned long adc_clock_rate, dac_clock_rate;
int ret;
- struct clk_bulk_data clocks[] = {
- { .id = "pll" },
- { .id = "nadc" },
- { .id = "madc" },
- { .id = "ndac" },
- { .id = "mdac" },
- { .id = "bdiv" },
- };
- ret = devm_clk_bulk_get(component->dev, ARRAY_SIZE(clocks), clocks);
- if (ret)
- return ret;
+ struct clk_bulk_data *clocks = &aic32x4->clocks[AIC32X4_CLK_PLL];
ret = aic32x4_configure_rate(component, sample_rate, &aosr,
&adc_resource_class, &dac_resource_class,
@@ -876,18 +872,15 @@ static int aic32x4_set_bias_level(struct snd_soc_component *component,
enum snd_soc_bias_level level)
{
struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component);
+ struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component);
int ret;
struct clk_bulk_data clocks[] = {
- { .id = "madc" },
- { .id = "mdac" },
- { .id = "bdiv" },
+ aic32x4->clocks[AIC32X4_CLK_MADC],
+ aic32x4->clocks[AIC32X4_CLK_MDAC],
+ aic32x4->clocks[AIC32X4_CLK_BDIV],
};
- ret = devm_clk_bulk_get(component->dev, ARRAY_SIZE(clocks), clocks);
- if (ret)
- return ret;
-
switch (level) {
case SND_SOC_BIAS_ON:
ret = clk_bulk_prepare_enable(ARRAY_SIZE(clocks), clocks);
@@ -970,23 +963,16 @@ static int aic32x4_component_probe(struct snd_soc_component *component)
{
struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component);
u32 tmp_reg;
- int ret;
- struct clk_bulk_data clocks[] = {
- { .id = "codec_clkin" },
- { .id = "pll" },
- { .id = "bdiv" },
- { .id = "mdac" },
- };
-
- ret = devm_clk_bulk_get(component->dev, ARRAY_SIZE(clocks), clocks);
- if (ret)
- return ret;
+ struct clk *codec_clkin = aic32x4->clocks[AIC32X4_CLK_CODEC_CLKIN].clk;
+ struct clk *pll = aic32x4->clocks[AIC32X4_CLK_PLL].clk;
+ struct clk *bdiv = aic32x4->clocks[AIC32X4_CLK_BDIV].clk;
+ struct clk *mdac = aic32x4->clocks[AIC32X4_CLK_MDAC].clk;
aic32x4_setup_gpios(component);
- clk_set_parent(clocks[0].clk, clocks[1].clk);
- clk_set_parent(clocks[2].clk, clocks[3].clk);
+ clk_set_parent(codec_clkin, pll);
+ clk_set_parent(bdiv, mdac);
/* Power platform configuration */
if (aic32x4->power_cfg & AIC32X4_PWR_MICBIAS_2075_LDOIN) {
@@ -1127,23 +1113,16 @@ static int aic32x4_tas2505_component_probe(struct snd_soc_component *component)
{
struct aic32x4_priv *aic32x4 = snd_soc_component_get_drvdata(component);
u32 tmp_reg;
- int ret;
-
- struct clk_bulk_data clocks[] = {
- { .id = "codec_clkin" },
- { .id = "pll" },
- { .id = "bdiv" },
- { .id = "mdac" },
- };
- ret = devm_clk_bulk_get(component->dev, ARRAY_SIZE(clocks), clocks);
- if (ret)
- return ret;
+ struct clk *codec_clkin = aic32x4->clocks[AIC32X4_CLK_CODEC_CLKIN].clk;
+ struct clk *pll = aic32x4->clocks[AIC32X4_CLK_PLL].clk;
+ struct clk *bdiv = aic32x4->clocks[AIC32X4_CLK_BDIV].clk;
+ struct clk *mdac = aic32x4->clocks[AIC32X4_CLK_MDAC].clk;
aic32x4_setup_gpios(component);
- clk_set_parent(clocks[0].clk, clocks[1].clk);
- clk_set_parent(clocks[2].clk, clocks[3].clk);
+ clk_set_parent(codec_clkin, pll);
+ clk_set_parent(bdiv, mdac);
/* Power platform configuration */
if (aic32x4->power_cfg & AIC32X4_PWR_AVDD_DVDD_WEAK_DISABLE)
@@ -1362,6 +1341,21 @@ int aic32x4_probe(struct device *dev, struct regmap *regmap,
if (ret)
goto err_disable_regulators;
+ aic32x4->clocks[AIC32X4_CLK_CODEC_CLKIN].id = "codec_clkin";
+ aic32x4->clocks[AIC32X4_CLK_PLL].id = "pll";
+ aic32x4->clocks[AIC32X4_CLK_NADC].id = "nadc";
+ aic32x4->clocks[AIC32X4_CLK_MADC].id = "madc";
+ aic32x4->clocks[AIC32X4_CLK_NDAC].id = "ndac";
+ aic32x4->clocks[AIC32X4_CLK_MDAC].id = "mdac";
+ aic32x4->clocks[AIC32X4_CLK_BDIV].id = "bdiv";
+
+ ret = devm_clk_bulk_get(dev, ARRAY_SIZE(aic32x4->clocks),
+ aic32x4->clocks);
+ if (ret) {
+ dev_err(dev, "Failed to get clocks\n");
+ goto err_disable_regulators;
+ }
+
switch (aic32x4->type) {
case AIC32X4_TYPE_TAS2505:
ret = devm_snd_soc_register_component(dev,
--
2.50.1
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v2 04/21] ASoC: codecs: twl4030: Acquire board params and hs_extmute GPIO in the platform probe
2026-09-23 6:12 [PATCH v2 00/21] ASoC: codecs: Fix resource leaks across card bind/unbind Chancel Liu
` (2 preceding siblings ...)
2026-09-23 6:12 ` [PATCH v2 03/21] ASoC: codecs: tlv320aic32x4: Fix clock leak from runtime callbacks Chancel Liu
@ 2026-09-23 6:12 ` Chancel Liu
2026-09-23 6:12 ` [PATCH v2 05/21] ASoC: codecs: es8316: Move mclk acquisition to the i2c probe Chancel Liu
` (17 subsequent siblings)
21 siblings, 0 replies; 28+ messages in thread
From: Chancel Liu @ 2026-09-23 6:12 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Oder Chiou, Cezary Rojewski
Cc: Support Opensource, linux-sound, linux-kernel, patches
From: Chancel Liu <chancel.liu@nxp.com>
The component .probe/.remove callbacks fire on ASoC card bind/unbind, but
component->dev is the underlying platform device whose devres is only
released on physical device removal. Resources allocated with
devm_*(component->dev, ...) in the component probe are therefore never
freed on card unbind, leaking one copy per bind/unbind cycle and leaking
the hs_extmute GPIO descriptor (which can also fail to be re-acquired on
re-bind).
The board parameters and the hs_extmute GPIO are pure hardware/device
level resources: they only depend on the physical device and the DT, not
on the ASoC component. Acquire them (together with the driver context)
in twl4030_codec_probe() using devm on the platform device, and hand the
context to the component through drvdata. Their devres lifetime then
correctly follows the device rather than the card bind/unbind, so no
explicit component .remove is needed. The component probe keeps only the
codec register initialisation that genuinely needs the component.
Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
---
sound/soc/codecs/twl4030.c | 63 +++++++++++++++++++++++++-------------
1 file changed, 42 insertions(+), 21 deletions(-)
diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c
index b986ece55d5a..442624cf2a31 100644
--- a/sound/soc/codecs/twl4030.c
+++ b/sound/soc/codecs/twl4030.c
@@ -212,21 +212,21 @@ twl4030_get_board_param_values(struct twl4030_board_params *board_params,
}
static struct twl4030_board_params*
-twl4030_get_board_params(struct snd_soc_component *component)
+twl4030_get_board_params(struct device *dev)
{
struct twl4030_board_params *board_params = NULL;
struct device_node *twl4030_codec_node = NULL;
- twl4030_codec_node = of_get_child_by_name(component->dev->parent->of_node,
+ twl4030_codec_node = of_get_child_by_name(dev->parent->of_node,
"codec");
if (twl4030_codec_node) {
- board_params = devm_kzalloc(component->dev,
+ board_params = devm_kzalloc(dev,
sizeof(struct twl4030_board_params),
GFP_KERNEL);
if (!board_params) {
of_node_put(twl4030_codec_node);
- return NULL;
+ return ERR_PTR(-ENOMEM);
}
twl4030_get_board_param_values(board_params, twl4030_codec_node);
of_node_put(twl4030_codec_node);
@@ -235,21 +235,22 @@ twl4030_get_board_params(struct snd_soc_component *component)
return board_params;
}
-static int twl4030_init_chip(struct snd_soc_component *component)
+static int twl4030_get_hw_params(struct device *dev,
+ struct twl4030_priv *twl4030)
{
struct twl4030_board_params *board_params;
- struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component);
- u8 reg, byte;
- int i = 0;
- board_params = twl4030_get_board_params(component);
+ board_params = twl4030_get_board_params(dev);
+ if (IS_ERR(board_params))
+ return PTR_ERR(board_params);
if (board_params && board_params->hs_extmute) {
- board_params->hs_extmute_gpio = devm_gpiod_get_optional(component->dev,
+ board_params->hs_extmute_gpio = devm_gpiod_get_optional(dev,
"ti,hs_extmute",
GPIOD_OUT_LOW);
if (IS_ERR(board_params->hs_extmute_gpio))
- return dev_err_probe(component->dev, PTR_ERR(board_params->hs_extmute_gpio),
+ return dev_err_probe(dev,
+ PTR_ERR(board_params->hs_extmute_gpio),
"Failed to get hs_extmute GPIO\n");
if (board_params->hs_extmute_gpio) {
@@ -257,7 +258,7 @@ static int twl4030_init_chip(struct snd_soc_component *component)
} else {
u8 pin_mux;
- dev_info(component->dev, "use TWL4030 GPIO6\n");
+ dev_info(dev, "use TWL4030 GPIO6\n");
/* Set TWL4030 GPIO6 as EXTMUTE signal */
twl_i2c_read_u8(TWL4030_MODULE_INTBR, &pin_mux,
@@ -269,6 +270,18 @@ static int twl4030_init_chip(struct snd_soc_component *component)
}
}
+ twl4030->board_params = board_params;
+
+ return 0;
+}
+
+static int twl4030_init_chip(struct snd_soc_component *component)
+{
+ struct twl4030_priv *twl4030 = snd_soc_component_get_drvdata(component);
+ struct twl4030_board_params *board_params = twl4030->board_params;
+ u8 reg, byte;
+ int i = 0;
+
/* Initialize the local ctl register cache */
tw4030_init_ctl_cache(twl4030);
@@ -288,8 +301,6 @@ static int twl4030_init_chip(struct snd_soc_component *component)
if (!board_params)
return 0;
- twl4030->board_params = board_params;
-
reg = twl4030_read(component, TWL4030_REG_HS_POPN_SET);
reg &= ~TWL4030_RAMP_DELAY;
reg |= (board_params->ramp_delay_value << 2);
@@ -2163,15 +2174,9 @@ static struct snd_soc_dai_driver twl4030_dai[] = {
static int twl4030_soc_probe(struct snd_soc_component *component)
{
- struct twl4030_priv *twl4030;
+ struct twl4030_priv *twl4030 = dev_get_drvdata(component->dev);
- twl4030 = devm_kzalloc(component->dev, sizeof(struct twl4030_priv),
- GFP_KERNEL);
- if (!twl4030)
- return -ENOMEM;
snd_soc_component_set_drvdata(component, twl4030);
- /* Set the defaults, and power up the codec */
- twl4030->sysclk = twl4030_audio_get_mclk() / 1000;
return twl4030_init_chip(component);
}
@@ -2193,6 +2198,22 @@ static const struct snd_soc_component_driver soc_component_dev_twl4030 = {
static int twl4030_codec_probe(struct platform_device *pdev)
{
+ struct twl4030_priv *twl4030;
+ int ret;
+
+ twl4030 = devm_kzalloc(&pdev->dev, sizeof(*twl4030), GFP_KERNEL);
+ if (!twl4030)
+ return -ENOMEM;
+
+ /* Set the defaults, and power up the codec */
+ twl4030->sysclk = twl4030_audio_get_mclk() / 1000;
+
+ ret = twl4030_get_hw_params(&pdev->dev, twl4030);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, twl4030);
+
return devm_snd_soc_register_component(&pdev->dev,
&soc_component_dev_twl4030,
twl4030_dai, ARRAY_SIZE(twl4030_dai));
--
2.50.1
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v2 05/21] ASoC: codecs: es8316: Move mclk acquisition to the i2c probe
2026-09-23 6:12 [PATCH v2 00/21] ASoC: codecs: Fix resource leaks across card bind/unbind Chancel Liu
` (3 preceding siblings ...)
2026-09-23 6:12 ` [PATCH v2 04/21] ASoC: codecs: twl4030: Acquire board params and hs_extmute GPIO in the platform probe Chancel Liu
@ 2026-09-23 6:12 ` Chancel Liu
2026-09-23 8:19 ` Cezary Rojewski
2026-09-23 6:12 ` [PATCH v2 06/21] ASoC: codecs: es8323: " Chancel Liu
` (16 subsequent siblings)
21 siblings, 1 reply; 28+ messages in thread
From: Chancel Liu @ 2026-09-23 6:12 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Oder Chiou, Cezary Rojewski
Cc: Support Opensource, linux-sound, linux-kernel, patches
From: Chancel Liu <chancel.liu@nxp.com>
component->dev is the underlying i2c device whose devres is only
released on physical device removal, not on ASoC card unbind. Getting
the mclk with devm_clk_get_optional(component->dev, ...) in the
component probe therefore leaks a clk reference on every card
bind/unbind cycle.
Move the devm_clk_get_optional() to es8316_i2c_probe() so the clk
reference is tied to the i2c device lifetime.
Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
---
sound/soc/codecs/es8316.c | 14 ++++++--------
1 file changed, 6 insertions(+), 8 deletions(-)
diff --git a/sound/soc/codecs/es8316.c b/sound/soc/codecs/es8316.c
index 59399476b9d7..5e7bf6f020e0 100644
--- a/sound/soc/codecs/es8316.c
+++ b/sound/soc/codecs/es8316.c
@@ -771,14 +771,6 @@ static int es8316_probe(struct snd_soc_component *component)
es8316->component = component;
- es8316->mclk = devm_clk_get_optional(component->dev, "mclk");
- if (IS_ERR(es8316->mclk)) {
- dev_err(component->dev, "unable to get mclk\n");
- return PTR_ERR(es8316->mclk);
- }
- if (!es8316->mclk)
- dev_warn(component->dev, "assuming static mclk\n");
-
ret = clk_prepare_enable(es8316->mclk);
if (ret) {
dev_err(component->dev, "unable to enable mclk\n");
@@ -883,6 +875,12 @@ static int es8316_i2c_probe(struct i2c_client *i2c_client)
i2c_set_clientdata(i2c_client, es8316);
+ es8316->mclk = devm_clk_get_optional(dev, "mclk");
+ if (IS_ERR(es8316->mclk))
+ return dev_err_probe(dev, PTR_ERR(es8316->mclk), "unable to get mclk\n");
+ if (!es8316->mclk)
+ dev_warn(dev, "assuming static mclk\n");
+
ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(es8316_supply_names),
es8316_supply_names);
if (ret)
--
2.50.1
^ permalink raw reply [flat|nested] 28+ messages in thread* Re: [PATCH v2 05/21] ASoC: codecs: es8316: Move mclk acquisition to the i2c probe
2026-09-23 6:12 ` [PATCH v2 05/21] ASoC: codecs: es8316: Move mclk acquisition to the i2c probe Chancel Liu
@ 2026-09-23 8:19 ` Cezary Rojewski
0 siblings, 0 replies; 28+ messages in thread
From: Cezary Rojewski @ 2026-09-23 8:19 UTC (permalink / raw)
To: Chancel Liu
Cc: Support Opensource, linux-sound, linux-kernel, patches,
Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Oder Chiou
On 9/23/2026 8:12 AM, Chancel Liu wrote:
> From: Chancel Liu <chancel.liu@nxp.com>
>
> component->dev is the underlying i2c device whose devres is only
> released on physical device removal, not on ASoC card unbind. Getting
> the mclk with devm_clk_get_optional(component->dev, ...) in the
> component probe therefore leaks a clk reference on every card
> bind/unbind cycle.
>
> Move the devm_clk_get_optional() to es8316_i2c_probe() so the clk
> reference is tied to the i2c device lifetime.
>
> Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
Reviewed-by: Cezary Rojewski <cezary.rojewski@intel.com>
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2 06/21] ASoC: codecs: es8323: Move mclk acquisition to the i2c probe
2026-09-23 6:12 [PATCH v2 00/21] ASoC: codecs: Fix resource leaks across card bind/unbind Chancel Liu
` (4 preceding siblings ...)
2026-09-23 6:12 ` [PATCH v2 05/21] ASoC: codecs: es8316: Move mclk acquisition to the i2c probe Chancel Liu
@ 2026-09-23 6:12 ` Chancel Liu
2026-09-23 6:12 ` [PATCH v2 07/21] ASoC: codecs: es8311: " Chancel Liu
` (15 subsequent siblings)
21 siblings, 0 replies; 28+ messages in thread
From: Chancel Liu @ 2026-09-23 6:12 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Oder Chiou, Cezary Rojewski
Cc: Support Opensource, linux-sound, linux-kernel, patches
From: Chancel Liu <chancel.liu@nxp.com>
component->dev is the underlying i2c device whose devres is only
released on physical device removal, not on ASoC card unbind. Getting
the mclk with devm_clk_get_optional(component->dev, ...) in the
component probe therefore leaks a clk reference on every card
bind/unbind cycle.
Move the devm_clk_get_optional() to es8323_i2c_probe() so the clk
reference is tied to the i2c device lifetime.
Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
---
sound/soc/codecs/es8323.c | 15 ++++++---------
1 file changed, 6 insertions(+), 9 deletions(-)
diff --git a/sound/soc/codecs/es8323.c b/sound/soc/codecs/es8323.c
index 12fcfe017ab2..3f7fbbdb111e 100644
--- a/sound/soc/codecs/es8323.c
+++ b/sound/soc/codecs/es8323.c
@@ -671,15 +671,6 @@ static int es8323_probe(struct snd_soc_component *component)
struct es8323_priv *es8323 = snd_soc_component_get_drvdata(component);
int ret;
- es8323->mclk = devm_clk_get_optional(component->dev, "mclk");
- if (IS_ERR(es8323->mclk)) {
- dev_err(component->dev, "unable to get mclk\n");
- return PTR_ERR(es8323->mclk);
- }
-
- if (!es8323->mclk)
- dev_warn(component->dev, "assuming static mclk\n");
-
ret = clk_prepare_enable(es8323->mclk);
if (ret) {
dev_err(component->dev, "unable to enable mclk\n");
@@ -789,6 +780,12 @@ static int es8323_i2c_probe(struct i2c_client *i2c_client)
i2c_set_clientdata(i2c_client, es8323);
+ es8323->mclk = devm_clk_get_optional(dev, "mclk");
+ if (IS_ERR(es8323->mclk))
+ return dev_err_probe(dev, PTR_ERR(es8323->mclk), "unable to get mclk\n");
+ if (!es8323->mclk)
+ dev_warn(dev, "assuming static mclk\n");
+
es8323->regmap = devm_regmap_init_i2c(i2c_client, &es8323_regmap);
if (IS_ERR(es8323->regmap))
return PTR_ERR(es8323->regmap);
--
2.50.1
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v2 07/21] ASoC: codecs: es8311: Move mclk acquisition to the i2c probe
2026-09-23 6:12 [PATCH v2 00/21] ASoC: codecs: Fix resource leaks across card bind/unbind Chancel Liu
` (5 preceding siblings ...)
2026-09-23 6:12 ` [PATCH v2 06/21] ASoC: codecs: es8323: " Chancel Liu
@ 2026-09-23 6:12 ` Chancel Liu
2026-09-23 6:12 ` [PATCH v2 08/21] ASoC: codecs: es8328: Move clk acquisition to the bus probe Chancel Liu
` (14 subsequent siblings)
21 siblings, 0 replies; 28+ messages in thread
From: Chancel Liu @ 2026-09-23 6:12 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Oder Chiou, Cezary Rojewski
Cc: Support Opensource, linux-sound, linux-kernel, patches
From: Chancel Liu <chancel.liu@nxp.com>
component->dev is the underlying i2c device whose devres is only
released on physical device removal, not on ASoC card unbind. Getting
the mclk with devm_clk_get_optional(component->dev, ...) in the
component probe therefore leaks a clk reference on every card
bind/unbind cycle.
Move the devm_clk_get_optional() to es8311_i2c_probe() so the clk
reference is tied to the i2c device lifetime.
Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
---
sound/soc/codecs/es8311.c | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/sound/soc/codecs/es8311.c b/sound/soc/codecs/es8311.c
index e46b85f11d57..ddbbfc2443b3 100644
--- a/sound/soc/codecs/es8311.c
+++ b/sound/soc/codecs/es8311.c
@@ -906,12 +906,6 @@ static int es8311_component_probe(struct snd_soc_component *component)
es8311 = snd_soc_component_get_drvdata(component);
- es8311->mclk = devm_clk_get_optional(component->dev, "mclk");
- if (IS_ERR(es8311->mclk)) {
- dev_err(component->dev, "invalid mclk\n");
- return PTR_ERR(es8311->mclk);
- }
-
es8311->mclk_freq = clk_get_rate(es8311->mclk);
if (es8311->mclk_freq > 0 && es8311->mclk_freq < ES8311_MCLK_MAX_FREQ)
es8311_set_sysclk_constraints(es8311->mclk_freq, es8311);
@@ -960,6 +954,10 @@ static int es8311_i2c_probe(struct i2c_client *i2c_client)
if (es8311 == NULL)
return -ENOMEM;
+ es8311->mclk = devm_clk_get_optional(dev, "mclk");
+ if (IS_ERR(es8311->mclk))
+ return dev_err_probe(dev, PTR_ERR(es8311->mclk), "invalid mclk\n");
+
es8311->regmap =
devm_regmap_init_i2c(i2c_client, &es8311_regmap_config);
if (IS_ERR(es8311->regmap))
--
2.50.1
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v2 08/21] ASoC: codecs: es8328: Move clk acquisition to the bus probe
2026-09-23 6:12 [PATCH v2 00/21] ASoC: codecs: Fix resource leaks across card bind/unbind Chancel Liu
` (6 preceding siblings ...)
2026-09-23 6:12 ` [PATCH v2 07/21] ASoC: codecs: es8311: " Chancel Liu
@ 2026-09-23 6:12 ` Chancel Liu
2026-09-23 6:12 ` [PATCH v2 09/21] ASoC: codecs: rt5640: Move mclk acquisition to the i2c probe Chancel Liu
` (13 subsequent siblings)
21 siblings, 0 replies; 28+ messages in thread
From: Chancel Liu @ 2026-09-23 6:12 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Oder Chiou, Cezary Rojewski
Cc: Support Opensource, linux-sound, linux-kernel, patches
From: Chancel Liu <chancel.liu@nxp.com>
component->dev is the underlying i2c/spi device whose devres is only
released on physical device removal, not on ASoC card unbind. Getting
the codec clock with devm_clk_get(component->dev, ...) in the component
probe therefore leaks a clk reference on every card bind/unbind cycle.
Move the devm_clk_get() to es8328_probe() (the shared i2c/spi bus level
probe) so the clk reference is tied to the physical device lifetime.
Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
---
sound/soc/codecs/es8328.c | 13 +++++--------
1 file changed, 5 insertions(+), 8 deletions(-)
diff --git a/sound/soc/codecs/es8328.c b/sound/soc/codecs/es8328.c
index 7d52b5d9d061..37fda2f42b0c 100644
--- a/sound/soc/codecs/es8328.c
+++ b/sound/soc/codecs/es8328.c
@@ -829,14 +829,6 @@ static int es8328_component_probe(struct snd_soc_component *component)
return ret;
}
- /* Setup clocks */
- es8328->clk = devm_clk_get(component->dev, NULL);
- if (IS_ERR(es8328->clk)) {
- dev_err(component->dev, "codec clock missing or invalid\n");
- ret = PTR_ERR(es8328->clk);
- goto clk_fail;
- }
-
ret = clk_prepare_enable(es8328->clk);
if (ret) {
dev_err(component->dev, "unable to prepare codec clk\n");
@@ -906,6 +898,11 @@ int es8328_probe(struct device *dev, struct regmap *regmap)
es8328->regmap = regmap;
+ es8328->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(es8328->clk))
+ return dev_err_probe(dev, PTR_ERR(es8328->clk),
+ "codec clock missing or invalid\n");
+
for (i = 0; i < ARRAY_SIZE(es8328->supplies); i++)
es8328->supplies[i].supply = supply_names[i];
--
2.50.1
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v2 09/21] ASoC: codecs: rt5640: Move mclk acquisition to the i2c probe
2026-09-23 6:12 [PATCH v2 00/21] ASoC: codecs: Fix resource leaks across card bind/unbind Chancel Liu
` (7 preceding siblings ...)
2026-09-23 6:12 ` [PATCH v2 08/21] ASoC: codecs: es8328: Move clk acquisition to the bus probe Chancel Liu
@ 2026-09-23 6:12 ` Chancel Liu
2026-09-23 8:16 ` Cezary Rojewski
2026-09-23 6:12 ` [PATCH v2 10/21] ASoC: codecs: rt5616: " Chancel Liu
` (12 subsequent siblings)
21 siblings, 1 reply; 28+ messages in thread
From: Chancel Liu @ 2026-09-23 6:12 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Oder Chiou, Cezary Rojewski
Cc: Support Opensource, linux-sound, linux-kernel, patches
From: Chancel Liu <chancel.liu@nxp.com>
component->dev is the underlying i2c device whose devres is only
released on physical device removal, not on ASoC card unbind. Getting
the codec clock with devm_clk_get_optional(component->dev, ...) in the
component probe therefore leaks a clk reference on every card
bind/unbind cycle.
Move the devm_clk_get_optional() to rt5640_i2c_probe() so the clk
reference is tied to the physical device lifetime.
Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
---
sound/soc/codecs/rt5640.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/sound/soc/codecs/rt5640.c b/sound/soc/codecs/rt5640.c
index 03d0ac3359f5..d6068bb5cb4c 100644
--- a/sound/soc/codecs/rt5640.c
+++ b/sound/soc/codecs/rt5640.c
@@ -2667,11 +2667,6 @@ static int rt5640_probe(struct snd_soc_component *component)
bool dmic_en = false;
u32 val;
- /* Check if MCLK provided */
- rt5640->mclk = devm_clk_get_optional(component->dev, "mclk");
- if (IS_ERR(rt5640->mclk))
- return PTR_ERR(rt5640->mclk);
-
rt5640->component = component;
snd_soc_dapm_force_bias_level(dapm, SND_SOC_BIAS_OFF);
@@ -3012,6 +3007,11 @@ static int rt5640_i2c_probe(struct i2c_client *i2c)
return -ENOMEM;
i2c_set_clientdata(i2c, rt5640);
+ /* Check if MCLK provided */
+ rt5640->mclk = devm_clk_get_optional(&i2c->dev, "mclk");
+ if (IS_ERR(rt5640->mclk))
+ return PTR_ERR(rt5640->mclk);
+
rt5640->ldo1_en = devm_gpiod_get_optional(&i2c->dev,
"realtek,ldo1-en",
GPIOD_OUT_HIGH);
--
2.50.1
^ permalink raw reply [flat|nested] 28+ messages in thread* Re: [PATCH v2 09/21] ASoC: codecs: rt5640: Move mclk acquisition to the i2c probe
2026-09-23 6:12 ` [PATCH v2 09/21] ASoC: codecs: rt5640: Move mclk acquisition to the i2c probe Chancel Liu
@ 2026-09-23 8:16 ` Cezary Rojewski
2026-09-23 8:22 ` Chancel Liu
0 siblings, 1 reply; 28+ messages in thread
From: Cezary Rojewski @ 2026-09-23 8:16 UTC (permalink / raw)
To: Chancel Liu
Cc: Support Opensource, linux-sound, linux-kernel, patches,
Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Oder Chiou
On 9/23/2026 8:12 AM, Chancel Liu wrote:
> From: Chancel Liu <chancel.liu@nxp.com>
>
> component->dev is the underlying i2c device whose devres is only
> released on physical device removal, not on ASoC card unbind. Getting
> the codec clock with devm_clk_get_optional(component->dev, ...) in the
> component probe therefore leaks a clk reference on every card
> bind/unbind cycle.
>
> Move the devm_clk_get_optional() to rt5640_i2c_probe() so the clk
> reference is tied to the physical device lifetime.
>
> Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
I see no reason why my tag was removed. Nonetheless:
Reviewed-by: Cezary Rojewski <cezary.rojewski@intel.com>
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v2 09/21] ASoC: codecs: rt5640: Move mclk acquisition to the i2c probe
2026-09-23 8:16 ` Cezary Rojewski
@ 2026-09-23 8:22 ` Chancel Liu
0 siblings, 0 replies; 28+ messages in thread
From: Chancel Liu @ 2026-09-23 8:22 UTC (permalink / raw)
To: Cezary Rojewski
Cc: Support Opensource, linux-sound, linux-kernel, patches,
Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Oder Chiou
On 9/23/2026 4:16 PM, Cezary Rojewski wrote:
> On 9/23/2026 8:12 AM, Chancel Liu wrote:
>> From: Chancel Liu <chancel.liu@nxp.com>
>>
>> component->dev is the underlying i2c device whose devres is only
>> released on physical device removal, not on ASoC card unbind. Getting
>> the codec clock with devm_clk_get_optional(component->dev, ...) in the
>> component probe therefore leaks a clk reference on every card
>> bind/unbind cycle.
>>
>> Move the devm_clk_get_optional() to rt5640_i2c_probe() so the clk
>> reference is tied to the physical device lifetime.
>>
>> Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
> I see no reason why my tag was removed. Nonetheless:
>
> Reviewed-by: Cezary Rojewski <cezary.rojewski@intel.com>
Thanks, and sorry for not noting it in the changelog. I dropped it only
because v2 change this patch slightly (acquiring the mclk earlier in the
probe), so I wasn't sure you'd still be happy with it. Glad it's still fine.
Regards,
Chancel Liu
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2 10/21] ASoC: codecs: rt5616: Move mclk acquisition to the i2c probe
2026-09-23 6:12 [PATCH v2 00/21] ASoC: codecs: Fix resource leaks across card bind/unbind Chancel Liu
` (8 preceding siblings ...)
2026-09-23 6:12 ` [PATCH v2 09/21] ASoC: codecs: rt5640: Move mclk acquisition to the i2c probe Chancel Liu
@ 2026-09-23 6:12 ` Chancel Liu
2026-09-23 6:12 ` [PATCH v2 11/21] ASoC: codecs: rt5514: Move clk " Chancel Liu
` (11 subsequent siblings)
21 siblings, 0 replies; 28+ messages in thread
From: Chancel Liu @ 2026-09-23 6:12 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Oder Chiou, Cezary Rojewski
Cc: Support Opensource, linux-sound, linux-kernel, patches
From: Chancel Liu <chancel.liu@nxp.com>
component->dev is the underlying i2c device whose devres is only
released on physical device removal, not on ASoC card unbind. Getting
the codec clock with devm_clk_get_optional(component->dev, ...) in the
component probe therefore leaks a clk reference on every card
bind/unbind cycle.
Move the devm_clk_get_optional() to rt5616_i2c_probe() so the clk
reference is tied to the physical device lifetime.
Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
---
sound/soc/codecs/rt5616.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/sound/soc/codecs/rt5616.c b/sound/soc/codecs/rt5616.c
index 3f9d97d04d48..e30e3b563c90 100644
--- a/sound/soc/codecs/rt5616.c
+++ b/sound/soc/codecs/rt5616.c
@@ -1222,11 +1222,6 @@ static int rt5616_probe(struct snd_soc_component *component)
{
struct rt5616_priv *rt5616 = snd_soc_component_get_drvdata(component);
- /* Check if MCLK provided */
- rt5616->mclk = devm_clk_get_optional(component->dev, "mclk");
- if (IS_ERR(rt5616->mclk))
- return PTR_ERR(rt5616->mclk);
-
rt5616->component = component;
return 0;
@@ -1357,6 +1352,11 @@ static int rt5616_i2c_probe(struct i2c_client *i2c)
i2c_set_clientdata(i2c, rt5616);
+ /* Check if MCLK provided */
+ rt5616->mclk = devm_clk_get_optional(&i2c->dev, "mclk");
+ if (IS_ERR(rt5616->mclk))
+ return PTR_ERR(rt5616->mclk);
+
rt5616->regmap = devm_regmap_init_i2c(i2c, &rt5616_regmap);
if (IS_ERR(rt5616->regmap)) {
ret = PTR_ERR(rt5616->regmap);
--
2.50.1
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v2 11/21] ASoC: codecs: rt5514: Move clk acquisition to the i2c probe
2026-09-23 6:12 [PATCH v2 00/21] ASoC: codecs: Fix resource leaks across card bind/unbind Chancel Liu
` (9 preceding siblings ...)
2026-09-23 6:12 ` [PATCH v2 10/21] ASoC: codecs: rt5616: " Chancel Liu
@ 2026-09-23 6:12 ` Chancel Liu
2026-09-23 8:19 ` Cezary Rojewski
2026-09-23 6:12 ` [PATCH v2 12/21] ASoC: codecs: rt5682s: Move mclk " Chancel Liu
` (10 subsequent siblings)
21 siblings, 1 reply; 28+ messages in thread
From: Chancel Liu @ 2026-09-23 6:12 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Oder Chiou, Cezary Rojewski
Cc: Support Opensource, linux-sound, linux-kernel, patches
From: Chancel Liu <chancel.liu@nxp.com>
component->dev is the underlying i2c device whose devres is only
released on physical device removal, not on ASoC card unbind. Getting
the codec clocks with devm_clk_get*(component->dev, ...) in the
component probe therefore leaks clk references on every card
bind/unbind cycle.
Move the mclk and dsp_calib_clk acquisition to rt5514_i2c_probe() so
the clk references are tied to the physical device lifetime.
Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
---
sound/soc/codecs/rt5514.c | 23 +++++++++++------------
1 file changed, 11 insertions(+), 12 deletions(-)
diff --git a/sound/soc/codecs/rt5514.c b/sound/soc/codecs/rt5514.c
index 753143e2d11f..a33fbc9fb047 100644
--- a/sound/soc/codecs/rt5514.c
+++ b/sound/soc/codecs/rt5514.c
@@ -1099,18 +1099,6 @@ static int rt5514_set_bias_level(struct snd_soc_component *component,
static int rt5514_probe(struct snd_soc_component *component)
{
struct rt5514_priv *rt5514 = snd_soc_component_get_drvdata(component);
- struct platform_device *pdev = to_platform_device(component->dev);
-
- rt5514->mclk = devm_clk_get_optional(component->dev, "mclk");
- if (IS_ERR(rt5514->mclk))
- return PTR_ERR(rt5514->mclk);
-
- if (rt5514->pdata.dsp_calib_clk_name) {
- rt5514->dsp_calib_clk = devm_clk_get(&pdev->dev,
- rt5514->pdata.dsp_calib_clk_name);
- if (PTR_ERR(rt5514->dsp_calib_clk) == -EPROBE_DEFER)
- return -EPROBE_DEFER;
- }
rt5514->component = component;
rt5514->pll3_cal_value = 0x0078b000;
@@ -1285,6 +1273,17 @@ static int rt5514_i2c_probe(struct i2c_client *i2c)
else
rt5514_parse_dp(rt5514, &i2c->dev);
+ rt5514->mclk = devm_clk_get_optional(&i2c->dev, "mclk");
+ if (IS_ERR(rt5514->mclk))
+ return PTR_ERR(rt5514->mclk);
+
+ if (rt5514->pdata.dsp_calib_clk_name) {
+ rt5514->dsp_calib_clk = devm_clk_get(&i2c->dev,
+ rt5514->pdata.dsp_calib_clk_name);
+ if (PTR_ERR(rt5514->dsp_calib_clk) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ }
+
rt5514->i2c_regmap = devm_regmap_init_i2c(i2c, &rt5514_i2c_regmap);
if (IS_ERR(rt5514->i2c_regmap)) {
ret = PTR_ERR(rt5514->i2c_regmap);
--
2.50.1
^ permalink raw reply [flat|nested] 28+ messages in thread* Re: [PATCH v2 11/21] ASoC: codecs: rt5514: Move clk acquisition to the i2c probe
2026-09-23 6:12 ` [PATCH v2 11/21] ASoC: codecs: rt5514: Move clk " Chancel Liu
@ 2026-09-23 8:19 ` Cezary Rojewski
0 siblings, 0 replies; 28+ messages in thread
From: Cezary Rojewski @ 2026-09-23 8:19 UTC (permalink / raw)
To: Chancel Liu
Cc: Support Opensource, linux-sound, linux-kernel, patches,
Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Oder Chiou
On 9/23/2026 8:12 AM, Chancel Liu wrote:
> From: Chancel Liu <chancel.liu@nxp.com>
>
> component->dev is the underlying i2c device whose devres is only
> released on physical device removal, not on ASoC card unbind. Getting
> the codec clocks with devm_clk_get*(component->dev, ...) in the
> component probe therefore leaks clk references on every card
> bind/unbind cycle.
>
> Move the mclk and dsp_calib_clk acquisition to rt5514_i2c_probe() so
> the clk references are tied to the physical device lifetime.
>
> Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
Reviewed-by: Cezary Rojewski <cezary.rojewski@intel.com>
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2 12/21] ASoC: codecs: rt5682s: Move mclk acquisition to the i2c probe
2026-09-23 6:12 [PATCH v2 00/21] ASoC: codecs: Fix resource leaks across card bind/unbind Chancel Liu
` (10 preceding siblings ...)
2026-09-23 6:12 ` [PATCH v2 11/21] ASoC: codecs: rt5514: Move clk " Chancel Liu
@ 2026-09-23 6:12 ` Chancel Liu
2026-09-23 6:12 ` [PATCH v2 13/21] ASoC: codecs: max98090: " Chancel Liu
` (9 subsequent siblings)
21 siblings, 0 replies; 28+ messages in thread
From: Chancel Liu @ 2026-09-23 6:12 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Oder Chiou, Cezary Rojewski
Cc: Support Opensource, linux-sound, linux-kernel, patches
From: Chancel Liu <chancel.liu@nxp.com>
component->dev is the underlying i2c device whose devres is only
released on physical device removal, not on ASoC card unbind. Getting
the codec clock with devm_clk_get_optional(component->dev, ...) in the
component probe chain therefore leaks a clk reference on every card
bind/unbind cycle.
Move the devm_clk_get_optional() to rt5682s_i2c_probe() so the clk
reference is tied to the physical device lifetime.
Reviewed-by: Cezary Rojewski <cezary.rojewski@intel.com>
Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
---
sound/soc/codecs/rt5682s.c | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/sound/soc/codecs/rt5682s.c b/sound/soc/codecs/rt5682s.c
index 6dd0d36a7186..0f80e4c32f57 100644
--- a/sound/soc/codecs/rt5682s.c
+++ b/sound/soc/codecs/rt5682s.c
@@ -2846,11 +2846,6 @@ static int rt5682s_dai_probe_clks(struct snd_soc_component *component)
struct rt5682s_priv *rt5682s = snd_soc_component_get_drvdata(component);
int ret;
- /* Check if MCLK provided */
- rt5682s->mclk = devm_clk_get_optional(component->dev, "mclk");
- if (IS_ERR(rt5682s->mclk))
- return PTR_ERR(rt5682s->mclk);
-
/* Register CCF DAI clock control */
ret = rt5682s_register_dai_clks(component);
if (ret)
@@ -3152,6 +3147,13 @@ static int rt5682s_i2c_probe(struct i2c_client *i2c)
return ret;
}
+#ifdef CONFIG_COMMON_CLK
+ /* Check if MCLK provided */
+ rt5682s->mclk = devm_clk_get_optional(&i2c->dev, "mclk");
+ if (IS_ERR(rt5682s->mclk))
+ return PTR_ERR(rt5682s->mclk);
+#endif
+
for (i = 0; i < ARRAY_SIZE(rt5682s->supplies); i++)
rt5682s->supplies[i].supply = rt5682s_supply_names[i];
--
2.50.1
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v2 13/21] ASoC: codecs: max98090: Move mclk acquisition to the i2c probe
2026-09-23 6:12 [PATCH v2 00/21] ASoC: codecs: Fix resource leaks across card bind/unbind Chancel Liu
` (11 preceding siblings ...)
2026-09-23 6:12 ` [PATCH v2 12/21] ASoC: codecs: rt5682s: Move mclk " Chancel Liu
@ 2026-09-23 6:12 ` Chancel Liu
2026-09-23 6:12 ` [PATCH v2 14/21] ASoC: codecs: max98095: " Chancel Liu
` (8 subsequent siblings)
21 siblings, 0 replies; 28+ messages in thread
From: Chancel Liu @ 2026-09-23 6:12 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Oder Chiou, Cezary Rojewski
Cc: Support Opensource, linux-sound, linux-kernel, patches
From: Chancel Liu <chancel.liu@nxp.com>
component->dev is the underlying i2c device whose devres is only
released on physical device removal, not on ASoC card unbind. Getting
the codec clock with devm_clk_get(component->dev, ...) in the component
probe therefore leaks a clk reference on every card bind/unbind cycle.
Move the devm_clk_get() to max98090_i2c_probe() so the clk reference is
tied to the physical device lifetime.
Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
---
sound/soc/codecs/max98090.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/sound/soc/codecs/max98090.c b/sound/soc/codecs/max98090.c
index c31f3d32fa43..4bb5478f4642 100644
--- a/sound/soc/codecs/max98090.c
+++ b/sound/soc/codecs/max98090.c
@@ -2448,11 +2448,6 @@ static int max98090_probe(struct snd_soc_component *component)
dev_dbg(component->dev, "max98090_probe\n");
- max98090->mclk = devm_clk_get(component->dev, "mclk");
- if (IS_ERR(max98090->mclk))
- if (PTR_ERR(max98090->mclk) == -EPROBE_DEFER)
- return -EPROBE_DEFER;
-
max98090->component = component;
/* Reset the codec, the DSP core, and disable all interrupts */
@@ -2633,6 +2628,11 @@ static int max98090_i2c_probe(struct i2c_client *i2c)
goto err_enable;
}
+ max98090->mclk = devm_clk_get(&i2c->dev, "mclk");
+ if (IS_ERR(max98090->mclk))
+ if (PTR_ERR(max98090->mclk) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+
ret = devm_request_threaded_irq(&i2c->dev, i2c->irq, NULL,
max98090_interrupt, IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
"max98090_interrupt", max98090);
--
2.50.1
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v2 14/21] ASoC: codecs: max98095: Move mclk acquisition to the i2c probe
2026-09-23 6:12 [PATCH v2 00/21] ASoC: codecs: Fix resource leaks across card bind/unbind Chancel Liu
` (12 preceding siblings ...)
2026-09-23 6:12 ` [PATCH v2 13/21] ASoC: codecs: max98090: " Chancel Liu
@ 2026-09-23 6:12 ` Chancel Liu
2026-09-23 6:12 ` [PATCH v2 15/21] ASoC: codecs: wm8985: Move regulator acquisition to the bus probe Chancel Liu
` (7 subsequent siblings)
21 siblings, 0 replies; 28+ messages in thread
From: Chancel Liu @ 2026-09-23 6:12 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Oder Chiou, Cezary Rojewski
Cc: Support Opensource, linux-sound, linux-kernel, patches
From: Chancel Liu <chancel.liu@nxp.com>
component->dev is the underlying i2c device whose devres is only
released on physical device removal, not on ASoC card unbind. Getting
the codec clock with devm_clk_get(component->dev, ...) in the component
probe therefore leaks a clk reference on every card bind/unbind cycle.
Move the devm_clk_get() to max98095_i2c_probe() so the clk reference is
tied to the physical device lifetime.
Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
---
sound/soc/codecs/max98095.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/sound/soc/codecs/max98095.c b/sound/soc/codecs/max98095.c
index a64a5ba00379..c827fd47a5b0 100644
--- a/sound/soc/codecs/max98095.c
+++ b/sound/soc/codecs/max98095.c
@@ -2005,11 +2005,6 @@ static int max98095_probe(struct snd_soc_component *component)
struct i2c_client *client;
int ret = 0;
- max98095->mclk = devm_clk_get(component->dev, "mclk");
- if (IS_ERR(max98095->mclk))
- if (PTR_ERR(max98095->mclk) == -EPROBE_DEFER)
- return -EPROBE_DEFER;
-
/* reset the codec, the DSP core, and disable all interrupts */
max98095_reset(component);
@@ -2153,6 +2148,11 @@ static int max98095_i2c_probe(struct i2c_client *i2c)
return ret;
}
+ max98095->mclk = devm_clk_get(&i2c->dev, "mclk");
+ if (IS_ERR(max98095->mclk))
+ if (PTR_ERR(max98095->mclk) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+
max98095->devtype = (uintptr_t)i2c_get_match_data(i2c);
i2c_set_clientdata(i2c, max98095);
max98095->pdata = i2c->dev.platform_data;
--
2.50.1
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v2 15/21] ASoC: codecs: wm8985: Move regulator acquisition to the bus probe
2026-09-23 6:12 [PATCH v2 00/21] ASoC: codecs: Fix resource leaks across card bind/unbind Chancel Liu
` (13 preceding siblings ...)
2026-09-23 6:12 ` [PATCH v2 14/21] ASoC: codecs: max98095: " Chancel Liu
@ 2026-09-23 6:12 ` Chancel Liu
2026-09-23 8:56 ` Charles Keepax
2026-09-23 6:13 ` [PATCH v2 16/21] ASoC: codecs: wm8955: Move regulator acquisition to the i2c probe Chancel Liu
` (6 subsequent siblings)
21 siblings, 1 reply; 28+ messages in thread
From: Chancel Liu @ 2026-09-23 6:12 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Oder Chiou, Cezary Rojewski
Cc: Support Opensource, linux-sound, linux-kernel, patches
From: Chancel Liu <chancel.liu@nxp.com>
component->dev is the underlying i2c/spi device whose devres is only
released on physical device removal, not on ASoC card unbind. Getting
the regulator supplies with devm_regulator_bulk_get(component->dev, ...)
in the component probe therefore leaks the regulator references on every
card bind/unbind cycle.
Move the devm_regulator_bulk_get() into a helper called from the i2c and
spi probes so the supplies are tied to the physical device lifetime.
Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
---
sound/soc/codecs/wm8985.c | 35 +++++++++++++++++++++++++----------
1 file changed, 25 insertions(+), 10 deletions(-)
diff --git a/sound/soc/codecs/wm8985.c b/sound/soc/codecs/wm8985.c
index 877a691c188b..1e862609f5b3 100644
--- a/sound/soc/codecs/wm8985.c
+++ b/sound/soc/codecs/wm8985.c
@@ -1029,23 +1029,30 @@ static int wm8985_set_bias_level(struct snd_soc_component *component,
return 0;
}
-static int wm8985_probe(struct snd_soc_component *component)
+static int wm8985_get_regulators(struct device *dev,
+ struct wm8985_priv *wm8985)
{
size_t i;
- struct wm8985_priv *wm8985;
int ret;
- wm8985 = snd_soc_component_get_drvdata(component);
-
for (i = 0; i < ARRAY_SIZE(wm8985->supplies); i++)
wm8985->supplies[i].supply = wm8985_supply_names[i];
- ret = devm_regulator_bulk_get(component->dev, ARRAY_SIZE(wm8985->supplies),
- wm8985->supplies);
- if (ret) {
- dev_err(component->dev, "Failed to request supplies: %d\n", ret);
- return ret;
- }
+ ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(wm8985->supplies),
+ wm8985->supplies);
+ if (ret)
+ dev_err(dev, "Failed to request supplies: %d\n", ret);
+
+ return ret;
+}
+
+static int wm8985_probe(struct snd_soc_component *component)
+{
+ size_t i;
+ struct wm8985_priv *wm8985;
+ int ret;
+
+ wm8985 = snd_soc_component_get_drvdata(component);
ret = regulator_bulk_enable(ARRAY_SIZE(wm8985->supplies),
wm8985->supplies);
@@ -1176,6 +1183,10 @@ static int wm8985_spi_probe(struct spi_device *spi)
return ret;
}
+ ret = wm8985_get_regulators(&spi->dev, wm8985);
+ if (ret)
+ return ret;
+
ret = devm_snd_soc_register_component(&spi->dev,
&soc_component_dev_wm8985, &wm8985_dai, 1);
return ret;
@@ -1212,6 +1223,10 @@ static int wm8985_i2c_probe(struct i2c_client *i2c)
return ret;
}
+ ret = wm8985_get_regulators(&i2c->dev, wm8985);
+ if (ret)
+ return ret;
+
ret = devm_snd_soc_register_component(&i2c->dev,
&soc_component_dev_wm8985, &wm8985_dai, 1);
return ret;
--
2.50.1
^ permalink raw reply [flat|nested] 28+ messages in thread* Re: [PATCH v2 15/21] ASoC: codecs: wm8985: Move regulator acquisition to the bus probe
2026-09-23 6:12 ` [PATCH v2 15/21] ASoC: codecs: wm8985: Move regulator acquisition to the bus probe Chancel Liu
@ 2026-09-23 8:56 ` Charles Keepax
0 siblings, 0 replies; 28+ messages in thread
From: Charles Keepax @ 2026-09-23 8:56 UTC (permalink / raw)
To: Chancel Liu
Cc: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Oder Chiou, Cezary Rojewski, Support Opensource, linux-sound,
linux-kernel, patches
On Wed, Sep 23, 2026 at 03:12:59PM +0900, Chancel Liu wrote:
> From: Chancel Liu <chancel.liu@nxp.com>
>
> component->dev is the underlying i2c/spi device whose devres is only
> released on physical device removal, not on ASoC card unbind. Getting
> the regulator supplies with devm_regulator_bulk_get(component->dev, ...)
> in the component probe therefore leaks the regulator references on every
> card bind/unbind cycle.
>
> Move the devm_regulator_bulk_get() into a helper called from the i2c and
> spi probes so the supplies are tied to the physical device lifetime.
>
> Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
> ---
Reviewed-by: Charles Keepax <ckeepax@opensource.cirrus.com>
Thanks,
Charles
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2 16/21] ASoC: codecs: wm8955: Move regulator acquisition to the i2c probe
2026-09-23 6:12 [PATCH v2 00/21] ASoC: codecs: Fix resource leaks across card bind/unbind Chancel Liu
` (14 preceding siblings ...)
2026-09-23 6:12 ` [PATCH v2 15/21] ASoC: codecs: wm8985: Move regulator acquisition to the bus probe Chancel Liu
@ 2026-09-23 6:13 ` Chancel Liu
2026-09-23 6:13 ` [PATCH v2 17/21] ASoC: codecs: es8389: Move regulator and mclk " Chancel Liu
` (5 subsequent siblings)
21 siblings, 0 replies; 28+ messages in thread
From: Chancel Liu @ 2026-09-23 6:13 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Oder Chiou, Cezary Rojewski
Cc: Support Opensource, linux-sound, linux-kernel, patches
From: Chancel Liu <chancel.liu@nxp.com>
component->dev is the underlying i2c device whose devres is only
released on physical device removal, not on ASoC card unbind. Getting
the regulator supplies with devm_regulator_bulk_get(component->dev, ...)
in the component probe therefore leaks the regulator references on every
card bind/unbind cycle.
Move the devm_regulator_bulk_get() to the i2c probe so the supplies are
tied to the physical device lifetime.
Reviewed-by: Charles Keepax <ckeepax@opensource.cirrus.com>
Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
---
sound/soc/codecs/wm8955.c | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/sound/soc/codecs/wm8955.c b/sound/soc/codecs/wm8955.c
index 1d367a957f3e..017ec2e37d1e 100644
--- a/sound/soc/codecs/wm8955.c
+++ b/sound/soc/codecs/wm8955.c
@@ -888,17 +888,7 @@ static int wm8955_probe(struct snd_soc_component *component)
struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component);
struct wm8955_priv *wm8955 = snd_soc_component_get_drvdata(component);
struct wm8955_pdata *pdata = dev_get_platdata(component->dev);
- int ret, i;
-
- for (i = 0; i < ARRAY_SIZE(wm8955->supplies); i++)
- wm8955->supplies[i].supply = wm8955_supply_names[i];
-
- ret = devm_regulator_bulk_get(component->dev, ARRAY_SIZE(wm8955->supplies),
- wm8955->supplies);
- if (ret != 0) {
- dev_err(component->dev, "Failed to request supplies: %d\n", ret);
- return ret;
- }
+ int ret;
ret = regulator_bulk_enable(ARRAY_SIZE(wm8955->supplies),
wm8955->supplies);
@@ -990,7 +980,7 @@ static const struct regmap_config wm8955_regmap = {
static int wm8955_i2c_probe(struct i2c_client *i2c)
{
struct wm8955_priv *wm8955;
- int ret;
+ int i, ret;
wm8955 = devm_kzalloc(&i2c->dev, sizeof(struct wm8955_priv),
GFP_KERNEL);
@@ -1007,6 +997,16 @@ static int wm8955_i2c_probe(struct i2c_client *i2c)
i2c_set_clientdata(i2c, wm8955);
+ for (i = 0; i < ARRAY_SIZE(wm8955->supplies); i++)
+ wm8955->supplies[i].supply = wm8955_supply_names[i];
+
+ ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(wm8955->supplies),
+ wm8955->supplies);
+ if (ret != 0) {
+ dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret);
+ return ret;
+ }
+
ret = devm_snd_soc_register_component(&i2c->dev,
&soc_component_dev_wm8955, &wm8955_dai, 1);
--
2.50.1
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v2 17/21] ASoC: codecs: es8389: Move regulator and mclk acquisition to the i2c probe
2026-09-23 6:12 [PATCH v2 00/21] ASoC: codecs: Fix resource leaks across card bind/unbind Chancel Liu
` (15 preceding siblings ...)
2026-09-23 6:13 ` [PATCH v2 16/21] ASoC: codecs: wm8955: Move regulator acquisition to the i2c probe Chancel Liu
@ 2026-09-23 6:13 ` Chancel Liu
2026-09-23 6:13 ` [PATCH v2 18/21] ASoC: codecs: rt5677-spi: Free the DSP context on component remove Chancel Liu
` (4 subsequent siblings)
21 siblings, 0 replies; 28+ messages in thread
From: Chancel Liu @ 2026-09-23 6:13 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Oder Chiou, Cezary Rojewski
Cc: Support Opensource, linux-sound, linux-kernel, patches
From: Chancel Liu <chancel.liu@nxp.com>
component->dev is the underlying i2c device whose devres is only
released on physical device removal, not on ASoC card unbind. Getting
the core regulator supplies and the mclk with devm_*(component->dev, ...)
in the component probe therefore leaks those references on every card
bind/unbind cycle.
Move the devm_regulator_bulk_get() and devm_clk_get_optional() into the
i2c probe so the resources are tied to the physical device lifetime.
Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
---
sound/soc/codecs/es8389.c | 36 ++++++++++++++++++------------------
1 file changed, 18 insertions(+), 18 deletions(-)
diff --git a/sound/soc/codecs/es8389.c b/sound/soc/codecs/es8389.c
index fe341fe56760..267806b72f87 100644
--- a/sound/soc/codecs/es8389.c
+++ b/sound/soc/codecs/es8389.c
@@ -1067,7 +1067,7 @@ static int es8389_resume(struct snd_soc_component *component)
static int es8389_probe(struct snd_soc_component *component)
{
- int ret, i;
+ int ret;
struct es8389_private *es8389 = snd_soc_component_get_drvdata(component);
ret = device_property_read_u8(component->dev, "everest,mclk-src", &es8389->mclk_src);
@@ -1076,22 +1076,6 @@ static int es8389_probe(struct snd_soc_component *component)
es8389->mclk_src = ES8389_MCLK_SOURCE;
}
- for (i = 0; i < ARRAY_SIZE(es8389_core_supplies); i++)
- es8389->core_supply[i].supply = es8389_core_supplies[i];
- ret = devm_regulator_bulk_get(component->dev, ARRAY_SIZE(es8389_core_supplies), es8389->core_supply);
- if (ret) {
- dev_err(component->dev, "Failed to request core supplies %d\n", ret);
- return ret;
- }
-
- es8389->mclk = devm_clk_get_optional(component->dev, "mclk");
- if (IS_ERR(es8389->mclk))
- return dev_err_probe(component->dev, PTR_ERR(es8389->mclk),
- "ES8389 is unable to get mclk\n");
-
- if (!es8389->mclk)
- dev_err(component->dev, "%s, assuming static mclk\n", __func__);
-
ret = clk_prepare_enable(es8389->mclk);
if (ret) {
dev_err(component->dev, "%s, unable to enable mclk\n", __func__);
@@ -1179,7 +1163,7 @@ static void es8389_i2c_shutdown(struct i2c_client *i2c)
static int es8389_i2c_probe(struct i2c_client *i2c_client)
{
struct es8389_private *es8389;
- int ret;
+ int ret, i;
es8389 = devm_kzalloc(&i2c_client->dev, sizeof(*es8389), GFP_KERNEL);
if (es8389 == NULL)
@@ -1191,6 +1175,22 @@ static int es8389_i2c_probe(struct i2c_client *i2c_client)
return dev_err_probe(&i2c_client->dev, PTR_ERR(es8389->regmap),
"regmap_init() failed\n");
+ for (i = 0; i < ARRAY_SIZE(es8389_core_supplies); i++)
+ es8389->core_supply[i].supply = es8389_core_supplies[i];
+ ret = devm_regulator_bulk_get(&i2c_client->dev, ARRAY_SIZE(es8389_core_supplies),
+ es8389->core_supply);
+ if (ret)
+ return dev_err_probe(&i2c_client->dev, ret,
+ "Failed to request core supplies\n");
+
+ es8389->mclk = devm_clk_get_optional(&i2c_client->dev, "mclk");
+ if (IS_ERR(es8389->mclk))
+ return dev_err_probe(&i2c_client->dev, PTR_ERR(es8389->mclk),
+ "ES8389 is unable to get mclk\n");
+
+ if (!es8389->mclk)
+ dev_err(&i2c_client->dev, "%s, assuming static mclk\n", __func__);
+
ret = devm_snd_soc_register_component(&i2c_client->dev,
&soc_codec_dev_es8389,
&es8389_dai,
--
2.50.1
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v2 18/21] ASoC: codecs: rt5677-spi: Free the DSP context on component remove
2026-09-23 6:12 [PATCH v2 00/21] ASoC: codecs: Fix resource leaks across card bind/unbind Chancel Liu
` (16 preceding siblings ...)
2026-09-23 6:13 ` [PATCH v2 17/21] ASoC: codecs: es8389: Move regulator and mclk " Chancel Liu
@ 2026-09-23 6:13 ` Chancel Liu
2026-09-23 6:13 ` [PATCH v2 19/21] ASoC: codecs: rt1011: Free the bq/drc coefficient arrays " Chancel Liu
` (3 subsequent siblings)
21 siblings, 0 replies; 28+ messages in thread
From: Chancel Liu @ 2026-09-23 6:13 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Oder Chiou, Cezary Rojewski
Cc: Support Opensource, linux-sound, linux-kernel, patches
From: Chancel Liu <chancel.liu@nxp.com>
component->dev is the underlying SPI bus device, whose devres lifetime
follows the physical devices probe/remove rather than the ASoC cards
bind/unbind. The rt5677_dsp context allocated in the component probe with
devm_kzalloc(component->dev, ...) therefore leaks on every card
bind/unbind cycle, and the delayed work initialised there is never
cancelled on unbind.
Allocate the context with kzalloc() and add a component .remove callback
that cancels the copy work, destroys the mutex and frees the context.
The exported rt5677_spi_hotword_detected(), called from the rt5677 codec
interrupt path, fetches the context via dev_get_drvdata(&g_spi->dev) and
dereferences it. Freeing the context on remove would let that callback
run concurrently and touch freed memory. Add a dsp_lock that serializes
the fetch/use in rt5677_spi_hotword_detected() against clearing the
drvdata in the remove callback, so once remove has cleared the pointer
the callback either observes NULL and bails out or has already finished.
Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
---
sound/soc/codecs/rt5677-spi.c | 21 +++++++++++++++++++--
1 file changed, 19 insertions(+), 2 deletions(-)
diff --git a/sound/soc/codecs/rt5677-spi.c b/sound/soc/codecs/rt5677-spi.c
index ebc527115ea5..0b2166082437 100644
--- a/sound/soc/codecs/rt5677-spi.c
+++ b/sound/soc/codecs/rt5677-spi.c
@@ -58,6 +58,8 @@
static struct spi_device *g_spi;
static DEFINE_MUTEX(spi_mutex);
+/* Serializes access to the DSP context against the exported hotword callback */
+static DEFINE_MUTEX(dsp_lock);
struct rt5677_dsp {
struct device *dev;
@@ -380,8 +382,7 @@ static int rt5677_spi_pcm_probe(struct snd_soc_component *component)
{
struct rt5677_dsp *rt5677_dsp;
- rt5677_dsp = devm_kzalloc(component->dev, sizeof(*rt5677_dsp),
- GFP_KERNEL);
+ rt5677_dsp = kzalloc_obj(*rt5677_dsp);
if (!rt5677_dsp)
return -ENOMEM;
rt5677_dsp->dev = &g_spi->dev;
@@ -392,9 +393,23 @@ static int rt5677_spi_pcm_probe(struct snd_soc_component *component)
return 0;
}
+static void rt5677_spi_pcm_remove(struct snd_soc_component *component)
+{
+ struct rt5677_dsp *rt5677_dsp =
+ snd_soc_component_get_drvdata(component);
+
+ scoped_guard(mutex, &dsp_lock)
+ snd_soc_component_set_drvdata(component, NULL);
+
+ cancel_delayed_work_sync(&rt5677_dsp->copy_work);
+ mutex_destroy(&rt5677_dsp->dma_lock);
+ kfree(rt5677_dsp);
+}
+
static const struct snd_soc_component_driver rt5677_spi_dai_component = {
.name = DRV_NAME,
.probe = rt5677_spi_pcm_probe,
+ .remove = rt5677_spi_pcm_remove,
.open = rt5677_spi_pcm_open,
.close = rt5677_spi_pcm_close,
.hw_params = rt5677_spi_hw_params,
@@ -579,6 +594,8 @@ void rt5677_spi_hotword_detected(void)
if (!g_spi)
return;
+ guard(mutex)(&dsp_lock);
+
rt5677_dsp = dev_get_drvdata(&g_spi->dev);
if (!rt5677_dsp) {
dev_err(&g_spi->dev, "Can't get rt5677_dsp\n");
--
2.50.1
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v2 19/21] ASoC: codecs: rt1011: Free the bq/drc coefficient arrays on component remove
2026-09-23 6:12 [PATCH v2 00/21] ASoC: codecs: Fix resource leaks across card bind/unbind Chancel Liu
` (17 preceding siblings ...)
2026-09-23 6:13 ` [PATCH v2 18/21] ASoC: codecs: rt5677-spi: Free the DSP context on component remove Chancel Liu
@ 2026-09-23 6:13 ` Chancel Liu
2026-09-23 6:13 ` [PATCH v2 20/21] ASoC: codecs: rt5645: Free the hardware EQ parameters " Chancel Liu
` (2 subsequent siblings)
21 siblings, 0 replies; 28+ messages in thread
From: Chancel Liu @ 2026-09-23 6:13 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Oder Chiou, Cezary Rojewski
Cc: Support Opensource, linux-sound, linux-kernel, patches
From: Chancel Liu <chancel.liu@nxp.com>
component->dev is the underlying i2c device whose devres is only
released on physical device removal, not on ASoC card unbind. The
bq_drc_params arrays allocated in the component probe with
devm_kcalloc(component->dev, ...) therefore leak on every card
bind/unbind cycle.
Allocate the arrays with kcalloc() and free them in the component
remove callback.
Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
---
sound/soc/codecs/rt1011.c | 30 +++++++++++++++++++++++-------
1 file changed, 23 insertions(+), 7 deletions(-)
diff --git a/sound/soc/codecs/rt1011.c b/sound/soc/codecs/rt1011.c
index d47b0370dd6b..c140c079838d 100644
--- a/sound/soc/codecs/rt1011.c
+++ b/sound/soc/codecs/rt1011.c
@@ -2053,29 +2053,45 @@ static int rt1011_probe(struct snd_soc_component *component)
schedule_work(&rt1011->cali_work);
rt1011->i2s_ref = 0;
- rt1011->bq_drc_params = devm_kcalloc(component->dev,
- RT1011_ADVMODE_NUM, sizeof(struct rt1011_bq_drc_params *),
- GFP_KERNEL);
+ rt1011->bq_drc_params = kcalloc(RT1011_ADVMODE_NUM,
+ sizeof(struct rt1011_bq_drc_params *),
+ GFP_KERNEL);
if (!rt1011->bq_drc_params)
return -ENOMEM;
for (i = 0; i < RT1011_ADVMODE_NUM; i++) {
- rt1011->bq_drc_params[i] = devm_kcalloc(component->dev,
- RT1011_BQ_DRC_NUM, sizeof(struct rt1011_bq_drc_params),
- GFP_KERNEL);
+ rt1011->bq_drc_params[i] = kcalloc(RT1011_BQ_DRC_NUM,
+ sizeof(struct rt1011_bq_drc_params),
+ GFP_KERNEL);
if (!rt1011->bq_drc_params[i])
- return -ENOMEM;
+ goto err;
}
return 0;
+
+err:
+ while (i--)
+ kfree(rt1011->bq_drc_params[i]);
+ kfree(rt1011->bq_drc_params);
+ rt1011->bq_drc_params = NULL;
+
+ return -ENOMEM;
}
static void rt1011_remove(struct snd_soc_component *component)
{
struct rt1011_priv *rt1011 = snd_soc_component_get_drvdata(component);
+ int i;
cancel_work_sync(&rt1011->cali_work);
rt1011_reset(rt1011->regmap);
+
+ if (rt1011->bq_drc_params) {
+ for (i = 0; i < RT1011_ADVMODE_NUM; i++)
+ kfree(rt1011->bq_drc_params[i]);
+ kfree(rt1011->bq_drc_params);
+ rt1011->bq_drc_params = NULL;
+ }
}
#ifdef CONFIG_PM
--
2.50.1
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v2 20/21] ASoC: codecs: rt5645: Free the hardware EQ parameters on component remove
2026-09-23 6:12 [PATCH v2 00/21] ASoC: codecs: Fix resource leaks across card bind/unbind Chancel Liu
` (18 preceding siblings ...)
2026-09-23 6:13 ` [PATCH v2 19/21] ASoC: codecs: rt1011: Free the bq/drc coefficient arrays " Chancel Liu
@ 2026-09-23 6:13 ` Chancel Liu
2026-09-23 6:13 ` [PATCH v2 21/21] ASoC: codecs: rt5514-spi: Free the DSP context " Chancel Liu
2026-09-23 12:27 ` [PATCH v2 00/21] ASoC: codecs: Fix resource leaks across card bind/unbind Mark Brown
21 siblings, 0 replies; 28+ messages in thread
From: Chancel Liu @ 2026-09-23 6:13 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Oder Chiou, Cezary Rojewski
Cc: Support Opensource, linux-sound, linux-kernel, patches
From: Chancel Liu <chancel.liu@nxp.com>
component->dev is the underlying I2C bus device, whose devres lifetime
follows the physical device's probe/remove rather than the ASoC card's
bind/unbind. The eq_param array was allocated in rt5645_probe() via
devm_kcalloc(component->dev, ...) but never released on component remove,
so it leaked one allocation on every card bind/unbind cycle.
Allocate eq_param with plain kcalloc() in rt5645_probe() and free it
explicitly in rt5645_remove() to keep its lifetime tied to the component.
Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
---
sound/soc/codecs/rt5645.c | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c
index bb448254275f..15d21097ed0c 100644
--- a/sound/soc/codecs/rt5645.c
+++ b/sound/soc/codecs/rt5645.c
@@ -3487,10 +3487,8 @@ static int rt5645_probe(struct snd_soc_component *component)
if (rt5645->pdata.long_name)
component->card->long_name = rt5645->pdata.long_name;
- rt5645->eq_param = devm_kcalloc(component->dev,
- RT5645_HWEQ_NUM, sizeof(struct rt5645_eq_param_s),
- GFP_KERNEL);
-
+ rt5645->eq_param = kcalloc(RT5645_HWEQ_NUM,
+ sizeof(struct rt5645_eq_param_s), GFP_KERNEL);
if (!rt5645->eq_param)
return -ENOMEM;
@@ -3503,7 +3501,12 @@ static int rt5645_probe(struct snd_soc_component *component)
static void rt5645_remove(struct snd_soc_component *component)
{
+ struct rt5645_priv *rt5645 = snd_soc_component_get_drvdata(component);
+
rt5645_reset(component);
+
+ kfree(rt5645->eq_param);
+ rt5645->eq_param = NULL;
}
#ifdef CONFIG_PM
--
2.50.1
^ permalink raw reply [flat|nested] 28+ messages in thread* [PATCH v2 21/21] ASoC: codecs: rt5514-spi: Free the DSP context on component remove
2026-09-23 6:12 [PATCH v2 00/21] ASoC: codecs: Fix resource leaks across card bind/unbind Chancel Liu
` (19 preceding siblings ...)
2026-09-23 6:13 ` [PATCH v2 20/21] ASoC: codecs: rt5645: Free the hardware EQ parameters " Chancel Liu
@ 2026-09-23 6:13 ` Chancel Liu
2026-09-23 12:27 ` [PATCH v2 00/21] ASoC: codecs: Fix resource leaks across card bind/unbind Mark Brown
21 siblings, 0 replies; 28+ messages in thread
From: Chancel Liu @ 2026-09-23 6:13 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Oder Chiou, Cezary Rojewski
Cc: Support Opensource, linux-sound, linux-kernel, patches
From: Chancel Liu <chancel.liu@nxp.com>
component->dev is the underlying SPI bus device, whose devres lifetime
follows the physical device's probe/remove rather than the ASoC card's
bind/unbind. The rt5514_dsp context was allocated in
rt5514_spi_pcm_probe() via devm_kzalloc(component->dev, ...) and its
delayed work and wakeup source were never torn down on component remove,
so the context leaked and the pending copy work / wakeup source were
left dangling on every card bind/unbind cycle.
Allocate the context with plain kzalloc() and add a component .remove
callback that cancels the copy work, undoes device_init_wakeup(),
destroys the mutex and frees the context.
Signed-off-by: Chancel Liu <chancel.liu@nxp.com>
---
sound/soc/codecs/rt5514-spi.c | 37 +++++++++++++++++++++++++++--------
1 file changed, 29 insertions(+), 8 deletions(-)
diff --git a/sound/soc/codecs/rt5514-spi.c b/sound/soc/codecs/rt5514-spi.c
index 91290bfe8daa..bcc2ff28b125 100644
--- a/sound/soc/codecs/rt5514-spi.c
+++ b/sound/soc/codecs/rt5514-spi.c
@@ -43,6 +43,7 @@ struct rt5514_dsp {
struct snd_pcm_substream *substream;
unsigned int buf_base, buf_limit, buf_rp;
size_t buf_size, get_size, dma_offset;
+ int irq;
};
static const struct snd_pcm_hardware rt5514_spi_pcm_hardware = {
@@ -257,8 +258,7 @@ static int rt5514_spi_pcm_probe(struct snd_soc_component *component)
struct rt5514_dsp *rt5514_dsp;
int ret;
- rt5514_dsp = devm_kzalloc(component->dev, sizeof(*rt5514_dsp),
- GFP_KERNEL);
+ rt5514_dsp = kzalloc_obj(*rt5514_dsp);
if (!rt5514_dsp)
return -ENOMEM;
@@ -268,21 +268,41 @@ static int rt5514_spi_pcm_probe(struct snd_soc_component *component)
snd_soc_component_set_drvdata(component, rt5514_dsp);
if (rt5514_spi->irq) {
- ret = devm_request_threaded_irq(&rt5514_spi->dev,
- rt5514_spi->irq, NULL, rt5514_spi_irq,
- IRQF_TRIGGER_RISING | IRQF_ONESHOT, "rt5514-spi",
- rt5514_dsp);
- if (ret)
+ ret = request_threaded_irq(rt5514_spi->irq, NULL,
+ rt5514_spi_irq,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ "rt5514-spi", rt5514_dsp);
+ if (ret) {
dev_err(&rt5514_spi->dev,
"%s Failed to request IRQ: %d\n", __func__,
ret);
- else
+ } else {
+ rt5514_dsp->irq = rt5514_spi->irq;
device_init_wakeup(rt5514_dsp->dev, true);
+ }
}
return 0;
}
+static void rt5514_spi_pcm_remove(struct snd_soc_component *component)
+{
+ struct rt5514_dsp *rt5514_dsp =
+ snd_soc_component_get_drvdata(component);
+
+ snd_soc_component_set_drvdata(component, NULL);
+
+ if (rt5514_dsp->irq) {
+ free_irq(rt5514_dsp->irq, rt5514_dsp);
+ device_init_wakeup(rt5514_dsp->dev, false);
+ }
+
+ cancel_delayed_work_sync(&rt5514_dsp->copy_work);
+
+ mutex_destroy(&rt5514_dsp->dma_lock);
+ kfree(rt5514_dsp);
+}
+
static int rt5514_spi_pcm_new(struct snd_soc_component *component,
struct snd_soc_pcm_runtime *rtd)
{
@@ -294,6 +314,7 @@ static int rt5514_spi_pcm_new(struct snd_soc_component *component,
static const struct snd_soc_component_driver rt5514_spi_component = {
.name = DRV_NAME,
.probe = rt5514_spi_pcm_probe,
+ .remove = rt5514_spi_pcm_remove,
.open = rt5514_spi_pcm_open,
.hw_params = rt5514_spi_hw_params,
.hw_free = rt5514_spi_hw_free,
--
2.50.1
^ permalink raw reply [flat|nested] 28+ messages in thread* Re: [PATCH v2 00/21] ASoC: codecs: Fix resource leaks across card bind/unbind
2026-09-23 6:12 [PATCH v2 00/21] ASoC: codecs: Fix resource leaks across card bind/unbind Chancel Liu
` (20 preceding siblings ...)
2026-09-23 6:13 ` [PATCH v2 21/21] ASoC: codecs: rt5514-spi: Free the DSP context " Chancel Liu
@ 2026-09-23 12:27 ` Mark Brown
21 siblings, 0 replies; 28+ messages in thread
From: Mark Brown @ 2026-09-23 12:27 UTC (permalink / raw)
To: Liam Girdwood, Jaroslav Kysela, Takashi Iwai, Charles Keepax,
Oder Chiou, Cezary Rojewski, Chancel Liu
Cc: Support Opensource, linux-sound, linux-kernel, patches
On Wed, 23 Sep 2026 15:12:44 +0900, Chancel Liu wrote:
> ASoC: codecs: Fix resource leaks across card bind/unbind
>
> From: Chancel Liu <chancel.liu@nxp.com>
>
> For an ASoC component, component->dev is the underlying bus device
> (I2C / SPI / platform). Its devres lifetime is tied to the physical
> device's probe/remove, not to the ASoC card's bind/unbind.
>
> [...]
Applied to
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-7.4
Thanks!
[01/21] ASoC: codecs: cpcap: Fix devm resource leaks across card bind/unbind
https://git.kernel.org/broonie/sound/c/3713c74a1b0a
[02/21] ASoC: codecs: da7218: Fix devm resource leaks across card bind/unbind
https://git.kernel.org/broonie/sound/c/116f1bd60ae3
[03/21] ASoC: codecs: tlv320aic32x4: Fix clock leak from runtime callbacks
https://git.kernel.org/broonie/sound/c/ef8caf5d2784
[04/21] ASoC: codecs: twl4030: Acquire board params and hs_extmute GPIO in the platform probe
https://git.kernel.org/broonie/sound/c/99fff20db8de
[05/21] ASoC: codecs: es8316: Move mclk acquisition to the i2c probe
https://git.kernel.org/broonie/sound/c/d009170e12ad
[06/21] ASoC: codecs: es8323: Move mclk acquisition to the i2c probe
https://git.kernel.org/broonie/sound/c/be9bc47e2569
[07/21] ASoC: codecs: es8311: Move mclk acquisition to the i2c probe
https://git.kernel.org/broonie/sound/c/5cc3c8f7264e
[08/21] ASoC: codecs: es8328: Move clk acquisition to the bus probe
https://git.kernel.org/broonie/sound/c/10a45015665e
[09/21] ASoC: codecs: rt5640: Move mclk acquisition to the i2c probe
https://git.kernel.org/broonie/sound/c/e68e66353b8e
[10/21] ASoC: codecs: rt5616: Move mclk acquisition to the i2c probe
https://git.kernel.org/broonie/sound/c/b5c9df40ca56
[11/21] ASoC: codecs: rt5514: Move clk acquisition to the i2c probe
https://git.kernel.org/broonie/sound/c/cc2977e80218
[12/21] ASoC: codecs: rt5682s: Move mclk acquisition to the i2c probe
https://git.kernel.org/broonie/sound/c/c580cd416b68
[13/21] ASoC: codecs: max98090: Move mclk acquisition to the i2c probe
https://git.kernel.org/broonie/sound/c/bf4379a7195e
[14/21] ASoC: codecs: max98095: Move mclk acquisition to the i2c probe
https://git.kernel.org/broonie/sound/c/8635b9033d03
[15/21] ASoC: codecs: wm8985: Move regulator acquisition to the bus probe
https://git.kernel.org/broonie/sound/c/fe15e876d9ee
[16/21] ASoC: codecs: wm8955: Move regulator acquisition to the i2c probe
https://git.kernel.org/broonie/sound/c/07bcfafa4719
[17/21] ASoC: codecs: es8389: Move regulator and mclk acquisition to the i2c probe
https://git.kernel.org/broonie/sound/c/5ef6e7ff52db
[18/21] ASoC: codecs: rt5677-spi: Free the DSP context on component remove
https://git.kernel.org/broonie/sound/c/57d1e55c7b5a
[19/21] ASoC: codecs: rt1011: Free the bq/drc coefficient arrays on component remove
https://git.kernel.org/broonie/sound/c/48f1dfbfe4e5
[20/21] ASoC: codecs: rt5645: Free the hardware EQ parameters on component remove
https://git.kernel.org/broonie/sound/c/6e7f4c5b30a3
[21/21] ASoC: codecs: rt5514-spi: Free the DSP context on component remove
https://git.kernel.org/broonie/sound/c/3943b84e063f
All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying
to this mail.
Thanks,
Mark
^ permalink raw reply [flat|nested] 28+ messages in thread