soc/intel/common/block: Add support for watchdog

Implement watchdog for intel based platform by filling ACPI Watchdog
Action Table (WDAT) table.
The WDAT ACPI table encompasses essential watchdog functions, including:
- Setting and retrieving countdown/timeout values
- Starting and stopping the watchdog
- Pinging the watchdog
- Retrieving the cause of the last reboot, whether it was triggered by
the watchdog or another reason

The general purpose register TCO_MESSAGE1 stores the reason for the most
recent reboot rather than the original register TCO2_STS. This is
because the firmware must clear TCO2_STS, and it can't be reused for
storing this information for the operating system.

The watchdog is designed for use by the OS through certain defined
actions in the WDAT table. It relies on the ACPI Power Management Timer,
which may result in an increase in power consumption.

BUG=b:314260167
TEST=Enable CONFIG_ACPI_WDAT_WDT and CONFIG_USE_PM_ACPI_TIMER in the
config. Enable CONFIG_WDAT_WDT in the kernel config. Build and deploy
both firmware and kernel to the device. Trigger the watchdog by
performing the command: “cat > /dev/watchdog”. Wait approximately 30
seconds for the watchdog to reset the device.

Change-Id: Iaf7971f8407920a553fd91d2ed04193c882e08f1
Signed-off-by: Marek Maslanka <mmaslanka@google.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/79909
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Subrata Banik <subratabanik@google.com>
diff --git a/src/soc/intel/common/block/acpi/acpi.c b/src/soc/intel/common/block/acpi/acpi.c
index eed1530..cd60416 100644
--- a/src/soc/intel/common/block/acpi/acpi.c
+++ b/src/soc/intel/common/block/acpi/acpi.c
@@ -16,8 +16,10 @@
 #include <intelblocks/lpc_lib.h>
 #include <intelblocks/pmclib.h>
 #include <intelblocks/sgx.h>
+#include <intelblocks/tco.h>
 #include <intelblocks/uart.h>
 #include <soc/gpio.h>
+#include <soc/intel/common/tco.h>
 #include <soc/iomap.h>
 #include <soc/pm.h>
 
@@ -389,3 +391,176 @@
 	if (CONFIG(SOC_INTEL_COMMON_BLOCK_SGX_ENABLE))
 		sgx_fill_ssdt();
 }
