/* Copyright 2013-2014 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.
 */

/* FSP DPO (Delayed Power Off) event support */

#define pr_fmt(fmt) "FSP-DPO: " fmt

#include <skiboot.h>
#include <fsp.h>
#include <stdio.h>
#include <timebase.h>
#include <opal.h>
#include <opal-msg.h>

#define DPO_CMD_SGN_BYTE0	0xf4 /* Byte[0] signature */
#define DPO_CMD_SGN_BYTE1	0x20 /* Byte[1] signature */
#define DPO_TIMEOUT		2700 /* 45 minutes in seconds */

bool fsp_dpo_pending;
static unsigned long fsp_dpo_init_tb;

/*
 * OPAL DPO interface
 *
 * Returns zero if DPO is not active, positive value indicating number
 * of seconds remaining for a forced system shutdown. This will enable
 * the host to schedule for shutdown voluntarily before timeout occurs.
 */
static int64_t fsp_opal_get_dpo_status(int64_t *dpo_timeout)
{
	if (!fsp_dpo_pending) {
		*dpo_timeout = 0;
		return OPAL_WRONG_STATE;
	}

	*dpo_timeout = DPO_TIMEOUT - tb_to_secs(mftb() - fsp_dpo_init_tb);
	return OPAL_SUCCESS;
}

/* Process FSP DPO init message */
static void fsp_process_dpo(struct fsp_msg *msg)
{
	struct fsp_msg *resp;
	u32 cmd = FSP_RSP_INIT_DPO;
	int rc;

	/* DPO message does not have the correct signatures */
	if ((msg->data.bytes[0] != DPO_CMD_SGN_BYTE0)
			|| (msg->data.bytes[1] != DPO_CMD_SGN_BYTE1)) {
		prerror("Message signatures did not match\n");
		cmd |= FSP_STATUS_INVALID_CMD;
		resp = fsp_mkmsg(cmd, 0);
		if (resp == NULL) {
			prerror("%s : Message allocation failed\n", __func__);
			return;
		}
		if (fsp_queue_msg(resp, fsp_freemsg)) {
			fsp_freemsg(resp);
			prerror("%s : Failed to queue response "
				"message\n", __func__);
		}
		return;
	}

	/* OPAL is already in "DPO pending" state */
	if (fsp_dpo_pending) {
		prlog(PR_INFO, "OPAL already in DPO pending state\n");
		cmd |= FSP_STATUS_INVALID_DPOSTATE;
		resp = fsp_mkmsg(cmd, 0);
		if (resp == NULL) {
			prerror("%s : Message allocation failed\n", __func__);
			return;
		}
		if (fsp_queue_msg(resp, fsp_freemsg)) {
			fsp_freemsg(resp);
			prerror("%s : Failed to queue response "
				"message\n", __func__);
		}
		return;
	}


	/* Inform the host about DPO */
	rc = opal_queue_msg(OPAL_MSG_DPO, NULL, NULL);
	if (rc) {
		prerror("OPAL message queuing failed\n");
		cmd |= FSP_STATUS_GENERIC_ERROR;
		resp = fsp_mkmsg(cmd, 0);
		if (resp == NULL) {
			prerror("%s : Message allocation failed\n", __func__);
			return;
		}
		if (fsp_queue_msg(resp, fsp_freemsg)) {
			fsp_freemsg(resp);
			prerror("%s : Failed to queue response "
				"message\n", __func__);
		}
		return;
	} else
		prlog(PR_INFO, "Notified host about DPO event\n");

	/* Acknowledge the FSP on DPO */
	resp = fsp_mkmsg(cmd, 0);
	if (resp == NULL) {
		prerror("%s : Message allocation failed\n", __func__);
		return;
	}
	if (fsp_queue_msg(resp, fsp_freemsg)) {
		fsp_freemsg(resp);
		prerror("%s : Failed to queue response message\n", __func__);
		return;
	}

	/* Record DPO init time and set DPO pending flag */
	fsp_dpo_init_tb = mftb();
	fsp_dpo_pending = true;

	/*
	 * OPAL is now in DPO pending state. After first detecting DPO
	 * condition from OPAL, the host will have 45 minutes to prepare
	 * the system for shutdown. The host must take all necessary actions
	 * required in that regard and at the end shutdown itself. The host
	 * shutdown sequence eventually will make the call OPAL_CEC_POWER_DOWN
	 * which in turn ask the FSP to shutdown the CEC. If the FSP does not
	 * receive the cec power down command from OPAL within 45 minutes,
	 * it will assume that the host and the OPAL has processed the DPO
	 * sequence successfully and hence force power off the system.
	 */
}

/* Handle DPO sub-command from FSP */
static bool fsp_dpo_message(u32 cmd_sub_mod, struct fsp_msg *msg)
{
	if (cmd_sub_mod == FSP_CMD_INIT_DPO) {
		prlog(PR_INFO, "Delayed Power Off (DPO) notification received\n");
		fsp_process_dpo(msg);
		return true;
	}

	prerror("Unknown command 0x%x\n", cmd_sub_mod);
	return false;
}

static struct fsp_client fsp_dpo_client = {
	.message = fsp_dpo_message,
};

void fsp_dpo_init(void)
{
	fsp_register_client(&fsp_dpo_client, FSP_MCLASS_SERVICE);
	opal_register(OPAL_GET_DPO_STATUS, fsp_opal_get_dpo_status, 1);
	prlog(PR_INFO, "FSP DPO support initialized\n");
}
