/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <nxge_impl.h>
#include <nxge_zcp.h>
#include <nxge_ipp.h>

nxge_status_t
nxge_zcp_init(p_nxge_t nxgep)
{
	uint8_t portn;
	npi_handle_t handle;
	zcp_iconfig_t istatus;
	npi_status_t rs = NPI_SUCCESS;
	int i;
	zcp_ram_unit_t w_data;
	zcp_ram_unit_t r_data;
	uint32_t cfifo_depth;

	handle = nxgep->npi_handle;
	portn = NXGE_GET_PORT_NUM(nxgep->function_num);

	if (nxgep->niu_type == N2_NIU) {
		cfifo_depth = ZCP_NIU_CFIFO_DEPTH;
	} else if (NXGE_IS_VALID_NEPTUNE_TYPE(nxgep->niu_type)) {
		if (portn < 2)
			cfifo_depth = ZCP_P0_P1_CFIFO_DEPTH;
		else
			cfifo_depth = ZCP_P2_P3_CFIFO_DEPTH;
	} else {
		goto fail;
	}

	/* Clean up CFIFO */
	w_data.w0 = 0;
	w_data.w1 = 0;
	w_data.w2 = 0;
	w_data.w3 = 0;
	w_data.w4 = 0;

	for (i = 0; i < cfifo_depth; i++) {
		if (npi_zcp_tt_cfifo_entry(handle, OP_SET,
				portn, i, &w_data) != NPI_SUCCESS)
			goto fail;
		if (npi_zcp_tt_cfifo_entry(handle, OP_GET,
				portn, i, &r_data) != NPI_SUCCESS)
			goto fail;
	}

	if (npi_zcp_rest_cfifo_port(handle, portn) != NPI_SUCCESS)
		goto fail;

	/*
	 * Making sure that error source is cleared if this is an injected
	 * error.
	 */
	switch (portn) {
	case 0:
		NXGE_REG_WR64(handle, ZCP_CFIFO_ECC_PORT0_REG, 0);
		break;
	case 1:
		NXGE_REG_WR64(handle, ZCP_CFIFO_ECC_PORT1_REG, 0);
		break;
	case 2:
		NXGE_REG_WR64(handle, ZCP_CFIFO_ECC_PORT2_REG, 0);
		break;
	case 3:
		NXGE_REG_WR64(handle, ZCP_CFIFO_ECC_PORT3_REG, 0);
		break;
	}

	if ((rs = npi_zcp_clear_istatus(handle)) != NPI_SUCCESS)
		return (NXGE_ERROR | rs);
	if ((rs = npi_zcp_get_istatus(handle, &istatus)) != NPI_SUCCESS)
		return (NXGE_ERROR | rs);
	if ((rs = npi_zcp_iconfig(handle, INIT, ICFG_ZCP_ALL)) != NPI_SUCCESS)
		goto fail;

	NXGE_DEBUG_MSG((nxgep, RX_CTL, "==> nxge_zcp_init: port%d", portn));
	return (NXGE_OK);

fail:
	NXGE_ERROR_MSG((nxgep, NXGE_ERR_CTL,
		"nxge_zcp_init: Fail to initialize ZCP Port #%d\n", portn));
	return (NXGE_ERROR | rs);
}

