diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-23 13:28:57 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-23 13:28:57 -0700 |
| commit | 4352b8aee98005853aa63f57d6377282de17a33f (patch) | |
| tree | 27614d7a9ef05c32d089004e104364ce42324837 /sound/hda/common/bind.c | |
| download | linux-stable-4352b8aee98005853aa63f57d6377282de17a33f.tar.gz linux-stable-4352b8aee98005853aa63f57d6377282de17a33f.zip | |
Merge tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linuxgrafted
Pull I3C updates from Alexandre Belloni:
"A new driver this cycle, for the AMD AXI I3C controller.
There are also new features such as IBI wakeup support, SETAASA device
discovery and ACPI support for the the DAA methods, meaning we can now
communicate with the SPD devices on DDR5 modules.
New driver:
- AMD AXI I3C master controller
Subsystem:
- I3C Common Command Code (CCC) handling improvements, especially
around GET CCCs
- SETAASA device discovery support
- ACPI support for all existing DAA methods like SETDASA, SETNEWDA as
well as I2C devices on I3C bus
- IBI-based wakeup support
Drivers:
- dw: SETAASA support
- mipi-i3c-hci: advertise IBI wakeup capability, AMD_PT I3C
controller support, PIO queue management support for HCI v1.2
- renesas: improve suspend to RAM support, add runtime PM support"
* tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux: (70 commits)
i3c: dw: reduce do_daa time if there's no client
i3c: mipi-i3c-hci: Add support for AMD_PT I3C controller
i3c: mipi-i3c-hci: Add PIO queue management support for HCI v1.2
i3c: mipi-i3c-hci: Fix missing STAT_IBI_STATUS_THLD in PIO mode
i3c: dw: rename "pclk" to "apb" to match dt-binding
i3c: dw: make struct dw_i3c_cmd smaller
i3c: dw: use COMMAND_PORT_TRANSFER_ARG instead of hardcoding
i3c: renesas: Don't register devices when ENTDAA times out
i3c: master: dw-i3c-master: fix OD timing for first broadcast
i3c: mipi-i3c-hci: Advertise IBI wakeup capability
i3c: mipi-i3c-hci: Factor out i3c_hci_sysdev()
i3c: mipi-i3c-hci-pci: Propagate I3C wakeup requirements to PCI
i3c: master: Reject IBI requests from non-IBI-capable devices
i3c: master: Add helper to query bus wakeup requirements
i3c: master: Report wakeup events for IBIs
i3c: master: Support IBI-based wakeup capability
i3c: master: Fix potential UAF in i3c_device_match()
i3c: master: Fix potential UAF in i3c_device_uevent()
i3c: Make dev->desc locking assumptions explicit
i3c: master: Fix use-after-free of master->this
...
Diffstat (limited to 'sound/hda/common/bind.c')
| -rw-r--r-- | sound/hda/common/bind.c | 345 |
1 files changed, 345 insertions, 0 deletions
diff --git a/sound/hda/common/bind.c b/sound/hda/common/bind.c new file mode 100644 index 000000000..6a728a773 --- /dev/null +++ b/sound/hda/common/bind.c @@ -0,0 +1,345 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HD-audio codec driver binding + * Copyright (c) Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/module.h> +#include <linux/export.h> +#include <linux/pm.h> +#include <sound/core.h> +#include <sound/hda_codec.h> +#include "hda_local.h" +#include "hda_jack.h" + +/* + * find a matching codec id + */ +static int hda_codec_match(struct hdac_device *dev, const struct hdac_driver *drv) +{ + struct hda_codec *codec = container_of(dev, struct hda_codec, core); + const struct hda_codec_driver *driver = + container_of(drv, struct hda_codec_driver, core); + const struct hda_device_id *list; + /* check probe_id instead of vendor_id if set */ + u32 id = codec->probe_id ? codec->probe_id : codec->core.vendor_id; + u32 rev_id = codec->core.revision_id; + + for (list = driver->id; list->vendor_id; list++) { + if (list->vendor_id == id && + (!list->rev_id || list->rev_id == rev_id)) { + codec->preset = list; + return 1; + } + } + return 0; +} + +/* process an unsolicited event */ +static void hda_codec_unsol_event(struct hdac_device *dev, unsigned int ev) +{ + struct hda_codec *codec = container_of(dev, struct hda_codec, core); + struct hda_codec_driver *driver = hda_codec_to_driver(codec); + + /* ignore unsol events during shutdown */ + if (codec->card->shutdown || codec->bus->shutdown) + return; + + /* ignore unsol events during system suspend/resume */ + if (codec->core.dev.power.power_state.event != PM_EVENT_ON) + return; + + if (driver->ops->unsol_event) + driver->ops->unsol_event(codec, ev); +} + +/** + * snd_hda_codec_set_name - set the codec name + * @codec: the HDA codec + * @name: name string to set + */ +int snd_hda_codec_set_name(struct hda_codec *codec, const char *name) +{ + int err; + + if (!name) + return 0; + err = snd_hdac_device_set_chip_name(&codec->core, name); + if (err < 0) + return err; + + /* update the mixer name */ + if (!*codec->card->mixername || + codec->bus->mixer_assigned >= codec->core.addr) { + snprintf(codec->card->mixername, + sizeof(codec->card->mixername), "%s %s", + codec->core.vendor_name, codec->core.chip_name); + codec->bus->mixer_assigned = codec->core.addr; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_codec_set_name); + +static int hda_codec_driver_probe(struct device *dev) +{ + struct hda_codec *codec = dev_to_hda_codec(dev); + struct module *owner = dev->driver->owner; + struct hda_codec_driver *driver = hda_codec_to_driver(codec); + int err; + + if (codec->bus->core.ext_ops) { + if (WARN_ON(!codec->bus->core.ext_ops->hdev_attach)) + return -EINVAL; + return codec->bus->core.ext_ops->hdev_attach(&codec->core); + } + + if (WARN_ON(!codec->preset)) + return -EINVAL; + + err = snd_hda_codec_set_name(codec, codec->preset->name); + if (err < 0) + goto error; + err = snd_hdac_regmap_init(&codec->core); + if (err < 0) + goto error; + + if (!try_module_get(owner)) { + err = -EINVAL; + goto error; + } + + if (WARN_ON(!(driver->ops && driver->ops->probe))) { + err = -EINVAL; + goto error_module_put; + } + + err = driver->ops->probe(codec, codec->preset); + if (err < 0) + goto error_module_put; + err = snd_hda_codec_build_pcms(codec); + if (err < 0) + goto error_module; + err = snd_hda_codec_build_controls(codec); + if (err < 0) + goto error_module; + /* only register after the bus probe finished; otherwise it's racy */ + if (!codec->bus->bus_probing && codec->card->registered) { + err = snd_card_register(codec->card); + if (err < 0) + goto error_module; + snd_hda_codec_register(codec); + } + + codec->core.lazy_cache = true; + return 0; + + error_module: + if (driver->ops->remove) + driver->ops->remove(codec); + error_module_put: + module_put(owner); + + error: + snd_hda_codec_cleanup_for_unbind(codec); + codec->preset = NULL; + return err; +} + +static int hda_codec_driver_remove(struct device *dev) +{ + struct hda_codec *codec = dev_to_hda_codec(dev); + struct hda_codec_driver *driver = hda_codec_to_driver(codec); + + if (codec->bus->core.ext_ops) { + if (WARN_ON(!codec->bus->core.ext_ops->hdev_detach)) + return -EINVAL; + return codec->bus->core.ext_ops->hdev_detach(&codec->core); + } + + snd_hda_codec_disconnect_pcms(codec); + snd_hda_jack_tbl_disconnect(codec); + snd_refcount_sync(&codec->pcm_ref); + snd_power_sync_ref(codec->bus->card); + + if (driver->ops->remove) + driver->ops->remove(codec); + snd_hda_codec_cleanup_for_unbind(codec); + codec->preset = NULL; + module_put(dev->driver->owner); + return 0; +} + +static void hda_codec_driver_shutdown(struct device *dev) +{ + snd_hda_codec_shutdown(dev_to_hda_codec(dev)); +} + +int __hda_codec_driver_register(struct hda_codec_driver *drv, const char *name, + struct module *owner) +{ + drv->core.driver.name = name; + drv->core.driver.owner = owner; + drv->core.driver.bus = &snd_hda_bus_type; + drv->core.driver.probe = hda_codec_driver_probe; + drv->core.driver.remove = hda_codec_driver_remove; + drv->core.driver.shutdown = hda_codec_driver_shutdown; + drv->core.driver.pm = pm_ptr(&hda_codec_driver_pm); + drv->core.type = HDA_DEV_LEGACY; + drv->core.match = hda_codec_match; + drv->core.unsol_event = hda_codec_unsol_event; + return driver_register(&drv->core.driver); +} +EXPORT_SYMBOL_GPL(__hda_codec_driver_register); + +void hda_codec_driver_unregister(struct hda_codec_driver *drv) +{ + driver_unregister(&drv->core.driver); +} +EXPORT_SYMBOL_GPL(hda_codec_driver_unregister); + +static inline bool codec_probed(struct hda_codec *codec) +{ + return device_attach(hda_codec_dev(codec)) > 0 && codec->preset; +} + +/* try to auto-load codec module */ +static void request_codec_module(struct hda_codec *codec) +{ +#ifdef MODULE + char modalias[32]; + const char *mod = NULL; + + switch (codec->probe_id) { + case HDA_CODEC_ID_GENERIC_HDMI: +#if IS_MODULE(CONFIG_SND_HDA_CODEC_HDMI) + mod = "snd-hda-codec-hdmi"; +#endif + break; + case HDA_CODEC_ID_GENERIC: +#if IS_MODULE(CONFIG_SND_HDA_GENERIC) + mod = "snd-hda-codec-generic"; +#endif + break; + default: + snd_hdac_codec_modalias(&codec->core, modalias, sizeof(modalias)); + mod = modalias; + break; + } + + if (mod) + request_module(mod); +#endif /* MODULE */ +} + +/* try to auto-load and bind the codec module */ +static void codec_bind_module(struct hda_codec *codec) +{ +#ifdef MODULE + request_codec_module(codec); + if (codec_probed(codec)) + return; +#endif +} + +#if IS_ENABLED(CONFIG_SND_HDA_CODEC_HDMI) +/* if all audio out widgets are digital, let's assume the codec as a HDMI/DP */ +static bool is_likely_hdmi_codec(struct hda_codec *codec) +{ + hda_nid_t nid; + + /* + * For ASoC users, if snd_hda_hdmi_codec module is denylisted and any + * event causes i915 enumeration to fail, ->wcaps remains uninitialized. + */ + if (!codec->wcaps) + return true; + + for_each_hda_codec_node(nid, codec) { + unsigned int wcaps = get_wcaps(codec, nid); + switch (get_wcaps_type(wcaps)) { + case AC_WID_AUD_IN: + return false; /* HDMI parser supports only HDMI out */ + case AC_WID_AUD_OUT: + if (!(wcaps & AC_WCAP_DIGITAL)) + return false; + break; + } + } + return true; +} +#else +/* no HDMI codec parser support */ +#define is_likely_hdmi_codec(codec) false +#endif /* CONFIG_SND_HDA_CODEC_HDMI */ + +static int codec_bind_generic(struct hda_codec *codec) +{ + if (codec->probe_id) + return -ENODEV; + + if (is_likely_hdmi_codec(codec)) { + codec->probe_id = HDA_CODEC_ID_GENERIC_HDMI; + request_codec_module(codec); + if (codec_probed(codec)) + return 0; + } + + codec->probe_id = HDA_CODEC_ID_GENERIC; + request_codec_module(codec); + if (codec_probed(codec)) + return 0; + return -ENODEV; +} + +#if IS_ENABLED(CONFIG_SND_HDA_GENERIC) +#define is_generic_config(codec) \ + (codec->modelname && !strcmp(codec->modelname, "generic")) +#else +#define is_generic_config(codec) 0 +#endif + +/** + * snd_hda_codec_configure - (Re-)configure the HD-audio codec + * @codec: the HDA codec + * + * Start parsing of the given codec tree and (re-)initialize the whole + * codec driver binding. + * + * Returns 0 if successful or a negative error code. + */ +int snd_hda_codec_configure(struct hda_codec *codec) +{ + int err; + + if (codec->configured) + return 0; + + if (is_generic_config(codec)) + codec->probe_id = HDA_CODEC_ID_GENERIC; + else + codec->probe_id = 0; + + if (!device_is_registered(&codec->core.dev)) { + err = snd_hdac_device_register(&codec->core); + if (err < 0) + return err; + } + + if (!codec->preset) + codec_bind_module(codec); + if (!codec->preset) { + err = codec_bind_generic(codec); + if (err < 0) { + codec_dbg(codec, "Unable to bind the codec\n"); + return err; + } + } + + codec->configured = 1; + return 0; +} +EXPORT_SYMBOL_GPL(snd_hda_codec_configure); |
