162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci// hdac_component.c - routines for sync between HD-A core and DRM driver 362306a36Sopenharmony_ci 462306a36Sopenharmony_ci#include <linux/init.h> 562306a36Sopenharmony_ci#include <linux/module.h> 662306a36Sopenharmony_ci#include <linux/pci.h> 762306a36Sopenharmony_ci#include <linux/component.h> 862306a36Sopenharmony_ci#include <sound/core.h> 962306a36Sopenharmony_ci#include <sound/hdaudio.h> 1062306a36Sopenharmony_ci#include <sound/hda_component.h> 1162306a36Sopenharmony_ci#include <sound/hda_register.h> 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_cistatic void hdac_acomp_release(struct device *dev, void *res) 1462306a36Sopenharmony_ci{ 1562306a36Sopenharmony_ci} 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_cistatic struct drm_audio_component *hdac_get_acomp(struct device *dev) 1862306a36Sopenharmony_ci{ 1962306a36Sopenharmony_ci return devres_find(dev, hdac_acomp_release, NULL, NULL); 2062306a36Sopenharmony_ci} 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci/** 2362306a36Sopenharmony_ci * snd_hdac_set_codec_wakeup - Enable / disable HDMI/DP codec wakeup 2462306a36Sopenharmony_ci * @bus: HDA core bus 2562306a36Sopenharmony_ci * @enable: enable or disable the wakeup 2662306a36Sopenharmony_ci * 2762306a36Sopenharmony_ci * This function is supposed to be used only by a HD-audio controller 2862306a36Sopenharmony_ci * driver that needs the interaction with graphics driver. 2962306a36Sopenharmony_ci * 3062306a36Sopenharmony_ci * This function should be called during the chip reset, also called at 3162306a36Sopenharmony_ci * resume for updating STATESTS register read. 3262306a36Sopenharmony_ci * 3362306a36Sopenharmony_ci * Returns zero for success or a negative error code. 3462306a36Sopenharmony_ci */ 3562306a36Sopenharmony_ciint snd_hdac_set_codec_wakeup(struct hdac_bus *bus, bool enable) 3662306a36Sopenharmony_ci{ 3762306a36Sopenharmony_ci struct drm_audio_component *acomp = bus->audio_component; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci if (!acomp || !acomp->ops) 4062306a36Sopenharmony_ci return -ENODEV; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci if (!acomp->ops->codec_wake_override) 4362306a36Sopenharmony_ci return 0; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci dev_dbg(bus->dev, "%s codec wakeup\n", 4662306a36Sopenharmony_ci enable ? "enable" : "disable"); 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci acomp->ops->codec_wake_override(acomp->dev, enable); 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci return 0; 5162306a36Sopenharmony_ci} 5262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_set_codec_wakeup); 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci/** 5562306a36Sopenharmony_ci * snd_hdac_display_power - Power up / down the power refcount 5662306a36Sopenharmony_ci * @bus: HDA core bus 5762306a36Sopenharmony_ci * @idx: HDA codec address, pass HDA_CODEC_IDX_CONTROLLER for controller 5862306a36Sopenharmony_ci * @enable: power up or down 5962306a36Sopenharmony_ci * 6062306a36Sopenharmony_ci * This function is used by either HD-audio controller or codec driver that 6162306a36Sopenharmony_ci * needs the interaction with graphics driver. 6262306a36Sopenharmony_ci * 6362306a36Sopenharmony_ci * This function updates the power status, and calls the get_power() and 6462306a36Sopenharmony_ci * put_power() ops accordingly, toggling the codec wakeup, too. 6562306a36Sopenharmony_ci */ 6662306a36Sopenharmony_civoid snd_hdac_display_power(struct hdac_bus *bus, unsigned int idx, bool enable) 6762306a36Sopenharmony_ci{ 6862306a36Sopenharmony_ci struct drm_audio_component *acomp = bus->audio_component; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci dev_dbg(bus->dev, "display power %s\n", 7162306a36Sopenharmony_ci enable ? "enable" : "disable"); 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci mutex_lock(&bus->lock); 7462306a36Sopenharmony_ci if (enable) 7562306a36Sopenharmony_ci set_bit(idx, &bus->display_power_status); 7662306a36Sopenharmony_ci else 7762306a36Sopenharmony_ci clear_bit(idx, &bus->display_power_status); 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci if (!acomp || !acomp->ops) 8062306a36Sopenharmony_ci goto unlock; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci if (bus->display_power_status) { 8362306a36Sopenharmony_ci if (!bus->display_power_active) { 8462306a36Sopenharmony_ci unsigned long cookie = -1; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci if (acomp->ops->get_power) 8762306a36Sopenharmony_ci cookie = acomp->ops->get_power(acomp->dev); 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci snd_hdac_set_codec_wakeup(bus, true); 9062306a36Sopenharmony_ci snd_hdac_set_codec_wakeup(bus, false); 9162306a36Sopenharmony_ci bus->display_power_active = cookie; 9262306a36Sopenharmony_ci } 9362306a36Sopenharmony_ci } else { 9462306a36Sopenharmony_ci if (bus->display_power_active) { 9562306a36Sopenharmony_ci unsigned long cookie = bus->display_power_active; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci if (acomp->ops->put_power) 9862306a36Sopenharmony_ci acomp->ops->put_power(acomp->dev, cookie); 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci bus->display_power_active = 0; 10162306a36Sopenharmony_ci } 10262306a36Sopenharmony_ci } 10362306a36Sopenharmony_ci unlock: 10462306a36Sopenharmony_ci mutex_unlock(&bus->lock); 10562306a36Sopenharmony_ci} 10662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_display_power); 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci/** 10962306a36Sopenharmony_ci * snd_hdac_sync_audio_rate - Set N/CTS based on the sample rate 11062306a36Sopenharmony_ci * @codec: HDA codec 11162306a36Sopenharmony_ci * @nid: the pin widget NID 11262306a36Sopenharmony_ci * @dev_id: device identifier 11362306a36Sopenharmony_ci * @rate: the sample rate to set 11462306a36Sopenharmony_ci * 11562306a36Sopenharmony_ci * This function is supposed to be used only by a HD-audio controller 11662306a36Sopenharmony_ci * driver that needs the interaction with graphics driver. 11762306a36Sopenharmony_ci * 11862306a36Sopenharmony_ci * This function sets N/CTS value based on the given sample rate. 11962306a36Sopenharmony_ci * Returns zero for success, or a negative error code. 12062306a36Sopenharmony_ci */ 12162306a36Sopenharmony_ciint snd_hdac_sync_audio_rate(struct hdac_device *codec, hda_nid_t nid, 12262306a36Sopenharmony_ci int dev_id, int rate) 12362306a36Sopenharmony_ci{ 12462306a36Sopenharmony_ci struct hdac_bus *bus = codec->bus; 12562306a36Sopenharmony_ci struct drm_audio_component *acomp = bus->audio_component; 12662306a36Sopenharmony_ci int port, pipe; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci if (!acomp || !acomp->ops || !acomp->ops->sync_audio_rate) 12962306a36Sopenharmony_ci return -ENODEV; 13062306a36Sopenharmony_ci port = nid; 13162306a36Sopenharmony_ci if (acomp->audio_ops && acomp->audio_ops->pin2port) { 13262306a36Sopenharmony_ci port = acomp->audio_ops->pin2port(codec, nid); 13362306a36Sopenharmony_ci if (port < 0) 13462306a36Sopenharmony_ci return -EINVAL; 13562306a36Sopenharmony_ci } 13662306a36Sopenharmony_ci pipe = dev_id; 13762306a36Sopenharmony_ci return acomp->ops->sync_audio_rate(acomp->dev, port, pipe, rate); 13862306a36Sopenharmony_ci} 13962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_sync_audio_rate); 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci/** 14262306a36Sopenharmony_ci * snd_hdac_acomp_get_eld - Get the audio state and ELD via component 14362306a36Sopenharmony_ci * @codec: HDA codec 14462306a36Sopenharmony_ci * @nid: the pin widget NID 14562306a36Sopenharmony_ci * @dev_id: device identifier 14662306a36Sopenharmony_ci * @audio_enabled: the pointer to store the current audio state 14762306a36Sopenharmony_ci * @buffer: the buffer pointer to store ELD bytes 14862306a36Sopenharmony_ci * @max_bytes: the max bytes to be stored on @buffer 14962306a36Sopenharmony_ci * 15062306a36Sopenharmony_ci * This function is supposed to be used only by a HD-audio controller 15162306a36Sopenharmony_ci * driver that needs the interaction with graphics driver. 15262306a36Sopenharmony_ci * 15362306a36Sopenharmony_ci * This function queries the current state of the audio on the given 15462306a36Sopenharmony_ci * digital port and fetches the ELD bytes onto the given buffer. 15562306a36Sopenharmony_ci * It returns the number of bytes for the total ELD data, zero for 15662306a36Sopenharmony_ci * invalid ELD, or a negative error code. 15762306a36Sopenharmony_ci * 15862306a36Sopenharmony_ci * The return size is the total bytes required for the whole ELD bytes, 15962306a36Sopenharmony_ci * thus it may be over @max_bytes. If it's over @max_bytes, it implies 16062306a36Sopenharmony_ci * that only a part of ELD bytes have been fetched. 16162306a36Sopenharmony_ci */ 16262306a36Sopenharmony_ciint snd_hdac_acomp_get_eld(struct hdac_device *codec, hda_nid_t nid, int dev_id, 16362306a36Sopenharmony_ci bool *audio_enabled, char *buffer, int max_bytes) 16462306a36Sopenharmony_ci{ 16562306a36Sopenharmony_ci struct hdac_bus *bus = codec->bus; 16662306a36Sopenharmony_ci struct drm_audio_component *acomp = bus->audio_component; 16762306a36Sopenharmony_ci int port, pipe; 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci if (!acomp || !acomp->ops || !acomp->ops->get_eld) 17062306a36Sopenharmony_ci return -ENODEV; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci port = nid; 17362306a36Sopenharmony_ci if (acomp->audio_ops && acomp->audio_ops->pin2port) { 17462306a36Sopenharmony_ci port = acomp->audio_ops->pin2port(codec, nid); 17562306a36Sopenharmony_ci if (port < 0) 17662306a36Sopenharmony_ci return -EINVAL; 17762306a36Sopenharmony_ci } 17862306a36Sopenharmony_ci pipe = dev_id; 17962306a36Sopenharmony_ci return acomp->ops->get_eld(acomp->dev, port, pipe, audio_enabled, 18062306a36Sopenharmony_ci buffer, max_bytes); 18162306a36Sopenharmony_ci} 18262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_acomp_get_eld); 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_cistatic int hdac_component_master_bind(struct device *dev) 18562306a36Sopenharmony_ci{ 18662306a36Sopenharmony_ci struct drm_audio_component *acomp = hdac_get_acomp(dev); 18762306a36Sopenharmony_ci int ret; 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci if (WARN_ON(!acomp)) 19062306a36Sopenharmony_ci return -EINVAL; 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci ret = component_bind_all(dev, acomp); 19362306a36Sopenharmony_ci if (ret < 0) 19462306a36Sopenharmony_ci return ret; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci if (WARN_ON(!(acomp->dev && acomp->ops))) { 19762306a36Sopenharmony_ci ret = -EINVAL; 19862306a36Sopenharmony_ci goto out_unbind; 19962306a36Sopenharmony_ci } 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci /* pin the module to avoid dynamic unbinding, but only if given */ 20262306a36Sopenharmony_ci if (!try_module_get(acomp->ops->owner)) { 20362306a36Sopenharmony_ci ret = -ENODEV; 20462306a36Sopenharmony_ci goto out_unbind; 20562306a36Sopenharmony_ci } 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci if (acomp->audio_ops && acomp->audio_ops->master_bind) { 20862306a36Sopenharmony_ci ret = acomp->audio_ops->master_bind(dev, acomp); 20962306a36Sopenharmony_ci if (ret < 0) 21062306a36Sopenharmony_ci goto module_put; 21162306a36Sopenharmony_ci } 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci complete_all(&acomp->master_bind_complete); 21462306a36Sopenharmony_ci return 0; 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci module_put: 21762306a36Sopenharmony_ci module_put(acomp->ops->owner); 21862306a36Sopenharmony_ciout_unbind: 21962306a36Sopenharmony_ci component_unbind_all(dev, acomp); 22062306a36Sopenharmony_ci complete_all(&acomp->master_bind_complete); 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci return ret; 22362306a36Sopenharmony_ci} 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_cistatic void hdac_component_master_unbind(struct device *dev) 22662306a36Sopenharmony_ci{ 22762306a36Sopenharmony_ci struct drm_audio_component *acomp = hdac_get_acomp(dev); 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci if (acomp->audio_ops && acomp->audio_ops->master_unbind) 23062306a36Sopenharmony_ci acomp->audio_ops->master_unbind(dev, acomp); 23162306a36Sopenharmony_ci module_put(acomp->ops->owner); 23262306a36Sopenharmony_ci component_unbind_all(dev, acomp); 23362306a36Sopenharmony_ci WARN_ON(acomp->ops || acomp->dev); 23462306a36Sopenharmony_ci} 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_cistatic const struct component_master_ops hdac_component_master_ops = { 23762306a36Sopenharmony_ci .bind = hdac_component_master_bind, 23862306a36Sopenharmony_ci .unbind = hdac_component_master_unbind, 23962306a36Sopenharmony_ci}; 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci/** 24262306a36Sopenharmony_ci * snd_hdac_acomp_register_notifier - Register audio component ops 24362306a36Sopenharmony_ci * @bus: HDA core bus 24462306a36Sopenharmony_ci * @aops: audio component ops 24562306a36Sopenharmony_ci * 24662306a36Sopenharmony_ci * This function is supposed to be used only by a HD-audio controller 24762306a36Sopenharmony_ci * driver that needs the interaction with graphics driver. 24862306a36Sopenharmony_ci * 24962306a36Sopenharmony_ci * This function sets the given ops to be called by the graphics driver. 25062306a36Sopenharmony_ci * 25162306a36Sopenharmony_ci * Returns zero for success or a negative error code. 25262306a36Sopenharmony_ci */ 25362306a36Sopenharmony_ciint snd_hdac_acomp_register_notifier(struct hdac_bus *bus, 25462306a36Sopenharmony_ci const struct drm_audio_component_audio_ops *aops) 25562306a36Sopenharmony_ci{ 25662306a36Sopenharmony_ci if (!bus->audio_component) 25762306a36Sopenharmony_ci return -ENODEV; 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci bus->audio_component->audio_ops = aops; 26062306a36Sopenharmony_ci return 0; 26162306a36Sopenharmony_ci} 26262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_acomp_register_notifier); 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci/** 26562306a36Sopenharmony_ci * snd_hdac_acomp_init - Initialize audio component 26662306a36Sopenharmony_ci * @bus: HDA core bus 26762306a36Sopenharmony_ci * @aops: audio component ops 26862306a36Sopenharmony_ci * @match_master: match function for finding components 26962306a36Sopenharmony_ci * @extra_size: Extra bytes to allocate 27062306a36Sopenharmony_ci * 27162306a36Sopenharmony_ci * This function is supposed to be used only by a HD-audio controller 27262306a36Sopenharmony_ci * driver that needs the interaction with graphics driver. 27362306a36Sopenharmony_ci * 27462306a36Sopenharmony_ci * This function initializes and sets up the audio component to communicate 27562306a36Sopenharmony_ci * with graphics driver. 27662306a36Sopenharmony_ci * 27762306a36Sopenharmony_ci * Unlike snd_hdac_i915_init(), this function doesn't synchronize with the 27862306a36Sopenharmony_ci * binding with the DRM component. Each caller needs to sync via master_bind 27962306a36Sopenharmony_ci * audio_ops. 28062306a36Sopenharmony_ci * 28162306a36Sopenharmony_ci * Returns zero for success or a negative error code. 28262306a36Sopenharmony_ci */ 28362306a36Sopenharmony_ciint snd_hdac_acomp_init(struct hdac_bus *bus, 28462306a36Sopenharmony_ci const struct drm_audio_component_audio_ops *aops, 28562306a36Sopenharmony_ci int (*match_master)(struct device *, int, void *), 28662306a36Sopenharmony_ci size_t extra_size) 28762306a36Sopenharmony_ci{ 28862306a36Sopenharmony_ci struct component_match *match = NULL; 28962306a36Sopenharmony_ci struct device *dev = bus->dev; 29062306a36Sopenharmony_ci struct drm_audio_component *acomp; 29162306a36Sopenharmony_ci int ret; 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci if (WARN_ON(hdac_get_acomp(dev))) 29462306a36Sopenharmony_ci return -EBUSY; 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci acomp = devres_alloc(hdac_acomp_release, sizeof(*acomp) + extra_size, 29762306a36Sopenharmony_ci GFP_KERNEL); 29862306a36Sopenharmony_ci if (!acomp) 29962306a36Sopenharmony_ci return -ENOMEM; 30062306a36Sopenharmony_ci acomp->audio_ops = aops; 30162306a36Sopenharmony_ci init_completion(&acomp->master_bind_complete); 30262306a36Sopenharmony_ci bus->audio_component = acomp; 30362306a36Sopenharmony_ci devres_add(dev, acomp); 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci component_match_add_typed(dev, &match, match_master, bus); 30662306a36Sopenharmony_ci ret = component_master_add_with_match(dev, &hdac_component_master_ops, 30762306a36Sopenharmony_ci match); 30862306a36Sopenharmony_ci if (ret < 0) 30962306a36Sopenharmony_ci goto out_err; 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci return 0; 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ciout_err: 31462306a36Sopenharmony_ci bus->audio_component = NULL; 31562306a36Sopenharmony_ci devres_destroy(dev, hdac_acomp_release, NULL, NULL); 31662306a36Sopenharmony_ci dev_info(dev, "failed to add audio component master (%d)\n", ret); 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci return ret; 31962306a36Sopenharmony_ci} 32062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_acomp_init); 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci/** 32362306a36Sopenharmony_ci * snd_hdac_acomp_exit - Finalize audio component 32462306a36Sopenharmony_ci * @bus: HDA core bus 32562306a36Sopenharmony_ci * 32662306a36Sopenharmony_ci * This function is supposed to be used only by a HD-audio controller 32762306a36Sopenharmony_ci * driver that needs the interaction with graphics driver. 32862306a36Sopenharmony_ci * 32962306a36Sopenharmony_ci * This function releases the audio component that has been used. 33062306a36Sopenharmony_ci * 33162306a36Sopenharmony_ci * Returns zero for success or a negative error code. 33262306a36Sopenharmony_ci */ 33362306a36Sopenharmony_ciint snd_hdac_acomp_exit(struct hdac_bus *bus) 33462306a36Sopenharmony_ci{ 33562306a36Sopenharmony_ci struct device *dev = bus->dev; 33662306a36Sopenharmony_ci struct drm_audio_component *acomp = bus->audio_component; 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci if (!acomp) 33962306a36Sopenharmony_ci return 0; 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci if (WARN_ON(bus->display_power_active) && acomp->ops) 34262306a36Sopenharmony_ci acomp->ops->put_power(acomp->dev, bus->display_power_active); 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci bus->display_power_active = 0; 34562306a36Sopenharmony_ci bus->display_power_status = 0; 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci component_master_del(dev, &hdac_component_master_ops); 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_ci bus->audio_component = NULL; 35062306a36Sopenharmony_ci devres_destroy(dev, hdac_acomp_release, NULL, NULL); 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci return 0; 35362306a36Sopenharmony_ci} 35462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_acomp_exit); 355