Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ALSA: hda - Add power-welll support for haswell HDA

For Intel Haswell chip, HDA controller and codec have
power well dependency from GPU side. This patch added support
to request/release power well in audio driver. Power save
feature should be enabled to get runtime power saving.

There's deadlock when request_module(i915) in azx_probe.
It looks like:
device_lock(audio pci device) -> azx_probe -> module_request
(or symbol_request) -> modprobe (userspace) -> i915 init ->
drm_pci_init -> pci_register_driver -> bus_add_driver -> driver_attach ->
which in turn tries all locks on pci bus, and when it tries the one on the
audio device, it will deadlock.

This patch introduce a work to store remaining probe stuff, and let
request_module run in safe work context.

Signed-off-by: Wang Xingchao <xingchao.wang@linux.intel.com>
Reviewed-by: Takashi Iwai <tiwai@suse.de>
Reviewed-by: Liam Girdwood <liam.r.girdwood@intel.com>
Reviewed-by: David Henningsson <david.henningsson@canonical.com>
Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>

authored by

Wang Xingchao and committed by
Daniel Vetter
99a2008d 5c90680e

+179 -3
+10
sound/pci/hda/Kconfig
··· 152 152 snd-hda-codec-hdmi. 153 153 This module is automatically loaded at probing. 154 154 155 + config SND_HDA_I915 156 + bool "Build Display HD-audio controller/codec power well support for i915 cards" 157 + depends on DRM_I915 158 + help 159 + Say Y here to include full HDMI and DisplayPort HD-audio controller/codec 160 + power-well support for Intel Haswell graphics cards based on the i915 driver. 161 + 162 + Note that this option must be enabled for Intel Haswell C+ stepping machines, otherwise 163 + the GPU audio controller/codecs will not be initialized or damaged when exit from S3 mode. 164 + 155 165 config SND_HDA_CODEC_CIRRUS 156 166 bool "Build Cirrus Logic codec support" 157 167 default y
+2
sound/pci/hda/Makefile
··· 1 1 snd-hda-intel-objs := hda_intel.o 2 + # for haswell power well 3 + snd-hda-intel-$(CONFIG_SND_HDA_I915) += hda_i915.o 2 4 3 5 snd-hda-codec-y := hda_codec.o hda_jack.o hda_auto_parser.o 4 6 snd-hda-codec-$(CONFIG_SND_HDA_GENERIC) += hda_generic.o
+75
sound/pci/hda/hda_i915.c
··· 1 + /* 2 + * hda_i915.c - routines for Haswell HDA controller power well support 3 + * 4 + * This program is free software; you can redistribute it and/or modify it 5 + * under the terms of the GNU General Public License as published by the Free 6 + * Software Foundation; either version 2 of the License, or (at your option) 7 + * any later version. 8 + * 9 + * This program is distributed in the hope that it will be useful, but 10 + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 11 + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 + * for more details. 13 + * 14 + * You should have received a copy of the GNU General Public License 15 + * along with this program; if not, write to the Free Software Foundation, 16 + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 17 + */ 18 + 19 + #include <linux/init.h> 20 + #include <linux/module.h> 21 + #include <sound/core.h> 22 + #include <drm/i915_powerwell.h> 23 + #include "hda_i915.h" 24 + 25 + static void (*get_power)(void); 26 + static void (*put_power)(void); 27 + 28 + void hda_display_power(bool enable) 29 + { 30 + if (!get_power || !put_power) 31 + return; 32 + 33 + snd_printdd("HDA display power %s \n", 34 + enable ? "Enable" : "Disable"); 35 + if (enable) 36 + get_power(); 37 + else 38 + put_power(); 39 + } 40 + 41 + int hda_i915_init(void) 42 + { 43 + int err = 0; 44 + 45 + get_power = symbol_request(i915_request_power_well); 46 + if (!get_power) { 47 + snd_printk(KERN_WARNING "hda-i915: get_power symbol get fail\n"); 48 + return -ENODEV; 49 + } 50 + 51 + put_power = symbol_request(i915_release_power_well); 52 + if (!put_power) { 53 + symbol_put(i915_request_power_well); 54 + get_power = NULL; 55 + return -ENODEV; 56 + } 57 + 58 + snd_printd("HDA driver get symbol successfully from i915 module\n"); 59 + 60 + return err; 61 + } 62 + 63 + int hda_i915_exit(void) 64 + { 65 + if (get_power) { 66 + symbol_put(i915_request_power_well); 67 + get_power = NULL; 68 + } 69 + if (put_power) { 70 + symbol_put(i915_release_power_well); 71 + put_power = NULL; 72 + } 73 + 74 + return 0; 75 + }
+35
sound/pci/hda/hda_i915.h
··· 1 + /* 2 + * This program is free software; you can redistribute it and/or modify it 3 + * under the terms of the GNU General Public License as published by the Free 4 + * Software Foundation; either version 2 of the License, or (at your option) 5 + * any later version. 6 + * 7 + * This program is distributed in the hope that it will be useful, but WITHOUT 8 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 10 + * more details. 11 + * 12 + * You should have received a copy of the GNU General Public License along with 13 + * this program; if not, write to the Free Software Foundation, Inc., 59 14 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 + */ 16 + #ifndef __SOUND_HDA_I915_H 17 + #define __SOUND_HDA_I915_H 18 + 19 + #ifdef CONFIG_SND_HDA_I915 20 + void hda_display_power(bool enable); 21 + int hda_i915_init(void); 22 + int hda_i915_exit(void); 23 + #else 24 + static inline void hda_display_power(bool enable) {} 25 + static inline int hda_i915_init(void) 26 + { 27 + return -ENODEV; 28 + } 29 + static inline int hda_i915_exit(void) 30 + { 31 + return 0; 32 + } 33 + #endif 34 + 35 + #endif
+57 -3
sound/pci/hda/hda_intel.c
··· 62 62 #include <linux/vga_switcheroo.h> 63 63 #include <linux/firmware.h> 64 64 #include "hda_codec.h" 65 + #include "hda_i915.h" 65 66 66 67 67 68 static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; ··· 542 541 /* for pending irqs */ 543 542 struct work_struct irq_pending_work; 544 543 544 + #ifdef CONFIG_SND_HDA_I915 545 + struct work_struct probe_work; 546 + #endif 547 + 545 548 /* reboot notifier (for mysterious hangup problem at power-down) */ 546 549 struct notifier_block reboot_notifier; 547 550 ··· 599 594 #define AZX_DCAPS_4K_BDLE_BOUNDARY (1 << 23) /* BDLE in 4k boundary */ 600 595 #define AZX_DCAPS_COUNT_LPIB_DELAY (1 << 25) /* Take LPIB as delay */ 601 596 #define AZX_DCAPS_PM_RUNTIME (1 << 26) /* runtime PM support */ 597 + #define AZX_DCAPS_I915_POWERWELL (1 << 27) /* HSW i915 power well support */ 602 598 603 599 /* quirks for Intel PCH */ 604 600 #define AZX_DCAPS_INTEL_PCH_NOPM \ ··· 2906 2900 pci_disable_device(pci); 2907 2901 pci_save_state(pci); 2908 2902 pci_set_power_state(pci, PCI_D3hot); 2903 + if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) 2904 + hda_display_power(false); 2909 2905 return 0; 2910 2906 } 2911 2907 ··· 2920 2912 if (chip->disabled) 2921 2913 return 0; 2922 2914 2915 + if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) 2916 + hda_display_power(true); 2923 2917 pci_set_power_state(pci, PCI_D0); 2924 2918 pci_restore_state(pci); 2925 2919 if (pci_enable_device(pci) < 0) { ··· 2954 2944 2955 2945 azx_stop_chip(chip); 2956 2946 azx_clear_irq_pending(chip); 2947 + if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) 2948 + hda_display_power(false); 2957 2949 return 0; 2958 2950 } 2959 2951 ··· 2964 2952 struct snd_card *card = dev_get_drvdata(dev); 2965 2953 struct azx *chip = card->private_data; 2966 2954 2955 + if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) 2956 + hda_display_power(true); 2967 2957 azx_init_pci(chip); 2968 2958 azx_init_chip(chip, 1); 2969 2959 return 0; ··· 3190 3176 if (chip->fw) 3191 3177 release_firmware(chip->fw); 3192 3178 #endif 3179 + if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) { 3180 + hda_display_power(false); 3181 + hda_i915_exit(); 3182 + } 3193 3183 kfree(chip); 3194 3184 3195 3185 return 0; ··· 3419 3401 } 3420 3402 } 3421 3403 3404 + #ifdef CONFIG_SND_HDA_I915 3405 + static void azx_probe_work(struct work_struct *work) 3406 + { 3407 + azx_probe_continue(container_of(work, struct azx, probe_work)); 3408 + } 3409 + #endif 3410 + 3422 3411 /* 3423 3412 * constructor 3424 3413 */ ··· 3501 3476 return err; 3502 3477 } 3503 3478 3479 + #ifdef CONFIG_SND_HDA_I915 3480 + /* continue probing in work context as may trigger request module */ 3481 + INIT_WORK(&chip->probe_work, azx_probe_work); 3482 + #endif 3483 + 3504 3484 *rchip = chip; 3485 + 3505 3486 return 0; 3506 3487 } 3507 3488 ··· 3778 3747 } 3779 3748 #endif /* CONFIG_SND_HDA_PATCH_LOADER */ 3780 3749 3750 + /* continue probing in work context, avoid request_module deadlock */ 3751 + if (probe_now && (chip->driver_caps & AZX_DCAPS_I915_POWERWELL)) { 3752 + #ifdef CONFIG_SND_HDA_I915 3753 + probe_now = false; 3754 + schedule_work(&chip->probe_work); 3755 + #else 3756 + snd_printk(KERN_ERR SFX "Haswell must build in CONFIG_SND_HDA_I915\n"); 3757 + #endif 3758 + } 3759 + 3781 3760 if (probe_now) { 3782 3761 err = azx_probe_continue(chip); 3783 3762 if (err < 0) ··· 3809 3768 struct pci_dev *pci = chip->pci; 3810 3769 int dev = chip->dev_index; 3811 3770 int err; 3771 + 3772 + /* Request power well for Haswell HDA controller and codec */ 3773 + if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) { 3774 + err = hda_i915_init(); 3775 + if (err < 0) { 3776 + snd_printk(KERN_ERR SFX "Error request power-well from i915\n"); 3777 + goto out_free; 3778 + } 3779 + hda_display_power(true); 3780 + } 3812 3781 3813 3782 err = azx_first_init(chip); 3814 3783 if (err < 0) ··· 3914 3863 .driver_data = AZX_DRIVER_PCH | AZX_DCAPS_INTEL_PCH }, 3915 3864 /* Haswell */ 3916 3865 { PCI_DEVICE(0x8086, 0x0a0c), 3917 - .driver_data = AZX_DRIVER_SCH | AZX_DCAPS_INTEL_PCH }, 3866 + .driver_data = AZX_DRIVER_SCH | AZX_DCAPS_INTEL_PCH | 3867 + AZX_DCAPS_I915_POWERWELL }, 3918 3868 { PCI_DEVICE(0x8086, 0x0c0c), 3919 - .driver_data = AZX_DRIVER_SCH | AZX_DCAPS_INTEL_PCH }, 3869 + .driver_data = AZX_DRIVER_SCH | AZX_DCAPS_INTEL_PCH | 3870 + AZX_DCAPS_I915_POWERWELL }, 3920 3871 { PCI_DEVICE(0x8086, 0x0d0c), 3921 - .driver_data = AZX_DRIVER_SCH | AZX_DCAPS_INTEL_PCH }, 3872 + .driver_data = AZX_DRIVER_SCH | AZX_DCAPS_INTEL_PCH | 3873 + AZX_DCAPS_I915_POWERWELL }, 3922 3874 /* 5 Series/3400 */ 3923 3875 { PCI_DEVICE(0x8086, 0x3b56), 3924 3876 .driver_data = AZX_DRIVER_SCH | AZX_DCAPS_INTEL_PCH_NOPM },