===========
	EPOW events are of two types: EPOW3, EPOW4.
Thse events are triggered by SPCN due to various reasons like utility
power loss, high ambient temperature etc.

* EPOW3 event is triggered for critical platform conditions that
require system shutdown in 15 mins time like system AC power loss and
system running on UPS power.
* EPOW4 event is triggered for critical platform conditions that
require system shutdown within 20 secs time like system AC power loss,
system running on UPS and UPS battery running low on charge.
	FSP sends EPOW notifications via mbox panel status notification alerts.
This notification contains information about EPOW event. Among these notification
UPS utility fail and UPS battery low bits in byte1 determine the EPOW event type
and byte5 value informs about EPOW reason. For any change in these two bits
SPCN triggers EPOW alerts.

EPOW event flow from HW to guest OS
====================================
	Upon receiving a EPOW alert from OPAL, host kernel should make OPAL
OPAL_GET_EPOW_EVENT_INFO call to get EPOW event details. For any EPOW event
missed while booting, host kernel should use same call to request EPOW event
info. Kernel should prepare for graceful system shutdown within time available
once it is notified about EPOW event. Prior to host shutdown, host kernel
should also notify guest OSes about EPOW event via EPOW interrupt mechanism
and pass on needed EPOW event info to guest OS to handle events. Since guest OS
follow PAPR standards thus host kerel should map EPOW event info details to
needed EPOP event details in PAPR format.
Below table summarises HW EPOW event to PAPR EPOW event info mappings.

HW EPOW event = EPOW3
PAPR EPOW Sensor value = 3
PAPR EPOW Action code = 3 (SYSTEM_SHUTDOWN)

HW EPOW event = EPOW4
PAPR EPOW Sensor value = 4
PAPR EPOW Action code = 4 (SYSTEM_HALT)

HW EPOW event = EPOW0
PAPR EPOW Sensor value = 0
PAPR EPOW Action Code = 0 (EPOW_RESET)

PAPR EPOW modifier value is obtained from EPOW reason code.

Signed-off-by: Vipin K Parashar <vipin@linux.vnet.ibm.com>
---
 hw/fsp/Makefile.inc        |   2 +-
 hw/fsp/fsp-epow-event.c    | 272 +++++++++++++++++++++++++++++++++++++++++++++
 hw/fsp/fsp-epow-event.h    |  41 +++++++
 include/fsp.h              |   3 +
 include/opal.h             |  20 +++-
 platforms/ibm-fsp/common.c |   6 +-
 6 files changed, 341 insertions(+), 3 deletions(-)
 create mode 100644 hw/fsp/fsp-epow-event.c
 create mode 100644 hw/fsp/fsp-epow-event.h

diff --git a/hw/fsp/Makefile.inc b/hw/fsp/Makefile.inc
index d9c21e6..091503b 100644
--- a/hw/fsp/Makefile.inc
+++ b/hw/fsp/Makefile.inc
@@ -4,7 +4,7 @@ FSP_OBJS  = fsp.o fsp-console.o fsp-rtc.o fsp-nvram.o fsp-sysparam.o
 FSP_OBJS += fsp-surveillance.o fsp-codeupdate.o fsp-sensor.o
 FSP_OBJS += fsp-diag.o fsp-leds.o fsp-mem-err.o fsp-op-panel.o
 FSP_OBJS += fsp-elog-read.o fsp-elog-write.o fsp-dpo.o
-FSP_OBJS += fsp-dump.o fsp-mdst-table.o
+FSP_OBJS += fsp-dump.o fsp-mdst-table.o fsp-epow-event.o
 FSP_OBJS += fsp-attn.o
 FSP = hw/fsp/built-in.o
 $(FSP): $(FSP_OBJS:%=hw/fsp/%)
