// SPDX-License-Identifier: GPL-2.0+
/* Microchip Sparx5 Switch driver
 *
 * Copyright (c) 2021 Microchip Technology Inc. and its subsidiaries.
 */

#include <linux/module.h>
#include <linux/phy/phy.h>

#include "sparx5_main_regs.h"
#include "sparx5_main.h"
#include "sparx5_port.h"

#define SPX5_ETYPE_TAG_C     0x8100
#define SPX5_ETYPE_TAG_S     0x88a8

#define SPX5_WAIT_US         1000
#define SPX5_WAIT_MAX_US     2000

enum port_error {
	SPX5_PERR_SPEED,
	SPX5_PERR_IFTYPE,
};

#define PAUSE_DISCARD        0xC
#define ETH_MAXLEN           (ETH_DATA_LEN + ETH_HLEN + ETH_FCS_LEN)

static void decode_sgmii_word(u16 lp_abil, struct sparx5_port_status *status)
{
	status->an_complete = true;
	if (!(lp_abil & LPA_SGMII_LINK)) {
		status->link = false;
		return;
	}

	switch (lp_abil & LPA_SGMII_SPD_MASK) {
	case LPA_SGMII_10:
		status->speed = SPEED_10;
		break;
	case LPA_SGMII_100:
		status->speed = SPEED_100;
		break;
	case LPA_SGMII_1000:
		status->speed = SPEED_1000;
		break;
	default:
		status->link = false;
		return;
	}
	if (lp_abil & LPA_SGMII_FULL_DUPLEX)
		status->duplex = DUPLEX_FULL;
	else
		status->duplex = DUPLEX_HALF;
}

static void decode_cl37_word(u16 lp_abil, uint16_t ld_abil, struct sparx5_port_status *status)
{
	status->link = !(lp_abil & ADVERTISE_RFAULT) && status->link;
	status->an_complete = true;
	status->duplex = (ADVERTISE_1000XFULL & lp_abil) ?
		DUPLEX_FULL : DUPLEX_UNKNOWN; // 1G HDX not supported

	if ((ld_abil & ADVERTISE_1000XPAUSE) &&
	    (lp_abil & ADVERTISE_1000XPAUSE)) {
		status->pause = MLO_PAUSE_RX | MLO_PAUSE_TX;
	} else if ((ld_abil & ADVERTISE_1000XPSE_ASYM) &&
		   (lp_abil & ADVERTISE_1000XPSE_ASYM)) {
		status->pause |= (lp_abil & ADVERTISE_1000XPAUSE) ?
			MLO_PAUSE_TX : 0;
		status->pause |= (ld_abil & ADVERTISE_1000XPAUSE) ?
			MLO_PAUSE_RX : 0;
	} else {
		status->pause = MLO_PAUSE_NONE;
	}
}

static int sparx5_get_dev2g5_status(struct sparx5 *sparx5,
				    struct sparx5_port *port,
				    struct sparx5_port_status *status)
{
	u32 portno = port->portno;
	u16 lp_adv, ld_adv;
	u32 value;

	/* Get PCS Link down sticky */
	value = spx5_rd(sparx5, DEV2G5_PCS1G_STICKY(portno));
	status->link_down = DEV2G5_PCS1G_STICKY_LINK_DOWN_STICKY_GET(value);
	if (status->link_down)	/* Clear the sticky */
		spx5_wr(value, sparx5, DEV2G5_PCS1G_STICKY(portno));

	/* Get both current Link and Sync status */
	value = spx5_rd(sparx5, DEV2G5_PCS1G_LINK_STATUS(portno));
	status->link = DEV2G5_PCS1G_LINK_STATUS_LINK_STATUS_GET(value) &&
		       DEV2G5_PCS1G_LINK_STATUS_SYNC_STATUS_GET(value);

	if (port->conf.portmode == PHY_INTERFACE_MODE_1000BASEX)
		status->speed = SPEED_1000;
	else if (port->conf.portmode == PHY_INTERFACE_MODE_2500BASEX)
		status->speed = SPEED_2500;

	status->duplex = DUPLEX_FULL;

	/* Get PCS ANEG status register */
	value = spx5_rd(sparx5, DEV2G5_PCS1G_ANEG_STATUS(portno));

	/* Aneg complete provides more information  */
	if (DEV2G5_PCS1G_ANEG_STATUS_ANEG_COMPLETE_GET(value)) {
		lp_adv = DEV2G5_PCS1G_ANEG_STATUS_LP_ADV_ABILITY_GET(value);
		if (port->conf.portmode == PHY_INTERFACE_MODE_SGMII) {
			decode_sgmii_word(lp_adv, status);
		} else {
			value = spx5_rd(sparx5, DEV2G5_PCS1G_ANEG_CFG(portno));
			ld_adv = DEV2G5_PCS1G_ANEG_CFG_ADV_ABILITY_GET(value);
			decode_cl37_word(lp_adv, ld_adv, status);
		}
	}
	return 0;
}

static int sparx5_get_sfi_status(struct sparx5 *sparx5,
				 struct sparx5_port *port,
				 struct sparx5_port_status *status)
{
	bool high_speed_dev = sparx5_is_baser(port->conf.portmode);
	u32 portno = port->portno;
	u32 value, dev, tinst;
	void __iomem *inst;

	if (!high_speed_dev) {
		netdev_err(port->ndev, "error: low speed and SFI mode\n");
		return -EINVAL;
	}

	dev = sparx5_to_high_dev(portno);
	tinst = sparx5_port_dev_index(portno);
	inst = spx5_inst_get(sparx5, dev, tinst);

	value = spx5_inst_rd(inst, DEV10G_MAC_TX_MONITOR_STICKY(0));
	if (value != DEV10G_MAC_TX_MONITOR_STICKY_IDLE_STATE_STICKY) {
		/* The link is or has been down. Clear the sticky bit */
		status->link_down = 1;
		spx5_inst_wr(0xffffffff, inst, DEV10G_MAC_TX_MONITOR_STICKY(0));
		value = spx5_inst_rd(inst, DEV10G_MAC_TX_MONITOR_STICKY(0));
	}
	status->link = (value == DEV10G_MAC_TX_MONITOR_STICKY_IDLE_STATE_STICKY);
	status->duplex = DUPLEX_FULL;
	if (port->conf.portmode == PHY_INTERFACE_MODE_5GBASER)
		status->speed = SPEED_5000;
	else if (port->conf.portmode == PHY_INTERFACE_MODE_10GBASER)
		status->speed = SPEED_10000;
	else
		status->speed = SPEED_25000;

