// SPDX-License-Identifier: GPL-2.0-only
/*
 * Airoha AN8855 DSA Switch driver
 * Copyright (C) 2023 Min Yao <min.yao@airoha.com>
 * Copyright (C) 2024 Christian Marangi <ansuelsmth@gmail.com>
 */
#include <linux/bitfield.h>
#include <linux/ethtool.h>
#include <linux/etherdevice.h>
#include <linux/gpio/consumer.h>
#include <linux/if_bridge.h>
#include <linux/iopoll.h>
#include <linux/netdevice.h>
#include <linux/of_net.h>
#include <linux/phylink.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <net/dsa.h>

#include "an8855.h"

static const struct an8855_mib_desc an8855_mib[] = {
	MIB_DESC(1, AN8855_PORT_MIB_TX_DROP, "TxDrop"),
	MIB_DESC(1, AN8855_PORT_MIB_TX_CRC_ERR, "TxCrcErr"),
	MIB_DESC(1, AN8855_PORT_MIB_TX_COLLISION, "TxCollision"),
	MIB_DESC(1, AN8855_PORT_MIB_TX_OVERSIZE_DROP, "TxOversizeDrop"),
	MIB_DESC(2, AN8855_PORT_MIB_TX_BAD_PKT_BYTES, "TxBadPktBytes"),
	MIB_DESC(1, AN8855_PORT_MIB_RX_DROP, "RxDrop"),
	MIB_DESC(1, AN8855_PORT_MIB_RX_FILTERING, "RxFiltering"),
	MIB_DESC(1, AN8855_PORT_MIB_RX_CRC_ERR, "RxCrcErr"),
	MIB_DESC(1, AN8855_PORT_MIB_RX_CTRL_DROP, "RxCtrlDrop"),
	MIB_DESC(1, AN8855_PORT_MIB_RX_INGRESS_DROP, "RxIngressDrop"),
	MIB_DESC(1, AN8855_PORT_MIB_RX_ARL_DROP, "RxArlDrop"),
	MIB_DESC(1, AN8855_PORT_MIB_FLOW_CONTROL_DROP, "FlowControlDrop"),
	MIB_DESC(1, AN8855_PORT_MIB_WRED_DROP, "WredDrop"),
	MIB_DESC(1, AN8855_PORT_MIB_MIRROR_DROP, "MirrorDrop"),
	MIB_DESC(2, AN8855_PORT_MIB_RX_BAD_PKT_BYTES, "RxBadPktBytes"),
	MIB_DESC(1, AN8855_PORT_MIB_RXS_FLOW_SAMPLING_PKT_DROP, "RxsFlowSamplingPktDrop"),
	MIB_DESC(1, AN8855_PORT_MIB_RXS_FLOW_TOTAL_PKT_DROP, "RxsFlowTotalPktDrop"),
	MIB_DESC(1, AN8855_PORT_MIB_PORT_CONTROL_DROP, "PortControlDrop"),
};

static int
an8855_mib_init(struct an8855_priv *priv)
{
	int ret;

	ret = regmap_write(priv->regmap, AN8855_MIB_CCR,
			   AN8855_CCR_MIB_ENABLE);
	if (ret)
		return ret;

	return regmap_write(priv->regmap, AN8855_MIB_CCR,
			    AN8855_CCR_MIB_ACTIVATE);
}

static void an8855_fdb_write(struct an8855_priv *priv, u16 vid,
			     u8 port_mask, const u8 *mac,
			     bool add) __must_hold(&priv->reg_mutex)
{
	u32 mac_reg[2] = { };
	u32 reg;

	mac_reg[0] |= FIELD_PREP(AN8855_ATA1_MAC0, mac[0]);
	mac_reg[0] |= FIELD_PREP(AN8855_ATA1_MAC1, mac[1]);
	mac_reg[0] |= FIELD_PREP(AN8855_ATA1_MAC2, mac[2]);
	mac_reg[0] |= FIELD_PREP(AN8855_ATA1_MAC3, mac[3]);
	mac_reg[1] |= FIELD_PREP(AN8855_ATA2_MAC4, mac[4]);
	mac_reg[1] |= FIELD_PREP(AN8855_ATA2_MAC5, mac[5]);

	regmap_bulk_write(priv->regmap, AN8855_ATA1, mac_reg,
			  ARRAY_SIZE(mac_reg));

	reg = AN8855_ATWD_IVL;
	if (add)
		reg |= AN8855_ATWD_VLD;
	reg |= FIELD_PREP(AN8855_ATWD_VID, vid);
	reg |= FIELD_PREP(AN8855_ATWD_FID, AN8855_FID_BRIDGED);
	regmap_write(priv->regmap, AN8855_ATWD, reg);
	regmap_write(priv->regmap, AN8855_ATWD2,
		     FIELD_PREP(AN8855_ATWD2_PORT, port_mask));
}

static void an8855_fdb_read(struct an8855_priv *priv, struct an8855_fdb *fdb)
{
	u32 reg[4];

	regmap_bulk_read(priv->regmap, AN8855_ATRD0, reg,
			 ARRAY_SIZE(reg));

	fdb->live = FIELD_GET(AN8855_ATRD0_LIVE, reg[0]);
	fdb->type = FIELD_GET(AN8855_ATRD0_TYPE, reg[0]);
	fdb->ivl = FIELD_GET(AN8855_ATRD0_IVL, reg[0]);
	fdb->vid = FIELD_GET(AN8855_ATRD0_VID, reg[0]);
	fdb->fid = FIELD_GET(AN8855_ATRD0_FID, reg[0]);
	fdb->aging = FIELD_GET(AN8855_ATRD1_AGING, reg[1]);
	fdb->port_mask = FIELD_GET(AN8855_ATRD3_PORTMASK, reg[3]);
	fdb->mac[0] = FIELD_GET(AN8855_ATRD2_MAC0, reg[2]);
	fdb->mac[1] = FIELD_GET(AN8855_ATRD2_MAC1, reg[2]);
	fdb->mac[2] = FIELD_GET(AN8855_ATRD2_MAC2, reg[2]);
	fdb->mac[3] = FIELD_GET(AN8855_ATRD2_MAC3, reg[2]);
	fdb->mac[4] = FIELD_GET(AN8855_ATRD1_MAC4, reg[1]);
	fdb->mac[5] = FIELD_GET(AN8855_ATRD1_MAC5, reg[1]);
	fdb->noarp = !!FIELD_GET(AN8855_ATRD0_ARP, reg[0]);
}

static int an8855_fdb_cmd(struct an8855_priv *priv, u32 cmd,
			  u32 *rsp) __must_hold(&priv->reg_mutex)
{
	u32 val;
	int ret;

	/* Set the command operating upon the MAC address entries */
	val = AN8855_ATC_BUSY | cmd;
	ret = regmap_write(priv->regmap, AN8855_ATC, val);
	if (ret)
		return ret;

	ret = regmap_read_poll_timeout(priv->regmap, AN8855_ATC, val,
				       !(val & AN8855_ATC_BUSY), 20, 200000);
	if (ret)
		return ret;

	if (rsp)
		*rsp = val;

	return 0;
}

static void
an8855_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
{
	struct dsa_port *dp = dsa_to_port(ds, port);
	struct an8855_priv *priv = ds->priv;
	bool learning = false;
	u32 stp_state;

	switch (state) {
	case BR_STATE_DISABLED:
		stp_state = AN8855_STP_DISABLED;
		break;
	case BR_STATE_BLOCKING:
		stp_state = AN8855_STP_BLOCKING;
		break;
	case BR_STATE_LISTENING:
		stp_state = AN8855_STP_LISTENING;
		break;
	case BR_STATE_LEARNING:
		stp_state = AN8855_STP_LEARNING;
		learning = dp->learning;
		break;
	case BR_STATE_FORWARDING:
		learning = dp->learning;
		fallthrough;
	default:
		stp_state = AN8855_STP_FORWARDING;
		break;
	}

	regmap_update_bits(priv->regmap, AN8855_SSP_P(port),
			   AN8855_FID_PST_MASK(AN8855_FID_BRIDGED),
			   AN8855_FID_PST_VAL(AN8855_FID_BRIDGED, stp_state));

	regmap_update_bits(priv->regmap, AN8855_PSC_P(port), AN8855_SA_DIS,
			   learning ? 0 : AN8855_SA_DIS);
}

static void an8855_port_fast_age(struct dsa_switch *ds, int port)
{
	struct an8855_priv *priv = ds->priv;
	int ret;

	/* Set to clean Dynamic entry */
	ret = regmap_write(priv->regmap, AN8855_ATA2, AN8855_ATA2_TYPE);
	if (ret)
		return;

	/* Set Port */
	ret = regmap_write(priv->regmap, AN8855_ATWD2,
			   FIELD_PREP(AN8855_ATWD2_PORT, BIT(port)));
	if (ret)
		return;

	/* Flush Dynamic entry at port */
	an8855_fdb_cmd(priv, AN8855_ATC_MAT(AND8855_FDB_MAT_MAC_TYPE_PORT) |
		       AN8855_FDB_FLUSH, NULL);
}

static int an8855_update_port_member(struct dsa_switch *ds, int port,
				     const struct net_device *bridge_dev,
				     bool join)
{
	struct an8855_priv *priv = ds->priv;
	bool isolated, other_isolated;
	struct dsa_port *dp;
	u32 port_mask = 0;
	int ret;

	isolated = !!(priv->port_isolated_map & BIT(port));

	dsa_switch_for_each_user_port(dp, ds) {
		if (dp->index == port)
			continue;

		if (!dsa_port_offloads_bridge_dev(dp, bridge_dev))
			continue;

		other_isolated = !!(priv->port_isolated_map & BIT(dp->index));
		port_mask |= BIT(dp->index);
		/* Add/remove this port to the portvlan mask of the other
		 * ports in the bridge
		 */
		if (join && !(isolated && other_isolated))
			ret = regmap_set_bits(priv->regmap,
					      AN8855_PORTMATRIX_P(dp->index),
					      FIELD_PREP(AN8855_USER_PORTMATRIX,
							 BIT(port)));
		else
			ret = regmap_clear_bits(priv->regmap,
						AN8855_PORTMATRIX_P(dp->index),
						FIELD_PREP(AN8855_USER_PORTMATRIX,
							   BIT(port)));
		if (ret)
			return ret;
	}

	/* Add/remove all other ports to this port's portvlan mask */
	return regmap_update_bits(priv->regmap, AN8855_PORTMATRIX_P(port),
				  AN8855_USER_PORTMATRIX,
				  join ? port_mask : ~port_mask);
}

static int an8855_port_pre_bridge_flags(struct dsa_switch *ds, int port,
					struct switchdev_brport_flags flags,
					struct netlink_ext_ack *extack)
{
	if (flags.mask & ~(BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD |
			   BR_BCAST_FLOOD | BR_ISOLATED))
		return -EINVAL;

	return 0;
}

static int an8855_port_bridge_flags(struct dsa_switch *ds, int port,
				    struct switchdev_brport_flags flags,
				    struct netlink_ext_ack *extack)
{
	struct an8855_priv *priv = ds->priv;
	int ret;

	if (flags.mask & BR_LEARNING) {
		ret = regmap_update_bits(priv->regmap, AN8855_PSC_P(port), AN8855_SA_DIS,
					 flags.val & BR_LEARNING ? 0 : AN8855_SA_DIS);
		if (ret)
			return ret;
	}

	if (flags.mask & BR_FLOOD) {
		ret = regmap_update_bits(priv->regmap, AN8855_UNUF, BIT(port),
					 flags.val & BR_FLOOD ? BIT(port) : 0);
		if (ret)
			return ret;
	}

