diff --git a/src/soc/intel/braswell/include/soc/irq.h b/src/soc/intel/braswell/include/soc/irq.h index a2327550a9..f9d3700be0 100644 --- a/src/soc/intel/braswell/include/soc/irq.h +++ b/src/soc/intel/braswell/include/soc/irq.h @@ -155,6 +155,7 @@ #define PIRQ_PIC_IRQ12 0xc #define PIRQ_PIC_IRQ14 0xe #define PIRQ_PIC_IRQ15 0xf +#define PIRQ_PIC_UNKNOWN_UNUSED 0xff /* Overloaded term, but these values determine the per device route. */ #define PIRQA 0 diff --git a/src/soc/intel/braswell/southcluster.c b/src/soc/intel/braswell/southcluster.c index 6ea2ea9aae..418035f493 100644 --- a/src/soc/intel/braswell/southcluster.c +++ b/src/soc/intel/braswell/southcluster.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -78,6 +79,112 @@ static void sc_add_mmio_resources(struct device *dev) #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 @@ static void sc_init(struct device *dev) 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 @@ static void sc_init(struct device *dev) 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 @@ static void sc_init(struct device *dev) 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(); }