summaryrefslogtreecommitdiff
path: root/src/soc/intel/broadwell/pch
diff options
context:
space:
mode:
authorAngel Pons <th3fanbus@gmail.com>2020-10-23 21:37:21 +0200
committerAngel Pons <th3fanbus@gmail.com>2020-10-30 00:45:51 +0000
commitc200e8c7cdebed98860a771888efbf998c5912b3 (patch)
tree2a3d0151583646b33a5ba6e518c23e403433be85 /src/soc/intel/broadwell/pch
parent3cc2c38d50741fffb9193851a4a3b7c636f7cd4d (diff)
downloadcoreboot-c200e8c7cdebed98860a771888efbf998c5912b3.tar.xz
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')
-rw-r--r--src/soc/intel/broadwell/pch/Makefile.inc38
-rw-r--r--src/soc/intel/broadwell/pch/adsp.c144
-rw-r--r--src/soc/intel/broadwell/pch/bootblock.c115
-rw-r--r--src/soc/intel/broadwell/pch/early_pch.c77
-rw-r--r--src/soc/intel/broadwell/pch/ehci.c57
-rw-r--r--src/soc/intel/broadwell/pch/elog.c124
-rw-r--r--src/soc/intel/broadwell/pch/fadt.c89
-rw-r--r--src/soc/intel/broadwell/pch/gpio.c133
-rw-r--r--src/soc/intel/broadwell/pch/hda.c155
-rw-r--r--src/soc/intel/broadwell/pch/iobp.c139
-rw-r--r--src/soc/intel/broadwell/pch/lpc.c677
-rw-r--r--src/soc/intel/broadwell/pch/me.c1057
-rw-r--r--src/soc/intel/broadwell/pch/me_status.c325
-rw-r--r--src/soc/intel/broadwell/pch/pch.c209
-rw-r--r--src/soc/intel/broadwell/pch/pcie.c650
-rw-r--r--src/soc/intel/broadwell/pch/pmutil.c455
-rw-r--r--src/soc/intel/broadwell/pch/power_state.c111
-rw-r--r--src/soc/intel/broadwell/pch/sata.c286
-rw-r--r--src/soc/intel/broadwell/pch/serialio.c295
-rw-r--r--src/soc/intel/broadwell/pch/smbus.c94
-rw-r--r--src/soc/intel/broadwell/pch/smi.c56
-rw-r--r--src/soc/intel/broadwell/pch/smihandler.c569
-rw-r--r--src/soc/intel/broadwell/pch/uart.c69
-rw-r--r--src/soc/intel/broadwell/pch/usb_debug.c17
-rw-r--r--src/soc/intel/broadwell/pch/xhci.c219
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__ */