	if (flags.mask & BR_MCAST_FLOOD) {
		ret = regmap_update_bits(priv->regmap, AN8855_UNMF, BIT(port),
					 flags.val & BR_MCAST_FLOOD ? BIT(port) : 0);
		if (ret)
			return ret;

		ret = regmap_update_bits(priv->regmap, AN8855_UNIPMF, BIT(port),
					 flags.val & BR_MCAST_FLOOD ? BIT(port) : 0);
		if (ret)
			return ret;
	}

	if (flags.mask & BR_BCAST_FLOOD) {
		ret = regmap_update_bits(priv->regmap, AN8855_BCF, BIT(port),
					 flags.val & BR_BCAST_FLOOD ? BIT(port) : 0);
		if (ret)
			return ret;
	}

	if (flags.mask & BR_ISOLATED) {
		struct dsa_port *dp = dsa_to_port(ds, port);
		struct net_device *bridge_dev = dsa_port_bridge_dev_get(dp);

		if (flags.val & BR_ISOLATED)
			priv->port_isolated_map |= BIT(port);
		else
			priv->port_isolated_map &= ~BIT(port);

		ret = an8855_update_port_member(ds, port, bridge_dev, true);
		if (ret)
			return ret;
	}

	return 0;
}

static int an8855_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
{
	struct an8855_priv *priv = ds->priv;
	u32 age_count, age_unit, val;

	/* Convert msec in AN8855_L2_AGING_MS_CONSTANT counter */
	val = msecs / AN8855_L2_AGING_MS_CONSTANT;
	/* Derive the count unit */
	age_unit = val / FIELD_MAX(AN8855_AGE_UNIT);
	/* Get the count in unit, age_unit is always incremented by 1 internally */
	age_count = val / (age_unit + 1);

	return regmap_update_bits(priv->regmap, AN8855_AAC,
				  AN8855_AGE_CNT | AN8855_AGE_UNIT,
				  FIELD_PREP(AN8855_AGE_CNT, age_count) |
				  FIELD_PREP(AN8855_AGE_UNIT, age_unit));
}

static int an8855_port_bridge_join(struct dsa_switch *ds, int port,
				   struct dsa_bridge bridge,
				   bool *tx_fwd_offload,
				   struct netlink_ext_ack *extack)
{
	struct an8855_priv *priv = ds->priv;
	int ret;

	ret = an8855_update_port_member(ds, port, bridge.dev, true);
	if (ret)
		return ret;

	/* Set to fallback mode for independent VLAN learning if in a bridge */
	return regmap_update_bits(priv->regmap, AN8855_PCR_P(port),
				  AN8855_PORT_VLAN,
				  FIELD_PREP(AN8855_PORT_VLAN,
					     AN8855_PORT_FALLBACK_MODE));
}

static void an8855_port_bridge_leave(struct dsa_switch *ds, int port,
				     struct dsa_bridge bridge)
{
	struct an8855_priv *priv = ds->priv;

	an8855_update_port_member(ds, port, bridge.dev, false);

	/* When a port is removed from the bridge, the port would be set up
	 * back to the default as is at initial boot which is a VLAN-unaware
	 * port.
	 */
	regmap_update_bits(priv->regmap, AN8855_PCR_P(port),
			   AN8855_PORT_VLAN,
			   FIELD_PREP(AN8855_PORT_VLAN,
				      AN8855_PORT_MATRIX_MODE));
}

static int an8855_port_fdb_add(struct dsa_switch *ds, int port,
			       const unsigned char *addr, u16 vid,
			       struct dsa_db db)
{
	struct an8855_priv *priv = ds->priv;
	u8 port_mask = BIT(port);
	int ret;

	/* Set the vid to the port vlan id if no vid is set */
	if (!vid)
		vid = AN8855_PORT_VID_DEFAULT;

	mutex_lock(&priv->reg_mutex);
	an8855_fdb_write(priv, vid, port_mask, addr, true);
	ret = an8855_fdb_cmd(priv, AN8855_FDB_WRITE, NULL);
	mutex_unlock(&priv->reg_mutex);

	return ret;
}

static int an8855_port_fdb_del(struct dsa_switch *ds, int port,
			       const unsigned char *addr, u16 vid,
			       struct dsa_db db)
{
	struct an8855_priv *priv = ds->priv;
	u8 port_mask = BIT(port);
	int ret;

	/* Set the vid to the port vlan id if no vid is set */
	if (!vid)
		vid = AN8855_PORT_VID_DEFAULT;

	mutex_lock(&priv->reg_mutex);
	an8855_fdb_write(priv, vid, port_mask, addr, false);
	ret = an8855_fdb_cmd(priv, AN8855_FDB_WRITE, NULL);
	mutex_unlock(&priv->reg_mutex);

	return ret;
}

static int an8855_port_fdb_dump(struct dsa_switch *ds, int port,
				dsa_fdb_dump_cb_t *cb, void *data)
{
	struct an8855_priv *priv = ds->priv;
	int banks, count = 0;
	u32 rsp;
	int ret;
	int i;

	mutex_lock(&priv->reg_mutex);

	/* Load search port */
	ret = regmap_write(priv->regmap, AN8855_ATWD2,
			   FIELD_PREP(AN8855_ATWD2_PORT, BIT(port)));
	if (ret)
		goto exit;
	ret = an8855_fdb_cmd(priv, AN8855_ATC_MAT(AND8855_FDB_MAT_MAC_PORT) |
			     AN8855_FDB_START, &rsp);
	if (ret < 0)
		goto exit;

	do {
		/* From response get the number of banks to read, exit if 0 */
		banks = FIELD_GET(AN8855_ATC_HIT, rsp);
		if (!banks)
			break;

		/* Each banks have 4 entry */
		for (i = 0; i < 4; i++) {
			struct an8855_fdb _fdb = {  };

			count++;

			/* Check if bank is present */
			if (!(banks & BIT(i)))
				continue;

			/* Select bank entry index */
			ret = regmap_write(priv->regmap, AN8855_ATRDS,
					   FIELD_PREP(AN8855_ATRD_SEL, i));
			if (ret)
				break;
			/* wait 1ms for the bank entry to be filled */
			usleep_range(1000, 1500);
			an8855_fdb_read(priv, &_fdb);

			if (!_fdb.live)
				continue;
			ret = cb(_fdb.mac, _fdb.vid, _fdb.noarp, data);
			if (ret < 0)
				break;
		}

		/* Stop if reached max FDB number */
		if (count >= AN8855_NUM_FDB_RECORDS)
			break;

		/* Read next bank */
		ret = an8855_fdb_cmd(priv, AN8855_ATC_MAT(AND8855_FDB_MAT_MAC_PORT) |
				     AN8855_FDB_NEXT, &rsp);
		if (ret < 0)
			break;
	} while (true);

exit:
	mutex_unlock(&priv->reg_mutex);
	return ret;
}

static int an8855_vlan_cmd(struct an8855_priv *priv, enum an8855_vlan_cmd cmd,
			   u16 vid) __must_hold(&priv->reg_mutex)
{
	u32 val;
	int ret;

	val = AN8855_VTCR_BUSY | FIELD_PREP(AN8855_VTCR_FUNC, cmd) |
	      FIELD_PREP(AN8855_VTCR_VID, vid);
	ret = regmap_write(priv->regmap, AN8855_VTCR, val);
	if (ret)
		return ret;

	return regmap_read_poll_timeout(priv->regmap, AN8855_VTCR, val,
					!(val & AN8855_VTCR_BUSY), 20, 200000);
}

static int an8855_vlan_add(struct an8855_priv *priv, u8 port, u16 vid,
			   bool untagged) __must_hold(&priv->reg_mutex)
{
	u32 port_mask;
	u32 val;
	int ret;

	/* Fetch entry */
	ret = an8855_vlan_cmd(priv, AN8855_VTCR_RD_VID, vid);
	if (ret)
		return ret;

	ret = regmap_read(priv->regmap, AN8855_VARD0, &val);
	if (ret)
		return ret;
	port_mask = FIELD_GET(AN8855_VA0_PORT, val) | BIT(port);

	/* Validate the entry with independent learning, create egress tag per
	 * VLAN and joining the port as one of the port members.
	 */
	val = (val & AN8855_VA0_ETAG) | AN8855_VA0_IVL_MAC |
	      AN8855_VA0_VTAG_EN | AN8855_VA0_VLAN_VALID |
	      FIELD_PREP(AN8855_VA0_PORT, port_mask) |
	      FIELD_PREP(AN8855_VA0_FID, AN8855_FID_BRIDGED);
	ret = regmap_write(priv->regmap, AN8855_VAWD0, val);
	if (ret)
		return ret;
	ret = regmap_write(priv->regmap, AN8855_VAWD1, 0);
	if (ret)
		return ret;

	/* CPU port is always taken as a tagged port for serving more than one
	 * VLANs across and also being applied with egress type stack mode for
	 * that VLAN tags would be appended after hardware special tag used as
	 * DSA tag.
	 */
	if (port == AN8855_CPU_PORT)
		val = AN8855_VLAN_EGRESS_STACK;
	/* Decide whether adding tag or not for those outgoing packets from the
	 * port inside the VLAN.
	 */
	else
		val = untagged ? AN8855_VLAN_EGRESS_UNTAG : AN8855_VLAN_EGRESS_TAG;
	ret = regmap_update_bits(priv->regmap, AN8855_VAWD0,
				 AN8855_VA0_ETAG_PORT_MASK(port),
				 AN8855_VA0_ETAG_PORT_VAL(port, val));
	if (ret)
		return ret;

	/* Flush result to hardware */
	return an8855_vlan_cmd(priv, AN8855_VTCR_WR_VID, vid);
}

static int an8855_vlan_del(struct an8855_priv *priv, u8 port,
			   u16 vid) __must_hold(&priv->reg_mutex)
{
	u32 port_mask;
	u32 val;
	int ret;

	/* Fetch entry */
	ret = an8855_vlan_cmd(priv, AN8855_VTCR_RD_VID, vid);
	if (ret)
		return ret;

	ret = regmap_read(priv->regmap, AN8855_VARD0, &val);
	if (ret)
		return ret;
	port_mask = FIELD_GET(AN8855_VA0_PORT, val) & ~BIT(port);

	if (!(val & AN8855_VA0_VLAN_VALID)) {
		dev_err(priv->dev, "Cannot be deleted due to invalid entry\n");
		return -EINVAL;
	}

	if (port_mask) {
		val = (val & AN8855_VA0_ETAG) | AN8855_VA0_IVL_MAC |
		       AN8855_VA0_VTAG_EN | AN8855_VA0_VLAN_VALID |
		       FIELD_PREP(AN8855_VA0_PORT, port_mask);
		ret = regmap_write(priv->regmap, AN8855_VAWD0, val);
		if (ret)
			return ret;
	} else {
		ret = regmap_write(priv->regmap, AN8855_VAWD0, 0);
		if (ret)
			return ret;
	}
	ret = regmap_write(priv->regmap, AN8855_VAWD1, 0);
	if (ret)
		return ret;

	/* Flush result to hardware */
	return an8855_vlan_cmd(priv, AN8855_VTCR_WR_VID, vid);
}

static int an8855_port_set_vlan_mode(struct an8855_priv *priv, int port,
				     enum an8855_port_mode port_mode,
				     enum an8855_vlan_port_eg_tag eg_tag,
				     enum an8855_vlan_port_attr vlan_attr,
				     enum an8855_vlan_port_acc_frm acc_frm)
{
	int ret;

	ret = regmap_update_bits(priv->regmap, AN8855_PCR_P(port),
				 AN8855_PORT_VLAN,
				 FIELD_PREP(AN8855_PORT_VLAN, port_mode));
	if (ret)
		return ret;

