/* 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.
 */


/*
 * LED location code and indicator handling
 */

#define pr_fmt(fmt) "FSPLED: " fmt
#include <skiboot.h>
#include <fsp.h>
#include <device.h>
#include <spcn.h>
#include <lock.h>
#include <errorlog.h>
#include <opal.h>
#include <opal-msg.h>
#include <fsp-leds.h>
#include <fsp-sysparam.h>

#define buf_write(p, type, val)  do { *(type *)(p) = val;\
					p += sizeof(type); } while(0)
#define buf_read(p, type, addr)  do { *addr = *(type *)(p);\
					p += sizeof(type); } while(0)

/* SPCN replay threshold */
#define SPCN_REPLAY_THRESHOLD 2

/* LED support status */
enum led_support_state {
	LED_STATE_ABSENT,
	LED_STATE_READING,
	LED_STATE_PRESENT,
};

static enum led_support_state led_support = LED_STATE_ABSENT;

/*
 *  PSI mapped buffer for LED data
 *
 * Mapped once and never unmapped. Used for fetching all
 * available LED information and creating the list. Also
 * used for setting individual LED state.
 *
 */
static void *led_buffer;
static u8 *loc_code_list_buffer = NULL;

/* Maintain list of all LEDs
 *
 * The contents here will be used to cater requests from FSP
 * async commands and HV initiated OPAL calls.
 */
static struct list_head  cec_ledq;		/* CEC LED list */
static struct list_head	 encl_ledq;	/* Enclosure LED list */
static struct list_head  spcn_cmdq;	/* SPCN command queue */

/* LED lock */
static struct lock led_lock = LOCK_UNLOCKED;
static struct lock spcn_cmd_lock = LOCK_UNLOCKED;
static struct lock sai_lock = LOCK_UNLOCKED;

static bool spcn_cmd_complete = true;	/* SPCN command complete */

/* Last SPCN command */
static u32 last_spcn_cmd;
static int replay = 0;

/*
 * FSP controls System Attention Indicator. But it expects hypervisor
 * keep track of the status and serve get LED state request (both from
 * Linux and FSP itself)!
 */
static struct sai_data sai_data;

/* Forward declaration */
static void fsp_read_leds_data_complete(struct fsp_msg *msg);
static int process_led_state_change(void);


DEFINE_LOG_ENTRY(OPAL_RC_LED_SPCN, OPAL_PLATFORM_ERR_EVT, OPAL_LED,
		OPAL_PLATFORM_FIRMWARE, OPAL_PREDICTIVE_ERR_GENERAL,
		OPAL_NA);

DEFINE_LOG_ENTRY(OPAL_RC_LED_BUFF, OPAL_PLATFORM_ERR_EVT, OPAL_LED,
		OPAL_PLATFORM_FIRMWARE, OPAL_PREDICTIVE_ERR_GENERAL,
		OPAL_NA);

DEFINE_LOG_ENTRY(OPAL_RC_LED_LC, OPAL_PLATFORM_ERR_EVT, OPAL_LED,
		OPAL_PLATFORM_FIRMWARE, OPAL_INFO, OPAL_NA);

DEFINE_LOG_ENTRY(OPAL_RC_LED_STATE, OPAL_PLATFORM_ERR_EVT, OPAL_LED,
		OPAL_PLATFORM_FIRMWARE, OPAL_PREDICTIVE_ERR_GENERAL,
		OPAL_NA);

DEFINE_LOG_ENTRY(OPAL_RC_LED_SUPPORT, OPAL_PLATFORM_ERR_EVT, OPAL_LED,
		OPAL_PLATFORM_FIRMWARE, OPAL_INFO, OPAL_NA);


/* Find descendent LED record with CEC location code in CEC list */
static struct fsp_led_data *fsp_find_cec_led(char *loc_code)
{
	struct fsp_led_data *led, *next;

	list_for_each_safe(&cec_ledq, led, next, link) {
		if (strcmp(led->loc_code, loc_code))
			continue;
		return led;
	}
	return NULL;
}

/* Find encl LED record with ENCL location code in ENCL list */
static struct fsp_led_data *fsp_find_encl_led(char *loc_code)
{
	struct fsp_led_data *led, *next;

	list_for_each_safe(&encl_ledq, led, next, link) {
		if (strcmp(led->loc_code, loc_code))
			continue;
		return led;
	}
	return NULL;
}

/* Find encl LED record with CEC location code in CEC list */
static struct fsp_led_data *fsp_find_encl_cec_led(char *loc_code)
{
	struct fsp_led_data *led, *next;

	list_for_each_safe(&cec_ledq, led, next, link) {
		if (strstr(led->loc_code, "-"))
			continue;
		if (!strstr(loc_code, led->loc_code))
			continue;
		return led;
	}
	return NULL;
}

/* Find encl LED record with CEC location code in ENCL list */
static struct fsp_led_data *fsp_find_encl_encl_led(char *loc_code)
{
	struct fsp_led_data *led, *next;

	list_for_each_safe(&encl_ledq, led, next, link) {
		if (!strstr(loc_code, led->loc_code))
			continue;
		return led;
	}
	return NULL;
}

/* Compute the ENCL LED status in CEC list */
static void compute_encl_status_cec(struct fsp_led_data *encl_led)
{
	struct fsp_led_data *led, *next;

	encl_led->status &= ~SPCN_LED_IDENTIFY_MASK;
	encl_led->status &= ~SPCN_LED_FAULT_MASK;

	list_for_each_safe(&cec_ledq, led, next, link) {
		if (!strstr(led->loc_code, encl_led->loc_code))
			continue;

		/* Don't count the enclsure LED itself */
		if (!strcmp(led->loc_code, encl_led->loc_code))
			continue;

		if (led->status & SPCN_LED_IDENTIFY_MASK)
			encl_led->status |= SPCN_LED_IDENTIFY_MASK;

		if (led->status & SPCN_LED_FAULT_MASK)
			encl_led->status |= SPCN_LED_FAULT_MASK;
	}
}

/* Is a enclosure LED */
static bool is_enclosure_led(char *loc_code)
{
	if (strstr(loc_code, "-"))
		return false;
	if (!fsp_find_cec_led(loc_code) || !fsp_find_encl_led(loc_code))
		return false;
	return true;
}

static inline void opal_led_update_complete(u64 async_token, u64 result)
{
	opal_queue_msg(OPAL_MSG_ASYNC_COMP, NULL, NULL, async_token, result);
}

static inline bool is_sai_loc_code(char *loc_code)
{
	if (!strcmp(sai_data.loc_code, loc_code))
		return true;

	return false;
}

/* Set/Reset System attention indicator */
static void fsp_set_sai_complete(struct fsp_msg *msg)
{
	int ret = OPAL_SUCCESS;
	int rc = msg->resp->word1 & 0xff00;
	struct led_set_cmd *spcn_cmd = (struct led_set_cmd *)msg->user_data;

	if (rc) {
		/**
		 * @fwts-label FSPSAIFailed
		 * @fwts-advice Failed to update System Attention Indicator.
		 * Likely means some bug with OPAL interacting with FSP.
		 */
		prlog(PR_ERR, "Update SAI cmd failed [rc=%d].\n", rc);
		ret = OPAL_INTERNAL_ERROR;

		/* Roll back */
		lock(&sai_lock);
		sai_data.state = spcn_cmd->ckpt_status;
		unlock(&sai_lock);
	}

	if (spcn_cmd->cmd_src == SPCN_SRC_OPAL)
		opal_led_update_complete(spcn_cmd->async_token, ret);

	/* free msg and spcn command */
	free(spcn_cmd);
	fsp_freemsg(msg);

	/* Process pending LED update request */
	process_led_state_change();
}