nxge_status_t
nxge_zcp_handle_sys_errors(p_nxge_t nxgep)
{
	npi_handle_t handle;
	npi_status_t rs = NPI_SUCCESS;
	p_nxge_zcp_stats_t statsp;
	uint8_t portn;
	zcp_iconfig_t istatus;
	boolean_t rxport_fatal = B_FALSE;
	nxge_status_t status = NXGE_OK;

	handle = nxgep->npi_handle;
	statsp = (p_nxge_zcp_stats_t)&nxgep->statsp->zcp_stats;
	portn = nxgep->mac.portnum;

	if ((rs = npi_zcp_get_istatus(handle, &istatus)) != NPI_SUCCESS)
		return (NXGE_ERROR | rs);

	if (istatus & ICFG_ZCP_RRFIFO_UNDERRUN) {
		statsp->rrfifo_underrun++;
		NXGE_FM_REPORT_ERROR(nxgep, portn, NULL,
			NXGE_FM_EREPORT_ZCP_RRFIFO_UNDERRUN);
		NXGE_ERROR_MSG((nxgep, NXGE_ERR_CTL,
			"nxge_zcp_err_evnts: rrfifo_underrun"));
	}

	if (istatus & ICFG_ZCP_RRFIFO_OVERRUN) {
		statsp->rrfifo_overrun++;
		NXGE_FM_REPORT_ERROR(nxgep, portn, NULL,
			NXGE_FM_EREPORT_ZCP_RRFIFO_OVERRUN);
		NXGE_ERROR_MSG((nxgep, NXGE_ERR_CTL,
			"nxge_zcp_err_evnts: buf_rrfifo_overrun"));
	}

	if (istatus & ICFG_ZCP_RSPFIFO_UNCORR_ERR) {
		statsp->rspfifo_uncorr_err++;
		NXGE_FM_REPORT_ERROR(nxgep, portn, NULL,
			NXGE_FM_EREPORT_ZCP_RSPFIFO_UNCORR_ERR);
		NXGE_ERROR_MSG((nxgep, NXGE_ERR_CTL,
			"nxge_zcp_err_evnts: rspfifo_uncorr_err"));
	}

	if (istatus & ICFG_ZCP_BUFFER_OVERFLOW) {
		statsp->buffer_overflow++;
		NXGE_FM_REPORT_ERROR(nxgep, portn, NULL,
			NXGE_FM_EREPORT_ZCP_BUFFER_OVERFLOW);
		NXGE_ERROR_MSG((nxgep, NXGE_ERR_CTL,
			"nxge_zcp_err_evnts: buffer_overflow"));
		rxport_fatal = B_TRUE;
	}

	if (istatus & ICFG_ZCP_STAT_TBL_PERR) {
		statsp->stat_tbl_perr++;
		NXGE_FM_REPORT_ERROR(nxgep, portn, NULL,
			NXGE_FM_EREPORT_ZCP_STAT_TBL_PERR);
		NXGE_ERROR_MSG((nxgep, NXGE_ERR_CTL,
			"nxge_zcp_err_evnts: stat_tbl_perr"));
	}

	if (istatus & ICFG_ZCP_DYN_TBL_PERR) {
		statsp->dyn_tbl_perr++;
		NXGE_FM_REPORT_ERROR(nxgep, portn, NULL,
			NXGE_FM_EREPORT_ZCP_DYN_TBL_PERR);
		NXGE_ERROR_MSG((nxgep, NXGE_ERR_CTL,
			"nxge_zcp_err_evnts: dyn_tbl_perr"));
	}

	if (istatus & ICFG_ZCP_BUF_TBL_PERR) {
		statsp->buf_tbl_perr++;
		NXGE_FM_REPORT_ERROR(nxgep, portn, NULL,
			NXGE_FM_EREPORT_ZCP_BUF_TBL_PERR);
		NXGE_ERROR_MSG((nxgep, NXGE_ERR_CTL,
			"nxge_zcp_err_evnts: buf_tbl_perr"));
	}

	if (istatus & ICFG_ZCP_TT_PROGRAM_ERR) {
		statsp->tt_program_err++;
		NXGE_FM_REPORT_ERROR(nxgep, portn, NULL,
			NXGE_FM_EREPORT_ZCP_TT_PROGRAM_ERR);
		NXGE_ERROR_MSG((nxgep, NXGE_ERR_CTL,
			"nxge_zcp_err_evnts: tt_program_err"));
	}

	if (istatus & ICFG_ZCP_RSP_TT_INDEX_ERR) {
		statsp->rsp_tt_index_err++;
		NXGE_FM_REPORT_ERROR(nxgep, portn, NULL,
			NXGE_FM_EREPORT_ZCP_RSP_TT_INDEX_ERR);
		NXGE_ERROR_MSG((nxgep, NXGE_ERR_CTL,
			"nxge_zcp_err_evnts: rsp_tt_index_err"));
	}

	if (istatus & ICFG_ZCP_SLV_TT_INDEX_ERR) {
		statsp->slv_tt_index_err++;
		NXGE_FM_REPORT_ERROR(nxgep, portn, NULL,
			NXGE_FM_EREPORT_ZCP_SLV_TT_INDEX_ERR);
		NXGE_ERROR_MSG((nxgep, NXGE_ERR_CTL,
			"nxge_zcp_err_evnts: slv_tt_index_err"));
	}

	if (istatus & ICFG_ZCP_TT_INDEX_ERR) {
		statsp->zcp_tt_index_err++;
		NXGE_FM_REPORT_ERROR(nxgep, portn, NULL,
			NXGE_FM_EREPORT_ZCP_TT_INDEX_ERR);
		NXGE_ERROR_MSG((nxgep, NXGE_ERR_CTL,
			"nxge_zcp_err_evnts: tt_index_err"));
	}

	if (((portn == 0) && (istatus & ICFG_ZCP_CFIFO_ECC0)) ||
			((portn == 1) && (istatus & ICFG_ZCP_CFIFO_ECC1)) ||
			((portn == 2) && (istatus & ICFG_ZCP_CFIFO_ECC2)) ||
			((portn == 3) && (istatus & ICFG_ZCP_CFIFO_ECC3))) {
		boolean_t ue_ecc_valid;

		if ((status = nxge_ipp_eccue_valid_check(nxgep,
				&ue_ecc_valid)) != NXGE_OK)
			return (status);

		if (ue_ecc_valid) {
			statsp->cfifo_ecc++;
			NXGE_FM_REPORT_ERROR(nxgep, portn, NULL,
				NXGE_FM_EREPORT_ZCP_CFIFO_ECC);
			NXGE_ERROR_MSG((nxgep, NXGE_ERR_CTL,
				"nxge_zcp_err_evnts: port%d buf_cfifo_ecc",
				portn));
			rxport_fatal = B_TRUE;
		}
	}

	/*
	 * Making sure that error source is cleared if this is an injected
	 * error.
	 */
	switch (portn) {
	case 0:
		NXGE_REG_WR64(handle, ZCP_CFIFO_ECC_PORT0_REG, 0);
		break;
	case 1:
		NXGE_REG_WR64(handle, ZCP_CFIFO_ECC_PORT1_REG, 0);
		break;
	case 2:
		NXGE_REG_WR64(handle, ZCP_CFIFO_ECC_PORT2_REG, 0);
		break;
	case 3:
		NXGE_REG_WR64(handle, ZCP_CFIFO_ECC_PORT3_REG, 0);
		break;
	}

	(void) npi_zcp_clear_istatus(handle);

	if (rxport_fatal) {
		NXGE_DEBUG_MSG((nxgep, IPP_CTL,
			" nxge_zcp_handle_sys_errors:"
			" fatal Error on  Port #%d\n", portn));
		status = nxge_zcp_fatal_err_recover(nxgep);
		if (status == NXGE_OK) {
			FM_SERVICE_RESTORED(nxgep);
		}
	}
	return (status);
}