	return 0;
}

/* Get link status of 1000Base-X/in-band and SFI ports.
 */
int sparx5_get_port_status(struct sparx5 *sparx5,
			   struct sparx5_port *port,
			   struct sparx5_port_status *status)
{
	memset(status, 0, sizeof(*status));
	status->speed = port->conf.speed;
	if (port->conf.power_down) {
		status->link = false;
		return 0;
	}
	switch (port->conf.portmode) {
	case PHY_INTERFACE_MODE_SGMII:
	case PHY_INTERFACE_MODE_QSGMII:
	case PHY_INTERFACE_MODE_1000BASEX:
	case PHY_INTERFACE_MODE_2500BASEX:
		return sparx5_get_dev2g5_status(sparx5, port, status);
	case PHY_INTERFACE_MODE_5GBASER:
	case PHY_INTERFACE_MODE_10GBASER:
	case PHY_INTERFACE_MODE_25GBASER:
		return sparx5_get_sfi_status(sparx5, port, status);
	case PHY_INTERFACE_MODE_NA:
		return 0;
	default:
		netdev_err(port->ndev, "Status not supported");
		return -ENODEV;
	}
	return 0;
}

static int sparx5_port_error(struct sparx5_port *port,
			     struct sparx5_port_config *conf,
			     enum port_error errtype)
{
	switch (errtype) {
	case SPX5_PERR_SPEED:
		netdev_err(port->ndev,
			   "Interface does not support speed: %u: for %s\n",
			   conf->speed, phy_modes(conf->portmode));
		break;
	case SPX5_PERR_IFTYPE:
		netdev_err(port->ndev,
			   "Switch port does not support interface type: %s\n",
			   phy_modes(conf->portmode));
		break;
	default:
		netdev_err(port->ndev,
			   "Interface configuration error\n");
	}

	return -EINVAL;
}

static int sparx5_port_verify_speed(struct sparx5 *sparx5,
				    struct sparx5_port *port,
				    struct sparx5_port_config *conf)
{
	if ((sparx5_port_is_2g5(port->portno) &&
	     conf->speed > SPEED_2500) ||
	    (sparx5_port_is_5g(port->portno)  &&
	     conf->speed > SPEED_5000) ||
	    (sparx5_port_is_10g(port->portno) &&
	     conf->speed > SPEED_10000))
		return sparx5_port_error(port, conf, SPX5_PERR_SPEED);

	switch (conf->portmode) {
	case PHY_INTERFACE_MODE_NA:
		return -EINVAL;
	case PHY_INTERFACE_MODE_1000BASEX:
		if (conf->speed != SPEED_1000 ||
		    sparx5_port_is_2g5(port->portno))
			return sparx5_port_error(port, conf, SPX5_PERR_SPEED);
		if (sparx5_port_is_2g5(port->portno))
			return sparx5_port_error(port, conf, SPX5_PERR_IFTYPE);
		break;
	case PHY_INTERFACE_MODE_2500BASEX:
		if (conf->speed != SPEED_2500 ||
		    sparx5_port_is_2g5(port->portno))
			return sparx5_port_error(port, conf, SPX5_PERR_SPEED);
		break;
	case PHY_INTERFACE_MODE_QSGMII:
		if (port->portno > 47)
			return sparx5_port_error(port, conf, SPX5_PERR_IFTYPE);
		fallthrough;
	case PHY_INTERFACE_MODE_SGMII:
		if (conf->speed != SPEED_1000 &&
		    conf->speed != SPEED_100 &&
		    conf->speed != SPEED_10 &&
		    conf->speed != SPEED_2500)
			return sparx5_port_error(port, conf, SPX5_PERR_SPEED);
		break;
	case PHY_INTERFACE_MODE_5GBASER:
	case PHY_INTERFACE_MODE_10GBASER:
	case PHY_INTERFACE_MODE_25GBASER:
		if ((conf->speed != SPEED_5000 &&
		     conf->speed != SPEED_10000 &&
		     conf->speed != SPEED_25000))
			return sparx5_port_error(port, conf, SPX5_PERR_SPEED);
		break;
	default:
		return sparx5_port_error(port, conf, SPX5_PERR_IFTYPE);
	}
	return 0;
}

static bool sparx5_dev_change(struct sparx5 *sparx5,
			      struct sparx5_port *port,
			      struct sparx5_port_config *conf)
{
	return sparx5_is_baser(port->conf.portmode) ^
		sparx5_is_baser(conf->portmode);
}

static int sparx5_port_flush_poll(struct sparx5 *sparx5, u32 portno)
{
	u32  value, resource, prio, delay_cnt = 0;
	bool poll_src = true;
	char *mem = "";

	/* Resource == 0: Memory tracked per source (SRC-MEM)
	 * Resource == 1: Frame references tracked per source (SRC-REF)
	 * Resource == 2: Memory tracked per destination (DST-MEM)
	 * Resource == 3: Frame references tracked per destination. (DST-REF)
	 */
	while (1) {
		bool empty = true;

		for (resource = 0; resource < (poll_src ? 2 : 1); resource++) {
			u32 base;

			base = (resource == 0 ? 2048 : 0) + SPX5_PRIOS * portno;
			for (prio = 0; prio < SPX5_PRIOS; prio++) {
				value = spx5_rd(sparx5,
						QRES_RES_STAT(base + prio));
				if (value) {
					mem = resource == 0 ?
						"DST-MEM" : "SRC-MEM";
					empty = false;
				}
			}
		}

		if (empty)
			break;

		if (delay_cnt++ == 2000) {
			dev_err(sparx5->dev,
				"Flush timeout port %u. %s queue not empty\n",
				portno, mem);
			return -EINVAL;
		}

		usleep_range(SPX5_WAIT_US, SPX5_WAIT_MAX_US);
	}
	return 0;
}

