aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/soc/intel/common/block/include/intelblocks/oc_wdt.h27
-rw-r--r--src/soc/intel/common/block/oc_wdt/Kconfig41
-rw-r--r--src/soc/intel/common/block/oc_wdt/Makefile.inc4
-rw-r--r--src/soc/intel/common/block/oc_wdt/oc_wdt.c89
-rw-r--r--src/soc/intel/common/block/smm/smihandler.c4
-rw-r--r--src/soc/intel/common/block/smm/smm.c39
6 files changed, 204 insertions, 0 deletions
diff --git a/src/soc/intel/common/block/include/intelblocks/oc_wdt.h b/src/soc/intel/common/block/include/intelblocks/oc_wdt.h
new file mode 100644
index 0000000000..8a03a18732
--- /dev/null
+++ b/src/soc/intel/common/block/include/intelblocks/oc_wdt.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef SOC_INTEL_COMMON_OC_WDT_H
+#define SOC_INTEL_COMMON_OC_WDT_H
+
+#include <stdbool.h>
+
+/*
+ * Starts and reloads the OC watchdog with given timeout.
+ *
+ * timeout - Time in seconds before OC watchdog times out. Supported range = 70 - 1024
+ */
+void oc_wdt_start(unsigned int timeout);
+
+/* Reloads the OC watchdog (if enabled) preserving the current settings. */
+void oc_wdt_reload(void);
+
+/* Disables the OC WDT */
+void oc_wdt_disable(void);
+
+/* Checks if OC WDT is enabled and returns true if so, otherwise false */
+bool is_oc_wdt_enabled(void);
+
+/* Returns currently programmed OC watchdog timeout in seconds */
+unsigned int oc_wdt_get_current_timeout(void);
+
+#endif
diff --git a/src/soc/intel/common/block/oc_wdt/Kconfig b/src/soc/intel/common/block/oc_wdt/Kconfig
new file mode 100644
index 0000000000..f0966e0f38
--- /dev/null
+++ b/src/soc/intel/common/block/oc_wdt/Kconfig
@@ -0,0 +1,41 @@
+## SPDX-License-Identifier: GPL-2.0-only
+
+config SOC_INTEL_COMMON_BLOCK_OC_WDT
+ bool
+ depends on SOC_INTEL_COMMON_BLOCK_PMC
+ help
+ Intel Processor common Overclocking Watchdog support
+
+config SOC_INTEL_COMMON_OC_WDT_ENABLE
+ bool "Enable overclocking watchdog during boot"
+ depends on SOC_INTEL_COMMON_BLOCK_OC_WDT
+ help
+ Enables Intel chipset Overclocking Watchdog to count during system
+ boot. The platform will reset during lockups if watchdog is not
+ reloaded. Software/firmware is responsible for feeding the watchdog.
+
+ If unsure, say N.
+
+config SOC_INTEL_COMMON_OC_WDT_TIMEOUT_SECONDS
+ int
+ depends on SOC_INTEL_COMMON_OC_WDT_ENABLE
+ range 70 1024
+ default 600
+ help
+ The Intel chipset Overclocking Watchdog timeout value in seconds.
+ coreboot will preload the watchdog with the timeout value specified
+ in this option. Specify a high enough value so that the platform
+ will have a chance to perform full memory training and boot. Default
+ is 10 minutes. Boards and SoCs may override this value.
+
+config SOC_INTEL_COMMON_OC_WDT_RELOAD_IN_PERIODIC_SMI
+ bool "Reload the overclocking watchdog using periodic SMI"
+ depends on SOC_INTEL_COMMON_OC_WDT_ENABLE
+ depends on SOC_INTEL_COMMON_BLOCK_SMM
+ default y
+ help
+ Enables Intel chipset Overclocking Watchdog reloading in the periodic
+ SMI handler. Without this option the platform will keep power cycling
+ unless the OS drivers are installed for this watchdog.
+
+ If unsure, say Y.
diff --git a/src/soc/intel/common/block/oc_wdt/Makefile.inc b/src/soc/intel/common/block/oc_wdt/Makefile.inc
new file mode 100644
index 0000000000..8f7282a004
--- /dev/null
+++ b/src/soc/intel/common/block/oc_wdt/Makefile.inc
@@ -0,0 +1,4 @@
+## SPDX-License-Identifier: GPL-2.0-only
+
+all-$(CONFIG_SOC_INTEL_COMMON_BLOCK_OC_WDT) += oc_wdt.c
+smm-$(CONFIG_SOC_INTEL_COMMON_BLOCK_OC_WDT) += oc_wdt.c
diff --git a/src/soc/intel/common/block/oc_wdt/oc_wdt.c b/src/soc/intel/common/block/oc_wdt/oc_wdt.c
new file mode 100644
index 0000000000..012621bb90
--- /dev/null
+++ b/src/soc/intel/common/block/oc_wdt/oc_wdt.c
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#include <arch/io.h>
+#include <console/console.h>
+#include <intelblocks/oc_wdt.h>
+#include <soc/iomap.h>
+#include <types.h>
+
+/* OC WDT configuration */
+#define PCH_OC_WDT_CTL (ACPI_BASE_ADDRESS + 0x54)
+#define PCH_OC_WDT_CTL_RLD BIT(31)
+#define PCH_OC_WDT_CTL_ICCSURV_STS BIT(25)
+#define PCH_OC_WDT_CTL_NO_ICCSURV_STS BIT(24)
+#define PCH_OC_WDT_CTL_FORCE_ALL BIT(15)
+#define PCH_OC_WDT_CTL_EN BIT(14)
+#define PCH_OC_WDT_CTL_ICCSURV BIT(13)
+#define PCH_OC_WDT_CTL_LCK BIT(12)
+#define PCH_OC_WDT_CTL_TOV_MASK 0x3FF
+
+/*
+ * Starts and reloads the OC watchdog with given timeout.
+ *
+ * timeout - Time in seconds before OC watchdog times out. Supported range = 70 - 1024
+ */
+void oc_wdt_start(unsigned int timeout)
+{
+ uint32_t oc_wdt_ctrl;
+
+ if (!CONFIG(SOC_INTEL_COMMON_OC_WDT_ENABLE))
+ return;
+
+ if ((timeout < 70) || (timeout > (PCH_OC_WDT_CTL_TOV_MASK + 1))) {
+ timeout = CONFIG_SOC_INTEL_COMMON_OC_WDT_TIMEOUT_SECONDS;
+ printk(BIOS_WARNING, "OC Watchdog: invalid timeout value,"
+ " using config default: %ds\n", timeout);
+ }
+
+ printk(BIOS_SPEW, "OC Watchdog: start and relaod timer (timeout %ds)\n", timeout);
+
+ oc_wdt_ctrl = inl(PCH_OC_WDT_CTL);
+ oc_wdt_ctrl |= (PCH_OC_WDT_CTL_EN | PCH_OC_WDT_CTL_FORCE_ALL | PCH_OC_WDT_CTL_ICCSURV);
+
+
+ oc_wdt_ctrl &= ~PCH_OC_WDT_CTL_TOV_MASK;
+ oc_wdt_ctrl |= (timeout - 1);
+ oc_wdt_ctrl |= PCH_OC_WDT_CTL_RLD;
+
+ outl(oc_wdt_ctrl, PCH_OC_WDT_CTL);
+}
+
+/* Reloads the OC watchdog (if enabled) preserving the current settings. */
+void oc_wdt_reload(void)
+{
+ uint32_t oc_wdt_ctrl;
+
+ /* Reload only works if OC WDT enable bit is set */
+ if (!is_oc_wdt_enabled())
+ return;
+
+ oc_wdt_ctrl = inl(PCH_OC_WDT_CTL);
+ /* Unset write-1-to-clear bits and preserve other settings */
+ oc_wdt_ctrl &= ~(PCH_OC_WDT_CTL_ICCSURV_STS | PCH_OC_WDT_CTL_NO_ICCSURV_STS);
+ oc_wdt_ctrl |= PCH_OC_WDT_CTL_RLD;
+ outl(oc_wdt_ctrl, PCH_OC_WDT_CTL);
+}
+
+/* Disables the OC WDT. */
+void oc_wdt_disable(void)
+{
+ uint32_t oc_wdt_ctrl;
+
+ printk(BIOS_INFO, "OC Watchdog: disabling watchdog timer\n");
+
+ oc_wdt_ctrl = inl(PCH_OC_WDT_CTL);
+ oc_wdt_ctrl &= ~(PCH_OC_WDT_CTL_EN | PCH_OC_WDT_CTL_FORCE_ALL);
+ outl(oc_wdt_ctrl, PCH_OC_WDT_CTL);
+}
+
+/* Checks if OC WDT is enabled and returns true if so, otherwise false. */
+bool is_oc_wdt_enabled(void)
+{
+ return (inl(PCH_OC_WDT_CTL) & PCH_OC_WDT_CTL_EN) ? true : false;
+}
+
+/* Returns currently programmed OC watchdog timeout in seconds */
+unsigned int oc_wdt_get_current_timeout(void)
+{
+ return (inl(PCH_OC_WDT_CTL) & PCH_OC_WDT_CTL_TOV_MASK) + 1;
+}
diff --git a/src/soc/intel/common/block/smm/smihandler.c b/src/soc/intel/common/block/smm/smihandler.c
index 3cbdd9fa09..680ec87f3e 100644
--- a/src/soc/intel/common/block/smm/smihandler.c
+++ b/src/soc/intel/common/block/smm/smihandler.c
@@ -15,6 +15,7 @@
#include <device/pci_ops.h>
#include <elog.h>
#include <intelblocks/fast_spi.h>
+#include <intelblocks/oc_wdt.h>
#include <intelblocks/pmclib.h>
#include <intelblocks/smihandler.h>
#include <intelblocks/tco.h>
@@ -480,6 +481,9 @@ void smihandler_southbridge_periodic(
if ((reg32 & PERIODIC_EN) == 0)
return;
printk(BIOS_DEBUG, "Periodic SMI.\n");
+
+ if (CONFIG(SOC_INTEL_COMMON_OC_WDT_RELOAD_IN_PERIODIC_SMI))
+ oc_wdt_reload();
}
void smihandler_southbridge_gpi(
diff --git a/src/soc/intel/common/block/smm/smm.c b/src/soc/intel/common/block/smm/smm.c
index 2fd97da221..b0bb378a1b 100644
--- a/src/soc/intel/common/block/smm/smm.c
+++ b/src/soc/intel/common/block/smm/smm.c
@@ -3,9 +3,12 @@
#include <console/console.h>
#include <cpu/x86/smm.h>
#include <cpu/intel/smm_reloc.h>
+#include <device/mmio.h>
+#include <intelblocks/oc_wdt.h>
#include <intelblocks/pmclib.h>
#include <intelblocks/systemagent.h>
#include <soc/pm.h>
+#include <soc/pmc.h>
void smm_southbridge_clear_state(void)
{
@@ -23,6 +26,36 @@ void smm_southbridge_clear_state(void)
pmc_clear_all_gpe_status();
}
+static void configure_periodic_smi_interval(void)
+{
+ uint32_t gen_pmcon;
+ uint32_t gen_pmcon_reg;
+
+ if (CONFIG(PERIODIC_SMI_RATE_SELECTION_IN_GEN_PMCON_B))
+ gen_pmcon_reg = GEN_PMCON_B;
+ else
+ gen_pmcon_reg = GEN_PMCON_A;
+
+ /*
+ * Periodic SMIs have +/- 1 second error, to be safe add few seconds
+ * more. Also we do not allow timeouts lower than 70s by Kconfig
+ * definition, so we need to handle one case.
+ */
+ gen_pmcon = read32p(soc_read_pmc_base() + gen_pmcon_reg);
+ gen_pmcon &= ~PER_SMI_SEL_MASK;
+ gen_pmcon |= SMI_RATE_64S;
+ write32p(soc_read_pmc_base() + gen_pmcon_reg, gen_pmcon);
+
+ /*
+ * We don't know when SMI timer is started or even if this is
+ * architecturally defined, but in worst case we may get SMI 64
+ * seconds (+ any error) from now, which may be more than OC watchdog
+ * timeout since it was last kicked, so we should kick it here, just
+ * in case.
+ */
+ oc_wdt_reload();
+}
+
static void smm_southbridge_enable(uint16_t pm1_events)
{
uint32_t smi_params = ENABLE_SMI_PARAMS;
@@ -48,6 +81,7 @@ static void smm_southbridge_enable(uint16_t pm1_events)
* - on writes to GBL_RLS (bios commands)
* - on eSPI events, unless disabled (does nothing on LPC systems)
* - on TCO events (TIMEOUT, case intrusion, ...), if enabled
+ * - periodically, if watchdog feeding through SMI is enabled
* No SMIs:
* - on microcontroller writes (io 0x62/0x66)
*/
@@ -57,6 +91,11 @@ static void smm_southbridge_enable(uint16_t pm1_events)
if (CONFIG(SOC_INTEL_COMMON_BLOCK_SMM_TCO_ENABLE))
smi_params |= TCO_SMI_EN;
+ if (CONFIG(SOC_INTEL_COMMON_OC_WDT_RELOAD_IN_PERIODIC_SMI)) {
+ smi_params |= PERIODIC_EN;
+ configure_periodic_smi_interval();
+ }
+
/* Enable SMI generation: */
pmc_enable_smi(smi_params);
}