soc/intel/braswell: Correct configuration of interrupts

The level/edge mode of PIRQ is not configured and i8259 PIC not initialized.
Add calls to:
- i8259_configure_irq_trigger()
- setup_i8259()
- write_pci_config_irqs()
to correct the configuration of interrupts.

BUG=N/A
TEST=Intel CherryHill CRB

Change-Id: I128cb35dd0e348a9cd9fb162651e0aa2b7e4a3ef
Signed-off-by: Frans Hendriks <fhendriks@eltan.com>
Reviewed-on: https://review.coreboot.org/c/29419
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Michał Żygowski <michal.zygowski@3mdeb.com>
diff --git a/src/soc/intel/braswell/southcluster.c b/src/soc/intel/braswell/southcluster.c
index 6ea2ea9..418035f 100644
--- a/src/soc/intel/braswell/southcluster.c
+++ b/src/soc/intel/braswell/southcluster.c
@@ -27,6 +27,7 @@
 #include <device/pci.h>
 #include <device/pci_ids.h>
 #include <pc80/i8254.h>
+#include <pc80/i8259.h>
 #include <romstage_handoff.h>
 #include <soc/acpi.h>
 #include <soc/iomap.h>
@@ -78,6 +79,112 @@
 #define LPC_DEFAULT_IO_RANGE_LOWER 0
 #define LPC_DEFAULT_IO_RANGE_UPPER 0x1000
 