	return regmap_update_bits(priv->regmap, AN8855_PVC_P(port),
				  AN8855_PVC_EG_TAG | AN8855_VLAN_ATTR | AN8855_ACC_FRM,
				  FIELD_PREP(AN8855_PVC_EG_TAG, eg_tag) |
				  FIELD_PREP(AN8855_VLAN_ATTR, vlan_attr) |
				  FIELD_PREP(AN8855_ACC_FRM, acc_frm));
}

static int an8855_port_set_pid(struct an8855_priv *priv, int port,
			       u16 pid)
{
	int ret;

	ret = regmap_update_bits(priv->regmap, AN8855_PPBV1_P(port),
				 AN8855_PPBV_G0_PORT_VID,
				 FIELD_PREP(AN8855_PPBV_G0_PORT_VID, pid));
	if (ret)
		return ret;

	return regmap_update_bits(priv->regmap, AN8855_PVID_P(port),
				  AN8855_G0_PORT_VID,
				  FIELD_PREP(AN8855_G0_PORT_VID, pid));
}

static int an8855_port_vlan_filtering(struct dsa_switch *ds, int port,
				      bool vlan_filtering,
				      struct netlink_ext_ack *extack)
{
	struct an8855_priv *priv = ds->priv;
	u32 val;
	int ret;

	/* The port is being kept as VLAN-unaware port when bridge is
	 * set up with vlan_filtering not being set, Otherwise, the
	 * port and the corresponding CPU port is required the setup
	 * for becoming a VLAN-aware port.
	 */
	if (vlan_filtering) {
		u32 acc_frm;
		/* CPU port is set to fallback mode to let untagged
		 * frames pass through.
		 */
		ret = an8855_port_set_vlan_mode(priv, AN8855_CPU_PORT,
						AN8855_PORT_FALLBACK_MODE,
						AN8855_VLAN_EG_CONSISTENT,
						AN8855_VLAN_USER,
						AN8855_VLAN_ACC_ALL);
		if (ret)
			return ret;

		ret = regmap_read(priv->regmap, AN8855_PVID_P(port), &val);
		if (ret)
			return ret;

		/* Only accept tagged frames if PVID is not set */
		if (FIELD_GET(AN8855_G0_PORT_VID, val) != AN8855_PORT_VID_DEFAULT)
			acc_frm = AN8855_VLAN_ACC_TAGGED;
		else
			acc_frm = AN8855_VLAN_ACC_ALL;

		/* Trapped into security mode allows packet forwarding through VLAN
		 * table lookup.
		 * Set the port as a user port which is to be able to recognize VID
		 * from incoming packets before fetching entry within the VLAN table.
		 */
		ret = an8855_port_set_vlan_mode(priv, port,
						AN8855_PORT_SECURITY_MODE,
						AN8855_VLAN_EG_DISABLED,
						AN8855_VLAN_USER,
						acc_frm);
		if (ret)
			return ret;
	} else {
		bool disable_cpu_vlan = true;
		struct dsa_port *dp;
		u32 port_mode;

		/* This is called after .port_bridge_leave when leaving a VLAN-aware
		 * bridge. Don't set standalone ports to fallback mode.
		 */
		if (dsa_port_bridge_dev_get(dsa_to_port(ds, port)))
			port_mode = AN8855_PORT_FALLBACK_MODE;
		else
			port_mode = AN8855_PORT_MATRIX_MODE;

		/* When a port is removed from the bridge, the port would be set up
		 * back to the default as is at initial boot which is a VLAN-unaware
		 * port.
		 */
		ret = an8855_port_set_vlan_mode(priv, port, port_mode,
						AN8855_VLAN_EG_CONSISTENT,
						AN8855_VLAN_TRANSPARENT,
						AN8855_VLAN_ACC_ALL);
		if (ret)
			return ret;

		/* Restore default PVID */
		ret = an8855_port_set_pid(priv, port, AN8855_PORT_VID_DEFAULT);
		if (ret)
			return ret;

		dsa_switch_for_each_user_port(dp, ds) {
			if (dsa_port_is_vlan_filtering(dp)) {
				disable_cpu_vlan = false;
				break;
			}
		}

		if (disable_cpu_vlan) {
			ret = an8855_port_set_vlan_mode(priv, AN8855_CPU_PORT,
							AN8855_PORT_MATRIX_MODE,
							AN8855_VLAN_EG_CONSISTENT,
							AN8855_VLAN_USER,
							AN8855_VLAN_ACC_ALL);
			if (ret)
				return ret;
		}
	}

	return 0;
}

static int an8855_port_vlan_add(struct dsa_switch *ds, int port,
				const struct switchdev_obj_port_vlan *vlan,
				struct netlink_ext_ack *extack)
{
	bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
	bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
	struct an8855_priv *priv = ds->priv;
	u32 val;
	int ret;

	mutex_lock(&priv->reg_mutex);
	ret = an8855_vlan_add(priv, port, vlan->vid, untagged);
	mutex_unlock(&priv->reg_mutex);
	if (ret)
		return ret;

	if (pvid) {
		/* Accept all frames if PVID is set */
		regmap_update_bits(priv->regmap, AN8855_PVC_P(port), AN8855_ACC_FRM,
				   FIELD_PREP(AN8855_ACC_FRM, AN8855_VLAN_ACC_ALL));

		/* Only configure PVID if VLAN filtering is enabled */
		if (dsa_port_is_vlan_filtering(dsa_to_port(ds, port))) {
			ret = an8855_port_set_pid(priv, port, vlan->vid);
			if (ret)
				return ret;
		}
	} else if (vlan->vid) {
		ret = regmap_read(priv->regmap, AN8855_PVID_P(port), &val);
		if (ret)
			return ret;

		if (FIELD_GET(AN8855_G0_PORT_VID, val) != vlan->vid)
			return 0;

		/* This VLAN is overwritten without PVID, so unset it */
		if (dsa_port_is_vlan_filtering(dsa_to_port(ds, port))) {
			ret = regmap_update_bits(priv->regmap, AN8855_PVC_P(port),
						 AN8855_ACC_FRM,
						 FIELD_PREP(AN8855_ACC_FRM,
							    AN8855_VLAN_ACC_TAGGED));
			if (ret)
				return ret;
		}

		ret = an8855_port_set_pid(priv, port, AN8855_PORT_VID_DEFAULT);
		if (ret)
			return ret;
	}

	return 0;
}

static int an8855_port_vlan_del(struct dsa_switch *ds, int port,
				const struct switchdev_obj_port_vlan *vlan)
{
	struct an8855_priv *priv = ds->priv;
	u32 val;
	int ret;

	mutex_lock(&priv->reg_mutex);
	ret = an8855_vlan_del(priv, port, vlan->vid);
	mutex_unlock(&priv->reg_mutex);
	if (ret)
		return ret;

	ret = regmap_read(priv->regmap, AN8855_PVID_P(port), &val);
	if (ret)
		return ret;

	/* PVID is being restored to the default whenever the PVID port
	 * is being removed from the VLAN.
	 */
	if (FIELD_GET(AN8855_G0_PORT_VID, val) == vlan->vid) {
		/* Only accept tagged frames if the port is VLAN-aware */
		if (dsa_port_is_vlan_filtering(dsa_to_port(ds, port))) {
			ret = regmap_update_bits(priv->regmap, AN8855_PVC_P(port),
						 AN8855_ACC_FRM,
						 FIELD_PREP(AN8855_ACC_FRM,
							    AN8855_VLAN_ACC_TAGGED));
			if (ret)
				return ret;
		}

		ret = an8855_port_set_pid(priv, port, AN8855_PORT_VID_DEFAULT);
		if (ret)
			return ret;
	}

	return 0;
}

static int
an8855_port_mdb_add(struct dsa_switch *ds, int port,
		    const struct switchdev_obj_port_mdb *mdb,
		    struct dsa_db db)
{
	struct an8855_priv *priv = ds->priv;
	const u8 *addr = mdb->addr;
	u16 vid = mdb->vid;
	u8 port_mask = 0;
	u32 val;
	int ret;

	/* Set the vid to the port vlan id if no vid is set */
	if (!vid)
		vid = AN8855_PORT_VID_DEFAULT;

	mutex_lock(&priv->reg_mutex);

	an8855_fdb_write(priv, vid, 0, addr, false);
	if (!an8855_fdb_cmd(priv, AN8855_FDB_READ, NULL)) {
		ret = regmap_read(priv->regmap, AN8855_ATRD3, &val);
		if (ret)
			goto exit;

		port_mask = FIELD_GET(AN8855_ATRD3_PORTMASK, val);
	}

	port_mask |= BIT(port);
	an8855_fdb_write(priv, vid, port_mask, addr, true);
	ret = an8855_fdb_cmd(priv, AN8855_FDB_WRITE, NULL);

exit:
	mutex_unlock(&priv->reg_mutex);

	return ret;
}

static int
an8855_port_mdb_del(struct dsa_switch *ds, int port,
		    const struct switchdev_obj_port_mdb *mdb,
		    struct dsa_db db)
{
	struct an8855_priv *priv = ds->priv;
	const u8 *addr = mdb->addr;
	u16 vid = mdb->vid;
	u8 port_mask = 0;
	u32 val;
	int ret;

	/* Set the vid to the port vlan id if no vid is set */
	if (!vid)
		vid = AN8855_PORT_VID_DEFAULT;

	mutex_lock(&priv->reg_mutex);

	an8855_fdb_write(priv, vid, 0, addr, 0);
	if (!an8855_fdb_cmd(priv, AN8855_FDB_READ, NULL)) {
		ret = regmap_read(priv->regmap, AN8855_ATRD3, &val);
		if (ret)
			goto exit;

		port_mask = FIELD_GET(AN8855_ATRD3_PORTMASK, val);
	}

	port_mask &= ~BIT(port);
	an8855_fdb_write(priv, vid, port_mask, addr, port_mask ? true : false);
	ret = an8855_fdb_cmd(priv, AN8855_FDB_WRITE, NULL);

exit:
	mutex_unlock(&priv->reg_mutex);

	return ret;
}

static int
an8855_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu)
{
	struct an8855_priv *priv = ds->priv;
	int length;
	u32 val;

	/* When a new MTU is set, DSA always set the CPU port's MTU to the
	 * largest MTU of the slave ports. Because the switch only has a global
	 * RX length register, only allowing CPU port here is enough.
	 */
	if (!dsa_is_cpu_port(ds, port))
		return 0;

	/* RX length also includes Ethernet header, MTK tag, and FCS length */
	length = new_mtu + ETH_HLEN + MTK_TAG_LEN + ETH_FCS_LEN;
	if (length <= 1522)
		val = AN8855_MAX_RX_PKT_1518_1522;
	else if (length <= 1536)
		val = AN8855_MAX_RX_PKT_1536;
	else if (length <= 1552)
		val = AN8855_MAX_RX_PKT_1552;
	else if (length <= 3072)
		val = AN8855_MAX_RX_JUMBO_3K;
	else if (length <= 4096)
		val = AN8855_MAX_RX_JUMBO_4K;
	else if (length <= 5120)
		val = AN8855_MAX_RX_JUMBO_5K;
	else if (length <= 6144)
		val = AN8855_MAX_RX_JUMBO_6K;
	else if (length <= 7168)
		val = AN8855_MAX_RX_JUMBO_7K;
	else if (length <= 8192)
		val = AN8855_MAX_RX_JUMBO_8K;
	else if (length <= 9216)
		val = AN8855_MAX_RX_JUMBO_9K;
	else if (length <= 12288)
		val = AN8855_MAX_RX_JUMBO_12K;
	else if (length <= 15360)
		val = AN8855_MAX_RX_JUMBO_15K;
	else
		val = AN8855_MAX_RX_JUMBO_16K;

	/* Enable JUMBO packet */
	if (length > 1552)
		val |= AN8855_MAX_RX_PKT_JUMBO;

	return regmap_update_bits(priv->regmap, AN8855_GMACCR,
				  AN8855_MAX_RX_JUMBO | AN8855_MAX_RX_PKT_LEN,
				  val);
}

