nb/intel/haswell/pcie.c: Add missing pre-ASPM init

Add devicetree configuration parameters for mainboard-specific settings,
and provide reasonable defaults, which should usually be good enough.
This is based on Haswell SA Reference Code version 1.9.0 (Nov 2014).

Tested on Asrock B85M Pro4, registers now have the expected values.

Change-Id: I0dcdd4ca431c2ae1e62f2719c376d8bdef3054bd
Signed-off-by: Angel Pons <th3fanbus@gmail.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/47223
Reviewed-by: Nico Huber <nico.h@gmx.de>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
diff --git a/src/northbridge/intel/haswell/chip.h b/src/northbridge/intel/haswell/chip.h
index 99102b6..274e549 100644
--- a/src/northbridge/intel/haswell/chip.h
+++ b/src/northbridge/intel/haswell/chip.h
@@ -4,6 +4,14 @@
 #define NORTHBRIDGE_INTEL_HASWELL_CHIP_H
 
 #include <drivers/intel/gma/gma.h>
+#include <types.h>
+
+struct peg_config {
+	bool is_onboard;
+	uint8_t power_limit_scale;
+	uint8_t power_limit_value;
+	uint16_t phys_slot_number;
+};
 
 /*
  * Digital Port Hotplug Enable:
@@ -20,6 +28,8 @@
 	/* IGD panel configuration */
 	struct i915_gpu_panel_config panel_cfg;
 
+	struct peg_config peg_cfg[3];
+
 	bool gpu_ddi_e_connected;
 
 	bool ec_present;
diff --git a/src/northbridge/intel/haswell/haswell.h b/src/northbridge/intel/haswell/haswell.h
index f158c21..b6c2b5f 100644
--- a/src/northbridge/intel/haswell/haswell.h
+++ b/src/northbridge/intel/haswell/haswell.h
@@ -20,12 +20,33 @@
 
 /* Device 0:1.0 PCI configuration space (PCIe Graphics) */
 
+#define PEG_CAP		0xa2
+#define PEG_DCAP	0xa4
+
+#define PEG_LCAP	0xac
+
+#define PEG_DSTS	0xaa
+
+#define PEG_SLOTCAP	0xb4
+
 #define PEG_DCAP2	0xc4	/* 32bit */
 
+#define PEG_LCTL2	0xd0
+
+#define PEG_VC0RCTL	0x114
+
 #define PEG_ESD		0x144	/* 32bit */
 #define PEG_LE1D	0x150	/* 32bit */
 #define PEG_LE1A	0x158	/* 64bit */
 
+#define PEG_UESTS	0x1c4
+#define PEG_UESEV	0x1cc
+#define PEG_CESTS	0x1d0
+
+#define PEG_L0SLAT	0x22c
+
+#define PEG_AFE_PM_TMR	0xc28
+
 /* Device 0:2.0 PCI configuration space (Graphics Device) */
 
 #define MSAC		0x62	/* Multi Size Aperture Control */
diff --git a/src/northbridge/intel/haswell/pcie.c b/src/northbridge/intel/haswell/pcie.c
index a631cc8..ac6d4ca 100644
--- a/src/northbridge/intel/haswell/pcie.c
+++ b/src/northbridge/intel/haswell/pcie.c
@@ -5,7 +5,12 @@
 #include <device/pci.h>
 #include <device/pciexp.h>
 #include <device/pci_ids.h>
+#include <device/pci_ops.h>
 #include <assert.h>
+#include <types.h>
+
+#include "chip.h"
+#include "haswell.h"
 
 #if CONFIG(HAVE_ACPI_TABLES)
 static const char *pcie_acpi_name(const struct device *dev)
@@ -41,12 +46,101 @@
 }
 #endif
 
