diff options
author | Angel Pons <th3fanbus@gmail.com> | 2020-10-23 21:37:21 +0200 |
---|---|---|
committer | Angel Pons <th3fanbus@gmail.com> | 2020-10-30 00:45:51 +0000 |
commit | c200e8c7cdebed98860a771888efbf998c5912b3 (patch) | |
tree | 2a3d0151583646b33a5ba6e518c23e403433be85 /src/soc/intel/broadwell/pch | |
parent | 3cc2c38d50741fffb9193851a4a3b7c636f7cd4d (diff) |
soc/intel/broadwell: Move PCH code into pch subdir
Change-Id: Icb57eb89b4f225298e43ae27970dc1e27fb6e222
Signed-off-by: Angel Pons <th3fanbus@gmail.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/46706
Reviewed-by: Arthur Heymans <arthur@aheymans.xyz>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Diffstat (limited to 'src/soc/intel/broadwell/pch')
25 files changed, 6160 insertions, 0 deletions
diff --git a/src/soc/intel/broadwell/pch/Makefile.inc b/src/soc/intel/broadwell/pch/Makefile.inc new file mode 100644 index 0000000000..7e5c65aa76 --- /dev/null +++ b/src/soc/intel/broadwell/pch/Makefile.inc @@ -0,0 +1,38 @@ +bootblock-y += bootblock.c + +ramstage-y += adsp.c +romstage-y += early_pch.c +ramstage-$(CONFIG_ELOG) += elog.c +ramstage-y += gpio.c +romstage-y += gpio.c +smm-y += gpio.c +ramstage-y += hda.c +ramstage-y += iobp.c +romstage-y += iobp.c +ramstage-y += fadt.c +ramstage-y += lpc.c +ramstage-y += me.c +ramstage-y += me_status.c +romstage-y += me_status.c +ramstage-y += pch.c +romstage-y += pch.c +ramstage-y += pcie.c +ramstage-y += pmutil.c +romstage-y += pmutil.c +smm-y += pmutil.c +verstage-y += pmutil.c +romstage-y += power_state.c +ramstage-y += sata.c +ramstage-y += serialio.c +ramstage-y += smbus.c +ramstage-y += smi.c +smm-y += smihandler.c +romstage-$(CONFIG_DRIVERS_UART_8250MEM) += uart.c +bootblock-y += usb_debug.c +romstage-y += usb_debug.c +ramstage-y += usb_debug.c +ramstage-y += ehci.c +ramstage-y += xhci.c +smm-y += xhci.c + +ramstage-srcs += src/mainboard/$(MAINBOARDDIR)/hda_verb.c diff --git a/src/soc/intel/broadwell/pch/adsp.c b/src/soc/intel/broadwell/pch/adsp.c new file mode 100644 index 0000000000..06dd38bd8a --- /dev/null +++ b/src/soc/intel/broadwell/pch/adsp.c @@ -0,0 +1,144 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <acpi/acpi_gnvs.h> +#include <console/console.h> +#include <device/device.h> +#include <device/pci.h> +#include <device/pci_ids.h> +#include <device/pci_ops.h> +#include <device/mmio.h> +#include <soc/adsp.h> +#include <soc/device_nvs.h> +#include <soc/iobp.h> +#include <soc/nvs.h> +#include <soc/pch.h> +#include <soc/ramstage.h> +#include <soc/rcba.h> +#include <soc/intel/broadwell/pch/chip.h> + +static void adsp_init(struct device *dev) +{ + const struct soc_intel_broadwell_pch_config *config = config_of(dev); + struct resource *bar0, *bar1; + u32 tmp32; + + /* Ensure memory and bus master are enabled */ + pci_or_config16(dev, PCI_COMMAND, PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY); + + /* Find BAR0 and BAR1 */ + bar0 = find_resource(dev, PCI_BASE_ADDRESS_0); + if (!bar0) + return; + bar1 = find_resource(dev, PCI_BASE_ADDRESS_1); + if (!bar1) + return; + + /* + * Set LTR value in DSP shim LTR control register to 3ms + * SNOOP_REQ[13]=1b SNOOP_SCALE[12:10]=100b (1ms) SNOOP_VAL[9:0]=3h + */ + tmp32 = pch_is_wpt() ? ADSP_SHIM_BASE_WPT : ADSP_SHIM_BASE_LPT; + write32(res2mmio(bar0, tmp32 + ADSP_SHIM_LTRC, 0), + ADSP_SHIM_LTRC_VALUE); + + /* Program VDRTCTL2 D19:F0:A8[31:0] = 0x00000fff */ + pci_write_config32(dev, ADSP_PCI_VDRTCTL2, ADSP_VDRTCTL2_VALUE); + + /* Program ADSP IOBP VDLDAT1 to 0x040100 */ + pch_iobp_write(ADSP_IOBP_VDLDAT1, ADSP_VDLDAT1_VALUE); + + /* Set D3 Power Gating Enable in D19:F0:A0 based on PCH type */ + tmp32 = pci_read_config32(dev, ADSP_PCI_VDRTCTL0); + if (pch_is_wpt()) { + if (config->adsp_d3_pg_enable) { + tmp32 &= ~ADSP_VDRTCTL0_D3PGD_WPT; + if (config->adsp_sram_pg_enable) + tmp32 &= ~ADSP_VDRTCTL0_D3SRAMPGD_WPT; + else + tmp32 |= ADSP_VDRTCTL0_D3SRAMPGD_WPT; + } else { + tmp32 |= ADSP_VDRTCTL0_D3PGD_WPT; + } + } else { + if (config->adsp_d3_pg_enable) { + tmp32 &= ~ADSP_VDRTCTL0_D3PGD_LPT; + if (config->adsp_sram_pg_enable) + tmp32 &= ~ADSP_VDRTCTL0_D3SRAMPGD_LPT; + else + tmp32 |= ADSP_VDRTCTL0_D3SRAMPGD_LPT; + } else { + tmp32 |= ADSP_VDRTCTL0_D3PGD_LPT; + } + } + pci_write_config32(dev, ADSP_PCI_VDRTCTL0, tmp32); + + /* Set PSF Snoop to SA, RCBA+0x3350[10]=1b */ + RCBA32_OR(0x3350, (1 << 10)); + + /* Set DSP IOBP PMCTL 0x1e0=0x3f */ + pch_iobp_write(ADSP_IOBP_PMCTL, ADSP_PMCTL_VALUE); + + if (config->sio_acpi_mode) { + /* Configure for ACPI mode */ + struct global_nvs *gnvs; + + printk(BIOS_INFO, "ADSP: Enable ACPI Mode IRQ3\n"); + + /* Find ACPI NVS to update BARs */ + gnvs = acpi_get_gnvs(); + if (!gnvs) + return; + + /* Save BAR0 and BAR1 to ACPI NVS */ + gnvs->dev.bar0[SIO_NVS_ADSP] = (u32)bar0->base; + gnvs->dev.bar1[SIO_NVS_ADSP] = (u32)bar1->base; + gnvs->dev.enable[SIO_NVS_ADSP] = 1; + + /* Set PCI Config Disable Bit */ + pch_iobp_update(ADSP_IOBP_PCICFGCTL, ~0, ADSP_PCICFGCTL_PCICD); + + /* Set interrupt de-assert/assert opcode override to IRQ3 */ + pch_iobp_write(ADSP_IOBP_VDLDAT2, ADSP_IOBP_ACPI_IRQ3); + + /* Enable IRQ3 in RCBA */ + RCBA32_OR(ACPIIRQEN, ADSP_ACPI_IRQEN); + + /* Set ACPI Interrupt Enable Bit */ + pch_iobp_update(ADSP_IOBP_PCICFGCTL, ~ADSP_PCICFGCTL_SPCBAD, + ADSP_PCICFGCTL_ACPIIE); + + /* Put ADSP in D3hot */ + tmp32 = read32(res2mmio(bar1, PCH_PCS, 0)); + tmp32 |= PCH_PCS_PS_D3HOT; + write32(res2mmio(bar1, PCH_PCS, 0), tmp32); + } else { + printk(BIOS_INFO, "ADSP: Enable PCI Mode IRQ23\n"); + + /* Configure for PCI mode */ + pci_write_config8(dev, PCI_INTERRUPT_LINE, ADSP_PCI_IRQ); + + /* Clear ACPI Interrupt Enable Bit */ + pch_iobp_update(ADSP_IOBP_PCICFGCTL, + ~(ADSP_PCICFGCTL_SPCBAD | ADSP_PCICFGCTL_ACPIIE), 0); + } +} + +static struct device_operations adsp_ops = { + .read_resources = pci_dev_read_resources, + .set_resources = pci_dev_set_resources, + .enable_resources = pci_dev_enable_resources, + .init = adsp_init, + .ops_pci = &pci_dev_ops_pci, +}; + +static const unsigned short pci_device_ids[] = { + 0x9c36, /* LynxPoint */ + 0x9cb6, /* WildcatPoint */ + 0 +}; + +static const struct pci_driver pch_adsp __pci_driver = { + .ops = &adsp_ops, + .vendor = PCI_VENDOR_ID_INTEL, + .devices = pci_device_ids, +}; diff --git a/src/soc/intel/broadwell/pch/bootblock.c b/src/soc/intel/broadwell/pch/bootblock.c new file mode 100644 index 0000000000..7f6d0d52d9 --- /dev/null +++ b/src/soc/intel/broadwell/pch/bootblock.c @@ -0,0 +1,115 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <arch/bootblock.h> +#include <device/pci_ops.h> +#include <soc/iomap.h> +#include <soc/lpc.h> +#include <soc/pci_devs.h> +#include <soc/rcba.h> +#include <soc/spi.h> +#include <soc/pm.h> +#include <soc/romstage.h> +#include <southbridge/intel/common/early_spi.h> + +static void map_rcba(void) +{ + pci_write_config32(PCH_DEV_LPC, RCBA, RCBA_BASE_ADDRESS | 1); +} + +static void enable_port80_on_lpc(void) +{ + /* Enable port 80 POST on LPC. The chipset does this by default, + * but it doesn't appear to hurt anything. */ + u32 gcs = RCBA32(GCS); + gcs = gcs & ~0x4; + RCBA32(GCS) = gcs; +} + +static void set_spi_speed(void) +{ + u32 fdod; + u8 ssfc; + + /* Observe SPI Descriptor Component Section 0 */ + SPIBAR32(SPIBAR_FDOC) = 0x1000; + + /* Extract the Write/Erase SPI Frequency from descriptor */ + fdod = SPIBAR32(SPIBAR_FDOD); + fdod >>= 24; + fdod &= 7; + + /* Set Software Sequence frequency to match */ + ssfc = SPIBAR8(SPIBAR_SSFC + 2); + ssfc &= ~7; + ssfc |= fdod; + SPIBAR8(SPIBAR_SSFC + 2) = ssfc; +} + +static void pch_enable_bars(void) +{ + /* Set up southbridge BARs */ + pci_write_config32(PCH_DEV_LPC, RCBA, RCBA_BASE_ADDRESS | 1); + + pci_write_config32(PCH_DEV_LPC, PMBASE, ACPI_BASE_ADDRESS | 1); + + pci_write_config8(PCH_DEV_LPC, ACPI_CNTL, ACPI_EN); + + pci_write_config32(PCH_DEV_LPC, GPIO_BASE, GPIO_BASE_ADDRESS | 1); + + /* Enable GPIO functionality. */ + pci_write_config8(PCH_DEV_LPC, GPIO_CNTL, GPIO_EN); +} + +static void pch_early_lpc(void) +{ + pch_enable_bars(); + + /* Set COM1/COM2 decode range */ + pci_write_config16(PCH_DEV_LPC, LPC_IO_DEC, 0x0010); + + /* Enable SuperIO + MC + COM1 + PS/2 Keyboard/Mouse */ + u16 lpc_config = CNF1_LPC_EN | CNF2_LPC_EN | GAMEL_LPC_EN | + COMA_LPC_EN | KBC_LPC_EN | MC_LPC_EN; + pci_write_config16(PCH_DEV_LPC, LPC_EN, lpc_config); + + /* Enable IOAPIC */ + RCBA16(OIC) = 0x0100; + + /* Read back for posted write */ + (void)RCBA16(OIC); + + /* Set HPET address and enable it */ + RCBA32_AND_OR(HPTC, ~3, 1 << 7); + + /* + * Reading the register back guarantees that the write is + * done before we use the configured base address below. + */ + (void)RCBA32(HPTC); + + /* Enable HPET to start counter */ + setbits32((void *)HPET_BASE_ADDRESS + 0x10, 1 << 0); + + /* Disable reset */ + RCBA32_OR(GCS, 1 << 5); + + /* TCO timer halt */ + u16 reg16 = inb(ACPI_BASE_ADDRESS + TCO1_CNT); + reg16 |= TCO_TMR_HLT; + outb(reg16, ACPI_BASE_ADDRESS + TCO1_CNT); + + /* Enable upper 128 bytes of CMOS */ + RCBA32_OR(RC, 1 << 2); + + /* Disable unused device (always) */ + RCBA32_OR(FD, PCH_DISABLE_ALWAYS); +} + +void bootblock_early_southbridge_init(void) +{ + map_rcba(); + enable_spi_prefetching_and_caching(); + enable_port80_on_lpc(); + set_spi_speed(); + pch_early_lpc(); +} diff --git a/src/soc/intel/broadwell/pch/early_pch.c b/src/soc/intel/broadwell/pch/early_pch.c new file mode 100644 index 0000000000..149dda1ca0 --- /dev/null +++ b/src/soc/intel/broadwell/pch/early_pch.c @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <device/device.h> +#include <device/pci_def.h> +#include <device/pci_ops.h> +#include <device/smbus_host.h> +#include <soc/iomap.h> +#include <soc/lpc.h> +#include <soc/pch.h> +#include <soc/pci_devs.h> +#include <soc/pm.h> +#include <soc/rcba.h> +#include <soc/romstage.h> +#include <soc/smbus.h> +#include <soc/intel/broadwell/pch/chip.h> + +static void pch_route_interrupts(void) +{ + /* + * GFX INTA -> PIRQA (MSI) + * D28IP_P1IP PCIE INTA -> PIRQA + * D29IP_E1P EHCI INTA -> PIRQD + * D20IP_XHCI XHCI INTA -> PIRQC (MSI) + * D31IP_SIP SATA INTA -> PIRQF (MSI) + * D31IP_SMIP SMBUS INTB -> PIRQG + * D31IP_TTIP THRT INTC -> PIRQA + * D27IP_ZIP HDA INTA -> PIRQG (MSI) + */ + + /* Device interrupt pin register (board specific) */ + RCBA32(D31IP) = (INTC << D31IP_TTIP) | (NOINT << D31IP_SIP2) | + (INTB << D31IP_SMIP) | (INTA << D31IP_SIP); + RCBA32(D29IP) = (INTA << D29IP_E1P); + RCBA32(D28IP) = (INTA << D28IP_P1IP) | (INTC << D28IP_P3IP) | + (INTB << D28IP_P4IP); + RCBA32(D27IP) = (INTA << D27IP_ZIP); + RCBA32(D26IP) = (INTA << D26IP_E2P); + RCBA32(D22IP) = (NOINT << D22IP_MEI1IP); + RCBA32(D20IP) = (INTA << D20IP_XHCI); + + /* Device interrupt route registers */ + RCBA32(D31IR) = DIR_ROUTE(PIRQG, PIRQC, PIRQB, PIRQA); /* LPC */ + RCBA32(D29IR) = DIR_ROUTE(PIRQD, PIRQD, PIRQD, PIRQD); /* EHCI */ + RCBA32(D28IR) = DIR_ROUTE(PIRQA, PIRQB, PIRQC, PIRQD); /* PCIE */ + RCBA32(D27IR) = DIR_ROUTE(PIRQG, PIRQG, PIRQG, PIRQG); /* HDA */ + RCBA32(D23IR) = DIR_ROUTE(PIRQH, PIRQH, PIRQH, PIRQH); /* SDIO */ + RCBA32(D22IR) = DIR_ROUTE(PIRQA, PIRQA, PIRQA, PIRQA); /* ME */ + RCBA32(D21IR) = DIR_ROUTE(PIRQE, PIRQF, PIRQF, PIRQF); /* SIO */ + RCBA32(D20IR) = DIR_ROUTE(PIRQC, PIRQC, PIRQC, PIRQC); /* XHCI */ +} + +static void pch_enable_lpc(void) +{ + /* Lookup device tree in romstage */ + const struct device *const dev = pcidev_on_root(0x1f, 0); + + const struct soc_intel_broadwell_pch_config *config = config_of(dev); + + pci_write_config32(PCH_DEV_LPC, LPC_GEN1_DEC, config->gen1_dec); + pci_write_config32(PCH_DEV_LPC, LPC_GEN2_DEC, config->gen2_dec); + pci_write_config32(PCH_DEV_LPC, LPC_GEN3_DEC, config->gen3_dec); + pci_write_config32(PCH_DEV_LPC, LPC_GEN4_DEC, config->gen4_dec); +} + +void pch_early_init(void) +{ + pch_route_interrupts(); + + pch_enable_lpc(); + + enable_smbus(); + + /* 8.14 Additional PCI Express Programming Steps, step #1 */ + pci_update_config32(_PCH_DEV(PCIE, 0), 0xf4, ~0x60, 0); + pci_update_config32(_PCH_DEV(PCIE, 0), 0xf4, ~0x80, 0x80); + pci_update_config32(_PCH_DEV(PCIE, 0), 0xe2, ~0x30, 0x30); +} diff --git a/src/soc/intel/broadwell/pch/ehci.c b/src/soc/intel/broadwell/pch/ehci.c new file mode 100644 index 0000000000..5b977dd3c6 --- /dev/null +++ b/src/soc/intel/broadwell/pch/ehci.c @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <device/device.h> +#include <device/pci.h> +#include <device/pci_ids.h> +#include <device/pci_ehci.h> +#include <device/pci_ops.h> +#include <soc/ehci.h> +#include <soc/pch.h> + +static void usb_ehci_set_subsystem(struct device *dev, unsigned int vendor, + unsigned int device) +{ + u8 access_cntl; + + access_cntl = pci_read_config8(dev, 0x80); + + /* Enable writes to protected registers. */ + pci_write_config8(dev, 0x80, access_cntl | 1); + + pci_dev_set_subsystem(dev, vendor, device); + + /* Restore protection. */ + pci_write_config8(dev, 0x80, access_cntl); +} + +static void ehci_enable(struct device *dev) +{ + if (CONFIG(USBDEBUG)) + dev->enabled = 1; + else + pch_disable_devfn(dev); +} + +static struct pci_operations ehci_ops_pci = { + .set_subsystem = &usb_ehci_set_subsystem, +}; + +static struct device_operations usb_ehci_ops = { + .read_resources = pci_ehci_read_resources, + .set_resources = pci_dev_set_resources, + .enable_resources = pci_dev_enable_resources, + .ops_pci = &ehci_ops_pci, + .enable = ehci_enable, +}; + +static const unsigned short pci_device_ids[] = { + 0x9c26, /* LynxPoint-LP */ + 0x9ca6, /* WildcatPoint */ + 0 +}; + +static const struct pci_driver pch_usb_ehci __pci_driver = { + .ops = &usb_ehci_ops, + .vendor = PCI_VENDOR_ID_INTEL, + .devices = pci_device_ids, +}; diff --git a/src/soc/intel/broadwell/pch/elog.c b/src/soc/intel/broadwell/pch/elog.c new file mode 100644 index 0000000000..9271e27872 --- /dev/null +++ b/src/soc/intel/broadwell/pch/elog.c @@ -0,0 +1,124 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <bootstate.h> +#include <cbmem.h> +#include <console/console.h> +#include <stdint.h> +#include <elog.h> +#include <soc/lpc.h> +#include <soc/pm.h> + +static void pch_log_gpio_gpe(u32 gpe0_sts, u32 gpe0_en, int start) +{ + int i; + + gpe0_sts &= gpe0_en; + + for (i = 0; i <= 31; i++) { + if (gpe0_sts & (1 << i)) + elog_add_event_wake(ELOG_WAKE_SOURCE_GPE, i + start); + } +} + +static void pch_log_wake_source(struct chipset_power_state *ps) +{ + /* Power Button */ + if (ps->pm1_sts & PWRBTN_STS) + elog_add_event_wake(ELOG_WAKE_SOURCE_PWRBTN, 0); + + /* RTC */ + if (ps->pm1_sts & RTC_STS) + elog_add_event_wake(ELOG_WAKE_SOURCE_RTC, 0); + + /* PCI Express (TODO: determine wake device) */ + if (ps->pm1_sts & PCIEXPWAK_STS) + elog_add_event_wake(ELOG_WAKE_SOURCE_PCIE, 0); + + /* PME (TODO: determine wake device) */ + if (ps->gpe0_sts[GPE_STD] & PME_STS) + elog_add_event_wake(ELOG_WAKE_SOURCE_PME, 0); + + /* Internal PME (TODO: determine wake device) */ + if (ps->gpe0_sts[GPE_STD] & PME_B0_STS) + elog_add_event_wake(ELOG_WAKE_SOURCE_PME_INTERNAL, 0); + + /* SMBUS Wake */ + if (ps->gpe0_sts[GPE_STD] & SMB_WAK_STS) + elog_add_event_wake(ELOG_WAKE_SOURCE_SMBUS, 0); + + /* GPIO27 */ + if (ps->gpe0_sts[GPE_STD] & GP27_STS) + elog_add_event_wake(ELOG_WAKE_SOURCE_GPE, 27); + + /* Log GPIO events in set 1-3 */ + pch_log_gpio_gpe(ps->gpe0_sts[GPE_31_0], ps->gpe0_en[GPE_31_0], 0); + pch_log_gpio_gpe(ps->gpe0_sts[GPE_63_32], ps->gpe0_en[GPE_63_32], 32); + pch_log_gpio_gpe(ps->gpe0_sts[GPE_94_64], ps->gpe0_en[GPE_94_64], 64); +} + +static void pch_log_power_and_resets(struct chipset_power_state *ps) +{ + /* Thermal Trip Status */ + if (ps->gen_pmcon2 & THERMTRIP_STS) + elog_add_event(ELOG_TYPE_THERM_TRIP); + + /* PWR_FLR Power Failure */ + if (ps->gen_pmcon2 & PWROK_FLR) + elog_add_event(ELOG_TYPE_POWER_FAIL); + + /* SUS Well Power Failure */ + if (ps->gen_pmcon3 & SUS_PWR_FLR) + elog_add_event(ELOG_TYPE_SUS_POWER_FAIL); + + /* SYS_PWROK Failure */ + if (ps->gen_pmcon2 & SYSPWR_FLR) + elog_add_event(ELOG_TYPE_SYS_PWROK_FAIL); + + /* PWROK Failure */ + if (ps->gen_pmcon2 & PWROK_FLR) + elog_add_event(ELOG_TYPE_PWROK_FAIL); + + /* TCO Timeout */ + if (ps->prev_sleep_state != ACPI_S3 && + ps->tco2_sts & TCO2_STS_SECOND_TO) + elog_add_event(ELOG_TYPE_TCO_RESET); + + /* Power Button Override */ + if (ps->pm1_sts & PRBTNOR_STS) + elog_add_event(ELOG_TYPE_POWER_BUTTON_OVERRIDE); + + /* RTC reset */ + if (ps->gen_pmcon3 & RTC_BATTERY_DEAD) + elog_add_event(ELOG_TYPE_RTC_RESET); + + /* System Reset Status (reset button pushed) */ + if (ps->gen_pmcon2 & SYSTEM_RESET_STS) + elog_add_event(ELOG_TYPE_RESET_BUTTON); + + /* General Reset Status */ + if (ps->gen_pmcon3 & GEN_RST_STS) + elog_add_event(ELOG_TYPE_SYSTEM_RESET); + + /* ACPI Wake Event */ + if (ps->prev_sleep_state != ACPI_S0) + elog_add_event_byte(ELOG_TYPE_ACPI_WAKE, ps->prev_sleep_state); +} + +static void pch_log_state(void *unused) +{ + struct chipset_power_state *ps = cbmem_find(CBMEM_ID_POWER_STATE); + + if (ps == NULL) { + printk(BIOS_ERR, "Not logging power state information. " + "Power state not found in cbmem.\n"); + return; + } + + /* Power and Reset */ + pch_log_power_and_resets(ps); + + /* Wake Sources */ + pch_log_wake_source(ps); +} + +BOOT_STATE_INIT_ENTRY(BS_DEV_INIT, BS_ON_ENTRY, pch_log_state, NULL); diff --git a/src/soc/intel/broadwell/pch/fadt.c b/src/soc/intel/broadwell/pch/fadt.c new file mode 100644 index 0000000000..8fbd0c45ad --- /dev/null +++ b/src/soc/intel/broadwell/pch/fadt.c @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <acpi/acpi.h> +#include <cpu/x86/smm.h> +#include <soc/iomap.h> +#include <soc/pm.h> +#include "chip.h" + +void acpi_fill_fadt(acpi_fadt_t *fadt) +{ + const uint16_t pmbase = ACPI_BASE_ADDRESS; + + fadt->sci_int = acpi_sci_irq(); + + if (permanent_smi_handler()) { + fadt->smi_cmd = APM_CNT; + fadt->acpi_enable = APM_CNT_ACPI_ENABLE; + fadt->acpi_disable = APM_CNT_ACPI_DISABLE; + } + + fadt->pm1a_evt_blk = pmbase + PM1_STS; + fadt->pm1a_cnt_blk = pmbase + PM1_CNT; + fadt->pm2_cnt_blk = pmbase + PM2_CNT; + fadt->pm_tmr_blk = pmbase + PM1_TMR; + fadt->gpe0_blk = pmbase + GPE0_STS(0); + + fadt->pm1_evt_len = 4; + fadt->pm1_cnt_len = 2; + fadt->pm2_cnt_len = 1; + fadt->pm_tmr_len = 4; + fadt->gpe0_blk_len = 32; + fadt->p_lvl2_lat = 1; + fadt->p_lvl3_lat = 87; + fadt->duty_offset = 1; + fadt->duty_width = 0; + fadt->day_alrm = 0xd; + fadt->mon_alrm = 0x00; + fadt->century = 0x00; + fadt->iapc_boot_arch = ACPI_FADT_LEGACY_DEVICES | ACPI_FADT_8042; + + fadt->flags |= ACPI_FADT_WBINVD | + ACPI_FADT_C1_SUPPORTED | + ACPI_FADT_C2_MP_SUPPORTED | + ACPI_FADT_SLEEP_BUTTON | + ACPI_FADT_SEALED_CASE | + ACPI_FADT_S4_RTC_WAKE | + ACPI_FADT_PLATFORM_CLOCK; + + fadt->x_pm1a_evt_blk.space_id = ACPI_ADDRESS_SPACE_IO; + fadt->x_pm1a_evt_blk.bit_width = fadt->pm1_evt_len * 8; + fadt->x_pm1a_evt_blk.bit_offset = 0; + fadt->x_pm1a_evt_blk.access_size = ACPI_ACCESS_SIZE_WORD_ACCESS; + fadt->x_pm1a_evt_blk.addrl = pmbase + PM1_STS; + fadt->x_pm1a_evt_blk.addrh = 0x0; + + fadt->x_pm1a_cnt_blk.space_id = ACPI_ADDRESS_SPACE_IO; + fadt->x_pm1a_cnt_blk.bit_width = fadt->pm1_cnt_len * 8; + fadt->x_pm1a_cnt_blk.bit_offset = 0; + fadt->x_pm1a_cnt_blk.access_size = ACPI_ACCESS_SIZE_WORD_ACCESS; + fadt->x_pm1a_cnt_blk.addrl = pmbase + PM1_CNT; + fadt->x_pm1a_cnt_blk.addrh = 0x0; + + fadt->x_pm2_cnt_blk.space_id = ACPI_ADDRESS_SPACE_IO; + fadt->x_pm2_cnt_blk.bit_width = fadt->pm2_cnt_len * 8; + fadt->x_pm2_cnt_blk.bit_offset = 0; + fadt->x_pm2_cnt_blk.access_size = ACPI_ACCESS_SIZE_BYTE_ACCESS; + fadt->x_pm2_cnt_blk.addrl = pmbase + PM2_CNT; + fadt->x_pm2_cnt_blk.addrh = 0x0; + + fadt->x_pm_tmr_blk.space_id = ACPI_ADDRESS_SPACE_IO; + fadt->x_pm_tmr_blk.bit_width = fadt->pm_tmr_len * 8; + fadt->x_pm_tmr_blk.bit_offset = 0; + fadt->x_pm_tmr_blk.access_size = ACPI_ACCESS_SIZE_DWORD_ACCESS; + fadt->x_pm_tmr_blk.addrl = pmbase + PM1_TMR; + fadt->x_pm_tmr_blk.addrh = 0x0; + + /* + * Windows 10 requires x_gpe0_blk to be set starting with FADT revision 5. + * The bit_width field intentionally overflows here. + * The OSPM can instead use the values in `fadt->gpe0_blk{,_len}`, which + * seems to work fine on Linux 5.0 and Windows 10. + */ + fadt->x_gpe0_blk.space_id = ACPI_ADDRESS_SPACE_IO; + fadt->x_gpe0_blk.bit_width = fadt->gpe0_blk_len * 8; + fadt->x_gpe0_blk.bit_offset = 0; + fadt->x_gpe0_blk.access_size = ACPI_ACCESS_SIZE_BYTE_ACCESS; + fadt->x_gpe0_blk.addrl = fadt->gpe0_blk; + fadt->x_gpe0_blk.addrh = 0x0; +} diff --git a/src/soc/intel/broadwell/pch/gpio.c b/src/soc/intel/broadwell/pch/gpio.c new file mode 100644 index 0000000000..ff1f019ce0 --- /dev/null +++ b/src/soc/intel/broadwell/pch/gpio.c @@ -0,0 +1,133 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <stdint.h> +#include <arch/io.h> +#include <device/device.h> +#include <device/pci.h> +#include <soc/gpio.h> +#include <soc/iomap.h> +#include <soc/pm.h> + +/* + * This function will return a number that indicates which PIRQ + * this GPIO maps to. If this is not a PIRQ capable GPIO then + * it will return -1. The GPIO to PIRQ mapping is not linear. + */ +static int gpio_to_pirq(int gpio) +{ + switch (gpio) { + case 8: return 0; /* PIRQI */ + case 9: return 1; /* PIRQJ */ + case 10: return 2; /* PIRQK */ + case 13: return 3; /* PIRQL */ + case 14: return 4; /* PIRQM */ + case 45: return 5; /* PIRQN */ + case 46: return 6; /* PIRQO */ + case 47: return 7; /* PIRQP */ + case 48: return 8; /* PIRQQ */ + case 49: return 9; /* PIRQR */ + case 50: return 10; /* PIRQS */ + case 51: return 11; /* PIRQT */ + case 52: return 12; /* PIRQU */ + case 53: return 13; /* PIRQV */ + case 54: return 14; /* PIRQW */ + case 55: return 15; /* PIRQX */ + default: return -1; + }; +} + +void init_gpios(const struct gpio_config config[]) +{ + const struct gpio_config *entry; + u32 owner[3] = {0}; + u32 route[3] = {0}; + u32 irqen[3] = {0}; + u32 reset[3] = {0}; + u32 blink = 0; + u16 pirq2apic = 0; + int set, bit, gpio = 0; + + for (entry = config; entry->conf0 != GPIO_LIST_END; entry++, gpio++) { + if (gpio > MAX_GPIO_NUMBER) + break; + + /* Setup Configuration registers 1 and 2 */ + outl(entry->conf0, GPIO_BASE_ADDRESS + GPIO_CONFIG0(gpio)); + outl(entry->conf1, GPIO_BASE_ADDRESS + GPIO_CONFIG1(gpio)); + + /* Determine set and bit based on GPIO number */ + set = gpio >> 5; + bit = gpio % 32; + + /* Apply settings to set specific bits */ + owner[set] |= entry->owner << bit; + route[set] |= entry->route << bit; + irqen[set] |= entry->irqen << bit; + reset[set] |= entry->reset << bit; + + if (set == 0) + blink |= entry->blink << bit; + + /* PIRQ to IO-APIC map */ + if (entry->pirq == GPIO_PIRQ_APIC_ROUTE) { + set = gpio_to_pirq(gpio); + if (set >= 0) + pirq2apic |= 1 << set; + } + } + + for (set = 0; set <= 2; set++) { + outl(owner[set], GPIO_BASE_ADDRESS + GPIO_OWNER(set)); + outl(route[set], GPIO_BASE_ADDRESS + GPIO_ROUTE(set)); + outl(irqen[set], GPIO_BASE_ADDRESS + GPIO_IRQ_IE(set)); + outl(reset[set], GPIO_BASE_ADDRESS + GPIO_RESET(set)); + } + + outl(blink, GPIO_BASE_ADDRESS + GPIO_BLINK); + outl(pirq2apic, GPIO_BASE_ADDRESS + GPIO_PIRQ_APIC_EN); +} + +int get_gpio(int gpio_num) +{ + if (gpio_num > MAX_GPIO_NUMBER) + return 0; + + return !!(inl(GPIO_BASE_ADDRESS + GPIO_CONFIG0(gpio_num)) & GPI_LEVEL); +} + +/* + * get a number comprised of multiple GPIO values. gpio_num_array points to + * the array of gpio pin numbers to scan, terminated by -1. + */ +unsigned int get_gpios(const int *gpio_num_array) +{ + int gpio; + unsigned int bitmask = 1; + unsigned int vector = 0; + + while (bitmask && + ((gpio = *gpio_num_array++) != -1)) { + if (get_gpio(gpio)) + vector |= bitmask; + bitmask <<= 1; + } + return vector; +} + +void set_gpio(int gpio_num, int value) +{ + u32 conf0; + + if (gpio_num > MAX_GPIO_NUMBER) + return; + + conf0 = inl(GPIO_BASE_ADDRESS + GPIO_CONFIG0(gpio_num)); + conf0 &= ~GPO_LEVEL_MASK; + conf0 |= value << GPO_LEVEL_SHIFT; + outl(conf0, GPIO_BASE_ADDRESS + GPIO_CONFIG0(gpio_num)); +} + +int gpio_is_native(int gpio_num) +{ + return !(inl(GPIO_BASE_ADDRESS + GPIO_CONFIG0(gpio_num)) & 1); +} diff --git a/src/soc/intel/broadwell/pch/hda.c b/src/soc/intel/broadwell/pch/hda.c new file mode 100644 index 0000000000..04390d1342 --- /dev/null +++ b/src/soc/intel/broadwell/pch/hda.c @@ -0,0 +1,155 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <console/console.h> +#include <device/device.h> +#include <device/azalia_device.h> +#include <device/pci.h> +#include <device/pci_ids.h> +#include <device/pci_ops.h> +#include <device/mmio.h> +#include <soc/intel/common/hda_verb.h> +#include <soc/pch.h> +#include <soc/ramstage.h> +#include <soc/rcba.h> + +static void codecs_init(u8 *base, u32 codec_mask) +{ + int i; + + /* Can support up to 4 codecs */ + for (i = 3; i >= 0; i--) { + if (codec_mask & (1 << i)) + hda_codec_init(base, i, + cim_verb_data_size, + cim_verb_data); + } + + if (pc_beep_verbs_size) + hda_codec_write(base, pc_beep_verbs_size, pc_beep_verbs); +} + +static void hda_pch_init(struct device *dev, u8 *base) +{ + u8 reg8; + u16 reg16; + u32 reg32; + + if (RCBA32(0x2030) & (1 << 31)) { + reg32 = pci_read_config32(dev, 0x120); + reg32 &= 0xf8ffff01; + reg32 |= (1 << 25); + reg32 |= RCBA32(0x2030) & 0xfe; + pci_write_config32(dev, 0x120, reg32); + } else + printk(BIOS_DEBUG, "HDA: V1CTL disabled.\n"); + + reg32 = pci_read_config32(dev, 0x114); + reg32 &= ~0xfe; + pci_write_config32(dev, 0x114, reg32); + + // Set VCi enable bit + if (pci_read_config32(dev, 0x120) & ((1 << 24) | + (1 << 25) | (1 << 26))) { + reg32 = pci_read_config32(dev, 0x120); + reg32 &= ~(1 << 31); + pci_write_config32(dev, 0x120, reg32); + } + + /* Additional programming steps */ + reg32 = pci_read_config32(dev, 0xc4); + reg32 |= (1 << 24); + pci_write_config32(dev, 0xc4, reg32); + + reg8 = pci_read_config8(dev, 0x40); // Audio Control + reg8 |= 1; // Select HDA mode + pci_write_config8(dev, 0x40, reg8); + + reg8 = pci_read_config8(dev, 0x4d); // Docking Status + reg8 &= ~(1 << 7); // Docking not supported + pci_write_config8(dev, 0x4d, reg8); + + reg16 = read32(base + 0x0012); + reg16 |= (1 << 0); + write32(base + 0x0012, reg16); + + /* disable Auto Voltage Detector */ + reg8 = pci_read_config8(dev, 0x42); + reg8 |= (1 << 2); + pci_write_config8(dev, 0x42, reg8); +} + +static void hda_init(struct device *dev) +{ + u8 *base; + struct resource *res; + u32 codec_mask; + + /* Find base address */ + res = find_resource(dev, PCI_BASE_ADDRESS_0); + if (!res) + return; + + base = res2mmio(res, 0, 0); + printk(BIOS_DEBUG, "HDA: base = %p\n", base); + + /* Set Bus Master */ + pci_or_config16(dev, PCI_COMMAND, PCI_COMMAND_MASTER); + + hda_pch_init(dev, base); + + codec_mask = hda_codec_detect(base); + + if (codec_mask) { + printk(BIOS_DEBUG, "HDA: codec_mask = %02x\n", codec_mask); + codecs_init(base, codec_mask); + } +} + +static void hda_enable(struct device *dev) +{ + u16 reg16; + u8 reg8; + + reg8 = pci_read_config8(dev, 0x43); + reg8 |= 0x6f; + pci_write_config8(dev, 0x43, reg8); + + if (!dev->enabled) { + /* Route I/O buffers to ADSP function */ + reg8 = pci_read_config8(dev, 0x42); + reg8 |= (1 << 7) | (1 << 6); + pci_write_config8(dev, 0x42, reg8); + + printk(BIOS_INFO, "HDA disabled, I/O buffers routed to ADSP\n"); + + /* Ensure memory, io, and bus master are all disabled */ + reg16 = pci_read_config16(dev, PCI_COMMAND); + reg16 &= ~(PCI_COMMAND_MASTER | + PCI_COMMAND_MEMORY | PCI_COMMAND_IO); + pci_write_config16(dev, PCI_COMMAND, reg16); + + /* Disable this device */ + pch_disable_devfn(dev); + } +} + +static struct device_operations hda_ops = { + .read_resources = &pci_dev_read_resources, + .set_resources = &pci_dev_set_resources, + .enable_resources = &pci_dev_enable_resources, + .init = &hda_init, + .enable = &hda_enable, + .ops_pci = &pci_dev_ops_pci, +}; + +static const unsigned short pci_device_ids[] = { + 0x9c20, /* LynxPoint-LP */ + 0x9ca0, /* WildcatPoint */ + 0 +}; + +static const struct pci_driver pch_hda __pci_driver = { + .ops = &hda_ops, + .vendor = PCI_VENDOR_ID_INTEL, + .devices = pci_device_ids, +}; diff --git a/src/soc/intel/broadwell/pch/iobp.c b/src/soc/intel/broadwell/pch/iobp.c new file mode 100644 index 0000000000..deb4156198 --- /dev/null +++ b/src/soc/intel/broadwell/pch/iobp.c @@ -0,0 +1,139 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <console/console.h> +#include <delay.h> +#include <soc/iobp.h> +#include <soc/rcba.h> + +#define IOBP_RETRY 1000 + +static inline int iobp_poll(void) +{ + unsigned int try; + + for (try = IOBP_RETRY; try > 0; try--) { + u16 status = RCBA16(IOBPS); + if ((status & IOBPS_READY) == 0) + return 1; + udelay(10); + } + + printk(BIOS_ERR, "IOBP: timeout waiting for transaction to complete\n"); + return 0; +} + +u32 pch_iobp_read(u32 address) +{ + u16 status; + + if (!iobp_poll()) + return 0; + + /* Set the address */ + RCBA32(IOBPIRI) = address; + + /* READ OPCODE */ + status = RCBA16(IOBPS); + status &= ~IOBPS_MASK; + status |= IOBPS_READ; + RCBA16(IOBPS) = status; + + /* Undocumented magic */ + RCBA16(IOBPU) = IOBPU_MAGIC; + + /* Set ready bit */ + status = RCBA16(IOBPS); + status |= IOBPS_READY; + RCBA16(IOBPS) = status; + + if (!iobp_poll()) + return 0; + + /* Check for successful transaction */ + status = RCBA16(IOBPS); + if (status & IOBPS_TX_MASK) { + printk(BIOS_ERR, "IOBP: read 0x%08x failed\n", address); + return 0; + } + + /* Read IOBP data */ + return RCBA32(IOBPD); +} + +void pch_iobp_write(u32 address, u32 data) +{ + u16 status; + + if (!iobp_poll()) + return; + + /* Set the address */ + RCBA32(IOBPIRI) = address; + + /* WRITE OPCODE */ + status = RCBA16(IOBPS); + status &= ~IOBPS_MASK; + status |= IOBPS_WRITE; + RCBA16(IOBPS) = status; + + RCBA32(IOBPD) = data; + + /* Undocumented magic */ + RCBA16(IOBPU) = IOBPU_MAGIC; + + /* Set ready bit */ + status = RCBA16(IOBPS); + status |= IOBPS_READY; + RCBA16(IOBPS) = status; + + if (!iobp_poll()) + return; + + /* Check for successful transaction */ + status = RCBA16(IOBPS); + if (status & IOBPS_TX_MASK) { + printk(BIOS_ERR, "IOBP: write 0x%08x failed\n", address); + return; + } + + printk(BIOS_SPEW, "IOBP: set 0x%08x to 0x%08x\n", address, data); +} + +void pch_iobp_update(u32 address, u32 andvalue, u32 orvalue) +{ + u32 data = pch_iobp_read(address); + + /* Update the data */ + data &= andvalue; + data |= orvalue; + + pch_iobp_write(address, data); +} + +void pch_iobp_exec(u32 addr, u16 op_code, u8 route_id, u32 *data, u8 *resp) +{ + if (!data || !resp) + return; + + *resp = -1; + if (!iobp_poll()) + return; + + /* RCBA2330[31:0] = Address */ + RCBA32(IOBPIRI) = addr; + /* RCBA2338[15:8] = opcode */ + RCBA16(IOBPS) = (RCBA16(IOBPS) & 0x00ff) | op_code; + /* RCBA233A[15:8] = 0xf0 RCBA233A[7:0] = Route ID */ + RCBA16(IOBPU) = IOBPU_MAGIC | route_id; + + if (op_code == IOBP_PCICFG_WRITE) + RCBA32(IOBPD) = *data; + /* Set RCBA2338[0] to trigger IOBP transaction*/ + RCBA16(IOBPS) = RCBA16(IOBPS) | 0x1; + + if (!iobp_poll()) + return; + + *resp = (RCBA16(IOBPS) & IOBPS_TX_MASK) >> 1; + *data = RCBA32(IOBPD); +} diff --git a/src/soc/intel/broadwell/pch/lpc.c b/src/soc/intel/broadwell/pch/lpc.c new file mode 100644 index 0000000000..2111913a0e --- /dev/null +++ b/src/soc/intel/broadwell/pch/lpc.c @@ -0,0 +1,677 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <console/console.h> +#include <device/device.h> +#include <device/pci.h> +#include <device/pci_ids.h> +#include <option.h> +#include <pc80/isa-dma.h> +#include <pc80/i8259.h> +#include <device/pci_ops.h> +#include <arch/ioapic.h> +#include <acpi/acpi.h> +#include <acpi/acpi_gnvs.h> +#include <cpu/x86/smm.h> +#include <cbmem.h> +#include <string.h> +#include <soc/gpio.h> +#include <soc/iobp.h> +#include <soc/iomap.h> +#include <soc/lpc.h> +#include <soc/nvs.h> +#include <soc/pch.h> +#include <soc/pci_devs.h> +#include <soc/pm.h> +#include <soc/ramstage.h> +#include <soc/rcba.h> +#include <soc/intel/broadwell/pch/chip.h> +#include <acpi/acpigen.h> +#include <southbridge/intel/common/rtc.h> + +static void pch_enable_ioapic(struct device *dev) +{ + u32 reg32; + + /* Assign unique bus/dev/fn for I/O APIC */ + pci_write_config16(dev, LPC_IBDF, + PCH_IOAPIC_PCI_BUS << 8 | PCH_IOAPIC_PCI_SLOT << 3); + + set_ioapic_id(VIO_APIC_VADDR, 0x02); + + /* affirm full set of redirection table entries ("write once") */ + reg32 = io_apic_read(VIO_APIC_VADDR, 0x01); + + /* PCH-LP has 39 redirection entries */ + reg32 &= ~0x00ff0000; + reg32 |= 0x00270000; + + io_apic_write(VIO_APIC_VADDR, 0x01, reg32); + + /* + * Select Boot Configuration register (0x03) and + * use Processor System Bus (0x01) to deliver interrupts. + */ + io_apic_write(VIO_APIC_VADDR, 0x03, 0x01); +} + +static void enable_hpet(struct device *dev) +{ + size_t i; + + /* Assign unique bus/dev/fn for each HPET */ + for (i = 0; i < 8; ++i) + pci_write_config16(dev, LPC_HnBDF(i), + PCH_HPET_PCI_BUS << 8 | PCH_HPET_PCI_SLOT << 3 | i); +} + +/* PIRQ[n]_ROUT[3:0] - PIRQ Routing Control + * 0x00 - 0000 = Reserved + * 0x01 - 0001 = Reserved + * 0x02 - 0010 = Reserved + * 0x03 - 0011 = IRQ3 + * 0x04 - 0100 = IRQ4 + * 0x05 - 0101 = IRQ5 + * 0x06 - 0110 = IRQ6 + * 0x07 - 0111 = IRQ7 + * 0x08 - 1000 = Reserved + * 0x09 - 1001 = IRQ9 + * 0x0A - 1010 = IRQ10 + * 0x0B - 1011 = IRQ11 + * 0x0C - 1100 = IRQ12 + * 0x0D - 1101 = Reserved + * 0x0E - 1110 = IRQ14 + * 0x0F - 1111 = IRQ15 + * PIRQ[n]_ROUT[7] - PIRQ Routing Control + * 0x80 - The PIRQ is not routed. + */ + +static void pch_pirq_init(struct device *dev) +{ + struct device *irq_dev; + + const uint8_t pirq = 0x80; + + pci_write_config8(dev, PIRQA_ROUT, pirq); + pci_write_config8(dev, PIRQB_ROUT, pirq); + pci_write_config8(dev, PIRQC_ROUT, pirq); + pci_write_config8(dev, PIRQD_ROUT, pirq); + + pci_write_config8(dev, PIRQE_ROUT, pirq); + pci_write_config8(dev, PIRQF_ROUT, pirq); + pci_write_config8(dev, PIRQG_ROUT, pirq); + pci_write_config8(dev, PIRQH_ROUT, pirq); + + for (irq_dev = all_devices; irq_dev; irq_dev = irq_dev->next) { + u8 int_pin = 0, int_line = 0; + + if (!irq_dev->enabled || irq_dev->path.type != DEVICE_PATH_PCI) + continue; + + int_pin = pci_read_config8(irq_dev, PCI_INTERRUPT_PIN); + + switch (int_pin) { + case 1: /* INTA# */ + case 2: /* INTB# */ + case 3: /* INTC# */ + case 4: /* INTD# */ + int_line = pirq; + break; + } + + if (!int_line) + continue; + + pci_write_config8(irq_dev, PCI_INTERRUPT_LINE, int_line); + } +} + +static void pch_power_options(struct device *dev) +{ + u16 reg16; + const char *state; + /* Get the chip configuration */ + const struct soc_intel_broadwell_pch_config *config = config_of(dev); + int pwr_on = CONFIG_MAINBOARD_POWER_FAILURE_STATE; + + /* Which state do we want to goto after g3 (power restored)? + * 0 == S0 Full On + * 1 == S5 Soft Off + * + * If the option is not existent (Laptops), use Kconfig setting. + */ + get_option(&pwr_on, "power_on_after_fail"); + + reg16 = pci_read_config16(dev, GEN_PMCON_3); + reg16 &= 0xfffe; + switch (pwr_on) { + case MAINBOARD_POWER_OFF: + reg16 |= 1; + state = "off"; + break; + case MAINBOARD_POWER_ON: + reg16 &= ~1; + state = "on"; + break; + case MAINBOARD_POWER_KEEP: + reg16 &= ~1; + state = "state keep"; + break; + default: + state = "undefined"; + } + pci_write_config16(dev, GEN_PMCON_3, reg16); + printk(BIOS_INFO, "Set power %s after power failure.\n", state); + + /* GPE setup based on device tree configuration */ + enable_all_gpe(config->gpe0_en_1, config->gpe0_en_2, + config->gpe0_en_3, config->gpe0_en_4); + + /* SMI setup based on device tree configuration */ + enable_alt_smi(config->alt_gp_smi_en); +} + +static void pch_misc_init(struct device *dev) +{ + u8 reg8; + u16 reg16; + u32 reg32; + + reg16 = pci_read_config16(dev, GEN_PMCON_3); + + reg16 &= ~(3 << 4); /* SLP_S4# Assertion Stretch 4s */ + reg16 |= (1 << 3); /* SLP_S4# Assertion Stretch Enable */ + + reg16 &= ~(1 << 10); + reg16 |= (1 << 11); /* SLP_S3# Min Assertion Width 50ms */ + + reg16 |= (1 << 12); /* Disable SLP stretch after SUS well */ + + pci_write_config16(dev, GEN_PMCON_3, reg16); + + /* Prepare sleep mode */ + reg32 = inl(ACPI_BASE_ADDRESS + PM1_CNT); + reg32 &= ~SLP_TYP; + reg32 |= SCI_EN; + outl(reg32, ACPI_BASE_ADDRESS + PM1_CNT); + + /* Set up NMI on errors */ + reg8 = inb(0x61); + reg8 &= ~0xf0; /* Higher nibble must be 0 */ + reg8 |= (1 << 2); /* PCI SERR# disable for now */ + outb(reg8, 0x61); + + /* Disable NMI sources */ + reg8 = inb(0x70); + reg8 |= (1 << 7); /* Can't mask NMI from PCI-E and NMI_NOW */ + outb(reg8, 0x70); + + /* Indicate DRAM init done for MRC */ + pci_or_config8(dev, GEN_PMCON_2, 1 << 7); + + /* Enable BIOS updates outside of SMM */ + pci_and_config8(dev, BIOS_CNTL, ~(1 << 5)); + + /* Clear status bits to prevent unexpected wake */ + RCBA32_OR(0x3310, 0x2f); + + RCBA32_AND_OR(0x3f02, ~0xf, 0); + + /* Enable PCIe Releaxed Order */ + RCBA32_OR(0x2314, (1 << 31) | (1 << 7)), + RCBA32_OR(0x1114, (1 << 15) | (1 << 14)), + + /* Setup SERIRQ, enable continuous mode */ + reg8 = pci_read_config8(dev, SERIRQ_CNTL); + reg8 |= 1 << 7; + + if (CONFIG(SERIRQ_CONTINUOUS_MODE)) + reg8 |= 1 << 6; + + pci_write_config8(dev, SERIRQ_CNTL, reg8); +} + +/* Magic register settings for power management */ +static void pch_pm_init_magic(struct device *dev) +{ + pci_write_config8(dev, 0xa9, 0x46); + + RCBA32_AND_OR(0x232c, ~1, 0); + + RCBA32_OR(0x1100, 0x0000c13f); + + RCBA32_AND_OR(0x2320, ~0x60, 0x10); + + RCBA32(0x3314) = 0x00012fff; + + RCBA32_AND_OR(0x3318, ~0x000f0330, 0x0dcf0400); + + RCBA32(0x3324) = 0x04000000; + RCBA32(0x3368) = 0x00041400; + RCBA32(0x3388) = 0x3f8ddbff; + RCBA32(0x33ac) = 0x00007001; + RCBA32(0x33b0) = 0x00181900; + RCBA32(0x33c0) = 0x00060A00; + RCBA32(0x33d0) = 0x06200840; + RCBA32(0x3a28) = 0x01010101; + RCBA32(0x3a2c) = 0x040c0404; + RCBA32(0x3a9c) = 0x9000000a; + RCBA32(0x2b1c) = 0x03808033; + RCBA32(0x2b34) = 0x80000009; + RCBA32(0x3348) = 0x022ddfff; + RCBA32(0x334c) = 0x00000001; + RCBA32(0x3358) = 0x0001c000; + RCBA32(0x3380) = 0x3f8ddbff; + RCBA32(0x3384) = 0x0001c7e1; + RCBA32(0x338c) = 0x0001c7e1; + RCBA32(0x3398) = 0x0001c000; + RCBA32(0x33a8) = 0x00181900; + RCBA32(0x33dc) = 0x00080000; + RCBA32(0x33e0) = 0x00000001; + RCBA32(0x3a20) = 0x0000040c; + RCBA32(0x3a24) = 0x01010101; + RCBA32(0x3a30) = 0x01010101; + + pci_update_config32(dev, 0xac, ~0x00200000, 0); + + RCBA32_OR(0x0410, 0x00000003); + RCBA32_OR(0x2618, 0x08000000); + RCBA32_OR(0x2300, 0x00000002); + RCBA32_OR(0x2600, 0x00000008); + + RCBA32(0x33b4) = 0x00007001; + RCBA32(0x3350) = 0x022ddfff; + RCBA32(0x3354) = 0x00000001; + + /* Power Optimizer */ + RCBA32_OR(0x33d4, 0x08000000); + RCBA32_OR(0x33c8, 0x00000080); + + RCBA32(0x2b10) = 0x0000883c; + RCBA32(0x2b14) = 0x1e0a4616; + RCBA32(0x2b24) = 0x40000005; + RCBA32(0x2b20) = 0x0005db01; + RCBA32(0x3a80) = 0x05145005; + RCBA32(0x3a84) = 0x00001005; + + RCBA32_OR(0x33d4, 0x2fff2fb1); + RCBA32_OR(0x33c8, 0x00008000); +} + +static void pch_enable_mphy(void) +{ + u32 gpio71_native = gpio_is_native(71); + u32 data_and = 0xffffffff; + u32 data_or = (1 << 14) | (1 << 13) | (1 << 12); + + if (gpio71_native) { + data_or |= (1 << 0); + if (pch_is_wpt()) { + data_and &= ~((1 << 7) | (1 << 6) | (1 << 3)); + data_or |= (1 << 5) | (1 << 4); + + if (pch_is_wpt_ulx()) { + /* Check if SATA and USB3 MPHY are enabled */ + u32 strap19 = pch_read_soft_strap(19); + strap19 &= ((1 << 31) | (1 << 30)); + strap19 >>= 30; + if (strap19 == 3) { + data_or |= (1 << 3); + printk(BIOS_DEBUG, "Enable ULX MPHY PG " + "control in single domain\n"); + } else if (strap19 == 0) { + printk(BIOS_DEBUG, "Enable ULX MPHY PG " + "control in split domains\n"); + } else { + printk(BIOS_DEBUG, "Invalid PCH Soft " + "Strap 19 configuration\n"); + } + } else { + data_or |= (1 << 3); + } + } + } + + pch_iobp_update(0xCF000000, data_and, data_or); +} + +static void pch_init_deep_sx(struct device *dev) +{ + const struct soc_intel_broadwell_pch_config *config = config_of(dev); + + if (config->deep_sx_enable_ac) { + RCBA32_OR(DEEP_S3_POL, DEEP_S3_EN_AC); + RCBA32_OR(DEEP_S5_POL, DEEP_S5_EN_AC); + } + + if (config->deep_sx_enable_dc) { + RCBA32_OR(DEEP_S3_POL, DEEP_S3_EN_DC); + RCBA32_OR(DEEP_S5_POL, DEEP_S5_EN_DC); + } + + if (config->deep_sx_enable_ac || config->deep_sx_enable_dc) + RCBA32_OR(DEEP_SX_CONFIG, + DEEP_SX_WAKE_PIN_EN | DEEP_SX_GP27_PIN_EN); +} + +/* Power Management init */ +static void pch_pm_init(struct device *dev) +{ + printk(BIOS_DEBUG, "PCH PM init\n"); + + pch_init_deep_sx(dev); + + pch_enable_mphy(); + + pch_pm_init_magic(dev); + + if (pch_is_wpt()) { + RCBA32_OR(0x33e0, (1 << 4) | (1 << 1)); + RCBA32_OR(0x2b1c, (1 << 22) | (1 << 14) | (1 << 13)); + RCBA32(0x33e4) = 0x16bf0002; + RCBA32_OR(0x33e4, 0x1); + } + + pch_iobp_update(0xCA000000, ~0UL, 0x00000009); + + /* Set RCBA 0x2b1c[29]=1 if DSP disabled */ + if (RCBA32(FD) & PCH_DISABLE_ADSPD) + RCBA32_OR(0x2b1c, (1 << 29)); + +} + +static void pch_cg_init(struct device *dev) +{ + u32 reg32; + u16 reg16; + struct device *igd_dev = pcidev_path_on_root(SA_DEVFN_IGD); + + /* DMI */ + RCBA32_OR(0x2234, 0xf); + + reg16 = pci_read_config16(dev, GEN_PMCON_1); + reg16 &= ~(1 << 10); /* Disable BIOS_PCI_EXP_EN for native PME */ + if (pch_is_wpt()) + reg16 &= ~(1 << 11); + else + reg16 |= (1 << 11); + reg16 |= (1 << 5) | (1 << 6) | (1 << 7) | (1 << 12); + reg16 |= (1 << 2); // PCI CLKRUN# Enable + pci_write_config16(dev, GEN_PMCON_1, reg16); + + /* + * RCBA + 0x2614[27:25,14:13,10,8] = 101,11,1,1 + * RCBA + 0x2614[23:16] = 0x20 + * RCBA + 0x2614[30:28] = 0x0 + * RCBA + 0x2614[26] = 1 (IF 0:2.0@0x08 >= 0x0b) + */ + RCBA32_AND_OR(0x2614, ~0x64ff0000, 0x0a206500); + + /* Check for 0:2.0@0x08 >= 0x0b */ + if (pch_is_wpt() || pci_read_config8(igd_dev, 0x8) >= 0x0b) + RCBA32_OR(0x2614, (1 << 26)); + + RCBA32_OR(0x900, 0x0000031f); + + reg32 = RCBA32(CG); + if (RCBA32(0x3454) & (1 << 4)) + reg32 &= ~(1 << 29); // LPC Dynamic + else + reg32 |= (1 << 29); // LPC Dynamic + reg32 |= (1 << 31); // LP LPC + reg32 |= (1 << 30); // LP BLA + if (RCBA32(0x3454) & (1 << 4)) + reg32 &= ~(1 << 29); + else + reg32 |= (1 << 29); + reg32 |= (1 << 28); // GPIO Dynamic + reg32 |= (1 << 27); // HPET Dynamic + reg32 |= (1 << 26); // Generic Platform Event Clock + if (RCBA32(BUC) & PCH_DISABLE_GBE) + reg32 |= (1 << 23); // GbE Static + if (RCBA32(FD) & PCH_DISABLE_HD_AUDIO) + reg32 |= (1 << 21); // HDA Static + reg32 |= (1 << 22); // HDA Dynamic + RCBA32(CG) = reg32; + + /* PCH-LP LPC */ + if (pch_is_wpt()) + RCBA32_AND_OR(0x3434, ~0x1f, 0x17); + else + RCBA32_OR(0x3434, 0x7); + + /* SPI */ + RCBA32_OR(0x38c0, 0x3c07); + + pch_iobp_update(0xCE00C000, ~1UL, 0x00000000); +} + +static void pch_set_acpi_mode(void) +{ + if (!acpi_is_wakeup_s3()) { + apm_control(APM_CNT_ACPI_DISABLE); + } +} + +static void lpc_init(struct device *dev) +{ + /* Legacy initialization */ + isa_dma_init(); + sb_rtc_init(); + pch_misc_init(dev); + + /* Interrupt configuration */ + pch_enable_ioapic(dev); + pch_pirq_init(dev); + setup_i8259(); + i8259_configure_irq_trigger(9, 1); + enable_hpet(dev); + + /* Initialize power management */ + pch_power_options(dev); + pch_pm_init(dev); + pch_cg_init(dev); + + pch_set_acpi_mode(); +} + +static void pch_lpc_add_mmio_resources(struct device *dev) +{ + u32 reg; + struct resource *res; + const u32 default_decode_base = IO_APIC_ADDR; + + /* + * Just report all resources from IO-APIC base to 4GiB. Don't mark + * them reserved as that may upset the OS if this range is marked + * as reserved in the e820. + */ + res = new_resource(dev, OIC); + res->base = default_decode_base; + res->size = 0 - default_decode_base; + res->flags = IORESOURCE_MEM | IORESOURCE_ASSIGNED | IORESOURCE_FIXED; + + /* RCBA */ + if (default_decode_base > RCBA_BASE_ADDRESS) { + res = new_resource(dev, RCBA); + res->base = RCBA_BASE_ADDRESS; + res->size = 16 * 1024; + res->flags = IORESOURCE_MEM | IORESOURCE_ASSIGNED | + IORESOURCE_FIXED | IORESOURCE_RESERVE; + } + + /* Check LPC Memory Decode register. */ + reg = pci_read_config32(dev, LGMR); + if (reg & 1) { + reg &= ~0xffff; + if (reg < default_decode_base) { + res = new_resource(dev, LGMR); + res->base = reg; + res->size = 16 * 1024; + res->flags = IORESOURCE_MEM | IORESOURCE_ASSIGNED | + IORESOURCE_FIXED | IORESOURCE_RESERVE; + } + } +} + +/* Default IO range claimed by the LPC device. The upper bound is exclusive. */ +#define LPC_DEFAULT_IO_RANGE_LOWER 0 +#define LPC_DEFAULT_IO_RANGE_UPPER 0x1000 + +static inline int pch_io_range_in_default(int base, int size) +{ + /* Does it start above the range? */ + if (base >= LPC_DEFAULT_IO_RANGE_UPPER) + return 0; + + /* Is it entirely contained? */ + if (base >= LPC_DEFAULT_IO_RANGE_LOWER && + (base + size) < LPC_DEFAULT_IO_RANGE_UPPER) + return 1; + + /* This will return not in range for partial overlaps. */ + return 0; +} + +/* + * Note: this function assumes there is no overlap with the default LPC device's + * claimed range: LPC_DEFAULT_IO_RANGE_LOWER -> LPC_DEFAULT_IO_RANGE_UPPER. + */ +static void pch_lpc_add_io_resource(struct device *dev, u16 base, u16 size, + int index) +{ + struct resource *res; + + if (pch_io_range_in_default(base, size)) + return; + + res = new_resource(dev, index); + res->base = base; + res->size = size; + res->flags = IORESOURCE_IO | IORESOURCE_ASSIGNED | IORESOURCE_FIXED; +} + +static void pch_lpc_add_gen_io_resources(struct device *dev, int reg_value, + int index) +{ + /* + * Check if the register is enabled. If so and the base exceeds the + * device's default claim range add the resource. + */ + if (reg_value & 1) { + u16 base = reg_value & 0xfffc; + u16 size = (0x3 | ((reg_value >> 16) & 0xfc)) + 1; + pch_lpc_add_io_resource(dev, base, size, index); + } +} + +static void pch_lpc_add_io_resources(struct device *dev) +{ + struct resource *res; + const struct soc_intel_broadwell_pch_config *config = config_of(dev); + + /* Add the default claimed IO range for the LPC device. */ + res = new_resource(dev, 0); + res->base = LPC_DEFAULT_IO_RANGE_LOWER; + res->size = LPC_DEFAULT_IO_RANGE_UPPER - LPC_DEFAULT_IO_RANGE_LOWER; + res->flags = IORESOURCE_IO | IORESOURCE_ASSIGNED | IORESOURCE_FIXED; + + /* GPIOBASE */ + pch_lpc_add_io_resource(dev, GPIO_BASE_ADDRESS, + GPIO_BASE_SIZE, GPIO_BASE); + + /* PMBASE */ + pch_lpc_add_io_resource(dev, ACPI_BASE_ADDRESS, ACPI_BASE_SIZE, PMBASE); + + /* LPC Generic IO Decode range. */ + pch_lpc_add_gen_io_resources(dev, config->gen1_dec, LPC_GEN1_DEC); + pch_lpc_add_gen_io_resources(dev, config->gen2_dec, LPC_GEN2_DEC); + pch_lpc_add_gen_io_resources(dev, config->gen3_dec, LPC_GEN3_DEC); + pch_lpc_add_gen_io_resources(dev, config->gen4_dec, LPC_GEN4_DEC); +} + +static void pch_lpc_read_resources(struct device *dev) +{ + struct global_nvs *gnvs; + + /* Get the normal PCI resources of this device. */ + pci_dev_read_resources(dev); + + /* Add non-standard MMIO resources. */ + pch_lpc_add_mmio_resources(dev); + + /* Add IO resources. */ + pch_lpc_add_io_resources(dev); + + /* Allocate ACPI NVS in CBMEM */ + gnvs = cbmem_add(CBMEM_ID_ACPI_GNVS, sizeof(struct global_nvs)); + if (!acpi_is_wakeup_s3() && gnvs) + memset(gnvs, 0, sizeof(struct global_nvs)); +} + +static void southcluster_inject_dsdt(const struct device *device) +{ + struct global_nvs *gnvs; + + gnvs = cbmem_find(CBMEM_ID_ACPI_GNVS); + if (!gnvs) { + gnvs = cbmem_add(CBMEM_ID_ACPI_GNVS, sizeof(*gnvs)); + if (gnvs) + memset(gnvs, 0, sizeof(*gnvs)); + } + + if (gnvs) { + acpi_create_gnvs(gnvs); + /* And tell SMI about it */ + apm_control(APM_CNT_GNVS_UPDATE); + + /* Add it to DSDT. */ + acpigen_write_scope("\\"); + acpigen_write_name_dword("NVSA", (u32) gnvs); + acpigen_pop_len(); + } +} + +static unsigned long broadwell_write_acpi_tables(const struct device *device, + unsigned long current, + struct acpi_rsdp *rsdp) +{ + if (CONFIG(INTEL_PCH_UART_CONSOLE)) + current = acpi_write_dbg2_pci_uart(rsdp, current, + (CONFIG_INTEL_PCH_UART_CONSOLE_NUMBER == 1) ? + PCH_DEV_UART1 : PCH_DEV_UART0, + ACPI_ACCESS_SIZE_BYTE_ACCESS); + return acpi_write_hpet(device, current, rsdp); +} + +static struct device_operations device_ops = { + .read_resources = &pch_lpc_read_resources, + .set_resources = &pci_dev_set_resources, + .enable_resources = &pci_dev_enable_resources, + .acpi_inject_dsdt = southcluster_inject_dsdt, + .write_acpi_tables = broadwell_write_acpi_tables, + .init = &lpc_init, + .scan_bus = &scan_static_bus, + .ops_pci = &pci_dev_ops_pci, +}; + +static const unsigned short pci_device_ids[] = { + PCH_LPT_LP_SAMPLE, + PCH_LPT_LP_PREMIUM, + PCH_LPT_LP_MAINSTREAM, + PCH_LPT_LP_VALUE, + PCH_WPT_HSW_U_SAMPLE, + PCH_WPT_BDW_U_SAMPLE, + PCH_WPT_BDW_U_PREMIUM, + PCH_WPT_BDW_U_BASE, + PCH_WPT_BDW_Y_SAMPLE, + PCH_WPT_BDW_Y_PREMIUM, + PCH_WPT_BDW_Y_BASE, + PCH_WPT_BDW_H, + 0 +}; + +static const struct pci_driver pch_lpc __pci_driver = { + .ops = &device_ops, + .vendor = PCI_VENDOR_ID_INTEL, + .devices = pci_device_ids, +}; diff --git a/src/soc/intel/broadwell/pch/me.c b/src/soc/intel/broadwell/pch/me.c new file mode 100644 index 0000000000..40a81d8810 --- /dev/null +++ b/src/soc/intel/broadwell/pch/me.c @@ -0,0 +1,1057 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +/* + * This is a ramstage driver for the Intel Management Engine found in the + * southbridge. It handles the required boot-time messages over the + * MMIO-based Management Engine Interface to tell the ME that the BIOS is + * finished with POST. Additional messages are defined for debug but are + * not used unless the console loglevel is high enough. + */ + +#include <acpi/acpi.h> +#include <device/mmio.h> +#include <device/pci_ops.h> +#include <console/console.h> +#include <device/device.h> +#include <device/pci.h> +#include <device/pci_ids.h> +#include <device/pci_def.h> +#include <stdlib.h> +#include <string.h> +#include <delay.h> +#include <elog.h> +#include <soc/me.h> +#include <soc/lpc.h> +#include <soc/pch.h> +#include <soc/pci_devs.h> +#include <soc/ramstage.h> +#include <soc/rcba.h> +#include <soc/intel/broadwell/pch/chip.h> + +#if CONFIG(CHROMEOS) +#include <vendorcode/google/chromeos/chromeos.h> +#include <vendorcode/google/chromeos/gnvs.h> +#endif + +/* Path that the BIOS should take based on ME state */ +static const char *me_bios_path_values[] = { + [ME_NORMAL_BIOS_PATH] = "Normal", + [ME_S3WAKE_BIOS_PATH] = "S3 Wake", + [ME_ERROR_BIOS_PATH] = "Error", + [ME_RECOVERY_BIOS_PATH] = "Recovery", + [ME_DISABLE_BIOS_PATH] = "Disable", + [ME_FIRMWARE_UPDATE_BIOS_PATH] = "Firmware Update", +}; + +/* MMIO base address for MEI interface */ +static u8 *mei_base_address; + +static void mei_dump(void *ptr, int dword, int offset, const char *type) +{ + struct mei_csr *csr; + + if (!CONFIG(DEBUG_INTEL_ME)) + return; + + printk(BIOS_SPEW, "%-9s[%02x] : ", type, offset); + + switch (offset) { + case MEI_H_CSR: + case MEI_ME_CSR_HA: + csr = ptr; + if (!csr) { + printk(BIOS_SPEW, "ERROR: 0x%08x\n", dword); + break; + } + printk(BIOS_SPEW, "cbd=%u cbrp=%02u cbwp=%02u ready=%u " + "reset=%u ig=%u is=%u ie=%u\n", csr->buffer_depth, + csr->buffer_read_ptr, csr->buffer_write_ptr, + csr->ready, csr->reset, csr->interrupt_generate, + csr->interrupt_status, csr->interrupt_enable); + break; + case MEI_ME_CB_RW: + case MEI_H_CB_WW: + printk(BIOS_SPEW, "CB: 0x%08x\n", dword); + break; + default: + printk(BIOS_SPEW, "0x%08x\n", offset); + break; + } +} + +/* + * ME/MEI access helpers using memcpy to avoid aliasing. + */ + +static inline void mei_read_dword_ptr(void *ptr, int offset) +{ + u32 dword = read32(mei_base_address + offset); + memcpy(ptr, &dword, sizeof(dword)); + mei_dump(ptr, dword, offset, "READ"); +} + +static inline void mei_write_dword_ptr(void *ptr, int offset) +{ + u32 dword = 0; + memcpy(&dword, ptr, sizeof(dword)); + write32(mei_base_address + offset, dword); + mei_dump(ptr, dword, offset, "WRITE"); +} + +static inline void pci_read_dword_ptr(struct device *dev, void *ptr, int offset) +{ + u32 dword = pci_read_config32(dev, offset); + memcpy(ptr, &dword, sizeof(dword)); + mei_dump(ptr, dword, offset, "PCI READ"); +} + +static inline void read_host_csr(struct mei_csr *csr) +{ + mei_read_dword_ptr(csr, MEI_H_CSR); +} + +static inline void write_host_csr(struct mei_csr *csr) +{ + mei_write_dword_ptr(csr, MEI_H_CSR); +} + +static inline void read_me_csr(struct mei_csr *csr) +{ + mei_read_dword_ptr(csr, MEI_ME_CSR_HA); +} + +static inline void write_cb(u32 dword) +{ + write32(mei_base_address + MEI_H_CB_WW, dword); + mei_dump(NULL, dword, MEI_H_CB_WW, "WRITE"); +} + +static inline u32 read_cb(void) +{ + u32 dword = read32(mei_base_address + MEI_ME_CB_RW); + mei_dump(NULL, dword, MEI_ME_CB_RW, "READ"); + return dword; +} + +/* Wait for ME ready bit to be asserted */ +static int mei_wait_for_me_ready(void) +{ + struct mei_csr me; + unsigned int try = ME_RETRY; + + while (try--) { + read_me_csr(&me); + if (me.ready) + return 0; + udelay(ME_DELAY); + } + + printk(BIOS_ERR, "ME: failed to become ready\n"); + return -1; +} + +static void mei_reset(void) +{ + struct mei_csr host; + + if (mei_wait_for_me_ready() < 0) + return; + + /* Reset host and ME circular buffers for next message */ + read_host_csr(&host); + host.reset = 1; + host.interrupt_generate = 1; + write_host_csr(&host); + + if (mei_wait_for_me_ready() < 0) + return; + + /* Re-init and indicate host is ready */ + read_host_csr(&host); + host.interrupt_generate = 1; + host.ready = 1; + host.reset = 0; + write_host_csr(&host); +} + +static int mei_send_packet(struct mei_header *mei, void *req_data) +{ + struct mei_csr host; + unsigned int ndata, n; + u32 *data; + + /* Number of dwords to write */ + ndata = mei->length >> 2; + + /* Pad non-dword aligned request message length */ + if (mei->length & 3) + ndata++; + if (!ndata) { + printk(BIOS_DEBUG, "ME: request has no data\n"); + return -1; + } + ndata++; /* Add MEI header */ + + /* + * Make sure there is still room left in the circular buffer. + * Reset the buffer pointers if the requested message will not fit. + */ + read_host_csr(&host); + if ((host.buffer_depth - host.buffer_write_ptr) < ndata) { + printk(BIOS_ERR, "ME: circular buffer full, resetting...\n"); + mei_reset(); + read_host_csr(&host); + } + + /* Ensure the requested length will fit in the circular buffer. */ + if ((host.buffer_depth - host.buffer_write_ptr) < ndata) { + printk(BIOS_ERR, "ME: message (%u) too large for buffer (%u)\n", + ndata + 2, host.buffer_depth); + return -1; + } + + /* Write MEI header */ + mei_write_dword_ptr(mei, MEI_H_CB_WW); + ndata--; + + /* Write message data */ + data = req_data; + for (n = 0; n < ndata; ++n) + write_cb(*data++); + + /* Generate interrupt to the ME */ + read_host_csr(&host); + host.interrupt_generate = 1; + write_host_csr(&host); + + /* Make sure ME is ready after sending request data */ + return mei_wait_for_me_ready(); +} + +static int mei_send_data(u8 me_address, u8 host_address, + void *req_data, int req_bytes) +{ + struct mei_header header = { + .client_address = me_address, + .host_address = host_address, + }; + struct mei_csr host; + int current = 0; + u8 *req_ptr = req_data; + + while (!header.is_complete) { + int remain = req_bytes - current; + int buf_len; + + read_host_csr(&host); + buf_len = host.buffer_depth - host.buffer_write_ptr; + + if (buf_len > remain) { + /* Send all remaining data as final message */ + header.length = req_bytes - current; + header.is_complete = 1; + } else { + /* Send as much data as the buffer can hold */ + header.length = buf_len; + } + + mei_send_packet(&header, req_ptr); + + req_ptr += header.length; + current += header.length; + } + + return 0; +} + +static int mei_send_header(u8 me_address, u8 host_address, + void *header, int header_len, int complete) +{ + struct mei_header mei = { + .client_address = me_address, + .host_address = host_address, + .length = header_len, + .is_complete = complete, + }; + return mei_send_packet(&mei, header); +} + +static int mei_recv_msg(void *header, int header_bytes, + void *rsp_data, int rsp_bytes) +{ + struct mei_header mei_rsp; + struct mei_csr me, host; + unsigned int ndata, n; + unsigned int expected; + u32 *data; + + /* Total number of dwords to read from circular buffer */ + expected = (rsp_bytes + sizeof(mei_rsp) + header_bytes) >> 2; + if (rsp_bytes & 3) + expected++; + + if (mei_wait_for_me_ready() < 0) + return -1; + + /* + * The interrupt status bit does not appear to indicate that the + * message has actually been received. Instead we wait until the + * expected number of dwords are present in the circular buffer. + */ + for (n = ME_RETRY; n; --n) { + read_me_csr(&me); + if ((me.buffer_write_ptr - me.buffer_read_ptr) >= expected) + break; + udelay(ME_DELAY); + } + if (!n) { + printk(BIOS_ERR, "ME: timeout waiting for data: expected " + "%u, available %u\n", expected, + me.buffer_write_ptr - me.buffer_read_ptr); + return -1; + } + + /* Read and verify MEI response header from the ME */ + mei_read_dword_ptr(&mei_rsp, MEI_ME_CB_RW); + if (!mei_rsp.is_complete) { + printk(BIOS_ERR, "ME: response is not complete\n"); + return -1; + } + + /* Handle non-dword responses and expect at least the header */ + ndata = mei_rsp.length >> 2; + if (mei_rsp.length & 3) + ndata++; + if (ndata != (expected - 1)) { + printk(BIOS_ERR, "ME: response is missing data %d != %d\n", + ndata, (expected - 1)); + return -1; + } + + /* Read response header from the ME */ + data = header; + for (n = 0; n < (header_bytes >> 2); ++n) + *data++ = read_cb(); + ndata -= header_bytes >> 2; + + /* Make sure caller passed a buffer with enough space */ + if (ndata != (rsp_bytes >> 2)) { + printk(BIOS_ERR, "ME: not enough room in response buffer: " + "%u != %u\n", ndata, rsp_bytes >> 2); + return -1; + } + + /* Read response data from the circular buffer */ + data = rsp_data; + for (n = 0; n < ndata; ++n) + *data++ = read_cb(); + + /* Tell the ME that we have consumed the response */ + read_host_csr(&host); + host.interrupt_status = 1; + host.interrupt_generate = 1; + write_host_csr(&host); + + return mei_wait_for_me_ready(); +} + +static inline int mei_sendrecv_mkhi(struct mkhi_header *mkhi, + void *req_data, int req_bytes, + void *rsp_data, int rsp_bytes) +{ + struct mkhi_header mkhi_rsp; + + /* Send header */ + if (mei_send_header(MEI_ADDRESS_MKHI, MEI_HOST_ADDRESS, + mkhi, sizeof(*mkhi), req_bytes ? 0 : 1) < 0) + return -1; + + /* Send data if available */ + if (req_bytes && mei_send_data(MEI_ADDRESS_MKHI, MEI_HOST_ADDRESS, + req_data, req_bytes) < 0) + return -1; + + /* Return now if no response expected */ + if (!rsp_bytes) + return 0; + + /* Read header and data */ + if (mei_recv_msg(&mkhi_rsp, sizeof(mkhi_rsp), + rsp_data, rsp_bytes) < 0) + return -1; + + if (!mkhi_rsp.is_response || + mkhi->group_id != mkhi_rsp.group_id || + mkhi->command != mkhi_rsp.command) { + printk(BIOS_ERR, "ME: invalid response, group %u ?= %u," + "command %u ?= %u, is_response %u\n", mkhi->group_id, + mkhi_rsp.group_id, mkhi->command, mkhi_rsp.command, + mkhi_rsp.is_response); + return -1; + } + + return 0; +} + +static inline int mei_sendrecv_icc(struct icc_header *icc, + void *req_data, int req_bytes, + void *rsp_data, int rsp_bytes) +{ + struct icc_header icc_rsp; + + /* Send header */ + if (mei_send_header(MEI_ADDRESS_ICC, MEI_HOST_ADDRESS, + icc, sizeof(*icc), req_bytes ? 0 : 1) < 0) + return -1; + + /* Send data if available */ + if (req_bytes && mei_send_data(MEI_ADDRESS_ICC, MEI_HOST_ADDRESS, + req_data, req_bytes) < 0) + return -1; + + /* Read header and data, if needed */ + if (rsp_bytes && mei_recv_msg(&icc_rsp, sizeof(icc_rsp), + rsp_data, rsp_bytes) < 0) + return -1; + + return 0; +} + +/* + * mbp give up routine. This path is taken if hfs.mpb_rdy is 0 or the read + * state machine on the BIOS end doesn't match the ME's state machine. + */ +static void intel_me_mbp_give_up(struct device *dev) +{ + struct mei_csr csr; + + pci_write_config32(dev, PCI_ME_H_GS2, PCI_ME_MBP_GIVE_UP); + + read_host_csr(&csr); + csr.reset = 1; + csr.interrupt_generate = 1; + write_host_csr(&csr); +} + +/* + * mbp clear routine. This will wait for the ME to indicate that + * the MBP has been read and cleared. + */ +static void intel_me_mbp_clear(struct device *dev) +{ + int count; + struct me_hfs2 hfs2; + + /* Wait for the mbp_cleared indicator */ + for (count = ME_RETRY; count > 0; --count) { + pci_read_dword_ptr(dev, &hfs2, PCI_ME_HFS2); + if (hfs2.mbp_cleared) + break; + udelay(ME_DELAY); + } + + if (count == 0) { + printk(BIOS_WARNING, "ME: Timeout waiting for mbp_cleared\n"); + intel_me_mbp_give_up(dev); + } else { + printk(BIOS_INFO, "ME: MBP cleared\n"); + } +} + +static void me_print_fw_version(mbp_fw_version_name *vers_name) +{ + if (!vers_name) { + printk(BIOS_ERR, "ME: mbp missing version report\n"); + return; + } + + printk(BIOS_DEBUG, "ME: found version %d.%d.%d.%d\n", + vers_name->major_version, vers_name->minor_version, + vers_name->hotfix_version, vers_name->build_version); +} + +static inline void print_cap(const char *name, int state) +{ + printk(BIOS_DEBUG, "ME Capability: %-41s : %sabled\n", + name, state ? " en" : "dis"); +} + +/* Get ME Firmware Capabilities */ +static int mkhi_get_fwcaps(mbp_mefwcaps *cap) +{ + u32 rule_id = 0; + struct me_fwcaps cap_msg; + struct mkhi_header mkhi = { + .group_id = MKHI_GROUP_ID_FWCAPS, + .command = MKHI_FWCAPS_GET_RULE, + }; + + /* Send request and wait for response */ + if (mei_sendrecv_mkhi(&mkhi, &rule_id, sizeof(u32), + &cap_msg, sizeof(cap_msg)) < 0) { + printk(BIOS_ERR, "ME: GET FWCAPS message failed\n"); + return -1; + } + *cap = cap_msg.caps_sku; + return 0; +} + +/* Get ME Firmware Capabilities */ +static void me_print_fwcaps(mbp_mefwcaps *cap) +{ + mbp_mefwcaps local_caps; + if (!cap) { + cap = &local_caps; + printk(BIOS_ERR, "ME: mbp missing fwcaps report\n"); + if (mkhi_get_fwcaps(cap)) + return; + } + + print_cap("Full Network manageability", cap->full_net); + print_cap("Regular Network manageability", cap->std_net); + print_cap("Manageability", cap->manageability); + print_cap("IntelR Anti-Theft (AT)", cap->intel_at); + print_cap("IntelR Capability Licensing Service (CLS)", cap->intel_cls); + print_cap("IntelR Power Sharing Technology (MPC)", cap->intel_mpc); + print_cap("ICC Over Clocking", cap->icc_over_clocking); + print_cap("Protected Audio Video Path (PAVP)", cap->pavp); + print_cap("IPV6", cap->ipv6); + print_cap("KVM Remote Control (KVM)", cap->kvm); + print_cap("Outbreak Containment Heuristic (OCH)", cap->och); + print_cap("Virtual LAN (VLAN)", cap->vlan); + print_cap("TLS", cap->tls); + print_cap("Wireless LAN (WLAN)", cap->wlan); +} + +/* Send END OF POST message to the ME */ +static int mkhi_end_of_post(void) +{ + struct mkhi_header mkhi = { + .group_id = MKHI_GROUP_ID_GEN, + .command = MKHI_END_OF_POST, + }; + u32 eop_ack; + + /* Send request and wait for response */ + if (mei_sendrecv_mkhi(&mkhi, NULL, 0, &eop_ack, sizeof(eop_ack)) < 0) { + printk(BIOS_ERR, "ME: END OF POST message failed\n"); + return -1; + } + + printk(BIOS_INFO, "ME: END OF POST message successful (%d)\n", eop_ack); + return 0; +} + +/* Send END OF POST message to the ME */ +static int mkhi_end_of_post_noack(void) +{ + struct mkhi_header mkhi = { + .group_id = MKHI_GROUP_ID_GEN, + .command = MKHI_END_OF_POST_NOACK, + }; + + /* Send request, do not wait for response */ + if (mei_sendrecv_mkhi(&mkhi, NULL, 0, NULL, 0) < 0) { + printk(BIOS_ERR, "ME: END OF POST NOACK message failed\n"); + return -1; + } + + printk(BIOS_INFO, "ME: END OF POST NOACK message successful\n"); + return 0; +} + +/* Send HMRFPO LOCK message to the ME */ +static int mkhi_hmrfpo_lock(void) +{ + struct mkhi_header mkhi = { + .group_id = MKHI_GROUP_ID_HMRFPO, + .command = MKHI_HMRFPO_LOCK, + }; + u32 ack; + + /* Send request and wait for response */ + if (mei_sendrecv_mkhi(&mkhi, NULL, 0, &ack, sizeof(ack)) < 0) { + printk(BIOS_ERR, "ME: HMRFPO LOCK message failed\n"); + return -1; + } + + printk(BIOS_INFO, "ME: HMRFPO LOCK message successful (%d)\n", ack); + return 0; +} + +/* Send HMRFPO LOCK message to the ME, do not wait for response */ +static int mkhi_hmrfpo_lock_noack(void) +{ + struct mkhi_header mkhi = { + .group_id = MKHI_GROUP_ID_HMRFPO, + .command = MKHI_HMRFPO_LOCK_NOACK, + }; + + /* Send request, do not wait for response */ + if (mei_sendrecv_mkhi(&mkhi, NULL, 0, NULL, 0) < 0) { + printk(BIOS_ERR, "ME: HMRFPO LOCK NOACK message failed\n"); + return -1; + } + + printk(BIOS_INFO, "ME: HMRFPO LOCK NOACK message successful\n"); + return 0; +} + +static void intel_me_finalize(struct device *dev) +{ + u16 reg16; + + /* S3 path will have hidden this device already */ + if (!mei_base_address || mei_base_address == (u8 *) 0xfffffff0) + return; + + /* Make sure IO is disabled */ + reg16 = pci_read_config16(dev, PCI_COMMAND); + reg16 &= ~(PCI_COMMAND_MASTER | + PCI_COMMAND_MEMORY | PCI_COMMAND_IO); + pci_write_config16(dev, PCI_COMMAND, reg16); + + /* Hide the PCI device */ + RCBA32_OR(FD2, PCH_DISABLE_MEI1); + RCBA32(FD2); +} + +static int me_icc_set_clock_enables(u32 mask) +{ + struct icc_clock_enables_msg clk = { + .clock_enables = 0, /* Turn off specified clocks */ + .clock_mask = mask, + .no_response = 1, /* Do not expect response */ + }; + struct icc_header icc = { + .api_version = ICC_API_VERSION_LYNXPOINT, + .icc_command = ICC_SET_CLOCK_ENABLES, + .length = sizeof(clk), + }; + + /* Send request and wait for response */ + if (mei_sendrecv_icc(&icc, &clk, sizeof(clk), NULL, 0) < 0) { + printk(BIOS_ERR, "ME: ICC SET CLOCK ENABLES message failed\n"); + return -1; + } + printk(BIOS_INFO, "ME: ICC SET CLOCK ENABLES 0x%08x\n", mask); + return 0; +} + +/* Determine the path that we should take based on ME status */ +static me_bios_path intel_me_path(struct device *dev) +{ + me_bios_path path = ME_DISABLE_BIOS_PATH; + struct me_hfs hfs; + struct me_hfs2 hfs2; + + /* Check and dump status */ + intel_me_status(); + + pci_read_dword_ptr(dev, &hfs, PCI_ME_HFS); + pci_read_dword_ptr(dev, &hfs2, PCI_ME_HFS2); + + /* Check Current Working State */ + switch (hfs.working_state) { + case ME_HFS_CWS_NORMAL: + path = ME_NORMAL_BIOS_PATH; + break; + case ME_HFS_CWS_REC: + path = ME_RECOVERY_BIOS_PATH; + break; + default: + path = ME_DISABLE_BIOS_PATH; + break; + } + + /* Check Current Operation Mode */ + switch (hfs.operation_mode) { + case ME_HFS_MODE_NORMAL: + break; + case ME_HFS_MODE_DEBUG: + case ME_HFS_MODE_DIS: + case ME_HFS_MODE_OVER_JMPR: + case ME_HFS_MODE_OVER_MEI: + default: + path = ME_DISABLE_BIOS_PATH; + break; + } + + /* Check for any error code and valid firmware and MBP */ + if (hfs.error_code || hfs.fpt_bad) + path = ME_ERROR_BIOS_PATH; + + /* Check if the MBP is ready */ + if (!hfs2.mbp_rdy) { + printk(BIOS_CRIT, "%s: mbp is not ready!\n", + __func__); + path = ME_ERROR_BIOS_PATH; + } + + if (CONFIG(ELOG) && path != ME_NORMAL_BIOS_PATH) { + struct elog_event_data_me_extended data = { + .current_working_state = hfs.working_state, + .operation_state = hfs.operation_state, + .operation_mode = hfs.operation_mode, + .error_code = hfs.error_code, + .progress_code = hfs2.progress_code, + .current_pmevent = hfs2.current_pmevent, + .current_state = hfs2.current_state, + }; + elog_add_event_byte(ELOG_TYPE_MANAGEMENT_ENGINE, path); + elog_add_event_raw(ELOG_TYPE_MANAGEMENT_ENGINE_EXT, + &data, sizeof(data)); + } + + return path; +} + +/* Prepare ME for MEI messages */ +static int intel_mei_setup(struct device *dev) +{ + struct resource *res; + struct mei_csr host; + + /* Find the MMIO base for the ME interface */ + res = find_resource(dev, PCI_BASE_ADDRESS_0); + if (!res || res->base == 0 || res->size == 0) { + printk(BIOS_DEBUG, "ME: MEI resource not present!\n"); + return -1; + } + mei_base_address = res2mmio(res, 0, 0); + + /* Ensure Memory and Bus Master bits are set */ + pci_or_config16(dev, PCI_COMMAND, PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY); + + /* Clean up status for next message */ + read_host_csr(&host); + host.interrupt_generate = 1; + host.ready = 1; + host.reset = 0; + write_host_csr(&host); + + return 0; +} + +/* Read the Extend register hash of ME firmware */ +static int intel_me_extend_valid(struct device *dev) +{ + struct me_heres status; + u32 extend[8] = {0}; + int i, count = 0; + + pci_read_dword_ptr(dev, &status, PCI_ME_HERES); + if (!status.extend_feature_present) { + printk(BIOS_ERR, "ME: Extend Feature not present\n"); + return -1; + } + + if (!status.extend_reg_valid) { + printk(BIOS_ERR, "ME: Extend Register not valid\n"); + return -1; + } + + switch (status.extend_reg_algorithm) { + case PCI_ME_EXT_SHA1: + count = 5; + printk(BIOS_DEBUG, "ME: Extend SHA-1: "); + break; + case PCI_ME_EXT_SHA256: + count = 8; + printk(BIOS_DEBUG, "ME: Extend SHA-256: "); + break; + default: + printk(BIOS_ERR, "ME: Extend Algorithm %d unknown\n", + status.extend_reg_algorithm); + return -1; + } + + for (i = 0; i < count; ++i) { + extend[i] = pci_read_config32(dev, PCI_ME_HER(i)); + printk(BIOS_DEBUG, "%08x", extend[i]); + } + printk(BIOS_DEBUG, "\n"); + +#if CONFIG(CHROMEOS) + /* Save hash in NVS for the OS to verify */ + chromeos_set_me_hash(extend, count); +#endif + + return 0; +} + +static void intel_me_print_mbp(me_bios_payload *mbp_data) +{ + me_print_fw_version(mbp_data->fw_version_name); + + if (CONFIG(DEBUG_INTEL_ME)) + me_print_fwcaps(mbp_data->fw_capabilities); + + if (mbp_data->plat_time) { + printk(BIOS_DEBUG, "ME: Wake Event to ME Reset: %u ms\n", + mbp_data->plat_time->wake_event_mrst_time_ms); + printk(BIOS_DEBUG, "ME: ME Reset to Platform Reset: %u ms\n", + mbp_data->plat_time->mrst_pltrst_time_ms); + printk(BIOS_DEBUG, "ME: Platform Reset to CPU Reset: %u ms\n", + mbp_data->plat_time->pltrst_cpurst_time_ms); + } +} + +static u32 me_to_host_words_pending(void) +{ + struct mei_csr me; + read_me_csr(&me); + if (!me.ready) + return 0; + return (me.buffer_write_ptr - me.buffer_read_ptr) & + (me.buffer_depth - 1); +} + +struct mbp_payload { + mbp_header header; + u32 data[0]; +}; + +/* + * Read and print ME MBP data + * + * Return -1 to indicate a problem (give up) + * Return 0 to indicate success (send LOCK+EOP) + * Return 1 to indicate success (send LOCK+EOP with NOACK) + */ +static int intel_me_read_mbp(me_bios_payload *mbp_data, struct device *dev) +{ + mbp_header mbp_hdr; + u32 me2host_pending; + struct mei_csr host; + struct me_hfs2 hfs2; + struct mbp_payload *mbp; + int i; + int ret = 0; + + pci_read_dword_ptr(dev, &hfs2, PCI_ME_HFS2); + + if (!hfs2.mbp_rdy) { + printk(BIOS_ERR, "ME: MBP not ready\n"); + intel_me_mbp_give_up(dev); + return -1; + } + + me2host_pending = me_to_host_words_pending(); + if (!me2host_pending) { + printk(BIOS_ERR, "ME: no mbp data!\n"); + intel_me_mbp_give_up(dev); + return -1; + } + + /* we know for sure that at least the header is there */ + mei_read_dword_ptr(&mbp_hdr, MEI_ME_CB_RW); + + if ((mbp_hdr.num_entries > (mbp_hdr.mbp_size / 2)) || + (me2host_pending < mbp_hdr.mbp_size)) { + printk(BIOS_ERR, "ME: mbp of %d entries, total size %d words" + " buffer contains %d words\n", + mbp_hdr.num_entries, mbp_hdr.mbp_size, + me2host_pending); + intel_me_mbp_give_up(dev); + return -1; + } + mbp = malloc(mbp_hdr.mbp_size * sizeof(u32)); + if (!mbp) { + intel_me_mbp_give_up(dev); + return -1; + } + + mbp->header = mbp_hdr; + me2host_pending--; + + i = 0; + while (i != me2host_pending) { + mei_read_dword_ptr(&mbp->data[i], MEI_ME_CB_RW); + i++; + } + + read_host_csr(&host); + + /* Check that read and write pointers are equal. */ + if (host.buffer_read_ptr != host.buffer_write_ptr) { + printk(BIOS_INFO, "ME: MBP Read/Write pointer mismatch\n"); + printk(BIOS_INFO, "ME: MBP Waiting for MBP cleared flag\n"); + + /* Tell ME that the host has finished reading the MBP. */ + host.interrupt_generate = 1; + host.reset = 0; + write_host_csr(&host); + + /* Wait for the mbp_cleared indicator. */ + intel_me_mbp_clear(dev); + } else { + /* Indicate NOACK messages should be used. */ + ret = 1; + } + + /* Dump out the MBP contents. */ + if (CONFIG(DEBUG_INTEL_ME)) { + printk(BIOS_INFO, "ME MBP: Header: items: %d, size dw: %d\n", + mbp->header.num_entries, mbp->header.mbp_size); + for (i = 0; i < mbp->header.mbp_size - 1; i++) + printk(BIOS_INFO, "ME MBP: %04x: 0x%08x\n", i, mbp->data[i]); + } + +#define ASSIGN_FIELD_PTR(field_, val_) \ + { \ + mbp_data->field_ = (typeof(mbp_data->field_))(void *)val_; \ + break; \ + } + + /* Setup the pointers in the me_bios_payload structure. */ + for (i = 0; i < mbp->header.mbp_size - 1;) { + mbp_item_header *item = (void *)&mbp->data[i]; + + switch (MBP_MAKE_IDENT(item->app_id, item->item_id)) { + case MBP_IDENT(KERNEL, FW_VER): + ASSIGN_FIELD_PTR(fw_version_name, &mbp->data[i+1]); + + case MBP_IDENT(ICC, PROFILE): + ASSIGN_FIELD_PTR(icc_profile, &mbp->data[i+1]); + + case MBP_IDENT(INTEL_AT, STATE): + ASSIGN_FIELD_PTR(at_state, &mbp->data[i+1]); + + case MBP_IDENT(KERNEL, FW_CAP): + ASSIGN_FIELD_PTR(fw_capabilities, &mbp->data[i+1]); + + case MBP_IDENT(KERNEL, ROM_BIST): + ASSIGN_FIELD_PTR(rom_bist_data, &mbp->data[i+1]); + + case MBP_IDENT(KERNEL, PLAT_KEY): + ASSIGN_FIELD_PTR(platform_key, &mbp->data[i+1]); + + case MBP_IDENT(KERNEL, FW_TYPE): + ASSIGN_FIELD_PTR(fw_plat_type, &mbp->data[i+1]); + + case MBP_IDENT(KERNEL, MFS_FAILURE): + ASSIGN_FIELD_PTR(mfsintegrity, &mbp->data[i+1]); + + case MBP_IDENT(KERNEL, PLAT_TIME): + ASSIGN_FIELD_PTR(plat_time, &mbp->data[i+1]); + + case MBP_IDENT(NFC, SUPPORT_DATA): + ASSIGN_FIELD_PTR(nfc_data, &mbp->data[i+1]); + } + i += item->length; + } + #undef ASSIGN_FIELD_PTR + + free(mbp); + return ret; +} + +/* Check whether ME is present and do basic init */ +static void intel_me_init(struct device *dev) +{ + const struct soc_intel_broadwell_pch_config *config = config_of(dev); + me_bios_path path = intel_me_path(dev); + me_bios_payload mbp_data; + int mbp_ret; + struct me_hfs hfs; + struct mei_csr csr; + + /* Do initial setup and determine the BIOS path */ + printk(BIOS_NOTICE, "ME: BIOS path: %s\n", me_bios_path_values[path]); + + if (path == ME_NORMAL_BIOS_PATH) { + /* Validate the extend register */ + intel_me_extend_valid(dev); +} + + memset(&mbp_data, 0, sizeof(mbp_data)); + + /* + * According to the ME9 BWG, BIOS is required to fetch MBP data in + * all boot flows except S3 Resume. + */ + + /* Prepare MEI MMIO interface */ + if (intel_mei_setup(dev) < 0) + return; + + /* Read ME MBP data */ + mbp_ret = intel_me_read_mbp(&mbp_data, dev); + if (mbp_ret < 0) + return; + intel_me_print_mbp(&mbp_data); + + /* Set clock enables according to devicetree */ + if (config->icc_clock_disable) + me_icc_set_clock_enables(config->icc_clock_disable); + + /* Make sure ME is in a mode that expects EOP */ + pci_read_dword_ptr(dev, &hfs, PCI_ME_HFS); + + /* Abort and leave device alone if not normal mode */ + if (hfs.fpt_bad || + hfs.working_state != ME_HFS_CWS_NORMAL || + hfs.operation_mode != ME_HFS_MODE_NORMAL) + return; + + if (mbp_ret) { + /* + * MBP Cleared wait is skipped, + * Do not expect ACK and reset when complete. + */ + + /* Send HMRFPO Lock command, no response */ + mkhi_hmrfpo_lock_noack(); + + /* Send END OF POST command, no response */ + mkhi_end_of_post_noack(); + + /* Assert reset and interrupt */ + read_host_csr(&csr); + csr.interrupt_generate = 1; + csr.reset = 1; + write_host_csr(&csr); + } else { + /* + * MBP Cleared wait was not skipped + */ + + /* Send HMRFPO LOCK command */ + mkhi_hmrfpo_lock(); + + /* Send EOP command so ME stops accepting other commands */ + mkhi_end_of_post(); + } +} + +static void intel_me_enable(struct device *dev) +{ + /* Avoid talking to the device in S3 path */ + if (acpi_is_wakeup_s3()) { + dev->enabled = 0; + pch_disable_devfn(dev); + } +} + +static struct device_operations device_ops = { + .read_resources = &pci_dev_read_resources, + .set_resources = &pci_dev_set_resources, + .enable_resources = &pci_dev_enable_resources, + .enable = &intel_me_enable, + .init = &intel_me_init, + .final = &intel_me_finalize, + .ops_pci = &pci_dev_ops_pci, +}; + +static const unsigned short pci_device_ids[] = { + 0x9c3a, /* Low Power */ + 0x9cba, /* WildcatPoint */ + 0 +}; + +static const struct pci_driver intel_me __pci_driver = { + .ops = &device_ops, + .vendor = PCI_VENDOR_ID_INTEL, + .devices = pci_device_ids, +}; diff --git a/src/soc/intel/broadwell/pch/me_status.c b/src/soc/intel/broadwell/pch/me_status.c new file mode 100644 index 0000000000..fa44c7c79d --- /dev/null +++ b/src/soc/intel/broadwell/pch/me_status.c @@ -0,0 +1,325 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <device/pci_ops.h> +#include <console/console.h> +#include <device/pci.h> +#include <string.h> +#include <soc/pci_devs.h> +#include <soc/me.h> +#include <delay.h> + +#define ARRAY_TO_ELEMENT(__array__, __index__, __default__) \ + (((__index__) < ARRAY_SIZE((__array__))) ? \ + (__array__)[(__index__)] : \ + (__default__)) + +static inline void me_read_dword_ptr(void *ptr, int offset) +{ + u32 dword = pci_read_config32(PCH_DEV_ME, offset); + memcpy(ptr, &dword, sizeof(dword)); +} + +/* HFS1[3:0] Current Working State Values */ +static const char *me_cws_values[] = { + [ME_HFS_CWS_RESET] = "Reset", + [ME_HFS_CWS_INIT] = "Initializing", + [ME_HFS_CWS_REC] = "Recovery", + [3] = "Unknown (3)", + [4] = "Unknown (4)", + [ME_HFS_CWS_NORMAL] = "Normal", + [ME_HFS_CWS_WAIT] = "Platform Disable Wait", + [ME_HFS_CWS_TRANS] = "OP State Transition", + [ME_HFS_CWS_INVALID] = "Invalid CPU Plugged In", + [9] = "Unknown (9)", + [10] = "Unknown (10)", + [11] = "Unknown (11)", + [12] = "Unknown (12)", + [13] = "Unknown (13)", + [14] = "Unknown (14)", + [15] = "Unknown (15)", +}; + +/* HFS1[8:6] Current Operation State Values */ +static const char *me_opstate_values[] = { + [ME_HFS_STATE_PREBOOT] = "Preboot", + [ME_HFS_STATE_M0_UMA] = "M0 with UMA", + [ME_HFS_STATE_M3] = "M3 without UMA", + [ME_HFS_STATE_M0] = "M0 without UMA", + [ME_HFS_STATE_BRINGUP] = "Bring up", + [ME_HFS_STATE_ERROR] = "M0 without UMA but with error" +}; + +/* HFS[19:16] Current Operation Mode Values */ +static const char *me_opmode_values[] = { + [ME_HFS_MODE_NORMAL] = "Normal", + [ME_HFS_MODE_DEBUG] = "Debug", + [ME_HFS_MODE_DIS] = "Soft Temporary Disable", + [ME_HFS_MODE_OVER_JMPR] = "Security Override via Jumper", + [ME_HFS_MODE_OVER_MEI] = "Security Override via MEI Message" +}; + +/* HFS[15:12] Error Code Values */ +static const char *me_error_values[] = { + [ME_HFS_ERROR_NONE] = "No Error", + [ME_HFS_ERROR_UNCAT] = "Uncategorized Failure", + [ME_HFS_ERROR_IMAGE] = "Image Failure", + [ME_HFS_ERROR_DEBUG] = "Debug Failure" +}; + +/* HFS2[31:28] ME Progress Code */ +static const char *me_progress_values[] = { + [ME_HFS2_PHASE_ROM] = "ROM Phase", + [ME_HFS2_PHASE_BUP] = "BUP Phase", + [ME_HFS2_PHASE_UKERNEL] = "uKernel Phase", + [ME_HFS2_PHASE_POLICY] = "Policy Module", + [ME_HFS2_PHASE_MODULE_LOAD] = "Module Loading", + [ME_HFS2_PHASE_UNKNOWN] = "Unknown", + [ME_HFS2_PHASE_HOST_COMM] = "Host Communication" +}; + +/* HFS2[27:24] Power Management Event */ +static const char *me_pmevent_values[] = { + [ME_HFS2_PMEVENT_CLEAN_MOFF_MX_WAKE] = + "Clean Moff->Mx wake", + [ME_HFS2_PMEVENT_MOFF_MX_WAKE_ERROR] = + "Moff->Mx wake after an error", + [ME_HFS2_PMEVENT_CLEAN_GLOBAL_RESET] = + "Clean global reset", + [ME_HFS2_PMEVENT_CLEAN_GLOBAL_RESET_ERROR] = + "Global reset after an error", + [ME_HFS2_PMEVENT_CLEAN_ME_RESET] = + "Clean Intel ME reset", + [ME_HFS2_PMEVENT_ME_RESET_EXCEPTION] = + "Intel ME reset due to exception", + [ME_HFS2_PMEVENT_PSEUDO_ME_RESET] = + "Pseudo-global reset", + [ME_HFS2_PMEVENT_S0MO_SXM3] = + "S0/M0->Sx/M3", + [ME_HFS2_PMEVENT_SXM3_S0M0] = + "Sx/M3->S0/M0", + [ME_HFS2_PMEVENT_NON_PWR_CYCLE_RESET] = + "Non-power cycle reset", + [ME_HFS2_PMEVENT_PWR_CYCLE_RESET_M3] = + "Power cycle reset through M3", + [ME_HFS2_PMEVENT_PWR_CYCLE_RESET_MOFF] = + "Power cycle reset through Moff", + [ME_HFS2_PMEVENT_SXMX_SXMOFF] = + "Sx/Mx->Sx/Moff" +}; + +/* Progress Code 0 states */ +static const char *me_progress_rom_values[] = { + [ME_HFS2_STATE_ROM_BEGIN] = "BEGIN", + [ME_HFS2_STATE_ROM_DISABLE] = "DISABLE" +}; + +/* Progress Code 1 states */ +static const char *me_progress_bup_values[] = { + [ME_HFS2_STATE_BUP_INIT] = + "Initialization starts", + [ME_HFS2_STATE_BUP_DIS_HOST_WAKE] = + "Disable the host wake event", + [ME_HFS2_STATE_BUP_FLOW_DET] = + "Flow determination start process", + [ME_HFS2_STATE_BUP_VSCC_ERR] = + "Error reading/matching the VSCC table in the descriptor", + [ME_HFS2_STATE_BUP_CHECK_STRAP] = + "Check to see if straps say ME DISABLED", + [ME_HFS2_STATE_BUP_PWR_OK_TIMEOUT] = + "Timeout waiting for PWROK", + [ME_HFS2_STATE_BUP_MANUF_OVRD_STRAP] = + "Possibly handle BUP manufacturing override strap", + [ME_HFS2_STATE_BUP_M3] = + "Bringup in M3", + [ME_HFS2_STATE_BUP_M0] = + "Bringup in M0", + [ME_HFS2_STATE_BUP_FLOW_DET_ERR] = + "Flow detection error", + [ME_HFS2_STATE_BUP_M3_CLK_ERR] = + "M3 clock switching error", + [ME_HFS2_STATE_BUP_CPU_RESET_DID_TIMEOUT_MEM_MISSING] = + "Host error - CPU reset timeout, DID timeout, memory missing", + [ME_HFS2_STATE_BUP_M3_KERN_LOAD] = + "M3 kernel load", + [ME_HFS2_STATE_BUP_T32_MISSING] = + "T34 missing - cannot program ICC", + [ME_HFS2_STATE_BUP_WAIT_DID] = + "Waiting for DID BIOS message", + [ME_HFS2_STATE_BUP_WAIT_DID_FAIL] = + "Waiting for DID BIOS message failure", + [ME_HFS2_STATE_BUP_DID_NO_FAIL] = + "DID reported no error", + [ME_HFS2_STATE_BUP_ENABLE_UMA] = + "Enabling UMA", + [ME_HFS2_STATE_BUP_ENABLE_UMA_ERR] = + "Enabling UMA error", + [ME_HFS2_STATE_BUP_SEND_DID_ACK] = + "Sending DID Ack to BIOS", + [ME_HFS2_STATE_BUP_SEND_DID_ACK_ERR] = + "Sending DID Ack to BIOS error", + [ME_HFS2_STATE_BUP_M0_CLK] = + "Switching clocks in M0", + [ME_HFS2_STATE_BUP_M0_CLK_ERR] = + "Switching clocks in M0 error", + [ME_HFS2_STATE_BUP_TEMP_DIS] = + "ME in temp disable", + [ME_HFS2_STATE_BUP_M0_KERN_LOAD] = + "M0 kernel load", +}; + +/* Progress Code 3 states */ +static const char *me_progress_policy_values[] = { + [ME_HFS2_STATE_POLICY_ENTRY] = "Entry into Policy Module", + [ME_HFS2_STATE_POLICY_RCVD_S3] = "Received S3 entry", + [ME_HFS2_STATE_POLICY_RCVD_S4] = "Received S4 entry", + [ME_HFS2_STATE_POLICY_RCVD_S5] = "Received S5 entry", + [ME_HFS2_STATE_POLICY_RCVD_UPD] = "Received UPD entry", + [ME_HFS2_STATE_POLICY_RCVD_PCR] = "Received PCR entry", + [ME_HFS2_STATE_POLICY_RCVD_NPCR] = "Received NPCR entry", + [ME_HFS2_STATE_POLICY_RCVD_HOST_WAKE] = "Received host wake", + [ME_HFS2_STATE_POLICY_RCVD_AC_DC] = "Received AC<>DC switch", + [ME_HFS2_STATE_POLICY_RCVD_DID] = "Received DRAM Init Done", + [ME_HFS2_STATE_POLICY_VSCC_NOT_FOUND] = + "VSCC Data not found for flash device", + [ME_HFS2_STATE_POLICY_VSCC_INVALID] = + "VSCC Table is not valid", + [ME_HFS2_STATE_POLICY_FPB_ERR] = + "Flash Partition Boundary is outside address space", + [ME_HFS2_STATE_POLICY_DESCRIPTOR_ERR] = + "ME cannot access the chipset descriptor region", + [ME_HFS2_STATE_POLICY_VSCC_NO_MATCH] = + "Required VSCC values for flash parts do not match", +}; + +void intel_me_status(void) +{ + if (CONFIG_DEFAULT_CONSOLE_LOGLEVEL < BIOS_DEBUG) + return; + + struct me_hfs _hfs, *hfs = &_hfs; + struct me_hfs2 _hfs2, *hfs2 = &_hfs2; + + me_read_dword_ptr(hfs, PCI_ME_HFS); + me_read_dword_ptr(hfs2, PCI_ME_HFS2); + + /* Check Current States */ + printk(BIOS_DEBUG, "ME: FW Partition Table : %s\n", + hfs->fpt_bad ? "BAD" : "OK"); + printk(BIOS_DEBUG, "ME: Bringup Loader Failure : %s\n", + hfs->ft_bup_ld_flr ? "YES" : "NO"); + printk(BIOS_DEBUG, "ME: Firmware Init Complete : %s\n", + hfs->fw_init_complete ? "YES" : "NO"); + printk(BIOS_DEBUG, "ME: Manufacturing Mode : %s\n", + hfs->mfg_mode ? "YES" : "NO"); + printk(BIOS_DEBUG, "ME: Boot Options Present : %s\n", + hfs->boot_options_present ? "YES" : "NO"); + printk(BIOS_DEBUG, "ME: Update In Progress : %s\n", + hfs->update_in_progress ? "YES" : "NO"); + printk(BIOS_DEBUG, "ME: Current Working State : %s\n", + ARRAY_TO_ELEMENT(me_cws_values, + hfs->working_state, + "Unknown (OOB)")); + printk(BIOS_DEBUG, "ME: Current Operation State : %s\n", + ARRAY_TO_ELEMENT(me_opstate_values, + hfs->operation_state, + "Unknown (OOB)")); + printk(BIOS_DEBUG, "ME: Current Operation Mode : %s\n", + ARRAY_TO_ELEMENT(me_opmode_values, + hfs->operation_mode, + "Unknown (OOB)")); + printk(BIOS_DEBUG, "ME: Error Code : %s\n", + ARRAY_TO_ELEMENT(me_error_values, + hfs->error_code, + "Unknown (OOB)")); + printk(BIOS_DEBUG, "ME: Progress Phase : %s\n", + ARRAY_TO_ELEMENT(me_progress_values, + hfs2->progress_code, + "Unknown (OOB)")); + printk(BIOS_DEBUG, "ME: Power Management Event : %s\n", + ARRAY_TO_ELEMENT(me_pmevent_values, + hfs2->current_pmevent, + "Unknown (OOB)")); + + printk(BIOS_DEBUG, "ME: Progress Phase State : "); + switch (hfs2->progress_code) { + case ME_HFS2_PHASE_ROM: /* ROM Phase */ + printk(BIOS_DEBUG, "%s", + ARRAY_TO_ELEMENT(me_progress_rom_values, + hfs2->current_state, + "Unknown (OOB)")); + break; + + case ME_HFS2_PHASE_UKERNEL: /* uKernel Phase */ + printk(BIOS_DEBUG, "0x%02x", hfs2->current_state); + break; + + case ME_HFS2_PHASE_BUP: /* Bringup Phase */ + if (ARRAY_TO_ELEMENT(me_progress_bup_values, + hfs2->current_state, NULL)) + printk(BIOS_DEBUG, "%s", + ARRAY_TO_ELEMENT(me_progress_bup_values, + hfs2->current_state, + NULL)); + else + printk(BIOS_DEBUG, "0x%02x", hfs2->current_state); + break; + + case ME_HFS2_PHASE_POLICY: /* Policy Module Phase */ + if (ARRAY_TO_ELEMENT(me_progress_policy_values, + hfs2->current_state, NULL)) + printk(BIOS_DEBUG, "%s", + ARRAY_TO_ELEMENT(me_progress_policy_values, + hfs2->current_state, + NULL)); + else + printk(BIOS_DEBUG, "0x%02x", hfs2->current_state); + break; + + case ME_HFS2_PHASE_HOST_COMM: /* Host Communication Phase */ + if (!hfs2->current_state) + printk(BIOS_DEBUG, "Host communication established"); + else + printk(BIOS_DEBUG, "0x%02x", hfs2->current_state); + break; + + default: + printk(BIOS_DEBUG, "Unknown phase: 0x%02x state: 0x%02x", + hfs2->progress_code, hfs2->current_state); + } + printk(BIOS_DEBUG, "\n"); +} + +void intel_me_hsio_version(uint16_t *version, uint16_t *checksum) +{ + int count; + u32 hsiover; + struct me_hfs hfs; + + /* Query for HSIO version, overloads H_GS and HFS */ + pci_write_config32(PCH_DEV_ME, PCI_ME_H_GS, + ME_HSIO_MESSAGE | ME_HSIO_CMD_GETHSIOVER); + + /* Must wait for ME acknowledgement */ + for (count = ME_RETRY; count > 0; --count) { + me_read_dword_ptr(&hfs, PCI_ME_HFS); + if (hfs.bios_msg_ack) + break; + udelay(ME_DELAY); + } + if (!count) { + printk(BIOS_ERR, "ERROR: ME failed to respond\n"); + return; + } + + /* HSIO version should be in HFS_5 */ + hsiover = pci_read_config32(PCH_DEV_ME, PCI_ME_HFS5); + *version = hsiover >> 16; + *checksum = hsiover & 0xffff; + + printk(BIOS_DEBUG, "ME: HSIO Version : %d (CRC 0x%04x)\n", + *version, *checksum); + + /* Reset registers to normal behavior */ + pci_write_config32(PCH_DEV_ME, PCI_ME_H_GS, + ME_HSIO_MESSAGE | ME_HSIO_CMD_GETHSIOVER); +} diff --git a/src/soc/intel/broadwell/pch/pch.c b/src/soc/intel/broadwell/pch/pch.c new file mode 100644 index 0000000000..e0c5bb0c4d --- /dev/null +++ b/src/soc/intel/broadwell/pch/pch.c @@ -0,0 +1,209 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <console/console.h> +#include <device/pci_ops.h> +#include <device/device.h> +#include <device/pci.h> +#include <device/pci_def.h> +#include <soc/iobp.h> +#include <soc/pch.h> +#include <soc/pci_devs.h> +#include <soc/ramstage.h> +#include <soc/rcba.h> +#include <soc/serialio.h> +#include <soc/spi.h> + +u8 pch_revision(void) +{ + return pci_read_config8(PCH_DEV_LPC, PCI_REVISION_ID); +} + +u16 pch_type(void) +{ + return pci_read_config16(PCH_DEV_LPC, PCI_DEVICE_ID); +} + +/* Return 1 if PCH type is WildcatPoint */ +int pch_is_wpt(void) +{ + return ((pch_type() & 0xfff0) == 0x9cc0) ? 1 : 0; +} + +/* Return 1 if PCH type is WildcatPoint ULX */ +int pch_is_wpt_ulx(void) +{ + u16 lpcid = pch_type(); + + switch (lpcid) { + case PCH_WPT_BDW_Y_SAMPLE: + case PCH_WPT_BDW_Y_PREMIUM: + case PCH_WPT_BDW_Y_BASE: + return 1; + } + + return 0; +} + +u32 pch_read_soft_strap(int id) +{ + u32 fdoc; + + fdoc = SPIBAR32(SPIBAR_FDOC); + fdoc &= ~0x00007ffc; + SPIBAR32(SPIBAR_FDOC) = fdoc; + + fdoc |= 0x00004000; + fdoc |= id * 4; + SPIBAR32(SPIBAR_FDOC) = fdoc; + + return SPIBAR32(SPIBAR_FDOD); +} + +#ifndef __SIMPLE_DEVICE__ + +/* Put device in D3Hot Power State */ +static void pch_enable_d3hot(struct device *dev) +{ + pci_or_config32(dev, PCH_PCS, PCH_PCS_PS_D3HOT); +} + +/* RCBA function disable and posting read to flush the transaction */ +static void rcba_function_disable(u32 reg, u32 bit) +{ + RCBA32_OR(reg, bit); + RCBA32(reg); +} + +/* Set bit in Function Disable register to hide this device */ +void pch_disable_devfn(struct device *dev) +{ + switch (dev->path.pci.devfn) { + case PCH_DEVFN_ADSP: /* Audio DSP */ + rcba_function_disable(FD, PCH_DISABLE_ADSPD); + break; + case PCH_DEVFN_XHCI: /* XHCI */ + rcba_function_disable(FD, PCH_DISABLE_XHCI); + break; + case PCH_DEVFN_SDMA: /* DMA */ + pch_enable_d3hot(dev); + pch_iobp_update(SIO_IOBP_FUNCDIS0, ~0UL, SIO_IOBP_FUNCDIS_DIS); + break; + case PCH_DEVFN_I2C0: /* I2C0 */ + pch_enable_d3hot(dev); + pch_iobp_update(SIO_IOBP_FUNCDIS1, ~0UL, SIO_IOBP_FUNCDIS_DIS); + break; + case PCH_DEVFN_I2C1: /* I2C1 */ + pch_enable_d3hot(dev); + pch_iobp_update(SIO_IOBP_FUNCDIS2, ~0UL, SIO_IOBP_FUNCDIS_DIS); + break; + case PCH_DEVFN_SPI0: /* SPI0 */ + pch_enable_d3hot(dev); + pch_iobp_update(SIO_IOBP_FUNCDIS3, ~0UL, SIO_IOBP_FUNCDIS_DIS); + break; + case PCH_DEVFN_SPI1: /* SPI1 */ + pch_enable_d3hot(dev); + pch_iobp_update(SIO_IOBP_FUNCDIS4, ~0UL, SIO_IOBP_FUNCDIS_DIS); + break; + case PCH_DEVFN_UART0: /* UART0 */ + pch_enable_d3hot(dev); + pch_iobp_update(SIO_IOBP_FUNCDIS5, ~0UL, SIO_IOBP_FUNCDIS_DIS); + break; + case PCH_DEVFN_UART1: /* UART1 */ + pch_enable_d3hot(dev); + pch_iobp_update(SIO_IOBP_FUNCDIS6, ~0UL, SIO_IOBP_FUNCDIS_DIS); + break; + case PCH_DEVFN_ME: /* MEI #1 */ + rcba_function_disable(FD2, PCH_DISABLE_MEI1); + break; + case PCH_DEVFN_ME_2: /* MEI #2 */ + rcba_function_disable(FD2, PCH_DISABLE_MEI2); + break; + case PCH_DEVFN_ME_IDER: /* IDE-R */ + rcba_function_disable(FD2, PCH_DISABLE_IDER); + break; + case PCH_DEVFN_ME_KT: /* KT */ + rcba_function_disable(FD2, PCH_DISABLE_KT); + break; + case PCH_DEVFN_SDIO: /* SDIO */ + pch_enable_d3hot(dev); + pch_iobp_update(SIO_IOBP_FUNCDIS7, ~0UL, SIO_IOBP_FUNCDIS_DIS); + break; + case PCH_DEVFN_GBE: /* Gigabit Ethernet */ + rcba_function_disable(BUC, PCH_DISABLE_GBE); + break; + case PCH_DEVFN_HDA: /* HD Audio Controller */ + rcba_function_disable(FD, PCH_DISABLE_HD_AUDIO); + break; + case PCI_DEVFN(PCH_DEV_SLOT_PCIE, 0): /* PCI Express Root Port 1 */ + case PCI_DEVFN(PCH_DEV_SLOT_PCIE, 1): /* PCI Express Root Port 2 */ + case PCI_DEVFN(PCH_DEV_SLOT_PCIE, 2): /* PCI Express Root Port 3 */ + case PCI_DEVFN(PCH_DEV_SLOT_PCIE, 3): /* PCI Express Root Port 4 */ + case PCI_DEVFN(PCH_DEV_SLOT_PCIE, 4): /* PCI Express Root Port 5 */ + case PCI_DEVFN(PCH_DEV_SLOT_PCIE, 5): /* PCI Express Root Port 6 */ + case PCI_DEVFN(PCH_DEV_SLOT_PCIE, 6): /* PCI Express Root Port 7 */ + case PCI_DEVFN(PCH_DEV_SLOT_PCIE, 7): /* PCI Express Root Port 8 */ + rcba_function_disable(FD, + PCH_DISABLE_PCIE(PCI_FUNC(dev->path.pci.devfn))); + break; + case PCH_DEVFN_EHCI: /* EHCI #1 */ + rcba_function_disable(FD, PCH_DISABLE_EHCI1); + break; + case PCH_DEVFN_LPC: /* LPC */ + rcba_function_disable(FD, PCH_DISABLE_LPC); + break; + case PCH_DEVFN_SATA: /* SATA #1 */ + rcba_function_disable(FD, PCH_DISABLE_SATA1); + break; + case PCH_DEVFN_SMBUS: /* SMBUS */ + rcba_function_disable(FD, PCH_DISABLE_SMBUS); + break; + case PCH_DEVFN_SATA2: /* SATA #2 */ + rcba_function_disable(FD, PCH_DISABLE_SATA2); + break; + case PCH_DEVFN_THERMAL: /* Thermal Subsystem */ + rcba_function_disable(FD, PCH_DISABLE_THERMAL); + break; + } +} + +static void broadwell_pch_enable_dev(struct device *dev) +{ + u16 reg16; + + if (dev->path.type != DEVICE_PATH_PCI) + return; + + if (dev->ops && dev->ops->enable) + return; + + /* These devices need special enable/disable handling */ + switch (PCI_SLOT(dev->path.pci.devfn)) { + case PCH_DEV_SLOT_PCIE: + case PCH_DEV_SLOT_EHCI: + case PCH_DEV_SLOT_HDA: + return; + } + + if (!dev->enabled) { + printk(BIOS_DEBUG, "%s: Disabling device\n", dev_path(dev)); + + /* Ensure memory, io, and bus master are all disabled */ + reg16 = pci_read_config16(dev, PCI_COMMAND); + reg16 &= ~(PCI_COMMAND_MASTER | + PCI_COMMAND_MEMORY | PCI_COMMAND_IO); + pci_write_config16(dev, PCI_COMMAND, reg16); + + /* Disable this device if possible */ + pch_disable_devfn(dev); + } else { + /* Enable SERR */ + pci_or_config16(dev, PCI_COMMAND, PCI_COMMAND_SERR); + } +} + +struct chip_operations soc_intel_broadwell_pch_ops = { + CHIP_NAME("Intel Broadwell PCH") + .enable_dev = &broadwell_pch_enable_dev, +}; + +#endif diff --git a/src/soc/intel/broadwell/pch/pcie.c b/src/soc/intel/broadwell/pch/pcie.c new file mode 100644 index 0000000000..c98201e5ab --- /dev/null +++ b/src/soc/intel/broadwell/pch/pcie.c @@ -0,0 +1,650 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <console/console.h> +#include <device/device.h> +#include <device/pci.h> +#include <device/pciexp.h> +#include <device/pci_def.h> +#include <device/pci_ids.h> +#include <device/pci_ops.h> +#include <soc/gpio.h> +#include <soc/lpc.h> +#include <soc/iobp.h> +#include <soc/pch.h> +#include <soc/pci_devs.h> +#include <soc/rcba.h> +#include <soc/intel/broadwell/pch/chip.h> +#include <soc/cpu.h> +#include <delay.h> + +/* Low Power variant has 6 root ports. */ +#define MAX_NUM_ROOT_PORTS 6 + +struct root_port_config { + /* RPFN is a write-once register so keep a copy until it is written */ + u32 orig_rpfn; + u32 new_rpfn; + u32 pin_ownership; + u32 strpfusecfg1; + u32 strpfusecfg2; + u32 strpfusecfg3; + u32 b0d28f0_32c; + u32 b0d28f4_32c; + u32 b0d28f5_32c; + int coalesce; + int gbe_port; + int num_ports; + struct device *ports[MAX_NUM_ROOT_PORTS]; +}; + +static struct root_port_config rpc; + +static inline int root_port_is_first(struct device *dev) +{ + return PCI_FUNC(dev->path.pci.devfn) == 0; +} + +static inline int root_port_is_last(struct device *dev) +{ + return PCI_FUNC(dev->path.pci.devfn) == (rpc.num_ports - 1); +} + +/* Root ports are numbered 1..N in the documentation. */ +static inline int root_port_number(struct device *dev) +{ + return PCI_FUNC(dev->path.pci.devfn) + 1; +} + +static void root_port_config_update_gbe_port(void) +{ + /* Is the Gbe Port enabled? */ + if (!((rpc.strpfusecfg1 >> 19) & 1)) + return; + + switch ((rpc.strpfusecfg1 >> 16) & 0x7) { + case 0: + rpc.gbe_port = 3; + break; + case 1: + rpc.gbe_port = 4; + break; + case 2: + case 3: + case 4: + case 5: + /* Lanes 0-4 of Root Port 5. */ + rpc.gbe_port = 5; + break; + default: + printk(BIOS_DEBUG, "Invalid GbE Port Selection.\n"); + } +} + +static void pcie_iosf_port_grant_count(struct device *dev) +{ + u8 update_val; + u32 rpcd = (pci_read_config32(dev, 0xfc) >> 14) & 0x3; + + switch (rpcd) { + case 1: + case 3: + update_val = 0x02; + break; + case 2: + update_val = 0x22; + break; + default: + update_val = 0x00; + break; + } + + RCBA32(0x103C) = (RCBA32(0x103C) & (~0xff)) | update_val; +} + +static void root_port_init_config(struct device *dev) +{ + int rp; + u32 data = 0; + u8 resp, id; + + if (root_port_is_first(dev)) { + rpc.orig_rpfn = RCBA32(RPFN); + rpc.new_rpfn = rpc.orig_rpfn; + rpc.num_ports = MAX_NUM_ROOT_PORTS; + rpc.gbe_port = -1; + /* RP0 f5[3:0] = 0101b*/ + pci_update_config8(dev, 0xf5, ~0xa, 0x5); + + pcie_iosf_port_grant_count(dev); + + rpc.pin_ownership = pci_read_config32(dev, 0x410); + root_port_config_update_gbe_port(); + + pci_update_config8(dev, 0xe2, ~(3 << 4), (3 << 4)); + const struct soc_intel_broadwell_pch_config *config = config_of(dev); + rpc.coalesce = config->pcie_port_coalesce; + } + + rp = root_port_number(dev); + if (rp > rpc.num_ports) { + printk(BIOS_ERR, "Found Root Port %d, expecting %d\n", + rp, rpc.num_ports); + return; + } + + /* Read the fuse configuration and pin ownership. */ + switch (rp) { + case 1: + rpc.strpfusecfg1 = pci_read_config32(dev, 0xfc); + rpc.b0d28f0_32c = pci_read_config32(dev, 0x32c); + break; + case 5: + rpc.strpfusecfg2 = pci_read_config32(dev, 0xfc); + rpc.b0d28f4_32c = pci_read_config32(dev, 0x32c); + break; + case 6: + rpc.b0d28f5_32c = pci_read_config32(dev, 0x32c); + rpc.strpfusecfg3 = pci_read_config32(dev, 0xfc); + break; + default: + break; + } + + pci_update_config32(dev, 0x418, 0, 0x02000430); + + if (root_port_is_first(dev)) { + /* + * set RP0 PCICFG E2h[5:4] = 11b and E1h[6] = 1 + * before configuring ASPM + */ + id = 0xe0 + (u8)(RCBA32(RPFN) & 0x07); + pch_iobp_exec(0xE00000E0, IOBP_PCICFG_READ, id, &data, &resp); + data |= ((0x30 << 16) | (0x40 << 8)); + pch_iobp_exec(0xE00000E0, IOBP_PCICFG_WRITE, id, &data, &resp); + } + + /* Cache pci device. */ + rpc.ports[rp - 1] = dev; +} + +/* Update devicetree with new Root Port function number assignment */ +static void pch_pcie_device_set_func(int index, int pci_func) +{ + struct device *dev; + unsigned int new_devfn; + + dev = rpc.ports[index]; + + /* Set the new PCI function field for this Root Port. */ + rpc.new_rpfn &= ~RPFN_FNMASK(index); + rpc.new_rpfn |= RPFN_FNSET(index, pci_func); + + /* Determine the new devfn for this port */ + new_devfn = PCI_DEVFN(PCH_DEV_SLOT_PCIE, pci_func); + + if (dev && dev->path.pci.devfn != new_devfn) { + printk(BIOS_DEBUG, + "PCH: PCIe map %02x.%1x -> %02x.%1x\n", + PCI_SLOT(dev->path.pci.devfn), + PCI_FUNC(dev->path.pci.devfn), + PCI_SLOT(new_devfn), PCI_FUNC(new_devfn)); + + dev->path.pci.devfn = new_devfn; + } +} + +static void pcie_enable_clock_gating(void) +{ + int i; + int enabled_ports = 0; + int is_broadwell = !!(cpu_family_model() == BROADWELL_FAMILY_ULT); + + for (i = 0; i < rpc.num_ports; i++) { + struct device *dev; + int rp; + + dev = rpc.ports[i]; + if (!dev) + continue; + + rp = root_port_number(dev); + + if (!dev->enabled) { + /* Configure shared resource clock gating. */ + if (rp == 1 || rp == 5 || rp == 6) + pci_update_config8(dev, 0xe1, 0xc3, 0x3c); + + pci_update_config8(dev, 0xe2, ~(3 << 4), (3 << 4)); + pci_update_config32(dev, 0x420, ~(1 << 31), (1 << 31)); + + /* Per-Port CLKREQ# handling. */ + if (gpio_is_native(18 + rp - 1)) + pci_update_config32(dev, 0x420, ~0, (3 << 29)); + + /* Enable static clock gating. */ + if (rp == 1 && !rpc.ports[1]->enabled && + !rpc.ports[2]->enabled && !rpc.ports[3]->enabled) { + pci_update_config8(dev, 0xe2, ~1, 1); + pci_update_config8(dev, 0xe1, 0x7f, 0x80); + } else if (rp == 5 || rp == 6) { + pci_update_config8(dev, 0xe2, ~1, 1); + pci_update_config8(dev, 0xe1, 0x7f, 0x80); + } + continue; + } + + enabled_ports++; + + /* Enable dynamic clock gating. */ + pci_update_config8(dev, 0xe1, 0xfc, 0x03); + pci_update_config8(dev, 0xe2, ~(1 << 6), (1 << 6)); + pci_update_config8(dev, 0xe8, ~(3 << 2), (2 << 2)); + + /* Update PECR1 register. */ + pci_update_config8(dev, 0xe8, ~0, 3); + if (is_broadwell) { + pci_update_config32(dev, 0x324, ~((1 << 5) | (1 << 14)), + ((1 << 5) | (1 << 14))); + } else { + pci_update_config32(dev, 0x324, ~(1 << 5), (1 << 5)); + } + /* Per-Port CLKREQ# handling. */ + if (gpio_is_native(18 + rp - 1)) + /* + * In addition to D28Fx PCICFG 420h[30:29] = 11b, + * set 420h[17] = 0b and 420[0] = 1b for L1 SubState. + */ + pci_update_config32(dev, 0x420, ~0x20000, + (3 << 29) | 1); + + /* Configure shared resource clock gating. */ + if (rp == 1 || rp == 5 || rp == 6) + pci_update_config8(dev, 0xe1, 0xc3, 0x3c); + + /* CLKREQ# VR Idle Enable */ + RCBA32_OR(0x2b1c, (1 << (16 + i))); + } + + if (!enabled_ports) + pci_update_config8(rpc.ports[0], 0xe1, ~(1 << 6), (1 << 6)); +} + +static void root_port_commit_config(void) +{ + int i; + + /* If the first root port is disabled the coalesce ports. */ + if (!rpc.ports[0]->enabled) + rpc.coalesce = 1; + + /* Perform clock gating configuration. */ + pcie_enable_clock_gating(); + + for (i = 0; i < rpc.num_ports; i++) { + struct device *dev; + u32 reg32; + int n = 0; + + dev = rpc.ports[i]; + + if (dev == NULL) { + printk(BIOS_ERR, "Root Port %d device is NULL?\n", i+1); + continue; + } + + if (dev->enabled) + continue; + + printk(BIOS_DEBUG, "%s: Disabling device\n", dev_path(dev)); + + /* 8.2 Configuration of PCI Express Root Ports */ + pci_update_config32(dev, 0x338, ~(1 << 26), 1 << 26); + + do { + reg32 = pci_read_config32(dev, 0x328); + n++; + if (((reg32 & 0xff000000) == 0x01000000) || (n > 50)) + break; + udelay(100); + } while (1); + + if (n > 50) + printk(BIOS_DEBUG, "%s: Timeout waiting for 328h\n", + dev_path(dev)); + + pci_update_config32(dev, 0x408, ~(1 << 27), 1 << 27); + + /* Disable this device if possible */ + pch_disable_devfn(dev); + } + + if (rpc.coalesce) { + int current_func; + + /* For all Root Ports N enabled ports get assigned the lower + * PCI function number. The disabled ones get upper PCI + * function numbers. */ + current_func = 0; + for (i = 0; i < rpc.num_ports; i++) { + if (!rpc.ports[i]->enabled) + continue; + pch_pcie_device_set_func(i, current_func); + current_func++; + } + + /* Allocate the disabled devices' PCI function number. */ + for (i = 0; i < rpc.num_ports; i++) { + if (rpc.ports[i]->enabled) + continue; + pch_pcie_device_set_func(i, current_func); + current_func++; + } + } + + printk(BIOS_SPEW, "PCH: RPFN 0x%08x -> 0x%08x\n", + rpc.orig_rpfn, rpc.new_rpfn); + RCBA32(RPFN) = rpc.new_rpfn; +} + +static void root_port_mark_disable(struct device *dev) +{ + /* Mark device as disabled. */ + dev->enabled = 0; + /* Mark device to be hidden. */ + rpc.new_rpfn |= RPFN_HIDE(PCI_FUNC(dev->path.pci.devfn)); +} + +static void root_port_check_disable(struct device *dev) +{ + int rp; + + /* Device already disabled. */ + if (!dev->enabled) { + root_port_mark_disable(dev); + return; + } + + rp = root_port_number(dev); + + /* Is the GbE port mapped to this Root Port? */ + if (rp == rpc.gbe_port) { + root_port_mark_disable(dev); + return; + } + + /* Check Root Port Configuration. */ + switch (rp) { + case 2: + /* Root Port 2 is disabled for all lane configurations + * but config 00b (4x1 links). */ + if ((rpc.strpfusecfg1 >> 14) & 0x3) { + root_port_mark_disable(dev); + return; + } + break; + case 3: + /* Root Port 3 is disabled in config 11b (1x4 links). */ + if (((rpc.strpfusecfg1 >> 14) & 0x3) == 0x3) { + root_port_mark_disable(dev); + return; + } + break; + case 4: + /* Root Port 4 is disabled in configs 11b (1x4 links) + * and 10b (2x2 links). */ + if ((rpc.strpfusecfg1 >> 14) & 0x2) { + root_port_mark_disable(dev); + return; + } + break; + } + + /* Check Pin Ownership. */ + switch (rp) { + case 1: + /* Bit 0 is Root Port 1 ownership. */ + if ((rpc.pin_ownership & 0x1) == 0) { + root_port_mark_disable(dev); + return; + } + break; + case 2: + /* Bit 2 is Root Port 2 ownership. */ + if ((rpc.pin_ownership & 0x4) == 0) { + root_port_mark_disable(dev); + return; + } + break; + case 6: + /* Bits 7:4 are Root Port 6 pin-lane ownership. */ + if ((rpc.pin_ownership & 0xf0) == 0) { + root_port_mark_disable(dev); + return; + } + break; + } +} + +static void pcie_add_0x0202000_iobp(u32 reg) +{ + u32 reg32; + + reg32 = pch_iobp_read(reg); + reg32 += (0x2 << 16) | (0x2 << 8); + pch_iobp_write(reg, reg32); +} + +static void pch_pcie_early(struct device *dev) +{ + const struct soc_intel_broadwell_pch_config *config = config_of(dev); + int do_aspm = 0; + int rp = root_port_number(dev); + + switch (rp) { + case 1: + case 2: + case 3: + case 4: + /* + * Bits 31:28 of b0d28f0 0x32c register correspond to + * Root Ports 4:1. + */ + do_aspm = !!(rpc.b0d28f0_32c & (1 << (28 + rp - 1))); + break; + case 5: + /* + * Bit 28 of b0d28f4 0x32c register correspond to + * Root Ports 4:1. + */ + do_aspm = !!(rpc.b0d28f4_32c & (1 << 28)); + break; + case 6: + /* + * Bit 28 of b0d28f5 0x32c register correspond to + * Root Ports 4:1. + */ + do_aspm = !!(rpc.b0d28f5_32c & (1 << 28)); + break; + } + + /* Allow ASPM to be forced on in devicetree */ + if ((config->pcie_port_force_aspm & (1 << (rp - 1)))) + do_aspm = 1; + + printk(BIOS_DEBUG, "PCIe Root Port %d ASPM is %sabled\n", + rp, do_aspm ? "en" : "dis"); + + if (do_aspm) { + /* Set ASPM bits in MPC2 register. */ + pci_update_config32(dev, 0xd4, ~(0x3 << 2), (1 << 4) | (0x2 << 2)); + + /* Set unique clock exit latency in MPC register. */ + pci_update_config32(dev, 0xd8, ~(0x7 << 18), (0x7 << 18)); + + switch (rp) { + case 1: + pcie_add_0x0202000_iobp(0xe9002440); + break; + case 2: + pcie_add_0x0202000_iobp(0xe9002640); + break; + case 3: + pcie_add_0x0202000_iobp(0xe9000840); + break; + case 4: + pcie_add_0x0202000_iobp(0xe9000a40); + break; + case 5: + pcie_add_0x0202000_iobp(0xe9000c40); + pcie_add_0x0202000_iobp(0xe9000e40); + pcie_add_0x0202000_iobp(0xe9001040); + pcie_add_0x0202000_iobp(0xe9001240); + break; + case 6: + /* Update IOBP based on lane ownership. */ + if (rpc.pin_ownership & (1 << 4)) + pcie_add_0x0202000_iobp(0xea002040); + if (rpc.pin_ownership & (1 << 5)) + pcie_add_0x0202000_iobp(0xea002240); + if (rpc.pin_ownership & (1 << 6)) + pcie_add_0x0202000_iobp(0xea002440); + if (rpc.pin_ownership & (1 << 7)) + pcie_add_0x0202000_iobp(0xea002640); + break; + } + + pci_update_config32(dev, 0x338, ~(1 << 26), 0); + } + + /* Enable LTR in Root Port. Disable OBFF. */ + pci_update_config32(dev, 0x64, ~(1 << 11) & ~(3 << 18), (1 << 11)); + pci_update_config32(dev, 0x68, ~(1 << 10), (1 << 10)); + + pci_update_config32(dev, 0x318, ~(0xffff << 16), (0x1414 << 16)); + + /* Set L1 exit latency in LCAP register. */ + if (!do_aspm && (pci_read_config8(dev, 0xf5) & 0x1)) + pci_update_config32(dev, 0x4c, ~(0x7 << 15), (0x4 << 15)); + else + pci_update_config32(dev, 0x4c, ~(0x7 << 15), (0x2 << 15)); + + pci_update_config32(dev, 0x314, 0, 0x743a361b); + + /* Set Common Clock Exit Latency in MPC register. */ + pci_update_config32(dev, 0xd8, ~(0x7 << 15), (0x3 << 15)); + + pci_update_config32(dev, 0x33c, ~0x00ffffff, 0x854d74); + + /* Set Invalid Receive Range Check Enable in MPC register. */ + pci_update_config32(dev, 0xd8, ~0, (1 << 25)); + + pci_update_config8(dev, 0xf5, 0x0f, 0); + + /* Set AER Extended Cap ID to 01h and Next Cap Pointer to 200h. */ + if (CONFIG(PCIEXP_AER)) + pci_update_config32(dev, 0x100, ~(1 << 29) & ~0xfffff, + (1 << 29) | 0x10001); + else + pci_update_config32(dev, 0x100, ~(1 << 29) & ~0xfffff, + (1 << 29)); + + /* Set L1 Sub-State Cap ID to 1Eh and Next Cap Pointer to None. */ + if (CONFIG(PCIEXP_L1_SUB_STATE)) + pci_update_config32(dev, 0x200, ~0xfffff, 0x001e); + else + pci_update_config32(dev, 0x200, ~0xfffff, 0); + + pci_update_config32(dev, 0x320, ~(3 << 20) & ~(7 << 6), + (1 << 20) | (3 << 6)); + /* Enable Relaxed Order from Root Port. */ + pci_update_config32(dev, 0x320, ~(3 << 23), (3 << 23)); + + if (rp == 1 || rp == 5 || rp == 6) + pci_update_config8(dev, 0xf7, ~0xc, 0); + + /* Set EOI forwarding disable. */ + pci_update_config32(dev, 0xd4, ~0, (1 << 1)); + + /* Read and write back write-once capability registers. */ + pci_update_config32(dev, 0x34, ~0, 0); + pci_update_config32(dev, 0x40, ~0, 0); + pci_update_config32(dev, 0x80, ~0, 0); + pci_update_config32(dev, 0x90, ~0, 0); +} + +static void pch_pcie_init(struct device *dev) +{ + printk(BIOS_DEBUG, "Initializing PCH PCIe bridge.\n"); + + /* Enable SERR */ + pci_or_config16(dev, PCI_COMMAND, PCI_COMMAND_SERR); + + /* Enable Bus Master */ + pci_or_config16(dev, PCI_COMMAND, PCI_COMMAND_MASTER); + + /* Set Cache Line Size to 0x10 */ + pci_write_config8(dev, 0x0c, 0x10); + + pci_and_config16(dev, PCI_BRIDGE_CONTROL, ~PCI_BRIDGE_CTL_PARITY); + + /* Clear errors in status registers */ + pci_update_config16(dev, 0x06, ~0, 0); + pci_update_config16(dev, 0x1e, ~0, 0); +} + +static void pch_pcie_enable(struct device *dev) +{ + /* Add this device to the root port config structure. */ + root_port_init_config(dev); + + /* Check to see if this Root Port should be disabled. */ + root_port_check_disable(dev); + + /* Power Management init before enumeration */ + if (dev->enabled) + pch_pcie_early(dev); + + /* + * When processing the last PCIe root port we can now + * update the Root Port Function Number and Hide register. + */ + if (root_port_is_last(dev)) + root_port_commit_config(); +} + +static void pcie_set_L1_ss_max_latency(struct device *dev, unsigned int off) +{ + /* Set max snoop and non-snoop latency for Broadwell */ + pci_write_config32(dev, off, + PCIE_LTR_MAX_NO_SNOOP_LATENCY_3146US << 16 | + PCIE_LTR_MAX_SNOOP_LATENCY_3146US); +} + +static struct pci_operations pcie_ops = { + .set_subsystem = pci_dev_set_subsystem, + .set_L1_ss_latency = pcie_set_L1_ss_max_latency, +}; + +static struct device_operations device_ops = { + .read_resources = pci_bus_read_resources, + .set_resources = pci_dev_set_resources, + .enable_resources = pci_bus_enable_resources, + .init = pch_pcie_init, + .enable = pch_pcie_enable, + .scan_bus = pciexp_scan_bridge, + .ops_pci = &pcie_ops, +}; + +static const unsigned short pcie_device_ids[] = { + /* Lynxpoint-LP */ + 0x9c10, 0x9c12, 0x9c14, 0x9c16, 0x9c18, 0x9c1a, + /* WildcatPoint */ + 0x9c90, 0x9c92, 0x9c94, 0x9c96, 0x9c98, 0x9c9a, 0x2448, + 0 +}; + +static const struct pci_driver pch_pcie __pci_driver = { + .ops = &device_ops, + .vendor = PCI_VENDOR_ID_INTEL, + .devices = pcie_device_ids, +}; diff --git a/src/soc/intel/broadwell/pch/pmutil.c b/src/soc/intel/broadwell/pch/pmutil.c new file mode 100644 index 0000000000..e63a981456 --- /dev/null +++ b/src/soc/intel/broadwell/pch/pmutil.c @@ -0,0 +1,455 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +/* + * Helper functions for dealing with power management registers + * and the differences between PCH variants. + */ + +#include <acpi/acpi.h> +#include <arch/io.h> +#include <bootmode.h> +#include <device/pci_ops.h> +#include <device/device.h> +#include <device/pci.h> +#include <device/pci_def.h> +#include <console/console.h> +#include <soc/iomap.h> +#include <soc/lpc.h> +#include <soc/pci_devs.h> +#include <soc/pm.h> +#include <soc/gpio.h> +#include <security/vboot/vbnv.h> +#include <stdint.h> + +static inline uint16_t get_gpiobase(void) +{ + return GPIO_BASE_ADDRESS; +} + +/* Print status bits with descriptive names */ +static void print_status_bits(u32 status, const char *bit_names[]) +{ + int i; + + if (!status) + return; + + for (i = 31; i >= 0; i--) { + if (status & (1 << i)) { + if (bit_names[i]) + printk(BIOS_DEBUG, "%s ", bit_names[i]); + else + printk(BIOS_DEBUG, "BIT%d ", i); + } + } +} + +/* Print status bits as GPIO numbers */ +static void print_gpio_status(u32 status, int start) +{ + int i; + + if (!status) + return; + + for (i = 31; i >= 0; i--) { + if (status & (1 << i)) + printk(BIOS_DEBUG, "GPIO%d ", start + i); + } +} + +/* + * PM1_CNT + */ + +/* Enable events in PM1 control register */ +void enable_pm1_control(u32 mask) +{ + u32 pm1_cnt = inl(get_pmbase() + PM1_CNT); + pm1_cnt |= mask; + outl(pm1_cnt, get_pmbase() + PM1_CNT); +} + +/* Disable events in PM1 control register */ +void disable_pm1_control(u32 mask) +{ + u32 pm1_cnt = inl(get_pmbase() + PM1_CNT); + pm1_cnt &= ~mask; + outl(pm1_cnt, get_pmbase() + PM1_CNT); +} + +/* + * PM1 + */ + +/* Clear and return PM1 status register */ +static u16 reset_pm1_status(void) +{ + u16 pm1_sts = inw(get_pmbase() + PM1_STS); + outw(pm1_sts, get_pmbase() + PM1_STS); + return pm1_sts; +} + +/* Print PM1 status bits */ +static u16 print_pm1_status(u16 pm1_sts) +{ + const char *pm1_sts_bits[] = { + [0] = "TMROF", + [4] = "BM", + [5] = "GBL", + [8] = "PWRBTN", + [10] = "RTC", + [11] = "PRBTNOR", + [14] = "PCIEXPWAK", + [15] = "WAK", + }; + + if (!pm1_sts) + return 0; + + printk(BIOS_SPEW, "PM1_STS: "); + print_status_bits(pm1_sts, pm1_sts_bits); + printk(BIOS_SPEW, "\n"); + + return pm1_sts; +} + +/* Print, clear, and return PM1 status */ +u16 clear_pm1_status(void) +{ + return print_pm1_status(reset_pm1_status()); +} + +/* Set the PM1 register to events */ +void enable_pm1(u16 events) +{ + outw(events, get_pmbase() + PM1_EN); +} + +/* + * SMI + */ + +/* Clear and return SMI status register */ +static u32 reset_smi_status(void) +{ + u32 smi_sts = inl(get_pmbase() + SMI_STS); + outl(smi_sts, get_pmbase() + SMI_STS); + return smi_sts; +} + +/* Print SMI status bits */ +static u32 print_smi_status(u32 smi_sts) +{ + const char *smi_sts_bits[] = { + [2] = "BIOS", + [3] = "LEGACY_USB", + [4] = "SLP_SMI", + [5] = "APM", + [6] = "SWSMI_TMR", + [8] = "PM1", + [9] = "GPE0", + [10] = "GPI", + [11] = "MCSMI", + [12] = "DEVMON", + [13] = "TCO", + [14] = "PERIODIC", + [15] = "SERIRQ_SMI", + [16] = "SMBUS_SMI", + [17] = "LEGACY_USB2", + [18] = "INTEL_USB2", + [20] = "PCI_EXP_SMI", + [21] = "MONITOR", + [26] = "SPI", + [27] = "GPIO_UNLOCK" + }; + + if (!smi_sts) + return 0; + + printk(BIOS_DEBUG, "SMI_STS: "); + print_status_bits(smi_sts, smi_sts_bits); + printk(BIOS_DEBUG, "\n"); + + return smi_sts; +} + +/* Print, clear, and return SMI status */ +u32 clear_smi_status(void) +{ + return print_smi_status(reset_smi_status()); +} + +/* Enable SMI event */ +void enable_smi(u32 mask) +{ + u32 smi_en = inl(get_pmbase() + SMI_EN); + smi_en |= mask; + outl(smi_en, get_pmbase() + SMI_EN); +} + +/* Disable SMI event */ +void disable_smi(u32 mask) +{ + u32 smi_en = inl(get_pmbase() + SMI_EN); + smi_en &= ~mask; + outl(smi_en, get_pmbase() + SMI_EN); +} + +/* + * ALT_GP_SMI + */ + +/* Clear GPIO SMI status and return events that are enabled and active */ +static u32 reset_alt_smi_status(void) +{ + u32 alt_sts, alt_en; + + /* Low Power variant moves this to GPIO region as dword */ + alt_sts = inl(get_gpiobase() + GPIO_ALT_GPI_SMI_STS); + outl(alt_sts, get_gpiobase() + GPIO_ALT_GPI_SMI_STS); + alt_en = inl(get_gpiobase() + GPIO_ALT_GPI_SMI_EN); + + /* Only report enabled events */ + return alt_sts & alt_en; +} + +/* Print GPIO SMI status bits */ +static u32 print_alt_smi_status(u32 alt_sts) +{ + if (!alt_sts) + return 0; + + printk(BIOS_DEBUG, "ALT_STS: "); + + /* First 16 events are GPIO 32-47 */ + print_gpio_status(alt_sts & 0xffff, 32); + + printk(BIOS_DEBUG, "\n"); + + return alt_sts; +} + +/* Print, clear, and return GPIO SMI status */ +u32 clear_alt_smi_status(void) +{ + return print_alt_smi_status(reset_alt_smi_status()); +} + +/* Enable GPIO SMI events */ +void enable_alt_smi(u32 mask) +{ + u32 alt_en; + + alt_en = inl(get_gpiobase() + GPIO_ALT_GPI_SMI_EN); + alt_en |= mask; + outl(alt_en, get_gpiobase() + GPIO_ALT_GPI_SMI_EN); +} + +/* + * TCO + */ + +/* Clear TCO status and return events that are enabled and active */ +static u32 reset_tco_status(void) +{ + u32 tcobase = get_pmbase() + 0x60; + u32 tco_sts = inl(tcobase + 0x04); + u32 tco_en = inl(get_pmbase() + 0x68); + + /* Don't clear BOOT_STS before SECOND_TO_STS */ + outl(tco_sts & ~(1 << 18), tcobase + 0x04); + + /* Clear BOOT_STS */ + if (tco_sts & (1 << 18)) + outl(tco_sts & (1 << 18), tcobase + 0x04); + + return tco_sts & tco_en; +} + +/* Print TCO status bits */ +static u32 print_tco_status(u32 tco_sts) +{ + const char *tco_sts_bits[] = { + [0] = "NMI2SMI", + [1] = "SW_TCO", + [2] = "TCO_INT", + [3] = "TIMEOUT", + [7] = "NEWCENTURY", + [8] = "BIOSWR", + [9] = "DMISCI", + [10] = "DMISMI", + [12] = "DMISERR", + [13] = "SLVSEL", + [16] = "INTRD_DET", + [17] = "SECOND_TO", + [18] = "BOOT", + [20] = "SMLINK_SLV" + }; + + if (!tco_sts) + return 0; + + printk(BIOS_DEBUG, "TCO_STS: "); + print_status_bits(tco_sts, tco_sts_bits); + printk(BIOS_DEBUG, "\n"); + + return tco_sts; +} + +/* Print, clear, and return TCO status */ +u32 clear_tco_status(void) +{ + return print_tco_status(reset_tco_status()); +} + +/* Enable TCO SCI */ +void enable_tco_sci(void) +{ + /* Clear pending events */ + outl(get_pmbase() + GPE0_STS(3), TCOSCI_STS); + + /* Enable TCO SCI events */ + enable_gpe(TCOSCI_EN); +} + +/* + * GPE0 + */ + +/* Clear a GPE0 status and return events that are enabled and active */ +static u32 reset_gpe_status(u16 sts_reg, u16 en_reg) +{ + u32 gpe0_sts = inl(get_pmbase() + sts_reg); + u32 gpe0_en = inl(get_pmbase() + en_reg); + + outl(gpe0_sts, get_pmbase() + sts_reg); + + /* Only report enabled events */ + return gpe0_sts & gpe0_en; +} + +/* Print GPE0 status bits */ +static u32 print_gpe_status(u32 gpe0_sts, const char *bit_names[]) +{ + if (!gpe0_sts) + return 0; + + printk(BIOS_DEBUG, "GPE0_STS: "); + print_status_bits(gpe0_sts, bit_names); + printk(BIOS_DEBUG, "\n"); + + return gpe0_sts; +} + +/* Print GPE0 GPIO status bits */ +static u32 print_gpe_gpio(u32 gpe0_sts, int start) +{ + if (!gpe0_sts) + return 0; + + printk(BIOS_DEBUG, "GPE0_STS: "); + print_gpio_status(gpe0_sts, start); + printk(BIOS_DEBUG, "\n"); + + return gpe0_sts; +} + +/* Clear all GPE status and return "standard" GPE event status */ +u32 clear_gpe_status(void) +{ + const char *gpe0_sts_3_bits[] = { + [1] = "HOTPLUG", + [2] = "SWGPE", + [6] = "TCO_SCI", + [7] = "SMB_WAK", + [9] = "PCI_EXP", + [10] = "BATLOW", + [11] = "PME", + [12] = "ME", + [13] = "PME_B0", + [16] = "GPIO27", + [18] = "WADT" + }; + + print_gpe_gpio(reset_gpe_status(GPE0_STS(GPE_31_0), GPE0_EN(GPE_31_0)), 0); + print_gpe_gpio(reset_gpe_status(GPE0_STS(GPE_63_32), GPE0_EN(GPE_63_32)), 32); + print_gpe_gpio(reset_gpe_status(GPE0_STS(GPE_94_64), GPE0_EN(GPE_94_64)), 64); + return print_gpe_status(reset_gpe_status(GPE0_STS(GPE_STD), GPE0_EN(GPE_STD)), + gpe0_sts_3_bits); +} + +/* Enable all requested GPE */ +void enable_all_gpe(u32 set1, u32 set2, u32 set3, u32 set4) +{ + u16 pmbase = get_pmbase(); + + outl(set1, pmbase + GPE0_EN(GPE_31_0)); + outl(set2, pmbase + GPE0_EN(GPE_63_32)); + outl(set3, pmbase + GPE0_EN(GPE_94_64)); + outl(set4, pmbase + GPE0_EN(GPE_STD)); +} + +/* Disable all GPE */ +void disable_all_gpe(void) +{ + enable_all_gpe(0, 0, 0, 0); +} + +/* Enable a standard GPE */ +void enable_gpe(u32 mask) +{ + u32 gpe0_en = inl(get_pmbase() + GPE0_EN(GPE_STD)); + gpe0_en |= mask; + outl(gpe0_en, get_pmbase() + GPE0_EN(GPE_STD)); +} + +/* Disable a standard GPE */ +void disable_gpe(u32 mask) +{ + u32 gpe0_en = inl(get_pmbase() + GPE0_EN(GPE_STD)); + gpe0_en &= ~mask; + outl(gpe0_en, get_pmbase() + GPE0_EN(GPE_STD)); +} + +int acpi_sci_irq(void) +{ + int scis = pci_read_config32(PCH_DEV_LPC, ACPI_CNTL) & SCI_IRQ_SEL; + int sci_irq = 9; + + /* Determine how SCI is routed. */ + switch (scis) { + case SCIS_IRQ9: + case SCIS_IRQ10: + case SCIS_IRQ11: + sci_irq = scis - SCIS_IRQ9 + 9; + break; + case SCIS_IRQ20: + case SCIS_IRQ21: + case SCIS_IRQ22: + case SCIS_IRQ23: + sci_irq = scis - SCIS_IRQ20 + 20; + break; + default: + printk(BIOS_DEBUG, "Invalid SCI route! Defaulting to IRQ9.\n"); + sci_irq = 9; + break; + } + + printk(BIOS_DEBUG, "SCI is IRQ%d\n", sci_irq); + return sci_irq; +} + +int platform_is_resuming(void) +{ + if (!(inw(get_pmbase() + PM1_STS) & WAK_STS)) + return 0; + + return acpi_sleep_from_pm1(inl(get_pmbase() + PM1_CNT)) == ACPI_S3; +} + +/* STM Support */ +uint16_t get_pmbase(void) +{ + return (uint16_t) ACPI_BASE_ADDRESS; +} diff --git a/src/soc/intel/broadwell/pch/power_state.c b/src/soc/intel/broadwell/pch/power_state.c new file mode 100644 index 0000000000..cb1d3e5b9c --- /dev/null +++ b/src/soc/intel/broadwell/pch/power_state.c @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <arch/io.h> +#include <device/pci_ops.h> +#include <cbmem.h> +#include <console/console.h> +#include <device/device.h> +#include <device/pci.h> +#include <device/pci_def.h> +#include <string.h> +#include <soc/iomap.h> +#include <soc/lpc.h> +#include <soc/pci_devs.h> +#include <soc/pm.h> +#include <soc/romstage.h> + +static struct chipset_power_state power_state; + +static void migrate_power_state(int is_recovery) +{ + struct chipset_power_state *ps_cbmem; + struct chipset_power_state *ps_car; + + ps_car = &power_state; + ps_cbmem = cbmem_add(CBMEM_ID_POWER_STATE, sizeof(*ps_cbmem)); + + if (ps_cbmem == NULL) { + printk(BIOS_DEBUG, "Not adding power state to cbmem!\n"); + return; + } + memcpy(ps_cbmem, ps_car, sizeof(*ps_cbmem)); +} +ROMSTAGE_CBMEM_INIT_HOOK(migrate_power_state) + +/* Return 0, 3, or 5 to indicate the previous sleep state. */ +static int prev_sleep_state(struct chipset_power_state *ps) +{ + /* Default to S0. */ + int prev_sleep_state = ACPI_S0; + + if (ps->pm1_sts & WAK_STS) { + switch (acpi_sleep_from_pm1(ps->pm1_cnt)) { + case ACPI_S3: + if (CONFIG(HAVE_ACPI_RESUME)) + prev_sleep_state = ACPI_S3; + break; + case ACPI_S5: + prev_sleep_state = ACPI_S5; + break; + } + /* Clear SLP_TYP. */ + outl(ps->pm1_cnt & ~(SLP_TYP), ACPI_BASE_ADDRESS + PM1_CNT); + } + + if (ps->gen_pmcon3 & (PWR_FLR | SUS_PWR_FLR)) + prev_sleep_state = ACPI_S5; + + return prev_sleep_state; +} + +static void dump_power_state(struct chipset_power_state *ps) +{ + printk(BIOS_DEBUG, "PM1_STS: %04x\n", ps->pm1_sts); + printk(BIOS_DEBUG, "PM1_EN: %04x\n", ps->pm1_en); + printk(BIOS_DEBUG, "PM1_CNT: %08x\n", ps->pm1_cnt); + printk(BIOS_DEBUG, "TCO_STS: %04x %04x\n", + ps->tco1_sts, ps->tco2_sts); + + printk(BIOS_DEBUG, "GPE0_STS: %08x %08x %08x %08x\n", + ps->gpe0_sts[0], ps->gpe0_sts[1], + ps->gpe0_sts[2], ps->gpe0_sts[3]); + printk(BIOS_DEBUG, "GPE0_EN: %08x %08x %08x %08x\n", + ps->gpe0_en[0], ps->gpe0_en[1], + ps->gpe0_en[2], ps->gpe0_en[3]); + + printk(BIOS_DEBUG, "GEN_PMCON: %04x %04x %04x\n", + ps->gen_pmcon1, ps->gen_pmcon2, ps->gen_pmcon3); + + printk(BIOS_DEBUG, "Previous Sleep State: S%d\n", + ps->prev_sleep_state); +} + +/* Fill power state structure from ACPI PM registers */ +struct chipset_power_state *fill_power_state(void) +{ + struct chipset_power_state *ps = &power_state; + + ps->pm1_sts = inw(ACPI_BASE_ADDRESS + PM1_STS); + ps->pm1_en = inw(ACPI_BASE_ADDRESS + PM1_EN); + ps->pm1_cnt = inl(ACPI_BASE_ADDRESS + PM1_CNT); + ps->tco1_sts = inw(ACPI_BASE_ADDRESS + TCO1_STS); + ps->tco2_sts = inw(ACPI_BASE_ADDRESS + TCO2_STS); + ps->gpe0_sts[0] = inl(ACPI_BASE_ADDRESS + GPE0_STS(0)); + ps->gpe0_sts[1] = inl(ACPI_BASE_ADDRESS + GPE0_STS(1)); + ps->gpe0_sts[2] = inl(ACPI_BASE_ADDRESS + GPE0_STS(2)); + ps->gpe0_sts[3] = inl(ACPI_BASE_ADDRESS + GPE0_STS(3)); + ps->gpe0_en[0] = inl(ACPI_BASE_ADDRESS + GPE0_EN(0)); + ps->gpe0_en[1] = inl(ACPI_BASE_ADDRESS + GPE0_EN(1)); + ps->gpe0_en[2] = inl(ACPI_BASE_ADDRESS + GPE0_EN(2)); + ps->gpe0_en[3] = inl(ACPI_BASE_ADDRESS + GPE0_EN(3)); + + ps->gen_pmcon1 = pci_read_config16(PCH_DEV_LPC, GEN_PMCON_1); + ps->gen_pmcon2 = pci_read_config16(PCH_DEV_LPC, GEN_PMCON_2); + ps->gen_pmcon3 = pci_read_config16(PCH_DEV_LPC, GEN_PMCON_3); + + ps->prev_sleep_state = prev_sleep_state(ps); + + dump_power_state(ps); + + return ps; +} diff --git a/src/soc/intel/broadwell/pch/sata.c b/src/soc/intel/broadwell/pch/sata.c new file mode 100644 index 0000000000..b496e53e3d --- /dev/null +++ b/src/soc/intel/broadwell/pch/sata.c @@ -0,0 +1,286 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <device/mmio.h> +#include <device/pci_ops.h> +#include <console/console.h> +#include <device/device.h> +#include <device/pci.h> +#include <device/pci_ids.h> +#include <delay.h> +#include <soc/iobp.h> +#include <soc/ramstage.h> +#include <soc/rcba.h> +#include <soc/sata.h> +#include <soc/intel/broadwell/pch/chip.h> + +static inline u32 sir_read(struct device *dev, int idx) +{ + pci_write_config32(dev, SATA_SIRI, idx); + return pci_read_config32(dev, SATA_SIRD); +} + +static inline void sir_write(struct device *dev, int idx, u32 value) +{ + pci_write_config32(dev, SATA_SIRI, idx); + pci_write_config32(dev, SATA_SIRD, value); +} + +static void sata_init(struct device *dev) +{ + const struct soc_intel_broadwell_pch_config *config = config_of(dev); + u32 reg32; + u8 *abar; + u16 reg16; + int port; + + printk(BIOS_DEBUG, "SATA: Initializing controller in AHCI mode.\n"); + + /* Enable BARs */ + pci_write_config16(dev, PCI_COMMAND, + PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY | PCI_COMMAND_IO); + + /* Set Interrupt Line */ + /* Interrupt Pin is set by D31IP.PIP */ + pci_write_config8(dev, PCI_INTERRUPT_LINE, 0x0a); + + /* Set timings */ + pci_write_config16(dev, IDE_TIM_PRI, IDE_DECODE_ENABLE); + pci_write_config16(dev, IDE_TIM_SEC, IDE_DECODE_ENABLE); + + /* for AHCI, Port Enable is managed in memory mapped space */ + reg16 = pci_read_config16(dev, 0x92); + reg16 &= ~0xf; + reg16 |= 0x8000 | config->sata_port_map; + pci_write_config16(dev, 0x92, reg16); + udelay(2); + + /* Setup register 98h */ + reg32 = pci_read_config32(dev, 0x98); + reg32 &= ~((1 << 31) | (1 << 30)); + reg32 |= 1 << 23; + reg32 |= 1 << 24; /* Enable MPHY Dynamic Power Gating */ + pci_write_config32(dev, 0x98, reg32); + + /* Setup register 9Ch */ + reg16 = (1 << 5); /* BWG step 12 */ + pci_write_config16(dev, 0x9c, reg16); + + /* SATA Initialization register */ + reg32 = 0x183; + reg32 |= (config->sata_port_map ^ 0xf) << 24; + reg32 |= (config->sata_devslp_mux & 1) << 15; + pci_write_config32(dev, 0x94, reg32); + + /* Initialize AHCI memory-mapped space */ + abar = (u8 *)(pci_read_config32(dev, PCI_BASE_ADDRESS_5)); + printk(BIOS_DEBUG, "ABAR: %p\n", abar); + + /* CAP (HBA Capabilities) : enable power management */ + reg32 = read32(abar + 0x00); + reg32 |= 0x0c006000; /* set PSC+SSC+SALP+SSS */ + reg32 &= ~0x00020060; /* clear SXS+EMS+PMS */ + reg32 |= (1 << 18); /* SAM: SATA AHCI MODE ONLY */ + write32(abar + 0x00, reg32); + + /* PI (Ports implemented) */ + write32(abar + 0x0c, config->sata_port_map); + (void) read32(abar + 0x0c); /* Read back 1 */ + (void) read32(abar + 0x0c); /* Read back 2 */ + + /* CAP2 (HBA Capabilities Extended)*/ + if (config->sata_devslp_disable) { + reg32 = read32(abar + 0x24); + reg32 &= ~(1 << 3); + write32(abar + 0x24, reg32); + } else { + /* Enable DEVSLP */ + reg32 = read32(abar + 0x24); + reg32 |= (1 << 5)|(1 << 4)|(1 << 3)|(1 << 2); + write32(abar + 0x24, reg32); + + for (port = 0; port < 4; port++) { + if (!(config->sata_port_map & (1 << port))) + continue; + reg32 = read32(abar + 0x144 + (0x80 * port)); + reg32 |= (1 << 1); /* DEVSLP DSP */ + write32(abar + 0x144 + (0x80 * port), reg32); + } + } + + /* + * Static Power Gating for unused ports + */ + reg32 = RCBA32(0x3a84); + /* Port 3 and 2 disabled */ + if ((config->sata_port_map & ((1 << 3)|(1 << 2))) == 0) + reg32 |= (1 << 24) | (1 << 26); + /* Port 1 and 0 disabled */ + if ((config->sata_port_map & ((1 << 1)|(1 << 0))) == 0) + reg32 |= (1 << 20) | (1 << 18); + RCBA32(0x3a84) = reg32; + + /* Set Gen3 Transmitter settings if needed */ + if (config->sata_port0_gen3_tx) + pch_iobp_update(SATA_IOBP_SP0_SECRT88, + ~(SATA_SECRT88_VADJ_MASK << + SATA_SECRT88_VADJ_SHIFT), + (config->sata_port0_gen3_tx & + SATA_SECRT88_VADJ_MASK) + << SATA_SECRT88_VADJ_SHIFT); + + if (config->sata_port1_gen3_tx) + pch_iobp_update(SATA_IOBP_SP1_SECRT88, + ~(SATA_SECRT88_VADJ_MASK << + SATA_SECRT88_VADJ_SHIFT), + (config->sata_port1_gen3_tx & + SATA_SECRT88_VADJ_MASK) + << SATA_SECRT88_VADJ_SHIFT); + + if (config->sata_port2_gen3_tx) + pch_iobp_update(SATA_IOBP_SP2_SECRT88, + ~(SATA_SECRT88_VADJ_MASK << + SATA_SECRT88_VADJ_SHIFT), + (config->sata_port2_gen3_tx & + SATA_SECRT88_VADJ_MASK) + << SATA_SECRT88_VADJ_SHIFT); + + if (config->sata_port3_gen3_tx) + pch_iobp_update(SATA_IOBP_SP3_SECRT88, + ~(SATA_SECRT88_VADJ_MASK << + SATA_SECRT88_VADJ_SHIFT), + (config->sata_port3_gen3_tx & + SATA_SECRT88_VADJ_MASK) + << SATA_SECRT88_VADJ_SHIFT); + + /* Set Gen3 DTLE DATA / EDGE registers if needed */ + if (config->sata_port0_gen3_dtle) { + pch_iobp_update(SATA_IOBP_SP0DTLE_DATA, + ~(SATA_DTLE_MASK << SATA_DTLE_DATA_SHIFT), + (config->sata_port0_gen3_dtle & SATA_DTLE_MASK) + << SATA_DTLE_DATA_SHIFT); + + pch_iobp_update(SATA_IOBP_SP0DTLE_EDGE, + ~(SATA_DTLE_MASK << SATA_DTLE_EDGE_SHIFT), + (config->sata_port0_gen3_dtle & SATA_DTLE_MASK) + << SATA_DTLE_EDGE_SHIFT); + } + + if (config->sata_port1_gen3_dtle) { + pch_iobp_update(SATA_IOBP_SP1DTLE_DATA, + ~(SATA_DTLE_MASK << SATA_DTLE_DATA_SHIFT), + (config->sata_port1_gen3_dtle & SATA_DTLE_MASK) + << SATA_DTLE_DATA_SHIFT); + + pch_iobp_update(SATA_IOBP_SP1DTLE_EDGE, + ~(SATA_DTLE_MASK << SATA_DTLE_EDGE_SHIFT), + (config->sata_port1_gen3_dtle & SATA_DTLE_MASK) + << SATA_DTLE_EDGE_SHIFT); + } + + if (config->sata_port2_gen3_dtle) { + pch_iobp_update(SATA_IOBP_SP2DTLE_DATA, + ~(SATA_DTLE_MASK << SATA_DTLE_DATA_SHIFT), + (config->sata_port2_gen3_dtle & SATA_DTLE_MASK) + << SATA_DTLE_DATA_SHIFT); + + pch_iobp_update(SATA_IOBP_SP2DTLE_EDGE, + ~(SATA_DTLE_MASK << SATA_DTLE_EDGE_SHIFT), + (config->sata_port2_gen3_dtle & SATA_DTLE_MASK) + << SATA_DTLE_EDGE_SHIFT); + } + if (config->sata_port3_gen3_dtle) { + pch_iobp_update(SATA_IOBP_SP3DTLE_DATA, + ~(SATA_DTLE_MASK << SATA_DTLE_DATA_SHIFT), + (config->sata_port3_gen3_dtle & SATA_DTLE_MASK) + << SATA_DTLE_DATA_SHIFT); + + pch_iobp_update(SATA_IOBP_SP3DTLE_EDGE, + ~(SATA_DTLE_MASK << SATA_DTLE_EDGE_SHIFT), + (config->sata_port3_gen3_dtle & SATA_DTLE_MASK) + << SATA_DTLE_EDGE_SHIFT); + } + + /* + * Additional Programming Requirements for Power Optimizer + */ + + /* Step 1 */ + sir_write(dev, 0x64, 0x883c9003); + + /* Step 2: SIR 68h[15:0] = 880Ah */ + reg32 = sir_read(dev, 0x68); + reg32 &= 0xffff0000; + reg32 |= 0x880a; + sir_write(dev, 0x68, reg32); + + /* Step 3: SIR 60h[3] = 1 */ + reg32 = sir_read(dev, 0x60); + reg32 |= (1 << 3); + sir_write(dev, 0x60, reg32); + + /* Step 4: SIR 60h[0] = 1 */ + reg32 = sir_read(dev, 0x60); + reg32 |= (1 << 0); + sir_write(dev, 0x60, reg32); + + /* Step 5: SIR 60h[1] = 1 */ + reg32 = sir_read(dev, 0x60); + reg32 |= (1 << 1); + sir_write(dev, 0x60, reg32); + + /* Clock Gating */ + sir_write(dev, 0x70, 0x3f00bf1f); + sir_write(dev, 0x54, 0xcf000f0f); + sir_write(dev, 0x58, 0x00190000); + RCBA32_AND_OR(0x333c, 0xffcfffff, 0x00c00000); + + reg32 = pci_read_config32(dev, 0x300); + reg32 |= (1 << 17) | (1 << 16) | (1 << 19); + reg32 |= (1 << 31) | (1 << 30) | (1 << 29); + pci_write_config32(dev, 0x300, reg32); + + reg32 = pci_read_config32(dev, 0x98); + reg32 |= 1 << 29; + pci_write_config32(dev, 0x98, reg32); + + /* Register Lock */ + reg32 = pci_read_config32(dev, 0x9c); + reg32 |= (1 << 31); + pci_write_config32(dev, 0x9c, reg32); +} + +/* + * Set SATA controller mode early so the resource allocator can + * properly assign IO/Memory resources for the controller. + */ +static void sata_enable(struct device *dev) +{ + /* Get the chip configuration */ + const struct soc_intel_broadwell_pch_config *config = config_of(dev); + u16 map = 0x0060; + + map |= (config->sata_port_map ^ 0xf) << 8; + + pci_write_config16(dev, 0x90, map); +} + +static struct device_operations sata_ops = { + .read_resources = pci_dev_read_resources, + .set_resources = pci_dev_set_resources, + .enable_resources = pci_dev_enable_resources, + .init = sata_init, + .enable = sata_enable, + .ops_pci = &pci_dev_ops_pci, +}; + +static const unsigned short pci_device_ids[] = { + 0x9c03, 0x9c05, 0x9c07, 0x9c0f, /* LynxPoint-LP */ + 0x9c83, 0x9c85, 0x282a, 0x9c87, 0x282a, 0x9c8f, /* WildcatPoint */ + 0 +}; + +static const struct pci_driver pch_sata __pci_driver = { + .ops = &sata_ops, + .vendor = PCI_VENDOR_ID_INTEL, + .devices = pci_device_ids, +}; diff --git a/src/soc/intel/broadwell/pch/serialio.c b/src/soc/intel/broadwell/pch/serialio.c new file mode 100644 index 0000000000..d32a27ddca --- /dev/null +++ b/src/soc/intel/broadwell/pch/serialio.c @@ -0,0 +1,295 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <device/mmio.h> +#include <device/pci_ops.h> +#include <acpi/acpi_gnvs.h> +#include <console/console.h> +#include <device/device.h> +#include <device/pci.h> +#include <device/pci_ids.h> +#include <soc/iobp.h> +#include <soc/nvs.h> +#include <soc/pci_devs.h> +#include <soc/pch.h> +#include <soc/ramstage.h> +#include <soc/rcba.h> +#include <soc/serialio.h> +#include <soc/intel/broadwell/pch/chip.h> + +/* Set D3Hot Power State in ACPI mode */ +static void serialio_enable_d3hot(struct resource *res) +{ + u32 reg32 = read32(res2mmio(res, PCH_PCS, 0)); + reg32 |= PCH_PCS_PS_D3HOT; + write32(res2mmio(res, PCH_PCS, 0), reg32); +} + +static int serialio_uart_is_debug(struct device *dev) +{ +#if CONFIG(INTEL_PCH_UART_CONSOLE) + switch (dev->path.pci.devfn) { + case PCH_DEVFN_UART0: /* UART0 */ + return !!(CONFIG_INTEL_PCH_UART_CONSOLE_NUMBER == 0); + case PCH_DEVFN_UART1: /* UART1 */ + return !!(CONFIG_INTEL_PCH_UART_CONSOLE_NUMBER == 1); + } +#endif + return 0; +} + +/* Enable clock in PCI mode */ +static void serialio_enable_clock(struct resource *bar0) +{ + u32 reg32 = read32(res2mmio(bar0, SIO_REG_PPR_CLOCK, 0)); + reg32 |= SIO_REG_PPR_CLOCK_EN; + write32(res2mmio(bar0, SIO_REG_PPR_CLOCK, 0), reg32); +} + +/* Put Serial IO D21:F0-F6 device into desired mode. */ +static void serialio_d21_mode(int sio_index, int int_pin, int acpi_mode) +{ + u32 portctrl = SIO_IOBP_PORTCTRL_PM_CAP_PRSNT; + + /* Snoop select 1. */ + portctrl |= SIO_IOBP_PORTCTRL_SNOOP_SELECT(1); + + /* Set interrupt pin. */ + portctrl |= SIO_IOBP_PORTCTRL_INT_PIN(int_pin); + + if (acpi_mode) { + /* Enable ACPI interrupt mode. */ + portctrl |= SIO_IOBP_PORTCTRL_ACPI_IRQ_EN; + + /* Disable PCI config space. */ + portctrl |= SIO_IOBP_PORTCTRL_PCI_CONF_DIS; + } + + pch_iobp_update(SIO_IOBP_PORTCTRLX(sio_index), 0, portctrl); +} + +/* Put Serial IO D23:F0 device into desired mode. */ +static void serialio_d23_mode(int acpi_mode) +{ + u32 portctrl = 0; + + /* Snoop select 1. */ + pch_iobp_update(SIO_IOBP_PORTCTRL1, 0, + SIO_IOBP_PORTCTRL1_SNOOP_SELECT(1)); + + if (acpi_mode) { + /* Enable ACPI interrupt mode. */ + portctrl |= SIO_IOBP_PORTCTRL0_ACPI_IRQ_EN; + + /* Disable PCI config space. */ + portctrl |= SIO_IOBP_PORTCTRL0_PCI_CONF_DIS; + } + + pch_iobp_update(SIO_IOBP_PORTCTRL0, 0, portctrl); +} + +/* Enable LTR Auto Mode for D21:F1-F6. */ +static void serialio_d21_ltr(struct resource *bar0) +{ + u32 reg; + + /* 1. Program BAR0 + 808h[2] = 0b */ + reg = read32(res2mmio(bar0, SIO_REG_PPR_GEN, 0)); + reg &= ~SIO_REG_PPR_GEN_LTR_MODE_MASK; + write32(res2mmio(bar0, SIO_REG_PPR_GEN, 0), reg); + + /* 2. Program BAR0 + 804h[1:0] = 00b */ + reg = read32(res2mmio(bar0, SIO_REG_PPR_RST, 0)); + reg &= ~SIO_REG_PPR_RST_ASSERT; + write32(res2mmio(bar0, SIO_REG_PPR_RST, 0), reg); + + /* 3. Program BAR0 + 804h[1:0] = 11b */ + reg = read32(res2mmio(bar0, SIO_REG_PPR_RST, 0)); + reg |= SIO_REG_PPR_RST_ASSERT; + write32(res2mmio(bar0, SIO_REG_PPR_RST, 0), reg); + + /* 4. Program BAR0 + 814h[31:0] = 00000000h */ + write32(res2mmio(bar0, SIO_REG_AUTO_LTR, 0), 0); +} + +/* Enable LTR Auto Mode for D23:F0. */ +static void serialio_d23_ltr(struct resource *bar0) +{ + u32 reg; + + /* Program BAR0 + 1008h[2] = 1b */ + reg = read32(res2mmio(bar0, SIO_REG_SDIO_PPR_GEN, 0)); + reg |= SIO_REG_PPR_GEN_LTR_MODE_MASK; + write32(res2mmio(bar0, SIO_REG_SDIO_PPR_GEN, 0), reg); + + /* Program BAR0 + 1010h = 0x00000000 */ + write32(res2mmio(bar0, SIO_REG_SDIO_PPR_SW_LTR, 0), 0); + + /* Program BAR0 + 3Ch[30] = 1b */ + reg = read32(res2mmio(bar0, SIO_REG_SDIO_PPR_CMD12, 0)); + reg |= SIO_REG_SDIO_PPR_CMD12_B30; + write32(res2mmio(bar0, SIO_REG_SDIO_PPR_CMD12, 0), reg); +} + +/* Select I2C voltage of 1.8V or 3.3V. */ +static void serialio_i2c_voltage_sel(struct resource *bar0, u8 voltage) +{ + u32 reg32 = read32(res2mmio(bar0, SIO_REG_PPR_GEN, 0)); + reg32 &= ~SIO_REG_PPR_GEN_VOLTAGE_MASK; + reg32 |= SIO_REG_PPR_GEN_VOLTAGE(voltage); + write32(res2mmio(bar0, SIO_REG_PPR_GEN, 0), reg32); +} + +/* Init sequence to be run once, done as part of D21:F0 (SDMA) init. */ +static void serialio_init_once(int acpi_mode) +{ + if (acpi_mode) { + /* Enable ACPI IRQ for IRQ13, IRQ7, IRQ6, IRQ5 in RCBA. */ + RCBA32_OR(ACPIIRQEN, (1 << 13)|(1 << 7)|(1 << 6)|(1 << 5)); + } + + /* Program IOBP CB000154h[12,9:8,4:0] = 1001100011111b. */ + pch_iobp_update(SIO_IOBP_GPIODF, ~0x0000131f, 0x0000131f); + + /* Program IOBP CB000180h[5:0] = 111111b (undefined register) */ + pch_iobp_update(0xcb000180, ~0x0000003f, 0x0000003f); +} + +static void serialio_init(struct device *dev) +{ + const struct soc_intel_broadwell_pch_config *config = config_of(dev); + struct resource *bar0, *bar1; + int sio_index = -1; + + printk(BIOS_DEBUG, "Initializing Serial IO device\n"); + + /* Ensure memory and bus master are enabled */ + pci_or_config16(dev, PCI_COMMAND, PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY); + + /* Find BAR0 and BAR1 */ + bar0 = find_resource(dev, PCI_BASE_ADDRESS_0); + if (!bar0) + return; + bar1 = find_resource(dev, PCI_BASE_ADDRESS_1); + if (!bar1) + return; + + if (!config->sio_acpi_mode) + serialio_enable_clock(bar0); + + switch (dev->path.pci.devfn) { + case PCH_DEVFN_SDMA: /* SDMA */ + sio_index = SIO_ID_SDMA; + serialio_init_once(config->sio_acpi_mode); + serialio_d21_mode(sio_index, SIO_PIN_INTB, + config->sio_acpi_mode); + break; + case PCH_DEVFN_I2C0: /* I2C0 */ + sio_index = SIO_ID_I2C0; + serialio_d21_ltr(bar0); + serialio_i2c_voltage_sel(bar0, config->sio_i2c0_voltage); + serialio_d21_mode(sio_index, SIO_PIN_INTC, + config->sio_acpi_mode); + break; + case PCH_DEVFN_I2C1: /* I2C1 */ + sio_index = SIO_ID_I2C1; + serialio_d21_ltr(bar0); + serialio_i2c_voltage_sel(bar0, config->sio_i2c1_voltage); + serialio_d21_mode(sio_index, SIO_PIN_INTC, + config->sio_acpi_mode); + break; + case PCH_DEVFN_SPI0: /* SPI0 */ + sio_index = SIO_ID_SPI0; + serialio_d21_ltr(bar0); + serialio_d21_mode(sio_index, SIO_PIN_INTC, + config->sio_acpi_mode); + break; + case PCH_DEVFN_SPI1: /* SPI1 */ + sio_index = SIO_ID_SPI1; + serialio_d21_ltr(bar0); + serialio_d21_mode(sio_index, SIO_PIN_INTC, + config->sio_acpi_mode); + break; + case PCH_DEVFN_UART0: /* UART0 */ + sio_index = SIO_ID_UART0; + if (!serialio_uart_is_debug(dev)) + serialio_d21_ltr(bar0); + serialio_d21_mode(sio_index, SIO_PIN_INTD, + config->sio_acpi_mode); + break; + case PCH_DEVFN_UART1: /* UART1 */ + sio_index = SIO_ID_UART1; + if (!serialio_uart_is_debug(dev)) + serialio_d21_ltr(bar0); + serialio_d21_mode(sio_index, SIO_PIN_INTD, + config->sio_acpi_mode); + break; + case PCH_DEVFN_SDIO: /* SDIO */ + sio_index = SIO_ID_SDIO; + serialio_d23_ltr(bar0); + serialio_d23_mode(config->sio_acpi_mode); + break; + default: + return; + } + + if (config->sio_acpi_mode) { + struct global_nvs *gnvs; + + /* Find ACPI NVS to update BARs */ + gnvs = acpi_get_gnvs(); + if (!gnvs) + return; + + /* Save BAR0 and BAR1 to ACPI NVS */ + gnvs->dev.bar0[sio_index] = (u32)bar0->base; + gnvs->dev.bar1[sio_index] = (u32)bar1->base; + + /* Do not enable UART if it is used as debug port */ + if (!serialio_uart_is_debug(dev)) + gnvs->dev.enable[sio_index] = 1; + + /* Put device in D3hot state via BAR1 */ + if (dev->path.pci.devfn != PCH_DEVFN_SDMA) + serialio_enable_d3hot(bar1); /* all but SDMA */ + } +} + +static void serialio_set_resources(struct device *dev) +{ + pci_dev_set_resources(dev); + +#if CONFIG(INTEL_PCH_UART_CONSOLE) + /* Update UART base address if used for debug */ + if (serialio_uart_is_debug(dev)) { + struct resource *res = find_resource(dev, PCI_BASE_ADDRESS_0); + if (res) + uartmem_setbaseaddr(res->base); + } +#endif +} + +static struct device_operations device_ops = { + .read_resources = &pci_dev_read_resources, + .set_resources = &serialio_set_resources, + .enable_resources = &pci_dev_enable_resources, + .init = &serialio_init, + .ops_pci = &pci_dev_ops_pci, +}; + +static const unsigned short pci_device_ids[] = { + 0x9c60, 0x9ce0, /* 0:15.0 - SDMA */ + 0x9c61, 0x9ce1, /* 0:15.1 - I2C0 */ + 0x9c62, 0x9ce2, /* 0:15.2 - I2C1 */ + 0x9c65, 0x9ce5, /* 0:15.3 - SPI0 */ + 0x9c66, 0x9ce6, /* 0:15.4 - SPI1 */ + 0x9c63, 0x9ce3, /* 0:15.5 - UART0 */ + 0x9c64, 0x9ce4, /* 0:15.6 - UART1 */ + 0x9c35, 0x9cb5, /* 0:17.0 - SDIO */ + 0 +}; + +static const struct pci_driver pch_pcie __pci_driver = { + .ops = &device_ops, + .vendor = PCI_VENDOR_ID_INTEL, + .devices = pci_device_ids, +}; diff --git a/src/soc/intel/broadwell/pch/smbus.c b/src/soc/intel/broadwell/pch/smbus.c new file mode 100644 index 0000000000..70655fc891 --- /dev/null +++ b/src/soc/intel/broadwell/pch/smbus.c @@ -0,0 +1,94 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <device/device.h> +#include <device/path.h> +#include <device/smbus.h> +#include <device/pci.h> +#include <device/pci_ids.h> +#include <device/pci_ops.h> +#include <soc/iomap.h> +#include <soc/ramstage.h> +#include <soc/smbus.h> +#include <device/smbus_host.h> + +static void pch_smbus_init(struct device *dev) +{ + struct resource *res; + u16 reg16; + + /* Enable clock gating */ + /* FIXME: Using 32-bit ops with a 16-bit variable is a bug! These should be 16-bit! */ + reg16 = pci_read_config32(dev, 0x80); + reg16 &= ~((1 << 8)|(1 << 10)|(1 << 12)|(1 << 14)); + pci_write_config32(dev, 0x80, reg16); + + /* Set Receive Slave Address */ + res = find_resource(dev, PCI_BASE_ADDRESS_4); + if (res) + smbus_set_slave_addr(res->base, SMBUS_SLAVE_ADDR); +} + +static int lsmbus_read_byte(struct device *dev, u8 address) +{ + u16 device; + struct resource *res; + struct bus *pbus; + + device = dev->path.i2c.device; + pbus = get_pbus_smbus(dev); + res = find_resource(pbus->dev, PCI_BASE_ADDRESS_4); + + return do_smbus_read_byte(res->base, device, address); +} + +static int lsmbus_write_byte(struct device *dev, u8 address, u8 data) +{ + u16 device; + struct resource *res; + struct bus *pbus; + + device = dev->path.i2c.device; + pbus = get_pbus_smbus(dev); + res = find_resource(pbus->dev, PCI_BASE_ADDRESS_4); + return do_smbus_write_byte(res->base, device, address, data); +} + +static struct smbus_bus_operations lops_smbus_bus = { + .read_byte = lsmbus_read_byte, + .write_byte = lsmbus_write_byte, +}; + +static void smbus_read_resources(struct device *dev) +{ + struct resource *res = new_resource(dev, PCI_BASE_ADDRESS_4); + res->base = SMBUS_BASE_ADDRESS; + res->size = 32; + res->limit = res->base + res->size - 1; + res->flags = IORESOURCE_IO | IORESOURCE_FIXED | IORESOURCE_RESERVE | + IORESOURCE_STORED | IORESOURCE_ASSIGNED; + + /* Also add MMIO resource */ + res = pci_get_resource(dev, PCI_BASE_ADDRESS_0); +} + +static struct device_operations smbus_ops = { + .read_resources = smbus_read_resources, + .set_resources = pci_dev_set_resources, + .enable_resources = pci_dev_enable_resources, + .scan_bus = scan_smbus, + .init = pch_smbus_init, + .ops_smbus_bus = &lops_smbus_bus, + .ops_pci = &pci_dev_ops_pci, +}; + +static const unsigned short pci_device_ids[] = { + 0x9c22, /* LynxPoint */ + 0x9ca2, /* WildcatPoint */ + 0 +}; + +static const struct pci_driver pch_smbus __pci_driver = { + .ops = &smbus_ops, + .vendor = PCI_VENDOR_ID_INTEL, + .devices = pci_device_ids, +}; diff --git a/src/soc/intel/broadwell/pch/smi.c b/src/soc/intel/broadwell/pch/smi.c new file mode 100644 index 0000000000..d7704fd8fa --- /dev/null +++ b/src/soc/intel/broadwell/pch/smi.c @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <device/device.h> +#include <device/pci.h> +#include <console/console.h> +#include <arch/io.h> +#include <cpu/x86/smm.h> +#include <cpu/intel/smm_reloc.h> +#include <soc/iomap.h> +#include <soc/pch.h> +#include <soc/pm.h> + +void smm_southbridge_clear_state(void) +{ + u32 smi_en; + + printk(BIOS_DEBUG, "Initializing Southbridge SMI..."); + printk(BIOS_SPEW, " ... pmbase = 0x%04x\n", ACPI_BASE_ADDRESS); + + smi_en = inl(ACPI_BASE_ADDRESS + SMI_EN); + if (smi_en & APMC_EN) { + printk(BIOS_INFO, "SMI# handler already enabled?\n"); + return; + } + + printk(BIOS_DEBUG, "\n"); + + /* Dump and clear status registers */ + clear_smi_status(); + clear_pm1_status(); + clear_tco_status(); + clear_gpe_status(); +} + +static void smm_southbridge_enable(uint16_t pm1_events) +{ + printk(BIOS_DEBUG, "Enabling SMIs.\n"); + /* Configure events */ + enable_pm1(pm1_events); + disable_gpe(PME_B0_EN); + + /* Enable SMI generation: + * - on APMC writes (io 0xb2) + * - on writes to SLP_EN (sleep states) + * - on writes to GBL_RLS (bios commands) + * No SMIs: + * - on microcontroller writes (io 0x62/0x66) + * - on TCO events + */ + enable_smi(APMC_EN | SLP_SMI_EN | GBL_SMI_EN | EOS); +} + +void global_smi_enable(void) +{ + smm_southbridge_enable(PWRBTN_EN | GBL_EN); +} diff --git a/src/soc/intel/broadwell/pch/smihandler.c b/src/soc/intel/broadwell/pch/smihandler.c new file mode 100644 index 0000000000..fd5d4522fa --- /dev/null +++ b/src/soc/intel/broadwell/pch/smihandler.c @@ -0,0 +1,569 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <delay.h> +#include <types.h> +#include <arch/io.h> +#include <device/mmio.h> +#include <device/pci_ops.h> +#include <console/console.h> +#include <cpu/x86/cache.h> +#include <device/pci_def.h> +#include <cpu/x86/smm.h> +#include <cpu/intel/em64t101_save_state.h> +#include <spi-generic.h> +#include <elog.h> +#include <halt.h> +#include <option.h> +#include <soc/lpc.h> +#include <soc/nvs.h> +#include <soc/pci_devs.h> +#include <soc/pm.h> +#include <soc/rcba.h> +#include <soc/xhci.h> +#include <drivers/intel/gma/i915_reg.h> +#include <smmstore.h> + +static u8 smm_initialized = 0; + +int southbridge_io_trap_handler(int smif) +{ + switch (smif) { + case 0x32: + printk(BIOS_DEBUG, "OS Init\n"); + /* gnvs->smif: + * On success, the IO Trap Handler returns 0 + * On failure, the IO Trap Handler returns a value != 0 + */ + gnvs->smif = 0; + return 1; /* IO trap handled */ + } + + /* Not handled */ + return 0; +} + +/** + * @brief Set the EOS bit + */ +void southbridge_smi_set_eos(void) +{ + enable_smi(EOS); +} + +static void busmaster_disable_on_bus(int bus) +{ + int slot, func; + unsigned int val; + unsigned char hdr; + + for (slot = 0; slot < 0x20; slot++) { + for (func = 0; func < 8; func++) { + pci_devfn_t dev = PCI_DEV(bus, slot, func); + + val = pci_read_config32(dev, PCI_VENDOR_ID); + + if (val == 0xffffffff || val == 0x00000000 || + val == 0x0000ffff || val == 0xffff0000) + continue; + + /* Disable Bus Mastering for this one device */ + pci_and_config16(dev, PCI_COMMAND, ~PCI_COMMAND_MASTER); + + /* If this is a bridge, then follow it. */ + hdr = pci_read_config8(dev, PCI_HEADER_TYPE); + hdr &= 0x7f; + if (hdr == PCI_HEADER_TYPE_BRIDGE || + hdr == PCI_HEADER_TYPE_CARDBUS) { + unsigned int buses; + buses = pci_read_config32(dev, PCI_PRIMARY_BUS); + busmaster_disable_on_bus((buses >> 8) & 0xff); + } + } + } +} + +/* + * Turn off the backlight if it is on, and wait for the specified + * backlight off delay. This will allow panel power timings to meet + * spec and prevent brief garbage on the screen when turned off + * during firmware with power button triggered SMI. + */ +static void backlight_off(void) +{ + void *reg_base; + uint32_t pp_ctrl; + uint32_t bl_off_delay; + + reg_base = (void *)((uintptr_t)pci_read_config32(SA_DEV_IGD, + PCI_BASE_ADDRESS_0) & ~0xf); + + /* Validate pointer before using it */ + if (smm_points_to_smram(reg_base, PCH_PP_OFF_DELAYS + sizeof(uint32_t))) + return; + + /* Check if backlight is enabled */ + pp_ctrl = read32(reg_base + PCH_PP_CONTROL); + if (!(pp_ctrl & EDP_BLC_ENABLE)) + return; + + /* Enable writes to this register */ + pp_ctrl &= ~PANEL_UNLOCK_MASK; + pp_ctrl |= PANEL_UNLOCK_REGS; + + /* Turn off backlight */ + pp_ctrl &= ~EDP_BLC_ENABLE; + + write32(reg_base + PCH_PP_CONTROL, pp_ctrl); + read32(reg_base + PCH_PP_CONTROL); + + /* Read backlight off delay in 100us units */ + bl_off_delay = read32(reg_base + PCH_PP_OFF_DELAYS); + bl_off_delay &= PANEL_LIGHT_OFF_DELAY_MASK; + bl_off_delay *= 100; + + /* Wait for backlight to turn off */ + udelay(bl_off_delay); + + printk(BIOS_INFO, "Backlight turned off\n"); +} + +static void southbridge_smi_sleep(void) +{ + u8 reg8; + u32 reg32; + u8 slp_typ; + u8 s5pwr = CONFIG_MAINBOARD_POWER_FAILURE_STATE; + u16 pmbase = get_pmbase(); + + /* save and recover RTC port values */ + u8 tmp70, tmp72; + tmp70 = inb(0x70); + tmp72 = inb(0x72); + get_option(&s5pwr, "power_on_after_fail"); + outb(tmp70, 0x70); + outb(tmp72, 0x72); + + /* First, disable further SMIs */ + disable_smi(SLP_SMI_EN); + + /* Figure out SLP_TYP */ + reg32 = inl(pmbase + PM1_CNT); + printk(BIOS_SPEW, "SMI#: SLP = 0x%08x\n", reg32); + slp_typ = acpi_sleep_from_pm1(reg32); + + /* Do any mainboard sleep handling */ + mainboard_smi_sleep(slp_typ); + + /* USB sleep preparations */ + usb_xhci_sleep_prepare(PCH_DEV_XHCI, slp_typ); + + /* Log S3, S4, and S5 entry */ + if (slp_typ >= ACPI_S3) + elog_gsmi_add_event_byte(ELOG_TYPE_ACPI_ENTER, slp_typ); + + /* Clear pending GPE events */ + clear_gpe_status(); + + /* Next, do the deed. + */ + + switch (slp_typ) { + case ACPI_S0: + printk(BIOS_DEBUG, "SMI#: Entering S0 (On)\n"); + break; + case ACPI_S1: + printk(BIOS_DEBUG, "SMI#: Entering S1 (Assert STPCLK#)\n"); + break; + case ACPI_S3: + printk(BIOS_DEBUG, "SMI#: Entering S3 (Suspend-To-RAM)\n"); + + /* Invalidate the cache before going to S3 */ + wbinvd(); + break; + case ACPI_S4: + printk(BIOS_DEBUG, "SMI#: Entering S4 (Suspend-To-Disk)\n"); + break; + case ACPI_S5: + printk(BIOS_DEBUG, "SMI#: Entering S5 (Soft Power off)\n"); + + /* Turn off backlight if needed */ + backlight_off(); + + /* Disable all GPE */ + disable_all_gpe(); + + /* Always set the flag in case CMOS was changed on runtime. For + * "KEEP", switch to "OFF" - KEEP is software emulated + */ + reg8 = pci_read_config8(PCH_DEV_LPC, GEN_PMCON_3); + if (s5pwr == MAINBOARD_POWER_ON) + reg8 &= ~1; + else + reg8 |= 1; + pci_write_config8(PCH_DEV_LPC, GEN_PMCON_3, reg8); + + /* also iterates over all bridges on bus 0 */ + busmaster_disable_on_bus(0); + break; + default: + printk(BIOS_DEBUG, "SMI#: ERROR: SLP_TYP reserved\n"); + break; + } + + /* + * Write back to the SLP register to cause the originally intended + * event again. We need to set BIT13 (SLP_EN) though to make the + * sleep happen. + */ + enable_pm1_control(SLP_EN); + + /* Make sure to stop executing code here for S3/S4/S5 */ + if (slp_typ >= ACPI_S3) + halt(); + + /* + * In most sleep states, the code flow of this function ends at + * the line above. However, if we entered sleep state S1 and wake + * up again, we will continue to execute code in this function. + */ + reg32 = inl(pmbase + PM1_CNT); + if (reg32 & SCI_EN) { + /* The OS is not an ACPI OS, so we set the state to S0 */ + disable_pm1_control(SLP_EN | SLP_TYP); + } +} + +/* + * Look for Synchronous IO SMI and use save state from that + * core in case we are not running on the same core that + * initiated the IO transaction. + */ +static em64t101_smm_state_save_area_t *smi_apmc_find_state_save(u8 cmd) +{ + em64t101_smm_state_save_area_t *state; + int node; + + /* Check all nodes looking for the one that issued the IO */ + for (node = 0; node < CONFIG_MAX_CPUS; node++) { + state = smm_get_save_state(node); + + /* Check for Synchronous IO (bit0 == 1) */ + if (!(state->io_misc_info & (1 << 0))) + continue; + + /* Make sure it was a write (bit4 == 0) */ + if (state->io_misc_info & (1 << 4)) + continue; + + /* Check for APMC IO port */ + if (((state->io_misc_info >> 16) & 0xff) != APM_CNT) + continue; + + /* Check AX against the requested command */ + if ((state->rax & 0xff) != cmd) + continue; + + return state; + } + + return NULL; +} + +static void southbridge_smi_gsmi(void) +{ + u32 *ret, *param; + u8 sub_command; + em64t101_smm_state_save_area_t *io_smi = + smi_apmc_find_state_save(APM_CNT_ELOG_GSMI); + + if (!io_smi) + return; + + /* Command and return value in EAX */ + ret = (u32 *)&io_smi->rax; + sub_command = (u8)(*ret >> 8); + + /* Parameter buffer in EBX */ + param = (u32 *)&io_smi->rbx; + + /* drivers/elog/gsmi.c */ + *ret = gsmi_exec(sub_command, param); +} + +static void southbridge_smi_store(void) +{ + u8 sub_command, ret; + em64t101_smm_state_save_area_t *io_smi = + smi_apmc_find_state_save(APM_CNT_SMMSTORE); + uint32_t reg_ebx; + + if (!io_smi) + return; + /* Command and return value in EAX */ + sub_command = (io_smi->rax >> 8) & 0xff; + + /* Parameter buffer in EBX */ + reg_ebx = io_smi->rbx; + + /* drivers/smmstore/smi.c */ + ret = smmstore_exec(sub_command, (void *)reg_ebx); + io_smi->rax = ret; +} + +static void southbridge_smi_apmc(void) +{ + u8 reg8; + em64t101_smm_state_save_area_t *state; + + /* Emulate B2 register as the FADT / Linux expects it */ + + reg8 = inb(APM_CNT); + switch (reg8) { + case APM_CNT_CST_CONTROL: + printk(BIOS_DEBUG, "C-state control\n"); + break; + case APM_CNT_PST_CONTROL: + printk(BIOS_DEBUG, "P-state control\n"); + break; + case APM_CNT_ACPI_DISABLE: + disable_pm1_control(SCI_EN); + printk(BIOS_DEBUG, "SMI#: ACPI disabled.\n"); + break; + case APM_CNT_ACPI_ENABLE: + enable_pm1_control(SCI_EN); + printk(BIOS_DEBUG, "SMI#: ACPI enabled.\n"); + break; + case APM_CNT_GNVS_UPDATE: + if (smm_initialized) { + printk(BIOS_DEBUG, + "SMI#: SMM structures already initialized!\n"); + return; + } + state = smi_apmc_find_state_save(reg8); + if (state) { + /* EBX in the state save contains the GNVS pointer */ + gnvs = (struct global_nvs *)((u32)state->rbx); + if (smm_points_to_smram(gnvs, sizeof(*gnvs))) { + printk(BIOS_ERR, "SMI#: ERROR: GNVS overlaps SMM\n"); + return; + } + smm_initialized = 1; + printk(BIOS_DEBUG, "SMI#: Setting GNVS to %p\n", gnvs); + } + break; + case APM_CNT_ELOG_GSMI: + if (CONFIG(ELOG_GSMI)) + southbridge_smi_gsmi(); + break; + case APM_CNT_SMMSTORE: + if (CONFIG(SMMSTORE)) + southbridge_smi_store(); + break; + } + + mainboard_smi_apmc(reg8); +} + +static void southbridge_smi_pm1(void) +{ + u16 pm1_sts = clear_pm1_status(); + + /* While OSPM is not active, poweroff immediately + * on a power button event. + */ + if (pm1_sts & PWRBTN_STS) { + /* power button pressed */ + elog_gsmi_add_event(ELOG_TYPE_POWER_BUTTON); + disable_pm1_control(-1UL); + enable_pm1_control(SLP_EN | (SLP_TYP_S5 << 10)); + } +} + +static void southbridge_smi_gpe0(void) +{ + clear_gpe_status(); +} + +static void southbridge_smi_gpi(void) +{ + mainboard_smi_gpi(clear_alt_smi_status()); + + /* Clear again after mainboard handler */ + clear_alt_smi_status(); +} + +static void southbridge_smi_mc(void) +{ + u32 reg32 = inl(get_pmbase() + SMI_EN); + + /* Are microcontroller SMIs enabled? */ + if ((reg32 & MCSMI_EN) == 0) + return; + + printk(BIOS_DEBUG, "Microcontroller SMI.\n"); +} + +static void southbridge_smi_tco(void) +{ + u32 tco_sts = clear_tco_status(); + + /* Any TCO event? */ + if (!tco_sts) + return; + + // BIOSWR + if (tco_sts & (1 << 8)) { + u8 bios_cntl = pci_read_config16(PCH_DEV_LPC, BIOS_CNTL); + + if (bios_cntl & 1) { + /* + * BWE is RW, so the SMI was caused by a + * write to BWE, not by a write to the BIOS + * + * This is the place where we notice someone + * is trying to tinker with the BIOS. We are + * trying to be nice and just ignore it. A more + * resolute answer would be to power down the + * box. + */ + printk(BIOS_DEBUG, "Switching back to RO\n"); + pci_write_config32(PCH_DEV_LPC, BIOS_CNTL, (bios_cntl & ~1)); + } /* No else for now? */ + } else if (tco_sts & (1 << 3)) { /* TIMEOUT */ + /* Handle TCO timeout */ + printk(BIOS_DEBUG, "TCO Timeout.\n"); + } +} + +static void southbridge_smi_periodic(void) +{ + u32 reg32 = inl(get_pmbase() + SMI_EN); + + /* Are periodic SMIs enabled? */ + if ((reg32 & PERIODIC_EN) == 0) + return; + + printk(BIOS_DEBUG, "Periodic SMI.\n"); +} + +static void southbridge_smi_monitor(void) +{ +#define IOTRAP(x) (trap_sts & (1 << x)) + u32 trap_sts, trap_cycle; + u32 mask = 0; + int i; + + trap_sts = RCBA32(0x1e00); // TRSR - Trap Status Register + RCBA32(0x1e00) = trap_sts; // Clear trap(s) in TRSR + + trap_cycle = RCBA32(0x1e10); + for (i = 16; i < 20; i++) { + if (trap_cycle & (1 << i)) + mask |= (0xff << ((i - 16) << 2)); + } + + /* IOTRAP(3) SMI function call */ + if (IOTRAP(3)) { + if (gnvs && gnvs->smif) + io_trap_handler(gnvs->smif); // call function smif + return; + } + + /* IOTRAP(2) currently unused + * IOTRAP(1) currently unused */ + + /* IOTRAP(0) SMIC */ + if (IOTRAP(0)) { + // It's a write + if (!(trap_cycle & (1 << 24))) { + printk(BIOS_DEBUG, "SMI1 command\n"); + (void)RCBA32(0x1e18); + // data = RCBA32(0x1e18); + // data &= mask; + // if (smi1) + // southbridge_smi_command(data); + // return; + } + // Fall through to debug + } + + printk(BIOS_DEBUG, " trapped io address = 0x%x\n", + trap_cycle & 0xfffc); + for (i = 0; i < 4; i++) + if (IOTRAP(i)) + printk(BIOS_DEBUG, " TRAP = %d\n", i); + printk(BIOS_DEBUG, " AHBE = %x\n", (trap_cycle >> 16) & 0xf); + printk(BIOS_DEBUG, " MASK = 0x%08x\n", mask); + printk(BIOS_DEBUG, " read/write: %s\n", + (trap_cycle & (1 << 24)) ? "read" : "write"); + + if (!(trap_cycle & (1 << 24))) { + /* Write Cycle */ + printk(BIOS_DEBUG, " iotrap written data = 0x%08x\n", RCBA32(0x1e18)); + } +#undef IOTRAP +} + +typedef void (*smi_handler_t)(void); + +static smi_handler_t southbridge_smi[32] = { + NULL, // [0] reserved + NULL, // [1] reserved + NULL, // [2] BIOS_STS + NULL, // [3] LEGACY_USB_STS + southbridge_smi_sleep, // [4] SLP_SMI_STS + southbridge_smi_apmc, // [5] APM_STS + NULL, // [6] SWSMI_TMR_STS + NULL, // [7] reserved + southbridge_smi_pm1, // [8] PM1_STS + southbridge_smi_gpe0, // [9] GPE0_STS + southbridge_smi_gpi, // [10] GPI_STS + southbridge_smi_mc, // [11] MCSMI_STS + NULL, // [12] DEVMON_STS + southbridge_smi_tco, // [13] TCO_STS + southbridge_smi_periodic, // [14] PERIODIC_STS + NULL, // [15] SERIRQ_SMI_STS + NULL, // [16] SMBUS_SMI_STS + NULL, // [17] LEGACY_USB2_STS + NULL, // [18] INTEL_USB2_STS + NULL, // [19] reserved + NULL, // [20] PCI_EXP_SMI_STS + southbridge_smi_monitor, // [21] MONITOR_STS + NULL, // [22] reserved + NULL, // [23] reserved + NULL, // [24] reserved + NULL, // [25] EL_SMI_STS + NULL, // [26] SPI_STS + NULL, // [27] reserved + NULL, // [28] reserved + NULL, // [29] reserved + NULL, // [30] reserved + NULL // [31] reserved +}; + +/** + * @brief Interrupt handler for SMI# + */ +void southbridge_smi_handler(void) +{ + int i; + u32 smi_sts; + + /* We need to clear the SMI status registers, or we won't see what's + * happening in the following calls. + */ + smi_sts = clear_smi_status(); + + /* Call SMI sub handler for each of the status bits */ + for (i = 0; i < 31; i++) { + if (smi_sts & (1 << i)) { + if (southbridge_smi[i]) { + southbridge_smi[i](); + } else { + printk(BIOS_DEBUG, + "SMI_STS[%d] occurred, but no " + "handler available.\n", i); + } + } + } +} diff --git a/src/soc/intel/broadwell/pch/uart.c b/src/soc/intel/broadwell/pch/uart.c new file mode 100644 index 0000000000..6b0da2d65c --- /dev/null +++ b/src/soc/intel/broadwell/pch/uart.c @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <device/pci_def.h> +#include <reg_script.h> +#include <stdint.h> +#include <uart8250.h> +#include <soc/iobp.h> +#include <soc/serialio.h> + +const struct reg_script uart_init[] = { + /* Set MMIO BAR */ + REG_PCI_WRITE32(PCI_BASE_ADDRESS_0, CONFIG_TTYS0_BASE), + /* Enable Memory access and Bus Master */ + REG_PCI_OR32(PCI_COMMAND, PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER), + /* Initialize LTR */ + REG_MMIO_RMW32(CONFIG_TTYS0_BASE + SIO_REG_PPR_GEN, + ~SIO_REG_PPR_GEN_LTR_MODE_MASK, 0), + REG_MMIO_RMW32(CONFIG_TTYS0_BASE + SIO_REG_PPR_RST, + ~(SIO_REG_PPR_RST_ASSERT), 0), + /* Take UART out of reset */ + REG_MMIO_OR32(CONFIG_TTYS0_BASE + SIO_REG_PPR_RST, + SIO_REG_PPR_RST_ASSERT), + /* Set M and N divisor inputs and enable clock */ + REG_MMIO_WRITE32(CONFIG_TTYS0_BASE + SIO_REG_PPR_CLOCK, + SIO_REG_PPR_CLOCK_EN | SIO_REG_PPR_CLOCK_UPDATE | + (SIO_REG_PPR_CLOCK_N_DIV << 16) | + (SIO_REG_PPR_CLOCK_M_DIV << 1)), + REG_SCRIPT_END +}; + +void pch_uart_init(void) +{ + /* Program IOBP CB000154h[12,9:8,4:0] = 1001100011111b */ + u32 gpiodf = 0x131f; +#if defined(__SIMPLE_DEVICE__) + pci_devfn_t dev; +#else + struct device *dev; +#endif + + /* Put UART in byte access mode for 16550 compatibility */ + switch (CONFIG_INTEL_PCH_UART_CONSOLE_NUMBER) { + case 0: + dev = PCH_DEV_UART0; + gpiodf |= SIO_IOBP_GPIODF_UART0_BYTE_ACCESS; + break; + case 1: + dev = PCH_DEV_UART1; + gpiodf |= SIO_IOBP_GPIODF_UART1_BYTE_ACCESS; + break; + default: + return; + } + + /* Program IOBP GPIODF */ + pch_iobp_update(SIO_IOBP_GPIODF, ~gpiodf, gpiodf); + + /* Program IOBP CB000180h[5:0] = 111111b (undefined register) */ + pch_iobp_update(0xcb000180, ~0x0000003f, 0x0000003f); + + /* Initialize chipset uart interface */ + reg_script_run_on_dev(dev, uart_init); + + /* + * Perform standard UART initialization + * Divisor 1 is 115200 BAUD + */ + uart8250_mem_init(CONFIG_TTYS0_BASE, 1); +} diff --git a/src/soc/intel/broadwell/pch/usb_debug.c b/src/soc/intel/broadwell/pch/usb_debug.c new file mode 100644 index 0000000000..0fda336d14 --- /dev/null +++ b/src/soc/intel/broadwell/pch/usb_debug.c @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +// Use simple device model for this file even in ramstage +#define __SIMPLE_DEVICE__ + +#include <device/pci_ehci.h> +#include <device/pci_def.h> + +pci_devfn_t pci_ehci_dbg_dev(unsigned int hcd_idx) +{ + return PCI_DEV(0, 0x1d, 0); +} + +void pci_ehci_dbg_set_port(pci_devfn_t dev, unsigned int port) +{ + /* Hardcoded to physical port 1 */ +} diff --git a/src/soc/intel/broadwell/pch/xhci.c b/src/soc/intel/broadwell/pch/xhci.c new file mode 100644 index 0000000000..baaf5ba6e6 --- /dev/null +++ b/src/soc/intel/broadwell/pch/xhci.c @@ -0,0 +1,219 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <delay.h> +#include <device/device.h> +#include <device/pci.h> +#include <device/pci_ids.h> +#include <acpi/acpi.h> +#include <device/mmio.h> +#include <device/pci_ops.h> +#include <soc/ramstage.h> +#include <soc/xhci.h> +#include <soc/cpu.h> + +#ifdef __SIMPLE_DEVICE__ +static u8 *usb_xhci_mem_base(pci_devfn_t dev) +{ + u32 mem_base = pci_read_config32(dev, PCI_BASE_ADDRESS_0); + + /* Check if the controller is disabled or not present */ + if (mem_base == 0 || mem_base == 0xffffffff) + return 0; + + return (u8 *)(mem_base & ~0xf); +} + +static int usb_xhci_port_count_usb3(pci_devfn_t dev) +{ + /* PCH-LP has 4 SS ports */ + return 4; +} + +static void usb_xhci_reset_status_usb3(u8 *mem_base, int port) +{ + u8 *portsc = mem_base + XHCI_USB3_PORTSC(port); + u32 status = read32(portsc); + /* Do not set Port Enabled/Disabled field */ + status &= ~XHCI_USB3_PORTSC_PED; + /* Clear all change status bits */ + status |= XHCI_USB3_PORTSC_CHST; + write32(portsc, status); +} + +static void usb_xhci_reset_port_usb3(u8 *mem_base, int port) +{ + u8 *portsc = mem_base + XHCI_USB3_PORTSC(port); + write32(portsc, read32(portsc) | XHCI_USB3_PORTSC_WPR); +} + +#define XHCI_RESET_DELAY_US 1000 /* 1ms */ +#define XHCI_RESET_TIMEOUT 100 /* 100ms */ + +/* + * 1) Wait until port is done polling + * 2) If port is disconnected + * a) Issue warm port reset + * b) Poll for warm reset complete + * c) Write 1 to port change status bits + */ +static void usb_xhci_reset_usb3(pci_devfn_t dev, int all) +{ + u32 status, port_disabled; + int timeout, port; + int port_count = usb_xhci_port_count_usb3(dev); + u8 *mem_base = usb_xhci_mem_base(dev); + + if (!mem_base || !port_count) + return; + + /* Get mask of disabled ports */ + port_disabled = pci_read_config32(dev, XHCI_USB3PDO); + + /* Wait until all enabled ports are done polling */ + for (timeout = XHCI_RESET_TIMEOUT; timeout; timeout--) { + int complete = 1; + for (port = 0; port < port_count; port++) { + /* Skip disabled ports */ + if (port_disabled & (1 << port)) + continue; + /* Read port link status field */ + status = read32(mem_base + XHCI_USB3_PORTSC(port)); + status &= XHCI_USB3_PORTSC_PLS; + if (status == XHCI_PLSR_POLLING) + complete = 0; + } + /* Exit if all ports not polling */ + if (complete) + break; + udelay(XHCI_RESET_DELAY_US); + } + + /* Reset all requested ports */ + for (port = 0; port < port_count; port++) { + u8 *portsc = mem_base + XHCI_USB3_PORTSC(port); + /* Skip disabled ports */ + if (port_disabled & (1 << port)) + continue; + status = read32(portsc) & XHCI_USB3_PORTSC_PLS; + /* Reset all or only disconnected ports */ + if (all || (status == XHCI_PLSR_RXDETECT || + status == XHCI_PLSR_POLLING)) + usb_xhci_reset_port_usb3(mem_base, port); + else + port_disabled |= 1 << port; + } + + /* Wait for warm reset complete on all reset ports */ + for (timeout = XHCI_RESET_TIMEOUT; timeout; timeout--) { + int complete = 1; + for (port = 0; port < port_count; port++) { + /* Only check ports that were reset */ + if (port_disabled & (1 << port)) + continue; + /* Check if warm reset is complete */ + status = read32(mem_base + XHCI_USB3_PORTSC(port)); + if (!(status & XHCI_USB3_PORTSC_WRC)) + complete = 0; + } + /* Check for warm reset complete in any port */ + if (complete) + break; + udelay(XHCI_RESET_DELAY_US); + } + + /* Clear port change status bits */ + for (port = 0; port < port_count; port++) + usb_xhci_reset_status_usb3(mem_base, port); +} + +/* Handler for XHCI controller on entry to S3/S4/S5 */ +void usb_xhci_sleep_prepare(pci_devfn_t dev, u8 slp_typ) +{ + u32 reg32; + u8 *mem_base = usb_xhci_mem_base(dev); + u8 is_broadwell = !!(cpu_family_model() == BROADWELL_FAMILY_ULT); + + if (!mem_base || slp_typ < ACPI_S3) + return; + + /* Set D0 state */ + pci_update_config16(dev, XHCI_PWR_CTL_STS, ~XHCI_PWR_CTL_SET_MASK, XHCI_PWR_CTL_SET_D0); + + if (!is_broadwell) { + /* This WA is only for lpt */ + + /* Clear PCI 0xB0[14:13] */ + pci_and_config32(dev, 0xb0, ~((1 << 14) | (1 << 13))); + + /* Clear MMIO 0x816c[14,2] */ + reg32 = read32(mem_base + 0x816c); + reg32 &= ~((1 << 14) | (1 << 2)); + write32(mem_base + 0x816c, reg32); + + /* Reset disconnected USB3 ports */ + usb_xhci_reset_usb3(dev, 0); + + /* Set MMIO 0x80e0[15] */ + reg32 = read32(mem_base + 0x80e0); + reg32 |= (1 << 15); + write32(mem_base + 0x80e0, reg32); + } else { + /* + * Clear port change status bits. Clearing CSC alone seemed to + * fix wakeup from S3 if entering USB compliance state even if + * bit wasn't set on the port. + */ + int port; + for (port = 0; port < usb_xhci_port_count_usb3(dev); port++) + usb_xhci_reset_status_usb3(mem_base, port); + } + + reg32 = read32(mem_base + 0x8154); + reg32 &= ~(1 << 31); + write32(mem_base + 0x8154, reg32); + + /* Set D3Hot state and enable PME */ + pci_or_config16(dev, XHCI_PWR_CTL_STS, XHCI_PWR_CTL_SET_D3); + pci_or_config16(dev, XHCI_PWR_CTL_STS, XHCI_PWR_CTL_STATUS_PME); + pci_or_config16(dev, XHCI_PWR_CTL_STS, XHCI_PWR_CTL_ENABLE_PME); +} +#else /* !__SIMPLE_DEVICE__ */ + +static void xhci_init(struct device *dev) +{ + struct resource *res = find_resource(dev, PCI_BASE_ADDRESS_0); + u16 reg16; + u32 reg32; + + /* Ensure controller is in D0 state */ + reg16 = pci_read_config16(dev, XHCI_PWR_CTL_STS); + reg16 &= ~XHCI_PWR_CTL_SET_MASK; + reg16 |= XHCI_PWR_CTL_SET_D0; + pci_write_config16(dev, XHCI_PWR_CTL_STS, reg16); + + /* Disable Compliance Mode Entry */ + reg32 = read32(res2mmio(res, 0x80ec, 0)); + reg32 |= (1 << 0); + write32(res2mmio(res, 0x80ec, 0), reg32); +} + +static struct device_operations usb_xhci_ops = { + .read_resources = pci_dev_read_resources, + .set_resources = pci_dev_set_resources, + .enable_resources = pci_dev_enable_resources, + .ops_pci = &pci_dev_ops_pci, + .init = xhci_init, +}; + +static const unsigned short pci_device_ids[] = { + 0x9c31, /* LynxPoint-LP */ + 0x9cb1, /* WildcatPoint */ + 0 +}; + +static const struct pci_driver pch_usb_xhci __pci_driver = { + .ops = &usb_xhci_ops, + .vendor = PCI_VENDOR_ID_INTEL, + .devices = pci_device_ids, +}; +#endif /* !__SIMPLE_DEVICE__ */ |