static int fsp_set_sai(struct led_set_cmd *spcn_cmd)
{
	int rc = -ENOMEM;
	uint32_t cmd = FSP_CMD_SA_INDICATOR;
	struct fsp_msg *msg;

	/*
	 * FSP does not allow hypervisor to set real SAI, but we can
	 * reset real SAI. Also in our case only host can control
	 * LEDs, not guests. Hence we will set platform virtual SAI
	 * and reset real SAI.
	 */
	if (spcn_cmd->state == LED_STATE_ON)
		cmd |= FSP_LED_SET_PLAT_SAI;
	else
		cmd |= FSP_LED_RESET_REAL_SAI;

	prlog(PR_TRACE, "Update SAI Indicator [cur : 0x%x, new : 0x%x].\n",
	      sai_data.state, spcn_cmd->state);

	msg = fsp_mkmsg(cmd, 0);
	if (!msg) {
		/**
		 * @fwts-label SAIMallocFail
		 * @fwts-advice OPAL ran out of memory while trying to
		 * allocate an FSP message in SAI code path. This indicates
		 * an OPAL bug that caused OPAL to run out of memory.
		 */
		prlog(PR_ERR, "%s: Memory allocation failed.\n", __func__);
		goto sai_fail;
	}

	spcn_cmd->ckpt_status = sai_data.state;
	msg->user_data = spcn_cmd;
	rc = fsp_queue_msg(msg, fsp_set_sai_complete);
	if (rc) {
		fsp_freemsg(msg);
		/**
		 * @fwts-label SAIQueueFail
		 * @fwts-advice Error in queueing message to FSP in SAI code
		 * path. Likely an OPAL bug.
		 */
		prlog(PR_ERR, "%s: Failed to queue the message\n", __func__);
		goto sai_fail;
	}

	lock(&sai_lock);
	sai_data.state = spcn_cmd->state;
	unlock(&sai_lock);

	return OPAL_SUCCESS;

sai_fail:
	if (spcn_cmd->cmd_src == SPCN_SRC_OPAL)
		opal_led_update_complete(spcn_cmd->async_token,
					 OPAL_INTERNAL_ERROR);

	return OPAL_INTERNAL_ERROR;
}

static void fsp_get_sai_complete(struct fsp_msg *msg)
{
	int rc = msg->resp->word1 & 0xff00;

	if (rc) {
		/**
		 * @fwts-label FSPSAIGetFailed
		 * @fwts-advice Possibly an error on FSP side, OPAL failed
		 * to read state from FSP.
		 */
		prlog(PR_ERR, "Read real SAI cmd failed [rc = 0x%x].\n", rc);
	} else { /* Update SAI state */
		lock(&sai_lock);
		sai_data.state = msg->resp->data.words[0] & 0xff;
		unlock(&sai_lock);

		prlog(PR_TRACE, "SAI initial state = 0x%x\n", sai_data.state);
	}

	fsp_freemsg(msg);
}

/* Read initial SAI state. */
static void fsp_get_sai(void)
{
	int rc;
	uint32_t cmd = FSP_CMD_SA_INDICATOR | FSP_LED_READ_REAL_SAI;
	struct fsp_msg *msg;

	msg = fsp_mkmsg(cmd, 0);
	if (!msg) {
		/**
		 * @fwts-label FSPGetSAIMallocFail
		 * @fwts-advice OPAL ran out of memory: OPAL bug.
		 */
		prlog(PR_ERR, "%s: Memory allocation failed.\n", __func__);
		return;
	}
	rc = fsp_queue_msg(msg, fsp_get_sai_complete);
	if (rc) {
		fsp_freemsg(msg);
		/**
		 * @fwts-label FSPGetSAIQueueFail
		 * @fwts-advice Failed to queue message to FSP: OPAL bug
		 */
		prlog(PR_ERR, "%s: Failed to queue the message\n", __func__);
	}
}

static bool sai_update_notification(struct fsp_msg *msg)
{
	uint32_t *state = &msg->data.words[2];
	uint32_t param_id = msg->data.words[0];
	int len = msg->data.words[1] & 0xffff;

	if (param_id != SYS_PARAM_REAL_SAI && param_id != SYS_PARAM_PLAT_SAI)
		return false;

	if ( len != 4)
		return false;

	if (*state != LED_STATE_ON && *state != LED_STATE_OFF)
		return false;

	/* Update SAI state */
	lock(&sai_lock);
	sai_data.state = *state;
	unlock(&sai_lock);

	prlog(PR_TRACE, "SAI updated. New SAI state = 0x%x\n", *state);
	return true;
}


/*
 * Update both the local LED lists to reflect upon led state changes
 * occurred with the recent SPCN command. Subsequent LED requests will
 * be served with these updates changed to the list.
 */
static void update_led_list(char *loc_code, u32 led_state, u32 excl_bit)
{
	struct fsp_led_data *led = NULL, *encl_led = NULL, *encl_cec_led = NULL;
	bool is_encl_led = is_enclosure_led(loc_code);

	/* Enclosure LED in CEC list */
	encl_cec_led = fsp_find_encl_cec_led(loc_code);
	if (!encl_cec_led) {
		log_simple_error(&e_info(OPAL_RC_LED_LC),
			"Could not find enclosure LED in CEC LC=%s\n",
			loc_code);
		return;
	}

	/* Update state */
	if (is_encl_led) {
		/* Enclosure exclusive bit */
		encl_cec_led->excl_bit = excl_bit;
	} else {	/* Descendant LED in CEC list */
		led = fsp_find_cec_led(loc_code);
		if (!led) {
			log_simple_error(&e_info(OPAL_RC_LED_LC),
					 "Could not find descendent LED in \
					 CEC LC=%s\n", loc_code);
			return;
		}
		led->status = led_state;
	}

	/* Enclosure LED in ENCL list */
	encl_led = fsp_find_encl_encl_led(loc_code);
	if (!encl_led) {
		log_simple_error(&e_info(OPAL_RC_LED_LC),
			"Could not find enclosure LED in ENCL LC=%s\n",
			loc_code);
		return;
	}

	/* Compute descendent rolled up status */
	compute_encl_status_cec(encl_cec_led);

	/* Check whether exclussive bits set */
	if (encl_cec_led->excl_bit & FSP_LED_EXCL_FAULT)
		encl_cec_led->status |= SPCN_LED_FAULT_MASK;

	if (encl_cec_led->excl_bit & FSP_LED_EXCL_IDENTIFY)
		encl_cec_led->status |= SPCN_LED_IDENTIFY_MASK;

	/* Copy over */
	encl_led->status = encl_cec_led->status;
	encl_led->excl_bit = encl_cec_led->excl_bit;
}

static int fsp_set_led_response(uint32_t cmd)
{
	struct fsp_msg *msg;
	int rc = -1;

	msg = fsp_mkmsg(cmd, 0);
	if (!msg) {
		prerror("Failed to allocate FSP_RSP_SET_LED_STATE [cmd=%x])\n",
			cmd);
	} else {
		rc = fsp_queue_msg(msg, fsp_freemsg);
		if (rc != OPAL_SUCCESS) {
			fsp_freemsg(msg);
			prerror("Failed to queue FSP_RSP_SET_LED_STATE"
				" [cmd=%x]\n", cmd);
		}
	}
	return rc;
}

static void fsp_spcn_set_led_completion(struct fsp_msg *msg)
{
	struct fsp_msg *resp = msg->resp;
	u32 cmd = FSP_RSP_SET_LED_STATE;
	u8 status = resp->word1 & 0xff00;
	struct led_set_cmd *spcn_cmd = (struct led_set_cmd *)msg->user_data;

	lock(&led_lock);

	/*
	 * LED state update request came as part of FSP async message
	 * FSP_CMD_SET_LED_STATE, we need to send response message.
	 *
	 * Also if SPCN command failed, then roll back changes.
	 */
	if (status != FSP_STATUS_SUCCESS) {
		log_simple_error(&e_info(OPAL_RC_LED_SPCN),
			"Last SPCN command failed, status=%02x\n",
			status);
		cmd |= FSP_STATUS_GENERIC_ERROR;

		/* Rollback the changes */
		update_led_list(spcn_cmd->loc_code,
				spcn_cmd->ckpt_status, spcn_cmd->ckpt_excl_bit);
	}

	/* FSP initiated SPCN command */
	if (spcn_cmd->cmd_src == SPCN_SRC_FSP)
		fsp_set_led_response(cmd);

	/* OPAL initiated SPCN command */
	if (spcn_cmd->cmd_src == SPCN_SRC_OPAL) {
		if (status != FSP_STATUS_SUCCESS)
			opal_led_update_complete(spcn_cmd->async_token,
						 OPAL_INTERNAL_ERROR);
		else
			opal_led_update_complete(spcn_cmd->async_token,
						 OPAL_SUCCESS);
	}

	unlock(&led_lock);

	/* free msg and spcn command */
	free(spcn_cmd);
	fsp_freemsg(msg);

	/* Process pending LED update request */
	process_led_state_change();
}

/*
 * Set the state of the LED pointed by the location code
 *
 * LED command:		FAULT state or IDENTIFY state
 * LED state  :		OFF (reset) or ON (set)
 *
 * SPCN TCE mapped buffer entries for setting LED state
 *
 * struct spcn_led_data {
 *	u8	lc_len;
 *	u16	state;
 *	char	lc_code[LOC_CODE_SIZE];
 *};
 */
