ASoC: hdac_hda: Fix module unbind/bind via sysfs

Peter Ujfalusi <peter.ujfalusi@linux.intel.com> says:

While looking at the reconfig lockup reported in
https://bugzilla.kernel.org/show_bug.cgi?id=222182
I checked whether the same can be done by unbinding and binding the HDA
codec driver via sysfs:

  echo -n ehdaudio0D0 > /sys/bus/hdaudio/drivers/snd_hda_codec_alc882/unbind
  echo -n ehdaudio0D0 > /sys/bus/hdaudio/drivers/snd_hda_codec_alc882/bind

The unbind works and the SOF card is removed, but the codec cannot be
bound again. This has been broken for a long time with hdac_hda; the
reconfig lockup itself is addressed by a separate patch.

Patch 1 and 2 fix the bind: the codec private data was lost on unbind
and the codec device type was left as HDA_DEV_ASOC, so no HDA codec
driver could match it anymore. Patch 2 depends on patch 1, otherwise
the bind would dereference NULL drvdata instead of failing.

Patch 3 fixes an unbalanced runtime PM disable in the component remove,
which leaves the codec with runtime PM disabled after the card is
re-created. This is also visible with machine driver unbind/bind.

Tested on PTL (ALC888 + iDisp): repeated codec and machine driver
unbind/bind, playback/capture, runtime suspend and module reload.

Link: https://patch.msgid.link/20261027083840.28813-1-peter.ujfalusi@linux.intel.com
This commit is contained in:
Mark Brown 2026-10-27 11:53:13 +00:00
commit 6eba74479d
2 changed files with 32 additions and 20 deletions

View File

@ -554,6 +554,7 @@ static void hdac_hda_codec_remove(struct snd_soc_component *component)
return;
}
/* prevent runtime PM callbacks while the link and the codec are torn down */
pm_runtime_disable(&hdev->dev);
snd_hdac_ext_bus_link_put(hdev->bus, hlink);
@ -561,6 +562,9 @@ static void hdac_hda_codec_remove(struct snd_soc_component *component)
driver->ops->remove(codec);
snd_hda_codec_cleanup_for_unbind(codec);
/* Balances the disable above, cleanup_for_unbind keeps runtime PM disabled */
pm_runtime_enable(&hdev->dev);
}
static const struct snd_soc_dapm_route hdac_hda_dapm_routes[] = {
@ -617,12 +621,35 @@ static const struct snd_soc_component_driver hdac_hda_hdmi_codec = {
.endianness = 1,
};
static void hdac_hda_restore_dev_type(void *data)
{
struct hdac_device *hdev = data;
hdev->type = HDA_DEV_LEGACY;
}
static int hdac_hda_dev_probe(struct hdac_device *hdev)
{
struct hdac_hda_priv *hda_pvt = dev_get_drvdata(&hdev->dev);
struct hdac_hda_priv *hda_pvt;
struct hdac_ext_link *hlink;
int ret;
hda_pvt = devm_kzalloc(&hdev->dev, sizeof(*hda_pvt), GFP_KERNEL);
if (!hda_pvt)
return -ENOMEM;
hda_pvt->codec = dev_to_hda_codec(&hdev->dev);
hda_pvt->dev_index = hdev->addr;
/* Intel iDisp (HDMI/DP) codec */
if (((hdev->vendor_id >> 16) & 0xFFFF) == 0x8086)
hda_pvt->need_display_power = true;
dev_set_drvdata(&hdev->dev, hda_pvt);
/* Allow the legacy codec driver to match again after unbind */
ret = devm_add_action(&hdev->dev, hdac_hda_restore_dev_type, hdev);
if (ret)
return ret;
/* hold the ref while we probe */
hlink = snd_hdac_ext_bus_get_hlink_by_name(hdev->bus, dev_name(&hdev->dev));
if (!hlink) {

View File

@ -15,8 +15,6 @@
#include "hda.h"
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
#include "../../codecs/hdac_hda.h"
#define CODEC_PROBE_RETRIES 3
#define IDISP_VID_INTEL 0x80860000
@ -148,7 +146,6 @@ static struct hda_codec *hda_codec_device_init(struct hdac_bus *bus, int addr, i
/* probe individual codec */
static int hda_codec_probe(struct snd_sof_dev *sdev, int address)
{
struct hdac_hda_priv *hda_priv;
struct hda_bus *hbus = sof_to_hbus(sdev);
struct hda_codec *codec;
u32 hda_cmd = (address << 28) | (AC_NODE_ROOT << 20) |
@ -168,27 +165,15 @@ static int hda_codec_probe(struct snd_sof_dev *sdev, int address)
dev_dbg(sdev->dev, "HDA codec #%d probed OK: response: %x\n",
address, resp);
hda_priv = devm_kzalloc(sdev->dev, sizeof(*hda_priv), GFP_KERNEL);
if (!hda_priv)
return -ENOMEM;
codec = hda_codec_device_init(&hbus->core, address, HDA_DEV_LEGACY);
ret = PTR_ERR_OR_ZERO(codec);
if (ret < 0)
return ret;
hda_priv->codec = codec;
hda_priv->dev_index = address;
dev_set_drvdata(&codec->core.dev, hda_priv);
if ((resp & 0xFFFF0000) == IDISP_VID_INTEL) {
if (!hbus->core.audio_component) {
dev_dbg(sdev->dev,
"iDisp hw present but no driver\n");
ret = -ENOENT;
goto out;
}
hda_priv->need_display_power = true;
if ((resp & 0xFFFF0000) == IDISP_VID_INTEL && !hbus->core.audio_component) {
dev_dbg(sdev->dev, "iDisp hw present but no driver\n");
ret = -ENOENT;
goto out;
}
if (is_generic_config(hbus))