diff --git a/hw/fsp/fsp-epow-event.c b/hw/fsp/fsp-epow-event.c
new file mode 100644
index 0000000..2296acb
--- /dev/null
+++ b/hw/fsp/fsp-epow-event.c
@@ -0,0 +1,272 @@
+/* Copyright 2015 IBM Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *	http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* EPOW (Early Power Off Warning) event support. */
+
+#include <fsp.h>
+#include <device.h>
+#include <lock.h>
+#include <skiboot.h>
+#include <opal-msg.h>
+#include <opal-api.h>
+
+#include "fsp-epow-event.h"
+
+#define	EPOW_EVENT_DETECTED	1
+
+#define PREFIX "FSP-EPOW: "
+
+/* Structure to hold latest EPOW event information. */
+static struct opal_epow_event epow;
+
+/* Lock variable used to update EPOW event information */
+static struct lock epow_lock = LOCK_UNLOCKED;
+
+/* Utilty Fail (UF) bit status */
+static u8 utility_fail_mask;
+
+/* Battery Low (BL) bit status */
+static u8 battery_low_mask;
+
+/* Determine EPOW reason text from EPOW reason code */
+static const char *get_epow_reason_string(int code)
+{
+	switch (code) {
+	case OPAL_EPOW_NONE:
+		return "EPOW condition not present/cleared";
+	case OPAL_EPOW_ON_UPS:
+		return "System on UPS/internal battery";
+	case OPAL_EPOW_TMP_AMB:
+		return "High ambient temp";
+	case OPAL_EPOW_TMP_INT:
+		return "High internal temp";
+	}
+
+	return "Unknown";
+}
+
+/*
+ * Notify host about EPOW event. Host should make a OPAL call
+ * subsequently to obtain EPOW event information.
+ */
+static int notify_epow_event(void)
+{
+	int rc;
+
+	/* Send OPAL message notification */
+	rc = opal_queue_msg(OPAL_MSG_EPOW, NULL, NULL);
+	if (rc)
+		prlog(PR_ERR, PREFIX "OPAL message queuing failed. rc = %d\n",
+					rc);
+
+	return rc;
+}
+
+/* Process captured EPOW event notification */
+static int process_epow_event(struct fsp_msg *msg)
+{
+	u8 ups_status;
+	bool new_epow_event = false;
+
+	/* Basic EPOW signature */
+	if (msg->data.bytes[0] != 0xF2) {
+		prlog(PR_ERR, PREFIX "Signature mismatch\n");
+		return OPAL_HARDWARE;
+	}
+
+	prlog(PR_DEBUG, PREFIX "UPS byte = 0x%x, SPCN byte = 0x%x\n",
+		msg->data.bytes[1], msg->data.bytes[2]);
+
+	/* UPS status bits information. */
+	ups_status = msg->data.bytes[1];
+
+	/*
+	* Check if there is any change in UF and BL bits,
+	* to identify any new EPOW event.
+	*/
+	lock(&epow_lock);
+	if (utility_fail_mask != (ups_status & UPS_UTILITY_FAIL)) {
+
+		/* Update new utilty fail mask */
+		utility_fail_mask = (ups_status & UPS_UTILITY_FAIL);
+		new_epow_event = true;
+	}
+
+
+	/*
+	 * With non-zero utility fail mask, check if there is any change in
+	 * battery low mask value . With zero utility fail mask value, battery
+	 * low mask change doesn't trigger EPOW event.
+	 */
+	if (utility_fail_mask) {
+		if (battery_low_mask != (ups_status & UPS_BATTERY_LOW)) {
+			/* Update new battery low mask */
+			battery_low_mask = (ups_status & UPS_BATTERY_LOW);
+			new_epow_event = true;
+		}
+	}
+
+	prlog(PR_DEBUG, PREFIX "UF Mask = 0x%x, BL Mask = 0x%x. "
+				"New EPOW event = %d\n", utility_fail_mask,
+				battery_low_mask, new_epow_event);
+
+	if (new_epow_event == false) {
+		unlock(&epow_lock);
+		return OPAL_SUCCESS;
+	}
+
+	/* Proceed with new EPOW event processing */
+	/* EPOW3 event (UF=1, BL=0) */
+	if (utility_fail_mask && !battery_low_mask) {
+		epow.type = OPAL_EPOW_EVENT_TYPE3;
+		epow.timeout = TIMEOUT_EPOW3;
+	}
+
+
+	/* EPOW4 event (UF=1, BL=1)*/
+	if (utility_fail_mask && battery_low_mask) {
+		epow.type = OPAL_EPOW_EVENT_TYPE4;
+		epow.timeout = TIMEOUT_EPOW4;
+	}
+
+	/* EPOW condition normal/reset event (UF=0, BL=0) */
+	if (!utility_fail_mask && !battery_low_mask) {
+		epow.type = OPAL_EPOW_NONE;
+		epow.timeout = TIMEOUT_EPOW_NONE;
+	}
+
+	/* Collect EPOW reason code information */
+	epow.reason_code = msg->data.bytes[4];
+
+	unlock(&epow_lock);
+
+	prlog(PR_INFO, PREFIX "EPOW%d event received. Timeout = %d secs, "
+			"Reason: %s\n", epow.type, epow.timeout,
+			get_epow_reason_string(epow.reason_code));
+
+	return EPOW_EVENT_DETECTED;
+}
+
+/*
+ * EPOW OPAL interface
+ *
+ * Host requests EPOW event information through this
+ * OPAl call.
+ */
+static int64_t opal_get_epow_event_info(struct opal_epow_event *epow_data)
+{
+	if (epow_data == NULL)
+		return OPAL_PARAMETER;
+
+	lock(&epow_lock);
+	memcpy(epow_data, &epow, sizeof(struct opal_epow_event));
+	unlock(&epow_lock);
+
+	return OPAL_SUCCESS;
+}
+
+/* Determine panel status type */
+static int get_panel_status_type(u32 cmd_sub_mod)
+{
+	switch (cmd_sub_mod) {
+	case FSP_CMD_PANELSTATUS:
+		return PANEL_STATUS_NORMAL;
+	case FSP_CMD_PANELSTATUS_EX1:
+		return PANEL_STATUS_EX1;
+	case FSP_CMD_PANELSTATUS_EX2:
+		return PANEL_STATUS_EX2;
+	default:
+		prerror(PREFIX "Unknown panel status type\n");
+	}
+
+	return OPAL_UNSUPPORTED;
+}
+
+/*
+ * FSP sends EPOW alerts via panel status notifications.
+ * Handle these notifications to obtain EPOW information.
+ */
+static bool fsp_epow_event_message(u32 cmd_sub_mod, struct fsp_msg *msg)
+{
+	int rc;
+	int panel_status_type;
+
+	/* Determine if its a panel status notification */
+	if ((cmd_sub_mod & FSP_CMD_PANELSTATUS) == FSP_CMD_PANELSTATUS) {
+
+		/* Get reported panel status type */
+		rc = get_panel_status_type(cmd_sub_mod);
+		if (rc == OPAL_UNSUPPORTED) {
+			prerror(PREFIX "Panel status notification with "
+				"unsupported panel status type, cmd = 0x%x\n",
+					cmd_sub_mod);
+			return true;
+		}
+
+		panel_status_type = rc;
+		prlog(PR_DEBUG, PREFIX "Panel status notification "
+			"received from FSP. Panel status type = %d\n",
+			panel_status_type);
+	} else {
+		return false;
+	}
+
+	/*
+	 * EPOW information is reported only in extended panel status 1.
+	 * Return for other panel status notification alerts.
+	 */
+	if (panel_status_type != PANEL_STATUS_EX1)
+		return true;
+
+	/* Process EPOW information present in notification */
+	rc = process_epow_event(msg);
+
+	if (rc < 0)
+		prerror(PREFIX "Failed to process EPOW information.\n");
+	else if (rc == EPOW_EVENT_DETECTED) {
+		/* Notify host about new EPOW event */
+		rc = notify_epow_event();
+		if (rc)
+			prerror(PREFIX "Failed to notify EPOW event to host. "
+				"Hardware will shutdown system after EPOW "
+				"event timeout");
+	}
+
+	return true;
+}
+
+static struct fsp_client fsp_epow_event_client = {
+	.message = fsp_epow_event_message,
+};
+
+void fsp_epow_event_init(void)
+{
+	struct dt_node *np;
+
+	/* Initialize variables */
+	utility_fail_mask = 0;
+	battery_low_mask = 0;
+
+	/* Register EPOW event notifications and OPAL calls */
+	fsp_register_client(&fsp_epow_event_client, FSP_MCLASS_SERVICE);
+	opal_register(OPAL_GET_EPOW_EVENT_INFO, opal_get_epow_event_info, 1);
+
+	/* Add EPOW device-tree node */
+	np = dt_new(opal_node, "epow");
+	dt_add_property_strings(np, "compatible", "ibm,opal-v3-epow");
+
+	prlog(PR_INFO, PREFIX "EPOW events support initialized\n");
+}
diff --git a/hw/fsp/fsp-epow-event.h b/hw/fsp/fsp-epow-event.h
new file mode 100644
index 0000000..631519d
--- /dev/null
+++ b/hw/fsp/fsp-epow-event.h
@@ -0,0 +1,41 @@
+/* Copyright 2015 IBM Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *	http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* EPOW (Early Power Off Warning) event support */
+
+#ifndef __FSP_EPOW_EVENT_H
+#define __FSP_EPOW_EVENT_H
+
+/* FSP Panel Status type */
+#define PANEL_STATUS_NORMAL	0	/* panel status normal */
+#define PANEL_STATUS_EX1	1	/* panel status extended 1 */
+#define PANEL_STATUS_EX2	2	/* panel status extended 2 */
+
+/*
+ * EPOW event timeout values in seconds. SPCN will shutdown system
+ * after these timeout values if its not shutdown by software.
+ */
+#define	TIMEOUT_EPOW_NONE	0
+#define TIMEOUT_EPOW3		900
+#define TIMEOUT_EPOW4		20
+
+/* UPS bits mask */
+#define UPS_PRESENT		0x80	/* UPS present */
+#define UPS_UTILITY_FAIL	0x40	/* UPS utility fail */
+#define UPS_BYPASED		0x20	/* UPS bypassed */
+#define UPS_BATTERY_LOW		0x10	/* UPS battery low */
+
+#endif
diff --git a/include/fsp.h b/include/fsp.h
index 260c4e1..47d3d29 100644
--- a/include/fsp.h
+++ b/include/fsp.h
@@ -789,6 +789,9 @@ extern void fsp_init_diag(void);
 extern void fsp_led_init(void);
 extern void create_led_device_nodes(void);
 