static int sparx5_port_disable(struct sparx5 *sparx5, struct sparx5_port *port, bool high_spd_dev)
{
	u32 tinst = high_spd_dev ?
		    sparx5_port_dev_index(port->portno) : port->portno;
	u32 dev = high_spd_dev ?
		  sparx5_to_high_dev(port->portno) : TARGET_DEV2G5;
	void __iomem *devinst = spx5_inst_get(sparx5, dev, tinst);
	u32 spd = port->conf.speed;
	u32 spd_prm;
	int err;

	if (high_spd_dev) {
		/* 1: Reset the PCS Rx clock domain  */
		spx5_inst_rmw(DEV10G_DEV_RST_CTRL_PCS_RX_RST,
			      DEV10G_DEV_RST_CTRL_PCS_RX_RST,
			      devinst,
			      DEV10G_DEV_RST_CTRL(0));

		/* 2: Disable MAC frame reception */
		spx5_inst_rmw(0,
			      DEV10G_MAC_ENA_CFG_RX_ENA,
			      devinst,
			      DEV10G_MAC_ENA_CFG(0));
	} else {
		/* 1: Reset the PCS Rx clock domain  */
		spx5_inst_rmw(DEV2G5_DEV_RST_CTRL_PCS_RX_RST,
			      DEV2G5_DEV_RST_CTRL_PCS_RX_RST,
			      devinst,
			      DEV2G5_DEV_RST_CTRL(0));
		/* 2: Disable MAC frame reception */
		spx5_inst_rmw(0,
			      DEV2G5_MAC_ENA_CFG_RX_ENA,
			      devinst,
			      DEV2G5_MAC_ENA_CFG(0));
	}
	/* 3: Disable traffic being sent to or from switch port->portno */
	spx5_rmw(0,
		 QFWD_SWITCH_PORT_MODE_PORT_ENA,
		 sparx5,
		 QFWD_SWITCH_PORT_MODE(port->portno));

	/* 4: Disable dequeuing from the egress queues  */
	spx5_rmw(HSCH_PORT_MODE_DEQUEUE_DIS,
		 HSCH_PORT_MODE_DEQUEUE_DIS,
		 sparx5,
		 HSCH_PORT_MODE(port->portno));

	/* 5: Disable Flowcontrol */
	spx5_rmw(QSYS_PAUSE_CFG_PAUSE_STOP_SET(0xFFF - 1),
		 QSYS_PAUSE_CFG_PAUSE_STOP,
		 sparx5,
		 QSYS_PAUSE_CFG(port->portno));

	spd_prm = spd == SPEED_10 ? 1000 : spd == SPEED_100 ? 100 : 10;
	/* 6: Wait while the last frame is exiting the queues */
	usleep_range(8 * spd_prm, 10 * spd_prm);

	/* 7: Flush the queues accociated with the port->portno */
	spx5_rmw(HSCH_FLUSH_CTRL_FLUSH_PORT_SET(port->portno) |
		 HSCH_FLUSH_CTRL_FLUSH_DST_SET(1) |
		 HSCH_FLUSH_CTRL_FLUSH_SRC_SET(1) |
		 HSCH_FLUSH_CTRL_FLUSH_ENA_SET(1),
		 HSCH_FLUSH_CTRL_FLUSH_PORT |
		 HSCH_FLUSH_CTRL_FLUSH_DST |
		 HSCH_FLUSH_CTRL_FLUSH_SRC |
		 HSCH_FLUSH_CTRL_FLUSH_ENA,
		 sparx5,
		 HSCH_FLUSH_CTRL);

	/* 8: Enable dequeuing from the egress queues */
	spx5_rmw(0,
		 HSCH_PORT_MODE_DEQUEUE_DIS,
		 sparx5,
		 HSCH_PORT_MODE(port->portno));

	/* 9: Wait until flushing is complete */
	err = sparx5_port_flush_poll(sparx5, port->portno);
	if (err)
		return err;

	/* 10: Reset the  MAC clock domain */
	if (high_spd_dev) {
		spx5_inst_rmw(DEV10G_DEV_RST_CTRL_PCS_TX_RST_SET(1) |
			      DEV10G_DEV_RST_CTRL_MAC_RX_RST_SET(1) |
			      DEV10G_DEV_RST_CTRL_MAC_TX_RST_SET(1),
			      DEV10G_DEV_RST_CTRL_PCS_TX_RST |
			      DEV10G_DEV_RST_CTRL_MAC_RX_RST |
			      DEV10G_DEV_RST_CTRL_MAC_TX_RST,
			      devinst,
			      DEV10G_DEV_RST_CTRL(0));

	} else {
		spx5_inst_rmw(DEV2G5_DEV_RST_CTRL_SPEED_SEL_SET(3) |
			      DEV2G5_DEV_RST_CTRL_PCS_TX_RST_SET(1) |
			      DEV2G5_DEV_RST_CTRL_PCS_RX_RST_SET(1) |
			      DEV2G5_DEV_RST_CTRL_MAC_TX_RST_SET(1) |
			      DEV2G5_DEV_RST_CTRL_MAC_RX_RST_SET(1),
			      DEV2G5_DEV_RST_CTRL_SPEED_SEL |
			      DEV2G5_DEV_RST_CTRL_PCS_TX_RST |
			      DEV2G5_DEV_RST_CTRL_PCS_RX_RST |
			      DEV2G5_DEV_RST_CTRL_MAC_TX_RST |
			      DEV2G5_DEV_RST_CTRL_MAC_RX_RST,
			      devinst,
			      DEV2G5_DEV_RST_CTRL(0));
	}
	/* 11: Clear flushing */
	spx5_rmw(HSCH_FLUSH_CTRL_FLUSH_PORT_SET(port->portno) |
		 HSCH_FLUSH_CTRL_FLUSH_ENA_SET(0),
		 HSCH_FLUSH_CTRL_FLUSH_PORT |
		 HSCH_FLUSH_CTRL_FLUSH_ENA,
		 sparx5,
		 HSCH_FLUSH_CTRL);

	if (high_spd_dev) {
		u32 pcs = sparx5_to_pcs_dev(port->portno);
		void __iomem *pcsinst = spx5_inst_get(sparx5, pcs, tinst);

		/* 12: Disable 5G/10G/25 BaseR PCS */
		spx5_inst_rmw(PCS10G_BR_PCS_CFG_PCS_ENA_SET(0),
			      PCS10G_BR_PCS_CFG_PCS_ENA,
			      pcsinst,
			      PCS10G_BR_PCS_CFG(0));

		if (sparx5_port_is_25g(port->portno))
			/* Disable 25G PCS */
			spx5_rmw(DEV25G_PCS25G_CFG_PCS25G_ENA_SET(0),
				 DEV25G_PCS25G_CFG_PCS25G_ENA,
				 sparx5,
				 DEV25G_PCS25G_CFG(tinst));
	} else {
		/* 12: Disable 1G PCS */
		spx5_rmw(DEV2G5_PCS1G_CFG_PCS_ENA_SET(0),
			 DEV2G5_PCS1G_CFG_PCS_ENA,
			 sparx5,
			 DEV2G5_PCS1G_CFG(port->portno));
	}

	/* The port is now flushed and disabled  */
	return 0;
}