static int
an8855_port_max_mtu(struct dsa_switch *ds, int port)
{
	return AN8855_MAX_MTU;
}

static void
an8855_get_strings(struct dsa_switch *ds, int port, u32 stringset,
		   uint8_t *data)
{
	int i;

	if (stringset != ETH_SS_STATS)
		return;

	for (i = 0; i < ARRAY_SIZE(an8855_mib); i++)
		ethtool_puts(&data, an8855_mib[i].name);
}

static void
an8855_read_port_stats(struct an8855_priv *priv, int port, u32 offset, u8 size,
		       uint64_t *data)
{
	u32 val, reg = AN8855_PORT_MIB_COUNTER(port) + offset;

	regmap_read(priv->regmap, reg, &val);
	*data = val;

	if (size == 2) {
		regmap_read(priv->regmap, reg + 4, &val);
		*data |= (u64)val << 32;
	}
}

static void
an8855_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *data)
{
	struct an8855_priv *priv = ds->priv;
	const struct an8855_mib_desc *mib;
	int i;

	for (i = 0; i < ARRAY_SIZE(an8855_mib); i++) {
		mib = &an8855_mib[i];

		an8855_read_port_stats(priv, port, mib->offset, mib->size,
				       data + i);
	}
}

static int
an8855_get_sset_count(struct dsa_switch *ds, int port, int sset)
{
	if (sset != ETH_SS_STATS)
		return 0;

	return ARRAY_SIZE(an8855_mib);
}

static void
an8855_get_eth_mac_stats(struct dsa_switch *ds, int port,
			 struct ethtool_eth_mac_stats *mac_stats)
{
	struct an8855_priv *priv = ds->priv;

	/* MIB counter doesn't provide a FramesTransmittedOK but instead
	 * provide stats for Unicast, Broadcast and Multicast frames separately.
	 * To simulate a global frame counter, read Unicast and addition Multicast
	 * and Broadcast later
	 */
	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_UNICAST, 1,
			       &mac_stats->FramesTransmittedOK);

	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_SINGLE_COLLISION, 1,
			       &mac_stats->SingleCollisionFrames);

	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_MULTIPLE_COLLISION, 1,
			       &mac_stats->MultipleCollisionFrames);

	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_UNICAST, 1,
			       &mac_stats->FramesReceivedOK);

	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_BYTES, 2,
			       &mac_stats->OctetsTransmittedOK);

	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_ALIGN_ERR, 1,
			       &mac_stats->AlignmentErrors);

	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_DEFERRED, 1,
			       &mac_stats->FramesWithDeferredXmissions);

	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_LATE_COLLISION, 1,
			       &mac_stats->LateCollisions);

	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_EXCESSIVE_COLLISION, 1,
			       &mac_stats->FramesAbortedDueToXSColls);

	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_BYTES, 2,
			       &mac_stats->OctetsReceivedOK);

	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_MULTICAST, 1,
			       &mac_stats->MulticastFramesXmittedOK);
	mac_stats->FramesTransmittedOK += mac_stats->MulticastFramesXmittedOK;
	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_BROADCAST, 1,
			       &mac_stats->BroadcastFramesXmittedOK);
	mac_stats->FramesTransmittedOK += mac_stats->BroadcastFramesXmittedOK;

	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_MULTICAST, 1,
			       &mac_stats->MulticastFramesReceivedOK);
	mac_stats->FramesReceivedOK += mac_stats->MulticastFramesReceivedOK;
	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_BROADCAST, 1,
			       &mac_stats->BroadcastFramesReceivedOK);
	mac_stats->FramesReceivedOK += mac_stats->BroadcastFramesReceivedOK;
}

static const struct ethtool_rmon_hist_range an8855_rmon_ranges[] = {
	{ 0, 64 },
	{ 65, 127 },
	{ 128, 255 },
	{ 256, 511 },
	{ 512, 1023 },
	{ 1024, 1518 },
	{ 1519, AN8855_MAX_MTU },
	{}
};

static void an8855_get_rmon_stats(struct dsa_switch *ds, int port,
				  struct ethtool_rmon_stats *rmon_stats,
				  const struct ethtool_rmon_hist_range **ranges)
{
	struct an8855_priv *priv = ds->priv;

	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_UNDER_SIZE_ERR, 1,
			       &rmon_stats->undersize_pkts);
	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_OVER_SZ_ERR, 1,
			       &rmon_stats->oversize_pkts);
	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_FRAG_ERR, 1,
			       &rmon_stats->fragments);
	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_JABBER_ERR, 1,
			       &rmon_stats->jabbers);

	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_PKT_SZ_64, 1,
			       &rmon_stats->hist[0]);
	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_PKT_SZ_65_TO_127, 1,
			       &rmon_stats->hist[1]);
	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_PKT_SZ_128_TO_255, 1,
			       &rmon_stats->hist[2]);
	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_PKT_SZ_256_TO_511, 1,
			       &rmon_stats->hist[3]);
	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_PKT_SZ_512_TO_1023, 1,
			       &rmon_stats->hist[4]);
	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_PKT_SZ_1024_TO_1518, 1,
			       &rmon_stats->hist[5]);
	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_PKT_SZ_1519_TO_MAX, 1,
			       &rmon_stats->hist[6]);

	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_PKT_SZ_64, 1,
			       &rmon_stats->hist_tx[0]);
	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_PKT_SZ_65_TO_127, 1,
			       &rmon_stats->hist_tx[1]);
	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_PKT_SZ_128_TO_255, 1,
			       &rmon_stats->hist_tx[2]);
	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_PKT_SZ_256_TO_511, 1,
			       &rmon_stats->hist_tx[3]);
	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_PKT_SZ_512_TO_1023, 1,
			       &rmon_stats->hist_tx[4]);
	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_PKT_SZ_1024_TO_1518, 1,
			       &rmon_stats->hist_tx[5]);
	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_PKT_SZ_1519_TO_MAX, 1,
			       &rmon_stats->hist_tx[6]);

	*ranges = an8855_rmon_ranges;
}

static void an8855_get_eth_ctrl_stats(struct dsa_switch *ds, int port,
				      struct ethtool_eth_ctrl_stats *ctrl_stats)
{
	struct an8855_priv *priv = ds->priv;

	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_TX_PAUSE, 1,
			       &ctrl_stats->MACControlFramesTransmitted);

	an8855_read_port_stats(priv, port, AN8855_PORT_MIB_RX_PAUSE, 1,
			       &ctrl_stats->MACControlFramesReceived);
}

static int an8855_port_mirror_add(struct dsa_switch *ds, int port,
				  struct dsa_mall_mirror_tc_entry *mirror,
				  bool ingress,
				  struct netlink_ext_ack *extack)
{
	struct an8855_priv *priv = ds->priv;
	int monitor_port;
	u32 val;
	int ret;

	/* Check for existent entry */
	if ((ingress ? priv->mirror_rx : priv->mirror_tx) & BIT(port))
		return -EEXIST;

	ret = regmap_read(priv->regmap, AN8855_MIR, &val);
	if (ret)
		return ret;

	/* AN8855 supports 4 monitor port, but only use first group */
	monitor_port = FIELD_GET(AN8855_MIRROR_PORT, val);
	if (val & AN8855_MIRROR_EN && monitor_port != mirror->to_local_port)
		return -EEXIST;

	val = AN8855_MIRROR_EN;
	val |= FIELD_PREP(AN8855_MIRROR_PORT, mirror->to_local_port);
	ret = regmap_update_bits(priv->regmap, AN8855_MIR,
				 AN8855_MIRROR_EN | AN8855_MIRROR_PORT,
				 val);
	if (ret)
		return ret;

	ret = regmap_set_bits(priv->regmap, AN8855_PCR_P(port),
			      ingress ? AN8855_PORT_RX_MIR : AN8855_PORT_TX_MIR);
	if (ret)
		return ret;

	if (ingress)
		priv->mirror_rx |= BIT(port);
	else
		priv->mirror_tx |= BIT(port);

	return 0;
}

static void an8855_port_mirror_del(struct dsa_switch *ds, int port,
				   struct dsa_mall_mirror_tc_entry *mirror)
{
	struct an8855_priv *priv = ds->priv;

	if (mirror->ingress)
		priv->mirror_rx &= ~BIT(port);
	else
		priv->mirror_tx &= ~BIT(port);

	regmap_clear_bits(priv->regmap, AN8855_PCR_P(port),
			  mirror->ingress ? AN8855_PORT_RX_MIR :
					    AN8855_PORT_TX_MIR);

	if (!priv->mirror_rx && !priv->mirror_tx)
		regmap_clear_bits(priv->regmap, AN8855_MIR, AN8855_MIRROR_EN);
}

static int an8855_port_set_status(struct an8855_priv *priv, int port,
				  bool enable)
{
	if (enable)
		return regmap_set_bits(priv->regmap, AN8855_PMCR_P(port),
				       AN8855_PMCR_TX_EN | AN8855_PMCR_RX_EN);
	else
		return regmap_clear_bits(priv->regmap, AN8855_PMCR_P(port),
					 AN8855_PMCR_TX_EN | AN8855_PMCR_RX_EN);
}

static int an8855_port_enable(struct dsa_switch *ds, int port,
			      struct phy_device *phy)
{
	return an8855_port_set_status(ds->priv, port, true);
}

static void an8855_port_disable(struct dsa_switch *ds, int port)
{
	an8855_port_set_status(ds->priv, port, false);
}

static u32 en8855_get_phy_flags(struct dsa_switch *ds, int port)
{
	struct an8855_priv *priv = ds->priv;

	/* PHY doesn't need calibration */
	if (!priv->phy_require_calib)
		return 0;

	/* Use AN8855_PHY_FLAGS_EN_CALIBRATION to signal
	 * calibration needed.
	 */
	return AN8855_PHY_FLAGS_EN_CALIBRATION;
}

static enum dsa_tag_protocol
an8855_get_tag_protocol(struct dsa_switch *ds, int port,
			enum dsa_tag_protocol mp)
{
	return DSA_TAG_PROTO_MTK;
}