static int fsp_msg_set_led_state(struct led_set_cmd *spcn_cmd)
{
	struct spcn_led_data sled;
	struct fsp_msg *msg = NULL;
	struct fsp_led_data *led = NULL;
	void *buf = led_buffer;
	u16 data_len = 0;
	u32 cmd_hdr = 0;
	u32 cmd = FSP_RSP_SET_LED_STATE;
	int rc = -1;

	sled.lc_len = strlen(spcn_cmd->loc_code);
	strncpy(sled.lc_code, spcn_cmd->loc_code, sled.lc_len);

	lock(&led_lock);

	/* Location code length + Location code + LED control */
	data_len = LOC_CODE_LEN + sled.lc_len + LED_CONTROL_LEN;
	cmd_hdr =  SPCN_MOD_SET_LED_CTL_LOC_CODE << 24 | SPCN_CMD_SET << 16 |
		data_len;

	/* Fetch the current state of LED */
	led = fsp_find_cec_led(spcn_cmd->loc_code);

	/* LED not present */
	if (led == NULL) {
		if (spcn_cmd->cmd_src == SPCN_SRC_FSP) {
			cmd |= FSP_STATUS_INVALID_LC;
			fsp_set_led_response(cmd);
		}

		if (spcn_cmd->cmd_src == SPCN_SRC_OPAL)
			opal_led_update_complete(spcn_cmd->async_token,
						 OPAL_INTERNAL_ERROR);

		unlock(&led_lock);
		return rc;
	}

	/*
	 * Checkpoint the status here, will use it if the SPCN
	 * command eventually fails.
	 */
	spcn_cmd->ckpt_status = led->status;
	spcn_cmd->ckpt_excl_bit = led->excl_bit;
	sled.state = led->status;

	/* Update the exclussive LED bits  */
	if (is_enclosure_led(spcn_cmd->loc_code)) {
		if (spcn_cmd->command == LED_COMMAND_FAULT) {
			if (spcn_cmd->state == LED_STATE_ON)
				led->excl_bit |= FSP_LED_EXCL_FAULT;
			if (spcn_cmd->state == LED_STATE_OFF)
				led->excl_bit &= ~FSP_LED_EXCL_FAULT;
		}

		if (spcn_cmd->command == LED_COMMAND_IDENTIFY) {
			if (spcn_cmd->state == LED_STATE_ON)
				led->excl_bit |= FSP_LED_EXCL_IDENTIFY;
			if (spcn_cmd->state == LED_STATE_OFF)
				led->excl_bit &= ~FSP_LED_EXCL_IDENTIFY;
		}
	}

	/* LED FAULT commad */
	if (spcn_cmd->command == LED_COMMAND_FAULT) {
		if (spcn_cmd->state == LED_STATE_ON)
			sled.state |= SPCN_LED_FAULT_MASK;
		if (spcn_cmd->state == LED_STATE_OFF)
			sled.state &= ~SPCN_LED_FAULT_MASK;
	}

	/* LED IDENTIFY command */
	if (spcn_cmd->command == LED_COMMAND_IDENTIFY) {
		if (spcn_cmd->state == LED_STATE_ON)
			sled.state |= SPCN_LED_IDENTIFY_MASK;
		if (spcn_cmd->state == LED_STATE_OFF)
			sled.state &= ~SPCN_LED_IDENTIFY_MASK;
	}

	/* Write into SPCN TCE buffer */
	buf_write(buf, u8, sled.lc_len);	 /* Location code length */
	strncpy(buf, sled.lc_code, sled.lc_len); /* Location code */
	buf += sled.lc_len;
	buf_write(buf, u16, sled.state);	/* LED state */

	msg = fsp_mkmsg(FSP_CMD_SPCN_PASSTHRU, 4,
			SPCN_ADDR_MODE_CEC_NODE, cmd_hdr, 0, PSI_DMA_LED_BUF);
	if (!msg) {
		cmd |= FSP_STATUS_GENERIC_ERROR;
		rc = -1;
		goto update_fail;
	}

	/*
	 * Update the local lists based on the attempted SPCN command to
	 * set/reset an individual led (CEC or ENCL).
	 */
	update_led_list(spcn_cmd->loc_code, sled.state, led->excl_bit);
	msg->user_data = spcn_cmd;

	rc = fsp_queue_msg(msg, fsp_spcn_set_led_completion);
	if (rc != OPAL_SUCCESS) {
		cmd |= FSP_STATUS_GENERIC_ERROR;
		fsp_freemsg(msg);
		/* Revert LED state update */
		update_led_list(spcn_cmd->loc_code, spcn_cmd->ckpt_status,
				spcn_cmd->ckpt_excl_bit);
	}

update_fail:
	if (rc) {
		log_simple_error(&e_info(OPAL_RC_LED_STATE),
				 "Set led state failed at LC=%s\n",
				 spcn_cmd->loc_code);

		if (spcn_cmd->cmd_src == SPCN_SRC_FSP)
			fsp_set_led_response(cmd);

		if (spcn_cmd->cmd_src == SPCN_SRC_OPAL)
			opal_led_update_complete(spcn_cmd->async_token,
						 OPAL_INTERNAL_ERROR);
	}

	unlock(&led_lock);
	return rc;
}

/*
 * process_led_state_change
 *
 * If the command queue is empty, it sets the 'spcn_cmd_complete' as true
 * and just returns. Else it pops one element from the command queue
 * and processes the command for the requested LED state change.
 */
static int process_led_state_change(void)
{
	struct led_set_cmd *spcn_cmd;
	int rc = 0;

	/*
	 * The command queue is empty. This will only
	 * happen during the SPCN command callback path
	 * in which case we set 'spcn_cmd_complete' as true.
	 */
	lock(&spcn_cmd_lock);
	if (list_empty(&spcn_cmdq)) {
		spcn_cmd_complete = true;
		unlock(&spcn_cmd_lock);
		return rc;
	}

	spcn_cmd = list_pop(&spcn_cmdq, struct led_set_cmd, link);
	unlock(&spcn_cmd_lock);

	if (is_sai_loc_code(spcn_cmd->loc_code))
		rc = fsp_set_sai(spcn_cmd);
	else
		rc = fsp_msg_set_led_state(spcn_cmd);

	if (rc) {
		free(spcn_cmd);
		process_led_state_change();
	}

	return rc;
}

/*
 * queue_led_state_change
 *
 * FSP async command or OPAL based request for LED state change gets queued
 * up in the command queue. If no previous SPCN command is pending, then it
 * immediately pops up one element from the list and processes it. If previous
 * SPCN commands are still pending then it just queues up and return. When the
 * SPCN command callback gets to execute, it processes one element from the
 * list and keeps the chain execution going. At last when there are no elements
 * in the command queue it sets 'spcn_cmd_complete' as true again.
 */
static int queue_led_state_change(char *loc_code, u8 command,
				  u8 state, int cmd_src, uint64_t async_token)
{
	struct led_set_cmd *cmd;
	int rc = 0;

	/* New request node */
	cmd = zalloc(sizeof(struct led_set_cmd));
	if (!cmd) {
		/**
		 * @fwts-label FSPLEDRequestMallocFail
		 * @fwts-advice OPAL failed to allocate memory for FSP LED
		 * command. Likely an OPAL bug led to out of memory.
		 */
		prlog(PR_ERR, "SPCN set command node allocation failed\n");
		return -1;
	}

	/* Save the request */
	strncpy(cmd->loc_code, loc_code, LOC_CODE_SIZE - 1);
	cmd->command = command;
	cmd->state = state;
	cmd->cmd_src = cmd_src;
	cmd->async_token = async_token;

	/* Add to the queue */
	lock(&spcn_cmd_lock);
	list_add_tail(&spcn_cmdq,  &cmd->link);

	/* No previous SPCN command pending */
	if (spcn_cmd_complete) {
		spcn_cmd_complete = false;
		unlock(&spcn_cmd_lock);
		rc = process_led_state_change();
		return rc;
	}

	unlock(&spcn_cmd_lock);
	return rc;
}

/*
 * Write single location code information into the TCE outbound buffer
 *
 * Data layout
 *
 * 2 bytes - Length of location code structure
 * 4 bytes - CCIN in ASCII
 * 1 byte  - Resource status flag
 * 1 byte  - Indicator state
 * 1 byte  - Raw loc code length
 * 1 byte  - Loc code field size
 * Field size byte - Null terminated ASCII string padded to 4 byte boundary
 *
 */