static int sparx5_port_fifo_sz(struct sparx5 *sparx5,
			       u32 portno, u32 speed)
{
	u32 sys_clk = sparx5_clk_period(sparx5->coreclock);
	const u32 taxi_dist[SPX5_PORTS_ALL] = {
		6, 8, 10, 6, 8, 10, 6, 8, 10, 6, 8, 10,
		4, 4, 4, 4,
		11, 12, 13, 14, 15, 16, 17, 18,
		11, 12, 13, 14, 15, 16, 17, 18,
		11, 12, 13, 14, 15, 16, 17, 18,
		11, 12, 13, 14, 15, 16, 17, 18,
		4, 6, 8, 4, 6, 8, 6, 8,
		2, 2, 2, 2, 2, 2, 2, 4, 2
	};
	u32 mac_per    = 6400, tmp1, tmp2, tmp3;
	u32 fifo_width = 16;
	u32 mac_width  = 8;
	u32 addition   = 0;

	switch (speed) {
	case SPEED_25000:
		return 0;
	case SPEED_10000:
		mac_per = 6400;
		mac_width = 8;
		addition = 1;
		break;
	case SPEED_5000:
		mac_per = 12800;
		mac_width = 8;
		addition = 0;
		break;
	case SPEED_2500:
		mac_per = 3200;
		mac_width = 1;
		addition = 0;
		break;
	case SPEED_1000:
		mac_per =  8000;
		mac_width = 1;
		addition = 0;
		break;
	case SPEED_100:
	case SPEED_10:
		return 1;
	default:
		break;
	}

	tmp1 = 1000 * mac_width / fifo_width;
	tmp2 = 3000 + ((12000 + 2 * taxi_dist[portno] * 1000)
		       * sys_clk / mac_per);
	tmp3 = tmp1 * tmp2 / 1000;
	return  (tmp3 + 2000 + 999) / 1000 + addition;
}

/* Configure port muxing:
 * QSGMII:     4x2G5 devices
 */
static int sparx5_port_mux_set(struct sparx5 *sparx5,
			       struct sparx5_port *port,
			       struct sparx5_port_config *conf)
{
	u32 portno = port->portno;
	u32 inst;

	if (port->conf.portmode == conf->portmode)
		return 0; /* Nothing to do */

	switch (conf->portmode) {
	case PHY_INTERFACE_MODE_QSGMII: /* QSGMII: 4x2G5 devices. Mode Q'  */
		inst = (portno - portno % 4) / 4;
		spx5_rmw(BIT(inst),
			 BIT(inst),
			 sparx5,
			 PORT_CONF_QSGMII_ENA);

		if ((portno / 4 % 2) == 0) {
			/* Affects d0-d3,d8-d11..d40-d43 */
			spx5_rmw(PORT_CONF_USGMII_CFG_BYPASS_SCRAM_SET(1) |
				 PORT_CONF_USGMII_CFG_BYPASS_DESCRAM_SET(1) |
				 PORT_CONF_USGMII_CFG_QUAD_MODE_SET(1),
				 PORT_CONF_USGMII_CFG_BYPASS_SCRAM |
				 PORT_CONF_USGMII_CFG_BYPASS_DESCRAM |
				 PORT_CONF_USGMII_CFG_QUAD_MODE,
				 sparx5,
				 PORT_CONF_USGMII_CFG((portno / 8)));
		}
		break;
	default:
		break;
	}
	return 0;
}

static int sparx5_port_max_tags_set(struct sparx5 *sparx5,
				    struct sparx5_port *port)
{
	enum sparx5_port_max_tags max_tags    = port->max_vlan_tags;
	int tag_ct          = max_tags == SPX5_PORT_MAX_TAGS_ONE ? 1 :
			      max_tags == SPX5_PORT_MAX_TAGS_TWO ? 2 : 0;
	bool dtag           = max_tags == SPX5_PORT_MAX_TAGS_TWO;
	enum sparx5_vlan_port_type vlan_type  = port->vlan_type;
	bool dotag          = max_tags != SPX5_PORT_MAX_TAGS_NONE;
	u32 dev             = sparx5_to_high_dev(port->portno);
	u32 tinst           = sparx5_port_dev_index(port->portno);
	void __iomem *inst  = spx5_inst_get(sparx5, dev, tinst);
	u32 etype;

	etype = (vlan_type == SPX5_VLAN_PORT_TYPE_S_CUSTOM ?
		 port->custom_etype :
		 vlan_type == SPX5_VLAN_PORT_TYPE_C ?
		 SPX5_ETYPE_TAG_C : SPX5_ETYPE_TAG_S);

	spx5_wr(DEV2G5_MAC_TAGS_CFG_TAG_ID_SET(etype) |
		DEV2G5_MAC_TAGS_CFG_PB_ENA_SET(dtag) |
		DEV2G5_MAC_TAGS_CFG_VLAN_AWR_ENA_SET(dotag) |
		DEV2G5_MAC_TAGS_CFG_VLAN_LEN_AWR_ENA_SET(dotag),
		sparx5,
		DEV2G5_MAC_TAGS_CFG(port->portno));

	if (sparx5_port_is_2g5(port->portno))
		return 0;

	spx5_inst_rmw(DEV10G_MAC_TAGS_CFG_TAG_ID_SET(etype) |
		      DEV10G_MAC_TAGS_CFG_TAG_ENA_SET(dotag),
		      DEV10G_MAC_TAGS_CFG_TAG_ID |
		      DEV10G_MAC_TAGS_CFG_TAG_ENA,
		      inst,
		      DEV10G_MAC_TAGS_CFG(0, 0));