/* Similar to MT7530 also trap link local frame and special frame to CPU */
static int an8855_trap_special_frames(struct an8855_priv *priv)
{
	int ret;

	/* Trap BPDUs to the CPU port(s) and egress them
	 * VLAN-untagged.
	 */
	ret = regmap_update_bits(priv->regmap, AN8855_BPC,
				 AN8855_BPDU_BPDU_FR | AN8855_BPDU_EG_TAG |
				 AN8855_BPDU_PORT_FW,
				 AN8855_BPDU_BPDU_FR |
				 FIELD_PREP(AN8855_BPDU_EG_TAG, AN8855_VLAN_EG_UNTAGGED) |
				 FIELD_PREP(AN8855_BPDU_PORT_FW, AN8855_BPDU_CPU_ONLY));
	if (ret)
		return ret;

	/* Trap 802.1X PAE frames to the CPU port(s) and egress them
	 * VLAN-untagged.
	 */
	ret = regmap_update_bits(priv->regmap, AN8855_PAC,
				 AN8855_PAE_BPDU_FR | AN8855_PAE_EG_TAG |
				 AN8855_PAE_PORT_FW,
				 AN8855_PAE_BPDU_FR |
				 FIELD_PREP(AN8855_PAE_EG_TAG, AN8855_VLAN_EG_UNTAGGED) |
				 FIELD_PREP(AN8855_PAE_PORT_FW, AN8855_BPDU_CPU_ONLY));
	if (ret)
		return ret;

	/* Trap frames with :01 MAC DAs to the CPU port(s) and egress
	 * them VLAN-untagged.
	 */
	ret = regmap_update_bits(priv->regmap, AN8855_RGAC1,
				 AN8855_R01_BPDU_FR | AN8855_R01_EG_TAG |
				 AN8855_R01_PORT_FW,
				 AN8855_R01_BPDU_FR |
				 FIELD_PREP(AN8855_R01_EG_TAG, AN8855_VLAN_EG_UNTAGGED) |
				 FIELD_PREP(AN8855_R01_PORT_FW, AN8855_BPDU_CPU_ONLY));
	if (ret)
		return ret;

	/* Trap frames with :02 MAC DAs to the CPU port(s) and egress
	 * them VLAN-untagged.
	 */
	ret = regmap_update_bits(priv->regmap, AN8855_RGAC1,
				 AN8855_R02_BPDU_FR | AN8855_R02_EG_TAG |
				 AN8855_R02_PORT_FW,
				 AN8855_R02_BPDU_FR |
				 FIELD_PREP(AN8855_R02_EG_TAG, AN8855_VLAN_EG_UNTAGGED) |
				 FIELD_PREP(AN8855_R02_PORT_FW, AN8855_BPDU_CPU_ONLY));
	if (ret)
		return ret;

	/* Trap frames with :03 MAC DAs to the CPU port(s) and egress
	 * them VLAN-untagged.
	 */
	ret = regmap_update_bits(priv->regmap, AN8855_RGAC1,
				 AN8855_R03_BPDU_FR | AN8855_R03_EG_TAG |
				 AN8855_R03_PORT_FW,
				 AN8855_R03_BPDU_FR |
				 FIELD_PREP(AN8855_R03_EG_TAG, AN8855_VLAN_EG_UNTAGGED) |
				 FIELD_PREP(AN8855_R03_PORT_FW, AN8855_BPDU_CPU_ONLY));
	if (ret)
		return ret;

	/* Trap frames with :0E MAC DAs to the CPU port(s) and egress
	 * them VLAN-untagged.
	 */
	return regmap_update_bits(priv->regmap, AN8855_RGAC1,
				  AN8855_R0E_BPDU_FR | AN8855_R0E_EG_TAG |
				  AN8855_R0E_PORT_FW,
				  AN8855_R0E_BPDU_FR |
				  FIELD_PREP(AN8855_R0E_EG_TAG, AN8855_VLAN_EG_UNTAGGED) |
				  FIELD_PREP(AN8855_R0E_PORT_FW, AN8855_BPDU_CPU_ONLY));
}

static int
an8855_setup_pvid_vlan(struct an8855_priv *priv)
{
	u32 val;
	int ret;

	/* Validate the entry with independent learning, keep the original
	 * ingress tag attribute.
	 */
	val = AN8855_VA0_IVL_MAC | AN8855_VA0_EG_CON |
	      FIELD_PREP(AN8855_VA0_FID, AN8855_FID_BRIDGED) |
	      AN8855_VA0_PORT | AN8855_VA0_VLAN_VALID;
	ret = regmap_write(priv->regmap, AN8855_VAWD0, val);
	if (ret)
		return ret;

	return an8855_vlan_cmd(priv, AN8855_VTCR_WR_VID,
			       AN8855_PORT_VID_DEFAULT);
}

static int an8855_setup(struct dsa_switch *ds)
{
	struct an8855_priv *priv = ds->priv;
	struct dsa_port *dp;
	int ret;

	/* Enable and reset MIB counters */
	ret = an8855_mib_init(priv);
	if (ret)
		return ret;

	dsa_switch_for_each_user_port(dp, ds) {
		/* Disable MAC by default on all user ports */
		ret = an8855_port_set_status(priv, dp->index, false);
		if (ret)
			return ret;

		/* Individual user ports get connected to CPU port only */
		ret = regmap_write(priv->regmap, AN8855_PORTMATRIX_P(dp->index),
				   FIELD_PREP(AN8855_PORTMATRIX, BIT(AN8855_CPU_PORT)));
		if (ret)
			return ret;

		/* Disable Broadcast Forward on user ports */
		ret = regmap_clear_bits(priv->regmap, AN8855_BCF, BIT(dp->index));
		if (ret)
			return ret;

		/* Disable Unknown Unicast Forward on user ports */
		ret = regmap_clear_bits(priv->regmap, AN8855_UNUF, BIT(dp->index));
		if (ret)
			return ret;

		/* Disable Unknown Multicast Forward on user ports */
		ret = regmap_clear_bits(priv->regmap, AN8855_UNMF, BIT(dp->index));
		if (ret)
			return ret;

		ret = regmap_clear_bits(priv->regmap, AN8855_UNIPMF, BIT(dp->index));
		if (ret)
			return ret;

		/* Set default PVID to on all user ports */
		ret = an8855_port_set_pid(priv, dp->index, AN8855_PORT_VID_DEFAULT);
		if (ret)
			return ret;
	}

	/* Enable Airoha header mode on the cpu port */
	ret = regmap_write(priv->regmap, AN8855_PVC_P(AN8855_CPU_PORT),
			   AN8855_PORT_SPEC_REPLACE_MODE | AN8855_PORT_SPEC_TAG);
	if (ret)
		return ret;

	/* Unknown multicast frame forwarding to the cpu port */
	ret = regmap_write(priv->regmap, AN8855_UNMF, BIT(AN8855_CPU_PORT));
	if (ret)
		return ret;

	/* Set CPU port number */
	ret = regmap_update_bits(priv->regmap, AN8855_MFC,
				 AN8855_CPU_EN | AN8855_CPU_PORT_IDX,
				 AN8855_CPU_EN |
				 FIELD_PREP(AN8855_CPU_PORT_IDX, AN8855_CPU_PORT));
	if (ret)
		return ret;

	/* CPU port gets connected to all user ports of
	 * the switch.
	 */
	ret = regmap_write(priv->regmap, AN8855_PORTMATRIX_P(AN8855_CPU_PORT),
			   FIELD_PREP(AN8855_PORTMATRIX, dsa_user_ports(ds)));
	if (ret)
		return ret;

	/* CPU port is set to fallback mode to let untagged
	 * frames pass through.
	 */
	ret = regmap_update_bits(priv->regmap, AN8855_PCR_P(AN8855_CPU_PORT),
				 AN8855_PORT_VLAN,
				 FIELD_PREP(AN8855_PORT_VLAN, AN8855_PORT_FALLBACK_MODE));
	if (ret)
		return ret;

	/* Enable Broadcast Forward on CPU port */
	ret = regmap_set_bits(priv->regmap, AN8855_BCF, BIT(AN8855_CPU_PORT));
	if (ret)
		return ret;

	/* Enable Unknown Unicast Forward on CPU port */
	ret = regmap_set_bits(priv->regmap, AN8855_UNUF, BIT(AN8855_CPU_PORT));
	if (ret)
		return ret;

	/* Enable Unknown Multicast Forward on CPU port */
	ret = regmap_set_bits(priv->regmap, AN8855_UNMF, BIT(AN8855_CPU_PORT));
	if (ret)
		return ret;

	ret = regmap_set_bits(priv->regmap, AN8855_UNIPMF, BIT(AN8855_CPU_PORT));
	if (ret)
		return ret;

	/* Setup Trap special frame to CPU rules */
	ret = an8855_trap_special_frames(priv);
	if (ret)
		return ret;

	dsa_switch_for_each_port(dp, ds) {
		/* Disable Learning on all ports.
		 * Learning on CPU is disabled for fdb isolation and handled by
		 * assisted_learning_on_cpu_port.
		 */
		ret = regmap_set_bits(priv->regmap, AN8855_PSC_P(dp->index),
				      AN8855_SA_DIS);
		if (ret)
			return ret;

		/* Enable consistent egress tag (for VLAN unware VLAN-passtrough) */
		ret = regmap_update_bits(priv->regmap, AN8855_PVC_P(dp->index),
					 AN8855_PVC_EG_TAG,
					 FIELD_PREP(AN8855_PVC_EG_TAG, AN8855_VLAN_EG_CONSISTENT));
		if (ret)
			return ret;
	}

	/* Setup VLAN for Default PVID */
	ret = an8855_setup_pvid_vlan(priv);
	if (ret)
		return ret;

	ret = regmap_clear_bits(priv->regmap, AN8855_CKGCR,
				AN8855_CKG_LNKDN_GLB_STOP | AN8855_CKG_LNKDN_PORT_STOP);
	if (ret)
		return ret;

	/* Release global PHY power down */
	ret = regmap_write(priv->regmap, AN8855_RG_GPHY_AFE_PWD, 0x0);
	if (ret)
		return ret;

	ds->configure_vlan_while_not_filtering = true;

	/* Flush the FDB table */
	ret = an8855_fdb_cmd(priv, AN8855_FDB_FLUSH, NULL);
	if (ret < 0)
		return ret;

	/* Set min a max ageing value supported */
	ds->ageing_time_min = AN8855_L2_AGING_MS_CONSTANT;
	ds->ageing_time_max = FIELD_MAX(AN8855_AGE_CNT) *
			      FIELD_MAX(AN8855_AGE_UNIT) *
			      AN8855_L2_AGING_MS_CONSTANT;

	/* Enable assisted learning for fdb isolation */
	ds->assisted_learning_on_cpu_port = true;

	return 0;
}

static struct phylink_pcs *an8855_phylink_mac_select_pcs(struct phylink_config *config,
							 phy_interface_t interface)
{
	struct dsa_port *dp = dsa_phylink_to_port(config);
	struct an8855_priv *priv = dp->ds->priv;

	switch (interface) {
	case PHY_INTERFACE_MODE_SGMII:
	case PHY_INTERFACE_MODE_2500BASEX:
		return &priv->pcs;
	default:
		return NULL;
	}
}

static void an8855_phylink_mac_config(struct phylink_config *config,
				      unsigned int mode,
				      const struct phylink_link_state *state)
{
	struct dsa_port *dp = dsa_phylink_to_port(config);
	struct dsa_switch *ds = dp->ds;
	struct an8855_priv *priv;
	int port = dp->index;

	priv = ds->priv;

	/* Nothing to configure for internal ports */
	if (port != 5)
		return;

	regmap_update_bits(priv->regmap, AN8855_PMCR_P(port),
			   AN8855_PMCR_IFG_XMIT | AN8855_PMCR_MAC_MODE |
			   AN8855_PMCR_BACKOFF_EN | AN8855_PMCR_BACKPR_EN,
			   FIELD_PREP(AN8855_PMCR_IFG_XMIT, 0x1) |
			   AN8855_PMCR_MAC_MODE | AN8855_PMCR_BACKOFF_EN |
			   AN8855_PMCR_BACKPR_EN);
}

static void an8855_phylink_get_caps(struct dsa_switch *ds, int port,
				    struct phylink_config *config)
{
	switch (port) {
	case 0:
	case 1:
	case 2:
	case 3:
	case 4:
		__set_bit(PHY_INTERFACE_MODE_GMII,
			  config->supported_interfaces);
		__set_bit(PHY_INTERFACE_MODE_INTERNAL,
			  config->supported_interfaces);
		break;
	case 5:
		phy_interface_set_rgmii(config->supported_interfaces);
		__set_bit(PHY_INTERFACE_MODE_SGMII,
			  config->supported_interfaces);
		__set_bit(PHY_INTERFACE_MODE_2500BASEX,
			  config->supported_interfaces);
		break;
	}