static u32 fsp_push_data_to_tce(struct fsp_led_data *led, u8 *out_data,
				u32 total_size)
{
	struct fsp_loc_code_data lcode;

	/* CCIN value is irrelevant */
	lcode.ccin = 0x0;

	lcode.status = FSP_IND_NOT_IMPLMNTD;

	if (led->parms & SPCN_LED_IDENTIFY_MASK)
		lcode.status = FSP_IND_IMPLMNTD;

	/* LED indicator status */
	lcode.ind_state = FSP_IND_INACTIVE;
	if (led->status & SPCN_LED_IDENTIFY_MASK)
		lcode.ind_state |= FSP_IND_IDENTIFY_ACTV;
	if (led->status & SPCN_LED_FAULT_MASK)
		lcode.ind_state |= FSP_IND_FAULT_ACTV;

	/* Location code */
	memset(lcode.loc_code, 0, LOC_CODE_SIZE);
	lcode.raw_len = strlen(led->loc_code);
	strncpy(lcode.loc_code, led->loc_code, lcode.raw_len);
	lcode.fld_sz = sizeof(lcode.loc_code);

	/* Rest of the structure */
	lcode.size = sizeof(lcode);
	lcode.status &= 0x0f;

	/*
	 * Check for outbound buffer overflow. If there are still
	 * more LEDs to be sent across to FSP, don't send, ignore.
	 */
	if ((total_size + lcode.size) > PSI_DMA_LOC_COD_BUF_SZ)
		return 0;

	/* Copy over to the buffer */
	memcpy(out_data, &lcode, sizeof(lcode));

	return lcode.size;
}

/*
 * Send out LED information structure pointed by "loc_code"
 * to FSP through the PSI DMA mapping. Buffer layout structure
 * must be followed.
 */
static void fsp_ret_loc_code_list(u16 req_type, char *loc_code)
{
	struct fsp_led_data *led, *next;
	struct fsp_msg *msg;

	u8 *data;			/* Start of TCE mapped buffer */
	u8 *out_data;			/* Start of location code data */
	u32 bytes_sent = 0, total_size = 0;
	u16 header_size = 0, flags = 0;

	if (loc_code_list_buffer == NULL) {
		prerror("No loc_code_list_buffer\n");
		return;
	}

	/* Init the addresses */
	data = loc_code_list_buffer;
	out_data = NULL;

	/* Unmapping through FSP_CMD_RET_LOC_BUFFER command */
	fsp_tce_map(PSI_DMA_LOC_COD_BUF, (void *)data, PSI_DMA_LOC_COD_BUF_SZ);
	out_data = data + 8;

	/* CEC LED list */
	list_for_each_safe(&cec_ledq, led, next, link) {
		/*
		 * When the request type is system wide led list
		 * i.e GET_LC_CMPLT_SYS, send the entire contents
		 * of the CEC list including both all descendents
		 * and all of their enclosures.
		 */

		if (req_type == GET_LC_ENCLOSURES)
			break;

		if (req_type == GET_LC_ENCL_DESCENDANTS) {
			if (strstr(led->loc_code, loc_code) == NULL)
				continue;
		}

		if (req_type == GET_LC_SINGLE_LOC_CODE) {
			if (strcmp(led->loc_code, loc_code))
				continue;
		}

		/* Push the data into TCE buffer */
		bytes_sent = fsp_push_data_to_tce(led, out_data, total_size);

		/* Advance the TCE pointer */
		out_data += bytes_sent;
		total_size += bytes_sent;
	}

	/* Enclosure LED list */
	if (req_type == GET_LC_ENCLOSURES) {
		list_for_each_safe(&encl_ledq, led, next, link) {

			/* Push the data into TCE buffer */
			bytes_sent = fsp_push_data_to_tce(led,
							  out_data, total_size);

			/* Advance the TCE pointer */
			out_data += bytes_sent;
			total_size += bytes_sent;
		}
	}

	/* Count from 'data' instead of 'data_out' */
	total_size += 8;
	memcpy(data, &total_size, sizeof(total_size));

	header_size = OUTBUF_HEADER_SIZE;
	memcpy(data + sizeof(total_size), &header_size, sizeof(header_size));

	if (req_type == GET_LC_ENCL_DESCENDANTS)
		flags = 0x8000;

	memcpy(data +  sizeof(total_size) + sizeof(header_size), &flags,
	       sizeof(flags));
	msg = fsp_mkmsg(FSP_RSP_GET_LED_LIST, 3, 0,
			PSI_DMA_LOC_COD_BUF, total_size);
	if (!msg) {
		prerror("Failed to allocate FSP_RSP_GET_LED_LIST.\n");
	} else {
		if (fsp_queue_msg(msg, fsp_freemsg)) {
			fsp_freemsg(msg);
			prerror("Failed to queue FSP_RSP_GET_LED_LIST\n");
		}
	}
}

/*
 * FSP async command: FSP_CMD_GET_LED_LIST
 *
 * (1) FSP sends the list of location codes through inbound buffer
 * (2) HV sends the status of those location codes through outbound buffer
 *
 * Inbound buffer data layout (loc code request structure)
 *
 * 2 bytes - Length of entire structure
 * 2 bytes - Request type
 * 1 byte - Raw length of location code
 * 1 byte - Location code field size
 * `Field size` bytes - NULL terminated ASCII location code string
 */
static void fsp_get_led_list(struct fsp_msg *msg)
{
	struct fsp_loc_code_req req;
	u32 tce_token = msg->data.words[1];
	void *buf;

	/* Parse inbound buffer */
	buf = fsp_inbound_buf_from_tce(tce_token);
	if (!buf) {
		struct fsp_msg *msg;
		msg = fsp_mkmsg(FSP_RSP_GET_LED_LIST | FSP_STATUS_INVALID_DATA,
				0);
		if (!msg) {
			prerror("Failed to allocate FSP_RSP_GET_LED_LIST"
				" | FSP_STATUS_INVALID_DATA\n");
		} else {
			if (fsp_queue_msg(msg, fsp_freemsg)) {
				fsp_freemsg(msg);
				prerror("Failed to queue "
					"FSP_RSP_GET_LED_LIST |"
					" FSP_STATUS_INVALID_DATA\n");
			}
		}
		return;
	}
	memcpy(&req, buf, sizeof(req));

	prlog(PR_TRACE, "Request for loc code list type 0x%04x LC=%s\n",
	       req.req_type, req.loc_code);

	fsp_ret_loc_code_list(req.req_type, req.loc_code);
}

/*
 * FSP async command: FSP_CMD_RET_LOC_BUFFER
 *
 * With this command FSP returns ownership of the outbound buffer
 * used by Sapphire to pass the indicator list previous time. That
 * way FSP tells Sapphire that it has consumed all the data present
 * on the outbound buffer and Sapphire can reuse it for next request.
 */
static void fsp_free_led_list_buf(struct fsp_msg *msg)
{
	u32 tce_token = msg->data.words[1];
	u32 cmd = FSP_RSP_RET_LED_BUFFER;
	struct fsp_msg *resp;

	/* Token does not point to outbound buffer */
	if (tce_token != PSI_DMA_LOC_COD_BUF) {
		log_simple_error(&e_info(OPAL_RC_LED_BUFF),
			"Invalid tce token from FSP\n");
		cmd |=  FSP_STATUS_GENERIC_ERROR;
		resp = fsp_mkmsg(cmd, 0);
		if (!resp) {
			prerror("Failed to allocate FSP_RSP_RET_LED_BUFFER"
				"| FSP_STATUS_GENERIC_ERROR\n");
			return;
		}

		if (fsp_queue_msg(resp, fsp_freemsg)) {
			fsp_freemsg(resp);
			prerror("Failed to queue "
				"RET_LED_BUFFER|ERROR\n");
		}
		return;
	}

	/* Unmap the location code DMA buffer */
	fsp_tce_unmap(PSI_DMA_LOC_COD_BUF, PSI_DMA_LOC_COD_BUF_SZ);

	resp = fsp_mkmsg(cmd, 0);
	if (!resp) {
		prerror("Failed to allocate FSP_RSP_RET_LED_BUFFER\n");
		return;
	}
	if (fsp_queue_msg(resp, fsp_freemsg)) {
		fsp_freemsg(resp);
		prerror("Failed to queue FSP_RSP_RET_LED_BUFFER\n");
	}
}