	spx5_inst_rmw(DEV10G_MAC_NUM_TAGS_CFG_NUM_TAGS_SET(tag_ct),
		      DEV10G_MAC_NUM_TAGS_CFG_NUM_TAGS,
		      inst,
		      DEV10G_MAC_NUM_TAGS_CFG(0));

	spx5_inst_rmw(DEV10G_MAC_MAXLEN_CFG_MAX_LEN_TAG_CHK_SET(dotag),
		      DEV10G_MAC_MAXLEN_CFG_MAX_LEN_TAG_CHK,
		      inst,
		      DEV10G_MAC_MAXLEN_CFG(0));
	return 0;
}

int sparx5_port_fwd_urg(struct sparx5 *sparx5, u32 speed)
{
	u32 clk_period_ps = 1600; /* 625Mhz for now */
	u32 urg = 672000;

	switch (speed) {
	case SPEED_10:
	case SPEED_100:
	case SPEED_1000:
		urg = 672000;
		break;
	case SPEED_2500:
		urg = 270000;
		break;
	case SPEED_5000:
		urg = 135000;
		break;
	case SPEED_10000:
		urg = 67200;
		break;
	case SPEED_25000:
		urg = 27000;
		break;
	}
	return urg / clk_period_ps - 1;
}

static u16 sparx5_wm_enc(u16 value)
{
	if (value >= 2048)
		return 2048 + value / 16;

	return value;
}

static int sparx5_port_fc_setup(struct sparx5 *sparx5,
				struct sparx5_port *port,
				struct sparx5_port_config *conf)
{
	bool fc_obey = conf->pause & MLO_PAUSE_RX ? 1 : 0;
	u32 pause_stop = 0xFFF - 1; /* FC gen disabled */

	if (conf->pause & MLO_PAUSE_TX)
		pause_stop = sparx5_wm_enc(4  * (ETH_MAXLEN /
						 SPX5_BUFFER_CELL_SZ));

	/* Set HDX flowcontrol */
	spx5_rmw(DSM_MAC_CFG_HDX_BACKPREASSURE_SET(conf->duplex == DUPLEX_HALF),
		 DSM_MAC_CFG_HDX_BACKPREASSURE,
		 sparx5,
		 DSM_MAC_CFG(port->portno));

	/* Obey flowcontrol  */
	spx5_rmw(DSM_RX_PAUSE_CFG_RX_PAUSE_EN_SET(fc_obey),
		 DSM_RX_PAUSE_CFG_RX_PAUSE_EN,
		 sparx5,
		 DSM_RX_PAUSE_CFG(port->portno));

	/* Disable forward pressure */
	spx5_rmw(QSYS_FWD_PRESSURE_FWD_PRESSURE_DIS_SET(fc_obey),
		 QSYS_FWD_PRESSURE_FWD_PRESSURE_DIS,
		 sparx5,
		 QSYS_FWD_PRESSURE(port->portno));

	/* Generate pause frames */
	spx5_rmw(QSYS_PAUSE_CFG_PAUSE_STOP_SET(pause_stop),
		 QSYS_PAUSE_CFG_PAUSE_STOP,
		 sparx5,
		 QSYS_PAUSE_CFG(port->portno));

	return 0;
}

static u16 sparx5_get_aneg_word(struct sparx5_port_config *conf)
{
	if (conf->portmode == PHY_INTERFACE_MODE_1000BASEX) /* cl-37 aneg */
		return (conf->pause_adv | ADVERTISE_LPACK | ADVERTISE_1000XFULL);
	else
		return 1; /* Enable SGMII Aneg */
}

int sparx5_serdes_set(struct sparx5 *sparx5,
		      struct sparx5_port *port,
		      struct sparx5_port_config *conf)
{
	int portmode, err, speed = conf->speed;

	if (conf->portmode == PHY_INTERFACE_MODE_QSGMII &&
	    ((port->portno % 4) != 0)) {
		return 0;
	}
	if (sparx5_is_baser(conf->portmode)) {
		if (conf->portmode == PHY_INTERFACE_MODE_25GBASER)
			speed = SPEED_25000;
		else if (conf->portmode == PHY_INTERFACE_MODE_10GBASER)
			speed = SPEED_10000;
		else
			speed = SPEED_5000;
	}

	err = phy_set_media(port->serdes, conf->media);
	if (err)
		return err;
	if (speed > 0) {
		err = phy_set_speed(port->serdes, speed);
		if (err)
			return err;
	}
	if (conf->serdes_reset) {
		err = phy_reset(port->serdes);
		if (err)
			return err;
	}

	/* Configure SerDes with port parameters
	 * For BaseR, the serdes driver supports 10GGBASE-R and speed 5G/10G/25G
	 */
	portmode = conf->portmode;
	if (sparx5_is_baser(conf->portmode))
		portmode = PHY_INTERFACE_MODE_10GBASER;
	err = phy_set_mode_ext(port->serdes, PHY_MODE_ETHERNET, portmode);
	if (err)
		return err;
	conf->serdes_reset = false;
	return err;
}

static int sparx5_port_pcs_low_set(struct sparx5 *sparx5,
				   struct sparx5_port *port,
				   struct sparx5_port_config *conf)
{
	bool sgmii = false, inband_aneg = false;
	int err;

	if (port->conf.inband) {
		if (conf->portmode == PHY_INTERFACE_MODE_SGMII ||
		    conf->portmode == PHY_INTERFACE_MODE_QSGMII)
			inband_aneg = true; /* Cisco-SGMII in-band-aneg */
		else if (conf->portmode == PHY_INTERFACE_MODE_1000BASEX &&
			 conf->autoneg)
			inband_aneg = true; /* Clause-37 in-band-aneg */

		err = sparx5_serdes_set(sparx5, port, conf);
		if (err)
			return -EINVAL;
	} else {
		sgmii = true; /* Phy is connected to the MAC */
	}

	/* Choose SGMII or 1000BaseX/2500BaseX PCS mode */
	spx5_rmw(DEV2G5_PCS1G_MODE_CFG_SGMII_MODE_ENA_SET(sgmii),
		 DEV2G5_PCS1G_MODE_CFG_SGMII_MODE_ENA,
		 sparx5,
		 DEV2G5_PCS1G_MODE_CFG(port->portno));

