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 + +/* + * 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 +#include +#include +#include +#include + +/* 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 #include #include +#include #include #include #include @@ -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 #include #include +#include +#include #include #include #include +#include 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); }