static void fsp_ret_led_state(char *loc_code)
{
	bool found = false;
	u8 ind_state = 0;
	u32 cmd = FSP_RSP_GET_LED_STATE;
	struct fsp_led_data *led, *next;
	struct fsp_msg *msg;

	if (is_sai_loc_code(loc_code)) {
		if (sai_data.state & OPAL_SLOT_LED_STATE_ON)
			ind_state = FSP_IND_FAULT_ACTV;
		found = true;
	} else {
		list_for_each_safe(&cec_ledq, led, next, link) {
			if (strcmp(loc_code, led->loc_code))
				continue;

			/* Found the location code */
			if (led->status & SPCN_LED_IDENTIFY_MASK)
				ind_state |= FSP_IND_IDENTIFY_ACTV;
			if (led->status & SPCN_LED_FAULT_MASK)
				ind_state |= FSP_IND_FAULT_ACTV;

			found = true;
			break;
		}
	}

	/* Location code not found */
	if (!found) {
		log_simple_error(&e_info(OPAL_RC_LED_LC),
				 "Could not find the location code LC=%s\n",
				 loc_code);
		cmd |= FSP_STATUS_INVALID_LC;
		ind_state = 0xff;
	}

	msg = fsp_mkmsg(cmd, 1, ind_state);
	if (!msg) {
		prerror("Couldn't alloc FSP_RSP_GET_LED_STATE\n");
		return;
	}

	if (fsp_queue_msg(msg, fsp_freemsg)) {
		fsp_freemsg(msg);
		prerror("Couldn't queue FSP_RSP_GET_LED_STATE\n");
	}
}

/*
 * FSP async command: FSP_CMD_GET_LED_STATE
 *
 * With this command FSP query the state for any given LED
 */
static void fsp_get_led_state(struct fsp_msg *msg)
{
	struct fsp_get_ind_state_req req;
	u32 tce_token = msg->data.words[1];
	void *buf;

	/* Parse the inbound buffer */
	buf = fsp_inbound_buf_from_tce(tce_token);
	if (!buf) {
		struct fsp_msg *msg;
		msg = fsp_mkmsg(FSP_RSP_GET_LED_STATE |
				FSP_STATUS_INVALID_DATA, 0);
		if (!msg) {
			prerror("Failed to allocate FSP_RSP_GET_LED_STATE"
				" | FSP_STATUS_INVALID_DATA\n");
			return;
		}
		if (fsp_queue_msg(msg, fsp_freemsg)) {
			fsp_freemsg(msg);
			prerror("Failed to queue FSP_RSP_GET_LED_STATE"
				" | FSP_STATUS_INVALID_DATA\n");
		}
		return;
	}
	memcpy(&req, buf, sizeof(req));

	prlog(PR_TRACE, "%s: tce=0x%08x buf=%p rq.sz=%d rq.lc_len=%d"
	      " rq.fld_sz=%d LC: %02x %02x %02x %02x....\n", __func__,
	      tce_token, buf, req.size, req.lc_len, req.fld_sz,
	      req.loc_code[0], req.loc_code[1],
	      req.loc_code[2], req.loc_code[3]);

	/* Bound check */
	if (req.lc_len >= LOC_CODE_SIZE) {
		log_simple_error(&e_info(OPAL_RC_LED_LC),
				 "Loc code too large in %s: %d bytes\n",
				 __func__, req.lc_len);
		req.lc_len = LOC_CODE_SIZE - 1;
	}
	/* Ensure NULL termination */
	req.loc_code[req.lc_len] = 0;

	/* Do the deed */
	fsp_ret_led_state(req.loc_code);
}

/*
 * FSP async command: FSP_CMD_SET_LED_STATE
 *
 * With this command FSP sets/resets the state for any given LED
 */
static void fsp_set_led_state(struct fsp_msg *msg)
{
	struct fsp_set_ind_state_req req;
	struct fsp_led_data *led, *next;
	u32 tce_token = msg->data.words[1];
	bool command, state;
	void *buf;
	int rc;

	/* Parse the inbound buffer */
	buf = fsp_inbound_buf_from_tce(tce_token);
	if (!buf) {
		fsp_set_led_response(FSP_RSP_SET_LED_STATE |
				     FSP_STATUS_INVALID_DATA);
		return;
	}
	memcpy(&req, buf, sizeof(req));

	prlog(PR_TRACE, "%s: tce=0x%08x buf=%p rq.sz=%d rq.typ=0x%04x"
	      " rq.lc_len=%d rq.fld_sz=%d LC: %02x %02x %02x %02x....\n",
	      __func__, tce_token, buf, req.size, req.lc_len, req.fld_sz,
	      req.req_type,
	      req.loc_code[0], req.loc_code[1],
	      req.loc_code[2], req.loc_code[3]);

	/* Bound check */
	if (req.lc_len >= LOC_CODE_SIZE) {
		log_simple_error(&e_info(OPAL_RC_LED_LC),
				 "Loc code too large in %s: %d bytes\n",
				 __func__, req.lc_len);
		req.lc_len = LOC_CODE_SIZE - 1;
	}
	/* Ensure NULL termination */
	req.loc_code[req.lc_len] = 0;

	/* Decode command */
	command =  (req.ind_state & LOGICAL_IND_STATE_MASK) ?
		LED_COMMAND_FAULT : LED_COMMAND_IDENTIFY;
	state = (req.ind_state & ACTIVE_LED_STATE_MASK) ?
		LED_STATE_ON : LED_STATE_OFF;

	/* Handle requests */
	switch (req.req_type) {
	case SET_IND_ENCLOSURE:
		list_for_each_safe(&cec_ledq, led, next, link) {
			/* Only descendants of the same enclosure */
			if (!strstr(led->loc_code, req.loc_code))
				continue;

			/* Skip the enclosure */
			if (!strcmp(led->loc_code, req.loc_code))
				continue;

			rc = queue_led_state_change(led->loc_code, command,
						    state, SPCN_SRC_FSP, 0);
			if (rc != 0)
				fsp_set_led_response(FSP_RSP_SET_LED_STATE |
						     FSP_STATUS_GENERIC_ERROR);
		}
		break;
	case SET_IND_SINGLE_LOC_CODE:
		/* Set led state for single descendent led */
		rc = queue_led_state_change(req.loc_code,
					    command, state, SPCN_SRC_FSP, 0);
		if (rc != 0)
			fsp_set_led_response(FSP_RSP_SET_LED_STATE |
					     FSP_STATUS_GENERIC_ERROR);
		break;
	default:
		fsp_set_led_response(FSP_RSP_SET_LED_STATE |
				     FSP_STATUS_NOT_SUPPORTED);
		break;
	}
}

/* Handle received indicator message from FSP */
static bool fsp_indicator_message(u32 cmd_sub_mod, struct fsp_msg *msg)
{
	u32 cmd;
	struct fsp_msg *resp;

	/* LED support not available yet */
	if (led_support != LED_STATE_PRESENT) {
		log_simple_error(&e_info(OPAL_RC_LED_SUPPORT),
			"Indicator message while LED support not"
			" available yet\n");
		return false;
	}

	switch (cmd_sub_mod) {
	case FSP_CMD_GET_LED_LIST:
		prlog(PR_TRACE, "FSP_CMD_GET_LED_LIST command received\n");
		fsp_get_led_list(msg);
		return true;
	case FSP_CMD_RET_LED_BUFFER:
		prlog(PR_TRACE, "FSP_CMD_RET_LED_BUFFER command received\n");
		fsp_free_led_list_buf(msg);
		return true;
	case FSP_CMD_GET_LED_STATE:
		prlog(PR_TRACE, "FSP_CMD_GET_LED_STATE command received\n");
		fsp_get_led_state(msg);
		return true;
	case FSP_CMD_SET_LED_STATE:
		prlog(PR_TRACE, "FSP_CMD_SET_LED_STATE command received\n");
		fsp_set_led_state(msg);
		return true;
	/*
	 * FSP async sub commands which have not been implemented.
	 * For these async sub commands, print for the log and ack
	 * the field service processor with a generic error.
	 */
	case FSP_CMD_GET_MTMS_LIST:
		prlog(PR_TRACE, "FSP_CMD_GET_MTMS_LIST command received\n");
		cmd = FSP_RSP_GET_MTMS_LIST;
		break;
	case FSP_CMD_RET_MTMS_BUFFER:
		prlog(PR_TRACE, "FSP_CMD_RET_MTMS_BUFFER command received\n");
		cmd = FSP_RSP_RET_MTMS_BUFFER;
		break;
	case FSP_CMD_SET_ENCL_MTMS:
		prlog(PR_TRACE, "FSP_CMD_SET_MTMS command received\n");
		cmd = FSP_RSP_SET_ENCL_MTMS;
		break;
	case FSP_CMD_CLR_INCT_ENCL:
		prlog(PR_TRACE, "FSP_CMD_CLR_INCT_ENCL command received\n");
		cmd = FSP_RSP_CLR_INCT_ENCL;
		break;
	case FSP_CMD_ENCL_MCODE_INIT:
		prlog(PR_TRACE, "FSP_CMD_ENCL_MCODE_INIT command received\n");
		cmd = FSP_RSP_ENCL_MCODE_INIT;
		break;
	case FSP_CMD_ENCL_MCODE_INTR:
		prlog(PR_TRACE, "FSP_CMD_ENCL_MCODE_INTR command received\n");
		cmd = FSP_RSP_ENCL_MCODE_INTR;
		break;
	case FSP_CMD_ENCL_POWR_TRACE:
		prlog(PR_TRACE, "FSP_CMD_ENCL_POWR_TRACE command received\n");
		cmd = FSP_RSP_ENCL_POWR_TRACE;
		break;
	case FSP_CMD_RET_ENCL_TRACE_BUFFER:
		prlog(PR_TRACE, "FSP_CMD_RET_ENCL_TRACE_BUFFER command received\n");
		cmd = FSP_RSP_RET_ENCL_TRACE_BUFFER;
		break;
	case FSP_CMD_GET_SPCN_LOOP_STATUS:
		prlog(PR_TRACE, "FSP_CMD_GET_SPCN_LOOP_STATUS command received\n");
		cmd = FSP_RSP_GET_SPCN_LOOP_STATUS;
		break;
	case FSP_CMD_INITIATE_LAMP_TEST:
		/* XXX: FSP ACK not required for this sub command */
		prlog(PR_TRACE, "FSP_CMD_INITIATE_LAMP_TEST command received\n");
		return true;
	default:
		return false;
	}
	cmd |= FSP_STATUS_GENERIC_ERROR;
	resp = fsp_mkmsg(cmd, 0);
	if (!resp) {
		prerror("Failed to allocate FSP_STATUS_GENERIC_ERROR\n");
		return false;
	}
	if (fsp_queue_msg(resp, fsp_freemsg)) {
		fsp_freemsg(resp);
		prerror("Failed to queue FSP_STATUS_GENERIC_ERROR\n");
		return false;
	}
	return true;
}

