/* SPDX-License-Identifier: BSD-3-Clause
 * Copyright(c) 2018-2021 Beijing WangXun Technology Co., Ltd.
 */

#include "ngbe_phy_mvl.h"

#define MVL_PHY_RST_WAIT_PERIOD  5

s32 ngbe_read_phy_reg_mvl(struct ngbe_hw *hw,
		u32 reg_addr, u32 device_type, u16 *phy_data)
{
	mdi_reg_t reg;
	mdi_reg_22_t reg22;

	reg.device_type = device_type;
	reg.addr = reg_addr;

	if (hw->phy.media_type == ngbe_media_type_fiber)
		ngbe_write_phy_reg_mdi(hw, MVL_PAGE_SEL, 0, 1);
	else
		ngbe_write_phy_reg_mdi(hw, MVL_PAGE_SEL, 0, 0);

	ngbe_mdi_map_register(&reg, &reg22);

	ngbe_read_phy_reg_mdi(hw, reg22.addr, reg22.device_type, phy_data);

	return 0;
}

s32 ngbe_write_phy_reg_mvl(struct ngbe_hw *hw,
		u32 reg_addr, u32 device_type, u16 phy_data)
{
	mdi_reg_t reg;
	mdi_reg_22_t reg22;

	reg.device_type = device_type;
	reg.addr = reg_addr;

	if (hw->phy.media_type == ngbe_media_type_fiber)
		ngbe_write_phy_reg_mdi(hw, MVL_PAGE_SEL, 0, 1);
	else
		ngbe_write_phy_reg_mdi(hw, MVL_PAGE_SEL, 0, 0);

	ngbe_mdi_map_register(&reg, &reg22);

	ngbe_write_phy_reg_mdi(hw, reg22.addr, reg22.device_type, phy_data);

	return 0;
}

s32 ngbe_check_phy_mode_mvl(struct ngbe_hw *hw)
{
	u8 value = 0;
	u32 phy_mode = 0;

	ngbe_flash_read_dword(hw, 0xFF010, &phy_mode);
	value = (u8)(phy_mode >> (hw->bus.lan_id * 8));

	if (MVL_GEN_CTL_MODE(value) == MVL_GEN_CTL_MODE_COPPER) {
		/* mode select to RGMII-to-copper */
		hw->phy.type = ngbe_phy_mvl;
		hw->phy.media_type = ngbe_media_type_copper;
		hw->mac.link_type = ngbe_link_copper;
	} else if (MVL_GEN_CTL_MODE(value) == MVL_GEN_CTL_MODE_FIBER) {
		/* mode select to RGMII-to-sfi */
		hw->phy.type = ngbe_phy_mvl_sfi;
		hw->phy.media_type = ngbe_media_type_fiber;
		hw->mac.link_type = ngbe_link_fiber;
	} else {
		DEBUGOUT("marvell 88E1512 mode %x is not supported.", value);
		return NGBE_ERR_DEVICE_NOT_SUPPORTED;
	}

	return 0;
}

