// SPDX-License-Identifier: GPL-2.0 // hdac_component.c - routines for sync between HD-A core and DRM driver #include <linux/init.h> #include <linux/module.h> #include <linux/pci.h> #include <linux/component.h> #include <sound/core.h> #include <sound/hdaudio.h> #include <sound/hda_component.h> #include <sound/hda_register.h> static void hdac_acomp_release(struct device *dev, void *res) { } static struct drm_audio_component *hdac_get_acomp(struct device *dev) { return devres_find(dev, hdac_acomp_release, NULL, NULL); } /** * snd_hdac_set_codec_wakeup - Enable / disable HDMI/DP codec wakeup * @bus: HDA core bus * @enable: enable or disable the wakeup * * This function is supposed to be used only by a HD-audio controller * driver that needs the interaction with graphics driver. * * This function should be called during the chip reset, also called at * resume for updating STATESTS register read. * * Returns zero for success or a negative error code. */ int snd_hdac_set_codec_wakeup(struct hdac_bus *bus, bool enable) { struct drm_audio_component *acomp = bus->audio_component; if (!acomp || !acomp->ops) return -ENODEV; if (!acomp->ops->codec_wake_override) return 0; dev_dbg(bus->dev, "%s codec wakeup\n", enable ? "enable" : "disable"); acomp->ops->codec_wake_override(acomp->dev, enable); return 0; } EXPORT_SYMBOL_GPL(snd_hdac_set_codec_wakeup); /** * snd_hdac_display_power - Power up / down the power refcount * @bus: HDA core bus * @enable: power up or down * * This function is supposed to be used only by a HD-audio controller * driver that needs the interaction with graphics driver. * * This function manages a refcount and calls the get_power() and * put_power() ops accordingly, toggling the codec wakeup, too. * * Returns zero for success or a negative error code. */ int snd_hdac_display_power(struct hdac_bus *bus, bool enable) { struct drm_audio_component *acomp = bus->audio_component; if (!acomp || !acomp->ops) return -ENODEV; dev_dbg(bus->dev, "display power %s\n", enable ? "enable" : "disable"); if (enable) { if (!bus->drm_power_refcount++) { if (acomp->ops->get_power) acomp->ops->get_power(acomp->dev); snd_hdac_set_codec_wakeup(bus, true); snd_hdac_set_codec_wakeup(bus, false); } } else { WARN_ON(!bus->drm_power_refcount); if (!--bus->drm_power_refcount) if (acomp->ops->put_power) acomp->ops->put_power(acomp->dev); } return 0; } EXPORT_SYMBOL_GPL(snd_hdac_display_power); /** * snd_hdac_sync_audio_rate - Set N/CTS based on the sample rate * @codec: HDA codec * @nid: the pin widget NID * @dev_id: device identifier * @rate: the sample rate to set * * This function is supposed to be used only by a HD-audio controller * driver that needs the interaction with graphics driver. * * This function sets N/CTS value based on the given sample rate. * Returns zero for success, or a negative error code. */ int snd_hdac_sync_audio_rate(struct hdac_device *codec, hda_nid_t nid, int dev_id, int rate) { struct hdac_bus *bus = codec->bus; struct drm_audio_component *acomp = bus->audio_component; int port, pipe; if (!acomp || !acomp->ops || !acomp->ops->sync_audio_rate) return -ENODEV; port = nid; if (acomp->audio_ops && acomp->audio_ops->pin2port) { port = acomp->audio_ops->pin2port(codec, nid); if (port < 0) return -EINVAL; } pipe = dev_id; return acomp->ops->sync_audio_rate(acomp->dev, port, pipe, rate); } EXPORT_SYMBOL_GPL(snd_hdac_sync_audio_rate); /** * snd_hdac_acomp_get_eld - Get the audio state and ELD via component * @codec: HDA codec * @nid: the pin widget NID * @dev_id: device identifier * @audio_enabled: the pointer to store the current audio state * @buffer: the buffer pointer to store ELD bytes * @max_bytes: the max bytes to be stored on @buffer * * This function is supposed to be used only by a HD-audio controller * driver that needs the interaction with graphics driver. * * This function queries the current state of the audio on the given * digital port and fetches the ELD bytes onto the given buffer. * It returns the number of bytes for the total ELD data, zero for * invalid ELD, or a negative error code. * * The return size is the total bytes required for the whole ELD bytes, * thus it may be over @max_bytes. If it's over @max_bytes, it implies * that only a part of ELD bytes have been fetched. */ int snd_hdac_acomp_get_eld(struct hdac_device *codec, hda_nid_t nid, int dev_id, bool *audio_enabled, char *buffer, int max_bytes) { struct hdac_bus *bus = codec->bus; struct drm_audio_component *acomp = bus->audio_component; int port, pipe; if (!acomp || !acomp->ops || !acomp->ops->get_eld) return -ENODEV; port = nid; if (acomp->audio_ops && acomp->audio_ops->pin2port) { port = acomp->audio_ops->pin2port(codec, nid); if (port < 0) return -EINVAL; } pipe = dev_id; return acomp->ops->get_eld(acomp->dev, port, pipe, audio_enabled, buffer, max_bytes); } EXPORT_SYMBOL_GPL(snd_hdac_acomp_get_eld); static int hdac_component_master_bind(struct device *dev) { struct drm_audio_component *acomp = hdac_get_acomp(dev); int ret; if (WARN_ON(!acomp)) return -EINVAL; ret = component_bind_all(dev, acomp); if (ret < 0) return ret; if (WARN_ON(!(acomp->dev && acomp->ops))) { ret = -EINVAL; goto out_unbind; } /* pin the module to avoid dynamic unbinding, but only if given */ if (!try_module_get(acomp->ops->owner)) { ret = -ENODEV; goto out_unbind; } if (acomp->audio_ops && acomp->audio_ops->master_bind) { ret = acomp->audio_ops->master_bind(dev, acomp); if (ret < 0) goto module_put; } return 0; module_put: module_put(acomp->ops->owner); out_unbind: component_unbind_all(dev, acomp); return ret; } static void hdac_component_master_unbind(struct device *dev) { struct drm_audio_component *acomp = hdac_get_acomp(dev); if (acomp->audio_ops && acomp->audio_ops->master_unbind) acomp->audio_ops->master_unbind(dev, acomp); module_put(acomp->ops->owner); component_unbind_all(dev, acomp); WARN_ON(acomp->ops || acomp->dev); } static const struct component_master_ops hdac_component_master_ops = { .bind = hdac_component_master_bind, .unbind = hdac_component_master_unbind, }; /** * snd_hdac_acomp_register_notifier - Register audio component ops * @bus: HDA core bus * @aops: audio component ops * * This function is supposed to be used only by a HD-audio controller * driver that needs the interaction with graphics driver. * * This function sets the given ops to be called by the graphics driver. * * Returns zero for success or a negative error code. */ int snd_hdac_acomp_register_notifier(struct hdac_bus *bus, const struct drm_audio_component_audio_ops *aops) { if (!bus->audio_component) return -ENODEV; bus->audio_component->audio_ops = aops; return 0; } EXPORT_SYMBOL_GPL(snd_hdac_acomp_register_notifier); /** * snd_hdac_acomp_init - Initialize audio component * @bus: HDA core bus * @match_master: match function for finding components * @extra_size: Extra bytes to allocate * * This function is supposed to be used only by a HD-audio controller * driver that needs the interaction with graphics driver. * * This function initializes and sets up the audio component to communicate * with graphics driver. * * Unlike snd_hdac_i915_init(), this function doesn't synchronize with the * binding with the DRM component. Each caller needs to sync via master_bind * audio_ops. * * Returns zero for success or a negative error code. */ int snd_hdac_acomp_init(struct hdac_bus *bus, const struct drm_audio_component_audio_ops *aops, int (*match_master)(struct device *, void *), size_t extra_size) { struct component_match *match = NULL; struct device *dev = bus->dev; struct drm_audio_component *acomp; int ret; if (WARN_ON(hdac_get_acomp(dev))) return -EBUSY; acomp = devres_alloc(hdac_acomp_release, sizeof(*acomp) + extra_size, GFP_KERNEL); if (!acomp) return -ENOMEM; acomp->audio_ops = aops; bus->audio_component = acomp; devres_add(dev, acomp); component_match_add(dev, &match, match_master, bus); ret = component_master_add_with_match(dev, &hdac_component_master_ops, match); if (ret < 0) goto out_err; return 0; out_err: bus->audio_component = NULL; devres_destroy(dev, hdac_acomp_release, NULL, NULL); dev_info(dev, "failed to add audio component master (%d)\n", ret); return ret; } EXPORT_SYMBOL_GPL(snd_hdac_acomp_init); /** * snd_hdac_acomp_exit - Finalize audio component * @bus: HDA core bus * * This function is supposed to be used only by a HD-audio controller * driver that needs the interaction with graphics driver. * * This function releases the audio component that has been used. * * Returns zero for success or a negative error code. */ int snd_hdac_acomp_exit(struct hdac_bus *bus) { struct device *dev = bus->dev; struct drm_audio_component *acomp = bus->audio_component; if (!acomp) return 0; WARN_ON(bus->drm_power_refcount); if (bus->drm_power_refcount > 0 && acomp->ops) acomp->ops->put_power(acomp->dev); component_master_del(dev, &hdac_component_master_ops); bus->audio_component = NULL; devres_destroy(dev, hdac_acomp_release, NULL, NULL); return 0; } EXPORT_SYMBOL_GPL(snd_hdac_acomp_exit);