/* Indicator class client */
static struct fsp_client fsp_indicator_client = {
	.message = fsp_indicator_message,
};


static int fsp_opal_get_sai(u64 *led_mask, u64 *led_value)
{
	*led_mask |= OPAL_SLOT_LED_STATE_ON << OPAL_SLOT_LED_TYPE_ATTN;
	if (sai_data.state & OPAL_SLOT_LED_STATE_ON)
		*led_value |=
			OPAL_SLOT_LED_STATE_ON << OPAL_SLOT_LED_TYPE_ATTN;

	return OPAL_SUCCESS;
}

static int fsp_opal_set_sai(uint64_t async_token, char *loc_code,
			    const u64 led_mask, const u64 led_value)
{
	int state = LED_STATE_OFF;

	if (!((led_mask >> OPAL_SLOT_LED_TYPE_ATTN) & OPAL_SLOT_LED_STATE_ON))
		return OPAL_PARAMETER;

	if ((led_value >> OPAL_SLOT_LED_TYPE_ATTN) & OPAL_SLOT_LED_STATE_ON)
		state = LED_STATE_ON;

	return queue_led_state_change(loc_code, 0,
				      state, SPCN_SRC_OPAL, async_token);
}

/*
 * fsp_opal_leds_get_ind (OPAL_LEDS_GET_INDICATOR)
 *
 * Argument	 Description				Updated By
 * --------	 -----------				----------
 * loc_code	 Location code of the LEDs		(Host)
 * led_mask	 LED types whose status is available	(OPAL)
 * led_value	 Status of the available LED types	(OPAL)
 * max_led_type  Maximum number of supported LED types	(Host/OPAL)
 *
 * The host will pass the location code of the LED types (loc_code) and
 * maximum number of LED types it understands (max_led_type). OPAL will
 * update the 'led_mask' with set bits pointing to LED types whose status
 * is available and updates the 'led_value' with actual status. OPAL checks
 * the 'max_led_type' to understand whether the host is newer or older
 * compared to itself. In the case where the OPAL is newer compared
 * to host (OPAL's max_led_type > host's max_led_type), it will update
 * led_mask and led_value according to max_led_type requested by the host.
 * When the host is newer compared to the OPAL (host's max_led_type >
 * OPAL's max_led_type), OPAL updates 'max_led_type' to the maximum
 * number of LED type it understands and updates 'led_mask', 'led_value'
 * based on that maximum value of LED types.
 */
static int64_t fsp_opal_leds_get_ind(char *loc_code, u64 *led_mask,
				     u64 *led_value, u64 *max_led_type)
{
	bool supported = true;
	int64_t max;
	int rc;
	struct fsp_led_data *led;

	/* FSP not present */
	if (!fsp_present())
		return OPAL_HARDWARE;

	/* LED support not available */
	if (led_support != LED_STATE_PRESENT)
		return OPAL_HARDWARE;

	/* Adjust max LED type */
	if (*max_led_type > OPAL_SLOT_LED_TYPE_MAX) {
		supported = false;
		*max_led_type = OPAL_SLOT_LED_TYPE_MAX;
	}

	/* Invalid parameter */
	max = *max_led_type;
	if (max <= 0)
		return OPAL_PARAMETER;

	/* Get System attention indicator state */
	if (is_sai_loc_code(loc_code)) {
		rc = fsp_opal_get_sai(led_mask, led_value);
		return rc;
	}

	/* LED not found */
	led = fsp_find_cec_led(loc_code);
	if (!led)
		return OPAL_PARAMETER;

	*led_mask = 0;
	*led_value = 0;

	/* Identify LED */
	--max;
	*led_mask |= OPAL_SLOT_LED_STATE_ON << OPAL_SLOT_LED_TYPE_ID;
	if (led->status & SPCN_LED_IDENTIFY_MASK)
		*led_value |=
			OPAL_SLOT_LED_STATE_ON << OPAL_SLOT_LED_TYPE_ID;

	/* Fault LED */
	if (!max)
		return OPAL_SUCCESS;

	--max;
	*led_mask |= OPAL_SLOT_LED_STATE_ON << OPAL_SLOT_LED_TYPE_FAULT;
	if (led->status & SPCN_LED_FAULT_MASK)
		*led_value |=
			OPAL_SLOT_LED_STATE_ON << OPAL_SLOT_LED_TYPE_FAULT;

	/* OPAL doesn't support all the LED type requested by payload */
	if (!supported)
		return OPAL_PARTIAL;

	return OPAL_SUCCESS;
}

/*
 * fsp_opal_leds_set_ind (OPAL_LEDS_SET_INDICATOR)
 *
 * Argument	 Description				Updated By
 * --------	 -----------				----------
 * loc_code	 Location code of the LEDs		(Host)
 * led_mask	 LED types whose status will be updated	(Host)
 * led_value	 Requested status of various LED types	(Host)
 * max_led_type  Maximum number of supported LED types	(Host/OPAL)
 *
 * The host will pass the location code of the LED types, mask, value
 * and maximum number of LED types it understands. OPAL will update
 * LED status for all the LED types mentioned in the mask with their
 * value mentioned. OPAL checks the 'max_led_type' to understand
 * whether the host is newer or older compared to itself. In case where
 * the OPAL is newer compared to the host (OPAL's max_led_type >
 * host's max_led_type), it updates LED status based on max_led_type
 * requested from the host. When the host is newer compared to the OPAL
 * (host's max_led_type > OPAL's max_led_type), OPAL updates
 * 'max_led_type' to the maximum number of LED type it understands and
 * then it updates LED status based on that updated  maximum value of LED
 * types. Host needs to check the returned updated value of max_led_type
 * to figure out which part of it's request got served and which ones got
 * ignored.
 */
