| // SPDX-License-Identifier: GPL-2.0-or-later | 
 | /* | 
 |  *  hdac_i915.c - routines for sync between HD-A core and i915 display driver | 
 |  */ | 
 |  | 
 | #include <linux/init.h> | 
 | #include <linux/module.h> | 
 | #include <linux/pci.h> | 
 | #include <sound/core.h> | 
 | #include <sound/hdaudio.h> | 
 | #include <sound/hda_i915.h> | 
 | #include <sound/hda_register.h> | 
 |  | 
 | #define IS_HSW_CONTROLLER(pci) (((pci)->device == 0x0a0c) || \ | 
 | 				((pci)->device == 0x0c0c) || \ | 
 | 				((pci)->device == 0x0d0c) || \ | 
 | 				((pci)->device == 0x160c)) | 
 |  | 
 | /** | 
 |  * snd_hdac_i915_set_bclk - Reprogram BCLK for HSW/BDW | 
 |  * @bus: HDA core bus | 
 |  * | 
 |  * Intel HSW/BDW display HDA controller is in GPU. Both its power and link BCLK | 
 |  * depends on GPU. Two Extended Mode registers EM4 (M value) and EM5 (N Value) | 
 |  * are used to convert CDClk (Core Display Clock) to 24MHz BCLK: | 
 |  * BCLK = CDCLK * M / N | 
 |  * The values will be lost when the display power well is disabled and need to | 
 |  * be restored to avoid abnormal playback speed. | 
 |  * | 
 |  * Call this function at initializing and changing power well, as well as | 
 |  * at ELD notifier for the hotplug. | 
 |  */ | 
 | void snd_hdac_i915_set_bclk(struct hdac_bus *bus) | 
 | { | 
 | 	struct drm_audio_component *acomp = bus->audio_component; | 
 | 	struct pci_dev *pci = to_pci_dev(bus->dev); | 
 | 	int cdclk_freq; | 
 | 	unsigned int bclk_m, bclk_n; | 
 |  | 
 | 	if (!acomp || !acomp->ops || !acomp->ops->get_cdclk_freq) | 
 | 		return; /* only for i915 binding */ | 
 | 	if (!IS_HSW_CONTROLLER(pci)) | 
 | 		return; /* only HSW/BDW */ | 
 |  | 
 | 	cdclk_freq = acomp->ops->get_cdclk_freq(acomp->dev); | 
 | 	switch (cdclk_freq) { | 
 | 	case 337500: | 
 | 		bclk_m = 16; | 
 | 		bclk_n = 225; | 
 | 		break; | 
 |  | 
 | 	case 450000: | 
 | 	default: /* default CDCLK 450MHz */ | 
 | 		bclk_m = 4; | 
 | 		bclk_n = 75; | 
 | 		break; | 
 |  | 
 | 	case 540000: | 
 | 		bclk_m = 4; | 
 | 		bclk_n = 90; | 
 | 		break; | 
 |  | 
 | 	case 675000: | 
 | 		bclk_m = 8; | 
 | 		bclk_n = 225; | 
 | 		break; | 
 | 	} | 
 |  | 
 | 	snd_hdac_chip_writew(bus, HSW_EM4, bclk_m); | 
 | 	snd_hdac_chip_writew(bus, HSW_EM5, bclk_n); | 
 | } | 
 | EXPORT_SYMBOL_GPL(snd_hdac_i915_set_bclk); | 
 |  | 
 | /* returns true if the devices can be connected for audio */ | 
 | static bool connectivity_check(struct pci_dev *i915, struct pci_dev *hdac) | 
 | { | 
 | 	struct pci_bus *bus_a = i915->bus, *bus_b = hdac->bus; | 
 |  | 
 | 	/* directly connected on the same bus */ | 
 | 	if (bus_a == bus_b) | 
 | 		return true; | 
 |  | 
 | 	/* | 
 | 	 * on i915 discrete GPUs with embedded HDA audio, the two | 
 | 	 * devices are connected via 2nd level PCI bridge | 
 | 	 */ | 
 | 	bus_a = bus_a->parent; | 
 | 	bus_b = bus_b->parent; | 
 | 	if (!bus_a || !bus_b) | 
 | 		return false; | 
 | 	bus_a = bus_a->parent; | 
 | 	bus_b = bus_b->parent; | 
 | 	if (bus_a && bus_a == bus_b) | 
 | 		return true; | 
 |  | 
 | 	return false; | 
 | } | 
 |  | 
 | static int i915_component_master_match(struct device *dev, int subcomponent, | 
 | 				       void *data) | 
 | { | 
 | 	struct pci_dev *hdac_pci, *i915_pci; | 
 | 	struct hdac_bus *bus = data; | 
 |  | 
 | 	if (!dev_is_pci(dev)) | 
 | 		return 0; | 
 |  | 
 | 	hdac_pci = to_pci_dev(bus->dev); | 
 | 	i915_pci = to_pci_dev(dev); | 
 |  | 
 | 	if (!strcmp(dev->driver->name, "i915") && | 
 | 	    subcomponent == I915_COMPONENT_AUDIO && | 
 | 	    connectivity_check(i915_pci, hdac_pci)) | 
 | 		return 1; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* check whether intel graphics is present */ | 
 | static bool i915_gfx_present(void) | 
 | { | 
 | 	static const struct pci_device_id ids[] = { | 
 | 		{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_ANY_ID), | 
 | 		  .class = PCI_BASE_CLASS_DISPLAY << 16, | 
 | 		  .class_mask = 0xff << 16 }, | 
 | 		{} | 
 | 	}; | 
 | 	return pci_dev_present(ids); | 
 | } | 
 |  | 
 | /** | 
 |  * snd_hdac_i915_init - Initialize i915 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 i915 graphics. | 
 |  * | 
 |  * This function initializes and sets up the audio component to communicate | 
 |  * with i915 graphics driver. | 
 |  * | 
 |  * Returns zero for success or a negative error code. | 
 |  */ | 
 | int snd_hdac_i915_init(struct hdac_bus *bus) | 
 | { | 
 | 	struct drm_audio_component *acomp; | 
 | 	int err; | 
 |  | 
 | 	if (!i915_gfx_present()) | 
 | 		return -ENODEV; | 
 |  | 
 | 	err = snd_hdac_acomp_init(bus, NULL, | 
 | 				  i915_component_master_match, | 
 | 				  sizeof(struct i915_audio_component) - sizeof(*acomp)); | 
 | 	if (err < 0) | 
 | 		return err; | 
 | 	acomp = bus->audio_component; | 
 | 	if (!acomp) | 
 | 		return -ENODEV; | 
 | 	if (!acomp->ops) { | 
 | 		if (!IS_ENABLED(CONFIG_MODULES) || | 
 | 		    !request_module("i915")) { | 
 | 			/* 60s timeout */ | 
 | 			wait_for_completion_timeout(&acomp->master_bind_complete, | 
 | 						    msecs_to_jiffies(60 * 1000)); | 
 | 		} | 
 | 	} | 
 | 	if (!acomp->ops) { | 
 | 		dev_info(bus->dev, "couldn't bind with audio component\n"); | 
 | 		snd_hdac_acomp_exit(bus); | 
 | 		return -ENODEV; | 
 | 	} | 
 | 	return 0; | 
 | } | 
 | EXPORT_SYMBOL_GPL(snd_hdac_i915_init); |