>From 9b96b5c5dc578dc5cf170a62da6b5c67954e4a45 Mon Sep 17 00:00:00 2001
From: Takashi Iwai <tiwai@suse.de>
Date: Tue, 16 Mar 2021 23:01:02 -0400
Subject: [PATCH v2 8/8] ALSA: hda: Add support for BARs move on PCI rescan

Signed-off-by: Takashi Iwai <tiwai@suse.de>
---
 sound/pci/hda/hda_controller.c |   9 +++
 sound/pci/hda/hda_controller.h |   1 +
 sound/pci/hda/hda_intel.c      | 115 ++++++++++++++++++++++++++++++---
 3 files changed, 116 insertions(+), 9 deletions(-)

diff --git a/sound/pci/hda/hda_controller.c b/sound/pci/hda/hda_controller.c
index ca2f2ecd1488..5e32aa78aa32 100644
--- a/sound/pci/hda/hda_controller.c
+++ b/sound/pci/hda/hda_controller.c
@@ -1057,6 +1057,15 @@ void azx_stop_chip(struct azx *chip)
 }
 EXPORT_SYMBOL_GPL(azx_stop_chip);
 
+void azx_suspend_streams(struct azx *chip)
+{
+	struct azx_pcm *apcm;
+
+	list_for_each_entry(apcm, &chip->pcm_list, list)
+		snd_pcm_suspend_all(apcm->pcm);
+}
+EXPORT_SYMBOL_GPL(azx_suspend_streams);
+
 /*
  * interrupt handler
  */
diff --git a/sound/pci/hda/hda_controller.h b/sound/pci/hda/hda_controller.h
index 68f9668788ea..d40a5d87d34e 100644
--- a/sound/pci/hda/hda_controller.h
+++ b/sound/pci/hda/hda_controller.h
@@ -212,5 +212,6 @@ int azx_probe_codecs(struct azx *chip, unsigned int max_slots);
 int azx_codec_configure(struct azx *chip);
 int azx_init_streams(struct azx *chip);
 void azx_free_streams(struct azx *chip);
+void azx_suspend_streams(struct azx *chip);
 
 #endif /* __SOUND_HDA_CONTROLLER_H */
diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c
index c4146e8617de..d96093f0eeb5 100644
--- a/sound/pci/hda/hda_intel.c
+++ b/sound/pci/hda/hda_intel.c
@@ -1868,6 +1868,26 @@ static int azx_create(struct snd_card *card, struct pci_dev *pci,
 	return 0;
 }
 
+static int azx_request_pci_regions(struct azx *chip)
+{
+	struct hdac_bus *bus = azx_bus(chip);
+	struct pci_dev *pci = chip->pci;
+	int err;
+
+	err = pci_request_regions(pci, "ICH HD audio");
+	if (err < 0)
+		return err;
+	chip->region_requested = 1;
+
+	bus->addr = pci_resource_start(pci, 0);
+	bus->remap_addr = pci_ioremap_bar(pci, 0);
+	if (bus->remap_addr == NULL) {
+		dev_err(&pci->dev, "ioremap error\n");
+		return -ENXIO;
+	}
+	return 0;
+}
+
 static int azx_first_init(struct azx *chip)
 {
 	int dev = chip->dev_index;
@@ -1888,17 +1908,9 @@ static int azx_first_init(struct azx *chip)
 	}
 #endif
 
-	err = pci_request_regions(pci, "ICH HD audio");
+	err = azx_request_pci_regions(chip);
 	if (err < 0)
 		return err;
-	chip->region_requested = 1;
-
-	bus->addr = pci_resource_start(pci, 0);
-	bus->remap_addr = pci_ioremap_bar(pci, 0);
-	if (bus->remap_addr == NULL) {
-		dev_err(card->dev, "ioremap error\n");
-		return -ENXIO;
-	}
 
 	if (chip->driver_type == AZX_DRIVER_SKL)
 		snd_hdac_bus_parse_capabilities(bus);
@@ -2416,6 +2428,86 @@ static void azx_shutdown(struct pci_dev *pci)
 		azx_stop_chip(chip);
 }
 
+#ifdef CONFIG_PM
+static bool azx_bar_fixed(struct pci_dev *pdev, int resno)
+{
+	return false;
+}
+
+static void azx_rescan_prepare(struct pci_dev *pdev)
+{
+	struct snd_card *card = pci_get_drvdata(pdev);
+	struct azx *chip = card->private_data;
+	struct hda_intel *hda = container_of(chip, struct hda_intel, chip);
+	struct hdac_bus *bus = azx_bus(chip);
+	struct hda_codec *codec;
+
+	// FIXME: need unlock/lock dance as in azx_remove()?
+	flush_work(&hda->probe_work);
+
+	if (hda->freed || hda->init_failed)
+		return;
+
+	if (chip->running) {
+		pm_runtime_get_sync(&pdev->dev);
+		azx_prepare(&pdev->dev);
+		azx_suspend_streams(chip);
+		wait_event(card->power_ref_sleep,
+			   !refcount_read(&card->power_ref));
+		list_for_each_codec(codec, &chip->bus) {
+			pm_runtime_suspend(hda_codec_dev(codec));
+			pm_runtime_disable(hda_codec_dev(codec));
+		}
+		azx_suspend(&pdev->dev);
+	}
+
+	/* Unmap MMIO and release BAR resource */
+	iounmap(bus->remap_addr);
+	if (chip->region_requested) {
+		pci_release_regions(chip->pci);
+		chip->region_requested = 0;
+	}
+}
+
+static void azx_rescan_done(struct pci_dev *pdev)
+{
+	struct snd_card *card = pci_get_drvdata(pdev);
+	struct azx *chip = card->private_data;
+	struct hda_intel *hda = container_of(chip, struct hda_intel, chip);
+	struct hdac_bus *bus = azx_bus(chip);
+	struct hdac_stream *azx_dev;
+	struct hda_codec *codec;
+	int err;
+
+	if (hda->freed || hda->init_failed)
+		return;
+
+	/* Reassign BAR and remap */
+	err = azx_request_pci_regions(chip);
+	if (err < 0) {
+		dev_err(card->dev, "Rescan failed: disabling the device\n");
+		card->shutdown = 1;
+		hda->init_failed = true;
+		// FIXME: any better handling?
+		return;
+	}
+
+	// FIXME: should be in hdac_stream.c
+	list_for_each_entry(azx_dev, &bus->stream_list, list)
+		azx_dev->sd_addr = bus->remap_addr + (0x20 * azx_dev->index + 0x80);
+
+	if (chip->running) {
+		azx_resume(&pdev->dev);
+		list_for_each_codec(codec, &chip->bus) {
+			pm_runtime_enable(hda_codec_dev(codec));
+			pm_runtime_resume(hda_codec_dev(codec));
+		}
+		azx_complete(&pdev->dev);
+		pm_runtime_put_sync(&pdev->dev);
+	}
+}
+#endif /* CONFIG_PM */
+
 /* PCI IDs */
 static const struct pci_device_id azx_ids[] = {
 	/* CPT */
@@ -2783,6 +2875,11 @@ static struct pci_driver azx_driver = {
 	.driver = {
 		.pm = AZX_PM_OPS,
 	},
+#ifdef CONFIG_PM
+	.rescan_prepare	= azx_rescan_prepare,
+	.rescan_done	= azx_rescan_done,
+	.bar_fixed	= azx_bar_fixed,
+#endif
 };
 
 module_pci_driver(azx_driver);
-- 
2.26.2

