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