/*
 * Copyright 2014-2017 Cavium, Inc.
 * The contents of this file are subject to the terms of the Common Development
 * and Distribution License, v.1,  (the "License").
 *
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the License at available
 * at http://opensource.org/licenses/CDDL-1.0
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2019, Joyent, Inc.
 */

#include "bnxtmr.h"
#include "bnxrcv.h"
#include "bnxgld.h"


/* 1.5 seconds */
#define	BNX_LINK_CHECK_INTERVAL 10

/* Approximately every second. */
#define	BNX_LINK_CHECK_INTERVAL2 7

/* 500 msecs */
#define	BNX_TIMER_INTERVAL	500000


typedef struct _bnx_fw_t {
	u32_t shmemaddr;
	u32_t length;
	u32_t nvramaddr;
} bnx_fw_t;

static void
bnx_link_check(lm_device_t *const lmdevice)
{
	if (lmdevice->vars.link_status == LM_STATUS_LINK_ACTIVE) {
		/*
		 * If we have link and we are in the fallback (1gb forced),
		 * mode, we need to see if our link partner is sending us
		 * configs.  If this is the case, we'll switch back to autoneg.
		 */
		if (lmdevice->vars.serdes_fallback_status) {
			u32_t intr_exp_status;

			(void) lm_mwrite(lmdevice, lmdevice->params.phy_addr,
			    0x17, 0x0f01);
			(void) lm_mread(lmdevice, lmdevice->params.phy_addr,
			    0x15, &intr_exp_status);
			(void) lm_mread(lmdevice, lmdevice->params.phy_addr,
			    0x15, &intr_exp_status);

			if (intr_exp_status & 0x20) {
				(void) lm_mwrite(lmdevice,
				    lmdevice->params.phy_addr,
				    PHY_CTRL_REG, PHY_CTRL_AUTO_NEG_ENABLE |
				    PHY_CTRL_RESTART_AUTO_NEG);
			}
		}
	} else {
		lm_service_phy_int(lmdevice, TRUE);
	}
}

static void
bnx_link_check2(lm_device_t *const lmdevice)
{
	if (lmdevice->vars.link_status == LM_STATUS_LINK_ACTIVE) {
		u32_t val;
		u32_t phy_addr;

		phy_addr = lmdevice->params.phy_addr;

		/* Is the link really up? */
		(void) lm_mwrite(lmdevice, phy_addr, 0x1c, 0x6800);
		(void) lm_mread(lmdevice, phy_addr, 0x1c, &val);
		(void) lm_mread(lmdevice, phy_addr, 0x1c, &val);

		if (val & 2) {
			/* Nope.  Force the link down. */
			(void) lm_mwrite(lmdevice, phy_addr, 0x17, 0x0f03);
			(void) lm_mread(lmdevice, phy_addr, 0x15, &val);
			(void) lm_mwrite(lmdevice, phy_addr, 0x15,
			    val & 0xff0f);

			lmdevice->vars.bcm5706s_tx_drv_cur = (u16_t)val;
		}
	}
}



/*
 * Name:    bnx_timer
 *
 * Input:   ptr to device structure
 *
 * Return:  None
 *
 * Description: bnx_timer is the periodic timer callback funtion.
 */
static void
bnx_timer(void *arg)
{
	lm_device_t *lmdevice;
	um_device_t *umdevice;

	umdevice = (um_device_t *)arg;
	lmdevice = &(umdevice->lm_dev);

	mutex_enter(&umdevice->tmr_mutex);

	if (umdevice->timer_enabled != B_TRUE) {
		goto done;
	}

	um_send_driver_pulse(umdevice);

	/*
	 * Take this opportunity to replenish any unused Rx Bds.  Don't
	 * wait around for the rcv_mutex though.  We share the
	 * responsibility of replenishing the rx buffers with the ISR.
	 */
	if (mutex_tryenter(&umdevice->os_param.rcv_mutex)) {
		/* This function does an implicit *_fill(). */
		bnx_rxpkts_post(umdevice);

		mutex_exit(&umdevice->os_param.rcv_mutex);
	}

	if (umdevice->timer_link_check_interval2) {
		/*
		 * If enabled, check to see if the serdes
		 * PHY can fallback to a forced mode.
		 */
		if (umdevice->timer_link_check_interval) {
			if (umdevice->timer_link_check_counter) {
				if (umdevice->timer_link_check_counter == 1) {
					mutex_enter(
					    &umdevice->os_param.phy_mutex);
					bnx_link_check(lmdevice);
					mutex_exit(
					    &umdevice->os_param.phy_mutex);
				}
				umdevice->timer_link_check_counter--;
			}
		}

		umdevice->timer_link_check_counter2--;
		if (umdevice->timer_link_check_counter2 == 0) {
			mutex_enter(&umdevice->os_param.phy_mutex);
			bnx_link_check2(lmdevice);
			mutex_exit(&umdevice->os_param.phy_mutex);

			umdevice->timer_link_check_counter2 =
			    umdevice->timer_link_check_interval2;
		}
	}

	FLUSHPOSTEDWRITES(lmdevice);

	umdevice->tmrtid = timeout(bnx_timer, (void *)umdevice,
	    drv_usectohz(BNX_TIMER_INTERVAL));

done:
	mutex_exit(&umdevice->tmr_mutex);
}

void
bnx_timer_start(um_device_t *const umdevice)
{
	lm_device_t *lmdevice;

	lmdevice = &(umdevice->lm_dev);

	umdevice->timer_enabled = B_TRUE;

	if (CHIP_NUM(lmdevice) == CHIP_NUM_5706 &&
	    umdevice->dev_var.isfiber == B_TRUE) {
		if (lmdevice->vars.serdes_fallback_select !=
		    SERDES_FALLBACK_NONE) {
			umdevice->timer_link_check_interval =
			    BNX_LINK_CHECK_INTERVAL;
		} else {
			umdevice->timer_link_check_interval = 0;
		}

		umdevice->timer_link_check_interval2 = BNX_LINK_CHECK_INTERVAL2;
		umdevice->timer_link_check_counter2 =
		    umdevice->timer_link_check_interval2;
	} else {
		umdevice->timer_link_check_interval2 = 0;
	}

	umdevice->tmrtid = timeout(bnx_timer, (void *)umdevice,
	    drv_usectohz(BNX_TIMER_INTERVAL));
}


void
bnx_timer_stop(um_device_t *const umdevice)
{
	mutex_enter(&umdevice->tmr_mutex);
	umdevice->timer_enabled = B_FALSE;
	mutex_exit(&umdevice->tmr_mutex);

	(void) untimeout(umdevice->tmrtid);
	umdevice->tmrtid = 0;
}



/*
 * Name:	bnx_link_timer_restart
 *
 * Input:	ptr to device structure
 *
 * Return:	None
 *
 * Description:	This function restarts the link poll timer
 *
 */
void
bnx_link_timer_restart(um_device_t *const umdevice)
{
	/* FIXME -- Make timer_link_check_counter atomic */
	umdevice->timer_link_check_counter =
	    umdevice->timer_link_check_interval;
}



void
bnx_timer_init(um_device_t *const umdevice)
{
	mutex_init(&umdevice->tmr_mutex, NULL, MUTEX_DRIVER,
	    DDI_INTR_PRI(umdevice->intrPriority));
}



void
bnx_timer_fini(um_device_t *const umdevice)
{
	mutex_destroy(&umdevice->tmr_mutex);
}