s32 ngbe_init_phy_mvl(struct ngbe_hw *hw)
{
	s32 ret_val = 0;
	u16 value = 0;
	int i;

	/* enable interrupts, only link status change and an done is allowed */
	ngbe_write_phy_reg_mdi(hw, MVL_PAGE_SEL, 0, 2);
	ngbe_read_phy_reg_mdi(hw, MVL_RGM_CTL2, 0, &value);
	value &= ~MVL_RGM_CTL2_TTC;
	value |= MVL_RGM_CTL2_RTC;
	ngbe_write_phy_reg_mdi(hw, MVL_RGM_CTL2, 0, value);

	hw->phy.write_reg(hw, MVL_CTRL, 0, MVL_CTRL_RESET);
	for (i = 0; i < 15; i++) {
		ngbe_read_phy_reg_mdi(hw, MVL_CTRL, 0, &value);
		if (value & MVL_CTRL_RESET)
			msleep(1);
		else
			break;
	}

	if (i == 15) {
		DEBUGOUT("phy reset exceeds maximum waiting period.");
		return NGBE_ERR_TIMEOUT;
	}

	ret_val = hw->phy.reset_hw(hw);
	if (ret_val)
		return ret_val;

	/* set LED2 to interrupt output and INTn active low */
	ngbe_write_phy_reg_mdi(hw, MVL_PAGE_SEL, 0, 3);
	ngbe_read_phy_reg_mdi(hw, MVL_LEDTCR, 0, &value);
	value |= MVL_LEDTCR_INTR_EN;
	value &= ~(MVL_LEDTCR_INTR_POL);
	ngbe_write_phy_reg_mdi(hw, MVL_LEDTCR, 0, value);

	if (hw->phy.type == ngbe_phy_mvl_sfi) {
		hw->phy.read_reg(hw, MVL_CTRL1, 0, &value);
		value &= ~MVL_CTRL1_INTR_POL;
		ngbe_write_phy_reg_mdi(hw, MVL_CTRL1, 0, value);
	}

	/* enable link status change and AN complete interrupts */
	value = MVL_INTR_EN_ANC | MVL_INTR_EN_LSC;
	hw->phy.write_reg(hw, MVL_INTR_EN, 0, value);

	hw->phy.set_phy_power(hw, false);

	return ret_val;
}

s32 ngbe_setup_phy_link_mvl(struct ngbe_hw *hw, u32 speed,
				bool autoneg_wait_to_complete)
{
	u16 value_r4 = 0;
	u16 value_r9 = 0;
	u16 value;

	UNREFERENCED_PARAMETER(autoneg_wait_to_complete);

	if (hw->led_conf == 0xFFFF) {
		/* LED control */
		ngbe_write_phy_reg_mdi(hw, MVL_PAGE_SEL, 0, 3);
		ngbe_read_phy_reg_mdi(hw, MVL_LEDFCR, 0, &value);
		value &= ~(MVL_LEDFCR_CTL0 | MVL_LEDFCR_CTL1);
		value |= MVL_LEDFCR_CTL0_CONF | MVL_LEDFCR_CTL1_CONF;
		ngbe_write_phy_reg_mdi(hw, MVL_LEDFCR, 0, value);
		ngbe_read_phy_reg_mdi(hw, MVL_LEDPCR, 0, &value);
		value &= ~(MVL_LEDPCR_CTL0 | MVL_LEDPCR_CTL1);
		value |= MVL_LEDPCR_CTL0_CONF | MVL_LEDPCR_CTL1_CONF;
		ngbe_write_phy_reg_mdi(hw, MVL_LEDPCR, 0, value);
	}

	hw->phy.autoneg_advertised = 0;

	if (hw->phy.type == ngbe_phy_mvl) {
		if (!hw->mac.autoneg) {
			switch (speed) {
			case NGBE_LINK_SPEED_1GB_FULL:
				value = MVL_CTRL_SPEED_SELECT1;
				break;
			case NGBE_LINK_SPEED_100M_FULL:
				value = MVL_CTRL_SPEED_SELECT0;
				break;
			case NGBE_LINK_SPEED_10M_FULL:
				value = 0;
				break;
			default:
				value = MVL_CTRL_SPEED_SELECT0 |
					MVL_CTRL_SPEED_SELECT1;
				DEBUGOUT("unknown speed = 0x%x.", speed);
				break;
			}
			/* duplex full */
			value |= MVL_CTRL_DUPLEX | MVL_CTRL_RESET;
			ngbe_write_phy_reg_mdi(hw, MVL_CTRL, 0, value);

			goto skip_an;
		}
		if (speed & NGBE_LINK_SPEED_1GB_FULL) {
			value_r9 |= MVL_PHY_1000BASET_FULL;
			hw->phy.autoneg_advertised |= NGBE_LINK_SPEED_1GB_FULL;
		}

		if (speed & NGBE_LINK_SPEED_100M_FULL) {
			value_r4 |= MVL_PHY_100BASET_FULL;
			hw->phy.autoneg_advertised |= NGBE_LINK_SPEED_100M_FULL;
		}

		if (speed & NGBE_LINK_SPEED_10M_FULL) {
			value_r4 |= MVL_PHY_10BASET_FULL;
			hw->phy.autoneg_advertised |= NGBE_LINK_SPEED_10M_FULL;
		}

		hw->phy.read_reg(hw, MVL_ANA, 0, &value);
		value &= ~(MVL_PHY_100BASET_FULL |
			   MVL_PHY_100BASET_HALF |
			   MVL_PHY_10BASET_FULL |
			   MVL_PHY_10BASET_HALF);
		value_r4 |= value;
		hw->phy.write_reg(hw, MVL_ANA, 0, value_r4);

		hw->phy.read_reg(hw, MVL_PHY_1000BASET, 0, &value);
		value &= ~(MVL_PHY_1000BASET_FULL |
			   MVL_PHY_1000BASET_HALF);
		value_r9 |= value;
		hw->phy.write_reg(hw, MVL_PHY_1000BASET, 0, value_r9);

		value = MVL_CTRL_RESTART_AN | MVL_CTRL_ANE |
			MVL_CTRL_RESET | MVL_CTRL_DUPLEX;
		ngbe_write_phy_reg_mdi(hw, MVL_CTRL, 0, value);
	} else {
		hw->phy.autoneg_advertised |= NGBE_LINK_SPEED_1GB_FULL;

		hw->phy.read_reg(hw, MVL_ANA, 0, &value);
		value &= ~(MVL_PHY_1000BASEX_HALF | MVL_PHY_1000BASEX_FULL);
		value |= MVL_PHY_1000BASEX_FULL;
		hw->phy.write_reg(hw, MVL_ANA, 0, value);

		if (hw->mac.autoneg)
			value = MVL_CTRL_RESTART_AN | MVL_CTRL_ANE |
				MVL_CTRL_RESET | MVL_CTRL_DUPLEX |
				MVL_CTRL_SPEED_SELECT1;
		else
			value = MVL_CTRL_RESET | MVL_CTRL_DUPLEX |
				MVL_CTRL_SPEED_SELECT1;
		ngbe_write_phy_reg_mdi(hw, MVL_CTRL, 0, value);
	}

skip_an:
	hw->phy.set_phy_power(hw, true);

	hw->phy.read_reg(hw, MVL_INTR, 0, &value);

	return 0;
}