+/* EPOW */
+extern void fsp_epow_event_init(void);
+
 /* DPO */
 extern void fsp_dpo_init(void);
 
diff --git a/include/opal.h b/include/opal.h
index 952000c..43088b5 100644
--- a/include/opal.h
+++ b/include/opal.h
@@ -167,7 +167,8 @@
 #define OPAL_PRD_MSG				113
 #define OPAL_LEDS_GET_INDICATOR			114
 #define OPAL_LEDS_SET_INDICATOR			115
-#define OPAL_LAST				115
+#define OPAL_GET_EPOW_EVENT_INFO                116
+#define OPAL_LAST				116
 
 /* Device tree flags */
 
@@ -900,6 +901,23 @@ struct opal_i2c_request {
 	__be64 buffer_ra;		/* Buffer real address */
 };
 
+/* EPOW event types */
+#define OPAL_EPOW_EVENT_TYPE3	3	/* EPOW3 event */
+#define OPAL_EPOW_EVENT_TYPE4	4	/* EPOW4 event */
+
+/* EPOW reason code values */
+#define OPAL_EPOW_NONE		0       /* EPOW normal/reset */
+#define OPAL_EPOW_ON_UPS	1       /* System on UPS */
+#define OPAL_EPOW_TMP_AMB	2       /* Over ambient temperature */
+#define OPAL_EPOW_TMP_INT	3       /* Over internal temperature */
+
+/* EPOW event information */
+struct opal_epow_event {
+	int32_t type;			/* EPOW event types */
+	int32_t reason_code;		/* EPOW reason */
+	int32_t timeout;		/* Time allowed for EPOW event */
+};
+
 #endif /* __ASSEMBLY__ */
 
 #endif /* __OPAL_H */
diff --git a/platforms/ibm-fsp/common.c b/platforms/ibm-fsp/common.c
index a4e9903..b07638b 100644
--- a/platforms/ibm-fsp/common.c
+++ b/platforms/ibm-fsp/common.c
@@ -4,7 +4,7 @@
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- * 	http://www.apache.org/licenses/LICENSE-2.0
+ *	http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -147,6 +147,10 @@ void ibm_fsp_init(void)
 	fsp_code_update_init();
 
 	/* EPOW */
+	op_display(OP_LOG, OP_MOD_INIT, 0x000A);
+	fsp_epow_event_init();
+
+	/* DPO */
 	op_display(OP_LOG, OP_MOD_INIT, 0x000B);
 	fsp_dpo_init();
 