static int64_t fsp_opal_leds_set_ind(uint64_t async_token,
				     char *loc_code, const u64 led_mask,
				     const u64 led_value, u64 *max_led_type)
{
	bool supported = true;
	int command, state, rc = OPAL_SUCCESS;
	int64_t max;
	struct fsp_led_data *led;

	/* FSP not present */
	if (!fsp_present())
		return OPAL_HARDWARE;

	/* LED support not available */
	if (led_support != LED_STATE_PRESENT)
		return OPAL_HARDWARE;

	/* Adjust max LED type */
	if (*max_led_type > OPAL_SLOT_LED_TYPE_MAX) {
		supported = false;
		*max_led_type = OPAL_SLOT_LED_TYPE_MAX;
	}

	max = *max_led_type;
	/* Invalid parameter */
	if (max <= 0)
		return OPAL_PARAMETER;

	/* Set System attention indicator state */
	if (is_sai_loc_code(loc_code)) {
		supported = true;
		rc = fsp_opal_set_sai(async_token,
				      loc_code, led_mask, led_value);
		goto success;
	}

	/* LED not found */
	led = fsp_find_cec_led(loc_code);
	if (!led)
		return OPAL_PARAMETER;

	/* Indentify LED mask */
	--max;

	if ((led_mask >> OPAL_SLOT_LED_TYPE_ID) & OPAL_SLOT_LED_STATE_ON) {
		supported = true;

		command = LED_COMMAND_IDENTIFY;
		state = LED_STATE_OFF;
		if ((led_value >> OPAL_SLOT_LED_TYPE_ID)
					& OPAL_SLOT_LED_STATE_ON)
			state = LED_STATE_ON;

		rc = queue_led_state_change(loc_code, command,
					    state, SPCN_SRC_OPAL, async_token);
	}

	if (!max)
		goto success;

	/* Fault LED mask */
	--max;
	if ((led_mask >> OPAL_SLOT_LED_TYPE_FAULT) & OPAL_SLOT_LED_STATE_ON) {
		supported = true;

		command = LED_COMMAND_FAULT;
		state = LED_STATE_OFF;
		if ((led_value >> OPAL_SLOT_LED_TYPE_FAULT)
					& OPAL_SLOT_LED_STATE_ON)
			state = LED_STATE_ON;

		rc = queue_led_state_change(loc_code, command,
					    state, SPCN_SRC_OPAL, async_token);
	}

success:
	/* Unsupported LED type */
	if (!supported)
		return OPAL_UNSUPPORTED;

	if (rc == OPAL_SUCCESS)
		rc = OPAL_ASYNC_COMPLETION;
	else
		rc = OPAL_INTERNAL_ERROR;

	return rc;
}

/* Get LED node from device tree */
static struct dt_node *dt_get_led_node(void)
{
	struct dt_node *pled;

	if (!opal_node) {
		prlog(PR_WARNING, "OPAL parent device node not available\n");
		return NULL;
	}

	pled = dt_find_by_path(opal_node, DT_PROPERTY_LED_NODE);
	if (!pled)
		prlog(PR_WARNING, "Parent device node not available\n");

	return pled;
}

/* Get System attention indicator location code from device tree */
static void dt_get_sai_loc_code(void)
{
	struct dt_node *pled, *child;
	const char *led_type = NULL;

	memset(sai_data.loc_code, 0, LOC_CODE_SIZE);

	pled = dt_get_led_node();
	if (!pled)
		return;

	list_for_each(&pled->children, child, list) {
		led_type = dt_prop_get(child, DT_PROPERTY_LED_TYPES);
		if (!led_type)
			continue;

		if (strcmp(led_type, LED_TYPE_ATTENTION))
			continue;

		memcpy(sai_data.loc_code, child->name, LOC_CODE_SIZE - 1);

		prlog(PR_TRACE, "SAI Location code = %s\n", sai_data.loc_code);
		return;
	}
}

/*
 * create_led_device_node
 *
 * Creates the system parent LED device node and all individual
 * child LED device nodes under it. This is called right before
 * starting the payload (Linux) to ensure that the SPCN command
 * sequence to fetch the LED location code list has been finished
 * and to have a better chance of creating the deviced nodes.
 */
void create_led_device_nodes(void)
{
	const char *led_mode = NULL;
	struct fsp_led_data *led, *next;
	struct dt_node *pled, *cled;

	if (!fsp_present())
		return;

	/* Make sure LED list read is completed */
	while (led_support == LED_STATE_READING)
		opal_run_pollers();

	if (led_support == LED_STATE_ABSENT) {
		prlog(PR_WARNING, "LED support not available, \
		      hence device tree nodes will not be created\n");
		return;
	}

	/* Get LED node */
	pled = dt_get_led_node();
	if (!pled)
		return;

	/* Check if already populated (fast-reboot) */
	if (dt_has_node_property(pled, "compatible", NULL))
		return;
	dt_add_property_strings(pled, "compatible", DT_PROPERTY_LED_COMPATIBLE);

	led_mode = dt_prop_get(pled, DT_PROPERTY_LED_MODE);
	if (!led_mode) {
		prlog(PR_WARNING, "Unknown LED operating mode\n");
		return;
	}

	/* LED child nodes */
	list_for_each_safe(&cec_ledq, led, next, link) {
		/* Duplicate LED location code */
		if (dt_find_by_path(pled, led->loc_code)) {
			prlog(PR_WARNING, "duplicate location code %s\n",
			      led->loc_code);
			continue;
		}

		cled = dt_new(pled, led->loc_code);
		if (!cled) {
			prlog(PR_WARNING, "Child device node creation "
			      "failed\n");
			continue;
		}

		if (!strcmp(led_mode, LED_MODE_LIGHT_PATH))
			dt_add_property_strings(cled, DT_PROPERTY_LED_TYPES,
						LED_TYPE_IDENTIFY,
						LED_TYPE_FAULT);
		else
			dt_add_property_strings(cled, DT_PROPERTY_LED_TYPES,
						LED_TYPE_IDENTIFY);
	}
}

/*
 * Process the received LED data from SPCN
 *
 * Every LED state data is added into the CEC list. If the location
 * code is a enclosure type, its added into the enclosure list as well.
 *
 */
static void fsp_process_leds_data(u16 len)
{
	struct fsp_led_data *led_data = NULL;
	void *buf = NULL;

	/*
	 * Process the entire captured data from the last command
	 *
	 * TCE mapped 'led_buffer' contains the fsp_led_data structure
	 * one after the other till the total length 'len'.
	 *
	 */
	buf = led_buffer;
	while (len) {
		/* Prepare */
		led_data = zalloc(sizeof(struct fsp_led_data));
		assert(led_data);

		/* Resource ID */
		buf_read(buf, u16, &led_data->rid);
		len -= sizeof(led_data->rid);

		/* Location code length */
		buf_read(buf, u8, &led_data->lc_len);
		len -= sizeof(led_data->lc_len);

		if (led_data->lc_len == 0) {
			free(led_data);
			break;
		}

		/* Location code */
		strncpy(led_data->loc_code, buf, led_data->lc_len);
		strcat(led_data->loc_code, "\0");

		buf += led_data->lc_len;
		len -= led_data->lc_len;

		/* Parameters */
		buf_read(buf, u16, &led_data->parms);
		len -=  sizeof(led_data->parms);

		/* Status */
		buf_read(buf, u16, &led_data->status);
		len -=  sizeof(led_data->status);

		/*
		 * This is Enclosure LED's location code, need to go
		 * inside the enclosure LED list as well.
		 */
		if (!strstr(led_data->loc_code, "-")) {
			struct fsp_led_data *encl_led_data = NULL;
			encl_led_data = zalloc(sizeof(struct fsp_led_data));
			assert(encl_led_data);

			/* copy over the original */
			encl_led_data->rid = led_data->rid;
			encl_led_data->lc_len = led_data->lc_len;
			strncpy(encl_led_data->loc_code, led_data->loc_code,
				led_data->lc_len);
			encl_led_data->loc_code[led_data->lc_len] = '\0';
			encl_led_data->parms = led_data->parms;
			encl_led_data->status = led_data->status;

			/* Add to the list of enclosure LEDs */
			list_add_tail(&encl_ledq, &encl_led_data->link);
		}

		/* Push this onto the list */
		list_add_tail(&cec_ledq, &led_data->link);
	}
}

/* Replay the SPCN command */
static void replay_spcn_cmd(u32 last_spcn_cmd)
{
	u32 cmd_hdr = 0;
	int rc = -1;

	/* Reached threshold */
	if (replay == SPCN_REPLAY_THRESHOLD) {
		replay = 0;
		led_support = LED_STATE_ABSENT;
		return;
	}

	replay++;
	if (last_spcn_cmd == SPCN_MOD_PRS_LED_DATA_FIRST) {
		cmd_hdr = SPCN_MOD_PRS_LED_DATA_FIRST << 24 |
			SPCN_CMD_PRS << 16;
		rc = fsp_queue_msg(fsp_mkmsg(FSP_CMD_SPCN_PASSTHRU, 4,
					     SPCN_ADDR_MODE_CEC_NODE,
					     cmd_hdr, 0,
					     PSI_DMA_LED_BUF),
				   fsp_read_leds_data_complete);
		if (rc)
			prlog(PR_ERR, "Replay SPCN_MOD_PRS_LED_DATA_FIRST"
			      " command could not be queued\n");
	}

	if (last_spcn_cmd == SPCN_MOD_PRS_LED_DATA_SUB) {
		cmd_hdr = SPCN_MOD_PRS_LED_DATA_SUB << 24 | SPCN_CMD_PRS << 16;
		rc = fsp_queue_msg(fsp_mkmsg(FSP_CMD_SPCN_PASSTHRU, 4,
					     SPCN_ADDR_MODE_CEC_NODE, cmd_hdr,
					     0, PSI_DMA_LED_BUF),
				   fsp_read_leds_data_complete);
		if (rc)
			prlog(PR_ERR, "Replay SPCN_MOD_PRS_LED_DATA_SUB"
			      " command could not be queued\n");
	}

	/* Failed to queue MBOX message */
	if (rc)
		led_support = LED_STATE_ABSENT;
}