	config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE |
				   MAC_10 | MAC_100 | MAC_1000FD | MAC_2500FD;
}

static void an8855_phylink_mac_link_down(struct phylink_config *config,
					 unsigned int mode,
					 phy_interface_t interface)
{
	struct dsa_port *dp = dsa_phylink_to_port(config);
	struct an8855_priv *priv = dp->ds->priv;

	/* With autoneg just disable TX/RX else also force link down */
	if (phylink_autoneg_inband(mode)) {
		regmap_clear_bits(priv->regmap, AN8855_PMCR_P(dp->index),
				  AN8855_PMCR_TX_EN | AN8855_PMCR_RX_EN);
	} else {
		regmap_update_bits(priv->regmap, AN8855_PMCR_P(dp->index),
				   AN8855_PMCR_TX_EN | AN8855_PMCR_RX_EN |
				   AN8855_PMCR_FORCE_MODE | AN8855_PMCR_FORCE_LNK,
				   AN8855_PMCR_FORCE_MODE);
	}
}

static void an8855_phylink_mac_link_up(struct phylink_config *config,
				       struct phy_device *phydev, unsigned int mode,
				       phy_interface_t interface, int speed,
				       int duplex, bool tx_pause, bool rx_pause)
{
	struct dsa_port *dp = dsa_phylink_to_port(config);
	struct an8855_priv *priv = dp->ds->priv;
	int port = dp->index;
	u32 reg;

	reg = regmap_read(priv->regmap, AN8855_PMCR_P(port), &reg);
	if (phylink_autoneg_inband(mode)) {
		reg &= ~AN8855_PMCR_FORCE_MODE;
	} else {
		reg |= AN8855_PMCR_FORCE_MODE | AN8855_PMCR_FORCE_LNK;

		reg &= ~AN8855_PMCR_FORCE_SPEED;
		switch (speed) {
		case SPEED_10:
			reg |= AN8855_PMCR_FORCE_SPEED_10;
			break;
		case SPEED_100:
			reg |= AN8855_PMCR_FORCE_SPEED_100;
			break;
		case SPEED_1000:
			reg |= AN8855_PMCR_FORCE_SPEED_1000;
			break;
		case SPEED_2500:
			reg |= AN8855_PMCR_FORCE_SPEED_2500;
			break;
		case SPEED_5000:
			dev_err(priv->dev, "Missing support for 5G speed. Aborting...\n");
			return;
		}

		reg &= ~AN8855_PMCR_FORCE_FDX;
		if (duplex == DUPLEX_FULL)
			reg |= AN8855_PMCR_FORCE_FDX;

		reg &= ~AN8855_PMCR_RX_FC_EN;
		if (rx_pause || dsa_port_is_cpu(dp))
			reg |= AN8855_PMCR_RX_FC_EN;

		reg &= ~AN8855_PMCR_TX_FC_EN;
		if (rx_pause || dsa_port_is_cpu(dp))
			reg |= AN8855_PMCR_TX_FC_EN;

		/* Disable any EEE options */
		reg &= ~(AN8855_PMCR_FORCE_EEE5G | AN8855_PMCR_FORCE_EEE2P5G |
			 AN8855_PMCR_FORCE_EEE1G | AN8855_PMCR_FORCE_EEE100);
	}

	reg |= AN8855_PMCR_TX_EN | AN8855_PMCR_RX_EN;

	regmap_write(priv->regmap, AN8855_PMCR_P(port), reg);
}

static unsigned int an8855_pcs_inband_caps(struct phylink_pcs *pcs,
					   phy_interface_t interface)
{
	/* SGMII can be configured to use inband with AN result */
	if (interface == PHY_INTERFACE_MODE_SGMII)
		return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;

	/* inband is not supported in 2500-baseX and must be disabled */
	return  LINK_INBAND_DISABLE;
}

static void an8855_pcs_get_state(struct phylink_pcs *pcs,
				 struct phylink_link_state *state)
{
	struct an8855_priv *priv = container_of(pcs, struct an8855_priv, pcs);
	u32 val;
	int ret;

	ret = regmap_read(priv->regmap, AN8855_PMSR_P(AN8855_CPU_PORT), &val);
	if (ret < 0) {
		state->link = false;
		return;
	}

	state->link = !!(val & AN8855_PMSR_LNK);
	state->an_complete = state->link;
	state->duplex = (val & AN8855_PMSR_DPX) ? DUPLEX_FULL :
						  DUPLEX_HALF;

	switch (val & AN8855_PMSR_SPEED) {
	case AN8855_PMSR_SPEED_10:
		state->speed = SPEED_10;
		break;
	case AN8855_PMSR_SPEED_100:
		state->speed = SPEED_100;
		break;
	case AN8855_PMSR_SPEED_1000:
		state->speed = SPEED_1000;
		break;
	case AN8855_PMSR_SPEED_2500:
		state->speed = SPEED_2500;
		break;
	case AN8855_PMSR_SPEED_5000:
		dev_err(priv->dev, "Missing support for 5G speed. Setting Unknown.\n");
		fallthrough;
	default:
		state->speed = SPEED_UNKNOWN;
		break;
	}

	if (val & AN8855_PMSR_RX_FC)
		state->pause |= MLO_PAUSE_RX;
	if (val & AN8855_PMSR_TX_FC)
		state->pause |= MLO_PAUSE_TX;
}