void
nxge_zcp_inject_err(p_nxge_t nxgep, uint32_t err_id)
{
	zcp_int_stat_reg_t zcps;
	uint8_t portn = nxgep->mac.portnum;
	zcp_ecc_ctrl_t ecc_ctrl;

	switch (err_id) {
	case NXGE_FM_EREPORT_ZCP_CFIFO_ECC:
		ecc_ctrl.value = 0;
		ecc_ctrl.bits.w0.cor_dbl = 1;
		ecc_ctrl.bits.w0.cor_lst = 1;
		ecc_ctrl.bits.w0.cor_all = 0;
		switch (portn) {
		case 0:
			cmn_err(CE_NOTE,
				"!Write 0x%llx to port%d ZCP_CFIFO_ECC_PORT\n",
				(unsigned long long) ecc_ctrl.value, portn);
			NXGE_REG_WR64(nxgep->npi_handle,
				ZCP_CFIFO_ECC_PORT0_REG,
				ecc_ctrl.value);
			break;
		case 1:
			cmn_err(CE_NOTE,
				"!Write 0x%llx to port%d ZCP_CFIFO_ECC_PORT\n",
				(unsigned long long) ecc_ctrl.value, portn);
			NXGE_REG_WR64(nxgep->npi_handle,
				ZCP_CFIFO_ECC_PORT1_REG,
				ecc_ctrl.value);
			break;
		case 2:
			cmn_err(CE_NOTE,
				"!Write 0x%llx to port%d ZCP_CFIFO_ECC_PORT\n",
				(unsigned long long) ecc_ctrl.value, portn);
			NXGE_REG_WR64(nxgep->npi_handle,
				ZCP_CFIFO_ECC_PORT2_REG,
				ecc_ctrl.value);
			break;
		case 3:
			cmn_err(CE_NOTE,
				"!Write 0x%llx to port%d ZCP_CFIFO_ECC_PORT\n",
				(unsigned long long) ecc_ctrl.value, portn);
			NXGE_REG_WR64(nxgep->npi_handle,
				ZCP_CFIFO_ECC_PORT3_REG,
				ecc_ctrl.value);
			break;
		}
		break;

	case NXGE_FM_EREPORT_ZCP_RRFIFO_UNDERRUN:
	case NXGE_FM_EREPORT_ZCP_RSPFIFO_UNCORR_ERR:
	case NXGE_FM_EREPORT_ZCP_STAT_TBL_PERR:
	case NXGE_FM_EREPORT_ZCP_DYN_TBL_PERR:
	case NXGE_FM_EREPORT_ZCP_BUF_TBL_PERR:
	case NXGE_FM_EREPORT_ZCP_RRFIFO_OVERRUN:
	case NXGE_FM_EREPORT_ZCP_BUFFER_OVERFLOW:
	case NXGE_FM_EREPORT_ZCP_TT_PROGRAM_ERR:
	case NXGE_FM_EREPORT_ZCP_RSP_TT_INDEX_ERR:
	case NXGE_FM_EREPORT_ZCP_SLV_TT_INDEX_ERR:
	case NXGE_FM_EREPORT_ZCP_TT_INDEX_ERR:
		NXGE_REG_RD64(nxgep->npi_handle, ZCP_INT_STAT_TEST_REG,
			&zcps.value);
		if (err_id == NXGE_FM_EREPORT_ZCP_RRFIFO_UNDERRUN)
			zcps.bits.ldw.rrfifo_urun = 1;
		if (err_id == NXGE_FM_EREPORT_ZCP_RSPFIFO_UNCORR_ERR)
			zcps.bits.ldw.rspfifo_uc_err = 1;
		if (err_id == NXGE_FM_EREPORT_ZCP_STAT_TBL_PERR)
			zcps.bits.ldw.stat_tbl_perr = 1;
		if (err_id == NXGE_FM_EREPORT_ZCP_DYN_TBL_PERR)
			zcps.bits.ldw.dyn_tbl_perr = 1;
		if (err_id == NXGE_FM_EREPORT_ZCP_BUF_TBL_PERR)
			zcps.bits.ldw.buf_tbl_perr = 1;
		if (err_id == NXGE_FM_EREPORT_ZCP_CFIFO_ECC) {
			switch (portn) {
			case 0:
				zcps.bits.ldw.cfifo_ecc0 = 1;
				break;
			case 1:
				zcps.bits.ldw.cfifo_ecc1 = 1;
				break;
			case 2:
				zcps.bits.ldw.cfifo_ecc2 = 1;
				break;
			case 3:
				zcps.bits.ldw.cfifo_ecc3 = 1;
				break;
			}
		}

	default:
		if (err_id == NXGE_FM_EREPORT_ZCP_RRFIFO_OVERRUN)
			zcps.bits.ldw.rrfifo_orun = 1;
		if (err_id == NXGE_FM_EREPORT_ZCP_BUFFER_OVERFLOW)
			zcps.bits.ldw.buf_overflow = 1;
		if (err_id == NXGE_FM_EREPORT_ZCP_TT_PROGRAM_ERR)
			zcps.bits.ldw.tt_tbl_perr = 1;
		if (err_id == NXGE_FM_EREPORT_ZCP_RSP_TT_INDEX_ERR)
			zcps.bits.ldw.rsp_tt_index_err = 1;
		if (err_id == NXGE_FM_EREPORT_ZCP_SLV_TT_INDEX_ERR)
			zcps.bits.ldw.slv_tt_index_err = 1;
		if (err_id == NXGE_FM_EREPORT_ZCP_TT_INDEX_ERR)
			zcps.bits.ldw.zcp_tt_index_err = 1;
		cmn_err(CE_NOTE, "!Write 0x%lx to ZCP_INT_STAT_TEST_REG\n",
			zcps.value);
		NXGE_REG_WR64(nxgep->npi_handle, ZCP_INT_STAT_TEST_REG,
			zcps.value);
		break;
	}
}