+/*
+ * Write PCI config space IRQ assignments.  PCI devices have the INT_LINE
+ * (0x3C) and INT_PIN (0x3D) registers which report interrupt routing
+ * information to operating systems and drivers.  The INT_PIN register is
+ * generally read only and reports which interrupt pin A - D it uses.  The
+ * INT_LINE register is configurable and reports which IRQ (generally the
+ * PIC IRQs 1 - 15) it will use.  This needs to take interrupt pin swizzling
+ * on devices that are downstream on a PCI bridge into account.
+ *
+ * This function will loop through all enabled PCI devices and program the
+ * INT_LINE register with the correct PIC IRQ number for the INT_PIN that it
+ * uses.  It then configures each interrupt in the pic to be level triggered.
+ */
+static void write_pci_config_irqs(void)
+{
+	struct device *irq_dev;
+	struct device *targ_dev;
+	uint8_t int_line = 0;
+	uint8_t original_int_pin = 0;
+	uint8_t new_int_pin = 0;
+	uint16_t current_bdf = 0;
+	uint16_t parent_bdf = 0;
+	uint8_t pirq = 0;
+	uint8_t device_num = 0;
+	const struct soc_irq_route *ir = &global_soc_irq_route;
+
+	if (ir == NULL) {
+		printk(BIOS_WARNING, "Warning: Can't write PCI IRQ assignments"
+			" because 'global_braswell_irq_route' structure does"
+			" not exist\n");
+		return;
+	}
+
+	/*
+	 * Loop through all enabled devices and program their
+	 * INT_LINE, INT_PIN registers from values taken from
+	 * the Interrupt Route registers in the ILB
+	 */
+	printk(BIOS_DEBUG, "PCI_CFG IRQ: Write PIRQ assignments\n");
+	for (irq_dev = all_devices; irq_dev; irq_dev = irq_dev->next) {
+
+		if ((irq_dev->path.type != DEVICE_PATH_PCI) ||
+			(!irq_dev->enabled))
+			continue;
+
+		current_bdf = irq_dev->path.pci.devfn |
+			irq_dev->bus->secondary << 8;
+
+		/*
+		 * Step 1: Get the INT_PIN and device structure to look for
+		 * in the pirq_data table defined in the mainboard directory.
+		 */
+		targ_dev = NULL;
+		new_int_pin = get_pci_irq_pins(irq_dev, &targ_dev);
+		if (targ_dev == NULL || new_int_pin < 1)
+			continue;
+
+		/* Get the original INT_PIN for record keeping */
+		original_int_pin = pci_read_config8(irq_dev, PCI_INTERRUPT_PIN);
+
+		parent_bdf = targ_dev->path.pci.devfn
+			| targ_dev->bus->secondary << 8;
+		device_num = PCI_SLOT(parent_bdf);
+
+		if (ir->pcidev[device_num] == 0) {
+			printk(BIOS_WARNING,
+				"Warning: PCI Device %d does not have an IRQ "
+				"entry, skipping it\n", device_num);
+			continue;
+		}
+
+		/* Find the PIRQ that is attached to the INT_PIN  */
+		pirq = (ir->pcidev[device_num] >> ((new_int_pin - 1) * 4))
+					& 0x7;
+
+		/* Get the INT_LINE this device/function will use */
+		int_line = ir->pic[pirq];
+
+		if (int_line != PIRQ_PIC_IRQDISABLE) {
+			/* Set this IRQ to level triggered */
+			i8259_configure_irq_trigger(int_line,
+				IRQ_LEVEL_TRIGGERED);
+			/* Set the Interrupt Line register */
+			pci_write_config8(irq_dev, PCI_INTERRUPT_LINE,
+				int_line);
+		} else {
+			/*
+			 * Set the Interrupt line register as 'unknown' or
+			 * 'unused'
+			 */
+			pci_write_config8(irq_dev, PCI_INTERRUPT_LINE,
+				PIRQ_PIC_UNKNOWN_UNUSED);
+		}
+
+		printk(BIOS_SPEW, "\tINT_PIN\t\t: %d (%s)\n",
+			original_int_pin, pin_to_str(original_int_pin));
+		if (parent_bdf != current_bdf)
+			printk(BIOS_SPEW, "\tSwizzled to\t: %d (%s)\n",
+				new_int_pin, pin_to_str(new_int_pin));
+		printk(BIOS_SPEW, "\tPIRQ\t\t: %c\n"
+					"\tINT_LINE\t: 0x%X (IRQ %d)\n",
+					'A' + pirq, int_line, int_line);
+	}
+	printk(BIOS_DEBUG, "PCI_CFG IRQ: Finished writing PIRQ assignments\n");
+}
+
 static inline int io_range_in_default(int base, int size)
 {
 	/* Does it start above the range? */
@@ -155,7 +262,6 @@
 	const unsigned long pr_base = ILB_BASE_ADDRESS + 0x08;
 	const unsigned long ir_base = ILB_BASE_ADDRESS + 0x20;
 	void *gen_pmcon1 = (void *)(PMC_BASE_ADDRESS + GEN_PMCON1);
-	void *actl = (void *)(ILB_BASE_ADDRESS + ACTL);
 	const struct soc_irq_route *ir = &global_soc_irq_route;
 	struct soc_intel_braswell_config *config = dev->chip_info;
 
@@ -172,8 +278,13 @@
 		write16((void *)(ir_base + i*sizeof(ir->pcidev[i])),
 			ir->pcidev[i]);
 
-	/* Route SCI to IRQ9 */
-	write32(actl, (read32(actl) & ~SCIS_MASK) | SCIS_IRQ9);
+	/* Interrupt 9 should be level triggered (SCI) */
+	i8259_configure_irq_trigger(9, 1);
+
+	for (i = 0; i < NUM_PIRQS; i++) {
+		if (ir->pic[i])
+			i8259_configure_irq_trigger(ir->pic[i], 1);
+	}
 
 	if (config->disable_slp_x_stretch_sus_fail) {
 		printk(BIOS_DEBUG, "Disabling slp_x stretching.\n");
@@ -184,6 +295,12 @@
 			read32(gen_pmcon1) & ~DIS_SLP_X_STRCH_SUS_UP);
 	}
 
+	/* Write IRQ assignments to PCI config space */
+	write_pci_config_irqs();
+
+	/* Initialize i8259 pic */
+	setup_i8259();
+
 	/* Initialize i8254 timers */
 	setup_i8254();
 }