	/* Enable PCS */
	spx5_wr(DEV2G5_PCS1G_CFG_PCS_ENA_SET(1),
		sparx5,
		DEV2G5_PCS1G_CFG(port->portno));

	if (inband_aneg) {
		u16 abil = sparx5_get_aneg_word(conf);

		/* Enable in-band aneg */
		spx5_wr(DEV2G5_PCS1G_ANEG_CFG_ADV_ABILITY_SET(abil) |
			DEV2G5_PCS1G_ANEG_CFG_SW_RESOLVE_ENA_SET(1) |
			DEV2G5_PCS1G_ANEG_CFG_ANEG_ENA_SET(1) |
			DEV2G5_PCS1G_ANEG_CFG_ANEG_RESTART_ONE_SHOT_SET(1),
			sparx5,
			DEV2G5_PCS1G_ANEG_CFG(port->portno));
	} else {
		spx5_wr(0, sparx5, DEV2G5_PCS1G_ANEG_CFG(port->portno));
	}

	/* Take PCS out of reset */
	spx5_rmw(DEV2G5_DEV_RST_CTRL_SPEED_SEL_SET(2) |
		 DEV2G5_DEV_RST_CTRL_PCS_TX_RST_SET(0) |
		 DEV2G5_DEV_RST_CTRL_PCS_RX_RST_SET(0),
		 DEV2G5_DEV_RST_CTRL_SPEED_SEL |
		 DEV2G5_DEV_RST_CTRL_PCS_TX_RST |
		 DEV2G5_DEV_RST_CTRL_PCS_RX_RST,
		 sparx5,
		 DEV2G5_DEV_RST_CTRL(port->portno));

	return 0;
}

static int sparx5_port_pcs_high_set(struct sparx5 *sparx5,
				    struct sparx5_port *port,
				    struct sparx5_port_config *conf)
{
	u32 clk_spd = conf->portmode == PHY_INTERFACE_MODE_5GBASER ? 1 : 0;
	u32 pix = sparx5_port_dev_index(port->portno);
	u32 dev = sparx5_to_high_dev(port->portno);
	u32 pcs = sparx5_to_pcs_dev(port->portno);
	void __iomem *devinst;
	void __iomem *pcsinst;
	int err;

	devinst = spx5_inst_get(sparx5, dev, pix);
	pcsinst = spx5_inst_get(sparx5, pcs, pix);

	/*  SFI : No in-band-aneg. Speeds 5G/10G/25G */
	err = sparx5_serdes_set(sparx5, port, conf);
	if (err)
		return -EINVAL;
	if (conf->portmode == PHY_INTERFACE_MODE_25GBASER) {
		/* Enable PCS for 25G device, speed 25G */
		spx5_rmw(DEV25G_PCS25G_CFG_PCS25G_ENA_SET(1),
			 DEV25G_PCS25G_CFG_PCS25G_ENA,
			 sparx5,
			 DEV25G_PCS25G_CFG(pix));
	} else {
		/* Enable PCS for 5G/10G/25G devices, speed 5G/10G */
		spx5_inst_rmw(PCS10G_BR_PCS_CFG_PCS_ENA_SET(1),
			      PCS10G_BR_PCS_CFG_PCS_ENA,
			      pcsinst,
			      PCS10G_BR_PCS_CFG(0));
	}

	/* Enable 5G/10G/25G MAC module */
	spx5_inst_wr(DEV10G_MAC_ENA_CFG_RX_ENA_SET(1) |
		     DEV10G_MAC_ENA_CFG_TX_ENA_SET(1),
		     devinst,
		     DEV10G_MAC_ENA_CFG(0));

	/* Take the device out of reset */
	spx5_inst_rmw(DEV10G_DEV_RST_CTRL_PCS_RX_RST_SET(0) |
		      DEV10G_DEV_RST_CTRL_PCS_TX_RST_SET(0) |
		      DEV10G_DEV_RST_CTRL_MAC_RX_RST_SET(0) |
		      DEV10G_DEV_RST_CTRL_MAC_TX_RST_SET(0) |
		      DEV10G_DEV_RST_CTRL_SPEED_SEL_SET(clk_spd),
		      DEV10G_DEV_RST_CTRL_PCS_RX_RST |
		      DEV10G_DEV_RST_CTRL_PCS_TX_RST |
		      DEV10G_DEV_RST_CTRL_MAC_RX_RST |
		      DEV10G_DEV_RST_CTRL_MAC_TX_RST |
		      DEV10G_DEV_RST_CTRL_SPEED_SEL,
		      devinst,
		      DEV10G_DEV_RST_CTRL(0));

	return 0;
}

/* Switch between 1G/2500 and 5G/10G/25G devices */
static void sparx5_dev_switch(struct sparx5 *sparx5, int port, bool hsd)
{
	int bt_indx = BIT(sparx5_port_dev_index(port));

	if (sparx5_port_is_5g(port)) {
		spx5_rmw(hsd ? 0 : bt_indx,
			 bt_indx,
			 sparx5,
			 PORT_CONF_DEV5G_MODES);
	} else if (sparx5_port_is_10g(port)) {
		spx5_rmw(hsd ? 0 : bt_indx,
			 bt_indx,
			 sparx5,
			 PORT_CONF_DEV10G_MODES);
	} else if (sparx5_port_is_25g(port)) {
		spx5_rmw(hsd ? 0 : bt_indx,
			 bt_indx,
			 sparx5,
			 PORT_CONF_DEV25G_MODES);
	}
}

/* Configure speed/duplex dependent registers */
static int sparx5_port_config_low_set(struct sparx5 *sparx5,
				      struct sparx5_port *port,
				      struct sparx5_port_config *conf)
{
	u32 clk_spd, gig_mode, tx_gap, hdx_gap_1, hdx_gap_2;
	bool fdx = conf->duplex == DUPLEX_FULL;
	int spd = conf->speed;

	clk_spd = spd == SPEED_10 ? 0 : spd == SPEED_100 ? 1 : 2;
	gig_mode = spd == SPEED_1000 || spd == SPEED_2500;
	tx_gap = spd == SPEED_1000 ? 4 : fdx ? 6 : 5;
	hdx_gap_1 = spd == SPEED_1000 ? 0 : spd == SPEED_100 ? 1 : 2;
	hdx_gap_2 = spd == SPEED_1000 ? 0 : spd == SPEED_100 ? 4 : 1;