nxge_status_t
nxge_zcp_fatal_err_recover(p_nxge_t nxgep)
{
	npi_handle_t handle;
	npi_status_t rs = NPI_SUCCESS;
	nxge_status_t status = NXGE_OK;
	uint8_t portn;
	zcp_ram_unit_t w_data;
	zcp_ram_unit_t r_data;
	uint32_t cfifo_depth;
	int i;

	NXGE_DEBUG_MSG((nxgep, RX_CTL, "<== nxge_zcp_fatal_err_recover"));
	NXGE_ERROR_MSG((nxgep, NXGE_ERR_CTL,
		"Recovering from RxPort error..."));

	handle = nxgep->npi_handle;
	portn = nxgep->mac.portnum;

	/* Disable RxMAC */
	if (nxge_rx_mac_disable(nxgep) != NXGE_OK)
		goto fail;

	/* Make sure source is clear if this is an injected error */
	switch (portn) {
	case 0:
		NXGE_REG_WR64(handle, ZCP_CFIFO_ECC_PORT0_REG, 0);
		break;
	case 1:
		NXGE_REG_WR64(handle, ZCP_CFIFO_ECC_PORT1_REG, 0);
		break;
	case 2:
		NXGE_REG_WR64(handle, ZCP_CFIFO_ECC_PORT2_REG, 0);
		break;
	case 3:
		NXGE_REG_WR64(handle, ZCP_CFIFO_ECC_PORT3_REG, 0);
		break;
	}

	/* Clear up CFIFO */
	if (nxgep->niu_type == N2_NIU) {
		cfifo_depth = ZCP_NIU_CFIFO_DEPTH;
	} else if (NXGE_IS_VALID_NEPTUNE_TYPE(nxgep->niu_type)) {
		if (portn < 2)
			cfifo_depth = ZCP_P0_P1_CFIFO_DEPTH;
		else
			cfifo_depth = ZCP_P2_P3_CFIFO_DEPTH;
	} else {
		goto fail;
	}

	w_data.w0 = 0;
	w_data.w1 = 0;
	w_data.w2 = 0;
	w_data.w3 = 0;
	w_data.w4 = 0;

	for (i = 0; i < cfifo_depth; i++) {
		if (npi_zcp_tt_cfifo_entry(handle, OP_SET,
				portn, i, &w_data) != NPI_SUCCESS)
			goto fail;
		if (npi_zcp_tt_cfifo_entry(handle, OP_GET,
				portn, i, &r_data) != NPI_SUCCESS)
			goto fail;
	}

	/* When recovering from ZCP, RxDMA channel resets are not necessary */
	/* Reset ZCP CFIFO */
	NXGE_DEBUG_MSG((nxgep, RX_CTL, "port%d Reset ZCP CFIFO...", portn));
	if ((rs = npi_zcp_rest_cfifo_port(handle, portn)) != NPI_SUCCESS)
		goto fail;

	/* Reset IPP */
	NXGE_DEBUG_MSG((nxgep, RX_CTL, "port%d Reset IPP...", portn));
	if ((rs = npi_ipp_reset(handle, portn)) != NPI_SUCCESS)
		goto fail;

	NXGE_DEBUG_MSG((nxgep, RX_CTL, "port%d Reset RxMAC...", portn));
	if (nxge_rx_mac_reset(nxgep) != NXGE_OK)
		goto fail;

	NXGE_DEBUG_MSG((nxgep, RX_CTL, "port%d Initialize RxMAC...", portn));
	if ((status = nxge_rx_mac_init(nxgep)) != NXGE_OK)
		goto fail;

	NXGE_DEBUG_MSG((nxgep, RX_CTL, "port%d Enable RxMAC...", portn));
	if (nxge_rx_mac_enable(nxgep) != NXGE_OK)
		goto fail;

	NXGE_ERROR_MSG((nxgep, NXGE_ERR_CTL,
		"Recovery Sucessful, RxPort Restored"));
	NXGE_DEBUG_MSG((nxgep, RX_CTL, "==> nxge_zcp_fatal_err_recover"));
	return (NXGE_OK);
fail:
	NXGE_ERROR_MSG((nxgep, NXGE_ERR_CTL, "Recovery failed"));
	return (status | rs);
}