+
+
+static bool fill_wdat_timeout_entry(acpi_wdat_entry_t *entry)
+{
+	uint16_t tcobase = tco_get_bar();
+
+	if (tcobase == 0)
+		return false;
+
+	memset((void *)entry, 0, sizeof(acpi_wdat_entry_t));
+
+	entry->action = ACPI_WDAT_SET_COUNTDOWN;
+	entry->instruction = ACPI_WDAT_WRITE_COUNTDOWN | ACPI_WDAT_PRESERVE_REGISTER;
+	entry->mask = TCO_TMR_MASK;
+	entry->register_region.space_id = ACPI_ADDRESS_SPACE_IO;
+	entry->register_region.addrl = tcobase + TCO_TMR;
+	entry->register_region.access_size = ACPI_WDAT_ACCESS_SIZE_WORD;
+
+	return true;
+}
+
+static bool fill_wdat_boot_status_entry(acpi_wdat_entry_t *entry, uint8_t action,
+					uint8_t instruction, uint32_t value)
+{
+	uint16_t tcobase = tco_get_bar();
+
+	if (tcobase == 0)
+		return false;
+
+	memset((void *)entry, 0, sizeof(acpi_wdat_entry_t));
+
+	entry->action = action;
+	entry->instruction = instruction;
+	entry->value = value;
+	entry->mask = TCO2_STS_SECOND_TO;
+	entry->register_region.space_id = ACPI_ADDRESS_SPACE_IO;
+	entry->register_region.addrl = tcobase + TCO_MESSAGE1;
+	entry->register_region.access_size = ACPI_WDAT_ACCESS_SIZE_BYTE;
+
+	return true;
+}
+
+static bool fill_wdat_run_state_entry(acpi_wdat_entry_t *entry, uint8_t action,
+				       uint8_t instruction, uint32_t value)
+{
+	uint16_t tcobase = tco_get_bar();
+
+	if (tcobase == 0)
+		return false;
+
+	memset((void *)entry, 0, sizeof(acpi_wdat_entry_t));
+
+	entry->action = action;
+	entry->instruction = instruction;
+	entry->value = value;
+	entry->mask = TCO1_TMR_HLT;
+	entry->register_region.space_id = ACPI_ADDRESS_SPACE_IO;
+	entry->register_region.addrl = tcobase + TCO1_CNT;
+	entry->register_region.access_size = ACPI_WDAT_ACCESS_SIZE_WORD;
+
+	return true;
+}
+
+static bool fill_wdat_ping_entry(acpi_wdat_entry_t *entry)
+{
+	uint16_t tcobase = tco_get_bar();
+
+	if (tcobase == 0)
+		return false;
+
+	memset((void *)entry, 0, sizeof(acpi_wdat_entry_t));
+
+	entry->action = ACPI_WDAT_RESET;
+	entry->instruction = ACPI_WDAT_WRITE_VALUE;
+	entry->value = 0x01;
+	entry->mask = 0x01;
+	entry->register_region.space_id = ACPI_ADDRESS_SPACE_IO;
+	entry->register_region.addrl = tcobase + TCO_RLD;
+	entry->register_region.access_size = ACPI_WDAT_ACCESS_SIZE_WORD;
+
+	return true;
+}
+
+unsigned long acpi_soc_fill_wdat(acpi_wdat_t *wdat, unsigned long current)
+{
+	if (!wdat)
+		return current;
+
+	uint16_t tcobase = tco_get_bar();
+
+	if (tcobase == 0)
+		goto out_err;
+
+	wdat->pci_segment = 0xff;
+	wdat->pci_bus = 0xff;
+	wdat->pci_device = 0xff;
+	wdat->pci_function = 0xff;
+
+	wdat->timer_period = tco_get_timer_period();
+	wdat->min_count = tco_get_timer_min_value();
+	wdat->max_count = tco_get_timer_max_value();
+	wdat->flags = ACPI_WDAT_FLAG_ENABLED;
+	wdat->entries = 0;
+
+	acpi_wdat_entry_t *entry = (acpi_wdat_entry_t *)current;
+
+	/* Write countdown */
+	if (!fill_wdat_timeout_entry(entry))
+		goto out_err;
+
+	entry++;
+
+	/* Get boot status */
+	if (!fill_wdat_boot_status_entry(entry, ACPI_WDAT_GET_STATUS,
+					 ACPI_WDAT_READ_VALUE, TCO2_STS_SECOND_TO))
+		goto out_err;
+
+	entry++;
+
+	/* Set boot status */
+	if (!fill_wdat_boot_status_entry(entry, ACPI_WDAT_SET_STATUS,
+					 ACPI_WDAT_WRITE_VALUE | ACPI_WDAT_PRESERVE_REGISTER,
+					 0))
+		goto out_err;
+
+	entry++;
+
+	/* Get running status */
+	if (!fill_wdat_run_state_entry(entry, ACPI_WDAT_GET_RUNNING_STATE,
+				       ACPI_WDAT_READ_VALUE, 0))
+		goto out_err;
+
+	entry++;
+
+	/* Start the watchdog */
+	if (!fill_wdat_run_state_entry(entry, ACPI_WDAT_SET_RUNNING_STATE,
+				       ACPI_WDAT_WRITE_VALUE | ACPI_WDAT_PRESERVE_REGISTER,
+				       0))
+		goto out_err;
+
+	entry++;
+
+	/* Get stopped status */
+	if (!fill_wdat_run_state_entry(entry, ACPI_WDAT_GET_STOPPED_STATE,
+				       ACPI_WDAT_READ_VALUE, TCO1_TMR_HLT))
+		goto out_err;
+
+	entry++;
+
+	/* Stop the watchdog */
+	if (!fill_wdat_run_state_entry(entry, ACPI_WDAT_SET_STOPPED_STATE,
+				       ACPI_WDAT_WRITE_VALUE | ACPI_WDAT_PRESERVE_REGISTER,
+				       TCO1_TMR_HLT))
+		goto out_err;
+
+	entry++;
+
+	/* Ping */
+	if (!fill_wdat_ping_entry(entry))
+		goto out_err;
+
+	entry++;
+
+	wdat->entries = ((unsigned long)entry - current) / sizeof(acpi_wdat_entry_t);
+
+	return (unsigned long)entry;
+
+out_err:
+	wdat->flags = ACPI_WDAT_FLAG_DISABLED;
+	printk(BIOS_ERR, "Fail to populate WDAT ACPI Table");
+
+	return current;
+}
diff --git a/src/soc/intel/common/block/include/intelblocks/tco.h b/src/soc/intel/common/block/include/intelblocks/tco.h
index 35cbfb1..1ee24b6 100644
--- a/src/soc/intel/common/block/include/intelblocks/tco.h
+++ b/src/soc/intel/common/block/include/intelblocks/tco.h
@@ -5,6 +5,8 @@
 
 #include <stdint.h>
 