	/* GIG/FDX mode */
	spx5_rmw(DEV2G5_MAC_MODE_CFG_GIGA_MODE_ENA_SET(gig_mode) |
		 DEV2G5_MAC_MODE_CFG_FDX_ENA_SET(fdx),
		 DEV2G5_MAC_MODE_CFG_GIGA_MODE_ENA |
		 DEV2G5_MAC_MODE_CFG_FDX_ENA,
		 sparx5,
		 DEV2G5_MAC_MODE_CFG(port->portno));

	/* Set MAC IFG Gaps */
	spx5_wr(DEV2G5_MAC_IFG_CFG_TX_IFG_SET(tx_gap) |
		DEV2G5_MAC_IFG_CFG_RX_IFG1_SET(hdx_gap_1) |
		DEV2G5_MAC_IFG_CFG_RX_IFG2_SET(hdx_gap_2),
		sparx5,
		DEV2G5_MAC_IFG_CFG(port->portno));

	/* Disabling frame aging when in HDX (due to HDX issue) */
	spx5_rmw(HSCH_PORT_MODE_AGE_DIS_SET(fdx == 0),
		 HSCH_PORT_MODE_AGE_DIS,
		 sparx5,
		 HSCH_PORT_MODE(port->portno));

	/* Enable MAC module */
	spx5_wr(DEV2G5_MAC_ENA_CFG_RX_ENA |
		DEV2G5_MAC_ENA_CFG_TX_ENA,
		sparx5,
		DEV2G5_MAC_ENA_CFG(port->portno));

	/* Select speed and take MAC out of reset */
	spx5_rmw(DEV2G5_DEV_RST_CTRL_SPEED_SEL_SET(clk_spd) |
		 DEV2G5_DEV_RST_CTRL_MAC_TX_RST_SET(0) |
		 DEV2G5_DEV_RST_CTRL_MAC_RX_RST_SET(0),
		 DEV2G5_DEV_RST_CTRL_SPEED_SEL |
		 DEV2G5_DEV_RST_CTRL_MAC_TX_RST |
		 DEV2G5_DEV_RST_CTRL_MAC_RX_RST,
		 sparx5,
		 DEV2G5_DEV_RST_CTRL(port->portno));

	return 0;
}

int sparx5_port_pcs_set(struct sparx5 *sparx5,
			struct sparx5_port *port,
			struct sparx5_port_config *conf)

{
	bool high_speed_dev = sparx5_is_baser(conf->portmode);
	int err;

	if (sparx5_dev_change(sparx5, port, conf)) {
		/* switch device */
		sparx5_dev_switch(sparx5, port->portno, high_speed_dev);

		/* Disable the not-in-use device */
		err = sparx5_port_disable(sparx5, port, !high_speed_dev);
		if (err)
			return err;
	}
	/* Disable the port before re-configuring */
	err = sparx5_port_disable(sparx5, port, high_speed_dev);
	if (err)
		return -EINVAL;

	if (high_speed_dev)
		err = sparx5_port_pcs_high_set(sparx5, port, conf);
	else
		err = sparx5_port_pcs_low_set(sparx5, port, conf);

	if (err)
		return -EINVAL;

	if (port->conf.inband) {
		/* Enable/disable 1G counters in ASM */
		spx5_rmw(ASM_PORT_CFG_CSC_STAT_DIS_SET(high_speed_dev),
			 ASM_PORT_CFG_CSC_STAT_DIS,
			 sparx5,
			 ASM_PORT_CFG(port->portno));

		/* Enable/disable 1G counters in DSM */
		spx5_rmw(DSM_BUF_CFG_CSC_STAT_DIS_SET(high_speed_dev),
			 DSM_BUF_CFG_CSC_STAT_DIS,
			 sparx5,
			 DSM_BUF_CFG(port->portno));
	}

	port->conf = *conf;

	return 0;
}

int sparx5_port_config(struct sparx5 *sparx5,
		       struct sparx5_port *port,
		       struct sparx5_port_config *conf)
{
	bool high_speed_dev = sparx5_is_baser(conf->portmode);
	int err, urgency, stop_wm;

	err = sparx5_port_verify_speed(sparx5, port, conf);
	if (err)
		return err;

	/* high speed device is already configured */
	if (!high_speed_dev)
		sparx5_port_config_low_set(sparx5, port, conf);

	/* Configure flow control */
	err = sparx5_port_fc_setup(sparx5, port, conf);
	if (err)
		return err;

	/* Set the DSM stop watermark */
	stop_wm = sparx5_port_fifo_sz(sparx5, port->portno, conf->speed);
	spx5_rmw(DSM_DEV_TX_STOP_WM_CFG_DEV_TX_STOP_WM_SET(stop_wm),
		 DSM_DEV_TX_STOP_WM_CFG_DEV_TX_STOP_WM,
		 sparx5,
		 DSM_DEV_TX_STOP_WM_CFG(port->portno));

	/* Enable port in queue system */
	urgency = sparx5_port_fwd_urg(sparx5, conf->speed);
	spx5_rmw(QFWD_SWITCH_PORT_MODE_PORT_ENA_SET(1) |
		 QFWD_SWITCH_PORT_MODE_FWD_URGENCY_SET(urgency),
		 QFWD_SWITCH_PORT_MODE_PORT_ENA |
		 QFWD_SWITCH_PORT_MODE_FWD_URGENCY,
		 sparx5,
		 QFWD_SWITCH_PORT_MODE(port->portno));

	/* Save the new values */
	port->conf = *conf;

	return 0;
}

/* Initialize port config to default */
int sparx5_port_init(struct sparx5 *sparx5,
		     struct sparx5_port *port,
		     struct sparx5_port_config *conf)
{
	u32 pause_start = sparx5_wm_enc(6  * (ETH_MAXLEN / SPX5_BUFFER_CELL_SZ));
	u32 atop = sparx5_wm_enc(20 * (ETH_MAXLEN / SPX5_BUFFER_CELL_SZ));
	u32 devhigh = sparx5_to_high_dev(port->portno);
	u32 pix = sparx5_port_dev_index(port->portno);
	u32 pcs = sparx5_to_pcs_dev(port->portno);
	bool sd_pol = port->signd_active_high;
	bool sd_sel = !port->signd_internal;
	bool sd_ena = port->signd_enable;
	u32 pause_stop = 0xFFF - 1; /* FC generate disabled */
	void __iomem *devinst;
	void __iomem *pcsinst;
	int err;