static int an8855_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
			     phy_interface_t interface,
			     const unsigned long *advertising,
			     bool permit_pause_to_mac)
{
	struct an8855_priv *priv = container_of(pcs, struct an8855_priv, pcs);
	u32 val;
	int ret;

	/*                   !!! WELCOME TO HELL !!!                   */

	/* TX FIR - improve TX EYE */
	ret = regmap_update_bits(priv->regmap, AN8855_INTF_CTRL_10,
				 AN8855_RG_DA_QP_TX_FIR_C2_SEL |
				 AN8855_RG_DA_QP_TX_FIR_C2_FORCE |
				 AN8855_RG_DA_QP_TX_FIR_C1_SEL |
				 AN8855_RG_DA_QP_TX_FIR_C1_FORCE,
				 AN8855_RG_DA_QP_TX_FIR_C2_SEL |
				 FIELD_PREP(AN8855_RG_DA_QP_TX_FIR_C2_FORCE, 0x4) |
				 AN8855_RG_DA_QP_TX_FIR_C1_SEL |
				 FIELD_PREP(AN8855_RG_DA_QP_TX_FIR_C1_FORCE, 0x0));
	if (ret)
		return ret;

	if (interface == PHY_INTERFACE_MODE_2500BASEX)
		val = 0x0;
	else
		val = 0xd;
	ret = regmap_update_bits(priv->regmap, AN8855_INTF_CTRL_11,
				 AN8855_RG_DA_QP_TX_FIR_C0B_SEL |
				 AN8855_RG_DA_QP_TX_FIR_C0B_FORCE,
				 AN8855_RG_DA_QP_TX_FIR_C0B_SEL |
				 FIELD_PREP(AN8855_RG_DA_QP_TX_FIR_C0B_FORCE, val));
	if (ret)
		return ret;

	/* RX CDR - improve RX Jitter Tolerance */
	if (interface == PHY_INTERFACE_MODE_2500BASEX)
		val = 0x5;
	else
		val = 0x6;
	ret = regmap_update_bits(priv->regmap, AN8855_RG_QP_CDR_LPF_BOT_LIM,
				 AN8855_RG_QP_CDR_LPF_KP_GAIN |
				 AN8855_RG_QP_CDR_LPF_KI_GAIN,
				 FIELD_PREP(AN8855_RG_QP_CDR_LPF_KP_GAIN, val) |
				 FIELD_PREP(AN8855_RG_QP_CDR_LPF_KI_GAIN, val));
	if (ret)
		return ret;

	/* PLL */
	if (interface == PHY_INTERFACE_MODE_2500BASEX)
		val = 0x1;
	else
		val = 0x0;
	ret = regmap_update_bits(priv->regmap, AN8855_QP_DIG_MODE_CTRL_1,
				 AN8855_RG_TPHY_SPEED,
				 FIELD_PREP(AN8855_RG_TPHY_SPEED, val));
	if (ret)
		return ret;

	/* PLL - LPF */
	ret = regmap_update_bits(priv->regmap, AN8855_PLL_CTRL_2,
				 AN8855_RG_DA_QP_PLL_RICO_SEL_INTF |
				 AN8855_RG_DA_QP_PLL_FBKSEL_INTF |
				 AN8855_RG_DA_QP_PLL_BR_INTF |
				 AN8855_RG_DA_QP_PLL_BPD_INTF |
				 AN8855_RG_DA_QP_PLL_BPA_INTF |
				 AN8855_RG_DA_QP_PLL_BC_INTF,
				 AN8855_RG_DA_QP_PLL_RICO_SEL_INTF |
				 FIELD_PREP(AN8855_RG_DA_QP_PLL_FBKSEL_INTF, 0x0) |
				 FIELD_PREP(AN8855_RG_DA_QP_PLL_BR_INTF, 0x3) |
				 FIELD_PREP(AN8855_RG_DA_QP_PLL_BPD_INTF, 0x0) |
				 FIELD_PREP(AN8855_RG_DA_QP_PLL_BPA_INTF, 0x5) |
				 FIELD_PREP(AN8855_RG_DA_QP_PLL_BC_INTF, 0x1));
	if (ret)
		return ret;

	/* PLL - ICO */
	ret = regmap_set_bits(priv->regmap, AN8855_PLL_CTRL_4,
			      AN8855_RG_DA_QP_PLL_ICOLP_EN_INTF);
	if (ret)
		return ret;
	ret = regmap_clear_bits(priv->regmap, AN8855_PLL_CTRL_2,
				AN8855_RG_DA_QP_PLL_ICOIQ_EN_INTF);
	if (ret)
		return ret;

	/* PLL - CHP */
	if (interface == PHY_INTERFACE_MODE_2500BASEX)
		val = 0x6;
	else
		val = 0x4;
	ret = regmap_update_bits(priv->regmap, AN8855_PLL_CTRL_2,
				 AN8855_RG_DA_QP_PLL_IR_INTF,
				 FIELD_PREP(AN8855_RG_DA_QP_PLL_IR_INTF, val));
	if (ret)
		return ret;

	/* PLL - PFD */
	ret = regmap_update_bits(priv->regmap, AN8855_PLL_CTRL_2,
				 AN8855_RG_DA_QP_PLL_PFD_OFFSET_EN_INTRF |
				 AN8855_RG_DA_QP_PLL_PFD_OFFSET_INTF |
				 AN8855_RG_DA_QP_PLL_KBAND_PREDIV_INTF,
				 FIELD_PREP(AN8855_RG_DA_QP_PLL_PFD_OFFSET_INTF, 0x1) |
				 FIELD_PREP(AN8855_RG_DA_QP_PLL_KBAND_PREDIV_INTF, 0x1));
	if (ret)
		return ret;

	/* PLL - POSTDIV */
	ret = regmap_update_bits(priv->regmap, AN8855_PLL_CTRL_2,
				 AN8855_RG_DA_QP_PLL_POSTDIV_EN_INTF |
				 AN8855_RG_DA_QP_PLL_PHY_CK_EN_INTF |
				 AN8855_RG_DA_QP_PLL_PCK_SEL_INTF,
				 AN8855_RG_DA_QP_PLL_PCK_SEL_INTF);
	if (ret)
		return ret;

	/* PLL - SDM */
	ret = regmap_update_bits(priv->regmap, AN8855_PLL_CTRL_2,
				 AN8855_RG_DA_QP_PLL_SDM_HREN_INTF,
				 FIELD_PREP(AN8855_RG_DA_QP_PLL_SDM_HREN_INTF, 0x0));
	if (ret)
		return ret;
	ret = regmap_clear_bits(priv->regmap, AN8855_PLL_CTRL_2,
				AN8855_RG_DA_QP_PLL_SDM_IFM_INTF);
	if (ret)
		return ret;

	ret = regmap_update_bits(priv->regmap, AN8855_SS_LCPLL_PWCTL_SETTING_2,
				 AN8855_RG_NCPO_ANA_MSB,
				 FIELD_PREP(AN8855_RG_NCPO_ANA_MSB, 0x1));
	if (ret)
		return ret;

	if (interface == PHY_INTERFACE_MODE_2500BASEX)
		val = 0x7a000000;
	else
		val = 0x48000000;
	ret = regmap_write(priv->regmap, AN8855_SS_LCPLL_TDC_FLT_2,
			   FIELD_PREP(AN8855_RG_LCPLL_NCPO_VALUE, val));
	if (ret)
		return ret;
	ret = regmap_write(priv->regmap, AN8855_SS_LCPLL_TDC_PCW_1,
			   FIELD_PREP(AN8855_RG_LCPLL_PON_HRDDS_PCW_NCPO_GPON, val));
	if (ret)
		return ret;

	ret = regmap_clear_bits(priv->regmap, AN8855_SS_LCPLL_TDC_FLT_5,
				AN8855_RG_LCPLL_NCPO_CHG);
	if (ret)
		return ret;
	ret = regmap_clear_bits(priv->regmap, AN8855_PLL_CK_CTRL_0,
				AN8855_RG_DA_QP_PLL_SDM_DI_EN_INTF);
	if (ret)
		return ret;

	/* PLL - SS */
	ret = regmap_update_bits(priv->regmap, AN8855_PLL_CTRL_3,
				 AN8855_RG_DA_QP_PLL_SSC_DELTA_INTF,
				 FIELD_PREP(AN8855_RG_DA_QP_PLL_SSC_DELTA_INTF, 0x0));
	if (ret)
		return ret;
	ret = regmap_update_bits(priv->regmap, AN8855_PLL_CTRL_4,
				 AN8855_RG_DA_QP_PLL_SSC_DIR_DLY_INTF,
				 FIELD_PREP(AN8855_RG_DA_QP_PLL_SSC_DIR_DLY_INTF, 0x0));
	if (ret)
		return ret;
	ret = regmap_update_bits(priv->regmap, AN8855_PLL_CTRL_3,
				 AN8855_RG_DA_QP_PLL_SSC_PERIOD_INTF,
				 FIELD_PREP(AN8855_RG_DA_QP_PLL_SSC_PERIOD_INTF, 0x0));
	if (ret)
		return ret;

	/* PLL - TDC */
	ret = regmap_clear_bits(priv->regmap, AN8855_PLL_CK_CTRL_0,
				AN8855_RG_DA_QP_PLL_TDC_TXCK_SEL_INTF);
	if (ret)
		return ret;

	ret = regmap_set_bits(priv->regmap, AN8855_RG_QP_PLL_SDM_ORD,
			      AN8855_RG_QP_PLL_SSC_TRI_EN);
	if (ret)
		return ret;
	ret = regmap_set_bits(priv->regmap, AN8855_RG_QP_PLL_SDM_ORD,
			      AN8855_RG_QP_PLL_SSC_PHASE_INI);
	if (ret)
		return ret;

	ret = regmap_update_bits(priv->regmap, AN8855_RG_QP_RX_DAC_EN,
				 AN8855_RG_QP_SIGDET_HF,
				 FIELD_PREP(AN8855_RG_QP_SIGDET_HF, 0x2));
	if (ret)
		return ret;

	/* TCL Disable (only for Co-SIM) */
	ret = regmap_clear_bits(priv->regmap, AN8855_PON_RXFEDIG_CTRL_0,
				AN8855_RG_QP_EQ_RX500M_CK_SEL);
	if (ret)
		return ret;

	/* TX Init */
	if (interface == PHY_INTERFACE_MODE_2500BASEX)
		val = 0x4;
	else
		val = 0x0;
	ret = regmap_update_bits(priv->regmap, AN8855_RG_QP_TX_MODE,
				 AN8855_RG_QP_TX_RESERVE |
				 AN8855_RG_QP_TX_MODE_16B_EN,
				 FIELD_PREP(AN8855_RG_QP_TX_RESERVE, val));
	if (ret)
		return ret;

	/* RX Control/Init */
	ret = regmap_set_bits(priv->regmap, AN8855_RG_QP_RXAFE_RESERVE,
			      AN8855_RG_QP_CDR_PD_10B_EN);
	if (ret)
		return ret;

	if (interface == PHY_INTERFACE_MODE_2500BASEX)
		val = 0x1;
	else
		val = 0x2;
	ret = regmap_update_bits(priv->regmap, AN8855_RG_QP_CDR_LPF_MJV_LIM,
				 AN8855_RG_QP_CDR_LPF_RATIO,
				 FIELD_PREP(AN8855_RG_QP_CDR_LPF_RATIO, val));
	if (ret)
		return ret;

	ret = regmap_update_bits(priv->regmap, AN8855_RG_QP_CDR_LPF_SETVALUE,
				 AN8855_RG_QP_CDR_PR_BUF_IN_SR |
				 AN8855_RG_QP_CDR_PR_BETA_SEL,
				 FIELD_PREP(AN8855_RG_QP_CDR_PR_BUF_IN_SR, 0x6) |
				 FIELD_PREP(AN8855_RG_QP_CDR_PR_BETA_SEL, 0x1));
	if (ret)
		return ret;

	if (interface == PHY_INTERFACE_MODE_2500BASEX)
		val = 0xf;
	else
		val = 0xc;
	ret = regmap_update_bits(priv->regmap, AN8855_RG_QP_CDR_PR_CKREF_DIV1,
				 AN8855_RG_QP_CDR_PR_DAC_BAND,
				 FIELD_PREP(AN8855_RG_QP_CDR_PR_DAC_BAND, val));
	if (ret)
		return ret;

	ret = regmap_update_bits(priv->regmap, AN8855_RG_QP_CDR_PR_KBAND_DIV_PCIE,
				 AN8855_RG_QP_CDR_PR_KBAND_PCIE_MODE |
				 AN8855_RG_QP_CDR_PR_KBAND_DIV_PCIE_MASK,
				 FIELD_PREP(AN8855_RG_QP_CDR_PR_KBAND_DIV_PCIE_MASK, 0x19));
	if (ret)
		return ret;

	ret = regmap_update_bits(priv->regmap, AN8855_RG_QP_CDR_FORCE_IBANDLPF_R_OFF,
				 AN8855_RG_QP_CDR_PHYCK_SEL |
				 AN8855_RG_QP_CDR_PHYCK_RSTB |
				 AN8855_RG_QP_CDR_PHYCK_DIV,
				 FIELD_PREP(AN8855_RG_QP_CDR_PHYCK_SEL, 0x2) |
				 FIELD_PREP(AN8855_RG_QP_CDR_PHYCK_DIV, 0x21));
	if (ret)
		return ret;

	ret = regmap_clear_bits(priv->regmap, AN8855_RG_QP_CDR_PR_KBAND_DIV_PCIE,
				AN8855_RG_QP_CDR_PR_XFICK_EN);
	if (ret)
		return ret;

	ret = regmap_update_bits(priv->regmap, AN8855_RG_QP_CDR_PR_CKREF_DIV1,
				 AN8855_RG_QP_CDR_PR_KBAND_DIV,
				 FIELD_PREP(AN8855_RG_QP_CDR_PR_KBAND_DIV, 0x4));
	if (ret)
		return ret;

	ret = regmap_update_bits(priv->regmap, AN8855_RX_CTRL_26,
				 AN8855_RG_QP_EQ_RETRAIN_ONLY_EN |
				 AN8855_RG_LINK_NE_EN |
				 AN8855_RG_LINK_ERRO_EN,
				 AN8855_RG_QP_EQ_RETRAIN_ONLY_EN |
				 AN8855_RG_LINK_ERRO_EN);
	if (ret)
		return ret;

	ret = regmap_update_bits(priv->regmap, AN8855_RX_DLY_0,
				 AN8855_RG_QP_RX_SAOSC_EN_H_DLY |
				 AN8855_RG_QP_RX_PI_CAL_EN_H_DLY,
				 FIELD_PREP(AN8855_RG_QP_RX_SAOSC_EN_H_DLY, 0x3f) |
				 FIELD_PREP(AN8855_RG_QP_RX_PI_CAL_EN_H_DLY, 0x6f));
	if (ret)
		return ret;

	ret = regmap_update_bits(priv->regmap, AN8855_RX_CTRL_42,
				 AN8855_RG_QP_EQ_EN_DLY,
				 FIELD_PREP(AN8855_RG_QP_EQ_EN_DLY, 0x150));
	if (ret)
		return ret;

	ret = regmap_update_bits(priv->regmap, AN8855_RX_CTRL_2,
				 AN8855_RG_QP_RX_EQ_EN_H_DLY,
				 FIELD_PREP(AN8855_RG_QP_RX_EQ_EN_H_DLY, 0x150));
	if (ret)
		return ret;

	ret = regmap_update_bits(priv->regmap, AN8855_PON_RXFEDIG_CTRL_9,
				 AN8855_RG_QP_EQ_LEQOSC_DLYCNT,
				 FIELD_PREP(AN8855_RG_QP_EQ_LEQOSC_DLYCNT, 0x1));
	if (ret)
		return ret;

	ret = regmap_update_bits(priv->regmap, AN8855_RX_CTRL_8,
				 AN8855_RG_DA_QP_SAOSC_DONE_TIME |
				 AN8855_RG_DA_QP_LEQOS_EN_TIME,
				 FIELD_PREP(AN8855_RG_DA_QP_SAOSC_DONE_TIME, 0x200) |
				 FIELD_PREP(AN8855_RG_DA_QP_LEQOS_EN_TIME, 0xfff));
	if (ret)
		return ret;

	/* Frequency meter */
	if (interface == PHY_INTERFACE_MODE_2500BASEX)
		val = 0x10;
	else
		val = 0x28;
	ret = regmap_update_bits(priv->regmap, AN8855_RX_CTRL_5,
				 AN8855_RG_FREDET_CHK_CYCLE,
				 FIELD_PREP(AN8855_RG_FREDET_CHK_CYCLE, val));
	if (ret)
		return ret;

	ret = regmap_update_bits(priv->regmap, AN8855_RX_CTRL_6,
				 AN8855_RG_FREDET_GOLDEN_CYCLE,
				 FIELD_PREP(AN8855_RG_FREDET_GOLDEN_CYCLE, 0x64));
	if (ret)
		return ret;

	ret = regmap_update_bits(priv->regmap, AN8855_RX_CTRL_7,
				 AN8855_RG_FREDET_TOLERATE_CYCLE,
				 FIELD_PREP(AN8855_RG_FREDET_TOLERATE_CYCLE, 0x2710));
	if (ret)
		return ret;

	ret = regmap_set_bits(priv->regmap, AN8855_PLL_CTRL_0,
			      AN8855_RG_PHYA_AUTO_INIT);
	if (ret)
		return ret;

	/* PCS Init */
	if (interface == PHY_INTERFACE_MODE_SGMII &&
	    neg_mode == PHYLINK_PCS_NEG_INBAND_DISABLED) {
		ret = regmap_clear_bits(priv->regmap, AN8855_QP_DIG_MODE_CTRL_0,
					AN8855_RG_SGMII_MODE | AN8855_RG_SGMII_AN_EN);
		if (ret)
			return ret;
	}

	ret = regmap_clear_bits(priv->regmap, AN8855_RG_HSGMII_PCS_CTROL_1,
				AN8855_RG_TBI_10B_MODE);
	if (ret)
		return ret;

	if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) {
		/* Set AN Ability - Interrupt */
		ret = regmap_set_bits(priv->regmap, AN8855_SGMII_REG_AN_FORCE_CL37,
				      AN8855_RG_FORCE_AN_DONE);
		if (ret)
			return ret;

		ret = regmap_update_bits(priv->regmap, AN8855_SGMII_REG_AN_13,
					 AN8855_SGMII_REMOTE_FAULT_DIS |
					 AN8855_SGMII_IF_MODE,
					 AN8855_SGMII_REMOTE_FAULT_DIS |
					 FIELD_PREP(AN8855_SGMII_IF_MODE, 0xb));
		if (ret)
			return ret;
	}

	/* Rate Adaption - GMII path config. */
	if (interface == PHY_INTERFACE_MODE_2500BASEX) {
		ret = regmap_clear_bits(priv->regmap, AN8855_RATE_ADP_P0_CTRL_0,
					AN8855_RG_P0_DIS_MII_MODE);
		if (ret)
			return ret;
	} else {
		if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) {
			ret = regmap_set_bits(priv->regmap, AN8855_MII_RA_AN_ENABLE,
					      AN8855_RG_P0_RA_AN_EN);
			if (ret)
				return ret;
		} else {
			ret = regmap_update_bits(priv->regmap, AN8855_RG_AN_SGMII_MODE_FORCE,
						 AN8855_RG_FORCE_CUR_SGMII_MODE |
						 AN8855_RG_FORCE_CUR_SGMII_SEL,
						 AN8855_RG_FORCE_CUR_SGMII_SEL);
			if (ret)
				return ret;

			ret = regmap_clear_bits(priv->regmap, AN8855_RATE_ADP_P0_CTRL_0,
						AN8855_RG_P0_MII_RA_RX_EN |
						AN8855_RG_P0_MII_RA_TX_EN |
						AN8855_RG_P0_MII_RA_RX_MODE |
						AN8855_RG_P0_MII_RA_TX_MODE);
			if (ret)
				return ret;
		}

		ret = regmap_set_bits(priv->regmap, AN8855_RATE_ADP_P0_CTRL_0,
				      AN8855_RG_P0_MII_MODE);
		if (ret)
			return ret;
	}

	ret = regmap_set_bits(priv->regmap, AN8855_RG_RATE_ADAPT_CTRL_0,
			      AN8855_RG_RATE_ADAPT_RX_BYPASS |
			      AN8855_RG_RATE_ADAPT_TX_BYPASS |
			      AN8855_RG_RATE_ADAPT_RX_EN |
			      AN8855_RG_RATE_ADAPT_TX_EN);
	if (ret)
		return ret;

	/* Disable AN if not in autoneg */
	ret = regmap_update_bits(priv->regmap, AN8855_SGMII_REG_AN0, BMCR_ANENABLE,
				 neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED ? BMCR_ANENABLE :
									      0);
	if (ret)
		return ret;

	if (interface == PHY_INTERFACE_MODE_SGMII) {
		/* Follow SDK init flow with restarting AN after AN enable */
		if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) {
			ret = regmap_set_bits(priv->regmap, AN8855_SGMII_REG_AN0,
					      BMCR_ANRESTART);
			if (ret)
				return ret;
		} else {
			ret = regmap_set_bits(priv->regmap, AN8855_PHY_RX_FORCE_CTRL_0,
					      AN8855_RG_FORCE_TXC_SEL);
			if (ret)
				return ret;
		}
	}

	/* Force Speed with fixed-link or 2500base-x as doesn't support aneg */
	if (interface == PHY_INTERFACE_MODE_2500BASEX ||
	    neg_mode != PHYLINK_PCS_NEG_INBAND_ENABLED) {
		if (interface == PHY_INTERFACE_MODE_2500BASEX)
			val = AN8855_RG_LINK_MODE_P0_SPEED_2500;
		else
			val = AN8855_RG_LINK_MODE_P0_SPEED_1000;
		ret = regmap_update_bits(priv->regmap, AN8855_SGMII_STS_CTRL_0,
					 AN8855_RG_LINK_MODE_P0 |
					 AN8855_RG_FORCE_SPD_MODE_P0,
					 val | AN8855_RG_FORCE_SPD_MODE_P0);
		if (ret)
			return ret;
	}

	/* bypass flow control to MAC */
	ret = regmap_write(priv->regmap, AN8855_MSG_RX_LIK_STS_0,
			   AN8855_RG_DPX_STS_P3 | AN8855_RG_DPX_STS_P2 |
			   AN8855_RG_DPX_STS_P1 | AN8855_RG_TXFC_STS_P0 |
			   AN8855_RG_RXFC_STS_P0 | AN8855_RG_DPX_STS_P0);
	if (ret)
		return ret;
	ret = regmap_write(priv->regmap, AN8855_MSG_RX_LIK_STS_2,
			   AN8855_RG_RXFC_AN_BYPASS_P3 |
			   AN8855_RG_RXFC_AN_BYPASS_P2 |
			   AN8855_RG_RXFC_AN_BYPASS_P1 |
			   AN8855_RG_TXFC_AN_BYPASS_P3 |
			   AN8855_RG_TXFC_AN_BYPASS_P2 |
			   AN8855_RG_TXFC_AN_BYPASS_P1 |
			   AN8855_RG_DPX_AN_BYPASS_P3 |
			   AN8855_RG_DPX_AN_BYPASS_P2 |
			   AN8855_RG_DPX_AN_BYPASS_P1 |
			   AN8855_RG_DPX_AN_BYPASS_P0);
	if (ret)
		return ret;

	return 0;
}