s32 ngbe_reset_phy_mvl(struct ngbe_hw *hw)
{
	u32 i;
	u16 ctrl = 0;
	s32 status = 0;

	if (hw->phy.type != ngbe_phy_mvl && hw->phy.type != ngbe_phy_mvl_sfi)
		return NGBE_ERR_PHY_TYPE;

	/* select page 18 reg 20 */
	status = ngbe_write_phy_reg_mdi(hw, MVL_PAGE_SEL, 0, 18);

	/* mode select to RGMII-to-copper or RGMII-to-sfi*/
	if (hw->phy.type == ngbe_phy_mvl)
		ctrl = MVL_GEN_CTL_MODE_COPPER;
	else
		ctrl = MVL_GEN_CTL_MODE_FIBER;
	status = ngbe_write_phy_reg_mdi(hw, MVL_GEN_CTL, 0, ctrl);
	/* mode reset */
	ctrl |= MVL_GEN_CTL_RESET;
	status = ngbe_write_phy_reg_mdi(hw, MVL_GEN_CTL, 0, ctrl);

	for (i = 0; i < MVL_PHY_RST_WAIT_PERIOD; i++) {
		status = ngbe_read_phy_reg_mdi(hw, MVL_GEN_CTL, 0, &ctrl);
		if (!(ctrl & MVL_GEN_CTL_RESET))
			break;
		msleep(1);
	}

	if (i == MVL_PHY_RST_WAIT_PERIOD) {
		DEBUGOUT("PHY reset polling failed to complete.");
		return NGBE_ERR_RESET_FAILED;
	}

	return status;
}

