Thomas Gleixner | c942fdd | 2019-05-27 08:55:06 +0200 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
Mengdong Lin | 98d8fc6 | 2015-05-19 22:29:30 +0800 | [diff] [blame] | 2 | /* |
| 3 | * hdac_i915.c - routines for sync between HD-A core and i915 display driver |
Mengdong Lin | 98d8fc6 | 2015-05-19 22:29:30 +0800 | [diff] [blame] | 4 | */ |
| 5 | |
| 6 | #include <linux/init.h> |
| 7 | #include <linux/module.h> |
| 8 | #include <linux/pci.h> |
Mengdong Lin | 98d8fc6 | 2015-05-19 22:29:30 +0800 | [diff] [blame] | 9 | #include <sound/core.h> |
| 10 | #include <sound/hdaudio.h> |
| 11 | #include <sound/hda_i915.h> |
Takashi Iwai | bb03ed2 | 2016-04-21 16:39:17 +0200 | [diff] [blame] | 12 | #include <sound/hda_register.h> |
Mengdong Lin | 98d8fc6 | 2015-05-19 22:29:30 +0800 | [diff] [blame] | 13 | |
Kai Vehmanen | 534ad9a | 2020-09-21 17:17:41 +0300 | [diff] [blame] | 14 | #define IS_HSW_CONTROLLER(pci) (((pci)->device == 0x0a0c) || \ |
Takashi Iwai | bb03ed2 | 2016-04-21 16:39:17 +0200 | [diff] [blame] | 15 | ((pci)->device == 0x0c0c) || \ |
| 16 | ((pci)->device == 0x0d0c) || \ |
| 17 | ((pci)->device == 0x160c)) |
| 18 | |
Takashi Iwai | 78dd5e2 | 2015-10-28 12:26:48 +0100 | [diff] [blame] | 19 | /** |
Takashi Iwai | bb03ed2 | 2016-04-21 16:39:17 +0200 | [diff] [blame] | 20 | * snd_hdac_i915_set_bclk - Reprogram BCLK for HSW/BDW |
Takashi Iwai | 78dd5e2 | 2015-10-28 12:26:48 +0100 | [diff] [blame] | 21 | * @bus: HDA core bus |
| 22 | * |
Takashi Iwai | bb03ed2 | 2016-04-21 16:39:17 +0200 | [diff] [blame] | 23 | * Intel HSW/BDW display HDA controller is in GPU. Both its power and link BCLK |
| 24 | * depends on GPU. Two Extended Mode registers EM4 (M value) and EM5 (N Value) |
| 25 | * are used to convert CDClk (Core Display Clock) to 24MHz BCLK: |
| 26 | * BCLK = CDCLK * M / N |
| 27 | * The values will be lost when the display power well is disabled and need to |
| 28 | * be restored to avoid abnormal playback speed. |
Takashi Iwai | 78dd5e2 | 2015-10-28 12:26:48 +0100 | [diff] [blame] | 29 | * |
Takashi Iwai | bb03ed2 | 2016-04-21 16:39:17 +0200 | [diff] [blame] | 30 | * Call this function at initializing and changing power well, as well as |
| 31 | * at ELD notifier for the hotplug. |
Takashi Iwai | 78dd5e2 | 2015-10-28 12:26:48 +0100 | [diff] [blame] | 32 | */ |
Takashi Iwai | bb03ed2 | 2016-04-21 16:39:17 +0200 | [diff] [blame] | 33 | void snd_hdac_i915_set_bclk(struct hdac_bus *bus) |
Mengdong Lin | 98d8fc6 | 2015-05-19 22:29:30 +0800 | [diff] [blame] | 34 | { |
Takashi Iwai | ae891ab | 2018-07-11 15:17:22 +0200 | [diff] [blame] | 35 | struct drm_audio_component *acomp = bus->audio_component; |
Takashi Iwai | bb03ed2 | 2016-04-21 16:39:17 +0200 | [diff] [blame] | 36 | struct pci_dev *pci = to_pci_dev(bus->dev); |
| 37 | int cdclk_freq; |
| 38 | unsigned int bclk_m, bclk_n; |
Mengdong Lin | 98d8fc6 | 2015-05-19 22:29:30 +0800 | [diff] [blame] | 39 | |
Takashi Iwai | bb03ed2 | 2016-04-21 16:39:17 +0200 | [diff] [blame] | 40 | if (!acomp || !acomp->ops || !acomp->ops->get_cdclk_freq) |
| 41 | return; /* only for i915 binding */ |
Kai Vehmanen | 534ad9a | 2020-09-21 17:17:41 +0300 | [diff] [blame] | 42 | if (!IS_HSW_CONTROLLER(pci)) |
Takashi Iwai | bb03ed2 | 2016-04-21 16:39:17 +0200 | [diff] [blame] | 43 | return; /* only HSW/BDW */ |
Mengdong Lin | 98d8fc6 | 2015-05-19 22:29:30 +0800 | [diff] [blame] | 44 | |
Takashi Iwai | bb03ed2 | 2016-04-21 16:39:17 +0200 | [diff] [blame] | 45 | cdclk_freq = acomp->ops->get_cdclk_freq(acomp->dev); |
| 46 | switch (cdclk_freq) { |
| 47 | case 337500: |
| 48 | bclk_m = 16; |
| 49 | bclk_n = 225; |
| 50 | break; |
| 51 | |
| 52 | case 450000: |
| 53 | default: /* default CDCLK 450MHz */ |
| 54 | bclk_m = 4; |
| 55 | bclk_n = 75; |
| 56 | break; |
| 57 | |
| 58 | case 540000: |
| 59 | bclk_m = 4; |
| 60 | bclk_n = 90; |
| 61 | break; |
| 62 | |
| 63 | case 675000: |
| 64 | bclk_m = 8; |
| 65 | bclk_n = 225; |
| 66 | break; |
| 67 | } |
| 68 | |
| 69 | snd_hdac_chip_writew(bus, HSW_EM4, bclk_m); |
| 70 | snd_hdac_chip_writew(bus, HSW_EM5, bclk_n); |
Mengdong Lin | 98d8fc6 | 2015-05-19 22:29:30 +0800 | [diff] [blame] | 71 | } |
Takashi Iwai | bb03ed2 | 2016-04-21 16:39:17 +0200 | [diff] [blame] | 72 | EXPORT_SYMBOL_GPL(snd_hdac_i915_set_bclk); |
Mengdong Lin | 98d8fc6 | 2015-05-19 22:29:30 +0800 | [diff] [blame] | 73 | |
Kai Vehmanen | 502f389 | 2020-09-24 19:10:27 +0300 | [diff] [blame] | 74 | /* returns true if the devices can be connected for audio */ |
Kai Vehmanen | 7b882fe | 2020-09-21 17:17:40 +0300 | [diff] [blame] | 75 | static bool connectivity_check(struct pci_dev *i915, struct pci_dev *hdac) |
| 76 | { |
| 77 | struct pci_bus *bus_a = i915->bus, *bus_b = hdac->bus; |
| 78 | |
| 79 | /* directly connected on the same bus */ |
| 80 | if (bus_a == bus_b) |
| 81 | return true; |
| 82 | |
| 83 | /* |
| 84 | * on i915 discrete GPUs with embedded HDA audio, the two |
| 85 | * devices are connected via 2nd level PCI bridge |
| 86 | */ |
| 87 | bus_a = bus_a->parent; |
| 88 | bus_b = bus_b->parent; |
| 89 | if (!bus_a || !bus_b) |
| 90 | return false; |
| 91 | bus_a = bus_a->parent; |
| 92 | bus_b = bus_b->parent; |
| 93 | if (bus_a && bus_a == bus_b) |
| 94 | return true; |
| 95 | |
| 96 | return false; |
| 97 | } |
| 98 | |
Daniel Vetter | 8857c7d | 2019-02-08 00:27:59 +0100 | [diff] [blame] | 99 | static int i915_component_master_match(struct device *dev, int subcomponent, |
| 100 | void *data) |
Takashi Iwai | e2dc7d7 | 2015-12-01 12:39:38 +0100 | [diff] [blame] | 101 | { |
Kai Vehmanen | 7b882fe | 2020-09-21 17:17:40 +0300 | [diff] [blame] | 102 | struct pci_dev *hdac_pci, *i915_pci; |
| 103 | struct hdac_bus *bus = data; |
| 104 | |
| 105 | if (!dev_is_pci(dev)) |
| 106 | return 0; |
| 107 | |
| 108 | hdac_pci = to_pci_dev(bus->dev); |
| 109 | i915_pci = to_pci_dev(dev); |
| 110 | |
| 111 | if (!strcmp(dev->driver->name, "i915") && |
| 112 | subcomponent == I915_COMPONENT_AUDIO && |
| 113 | connectivity_check(i915_pci, hdac_pci)) |
| 114 | return 1; |
| 115 | |
| 116 | return 0; |
Mengdong Lin | 98d8fc6 | 2015-05-19 22:29:30 +0800 | [diff] [blame] | 117 | } |
| 118 | |
Takashi Iwai | bfa5fb1 | 2016-03-29 15:03:06 +0200 | [diff] [blame] | 119 | /* check whether intel graphics is present */ |
| 120 | static bool i915_gfx_present(void) |
| 121 | { |
Arvind Yadav | 6c5a266 | 2017-07-18 22:35:06 +0530 | [diff] [blame] | 122 | static const struct pci_device_id ids[] = { |
Takashi Iwai | bfa5fb1 | 2016-03-29 15:03:06 +0200 | [diff] [blame] | 123 | { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_ANY_ID), |
| 124 | .class = PCI_BASE_CLASS_DISPLAY << 16, |
| 125 | .class_mask = 0xff << 16 }, |
| 126 | {} |
| 127 | }; |
| 128 | return pci_dev_present(ids); |
| 129 | } |
| 130 | |
Takashi Iwai | 78dd5e2 | 2015-10-28 12:26:48 +0100 | [diff] [blame] | 131 | /** |
| 132 | * snd_hdac_i915_init - Initialize i915 audio component |
| 133 | * @bus: HDA core bus |
| 134 | * |
| 135 | * This function is supposed to be used only by a HD-audio controller |
| 136 | * driver that needs the interaction with i915 graphics. |
| 137 | * |
| 138 | * This function initializes and sets up the audio component to communicate |
| 139 | * with i915 graphics driver. |
| 140 | * |
| 141 | * Returns zero for success or a negative error code. |
| 142 | */ |
Mengdong Lin | 98d8fc6 | 2015-05-19 22:29:30 +0800 | [diff] [blame] | 143 | int snd_hdac_i915_init(struct hdac_bus *bus) |
| 144 | { |
Takashi Iwai | ae891ab | 2018-07-11 15:17:22 +0200 | [diff] [blame] | 145 | struct drm_audio_component *acomp; |
Takashi Iwai | a57942b | 2018-07-11 16:23:16 +0200 | [diff] [blame] | 146 | int err; |
Takashi Iwai | d745f5e | 2016-03-21 14:41:58 +0100 | [diff] [blame] | 147 | |
Takashi Iwai | bfa5fb1 | 2016-03-29 15:03:06 +0200 | [diff] [blame] | 148 | if (!i915_gfx_present()) |
| 149 | return -ENODEV; |
| 150 | |
Takashi Iwai | 96e503f | 2020-10-06 19:17:22 +0300 | [diff] [blame] | 151 | err = snd_hdac_acomp_init(bus, NULL, |
Takashi Iwai | a57942b | 2018-07-11 16:23:16 +0200 | [diff] [blame] | 152 | i915_component_master_match, |
| 153 | sizeof(struct i915_audio_component) - sizeof(*acomp)); |
| 154 | if (err < 0) |
| 155 | return err; |
| 156 | acomp = bus->audio_component; |
| 157 | if (!acomp) |
| 158 | return -ENODEV; |
Takashi Iwai | f9b54e1 | 2018-07-11 15:05:06 +0200 | [diff] [blame] | 159 | if (!acomp->ops) { |
Samuel Thibault | 74bf71e | 2019-07-26 23:47:02 +0200 | [diff] [blame] | 160 | if (!IS_ENABLED(CONFIG_MODULES) || |
| 161 | !request_module("i915")) { |
| 162 | /* 60s timeout */ |
Takashi Iwai | 96e503f | 2020-10-06 19:17:22 +0300 | [diff] [blame] | 163 | wait_for_completion_timeout(&acomp->master_bind_complete, |
| 164 | msecs_to_jiffies(60 * 1000)); |
Samuel Thibault | 74bf71e | 2019-07-26 23:47:02 +0200 | [diff] [blame] | 165 | } |
Takashi Iwai | f9b54e1 | 2018-07-11 15:05:06 +0200 | [diff] [blame] | 166 | } |
Mengdong Lin | 98d8fc6 | 2015-05-19 22:29:30 +0800 | [diff] [blame] | 167 | if (!acomp->ops) { |
Takashi Iwai | b3a5402 | 2018-09-18 18:21:11 +0200 | [diff] [blame] | 168 | dev_info(bus->dev, "couldn't bind with audio component\n"); |
Takashi Iwai | a57942b | 2018-07-11 16:23:16 +0200 | [diff] [blame] | 169 | snd_hdac_acomp_exit(bus); |
| 170 | return -ENODEV; |
Mengdong Lin | 98d8fc6 | 2015-05-19 22:29:30 +0800 | [diff] [blame] | 171 | } |
Mengdong Lin | 98d8fc6 | 2015-05-19 22:29:30 +0800 | [diff] [blame] | 172 | return 0; |
Mengdong Lin | 98d8fc6 | 2015-05-19 22:29:30 +0800 | [diff] [blame] | 173 | } |
| 174 | EXPORT_SYMBOL_GPL(snd_hdac_i915_init); |