	devinst = spx5_inst_get(sparx5, devhigh, pix);
	pcsinst = spx5_inst_get(sparx5, pcs, pix);

	/* Set the mux port mode  */
	err = sparx5_port_mux_set(sparx5, port, conf);
	if (err)
		return err;

	/* Configure MAC vlan awareness */
	err = sparx5_port_max_tags_set(sparx5, port);
	if (err)
		return err;

	/* Set Max Length */
	spx5_rmw(DEV2G5_MAC_MAXLEN_CFG_MAX_LEN_SET(ETH_MAXLEN),
		 DEV2G5_MAC_MAXLEN_CFG_MAX_LEN,
		 sparx5,
		 DEV2G5_MAC_MAXLEN_CFG(port->portno));

	/* 1G/2G5: Signal Detect configuration */
	spx5_wr(DEV2G5_PCS1G_SD_CFG_SD_POL_SET(sd_pol) |
		DEV2G5_PCS1G_SD_CFG_SD_SEL_SET(sd_sel) |
		DEV2G5_PCS1G_SD_CFG_SD_ENA_SET(sd_ena),
		sparx5,
		DEV2G5_PCS1G_SD_CFG(port->portno));

	/* Set Pause WM hysteresis */
	spx5_rmw(QSYS_PAUSE_CFG_PAUSE_START_SET(pause_start) |
		 QSYS_PAUSE_CFG_PAUSE_STOP_SET(pause_stop) |
		 QSYS_PAUSE_CFG_PAUSE_ENA_SET(1),
		 QSYS_PAUSE_CFG_PAUSE_START |
		 QSYS_PAUSE_CFG_PAUSE_STOP |
		 QSYS_PAUSE_CFG_PAUSE_ENA,
		 sparx5,
		 QSYS_PAUSE_CFG(port->portno));

	/* Port ATOP. Frames are tail dropped when this WM is hit */
	spx5_wr(QSYS_ATOP_ATOP_SET(atop),
		sparx5,
		QSYS_ATOP(port->portno));

	/* Discard pause frame 01-80-C2-00-00-01 */
	spx5_wr(PAUSE_DISCARD, sparx5, ANA_CL_CAPTURE_BPDU_CFG(port->portno));

	if (conf->portmode == PHY_INTERFACE_MODE_QSGMII ||
	    conf->portmode == PHY_INTERFACE_MODE_SGMII) {
		err = sparx5_serdes_set(sparx5, port, conf);
		if (err)
			return err;

		if (!sparx5_port_is_2g5(port->portno))
			/* Enable shadow device */
			spx5_rmw(DSM_DEV_TX_STOP_WM_CFG_DEV10G_SHADOW_ENA_SET(1),
				 DSM_DEV_TX_STOP_WM_CFG_DEV10G_SHADOW_ENA,
				 sparx5,
				 DSM_DEV_TX_STOP_WM_CFG(port->portno));

		sparx5_dev_switch(sparx5, port->portno, false);
	}
	if (conf->portmode == PHY_INTERFACE_MODE_QSGMII) {
		// All ports must be PCS enabled in QSGMII mode
		spx5_rmw(DEV2G5_DEV_RST_CTRL_PCS_TX_RST_SET(0),
			 DEV2G5_DEV_RST_CTRL_PCS_TX_RST,
			 sparx5,
			 DEV2G5_DEV_RST_CTRL(port->portno));
	}
	/* Default IFGs for 1G */
	spx5_wr(DEV2G5_MAC_IFG_CFG_TX_IFG_SET(6) |
		DEV2G5_MAC_IFG_CFG_RX_IFG1_SET(0) |
		DEV2G5_MAC_IFG_CFG_RX_IFG2_SET(0),
		sparx5,
		DEV2G5_MAC_IFG_CFG(port->portno));

	if (sparx5_port_is_2g5(port->portno))
		return 0; /* Low speed device only - return */

	/* Now setup the high speed device */
	if (conf->portmode == PHY_INTERFACE_MODE_NA)
		conf->portmode = PHY_INTERFACE_MODE_10GBASER;

	if (sparx5_is_baser(conf->portmode))
		sparx5_dev_switch(sparx5, port->portno, true);

	/* Set Max Length */
	spx5_inst_rmw(DEV10G_MAC_MAXLEN_CFG_MAX_LEN_SET(ETH_MAXLEN),
		      DEV10G_MAC_MAXLEN_CFG_MAX_LEN,
		      devinst,
		      DEV10G_MAC_ENA_CFG(0));

	/* Handle Signal Detect in 10G PCS */
	spx5_inst_wr(PCS10G_BR_PCS_SD_CFG_SD_POL_SET(sd_pol) |
		     PCS10G_BR_PCS_SD_CFG_SD_SEL_SET(sd_sel) |
		     PCS10G_BR_PCS_SD_CFG_SD_ENA_SET(sd_ena),
		     pcsinst,
		     PCS10G_BR_PCS_SD_CFG(0));

	if (sparx5_port_is_25g(port->portno)) {
		/* Handle Signal Detect in 25G PCS */
		spx5_wr(DEV25G_PCS25G_SD_CFG_SD_POL_SET(sd_pol) |
			DEV25G_PCS25G_SD_CFG_SD_SEL_SET(sd_sel) |
			DEV25G_PCS25G_SD_CFG_SD_ENA_SET(sd_ena),
			sparx5,
			DEV25G_PCS25G_SD_CFG(pix));
	}

	return 0;
}

void sparx5_port_enable(struct sparx5_port *port, bool enable)
{
	struct sparx5 *sparx5 = port->sparx5;

	/* Enable port for frame transfer? */
	spx5_rmw(QFWD_SWITCH_PORT_MODE_PORT_ENA_SET(enable),
		 QFWD_SWITCH_PORT_MODE_PORT_ENA,
		 sparx5,
		 QFWD_SWITCH_PORT_MODE(port->portno));
}