s32 ngbe_get_phy_advertised_pause_mvl(struct ngbe_hw *hw, u8 *pause_bit)
{
	u16 value;
	s32 status = 0;

	if (hw->phy.type == ngbe_phy_mvl) {
		status = hw->phy.read_reg(hw, MVL_ANA, 0, &value);
		value &= MVL_CANA_ASM_PAUSE | MVL_CANA_PAUSE;
		*pause_bit = (u8)(value >> 10);
	} else {
		status = hw->phy.read_reg(hw, MVL_ANA, 0, &value);
		value &= MVL_FANA_PAUSE_MASK;
		*pause_bit = (u8)(value >> 7);
	}

	return status;
}

s32 ngbe_get_phy_lp_advertised_pause_mvl(struct ngbe_hw *hw, u8 *pause_bit)
{
	u16 value;
	s32 status = 0;

	if (hw->phy.type == ngbe_phy_mvl) {
		status = hw->phy.read_reg(hw, MVL_LPAR, 0, &value);
		value &= MVL_CLPAR_ASM_PAUSE | MVL_CLPAR_PAUSE;
		*pause_bit = (u8)(value >> 10);
	} else {
		status = hw->phy.read_reg(hw, MVL_LPAR, 0, &value);
		value &= MVL_FLPAR_PAUSE_MASK;
		*pause_bit = (u8)(value >> 7);
	}

	return status;
}

s32 ngbe_set_phy_pause_adv_mvl(struct ngbe_hw *hw, u16 pause_bit)
{
	u16 value;
	s32 status = 0;

	if (hw->phy.type == ngbe_phy_mvl) {
		status = hw->phy.read_reg(hw, MVL_ANA, 0, &value);
		value &= ~(MVL_CANA_ASM_PAUSE | MVL_CANA_PAUSE);
	} else {
		status = hw->phy.read_reg(hw, MVL_ANA, 0, &value);
		value &= ~MVL_FANA_PAUSE_MASK;
	}

	value |= pause_bit;
	status = hw->phy.write_reg(hw, MVL_ANA, 0, value);

	return status;
}

s32 ngbe_check_phy_link_mvl(struct ngbe_hw *hw,
		u32 *speed, bool *link_up)
{
	s32 status = 0;
	u16 phy_link = 0;
	u16 phy_speed = 0;
	u16 phy_data = 0;
	u16 insr = 0;

	/* Initialize speed and link to default case */
	*link_up = false;
	*speed = NGBE_LINK_SPEED_UNKNOWN;

	hw->phy.read_reg(hw, MVL_INTR, 0, &insr);

	/*
	 * Check current speed and link status of the PHY register.
	 * This is a vendor specific register and may have to
	 * be changed for other copper PHYs.
	 */
	status = hw->phy.read_reg(hw, MVL_PHYSR, 0, &phy_data);
	phy_link = phy_data & MVL_PHYSR_LINK;
	phy_speed = phy_data & MVL_PHYSR_SPEED_MASK;

	if (phy_link == MVL_PHYSR_LINK) {
		*link_up = true;

		if (phy_speed == MVL_PHYSR_SPEED_1000M)
			*speed = NGBE_LINK_SPEED_1GB_FULL;
		else if (phy_speed == MVL_PHYSR_SPEED_100M)
			*speed = NGBE_LINK_SPEED_100M_FULL;
		else if (phy_speed == MVL_PHYSR_SPEED_10M)
			*speed = NGBE_LINK_SPEED_10M_FULL;
	}

	return status;
}

s32 ngbe_set_phy_power_mvl(struct ngbe_hw *hw, bool on)
{
	u16 value = 0;

	hw->phy.read_reg(hw, MVL_CTRL, 0, &value);
	if (on)
		value &= ~MVL_CTRL_PWDN;
	else
		value |= MVL_CTRL_PWDN;
	hw->phy.write_reg(hw, MVL_CTRL, 0, value);

	return 0;
}