+static void peg_enable(struct device *dev)
+{
+	const struct northbridge_intel_haswell_config *config = config_of(dev);
+
+	const uint8_t func = PCI_FUNC(PCI_BDF(dev));
+
+	assert(func < ARRAY_SIZE(config->peg_cfg));
+
+	const bool slot_implemented = !config->peg_cfg[func].is_onboard;
+
+	if (slot_implemented) {
+		/* Default is 1, but register is R/WO and needs to be written to once */
+		pci_or_config16(dev, PEG_CAP, 1 << 8);
+	} else {
+		pci_and_config16(dev, PEG_CAP, ~(1 << 8));
+	}
+
+	/* Note: this register is write-once */
+	uint32_t slotcap = pci_read_config32(dev, PEG_SLOTCAP);
+
+	/* Physical slot number (zero for ports connected to onboard devices) */
+	slotcap &= ~(0x1fff << 19);
+	if (slot_implemented) {
+		uint16_t slot_number = config->peg_cfg[func].phys_slot_number & 0x1fff;
+		if (slot_number == 0) {
+			/* Slot number must be non-zero and unique */
+			slot_number = func + 1;
+		}
+		slotcap |= slot_number << 19;
+	}
+
+	/* Default to 1.0 watt scale */
+	slotcap &= ~(3 << 15);
+	slotcap |= (config->peg_cfg[func].power_limit_scale & 3) << 15;
+
+	uint8_t power_limit_value = config->peg_cfg[func].power_limit_value;
+	if (power_limit_value == 0) {
+		/* Default to 75 watts */
+		power_limit_value = 75;
+	}
+	slotcap &= ~(0xff << 7);
+	slotcap |= power_limit_value << 7;
+
+	pci_write_config32(dev, PEG_SLOTCAP, slotcap);
+
+	/* Clear errors */
+	pci_write_config16(dev, PCI_STATUS, 0xffff);
+	pci_write_config16(dev, PCI_SEC_STATUS, 0xffff);
+	pci_write_config16(dev, PEG_DSTS, 0xffff);
+	pci_write_config32(dev, PEG_UESTS, 0xffffffff);
+	pci_write_config32(dev, PEG_CESTS, 0xffffffff);
+	pci_write_config32(dev, 0x1f0, 0xffffffff);
+
+	pci_or_config32(dev, PEG_VC0RCTL, 0x7f << 1);
+
+	/* Advertise OBFF support using WAKE# signaling only */
+	pci_or_config32(dev, PEG_DCAP2, 1 << 19);
+
+	pci_or_config32(dev, PEG_UESEV, 1 << 14);
+
+	/* Select -3.5 dB de-emphasis */
+	pci_or_config32(dev, PEG_LCTL2, 1 << 6);
+
+	pci_or_config32(dev, PEG_L0SLAT, 1 << 31);
+
+	pci_update_config32(dev, 0x250, ~(7 << 20), 2 << 20);
+
+	pci_or_config32(dev, 0x238, 1 << 29);
+
+	pci_or_config32(dev, 0x1f8, 1 << 16);
+
+	pci_update_config32(dev, PEG_AFE_PM_TMR, ~0x1f, 0x13);
+
+	/* Lock DCAP */
+	pci_update_config32(dev, PEG_DCAP,  ~0, 0);
+
+	if (func == 0)
+		pci_or_config32(dev, 0xcd0, 1 << 11);
+
+	/* Enable support for L0s and L1 */
+	pci_or_config32(dev, PEG_LCAP, 3 << 10);
+
+	pci_and_config32(dev, 0x200, ~(3 << 26));
+
+	/* Other fields in this register must not be changed while writing this */
+	pci_or_config16(dev, 0x258, 1 << 2);
+}
+
 static struct device_operations device_ops = {
 	.read_resources		= pci_bus_read_resources,
 	.set_resources		= pci_dev_set_resources,
 	.enable_resources	= pci_bus_enable_resources,
 	.scan_bus		= pciexp_scan_bridge,
 	.reset_bus		= pci_bus_reset,
+	.enable			= peg_enable,
 	.init			= pci_dev_init,
 	.ops_pci		= &pci_dev_ops_pci,
 #if CONFIG(HAVE_ACPI_TABLES)