/*
 * FSP message response handler for following SPCN LED commands
 * which are used to fetch all of the LED data from SPCN
 *
 * 1. SPCN_MOD_PRS_LED_DATA_FIRST      --> First 1KB of LED data
 * 2. SPCN_MOD_PRS_LED_DATA_SUB        --> Subsequent 1KB of LED data
 *
 * Once the SPCN_RSP_STATUS_SUCCESS response code has been received
 * indicating the last batch of 1KB LED data is here, the list addition
 * process is now complete and we enable LED support for FSP async commands
 * and for OPAL interface.
 */
static void fsp_read_leds_data_complete(struct fsp_msg *msg)
{
	struct fsp_led_data *led, *next;
	struct fsp_msg *resp = msg->resp;
	u32 cmd_hdr = 0;
	int rc = 0;

	u32 msg_status = resp->word1 & 0xff00;
	u32 led_status = (resp->data.words[1] >> 24) & 0xff;
	u16 data_len = (u16)(resp->data.words[1] & 0xffff);

	if (msg_status != FSP_STATUS_SUCCESS) {
		log_simple_error(&e_info(OPAL_RC_LED_SUPPORT),
				 "FSP returned error %x LED not supported\n",
				 msg_status);
		/* LED support not available */
		led_support = LED_STATE_ABSENT;

		fsp_freemsg(msg);
		return;
	}

	/* SPCN command status */
	switch (led_status) {
	/* Last 1KB of LED data */
	case SPCN_RSP_STATUS_SUCCESS:
		prlog(PR_DEBUG, "SPCN_RSP_STATUS_SUCCESS: %d bytes received\n",
		      data_len);

		led_support = LED_STATE_PRESENT;

		/* Copy data to the local list */
		fsp_process_leds_data(data_len);

		/* LEDs captured on the system */
		prlog(PR_DEBUG, "CEC LEDs captured on the system:\n");
		list_for_each_safe(&cec_ledq, led, next, link) {
			prlog(PR_DEBUG,
			       "rid: %x\t"
			       "len: %x      "
			       "lcode: %-30s\t"
			       "parms: %04x\t"
			       "status: %04x\n",
			       led->rid,
			       led->lc_len,
			       led->loc_code,
			       led->parms,
			       led->status);
		}

		prlog(PR_DEBUG, "ENCL LEDs captured on the system:\n");
		list_for_each_safe(&encl_ledq, led, next, link) {
			prlog(PR_DEBUG,
			       "rid: %x\t"
			       "len: %x      "
			       "lcode: %-30s\t"
			       "parms: %04x\t"
			       "status: %04x\n",
			       led->rid,
			       led->lc_len,
			       led->loc_code,
			       led->parms,
			       led->status);
		}

		break;

	/* If more 1KB of LED data present */
	case SPCN_RSP_STATUS_COND_SUCCESS:
		prlog(PR_DEBUG, "SPCN_RSP_STATUS_COND_SUCCESS: %d bytes "
		      " received\n", data_len);

		/* Copy data to the local list */
		fsp_process_leds_data(data_len);

		/* Fetch the remaining data from SPCN */
		last_spcn_cmd = SPCN_MOD_PRS_LED_DATA_SUB;
		cmd_hdr = SPCN_MOD_PRS_LED_DATA_SUB << 24 | SPCN_CMD_PRS << 16;
		rc = fsp_queue_msg(fsp_mkmsg(FSP_CMD_SPCN_PASSTHRU, 4,
					     SPCN_ADDR_MODE_CEC_NODE,
					     cmd_hdr, 0, PSI_DMA_LED_BUF),
				   fsp_read_leds_data_complete);
		if (rc) {
			prlog(PR_ERR, "SPCN_MOD_PRS_LED_DATA_SUB command"
			      " could not be queued\n");

			led_support = LED_STATE_ABSENT;
		}
		break;

	/* Other expected error codes*/
	case SPCN_RSP_STATUS_INVALID_RACK:
	case SPCN_RSP_STATUS_INVALID_SLAVE:
	case SPCN_RSP_STATUS_INVALID_MOD:
	case SPCN_RSP_STATUS_STATE_PROHIBIT:
	case SPCN_RSP_STATUS_UNKNOWN:
	default:
		/* Replay the previous SPCN command */
		replay_spcn_cmd(last_spcn_cmd);
	}
	fsp_freemsg(msg);
}

/*
 * Init the LED state
 *
 * This is called during the host boot process. This is the place where
 * we figure out all the LEDs present on the system, their state and then
 * create structure out of those information and popullate two master lists.
 * One for all the LEDs on the CEC and one for all the LEDs on the enclosure.
 * The LED information contained in the lists will cater either to various
 * FSP initiated async commands or POWERNV initiated OPAL calls. Need to make
 * sure that this initialization process is complete before allowing any requets
 * on LED. Also need to be called to re-fetch data from SPCN after any LED state
 * have been updated.
 */
static void fsp_leds_query_spcn(void)
{
	struct fsp_led_data *led = NULL;
	int rc = 0;

	u32 cmd_hdr = SPCN_MOD_PRS_LED_DATA_FIRST << 24 | SPCN_CMD_PRS << 16;

	/* Till the last batch of LED data */
	last_spcn_cmd = 0;

	/* Empty the lists */
	while (!list_empty(&cec_ledq)) {
		led = list_pop(&cec_ledq, struct fsp_led_data, link);
		free(led);
	}

	while (!list_empty(&encl_ledq)) {
		led = list_pop(&encl_ledq, struct fsp_led_data, link);
		free(led);
	}

	/* Allocate buffer with alignment requirements */
	if (led_buffer == NULL) {
		led_buffer = memalign(TCE_PSIZE, PSI_DMA_LED_BUF_SZ);
		if (!led_buffer)
			return;
	}

	/* TCE mapping - will not unmap */
	fsp_tce_map(PSI_DMA_LED_BUF, led_buffer, PSI_DMA_LED_BUF_SZ);

	/* Request the first 1KB of LED data */
	last_spcn_cmd = SPCN_MOD_PRS_LED_DATA_FIRST;
	rc = fsp_queue_msg(fsp_mkmsg(FSP_CMD_SPCN_PASSTHRU, 4,
			SPCN_ADDR_MODE_CEC_NODE, cmd_hdr, 0,
				PSI_DMA_LED_BUF), fsp_read_leds_data_complete);
	if (rc)
		prlog(PR_ERR,
		      "SPCN_MOD_PRS_LED_DATA_FIRST command could"
		      " not be queued\n");
	else	/* Initiated LED list fetch MBOX command */
		led_support = LED_STATE_READING;
}

/* Init the LED subsystem at boot time */
void fsp_led_init(void)
{
	led_buffer = NULL;

	if (!fsp_present())
		return;

	/* Init the master lists */
	list_head_init(&cec_ledq);
	list_head_init(&encl_ledq);
	list_head_init(&spcn_cmdq);

	fsp_leds_query_spcn();

	loc_code_list_buffer = memalign(TCE_PSIZE, PSI_DMA_LOC_COD_BUF_SZ);
	if (loc_code_list_buffer == NULL)
		prerror("ERROR: Unable to allocate loc_code_list_buffer!\n");

	prlog(PR_TRACE, "Init completed\n");

	/* Get System attention indicator state */
	dt_get_sai_loc_code();
	fsp_get_sai();

	/* Handle FSP initiated async LED commands */
	fsp_register_client(&fsp_indicator_client, FSP_MCLASS_INDICATOR);
	prlog(PR_TRACE, "FSP async command client registered\n");

	/* Register for SAI update notification */
	sysparam_add_update_notifier(sai_update_notification);

	opal_register(OPAL_LEDS_GET_INDICATOR, fsp_opal_leds_get_ind, 4);
	opal_register(OPAL_LEDS_SET_INDICATOR, fsp_opal_leds_set_ind, 5);
	prlog(PR_TRACE, "LED OPAL interface registered\n");
}