static void an8855_pcs_an_restart(struct phylink_pcs *pcs)
{
	return;
}

static const struct phylink_pcs_ops an8855_pcs_ops = {
	.pcs_inband_caps = an8855_pcs_inband_caps,
	.pcs_get_state = an8855_pcs_get_state,
	.pcs_config = an8855_pcs_config,
	.pcs_an_restart = an8855_pcs_an_restart,
};

static const struct phylink_mac_ops an8855_phylink_mac_ops = {
	.mac_select_pcs	= an8855_phylink_mac_select_pcs,
	.mac_config	= an8855_phylink_mac_config,
	.mac_link_down	= an8855_phylink_mac_link_down,
	.mac_link_up	= an8855_phylink_mac_link_up,
};

static const struct dsa_switch_ops an8855_switch_ops = {
	.get_tag_protocol = an8855_get_tag_protocol,
	.setup = an8855_setup,
	.get_phy_flags = en8855_get_phy_flags,
	.phylink_get_caps = an8855_phylink_get_caps,
	.get_strings = an8855_get_strings,
	.get_ethtool_stats = an8855_get_ethtool_stats,
	.get_sset_count = an8855_get_sset_count,
	.get_eth_mac_stats = an8855_get_eth_mac_stats,
	.get_eth_ctrl_stats = an8855_get_eth_ctrl_stats,
	.get_rmon_stats = an8855_get_rmon_stats,
	.port_enable = an8855_port_enable,
	.port_disable = an8855_port_disable,
	.set_ageing_time = an8855_set_ageing_time,
	.port_bridge_join = an8855_port_bridge_join,
	.port_bridge_leave = an8855_port_bridge_leave,
	.port_fast_age = an8855_port_fast_age,
	.port_stp_state_set = an8855_port_stp_state_set,
	.port_pre_bridge_flags = an8855_port_pre_bridge_flags,
	.port_bridge_flags = an8855_port_bridge_flags,
	.port_vlan_filtering = an8855_port_vlan_filtering,
	.port_vlan_add = an8855_port_vlan_add,
	.port_vlan_del = an8855_port_vlan_del,
	.port_fdb_add = an8855_port_fdb_add,
	.port_fdb_del = an8855_port_fdb_del,
	.port_fdb_dump = an8855_port_fdb_dump,
	.port_mdb_add = an8855_port_mdb_add,
	.port_mdb_del = an8855_port_mdb_del,
	.port_change_mtu = an8855_port_change_mtu,
	.port_max_mtu = an8855_port_max_mtu,
	.port_mirror_add = an8855_port_mirror_add,
	.port_mirror_del = an8855_port_mirror_del,
};

static int an8855_read_switch_id(struct an8855_priv *priv)
{
	u32 id;
	int ret;

	ret = regmap_read(priv->regmap, AN8855_CREV, &id);
	if (ret)
		return ret;

	if (id != AN8855_ID) {
		dev_err(priv->dev,
			"Switch id detected %x but expected %x\n",
			id, AN8855_ID);
		return -ENODEV;
	}

	return 0;
}

static int an8855_switch_probe(struct platform_device *pdev)
{
	struct an8855_priv *priv;
	u32 val;
	int ret;

	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	priv->dev = &pdev->dev;
	priv->phy_require_calib = of_property_read_bool(priv->dev->of_node,
							"airoha,ext-surge");

	priv->reset_gpio = devm_gpiod_get_optional(priv->dev, "reset",
						   GPIOD_OUT_LOW);
	if (IS_ERR(priv->reset_gpio))
		return PTR_ERR(priv->reset_gpio);

	/* Get regmap from MFD */
	priv->regmap = dev_get_regmap(priv->dev->parent, NULL);

	if (priv->reset_gpio) {
		usleep_range(100000, 150000);
		gpiod_set_value_cansleep(priv->reset_gpio, 0);
		usleep_range(100000, 150000);
		gpiod_set_value_cansleep(priv->reset_gpio, 1);

		/* Poll HWTRAP reg to wait for Switch to fully Init */
		ret = regmap_read_poll_timeout(priv->regmap, AN8855_HWTRAP, val,
					       val, 20, 200000);
		if (ret)
			return ret;
	}

	ret = an8855_read_switch_id(priv);
	if (ret)
		return ret;

	priv->ds = devm_kzalloc(priv->dev, sizeof(*priv->ds), GFP_KERNEL);
	if (!priv->ds)
		return -ENOMEM;

	priv->ds->dev = priv->dev;
	priv->ds->num_ports = AN8855_NUM_PORTS;
	priv->ds->priv = priv;
	priv->ds->ops = &an8855_switch_ops;
	devm_mutex_init(priv->dev, &priv->reg_mutex);
	priv->ds->phylink_mac_ops = &an8855_phylink_mac_ops;

	priv->pcs.ops = &an8855_pcs_ops;
	priv->pcs.neg_mode = true;
	priv->pcs.poll = true;

	dev_set_drvdata(priv->dev, priv);

	return dsa_register_switch(priv->ds);
}

static void an8855_switch_remove(struct platform_device *pdev)
{
	struct an8855_priv *priv = dev_get_drvdata(&pdev->dev);

	if (priv)
		dsa_unregister_switch(priv->ds);
}

static const struct of_device_id an8855_switch_of_match[] = {
	{ .compatible = "airoha,an8855-switch" },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, an8855_switch_of_match);

static struct platform_driver an8855_switch_driver = {
	.probe = an8855_switch_probe,
	.remove_new = an8855_switch_remove,
	.driver = {
		.name = "an8855-switch",
		.of_match_table = an8855_switch_of_match,
	},
};
module_platform_driver(an8855_switch_driver);

MODULE_AUTHOR("Min Yao <min.yao@airoha.com>");
MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
MODULE_DESCRIPTION("Driver for Airoha AN8855 Switch");
MODULE_LICENSE("GPL");