+/* Get base address of TCO I/O registers. */
+uint16_t tco_get_bar(void);
 /*
  * Enable TCO BAR using SMBUS TCO base to access TCO related register
  * also disable the timer.
@@ -20,4 +22,11 @@
 uint16_t tco_read_reg(uint16_t tco_reg);
 void tco_write_reg(uint16_t tco_reg, uint16_t value);
 
+/* Get TCO timer period in milliseconds */
+uint32_t tco_get_timer_period(void);
+/* Get the minimum time value for the TCO timer */
+uint32_t tco_get_timer_min_value(void);
+/* Get the maximum time value for the TCO timer */
+uint32_t tco_get_timer_max_value(void);
+
 #endif /* SOC_INTEL_COMMON_BLOCK_TCO_H */
diff --git a/src/soc/intel/common/block/smbus/tco.c b/src/soc/intel/common/block/smbus/tco.c
index 9045343..ef7ef07 100644
--- a/src/soc/intel/common/block/smbus/tco.c
+++ b/src/soc/intel/common/block/smbus/tco.c
@@ -22,8 +22,11 @@
 #define  TCO_BASE_EN		(1 << 8)
 #define  TCO_BASE_LOCK		(1 << 0)
 
-/* Get base address of TCO I/O registers. */
-static uint16_t tco_get_bar(void)
+#define TCO_TMR_MIN_VALUE	2
+#define TCO_TMR_MAX_VALUE	1023
+#define TCO_TMR_PERIOD_MS	600
+
+uint16_t tco_get_bar(void)
 {
 	return TCO_BASE_ADDRESS;
 }
@@ -73,6 +76,9 @@
 	tco2_sts = tco_read_reg(TCO2_STS);
 	tco_write_reg(TCO2_STS, tco2_sts | TCO2_STS_SECOND_TO);
 
+	if (CONFIG(ACPI_WDAT_WDT))
+		tco_write_reg(TCO_MESSAGE1, tco2_sts & TCO2_STS_SECOND_TO);
+
 	return (tco2_sts << 16) | tco1_sts;
 }
 
@@ -137,3 +143,18 @@
 	if (CONFIG(SOC_INTEL_COMMON_BLOCK_SMM_TCO_ENABLE))
 		tco_intruder_smi_enable();
 }
+
+uint32_t tco_get_timer_period(void)
+{
+	return TCO_TMR_PERIOD_MS;
+}
+
+uint32_t tco_get_timer_min_value(void)
+{
+	return TCO_TMR_MIN_VALUE;
+}
+
+uint32_t tco_get_timer_max_value(void)
+{
+	return TCO_TMR_MAX_VALUE;
+}