/*
 * Copyright (c) 2012, 2014-2021, The Linux Foundation. All rights reserved.
 *
 * Copyright (c) 2021-2023 Qualcomm Innovation Center, Inc. All rights reserved.
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*qca808x_start*/
#include "sw.h"
#include "ssdk_init.h"
#include "fal_init.h"
#include "fal.h"
#include "hsl.h"
#include "hsl_dev.h"
#include "hsl_phy.h"
/*qca808x_end*/
#include "ssdk_dts.h"
#include "ssdk_interrupt.h"
#include <linux/kconfig.h>
#include <linux/version.h>
#include <linux/kernel.h>
/*qca808x_start*/
#include <linux/module.h>
#include <linux/phy.h>
#include <linux/platform_device.h>
#include <linux/types.h>
/*qca808x_end*/
//#include <asm/mach-types.h>
#include <generated/autoconf.h>
#include <linux/if_arp.h>
#include <linux/inetdevice.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
/*qca808x_start*/
#include <linux/phy.h>
#include <linux/mdio.h>
/*qca808x_end*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/string.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>

#if defined(IN_SWCONFIG)
#if defined(CONFIG_OF) && (LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0))
#include <linux/switch.h>
#else
#include <net/switch.h>
#endif
#endif

#if defined(ISIS) ||defined(ISISC) ||defined(GARUDA)
#include <f1_phy.h>
#endif
#if defined(ATHENA) ||defined(SHIVA) ||defined(HORUS)
#include <f2_phy.h>
#endif
#ifdef IN_MALIBU_PHY
#include <malibu_phy.h>
#endif
#if defined(CONFIG_OF) && (LINUX_VERSION_CODE >= KERNEL_VERSION(4,1,0))
/*qca808x_start*/
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_net.h>
#include <linux/of_address.h>
#include <linux/reset.h>
/*qca808x_end*/
#ifdef BOARD_AR71XX
#ifdef CONFIG_AR8216_PHY
#include "drivers/net/phy/ar8327.h"
#endif
#include "drivers/net/ethernet/atheros/ag71xx/ag71xx.h"
#endif
#elif defined(CONFIG_OF) && (LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0))
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_net.h>
#include <linux/of_address.h>
#include <linux/reset.h>
#else
#include <linux/ar8216_platform.h>
#include <drivers/net/phy/ar8216.h>
#include <drivers/net/ethernet/atheros/ag71xx/ag71xx.h>
#endif
/*qca808x_start*/
#include "ssdk_plat.h"
/*qca808x_end*/
#include "ssdk_clk.h"
#include "ssdk_led.h"
#include "ref_vlan.h"
#include "ref_fdb.h"
#include "ref_mib.h"
#include "ref_port_ctrl.h"
#include "ref_misc.h"
#include "ref_uci.h"
#include "ref_vsi.h"
#include "shell.h"
#ifdef BOARD_AR71XX
#include "ssdk_uci.h"
#endif
/*qca808x_start*/
#if defined(IN_PHY_I2C_MODE)
#include "ssdk_phy_i2c.h"
#endif
/*qca808x_end*/
#ifdef IN_IP
#if defined (CONFIG_NF_FLOW_COOKIE)
#include "fal_flowcookie.h"
#ifdef IN_SFE
#include <shortcut-fe/sfe.h>
#endif
#endif
#endif

#ifdef IN_RFS
#if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE)
#include <linux/if_vlan.h>
#endif
#include <qca-rfs/rfs_dev.h>
#ifdef IN_IP
#include "fal_rfs.h"
#endif
#endif
#include "adpt.h"
#ifdef HPPE
#include "ssdk_hppe.h"
#include "adpt_hppe.h"
#endif
#ifdef APPE
#include "ssdk_appe.h"
#endif
#ifdef SCOMPHY
#include "ssdk_scomphy.h"
#endif
#ifdef IN_NETLINK
#include "ssdk_netlink.h"
#endif

#if defined(MHT)
#include "ssdk_mht.h"
#include "ssdk_mht_clk.h"
#endif

#ifdef IN_RFS
struct rfs_device rfs_dev;
struct notifier_block ssdk_inet_notifier;
ssdk_rfs_intf_t rfs_intf_tbl[SSDK_RFS_INTF_MAX] = {{0}};
#endif

//#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0))
struct notifier_block ssdk_dev_notifier;
//#endif


extern a_uint32_t hsl_dev_wan_port_get(a_uint32_t dev_id);
extern void dess_rgmii_sw_mac_polling_task(struct qca_phy_priv *priv);
extern void qca_ar8327_sw_mac_polling_task(struct qca_phy_priv *priv);
extern void qca_ar8327_sw_mib_task(struct qca_phy_priv *priv);

//#define PSGMII_DEBUG

#define QCA_QM_WORK_DELAY	100
#define QCA_QM_ITEM_NUMBER 41
#define QCA_RGMII_WORK_DELAY	1000
#define QCA_MAC_SW_SYNC_WORK_DELAY	1000
#define QCA_FDB_SW_SYNC_WORK_DELAY	1000
#ifdef DESS
static bool qca_dess_rfs_registered = false;
#endif
/*qca808x_start*/
struct qca_phy_priv **qca_phy_priv_global;

struct qca_phy_priv* ssdk_phy_priv_data_get(a_uint32_t dev_id)
{
	if (dev_id >= SW_MAX_NR_DEV || !qca_phy_priv_global)
		return NULL;

	return qca_phy_priv_global[dev_id];
}
/*qca808x_end*/

a_uint32_t hppe_port_type[6] = {0,0,0,0,0,0}; // this variable should be init by ssdk_init

a_uint32_t
qca_hppe_port_mac_type_get(a_uint32_t dev_id, a_uint32_t port_id)
{
	if ((port_id < 1) || (port_id > 6))
		return 0;
	return hppe_port_type[port_id - 1];
}

a_uint32_t
qca_hppe_port_mac_type_set(a_uint32_t dev_id, a_uint32_t port_id, a_uint32_t port_type)
{
	 if ((port_id < 1) || (port_id > 6))
		 return 0;
	hppe_port_type[port_id - 1] = port_type;

	return 0;
}

a_uint32_t
ssdk_ifname_to_port(a_uint32_t dev_id, const char *ifname)
{
	struct net_device *eth_dev = NULL;
	eth_dev = dev_get_by_name(&init_net, ifname);
	if (!eth_dev || !eth_dev->phydev)
	{
		return 0;
	}
	dev_put(eth_dev);
	return qca_ssdk_phydev_to_port(dev_id, eth_dev->phydev);
}

char *
ssdk_port_to_ifname(a_uint32_t dev_id, a_uint32_t port_id)
{
	struct phy_device *phydev = NULL;

	hsl_port_phydev_get(dev_id, port_id, &phydev);
	if (phydev && phydev->attached_dev)
	{
		return phydev->attached_dev->name;
	}
	else
	{
		return NULL;
	}
}

#ifndef BOARD_AR71XX
#if defined(CONFIG_OF) && (LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0))
static void
ssdk_phy_rgmii_set(struct qca_phy_priv *priv)
{
	struct device_node *np = NULL;
	u32 rgmii_en = 0, tx_delay = 0, rx_delay = 0;

	if (priv->ess_switch_flag == A_TRUE)
		np = priv->of_node;
	else
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,9,0))
		np = priv->phy->mdio.dev.of_node;
#else
		np = priv->phy->dev.of_node;
#endif

	if (!np)
		return;

	if (!of_property_read_u32(np, "phy_rgmii_en", &rgmii_en)) {
		a_uint16_t val = 0;
		/*enable RGMII  mode */
		qca_ar8327_phy_dbg_read(0, AR8327_PORT5_PHY_ADDR,
				AR8327_PHY_REG_MODE_SEL, &val);
		val |= AR8327_PHY_RGMII_MODE;
		qca_ar8327_phy_dbg_write(0, AR8327_PORT5_PHY_ADDR,
				AR8327_PHY_REG_MODE_SEL, val);
		if (!of_property_read_u32(np, "txclk_delay_en", &tx_delay)
				&& tx_delay == 1) {
			qca_ar8327_phy_dbg_read(0, AR8327_PORT5_PHY_ADDR,
					AR8327_PHY_REG_SYS_CTRL, &val);
			val |= AR8327_PHY_RGMII_TX_DELAY;
			qca_ar8327_phy_dbg_write(0, AR8327_PORT5_PHY_ADDR,
					AR8327_PHY_REG_SYS_CTRL, val);
		}
		if (!of_property_read_u32(np, "rxclk_delay_en", &rx_delay)
				&& rx_delay == 1) {
			qca_ar8327_phy_dbg_read(0, AR8327_PORT5_PHY_ADDR,
					AR8327_PHY_REG_TEST_CTRL, &val);
			val |= AR8327_PHY_RGMII_RX_DELAY;
			qca_ar8327_phy_dbg_write(0, AR8327_PORT5_PHY_ADDR,
					AR8327_PHY_REG_TEST_CTRL, val);
		}
	}
}
#else
static void
ssdk_phy_rgmii_set(struct qca_phy_priv *priv)
{
	struct ar8327_platform_data *plat_data;

	plat_data = priv->phy->dev.platform_data;
	if (plat_data == NULL) {
		return;
	}

	if(plat_data->pad5_cfg) {
		if(plat_data->pad5_cfg->mode == AR8327_PAD_PHY_RGMII) {
			a_uint16_t val = 0;
			/*enable RGMII  mode */
			priv->phy_dbg_read(0, AR8327_PORT5_PHY_ADDR,
					AR8327_PHY_REG_MODE_SEL, &val);
			val |= AR8327_PHY_RGMII_MODE;
			priv->phy_dbg_write(0, AR8327_PORT5_PHY_ADDR,
					AR8327_PHY_REG_MODE_SEL, val);
			if(plat_data->pad5_cfg->txclk_delay_en) {
				priv->phy_dbg_read(0, AR8327_PORT5_PHY_ADDR,
						AR8327_PHY_REG_SYS_CTRL, &val);
				val |= AR8327_PHY_RGMII_TX_DELAY;
				priv->phy_dbg_write(0, AR8327_PORT5_PHY_ADDR,
						AR8327_PHY_REG_SYS_CTRL, val);
			}
			if(plat_data->pad5_cfg->rxclk_delay_en) {
				priv->phy_dbg_read(0, AR8327_PORT5_PHY_ADDR,
						AR8327_PHY_REG_TEST_CTRL, &val);
				val |= AR8327_PHY_RGMII_RX_DELAY;
				priv->phy_dbg_write(0, AR8327_PORT5_PHY_ADDR,
						AR8327_PHY_REG_TEST_CTRL, val);
			}
		}
	}
}
#endif
#endif


static void
qca_ar8327_phy_fixup(struct qca_phy_priv *priv, int phy)
{
	switch (priv->revision) {
	case 1:
		/* 100m waveform */
		priv->phy_dbg_write(priv->device_id, phy, 0, 0x02ea);
		/* turn on giga clock */
		priv->phy_dbg_write(priv->device_id, phy, 0x3d, 0x68a0);
		break;

	case 2:
		priv->phy_mmd_write(priv->device_id, phy, 0x7, 0x3c);
		priv->phy_mmd_write(priv->device_id, phy, 0x4007, 0x0);
#if defined(FALLTHROUGH)
		fallthrough;
#else
		/* fall through */
#endif
	case 4:
		if(priv->version == QCA_VER_AR8327)
		{
			priv->phy_mmd_write(priv->device_id, phy, 0x3, 0x800d);
			priv->phy_mmd_write(priv->device_id, phy, 0x4003, 0x803f);

			priv->phy_dbg_write(priv->device_id, phy, 0x3d, 0x6860);
			priv->phy_dbg_write(priv->device_id, phy, 0x5, 0x2c46);
			priv->phy_dbg_write(priv->device_id, phy, 0x3c, 0x6000);
		}
		break;
	}
}

#ifdef IN_PORTVLAN
static void qca_port_isolate(a_uint32_t dev_id)
{
	a_uint32_t port_id, mem_port_id, mem_port_map[AR8327_NUM_PORTS]={0};

	for(port_id = 0; port_id < AR8327_NUM_PORTS; port_id++)
	{
		if(port_id == 6)
			for(mem_port_id = 1; mem_port_id<= 4; mem_port_id++)
				mem_port_map[port_id]  |= (1 << mem_port_id);
		else if (port_id == 0)
			mem_port_map[port_id]  |= (1 << 5);
		else if (port_id >= 1 && port_id <= 4)
			mem_port_map[port_id]  |= (1 << 6);
		else
			mem_port_map[port_id]  |= 1;
	}

	for(port_id = 0; port_id < AR8327_NUM_PORTS; port_id++)

		 fal_portvlan_member_update(dev_id, port_id, mem_port_map[port_id]);

}

void ssdk_portvlan_init(a_uint32_t dev_id)
{
	a_uint32_t port = 0;
	a_uint32_t cpu_bmp, lan_bmp, wan_bmp;

	cpu_bmp = ssdk_cpu_bmp_get(dev_id);
	lan_bmp = ssdk_lan_bmp_get(dev_id);
	wan_bmp = ssdk_wan_bmp_get(dev_id);

	if (!(cpu_bmp | lan_bmp | wan_bmp)) {
		qca_port_isolate(dev_id);
		return;
	}

	for(port = 0; port < SSDK_MAX_PORT_NUM; port++)
	{
		if(cpu_bmp & (1 << port))
		{
			fal_portvlan_member_update(dev_id, port, lan_bmp|wan_bmp);
		}
		if(lan_bmp & (1 << port))
		{
			fal_portvlan_member_update(dev_id, port, (lan_bmp|cpu_bmp)&(~(1<<port)));
		}
		if(wan_bmp & (1 << port))
		{
			fal_portvlan_member_update(dev_id, port, (wan_bmp|cpu_bmp)&(~(1<<port)));
		}
	}
}
#endif

sw_error_t
qca_switch_init(a_uint32_t dev_id)
{
#if (defined(DESS) || defined(ISISC) || defined(ISIS)) && defined(IN_QOS)
	a_uint32_t nr = 0, react_nr = 0;
#endif
#if defined(MHT)
	a_uint32_t port_hol_ctrl[2] = {0}, queue_hol_ctrl[6] = {0};
	a_uint32_t j = 0, cpu_bmp = 0;
#endif
	int i = 0;
	a_uint32_t port_bmp = 0;
	hsl_reg_mode reg_mode = HSL_REG_MDIO;
	a_bool_t flag = A_FALSE;
	fal_port_eee_cfg_t port_eee_cfg = {0};
	ssdk_chip_type chip_type = hsl_get_current_chip_type(dev_id);

	/*fal_reset(dev_id);*/
	/*enable cpu and disable mirror*/
#ifdef IN_MISC
	fal_cpu_port_status_set(dev_id, A_TRUE);
	/* setup MTU */
	fal_frame_max_size_set(dev_id, 9216);
#endif
#ifdef IN_MIB
	/* Enable MIB counters */
	fal_mib_status_set(dev_id, A_TRUE);
	fal_mib_cpukeep_set(dev_id, A_FALSE);
#endif
	/*set mirror analysis port as 0xf in default*/
#ifdef IN_MIRROR
	fal_mirr_analysis_port_set(dev_id, 0xf);
#endif
#ifdef IN_IGMP
	fal_igmp_mld_rp_set(dev_id, 0);
#endif

	/*enable pppoe for dakota to support RSS*/
#ifdef DESS
	if (chip_type == CHIP_DESS) {
#ifdef IN_PPPOE
		fal_pppoe_status_set(dev_id, A_TRUE);
#endif
	}
#endif

	reg_mode = ssdk_switch_reg_access_mode_get(dev_id);
	flag = ssdk_ess_switch_flag_get(dev_id);

	if (reg_mode == HSL_REG_MDIO && flag == A_FALSE) {
		/* For legacy S17C without defining port bmp in dts */
		port_bmp = 0x7f;
	} else {
		port_bmp = qca_ssdk_port_bmp_get(dev_id);
		/* Including CPU port */
		port_bmp |= ssdk_cpu_bmp_get(dev_id);
	}

	i = 0;
	while (port_bmp) {
		if (port_bmp & 1) {
			/* forward multicast and broadcast frames to CPU */
#ifdef IN_MISC
			fal_port_unk_uc_filter_set(dev_id, i, A_FALSE);
			fal_port_unk_mc_filter_set(dev_id, i, A_FALSE);
			fal_port_bc_filter_set(dev_id, i, A_FALSE);
#endif
#ifdef IN_PORTVLAN
			fal_port_default_svid_set(dev_id, i, 0);
			fal_port_default_cvid_set(dev_id, i, 0);
			fal_port_1qmode_set(dev_id, i, FAL_1Q_DISABLE);
			fal_port_egvlanmode_set(dev_id, i, FAL_EG_UNMODIFIED);
#endif

#ifdef IN_FDB
			fal_fdb_port_learn_set(dev_id, i, A_TRUE);
#endif
#ifdef IN_STP
			fal_stp_port_state_set(dev_id, 0, i, FAL_STP_FARWARDING);
#endif
#ifdef IN_PORTVLAN
			fal_port_vlan_propagation_set(dev_id, i, FAL_VLAN_PROPAGATION_REPLACE);
#endif
#ifdef IN_IGMP
			fal_port_igmps_status_set(dev_id, i, A_FALSE);
			fal_port_igmp_mld_join_set(dev_id, i, A_FALSE);
			fal_port_igmp_mld_leave_set(dev_id, i, A_FALSE);
			fal_igmp_mld_entry_creat_set(dev_id, A_FALSE);
			fal_igmp_mld_entry_v3_set(dev_id, A_FALSE);
#endif
#ifdef IN_PORTCONTROL
			fal_port_interface_eee_cfg_get(dev_id, i, &port_eee_cfg);
			port_eee_cfg.enable = A_FALSE;
			port_eee_cfg.lpi_tx_enable = A_FALSE;
			fal_port_interface_eee_cfg_set(dev_id, i, &port_eee_cfg);
#endif

			switch (chip_type) {
				case CHIP_SHIVA:
					return SW_OK;
				case CHIP_DESS:
#ifdef DESS
#ifdef IN_PORTCONTROL
					fal_port_flowctrl_forcemode_set(dev_id, i, A_FALSE);
					fal_port_link_forcemode_set(dev_id, i, A_TRUE);
#endif
#ifdef IN_QOS
					nr = 240; /*30*8*/
					fal_qos_port_tx_buf_nr_set(dev_id, i, &nr);
					fal_qos_port_rx_buf_nr_get(dev_id, i, &nr, &react_nr);
					nr = 48; /*6*8*/
					fal_qos_port_rx_buf_nr_set(dev_id, i, &nr, &react_nr);
					fal_qos_port_red_en_set(dev_id, i, A_TRUE);
					nr = 32;
					fal_qos_queue_tx_buf_nr_set(dev_id, i, 5, &nr);
					fal_qos_queue_tx_buf_nr_set(dev_id, i, 4, &nr);
					fal_qos_queue_tx_buf_nr_set(dev_id, i, 3, &nr);
					fal_qos_queue_tx_buf_nr_set(dev_id, i, 2, &nr);
					fal_qos_queue_tx_buf_nr_set(dev_id, i, 1, &nr);
					fal_qos_queue_tx_buf_nr_set(dev_id, i, 0, &nr);
					if (i != SSDK_PHYSICAL_PORT0)
						fal_qos_port_mode_set(dev_id, i,
								FAL_QOS_DSCP_MODE, A_TRUE);
#endif
#endif
					break;

				case CHIP_ISISC:
				case CHIP_ISIS:
#if defined(ISISC) || defined(ISIS)
#ifdef IN_PORTCONTROL
					fal_port_flowctrl_forcemode_set(dev_id, i, A_TRUE);
					fal_port_flowctrl_set(dev_id, i, A_FALSE);

					if (i != 0 && i != 6) {
						fal_port_flowctrl_forcemode_set(dev_id, i,
								A_FALSE);
					}
#endif
					if (i == 0 || i == 5 || i == 6) {
#ifdef IN_QOS
						nr = 240; /*30*8*/
						fal_qos_port_tx_buf_nr_set(dev_id, i, &nr);
						fal_qos_port_rx_buf_nr_get(dev_id, i, &nr,
							&react_nr);
						nr = 48; /*6*8*/
						fal_qos_port_rx_buf_nr_set(dev_id, i, &nr,
							&react_nr);
						fal_qos_port_red_en_set(dev_id, i, A_TRUE);
						if (chip_type == CHIP_ISISC) {
							nr = 64; /*8*8*/
						} else if (chip_type == CHIP_ISIS) {
							nr = 60;
						}

						fal_qos_queue_tx_buf_nr_set(dev_id, i, 5, &nr);
						nr = 48; /*6*8*/
						fal_qos_queue_tx_buf_nr_set(dev_id, i, 4, &nr);
						nr = 32; /*4*8*/
						fal_qos_queue_tx_buf_nr_set(dev_id, i, 3, &nr);
						nr = 32; /*4*8*/
						fal_qos_queue_tx_buf_nr_set(dev_id, i, 2, &nr);
						nr = 32; /*4*8*/
						fal_qos_queue_tx_buf_nr_set(dev_id, i, 1, &nr);
						nr = 24; /*3*8*/
						fal_qos_queue_tx_buf_nr_set(dev_id, i, 0, &nr);
#endif
					} else {
#ifdef IN_QOS
						nr = 200; /*25*8*/
						fal_qos_port_tx_buf_nr_set(dev_id, i, &nr);
						fal_qos_port_rx_buf_nr_get(dev_id, i, &nr,
							&react_nr);
						nr = 48; /*6*8*/
						fal_qos_port_rx_buf_nr_set(dev_id, i, &nr,
							&react_nr);
						fal_qos_port_red_en_set(dev_id, i, A_TRUE);
						if (chip_type == CHIP_ISISC) {
							nr = 64; /*8*8*/
						} else if (chip_type == CHIP_ISIS) {
							nr = 60;
						}
						fal_qos_queue_tx_buf_nr_set(dev_id, i, 3, &nr);
						nr = 48; /*6*8*/
						fal_qos_queue_tx_buf_nr_set(dev_id, i, 2, &nr);
						nr = 32; /*4*8*/
						fal_qos_queue_tx_buf_nr_set(dev_id, i, 1, &nr);
						nr = 24; /*3*8*/
						fal_qos_queue_tx_buf_nr_set(dev_id, i, 0, &nr);
#endif
					}
#endif
					break;
				case CHIP_MHT:
#if defined(MHT)
					aos_mem_zero(&port_hol_ctrl, sizeof(port_hol_ctrl));
					aos_mem_zero(&queue_hol_ctrl, sizeof(queue_hol_ctrl));
					cpu_bmp = ssdk_cpu_bmp_get(dev_id);
#if defined(IN_PORTCONTROL)
					fal_port_txmac_status_set(dev_id, i, A_FALSE);
					fal_port_rxmac_status_set(dev_id, i, A_FALSE);
					fal_port_flowctrl_set(dev_id, i, A_TRUE);
#endif
					if (cpu_bmp & BIT(i)) {
#if defined(IN_PORTCONTROL)
						fal_port_flowctrl_forcemode_set(dev_id, i, A_TRUE);
						fal_port_flow_ctrl_thres_set(dev_id, i,
								MHT_PORT0_XON_THRES, MHT_PORT0_XOFF_THRES);
						fal_header_type_set(dev_id,
								A_TRUE, MHT_HEADER_TYPE_VAL);
						fal_port_rxhdr_mode_set(dev_id,
								i, FAL_ONLY_MANAGE_FRAME_EN);
						fal_port_txhdr_mode_set(dev_id,
								i, FAL_NO_HEADER_EN);
#endif
						/* port tx buf number */
						port_hol_ctrl[0] = 600;
						/* port rx buf number */
						port_hol_ctrl[1] = 48;
						/* queue0 tx buf number */
						queue_hol_ctrl[0] = 24;
						/* queue1 tx buf number */
						queue_hol_ctrl[1] = 32;
						/* queue2 tx buf number */
						queue_hol_ctrl[2] = 32;
						/* queue3 tx buf number */
						queue_hol_ctrl[3] = 32;
						/* queue4 tx buf number */
						queue_hol_ctrl[4] = 48;
						/* queue5 tx buf number */
						queue_hol_ctrl[5] = 64;
					} else {
#if defined(IN_PORTCONTROL)
						fal_port_flowctrl_forcemode_set(dev_id, i,
								A_FALSE);
#endif
						/* port tx buf number */
						port_hol_ctrl[0] = 500;
						/* port rx buf number */
						port_hol_ctrl[1] = 48;
						/* queue0 tx buf number */
						queue_hol_ctrl[0] = 24;
						/* queue1 tx buf number */
						queue_hol_ctrl[1] = 32;
						/* queue2 tx buf number */
						queue_hol_ctrl[2] = 48;
						/* queue3 tx buf number */
						queue_hol_ctrl[3] = 64;
					}
#if defined(IN_QOS)
					fal_qos_port_red_en_set(dev_id, i, A_TRUE);
					fal_qos_port_tx_buf_nr_set(dev_id, i, &port_hol_ctrl[0]);
					fal_qos_port_rx_buf_nr_get(dev_id, i, &nr, &react_nr);
					nr = port_hol_ctrl[1];
					react_nr = 60;
					fal_qos_port_rx_buf_nr_set(dev_id, i, &nr, &react_nr);

					for (j = 0; j < ARRAY_SIZE(queue_hol_ctrl)
							&& queue_hol_ctrl[j] != 0; j++)
						fal_qos_queue_tx_buf_nr_set(dev_id, i, j,
								&queue_hol_ctrl[j]);
#endif
#endif
					break;
				default:
					break;
			}
		}
		port_bmp >>=1;
		i++;
	}

	return SW_OK;
}

void qca_ar8327_phy_linkdown(a_uint32_t dev_id)
{
	int i;
	a_uint16_t phy_val;

	for (i = 0; i < AR8327_NUM_PHYS; i++) {
		qca_ar8327_phy_write(dev_id, i, 0x0, 0x0800);	// phy powerdown

		qca_ar8327_phy_dbg_read(dev_id, i, 0x3d, &phy_val);
		phy_val &= ~0x0040;
		qca_ar8327_phy_dbg_write(dev_id, i, 0x3d, phy_val);

		/*PHY will stop the tx clock for a while when link is down
			1. en_anychange  debug port 0xb bit13 = 0  //speed up link down tx_clk
			2. sel_rst_80us  debug port 0xb bit10 = 0  //speed up speed mode change to 2'b10 tx_clk
		*/
		qca_ar8327_phy_dbg_read(dev_id, i, 0xb, &phy_val);
		phy_val &= ~0x2400;
		qca_ar8327_phy_dbg_write(dev_id, i, 0xb, phy_val);
	}
}

void
qca_mac_disable(a_uint32_t device_id)
{
	hsl_api_t *p_api;

	p_api = hsl_api_ptr_get (device_id);
	if(p_api
		&& p_api->interface_mac_pad_set
		&& p_api->interface_mac_sgmii_set)
	{
		p_api->interface_mac_pad_set(device_id,0,0);
		p_api->interface_mac_pad_set(device_id,5,0);
		p_api->interface_mac_pad_set(device_id,6,0);
		p_api->interface_mac_sgmii_set(device_id,AR8327_REG_PAD_SGMII_CTRL_HW_INIT);
	}
	else
	{
		SSDK_ERROR("API not support \n");
	}
}

static void qca_switch_set_mac_force(struct qca_phy_priv *priv)
{
	a_uint32_t value, value0, i;
	if (priv == NULL || (priv->mii_read == NULL) || (priv->mii_write == NULL)) {
		SSDK_ERROR("In qca_switch_set_mac_force, private data is NULL!\r\n");
		return;
	}

	for (i=0; i < AR8327_NUM_PORTS; ++i) {
		/* b3:2=0,Tx/Rx Mac disable,
		 b9=0,LINK_EN disable */
		value0 = priv->mii_read(priv->device_id, AR8327_REG_PORT_STATUS(i));
		value = value0 & ~(AR8327_PORT_STATUS_LINK_AUTO |
						AR8327_PORT_STATUS_TXMAC |
						AR8327_PORT_STATUS_RXMAC);
		priv->mii_write(priv->device_id, AR8327_REG_PORT_STATUS(i), value);

		/* Force speed to 1000M Full */
		value = priv->mii_read(priv->device_id, AR8327_REG_PORT_STATUS(i));
		value &= ~(AR8327_PORT_STATUS_DUPLEX | AR8327_PORT_STATUS_SPEED);
		value |= AR8327_PORT_SPEED_1000M | AR8327_PORT_STATUS_DUPLEX;
		priv->mii_write(priv->device_id, AR8327_REG_PORT_STATUS(i), value);
	}
	return;
}

void
qca_ar8327_phy_enable(struct qca_phy_priv *priv)
{
	int i = 0;
#ifndef BOARD_AR71XX
        ssdk_phy_rgmii_set(priv);
#endif
	for (i = 0; i < AR8327_NUM_PHYS; i++) {
		a_uint16_t value = 0;

		if (priv->version == QCA_VER_AR8327 || priv->version == QCA_VER_AR8337)
			qca_ar8327_phy_fixup(priv, i);

		/* start autoneg*/
		priv->phy_write(priv->device_id, i, MII_ADVERTISE, ADVERTISE_ALL |
						     ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM);
		//phy reg 0x9, b10,1 = Prefer multi-port device (master)
		priv->phy_write(priv->device_id, i, MII_CTRL1000, (0x0400|ADVERTISE_1000FULL));

		priv->phy_write(priv->device_id, i, MII_BMCR, BMCR_RESET | BMCR_ANENABLE);

		priv->phy_dbg_read(priv->device_id, i, 0, &value);
		value &= (~(1<<12));
		priv->phy_dbg_write(priv->device_id, i, 0, value);

		msleep(100);
	}
}
void qca_ar8327_sw_soft_reset(struct qca_phy_priv *priv)
{
	a_uint32_t value = 0;

	value = priv->mii_read(priv->device_id, AR8327_REG_CTRL);
	value |= 0x80000000;
	priv->mii_write(priv->device_id, AR8327_REG_CTRL, value);
	/*Need wait reset done*/
	do {
		udelay(10);
		value = priv->mii_read(priv->device_id, AR8327_REG_CTRL);
	} while(value & AR8327_CTRL_RESET);
	do {
		udelay(10);
		value = priv->mii_read(priv->device_id, 0x20);
	} while ((value & SSDK_GLOBAL_INITIALIZED_STATUS) !=
			SSDK_GLOBAL_INITIALIZED_STATUS);

	return;
}

#if defined(CONFIG_OF) && (LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0))
int qca_ar8327_hw_init(struct qca_phy_priv *priv)
{
	struct device_node *np = NULL;
	const __be32 *paddr;
	a_uint32_t reg, value, i;
	a_int32_t len;

	if (priv->ess_switch_flag == A_TRUE)
		np = priv->of_node;
	else
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,9,0))
		np = priv->phy->mdio.dev.of_node;
#else
		np = priv->phy->dev.of_node;
#endif

	if(!np)
		return -EINVAL;

	/*Before switch software reset, disable PHY and clear  MAC PAD*/
	qca_ar8327_phy_linkdown(priv->device_id);
	qca_mac_disable(priv->device_id);
	msleep(1000);

	/*First software reset S17 chip*/
	qca_ar8327_sw_soft_reset(priv);

	/*After switch software reset, need disable all ports' MAC with 1000M FULL*/
	qca_switch_set_mac_force(priv);

	/* Configure switch register from DT information */
	paddr = of_get_property(np, "qca,ar8327-initvals", &len);
	if (paddr) {
		if (len < (2 * sizeof(*paddr))) {
			SSDK_ERROR("len:%d < 2 * sizeof(*paddr):%zu\n", len, 2 * sizeof(*paddr));
			return -EINVAL;
		}

		len /= sizeof(*paddr);

		for (i = 0; i < len - 1; i += 2) {
			reg = be32_to_cpup(paddr + i);
			value = be32_to_cpup(paddr + i + 1);
			priv->mii_write(priv->device_id, reg, value);
		}
	}

	value = priv->mii_read(priv->device_id, AR8327_REG_MODULE_EN);
	value &= ~AR8327_REG_MODULE_EN_QM_ERR;
	value &= ~AR8327_REG_MODULE_EN_LOOKUP_ERR;
	priv->mii_write(priv->device_id, AR8327_REG_MODULE_EN, value);

	qca_switch_init(priv->device_id);
#ifdef IN_PORTVLAN
	ssdk_portvlan_init(priv->device_id);
#endif
	qca_switch_enable_intr(priv, FAL_SWITCH_INTR_LINK_STATUS);
	qca_ar8327_phy_enable(priv);

	return 0;
}
#else
static a_uint32_t
qca_ar8327_get_pad_cfg(struct ar8327_pad_cfg *pad_cfg)
{
	a_uint32_t value = 0;

	if (pad_cfg == 0) {
		return 0;
    }

    if(pad_cfg->mode == AR8327_PAD_MAC2MAC_MII) {
		value = AR8327_PAD_CTRL_MAC_MII_EN;
		if (pad_cfg->rxclk_sel)
			value |= AR8327_PAD_CTRL_MAC_MII_RXCLK_SEL;
		if (pad_cfg->txclk_sel)
			value |= AR8327_PAD_CTRL_MAC_MII_TXCLK_SEL;

    } else if (pad_cfg->mode == AR8327_PAD_MAC2MAC_GMII) {
		value = AR8327_PAD_CTRL_MAC_GMII_EN;
		if (pad_cfg->rxclk_sel)
			value |= AR8327_PAD_CTRL_MAC_GMII_RXCLK_SEL;
		if (pad_cfg->txclk_sel)
			value |= AR8327_PAD_CTRL_MAC_GMII_TXCLK_SEL;

    } else if (pad_cfg->mode == AR8327_PAD_MAC_SGMII) {
		value = AR8327_PAD_CTRL_SGMII_EN;

		/* WAR for AP136 board. */
		value |= pad_cfg->txclk_delay_sel <<
		        AR8327_PAD_CTRL_RGMII_TXCLK_DELAY_SEL_S;
		value |= pad_cfg->rxclk_delay_sel <<
                AR8327_PAD_CTRL_RGMII_RXCLK_DELAY_SEL_S;
		if (pad_cfg->rxclk_delay_en)
			value |= AR8327_PAD_CTRL_RGMII_RXCLK_DELAY_EN;
		if (pad_cfg->txclk_delay_en)
			value |= AR8327_PAD_CTRL_RGMII_TXCLK_DELAY_EN;

    } else if (pad_cfg->mode == AR8327_PAD_MAC2PHY_MII) {
		value = AR8327_PAD_CTRL_PHY_MII_EN;
		if (pad_cfg->rxclk_sel)
			value |= AR8327_PAD_CTRL_PHY_MII_RXCLK_SEL;
		if (pad_cfg->txclk_sel)
			value |= AR8327_PAD_CTRL_PHY_MII_TXCLK_SEL;

    } else if (pad_cfg->mode == AR8327_PAD_MAC2PHY_GMII) {
		value = AR8327_PAD_CTRL_PHY_GMII_EN;
		if (pad_cfg->pipe_rxclk_sel)
			value |= AR8327_PAD_CTRL_PHY_GMII_PIPE_RXCLK_SEL;
		if (pad_cfg->rxclk_sel)
			value |= AR8327_PAD_CTRL_PHY_GMII_RXCLK_SEL;
		if (pad_cfg->txclk_sel)
			value |= AR8327_PAD_CTRL_PHY_GMII_TXCLK_SEL;

    } else if (pad_cfg->mode == AR8327_PAD_MAC_RGMII) {
		value = AR8327_PAD_CTRL_RGMII_EN;
		value |= pad_cfg->txclk_delay_sel <<
                 AR8327_PAD_CTRL_RGMII_TXCLK_DELAY_SEL_S;
		value |= pad_cfg->rxclk_delay_sel <<
                 AR8327_PAD_CTRL_RGMII_RXCLK_DELAY_SEL_S;
		if (pad_cfg->rxclk_delay_en)
			value |= AR8327_PAD_CTRL_RGMII_RXCLK_DELAY_EN;
		if (pad_cfg->txclk_delay_en)
			value |= AR8327_PAD_CTRL_RGMII_TXCLK_DELAY_EN;

    } else if (pad_cfg->mode == AR8327_PAD_PHY_GMII) {
		value = AR8327_PAD_CTRL_PHYX_GMII_EN;

    } else if (pad_cfg->mode == AR8327_PAD_PHY_RGMII) {
		value = AR8327_PAD_CTRL_PHYX_RGMII_EN;

    } else if (pad_cfg->mode == AR8327_PAD_PHY_MII) {
		value = AR8327_PAD_CTRL_PHYX_MII_EN;

	} else {
        value = 0;
    }

	return value;
}

#ifndef BOARD_AR71XX
static a_uint32_t
qca_ar8327_get_pwr_sel(struct qca_phy_priv *priv,
                                struct ar8327_platform_data *plat_data)
{
	struct ar8327_pad_cfg *cfg = NULL;
	a_uint32_t value;

	if (!plat_data) {
		return 0;
	}

	value = priv->mii_read(priv->device_id, AR8327_REG_PAD_MAC_PWR_SEL);

	cfg = plat_data->pad0_cfg;

	if (cfg && (cfg->mode == AR8327_PAD_MAC_RGMII) &&
                cfg->rgmii_1_8v) {
		value |= AR8327_PAD_MAC_PWR_RGMII0_1_8V;
	}

	cfg = plat_data->pad5_cfg;
	if (cfg && (cfg->mode == AR8327_PAD_MAC_RGMII) &&
                cfg->rgmii_1_8v) {
		value |= AR8327_PAD_MAC_PWR_RGMII1_1_8V;
	}

	cfg = plat_data->pad6_cfg;
	if (cfg && (cfg->mode == AR8327_PAD_MAC_RGMII) &&
               cfg->rgmii_1_8v) {
		value |= AR8327_PAD_MAC_PWR_RGMII1_1_8V;
	}

	return value;
}
#endif

static a_uint32_t
qca_ar8327_set_led_cfg(struct qca_phy_priv *priv,
                              struct ar8327_platform_data *plat_data,
                              a_uint32_t pos)
{
	struct ar8327_led_cfg *led_cfg;
	a_uint32_t new_pos = pos;

	led_cfg = plat_data->led_cfg;
	if (led_cfg) {
		if (led_cfg->open_drain)
			new_pos |= AR8327_POS_LED_OPEN_EN;
		else
			new_pos &= ~AR8327_POS_LED_OPEN_EN;

		priv->mii_write(priv->device_id, AR8327_REG_LED_CTRL_0, led_cfg->led_ctrl0);
		priv->mii_write(priv->device_id, AR8327_REG_LED_CTRL_1, led_cfg->led_ctrl1);
		priv->mii_write(priv->device_id, AR8327_REG_LED_CTRL_2, led_cfg->led_ctrl2);
		priv->mii_write(priv->device_id, AR8327_REG_LED_CTRL_3, led_cfg->led_ctrl3);

		if (new_pos != pos) {
			new_pos |= AR8327_POS_POWER_ON_SEL;
		}
	}
	return new_pos;
}
#ifndef BOARD_AR71XX
static int
qca_ar8327_set_sgmii_cfg(struct qca_phy_priv *priv,
                              struct ar8327_platform_data *plat_data,
                              a_uint32_t* new_pos)
{
	a_uint32_t value = 0;

	/*configure the SGMII*/
	value = priv->mii_read(priv->device_id, AR8327_REG_PAD_SGMII_CTRL);
	value &= ~(AR8327_PAD_SGMII_CTRL_MODE_CTRL);
	value |= ((plat_data->sgmii_cfg->sgmii_mode) <<
          AR8327_PAD_SGMII_CTRL_MODE_CTRL_S);

	if (priv->version == QCA_VER_AR8337) {
		value |= (AR8327_PAD_SGMII_CTRL_EN_PLL |
		     AR8327_PAD_SGMII_CTRL_EN_RX |
		     AR8327_PAD_SGMII_CTRL_EN_TX);
	} else {
		value &= ~(AR8327_PAD_SGMII_CTRL_EN_PLL |
		       AR8327_PAD_SGMII_CTRL_EN_RX |
		       AR8327_PAD_SGMII_CTRL_EN_TX);
	}
	value |= AR8327_PAD_SGMII_CTRL_EN_SD;

	priv->mii_write(priv->device_id, AR8327_REG_PAD_SGMII_CTRL, value);

	if (plat_data->sgmii_cfg->serdes_aen) {
		*new_pos &= ~AR8327_POS_SERDES_AEN;
	} else {
		*new_pos |= AR8327_POS_SERDES_AEN;
	}
	return 0;
}
#endif

static int
qca_ar8327_set_plat_data_cfg(struct qca_phy_priv *priv,
                              struct ar8327_platform_data *plat_data)
{
	a_uint32_t pos, new_pos;

	pos = priv->mii_read(priv->device_id, AR8327_REG_POS);

	new_pos = qca_ar8327_set_led_cfg(priv, plat_data, pos);

#ifndef BOARD_AR71XX
	/*configure the SGMII*/
	if (plat_data->sgmii_cfg) {
		qca_ar8327_set_sgmii_cfg(priv, plat_data, &new_pos);
	}
#endif

	priv->mii_write(priv->device_id, AR8327_REG_POS, new_pos);

	return 0;
}

static int
qca_ar8327_set_pad_cfg(struct qca_phy_priv *priv,
                              struct ar8327_platform_data *plat_data)
{
	a_uint32_t pad0 = 0, pad5 = 0, pad6 = 0;

	pad0 = qca_ar8327_get_pad_cfg(plat_data->pad0_cfg);
	priv->mii_write(priv->device_id, AR8327_REG_PAD0_CTRL, pad0);

	pad5 = qca_ar8327_get_pad_cfg(plat_data->pad5_cfg);
	if(priv->version == QCA_VER_AR8337) {
	        pad5 |= AR8327_PAD_CTRL_RGMII_RXCLK_DELAY_EN;
	}
	priv->mii_write(priv->device_id, AR8327_REG_PAD5_CTRL, pad5);

	pad6 = qca_ar8327_get_pad_cfg(plat_data->pad6_cfg);
	if(plat_data->pad5_cfg &&
		(plat_data->pad5_cfg->mode == AR8327_PAD_PHY_RGMII))
		pad6 |= AR8327_PAD_CTRL_PHYX_RGMII_EN;
	priv->mii_write(priv->device_id, AR8327_REG_PAD6_CTRL, pad6);

	return 0;
}

void
qca_ar8327_port_init(struct qca_phy_priv *priv, a_uint32_t port)
{
	struct ar8327_platform_data *plat_data;
	struct ar8327_port_cfg *port_cfg;
	a_uint32_t value;

	plat_data = priv->phy->dev.platform_data;
	if (plat_data == NULL) {
		return;
	}

	if (((port == 0) && plat_data->pad0_cfg) ||
	    ((port == 5) && plat_data->pad5_cfg) ||
	    ((port == 6) && plat_data->pad6_cfg)) {
	        switch (port) {
		        case 0:
		            port_cfg = &plat_data->cpuport_cfg;
		            break;
		        case 5:
		            port_cfg = &plat_data->port5_cfg;
		            break;
		        case 6:
		            port_cfg = &plat_data->port6_cfg;
		            break;
	        }
	} else {
	        return;
	}

	/*disable mac at first*/
	fal_port_rxmac_status_set(priv->device_id, port, A_FALSE);
	fal_port_txmac_status_set(priv->device_id, port, A_FALSE);
	value = port_cfg->duplex ? FAL_FULL_DUPLEX : FAL_HALF_DUPLEX;
	fal_port_duplex_set(priv->device_id, port, value);
	value = port_cfg->txpause ? A_TRUE : A_FALSE;
	fal_port_txfc_status_set(priv->device_id, port, value);
	value = port_cfg->rxpause ? A_TRUE : A_FALSE;
	fal_port_rxfc_status_set(priv->device_id, port, value);
	if(port_cfg->speed == AR8327_PORT_SPEED_10) {
		value = FAL_SPEED_10;
	} else if(port_cfg->speed == AR8327_PORT_SPEED_100) {
		value = FAL_SPEED_100;
	} else if(port_cfg->speed == AR8327_PORT_SPEED_1000) {
		value = FAL_SPEED_1000;
	} else {
		value = FAL_SPEED_1000;
	}
	fal_port_speed_set(priv->device_id, port, value);
	/*enable mac at last*/
	udelay(800);
	fal_port_rxmac_status_set(priv->device_id, port, A_TRUE);
	fal_port_txmac_status_set(priv->device_id, port, A_TRUE);
}

int
qca_ar8327_hw_init(struct qca_phy_priv *priv)
{
	struct ar8327_platform_data *plat_data;
	a_uint32_t i = 0;
	a_uint32_t value = 0;

	plat_data = priv->phy->dev.platform_data;
	if (plat_data == NULL) {
		return -EINVAL;
	}

	/*Before switch software reset, disable PHY and clear MAC PAD*/
	qca_ar8327_phy_linkdown(priv->device_id);
	qca_mac_disable(priv->device_id);
	udelay(10);

	qca_ar8327_set_plat_data_cfg(priv, plat_data);

	/*mac reset*/
	priv->mii_write(priv->device_id, AR8327_REG_MAC_SFT_RST, 0x3fff);

	msleep(100);

	/*First software reset S17 chip*/
	qca_ar8327_sw_soft_reset(priv);
	udelay(1000);

	/*After switch software reset, need disable all ports' MAC with 1000M FULL*/
	qca_switch_set_mac_force(priv);

	qca_ar8327_set_pad_cfg(priv, plat_data);

	value = priv->mii_read(priv->device_id, AR8327_REG_MODULE_EN);
	value &= ~AR8327_REG_MODULE_EN_QM_ERR;
	value &= ~AR8327_REG_MODULE_EN_LOOKUP_ERR;
	priv->mii_write(priv->device_id, AR8327_REG_MODULE_EN, value);

	qca_switch_init(priv->device_id);

#ifndef BOARD_AR71XX
	value = qca_ar8327_get_pwr_sel(priv, plat_data);
	priv->mii_write(priv->device_id, AR8327_REG_PAD_MAC_PWR_SEL, value);
#endif

	msleep(1000);

	for (i = 0; i < AR8327_NUM_PORTS; i++) {
		qca_ar8327_port_init(priv, i);
	}

	qca_ar8327_phy_enable(priv);

	return 0;
}
#endif

#if defined(IN_SWCONFIG)
#ifndef BOARD_AR71XX
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 0, 0))
static int
qca_ar8327_sw_get_reg_val(struct switch_dev *dev,
                                    int reg, int *val)
{
	return 0;
}

static int
qca_ar8327_sw_set_reg_val(struct switch_dev *dev,
                                    int reg, int val)
{
	return 0;
}
#endif
#endif
static struct switch_attr qca_ar8327_globals[] = {
#if defined(IN_VLAN)
	{
		.name = "enable_vlan",
		.description = "Enable 8021q VLAN",
		.type = SWITCH_TYPE_INT,
		.set = qca_ar8327_sw_set_vlan,
		.get = qca_ar8327_sw_get_vlan,
		.max = 1
	},
#endif
#if defined(IN_MISC)
	{
		.name = "max_frame_size",
		.description = "Set Max frame Size Of Mac",
		.type = SWITCH_TYPE_INT,
		.set = qca_ar8327_sw_set_max_frame_size,
		.get = qca_ar8327_sw_get_max_frame_size,
		.max = 9018
	},
#endif
#if defined(IN_MIB)
	{
		.name = "reset_mibs",
		.description = "Reset All MIB Counters",
		.type = SWITCH_TYPE_NOVAL,
		.set = qca_ar8327_sw_set_reset_mibs,
	},
#endif
#ifdef IN_FDB
	{
		.name = "flush_arl",
		.description = "Flush All ARL table",
		.type = SWITCH_TYPE_NOVAL,
		.set = qca_ar8327_sw_atu_flush,
	},
	{
		.name = "dump_arl",
		.description = "Dump All ARL table",
		.type = SWITCH_TYPE_STRING,
		.get = qca_ar8327_sw_atu_dump,
	},
#endif
	{
		.name = "switch_ext",
		.description = "Switch extended configuration",
		.type = SWITCH_TYPE_EXT,
		.set = qca_ar8327_sw_switch_ext,
	},
};

static struct switch_attr qca_ar8327_port[] = {
#if defined(IN_MIB)
	{
		.name = "reset_mib",
		.description = "Reset Mib Counters",
		.type = SWITCH_TYPE_NOVAL,
		.set = qca_ar8327_sw_set_port_reset_mib,
	},
	{
		.name = "mib",
		.description = "Get Mib Counters",
		.type = SWITCH_TYPE_STRING,
		.set = NULL,
		.get = qca_ar8327_sw_get_port_mib,
	},
#endif
#if defined(IN_PORTCONTROL)
	{
		.type = SWITCH_TYPE_INT,
		.name = "enable_eee",
		.description = "Enable EEE",
		.set = qca_ar8327_sw_set_eee,
		.get = qca_ar8327_sw_get_eee,
		.max = 1,
	},
#endif
};

#if defined(IN_VLAN)
static struct switch_attr qca_ar8327_vlan[] = {
	{
		.name = "vid",
		.description = "Configure Vlan Id",
		.type = SWITCH_TYPE_INT,
		.set = qca_ar8327_sw_set_vid,
		.get = qca_ar8327_sw_get_vid,
		.max = 4094,
	},
};
#endif

const struct switch_dev_ops qca_ar8327_sw_ops = {
	.attr_global = {
		.attr = qca_ar8327_globals,
		.n_attr = ARRAY_SIZE(qca_ar8327_globals),
	},
	.attr_port = {
		.attr = qca_ar8327_port,
		.n_attr = ARRAY_SIZE(qca_ar8327_port),
	},
#if defined(IN_VLAN)
	.attr_vlan = {
		.attr = qca_ar8327_vlan,
		.n_attr = ARRAY_SIZE(qca_ar8327_vlan),
	},
	.get_port_pvid = qca_ar8327_sw_get_pvid,
	.set_port_pvid = qca_ar8327_sw_set_pvid,
	.get_vlan_ports = qca_ar8327_sw_get_ports,
	.set_vlan_ports = qca_ar8327_sw_set_ports,
	.apply_config = qca_ar8327_sw_hw_apply,
#endif
#if defined(IN_MISC)
	.reset_switch = qca_ar8327_sw_reset_switch,
#endif
#if defined(IN_PORTCONTROL)
	.get_port_link = qca_ar8327_sw_get_port_link,
#endif
#ifndef BOARD_AR71XX
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 0, 0))
	.get_reg_val = qca_ar8327_sw_get_reg_val,
	.set_reg_val = qca_ar8327_sw_set_reg_val,
#endif
#endif
};
#endif

#define SSDK_MIB_CHANGE_WQ

static int
qca_phy_mib_task(struct qca_phy_priv *priv)
{
	qca_ar8327_sw_mib_task(priv);
	return 0;
}

static void
qca_phy_mib_work_task(struct work_struct *work)
{
	struct qca_phy_priv *priv = container_of(work, struct qca_phy_priv,
                                            mib_dwork.work);

	mutex_lock(&priv->mib_lock);

    qca_phy_mib_task(priv);

	mutex_unlock(&priv->mib_lock);
#ifndef SSDK_MIB_CHANGE_WQ
	schedule_delayed_work(&priv->mib_dwork,
			      msecs_to_jiffies(QCA_PHY_MIB_WORK_DELAY));
#else
	queue_delayed_work_on(0, system_long_wq, &priv->mib_dwork,
					msecs_to_jiffies(QCA_PHY_MIB_WORK_DELAY));
#endif
}

int
qca_phy_mib_work_start(struct qca_phy_priv *priv)
{
	mutex_init(&priv->mib_lock);
	if(SW_OK != fal_mib_counter_alloc(priv->device_id, &priv->mib_counters)){
		SSDK_ERROR("Memory allocation fail\n");
		return -ENOMEM;
	}

	INIT_DELAYED_WORK(&priv->mib_dwork, qca_phy_mib_work_task);
#ifndef SSDK_MIB_CHANGE_WQ
	schedule_delayed_work(&priv->mib_dwork,
			               msecs_to_jiffies(QCA_PHY_MIB_WORK_DELAY));
#else
	queue_delayed_work_on(0, system_long_wq, &priv->mib_dwork,
					msecs_to_jiffies(QCA_PHY_MIB_WORK_DELAY));
#endif

	return 0;
}

void
qca_phy_mib_work_stop(struct qca_phy_priv *priv)
{
	if(!priv)
		return;
	if(priv->mib_counters)
		kfree(priv->mib_counters);
	cancel_delayed_work_sync(&priv->mib_dwork);
}

void
qca_phy_mib_work_pause(struct qca_phy_priv *priv)
{
	if(!priv)
		return;
	cancel_delayed_work_sync(&priv->mib_dwork);
}

int
qca_phy_mib_work_resume(struct qca_phy_priv *priv)
{
#ifndef SSDK_MIB_CHANGE_WQ
	schedule_delayed_work(&priv->mib_dwork,
			               msecs_to_jiffies(QCA_PHY_MIB_WORK_DELAY));
#else
	queue_delayed_work_on(0, system_long_wq, &priv->mib_dwork,
					msecs_to_jiffies(QCA_PHY_MIB_WORK_DELAY));
#endif
	return 0;
}

#define SSDK_QM_CHANGE_WQ

static void
qm_err_check_work_task_polling(struct work_struct *work)
{
	struct qca_phy_priv *priv = container_of(work, struct qca_phy_priv,
                                            qm_dwork_polling.work);

	mutex_lock(&priv->qm_lock);

	if(priv->version == QCA_VER_MHT) {
#if defined(MHT)
		qca_mht_sw_mac_polling_task(priv);
#endif
	} else {
		qca_ar8327_sw_mac_polling_task(priv);
	}

	mutex_unlock(&priv->qm_lock);

#ifndef SSDK_QM_CHANGE_WQ
	schedule_delayed_work(&priv->qm_dwork,
							msecs_to_jiffies(QCA_QM_WORK_DELAY));
#else
	queue_delayed_work_on(0, system_long_wq, &priv->qm_dwork_polling,
							msecs_to_jiffies(QCA_QM_WORK_DELAY));
#endif
}

int
qm_err_check_work_start(struct qca_phy_priv *priv)
{
	/*Only valid for S17c chip*/
	if (priv->version != QCA_VER_AR8337 &&
		priv->version != QCA_VER_AR8327 &&
		priv->version != QCA_VER_DESS &&
		priv->version != QCA_VER_MHT)
	{
		return 0;
	}

	mutex_init(&priv->qm_lock);
	INIT_DELAYED_WORK(&priv->qm_dwork_polling, qm_err_check_work_task_polling);
#ifndef SSDK_MIB_CHANGE_WQ
	schedule_delayed_work(&priv->qm_dwork_polling,
							msecs_to_jiffies(QCA_QM_WORK_DELAY));
#else
	queue_delayed_work_on(0, system_long_wq, &priv->qm_dwork_polling,
							msecs_to_jiffies(QCA_QM_WORK_DELAY));
#endif

	return 0;
}

void
qm_err_check_work_stop(struct qca_phy_priv *priv)
{
	/*Only valid for S17c chip*/
	if (priv->version != QCA_VER_AR8337 &&
		priv->version != QCA_VER_AR8327 &&
		priv->version != QCA_VER_DESS) return;

		cancel_delayed_work_sync(&priv->qm_dwork_polling);

}
#ifdef DESS
static void
dess_rgmii_mac_work_task(struct work_struct *work)
{
	struct qca_phy_priv *priv = container_of(work, struct qca_phy_priv,
                                            rgmii_dwork.work);

	mutex_lock(&priv->rgmii_lock);

	dess_rgmii_sw_mac_polling_task(priv);

	mutex_unlock(&priv->rgmii_lock);

	schedule_delayed_work(&priv->rgmii_dwork, msecs_to_jiffies(QCA_RGMII_WORK_DELAY));
}

int
dess_rgmii_mac_work_start(struct qca_phy_priv *priv)
{
	mutex_init(&priv->rgmii_lock);

	INIT_DELAYED_WORK(&priv->rgmii_dwork, dess_rgmii_mac_work_task);

	schedule_delayed_work(&priv->rgmii_dwork, msecs_to_jiffies(QCA_RGMII_WORK_DELAY));

	return 0;
}

void
dess_rgmii_mac_work_stop(struct qca_phy_priv *priv)
{
	cancel_delayed_work_sync(&priv->rgmii_dwork);
}
#endif

void
qca_mac_port_status_init(a_uint32_t dev_id, a_uint32_t port_id)
{
	if(port_id < SSDK_PHYSICAL_PORT1 || port_id >= SW_MAX_NR_PORT)
	{
		SSDK_ERROR("port %d does not support status init\n", port_id);
		return;
	}
	qca_phy_priv_global[dev_id]->port_old_link[port_id - 1] = 0;
	qca_phy_priv_global[dev_id]->port_old_speed[port_id - 1] = FAL_SPEED_BUTT;
	qca_phy_priv_global[dev_id]->port_old_duplex[port_id - 1] = FAL_DUPLEX_BUTT;
	if(hsl_port_feature_get(dev_id, port_id, PHY_F_FORCE) || hsl_port_feature_get
		(dev_id, port_id, PHY_F_SFP))
	{
		qca_phy_priv_global[dev_id]->port_tx_flowctrl_forcemode[port_id - 1] = A_TRUE;
		qca_phy_priv_global[dev_id]->port_rx_flowctrl_forcemode[port_id - 1] = A_TRUE;
	}

	return;
}

void
qca_mac_sw_sync_port_status_init(a_uint32_t dev_id)
{
	a_uint32_t port_id;

	for (port_id = SSDK_PHYSICAL_PORT1; port_id < SW_MAX_NR_PORT; port_id ++) {
		qca_mac_port_status_init(dev_id, port_id);
	}
}
void
qca_mac_sw_sync_work_task(struct work_struct *work)
{
	adpt_api_t *p_adpt_api;

	struct qca_phy_priv *priv = container_of(work, struct qca_phy_priv,
					mac_sw_sync_dwork.work);

	mutex_lock(&priv->mac_sw_sync_lock);

	 if((p_adpt_api = adpt_api_ptr_get(priv->device_id)) != NULL) {
		if (NULL == p_adpt_api->adpt_port_polling_sw_sync_set)
			return;
		p_adpt_api->adpt_port_polling_sw_sync_set(priv);
	}

	mutex_unlock(&priv->mac_sw_sync_lock);

	schedule_delayed_work(&priv->mac_sw_sync_dwork,
					msecs_to_jiffies(QCA_MAC_SW_SYNC_WORK_DELAY));
}

int
qca_mac_sw_sync_work_init(struct qca_phy_priv *priv)
{
	if ((priv->version == QCA_VER_HPPE) || (priv->version == QCA_VER_APPE)) {
		qca_mac_sw_sync_port_status_init(priv->device_id);
	}

	mutex_init(&priv->mac_sw_sync_lock);

	INIT_DELAYED_WORK(&priv->mac_sw_sync_dwork,
			qca_mac_sw_sync_work_task);

	return 0;
}

static sw_error_t
_ssdk_mac_sw_sync_chip_check(struct qca_phy_priv *priv)
{
	sw_error_t rv = SW_OK;

	switch (priv->version) {
		case QCA_VER_HPPE:
		case QCA_VER_SCOMPHY:
		case QCA_VER_APPE:
			break;
		default:
			SSDK_DEBUG("Unsupported chip version %d\n", priv->version);
			rv = SW_NOT_SUPPORTED;
	}

	return rv;
}

sw_error_t
ssdk_mac_sw_sync_work_stop(a_uint32_t dev_id)
{
	sw_error_t rv = SW_OK;
	struct qca_phy_priv *priv = ssdk_phy_priv_data_get(dev_id);
	SW_RTN_ON_NULL(priv);

	if (ssdk_is_emulation(priv->device_id))
	{
		return SW_NOT_SUPPORTED;
	}
	rv = _ssdk_mac_sw_sync_chip_check(priv);
	SW_RTN_ON_ERROR(rv);

	cancel_delayed_work_sync(&priv->mac_sw_sync_dwork);

	return rv;
}
EXPORT_SYMBOL(ssdk_mac_sw_sync_work_stop);

sw_error_t
ssdk_mac_sw_sync_work_start(a_uint32_t dev_id)
{
	sw_error_t rv = SW_OK;
	a_uint32_t port_id = 0;
	struct qca_phy_priv *priv = ssdk_phy_priv_data_get(dev_id);
	SW_RTN_ON_NULL(priv);

	if (ssdk_is_emulation(priv->device_id))
	{
		for (port_id = SSDK_PHYSICAL_PORT1; port_id < SSDK_PHYSICAL_PORT7; port_id++)
		{ /* enable mac for rumi ports */
			if (SW_IS_PBMP_MEMBER(qca_ssdk_port_bmp_get(dev_id), port_id))
			{
				fal_port_txmac_status_set(dev_id, port_id, A_TRUE);
				fal_port_rxmac_status_set(dev_id, port_id, A_TRUE);
			}
		}
		return SW_NOT_SUPPORTED;
	}

	rv = _ssdk_mac_sw_sync_chip_check(priv);
	SW_RTN_ON_ERROR(rv);

	schedule_delayed_work(&priv->mac_sw_sync_dwork,
			msecs_to_jiffies(QCA_MAC_SW_SYNC_WORK_DELAY));
	return rv;
}
EXPORT_SYMBOL(ssdk_mac_sw_sync_work_start);

void
qca_fdb_sw_sync_work_task(struct work_struct *work)
{
	struct qca_phy_priv *priv = container_of(work, struct qca_phy_priv,
		fdb_sw_sync_dwork.work);

#ifdef IN_FDB
	ref_fdb_sw_sync_task(priv);
#endif
	schedule_delayed_work(&priv->fdb_sw_sync_dwork,
		msecs_to_jiffies(QCA_FDB_SW_SYNC_WORK_DELAY));
}

int
qca_fdb_sw_sync_work_init(struct qca_phy_priv *priv)
{
	if ((priv->version != QCA_VER_HPPE) && (priv->version != QCA_VER_APPE) &&
		(priv->version != QCA_VER_MHT))
	{
		return 0;
	}

	aos_lock_init(&priv->fdb_sw_sync_lock);

	return 0;
}

/**
 * add fdb polling on port_map
 */
int
qca_fdb_sw_sync_work_start(struct qca_phy_priv *priv, fal_pbmp_t port_map)
{
	if ((priv->version != QCA_VER_HPPE) && (priv->version != QCA_VER_APPE) &&
		(priv->version != QCA_VER_MHT))
	{
		return 0;
	}

	aos_lock_bh(&priv->fdb_sw_sync_lock);
	SSDK_DEBUG("fdb_sw_sync_port_map 0x%x\n", priv->fdb_sw_sync_port_map);
	if (priv->fdb_sw_sync_port_map == 0 && priv->fdb_polling_started == A_FALSE)
	{
		INIT_DELAYED_WORK(&priv->fdb_sw_sync_dwork,
						qca_fdb_sw_sync_work_task);
		schedule_delayed_work(&priv->fdb_sw_sync_dwork,
						msecs_to_jiffies(QCA_FDB_SW_SYNC_WORK_DELAY));
		priv->fdb_polling_started = A_TRUE;
	}
	SW_PBMP_OR(priv->fdb_sw_sync_port_map, port_map);
	SSDK_DEBUG("fdb_sw_sync_port_map 0x%x\n", priv->fdb_sw_sync_port_map);
	aos_unlock_bh(&priv->fdb_sw_sync_lock);

	return 0;
}

/**
 * stop fdb polling on port_map
 */
void
qca_fdb_sw_sync_work_stop(struct qca_phy_priv *priv, fal_pbmp_t port_map)
{
	if ((priv->version != QCA_VER_HPPE) && (priv->version != QCA_VER_APPE))
	{
		return;
	}

	aos_lock_bh(&priv->fdb_sw_sync_lock);
	SSDK_DEBUG("fdb_sw_sync_port_map 0x%x\n", priv->fdb_sw_sync_port_map);
	SW_PBMP_AND(priv->fdb_sw_sync_port_map, ~port_map);
	if (priv->fdb_sw_sync_port_map == 0 && priv->fdb_polling_started == A_TRUE)
	{
		aos_unlock_bh(&priv->fdb_sw_sync_lock);
		cancel_delayed_work_sync(&priv->fdb_sw_sync_dwork);
		priv->fdb_polling_started = A_FALSE;
	}
	else
		aos_unlock_bh(&priv->fdb_sw_sync_lock);
#ifdef IN_FDB
	ref_fdb_sw_sync_reset(priv, port_map);
#endif
	SSDK_DEBUG("fdb_sw_sync_port_map 0x%x\n", priv->fdb_sw_sync_port_map);
}

int
qca_phy_id_chip(struct qca_phy_priv *priv)
{
	a_uint32_t value, version;

	value = qca_mii_read(priv->device_id, AR8327_REG_CTRL);
	version = value & (AR8327_CTRL_REVISION |
                AR8327_CTRL_VERSION);
	priv->version = (version & AR8327_CTRL_VERSION) >>
                           AR8327_CTRL_VERSION_S;
	priv->revision = (version & AR8327_CTRL_REVISION);

    if((priv->version == QCA_VER_AR8327) ||
       (priv->version == QCA_VER_AR8337) ||
       (priv->version == QCA_VER_AR8227)) {
		return 0;

    } else {
		SSDK_ERROR("unsupported QCA device\n");
		return -ENODEV;
	}
}

#if defined(IN_SWCONFIG)
static int qca_switchdev_register(struct qca_phy_priv *priv)
{
	struct switch_dev *sw_dev;
	int ret = SW_OK;
	sw_dev = &priv->sw_dev;

	switch (priv->version) {
		case QCA_VER_AR8227:
			sw_dev->name = "QCA AR8227";
			sw_dev->alias = "QCA AR8227";
			break;
		case QCA_VER_AR8327:
			sw_dev->name = "QCA AR8327";
			sw_dev->alias = "QCA AR8327";
			break;
		case QCA_VER_AR8337:
			sw_dev->name = "QCA AR8337";
			sw_dev->alias = "QCA AR8337";
			break;
		case QCA_VER_DESS:
			sw_dev->name = "QCA DESS";
			sw_dev->alias = "QCA DESS";
			break;
		case QCA_VER_APPE:
			sw_dev->name = "QCA APPE";
			sw_dev->alias = "QCA APPE";
			break;
		case QCA_VER_HPPE:
			sw_dev->name = "QCA HPPE";
			sw_dev->alias = "QCA HPPE";
			break;
		case QCA_VER_MHT:
			sw_dev->name = "QCA MHT";
			sw_dev->alias = "QCA MHT";
			break;
		case QCA_VER_SCOMPHY:
#ifdef MP
			if(adapt_scomphy_revision_get(priv->device_id)
				== MP_GEPHY)
			{
				sw_dev->name = "QCA MP";
				sw_dev->alias = "QCA MP";
			}
#endif
			break;
		default:
			sw_dev->name = "unknown switch";
			sw_dev->alias = "unknown switch";
			break;
	}

	sw_dev->ops = &qca_ar8327_sw_ops;
	sw_dev->vlans = AR8327_MAX_VLANS;
	sw_dev->ports = priv->ports;

	ret = register_switch(sw_dev, NULL);
	if (ret != SW_OK) {
		SSDK_ERROR("register_switch failed for %s\n", sw_dev->name);
		return ret;
	}

	return ret;
}
#endif

#if defined(DESS) || defined(HPPE) || defined (ISISC) || defined (ISIS) || defined(MP) || defined(MHT)
static int ssdk_switch_register(a_uint32_t dev_id, ssdk_chip_type  chip_type)
{
	struct qca_phy_priv *priv;
	int ret = 0;
	a_uint32_t chip_id = 0;
	priv = qca_phy_priv_global[dev_id];

	priv->mii_read = qca_mii_read;
	priv->mii_write = qca_mii_write;
	priv->phy_write = qca_ar8327_phy_write;
	priv->phy_read = qca_ar8327_phy_read;
	priv->phy_dbg_write = qca_ar8327_phy_dbg_write;
	priv->phy_dbg_read = qca_ar8327_phy_dbg_read;
	priv->phy_mmd_write = qca_ar8327_mmd_write;

	if (chip_type == CHIP_DESS || chip_type == CHIP_MHT) {
		priv->ports = 6;
	} else if ((chip_type == CHIP_ISIS) || (chip_type == CHIP_ISISC)) {
		priv->ports = 7;
	} else if (chip_type == CHIP_SCOMPHY) {
#ifdef MP
		if(adapt_scomphy_revision_get(priv->device_id) == MP_GEPHY) {
			/*for ipq50xx, port id is 1 and 2, port 0 is not available*/
			priv->ports = 3;
		}
#endif
	} else {
#ifdef MPPE
		if (chip_type == CHIP_APPE &&
			adpt_chip_revision_get(priv->device_id) == MPPE_REVISION) {
			priv->ports = 3;
		} else
#endif
			priv->ports = SSDK_MAX_PORT_NUM;
	}

#ifdef MP
	if(chip_type == CHIP_SCOMPHY)
	{
		priv->version = QCA_VER_SCOMPHY;
	}
	else
#endif
	{
		if (fal_reg_get(dev_id, 0, (a_uint8_t *)&chip_id, 4) == SW_OK) {
			priv->version = ((chip_id >> 8) & 0xff);
			priv->revision = (chip_id & 0xff);
		}
	}

	mutex_init(&priv->reg_mutex);

#if defined(IN_SWCONFIG)
	ret = qca_switchdev_register(priv);
	if (ret != SW_OK) {
		return ret;
	}
#endif
	priv->qca_ssdk_sw_dev_registered = A_TRUE;
	ret = qca_phy_mib_work_start(qca_phy_priv_global[dev_id]);
	if (ret != 0) {
			SSDK_ERROR("qca_phy_mib_work_start failed for chip 0x%02x%02x\n", priv->version, priv->revision);
			return ret;
	}

	if(priv->link_polling_required)
	{
		ret = qm_err_check_work_start(priv);
		if (ret != 0)
		{
			SSDK_ERROR("qm_err_check_work_start failed for chip 0x%02x%02x\n", priv->version, priv->revision);
			return ret;
		}
#ifdef HPPE
		if (_ssdk_mac_sw_sync_chip_check(priv) != SW_OK) {
			SSDK_INFO("mac_sw_sync is not enabled on chip 0x%02x%02x\n",
				priv->version, priv->revision);
			return 0;
		}

		if (!ssdk_is_emulation(dev_id)) {
			ret = qca_mac_sw_sync_work_init(priv);
			if (ret != 0) {
				SSDK_ERROR("qca_mac_sw_sync_work_init failed on chip 0x%02x%02x\n",
						priv->version, priv->revision);
				return ret;
			}
		}
#endif
	}
#if defined(HPPE) || defined(MHT)
	ret = qca_fdb_sw_sync_work_init(priv);
	if (ret != 0) {
		SSDK_ERROR("qca_fdb_sw_sync_work_init failed on chip 0x%02x%02x\n",
				priv->version, priv->revision);
		return ret;
	}
#endif
	if(priv->interrupt_no > 0)
	{
		snprintf(priv->intr_name, IFNAMSIZ, "switch%d", dev_id);
		switch(priv->version)
		{
#ifdef MHT
			case QCA_VER_MHT:
				priv->interrupt_flag = IRQF_TRIGGER_HIGH;
				break;
#endif
#ifdef ISISC
			case QCA_VER_AR8337:
				priv->interrupt_flag = IRQF_TRIGGER_LOW;
				break;
#endif
			default:
				priv->interrupt_flag = IRQF_TRIGGER_NONE;
				break;
		}
		SSDK_INFO("intr_number:%d, intr_name:%s, intr_flag:%d\n",
			priv->interrupt_no, priv->intr_name, priv->interrupt_flag);
		ret = qca_intr_init(priv);
		if(ret)
		{
			SSDK_ERROR("the interrupt init faild !\n");
			return ret;
		}
	}

#if 0
#ifdef DESS
	if ((ssdk_dt_global.mac_mode == PORT_WRAPPER_SGMII0_RGMII5)
		||(ssdk_dt_global.mac_mode == PORT_WRAPPER_SGMII1_RGMII5)
		||(ssdk_dt_global.mac_mode == PORT_WRAPPER_SGMII0_RGMII4)
		||(ssdk_dt_global.mac_mode == PORT_WRAPPER_SGMII1_RGMII4)
		||(ssdk_dt_global.mac_mode == PORT_WRAPPER_SGMII4_RGMII4)) {
		ret = dess_rgmii_mac_work_start(priv);
		if (ret != 0) {
			SSDK_ERROR("dess_rgmii_mac_work_start failed for chip 0x%02x%02x\n", priv->version, priv->revision);
			return ret;
		}
	}
#endif
#endif
	return 0;
}

static int ssdk_switch_unregister(a_uint32_t dev_id)
{
	qca_phy_mib_work_stop(qca_phy_priv_global[dev_id]);
	qm_err_check_work_stop(qca_phy_priv_global[dev_id]);
#ifdef HPPE
	if(!ssdk_is_emulation(dev_id)) {
		ssdk_mac_sw_sync_work_stop(dev_id);
	}
#endif
#if defined(IN_SWCONFIG)
	unregister_switch(&qca_phy_priv_global[dev_id]->sw_dev);
#endif
	return 0;
}
#endif

#ifndef BOARD_AR71XX
#if defined(CONFIG_OF) && (LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0))
#ifdef DESS
struct reset_control *ess_rst = NULL;
struct reset_control *ess_mac_clock_disable[5] = {NULL,NULL,NULL,NULL,NULL};

void ssdk_ess_reset(void)
{
	if (!ess_rst)
		return;
	reset_control_assert(ess_rst);
	mdelay(10);
	reset_control_deassert(ess_rst);
	mdelay(100);
}
#endif

char ssdk_driver_name[] = "ess_ssdk";

static int ssdk_probe(struct platform_device *pdev)
{
	struct device_node *np;

	np = of_node_get(pdev->dev.of_node);
	if (of_device_is_compatible(np, "qcom,ess-instance"))
		return of_platform_populate(np, NULL, NULL, &pdev->dev);

#ifdef DESS
	ess_rst = devm_reset_control_get(&pdev->dev, "ess_rst");
	ess_mac_clock_disable[0] = devm_reset_control_get(&pdev->dev, "ess_mac1_clk_dis");
	ess_mac_clock_disable[1] = devm_reset_control_get(&pdev->dev, "ess_mac2_clk_dis");
	ess_mac_clock_disable[2] = devm_reset_control_get(&pdev->dev, "ess_mac3_clk_dis");
	ess_mac_clock_disable[3] = devm_reset_control_get(&pdev->dev, "ess_mac4_clk_dis");
	ess_mac_clock_disable[4] = devm_reset_control_get(&pdev->dev, "ess_mac5_clk_dis");

	if (IS_ERR(ess_rst)) {
		return 0;
	}
	if (!ess_mac_clock_disable[0]) {
		SSDK_ERROR("ess_mac1_clock_disable fail!\n");
		return -1;
	}
	if (!ess_mac_clock_disable[1]) {
		SSDK_ERROR("ess_mac2_clock_disable fail!\n");
		return -1;
	}
	if (!ess_mac_clock_disable[2]) {
		SSDK_ERROR("ess_mac3_clock_disable fail!\n");
		return -1;
	}
	if (!ess_mac_clock_disable[3]) {
		SSDK_ERROR("ess_mac4_clock_disable fail!\n");
		return -1;
	}
	if (!ess_mac_clock_disable[4]) {
		SSDK_ERROR("ess_mac5_clock_disable fail!\n");
		return -1;
	}
#endif
	return 0;
}

static const struct of_device_id ssdk_of_mtable[] = {
	{.compatible = "qcom,ess-switch" },
	{.compatible = "qcom,ess-switch-ipq60xx" },
	{.compatible = "qcom,ess-switch-ipq807x" },
	{.compatible = "qcom,ess-switch-ipq95xx" },
	{.compatible = "qcom,ess-switch-ipq53xx" },
	{.compatible = "qcom,ess-instance" },
	{}
};

static struct platform_driver ssdk_driver = {
        .driver = {
                .name    = ssdk_driver_name,
                .owner   = THIS_MODULE,
                .of_match_table = ssdk_of_mtable,
        },
        .probe    = ssdk_probe,
};
#endif
#endif
#ifdef DESS
static u32 phy_t_status = 0;
static a_uint16_t modectrl_data = 0;
void ssdk_malibu_psgmii_and_dakota_dess_reset(a_uint32_t dev_id, a_uint32_t first_phy_addr)
{
#ifndef BOARD_AR71XX
#if defined(CONFIG_OF) && (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0))
	int m = 0, n = 0;
	a_uint32_t psgmii_phy_addr;

	psgmii_phy_addr = first_phy_addr + 5;

	/*reset Malibu PSGMII and Dakota ESS start*/
	qca_ar8327_phy_write(dev_id, psgmii_phy_addr, 0x0, 0x005b);/*fix phy psgmii RX 20bit*/
	qca_ar8327_phy_write(dev_id, psgmii_phy_addr, 0x0, 0x001b);/*reset phy psgmii*/
	qca_ar8327_phy_write(dev_id, psgmii_phy_addr, 0x0, 0x005b);/*release reset phy psgmii*/
	/* mdelay(100); this 100ms be replaced with below malibu psgmii calibration process*/
	/*check malibu psgmii calibration done start*/
	n = 0;
	while (n < 100) {
		u16 status;
		status = qca_phy_mmd_read(dev_id, psgmii_phy_addr, 1, 0x28);
		if (status & BIT(0))
			break;
		mdelay(10);
		n++;
	}
#ifdef PSGMII_DEBUG
	if (n >= 100)
		SSDK_INFO("MALIBU PSGMII PLL_VCO_CALIB NOT READY\n");
#endif
	mdelay(50);
	/*check malibu psgmii calibration done end..*/
	qca_ar8327_phy_write(dev_id, psgmii_phy_addr, 0x1a, 0x2230);/*freeze phy psgmii RX CDR*/

	ssdk_ess_reset();
	/*check dakota psgmii calibration done start*/
	m = 0;
	while (m < 100) {
		u32 status = 0;
		qca_psgmii_reg_read(dev_id, 0xa0, (a_uint8_t *)&status, 4);
		if (status & BIT(0))
			break;
		mdelay(10);
		m++;
	}
#ifdef PSGMII_DEBUG
	if (m >= 100)
		SSDK_INFO("DAKOTA PSGMII PLL_VCO_CALIB NOT READY\n");
#endif
	mdelay(50);
	/*check dakota psgmii calibration done end..*/
	qca_ar8327_phy_write(dev_id, psgmii_phy_addr, 0x1a, 0x3230);/*relesae phy psgmii RX CDR*/
	qca_ar8327_phy_write(dev_id, psgmii_phy_addr, 0x0, 0x005f);/*release phy psgmii RX 20bit*/
	mdelay(200);
#endif
#endif
	/*reset Malibu PSGMII and Dakota ESS end*/
	return;
}

static void ssdk_psgmii_phy_testing_printf(a_uint32_t phy, u32 tx_ok, u32 rx_ok,
				u32 tx_counter_error, u32 rx_counter_error)
{
	SSDK_INFO("tx_ok = 0x%x, rx_ok = 0x%x, tx_counter_error = 0x%x, rx_counter_error = 0x%x\n",
			tx_ok, rx_ok, tx_counter_error, rx_counter_error);
	if (tx_ok== 0x3000 && tx_counter_error == 0)
		SSDK_INFO("PHY %d single PSGMII test pass\n", phy);
	else
		SSDK_ERROR("PHY %d single PSGMII test fail\n", phy);
	return;

}
static void ssdk_psgmii_all_phy_testing_printf(a_uint32_t phy, u32 tx_ok, u32 rx_ok,
				u32 tx_counter_error, u32 rx_counter_error)
{
	SSDK_INFO("tx_ok = 0x%x, rx_ok = 0x%x, tx_counter_error = 0x%x, rx_counter_error = 0x%x\n",
			tx_ok, rx_ok, tx_counter_error, rx_counter_error);
	if (tx_ok== 0x3000 && tx_counter_error == 0)
		SSDK_INFO("PHY %d all PSGMII test pass\n", phy);
	else
		SSDK_ERROR("PHY %d all PSGMII test fail\n", phy);
	return;

}
void ssdk_psgmii_single_phy_testing(a_uint32_t dev_id, a_uint32_t phy, a_bool_t enable)
{
	int j = 0;

	u32 tx_counter_ok, tx_counter_error;
	u32 rx_counter_ok, rx_counter_error;
	u32 tx_counter_ok_high16;
	u32 rx_counter_ok_high16;
	u32 tx_ok, rx_ok;
	qca_ar8327_phy_write(dev_id, phy, 0x0, 0x9000);
	qca_ar8327_phy_write(dev_id, phy, 0x0, 0x4140);
	j = 0;
	while (j < 100) {
		u16 status = 0;
		qca_ar8327_phy_read(dev_id, phy, 0x11, &status);
		if (status & (1 << 10))
			break;
		mdelay(10);
		j++;
	}
	/*add a 300ms delay as qm polling task existing*/
	if (enable == A_TRUE)
		mdelay(300);

	/*enable check*/
	qca_phy_mmd_write(dev_id, phy, 7, 0x8029, 0x0000);
	qca_phy_mmd_write(dev_id, phy, 7, 0x8029, 0x0003);

	/*start traffic*/
	qca_phy_mmd_write(dev_id, phy, 7, 0x8020, 0xa000);
	mdelay(200);

	/*check counter*/
	tx_counter_ok = qca_phy_mmd_read(dev_id, phy, 7, 0x802e);
	tx_counter_ok_high16 = qca_phy_mmd_read(dev_id, phy, 7, 0x802d);
	tx_counter_error = qca_phy_mmd_read(dev_id, phy, 7, 0x802f);
	rx_counter_ok = qca_phy_mmd_read(dev_id, phy, 7, 0x802b);
	rx_counter_ok_high16 = qca_phy_mmd_read(dev_id, phy, 7, 0x802a);
	rx_counter_error = qca_phy_mmd_read(dev_id, phy, 7, 0x802c);
	tx_ok = tx_counter_ok + (tx_counter_ok_high16<<16);
	rx_ok = rx_counter_ok + (rx_counter_ok_high16<<16);
	if (tx_ok== 0x3000 && tx_counter_error == 0) {
		/*success*/
		phy_t_status &= (~(1<<phy));
	} else {
		phy_t_status |= (1<<phy);
	}

	if (enable == A_TRUE)
		ssdk_psgmii_phy_testing_printf(phy, tx_ok, rx_ok,
				tx_counter_error, rx_counter_error);

	qca_ar8327_phy_write(dev_id, phy, 0x0, 0x1840);
}

void ssdk_psgmii_all_phy_testing(a_uint32_t dev_id, a_uint32_t first_phy_addr, a_bool_t enable)
{
	int j = 0;
	a_uint32_t phy = 0;
	qca_ar8327_phy_write(dev_id, 0x1f, 0x0, 0x9000);
	qca_ar8327_phy_write(dev_id, 0x1f, 0x0, 0x4140);
	j = 0;
	while (j < 100) {
		for (phy = first_phy_addr; phy < first_phy_addr + 5; phy++) {
			u16 status = 0;
			qca_ar8327_phy_read(dev_id, phy, 0x11, &status);
			if (!(status & (1 << 10)))
				break;
		}

		if (phy >= (first_phy_addr + 5))
			break;
		mdelay(10);
		j++;
	}
	/*add a 300ms delay as qm polling task existing*/
	if (enable == A_TRUE)
		mdelay(300);

	/*enable check*/
	qca_phy_mmd_write(dev_id, 0x1f, 7, 0x8029, 0x0000);
	qca_phy_mmd_write(dev_id, 0x1f, 7, 0x8029, 0x0003);

	/*start traffic*/
	qca_phy_mmd_write(dev_id, 0x1f, 7, 0x8020, 0xa000);
	mdelay(200);
	for (phy = first_phy_addr; phy < first_phy_addr + 5; phy++) {
		u32 tx_counter_ok, tx_counter_error;
		u32 rx_counter_ok, rx_counter_error;
		u32 tx_counter_ok_high16;
		u32 rx_counter_ok_high16;
		u32 tx_ok, rx_ok;
		/*check counter*/
		tx_counter_ok = qca_phy_mmd_read(dev_id, phy, 7, 0x802e);
		tx_counter_ok_high16 = qca_phy_mmd_read(dev_id, phy, 7, 0x802d);
		tx_counter_error = qca_phy_mmd_read(dev_id, phy, 7, 0x802f);
		rx_counter_ok = qca_phy_mmd_read(dev_id, phy, 7, 0x802b);
		rx_counter_ok_high16 = qca_phy_mmd_read(dev_id, phy, 7, 0x802a);
		rx_counter_error = qca_phy_mmd_read(dev_id, phy, 7, 0x802c);
		tx_ok = tx_counter_ok + (tx_counter_ok_high16<<16);
		rx_ok = rx_counter_ok + (rx_counter_ok_high16<<16);
		if (tx_ok== 0x3000 && tx_counter_error == 0) {
			/*success*/
			phy_t_status &= (~(1<<(phy+8)));
		} else {
			phy_t_status |= (1<<(phy+8));
		}

		if (enable == A_TRUE)
			ssdk_psgmii_all_phy_testing_printf(phy, tx_ok,
					rx_ok,
					tx_counter_error, rx_counter_error);
		}
	if (enable == A_TRUE)
		SSDK_INFO("PHY final test result: 0x%x \r\n",phy_t_status);

}

void ssdk_psgmii_get_first_phy_address(a_uint32_t dev_id,
	a_uint32_t *first_phy_addr)
{
	a_uint32_t port_id = 0, phy_addr = 0, phy_cnt = 0;
	a_uint32_t port_bmp[SW_MAX_NR_DEV] = {0};

	port_bmp[dev_id] = qca_ssdk_phy_type_port_bmp_get(dev_id, MALIBU_PHY_CHIP);

	for (port_id = 0; port_id < SW_MAX_NR_PORT; port_id ++)
	{
		if (port_bmp[dev_id] & (0x1 << port_id))
		{
			phy_cnt ++;
			phy_addr = qca_ssdk_port_to_phy_addr(dev_id, port_id);
			if (phy_addr < *first_phy_addr) {
				*first_phy_addr = phy_addr;
			}
		}
	}
	if ((phy_cnt == QCA8072_PHY_NUM) && (*first_phy_addr >= 0x3)) {
		*first_phy_addr = *first_phy_addr - 3;
	}
}

void ssdk_psgmii_self_test(a_uint32_t dev_id, a_bool_t enable, a_uint32_t times,
				a_uint32_t *result)
{
	int i = 0;
	u32 value = 0;
	a_uint32_t first_phy_addr = MAX_PHY_ADDR + 1, phy = 0;

	ssdk_psgmii_get_first_phy_address(dev_id, &first_phy_addr);
	if ((first_phy_addr < 0) || (first_phy_addr > MAX_PHY_ADDR)) {
		return;
	}

	if (enable == A_FALSE) {
		ssdk_malibu_psgmii_and_dakota_dess_reset(dev_id, first_phy_addr);
	}

	qca_ar8327_phy_read(dev_id, first_phy_addr + 4, 0x1f, &modectrl_data);
	qca_ar8327_phy_write(dev_id, first_phy_addr + 4, 0x1f, 0x8500);/*switch to access MII reg for copper*/
	for(phy = first_phy_addr; phy < first_phy_addr + 5; phy++) {
		/*enable phy mdio broadcast write*/
		qca_phy_mmd_write(dev_id, phy, 7, 0x8028, 0x801f);
	}

	/* force no link by power down */
	qca_ar8327_phy_write(dev_id, 0x1f, 0x0, 0x1840);

	/*packet number*/
	qca_phy_mmd_write(dev_id, 0x1f, 7, 0x8021, 0x3000);
	qca_phy_mmd_write(dev_id, 0x1f, 7, 0x8062, 0x05e0);

	/*fix mdi status */
	qca_ar8327_phy_write(dev_id, 0x1f, 0x10, 0x6800);

	for(i = 0; i < times; i++) {
		phy_t_status = 0;

		for(phy = 0; phy < 5; phy++) {
			value = readl(qca_phy_priv_global[dev_id]->hw_addr + 0x66c + phy * 0xc);
			writel((value|(1<<21)), (qca_phy_priv_global[dev_id]->hw_addr + 0x66c + phy * 0xc));
		}

		for (phy = first_phy_addr; phy < first_phy_addr + 5; phy++) {
			ssdk_psgmii_single_phy_testing(dev_id, phy, enable);
		}
		ssdk_psgmii_all_phy_testing(dev_id, first_phy_addr, enable);
		if (enable == A_FALSE) {
			if (phy_t_status) {
				ssdk_malibu_psgmii_and_dakota_dess_reset(dev_id, first_phy_addr);
			}
			else
			{
		                break;
			}
		}
	}

	*result = phy_t_status;
#ifdef PSGMII_DEBUG
	if (i>=100)
		SSDK_ERROR("PSGMII cannot recover\n");
	else
		SSDK_INFO("PSGMII recovered after %d times reset\n",i);
#endif
	/*configuration recover*/
	/*packet number*/
	qca_phy_mmd_write(dev_id, 0x1f, 7, 0x8021, 0x0);
	/*disable check*/
	qca_phy_mmd_write(dev_id, 0x1f, 7, 0x8029, 0x0);
	/*disable traffic*/
	qca_phy_mmd_write(dev_id, 0x1f, 7, 0x8020, 0x0);
}


void clear_self_test_config(a_uint32_t dev_id)
{
	u32 value = 0;
	a_uint32_t first_phy_addr = MAX_PHY_ADDR + 1, phy = 0;

	ssdk_psgmii_get_first_phy_address(dev_id, &first_phy_addr);
	if ((first_phy_addr < 0) || (first_phy_addr > MAX_PHY_ADDR)) {
		return;
	}

	/* disable EEE */
	/* qca_phy_mmd_write(0, 0x1f, 0x7,  0x3c, 0x0); */

	/*disable phy internal loopback*/
	qca_ar8327_phy_write(dev_id, 0x1f, 0x10, 0x6860);
	qca_ar8327_phy_write(dev_id, 0x1f, 0x0, 0x9040);

	for(phy = 0; phy < 5; phy++)
	{
		/*disable mac loop back*/
		value = readl(qca_phy_priv_global[dev_id]->hw_addr+0x66c+phy*0xc);
		writel((value&(~(1<<21))), (qca_phy_priv_global[dev_id]->hw_addr+0x66c+phy*0xc));
	}

	for(phy = first_phy_addr; phy < first_phy_addr + 5; phy++)
	{
		/*diable phy mdio broadcast write*/
		qca_phy_mmd_write(dev_id, phy, 7, 0x8028, 0x001f);

	}

	qca_ar8327_phy_write(dev_id, first_phy_addr + 4, 0x1f, modectrl_data);

	/* clear fdb entry */
	fal_fdb_entry_flush(dev_id,1);
}
#endif
/*qca808x_start*/
sw_error_t
ssdk_init(a_uint32_t dev_id, ssdk_init_cfg * cfg)
{
	sw_error_t rv;

	rv = fal_init(dev_id, cfg);
	if (rv != SW_OK)
		SSDK_ERROR("ssdk fal init failed: %d. \r\n", rv);

/*qca808x_end*/
	if(!ssdk_is_emulation(dev_id))
/*qca808x_start*/
	{
		rv = ssdk_phy_driver_init(dev_id, cfg);
		if (rv != SW_OK)
			SSDK_ERROR("ssdk phy init failed: %d. \r\n", rv);
	}

	return rv;
}

sw_error_t
ssdk_cleanup(a_uint32_t dev_id)
{
	sw_error_t rv;

	rv = fal_cleanup(dev_id);
/*qca808x_end*/
	if(!ssdk_is_emulation(dev_id))
/*qca808x_start*/
	{
		rv = ssdk_phy_driver_cleanup(dev_id);
	}

	return rv;
}
/*qca808x_end*/

sw_error_t
ssdk_hsl_access_mode_set(a_uint32_t dev_id, hsl_access_mode reg_mode)
{
    sw_error_t rv;

    rv = hsl_access_mode_set(dev_id, reg_mode);
    return rv;
}

void switch_cpuport_setup(a_uint32_t dev_id)
{
#ifdef IN_PORTCONTROL
	//According to HW suggestion, enable CPU port flow control for Dakota
	fal_port_flowctrl_forcemode_set(dev_id, 0, A_TRUE);
	fal_port_flowctrl_set(dev_id, 0, A_TRUE);
	fal_port_duplex_set(dev_id, 0, FAL_FULL_DUPLEX);
	fal_port_speed_set(dev_id, 0, FAL_SPEED_1000);
	udelay(10);
	fal_port_txmac_status_set(dev_id, 0, A_TRUE);
	fal_port_rxmac_status_set(dev_id, 0, A_TRUE);
#endif
}
#ifdef IN_AQUANTIA_PHY
#ifdef CONFIG_MDIO
static struct mdio_if_info ssdk_mdio_ctl;
#endif
static struct net_device *ssdk_miireg_netdev = NULL;

static int ssdk_miireg_open(struct net_device *netdev)
{
	return 0;
}
static int ssdk_miireg_close(struct net_device *netdev)
{
	return 0;
}

static int ssdk_miireg_do_ioctl(struct net_device *netdev,
			struct ifreq *ifr, int32_t cmd)
{
	int ret = -EINVAL;
#ifdef CONFIG_MDIO
	struct mii_ioctl_data *mii_data = if_mii(ifr);
	ret = mdio_mii_ioctl(&ssdk_mdio_ctl, mii_data, cmd);
#endif
	return ret;
}

static const struct net_device_ops ssdk_netdev_ops = {
	.ndo_open = &ssdk_miireg_open,
	.ndo_stop = &ssdk_miireg_close,
	.ndo_do_ioctl = &ssdk_miireg_do_ioctl,
};

#ifdef CONFIG_MDIO
static int ssdk_miireg_ioctl_read(struct net_device *netdev, int phy_addr, int mmd, uint16_t addr)
{
	a_uint32_t reg = 0;
	a_uint16_t val = 0;

	if (MDIO_DEVAD_NONE == mmd) {
		qca_ar8327_phy_read(0, phy_addr, addr, &val);
		return (int)val;
	}

	reg = MII_ADDR_C45 | mmd << 16 | addr;
	qca_ar8327_phy_read(0, phy_addr, reg, &val);

	return (int)val;
}

static int ssdk_miireg_ioctl_write(struct net_device *netdev, int phy_addr, int mmd,
				uint16_t addr, uint16_t value)
{
	a_uint32_t reg = 0;

	if (MDIO_DEVAD_NONE == mmd) {
		qca_ar8327_phy_write(0, phy_addr, addr, value);
		return 0;
	}

	reg = MII_ADDR_C45 | mmd << 16 | addr;
	qca_ar8327_phy_write(0, phy_addr, reg, value);

	return 0;
}
#endif

static void ssdk_netdev_setup(struct net_device *dev)
{
	dev->netdev_ops = &ssdk_netdev_ops;
}
static void ssdk_miireg_ioctrl_register(void)
{
	if (ssdk_miireg_netdev)
		return;
#ifdef CONFIG_MDIO
	ssdk_mdio_ctl.mdio_read = ssdk_miireg_ioctl_read;
	ssdk_mdio_ctl.mdio_write = ssdk_miireg_ioctl_write;
	ssdk_mdio_ctl.mode_support = MDIO_SUPPORTS_C45;
#endif

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,18,0))
	ssdk_miireg_netdev = alloc_netdev(100, "miireg", 0, ssdk_netdev_setup);
#else
	ssdk_miireg_netdev = alloc_netdev(100, "miireg", ssdk_netdev_setup);
#endif
	if (ssdk_miireg_netdev)
		register_netdev(ssdk_miireg_netdev);
}

static void ssdk_miireg_ioctrl_unregister(void)
{
	if (ssdk_miireg_netdev) {
		unregister_netdev(ssdk_miireg_netdev);
		kfree(ssdk_miireg_netdev);
		ssdk_miireg_netdev = NULL;
	}
}
#endif
static void ssdk_driver_register(a_uint32_t dev_id)
{
	hsl_reg_mode reg_mode;

	reg_mode = ssdk_switch_reg_access_mode_get(dev_id);
	if(reg_mode == HSL_REG_LOCAL_BUS) {
#ifndef BOARD_AR71XX
#if defined(CONFIG_OF) && (LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0))
		platform_driver_register(&ssdk_driver);
#endif
#endif
	}
}

static void ssdk_driver_unregister(a_uint32_t dev_id)
{
	hsl_reg_mode reg_mode;

	reg_mode= ssdk_switch_reg_access_mode_get(dev_id);
	if (reg_mode == HSL_REG_LOCAL_BUS) {
#ifndef BOARD_AR71XX
#if defined(CONFIG_OF) && (LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0))
		platform_driver_unregister(&ssdk_driver);
#endif
#endif
	}
}
/*qca808x_start*/
static inline a_uint32_t qca_detect_phyid(a_uint32_t dev_id, ssdk_init_cfg* cfg)
{
	a_uint32_t phy_id = 0, port_id = 0;
	a_uint32_t port_bmp = qca_ssdk_port_bmp_get(dev_id);
	while (port_bmp) {
		if (port_bmp & 0x1) {
			phy_id = hsl_phyid_get(dev_id, port_id, cfg);
			if (INVALID_PHY_ID != phy_id)
				break;
		}
		port_bmp >>= 1;
		port_id++;
	}
	return phy_id;
}

static int chip_is_scomphy(a_uint32_t dev_id, ssdk_init_cfg* cfg)
{
	int rv = -ENODEV;
	a_uint32_t phy_id = qca_detect_phyid(dev_id, cfg);

	switch (phy_id) {
		/*qca808x_end*/
		case QCA8030_PHY:
		case QCA8033_PHY:
		case QCA8035_PHY:
		case MP_GEPHY:
			/*qca808x_start*/
		case QCA8081_PHY_V1_1:
			cfg->chip_type = CHIP_SCOMPHY;
			/*MP GEPHY is always the first port*/
			if(cfg->phy_id == 0) {
				cfg->phy_id = phy_id;
			}
			rv = SW_OK;
			break;
		default:
			break;
	}

	return rv;
}

static int chip_ver_get(a_uint32_t dev_id, ssdk_init_cfg* cfg)
{
	int rv = SW_OK;
	a_uint8_t chip_ver = 0;
	a_uint8_t chip_revision = 0;
/*qca808x_end*/
	hsl_reg_mode reg_mode;

	reg_mode= ssdk_switch_reg_access_mode_get(dev_id);
	if(reg_mode == HSL_REG_MDIO) {
		/**
		 * For the Manhattan phy detected, the Manhattan MDIO read function
		 * should be used, which is different from the other chips
		 **/
		a_uint32_t phy_id = qca_detect_phyid(dev_id, cfg);
		a_uint16_t reg_val;
		switch (phy_id) {
			case QCA8084_PHY:
				reg_val = qca_mht_mii_read(dev_id, 0);
				break;
			default:
				reg_val = qca_ar8216_mii_read(dev_id, 0);
				break;
		}
		chip_ver = (reg_val & BITS(8,8)) >> 8;
	} else {
		a_uint32_t reg_val = 0;
		qca_switch_reg_read(dev_id,0,(a_uint8_t *)&reg_val, 4);
		chip_ver = (reg_val&0xff00)>>8;
		chip_revision = reg_val&0xff;
	}
/*qca808x_start*/
	switch (chip_ver) {
		case QCA_VER_AR8227:
			cfg->chip_type = CHIP_SHIVA;
			break;
		case QCA_VER_AR8337:
			cfg->chip_type = CHIP_ISISC;
			break;
		case QCA_VER_AR8327:
			cfg->chip_type = CHIP_ISIS;
			break;
		case QCA_VER_DESS:
			cfg->chip_type = CHIP_DESS;
			break;
		case QCA_VER_HPPE:
			cfg->chip_type = CHIP_HPPE;
			cfg->chip_revision = chip_revision;
			break;
		case QCA_VER_APPE:
			cfg->chip_type = CHIP_APPE;
			cfg->chip_revision = chip_revision;
			break;
		case QCA_VER_MHT:
			cfg->chip_type = CHIP_MHT;
			break;
		default:
			/* try single phy without switch connected */
			rv = chip_is_scomphy(dev_id, cfg);
	}

	return rv;
}
/*qca808x_end*/

#ifdef DESS
static int ssdk_flow_default_act_init(a_uint32_t dev_id)
{
	a_uint32_t vrf_id = 0;
	fal_flow_type_t type = 0;
	for(vrf_id = FAL_MIN_VRF_ID; vrf_id <= FAL_MAX_VRF_ID; vrf_id++)
	{
		for(type = FAL_FLOW_LAN_TO_LAN; type <= FAL_FLOW_WAN_TO_WAN; type++)
		{
#ifdef IN_IP
#ifndef IN_IP_MINI
			fal_default_flow_cmd_set(dev_id, vrf_id, type, FAL_DEFAULT_FLOW_ADMIT_ALL);
#endif
#endif
		}
	}

	return 0;
}
static int ssdk_dess_mac_mode_init(a_uint32_t dev_id, a_uint32_t mac_mode)
{
	a_uint32_t reg_value;
	u8  __iomem      *gcc_addr = NULL;

	switch(mac_mode) {
		case PORT_WRAPPER_PSGMII:
		case PORT_WRAPPER_PSGMII_FIBER:
			reg_value = 0x2200;
			qca_psgmii_reg_write(dev_id, DESS_PSGMII_MODE_CONTROL,
								(a_uint8_t *)&reg_value, 4);
			reg_value = 0x8380;
			qca_psgmii_reg_write(dev_id, DESS_PSGMIIPHY_TX_CONTROL,
								(a_uint8_t *)&reg_value, 4);
			break;
		case PORT_WRAPPER_SGMII0_RGMII5:
		case PORT_WRAPPER_SGMII1_RGMII5:
		case PORT_WRAPPER_SGMII0_RGMII4:
		case PORT_WRAPPER_SGMII1_RGMII4:
		case PORT_WRAPPER_SGMII4_RGMII4:

		/*config sgmii */
			if ((mac_mode == PORT_WRAPPER_SGMII0_RGMII5)
				||(mac_mode == PORT_WRAPPER_SGMII0_RGMII4)) {
				/*PSGMII channnel 0 as SGMII*/
				reg_value = 0x2001;
				fal_psgmii_reg_set(dev_id, 0x1b4,
								(a_uint8_t *)&reg_value, 4);
				udelay(1000);
			}
			if ((mac_mode == PORT_WRAPPER_SGMII1_RGMII5)
				||(mac_mode == PORT_WRAPPER_SGMII1_RGMII4)) {
				/*PSGMII channnel 1 as SGMII*/
				reg_value = 0x2003;
				fal_psgmii_reg_set(dev_id, 0x1b4,
								(a_uint8_t *)&reg_value, 4);
				udelay(1000);
			}
			if ((mac_mode == PORT_WRAPPER_SGMII4_RGMII4)) {
				/*PSGMII channnel 4 as SGMII*/
				reg_value = 0x2005;
				fal_psgmii_reg_set(dev_id, 0x1b4,
								(a_uint8_t *)&reg_value, 4);
				udelay(1000);
			}

			/*clock gen 1*/
			reg_value = 0xea6;
			fal_psgmii_reg_set(dev_id, 0x13c,
							(a_uint8_t *)&reg_value, 4);
			mdelay(10);
			/*softreset psgmii, fixme*/
			gcc_addr = ioremap_nocache(0x1812000, 0x200);
			if (!gcc_addr) {
				SSDK_ERROR("gcc map fail!\n");
				return 0;
			} else {
				SSDK_INFO("gcc map success!\n");
				writel(0x20, gcc_addr+0xc);
				mdelay(10);
				writel(0x0, gcc_addr+0xc);
				mdelay(10);
				iounmap(gcc_addr);
			}
			/*relock pll*/
			reg_value = 0x2803;
			fal_psgmii_reg_set(dev_id, DESS_PSGMII_PLL_VCO_RELATED_CONTROL_1,
							(a_uint8_t *)&reg_value, 4);
			udelay(1000);
			reg_value = 0x4ADA;
			fal_psgmii_reg_set(dev_id, DESS_PSGMII_VCO_CALIBRATION_CONTROL_1,
							(a_uint8_t *)&reg_value, 4);
			udelay(1000);
			reg_value = 0xADA;
			fal_psgmii_reg_set(dev_id, DESS_PSGMII_VCO_CALIBRATION_CONTROL_1,
							(a_uint8_t *)&reg_value, 4);
			udelay(1000);

			/* Reconfig channel 0 as SGMII and re autoneg*/
			if ((mac_mode == PORT_WRAPPER_SGMII0_RGMII5)
				||(mac_mode == PORT_WRAPPER_SGMII0_RGMII4)) {
			/*PSGMII channnel 0 as SGMII*/
			reg_value = 0x2001;
			fal_psgmii_reg_set(dev_id, 0x1b4,
								(a_uint8_t *)&reg_value, 4);
			udelay(1000);
			/* restart channel 0 autoneg*/
			reg_value = 0xc4;
			fal_psgmii_reg_set(dev_id, 0x1c8,
							(a_uint8_t *)&reg_value, 4);
			mdelay(10);
			reg_value = 0x44;
			fal_psgmii_reg_set(dev_id, 0x1c8,
							(a_uint8_t *)&reg_value, 4);
			mdelay(10);
			}
			/* Reconfig channel 1 as SGMII and re autoneg*/
			if ((mac_mode == PORT_WRAPPER_SGMII1_RGMII5)
				||(mac_mode == PORT_WRAPPER_SGMII1_RGMII4)) {

			/*PSGMII channnel 1 as SGMII*/
			reg_value = 0x2003;
			fal_psgmii_reg_set(dev_id, 0x1b4,
							(a_uint8_t *)&reg_value, 4);
			udelay(1000);
			/* restart channel 1 autoneg*/
			reg_value = 0xc4;
			fal_psgmii_reg_set(dev_id, 0x1e0,
							(a_uint8_t *)&reg_value, 4);
			mdelay(10);
			reg_value = 0x44;
			fal_psgmii_reg_set(dev_id, 0x1e0,
							(a_uint8_t *)&reg_value, 4);
			mdelay(10);

			}
			/* Reconfig channel 4 as SGMII and re autoneg*/
			if ((mac_mode == PORT_WRAPPER_SGMII4_RGMII4)) {
			/*PSGMII channnel 4 as SGMII*/
			reg_value = 0x2005;
			fal_psgmii_reg_set(dev_id, 0x1b4,
							(a_uint8_t *)&reg_value, 4);
			udelay(1000);
			/* restart channel 4 autoneg*/
			reg_value = 0xc4;
			fal_psgmii_reg_set(dev_id, 0x228,
							(a_uint8_t *)&reg_value, 4);
			mdelay(10);
			reg_value = 0x44;
			fal_psgmii_reg_set(dev_id, 0x228,
							(a_uint8_t *)&reg_value, 4);
			mdelay(10);
			}

		  	/* config RGMII*/
			reg_value = 0x400;
			fal_reg_set(dev_id, 0x4, (a_uint8_t *)&reg_value, 4);
			/* config mac5 RGMII*/
			if ((mac_mode == PORT_WRAPPER_SGMII0_RGMII5)
				||(mac_mode == PORT_WRAPPER_SGMII1_RGMII5)) {
				qca_ar8327_phy_dbg_write(0, 4, 0x5, 0x2d47);
				qca_ar8327_phy_dbg_write(0, 4, 0xb, 0xbc40);
				qca_ar8327_phy_dbg_write(0, 4, 0x0, 0x82ee);
				reg_value = 0x72;
				qca_switch_reg_write(dev_id, 0x90, (a_uint8_t *)&reg_value, 4);
			}
			/* config mac4 RGMII*/
			if ((mac_mode == PORT_WRAPPER_SGMII0_RGMII4)
				||(mac_mode == PORT_WRAPPER_SGMII1_RGMII4)
				||(mac_mode == PORT_WRAPPER_SGMII4_RGMII4)) {
				qca_ar8327_phy_dbg_write(dev_id, 4, 0x5, 0x2d47);
				qca_ar8327_phy_dbg_write(dev_id, 4, 0xb, 0xbc40);
				qca_ar8327_phy_dbg_write(dev_id, 4, 0x0, 0x82ee);
				reg_value = 0x72;
				qca_switch_reg_write(dev_id, 0x8c, (a_uint8_t *)&reg_value, 4);
			}
			break;
		case PORT_WRAPPER_PSGMII_RMII0_RMII1:
		case PORT_WRAPPER_PSGMII_RMII0:
		case PORT_WRAPPER_PSGMII_RMII1:
			reg_value = 0x2200;
			qca_psgmii_reg_write(dev_id, DESS_PSGMII_MODE_CONTROL,
				(a_uint8_t *)&reg_value, 4);
			reg_value = 0x8380;
			qca_psgmii_reg_write(dev_id, DESS_PSGMIIPHY_TX_CONTROL,
					(a_uint8_t *)&reg_value, 4);
			/*switch RMII clock source to gcc_ess_clk,ESS_RGMII_CTRL:0x0C000004,dakota rmii1/rmii0 master mode*/
			if(mac_mode== PORT_WRAPPER_PSGMII_RMII0_RMII1)
				reg_value = 0x3000000;
			if(mac_mode== PORT_WRAPPER_PSGMII_RMII0)
				reg_value = 0x1000000;
			if(mac_mode== PORT_WRAPPER_PSGMII_RMII1)
				reg_value = 0x2000000;
			qca_switch_reg_write(dev_id, 0x4, (a_uint8_t *)&reg_value, 4);
			/*enable RMII MAC5 100M/full*/
			if(mac_mode == PORT_WRAPPER_PSGMII_RMII0_RMII1 || mac_mode == PORT_WRAPPER_PSGMII_RMII0)
			{
				reg_value = 0x7d;
				qca_switch_reg_write(dev_id, 0x90, (a_uint8_t *)&reg_value, 4);
			}

			/*enable RMII MAC4 100M/full*/
			if(mac_mode == PORT_WRAPPER_PSGMII_RMII0_RMII1 || mac_mode == PORT_WRAPPER_PSGMII_RMII1)
			{
				reg_value = 0x7d;
				qca_switch_reg_write(dev_id, 0x8C, (a_uint8_t *)&reg_value, 4);
			}
			/*set QM CONTROL REGISTER FLOW_DROP_CNT as max*/
			reg_value = 0x7f007f;
			qca_switch_reg_write(dev_id, 0x808, (a_uint8_t *)&reg_value, 4);

			/*relock PSGMII PLL*/
			reg_value = 0x2803;
			fal_psgmii_reg_set(dev_id, DESS_PSGMII_PLL_VCO_RELATED_CONTROL_1,
							(a_uint8_t *)&reg_value, 4);
			udelay(1000);
			reg_value = 0x4ADA;
			fal_psgmii_reg_set(dev_id, DESS_PSGMII_VCO_CALIBRATION_CONTROL_1,
							(a_uint8_t *)&reg_value, 4);
			udelay(1000);
			reg_value = 0xADA;
			fal_psgmii_reg_set(dev_id, DESS_PSGMII_VCO_CALIBRATION_CONTROL_1,
							(a_uint8_t *)&reg_value, 4);
			udelay(1000);
			break;
	}

	return 0;
}

#ifdef IN_TRUNK
#define MULTIPLE_WAN_PORT_CNT 2
#define TRUNK_ID_OF_MULTIPLE_WAN_PORTS 0

static a_bool_t
ssdk_dess_multiple_wan_port_check(a_uint32_t dev_id,
	a_uint32_t wan_bitmap)
{
	a_uint32_t port_id = SSDK_PHYSICAL_PORT0, wan_ports_cnt = 0;

	for(port_id = SSDK_PHYSICAL_PORT0; port_id < SSDK_MAX_PORT_NUM;
		port_id++)
	{
		if(BIT(port_id) & wan_bitmap)
		{
			wan_ports_cnt++;
		}
	}
	if(wan_ports_cnt >= MULTIPLE_WAN_PORT_CNT)
	{
		return A_TRUE;
	}
	else
	{
		return A_FALSE;
	}
}

sw_error_t
ssdk_dess_trunk_init(a_uint32_t dev_id, a_uint32_t wan_bitmap)
{
	sw_error_t rv = SW_OK;

	if(ssdk_dess_multiple_wan_port_check(dev_id, wan_bitmap))
	{
		rv = fal_trunk_group_set(dev_id, TRUNK_ID_OF_MULTIPLE_WAN_PORTS,
			A_TRUE, wan_bitmap);
		SW_RTN_ON_ERROR(rv);
	}

	return rv;
}
#endif

static sw_error_t
qca_dess_hw_init(ssdk_init_cfg *cfg, a_uint32_t dev_id)
{
	a_uint32_t reg_value = 0;
	hsl_api_t *p_api;
	a_uint32_t psgmii_result = 0;
	a_uint32_t mac_mode;

	mac_mode = ssdk_dt_global_get_mac_mode(dev_id, 0);
	/*Do Malibu self test to fix packet drop issue firstly*/
	if ((mac_mode == PORT_WRAPPER_PSGMII) ||
	    (mac_mode == PORT_WRAPPER_PSGMII_FIBER)) {
		ssdk_psgmii_self_test(dev_id, A_FALSE, 100, &psgmii_result);
		clear_self_test_config(dev_id);
	} else {
#ifndef BOARD_AR71XX
		ssdk_ess_reset();
#endif
	}

	qca_switch_init(dev_id);
#ifdef IN_PORTVLAN
	ssdk_portvlan_init(dev_id);
#endif

#ifdef IN_PORTVLAN
	fal_port_rxhdr_mode_set(dev_id, 0, FAL_ALL_TYPE_FRAME_EN);
#endif
#ifdef IN_IP
#ifndef IN_IP_MINI
	fal_ip_route_status_set(dev_id, A_TRUE);
#endif
#endif

	ssdk_flow_default_act_init(dev_id);

	/*set normal hash and disable nat/napt*/
	qca_switch_reg_read(dev_id, 0x0e38, (a_uint8_t *)&reg_value, 4);
	reg_value = (reg_value|0x1000000|0x8);
	reg_value &= ~2;
	qca_switch_reg_write(dev_id, 0x0e38, (a_uint8_t *)&reg_value, 4);
#ifdef IN_IP
#ifndef IN_IP_MINI
	fal_ip_vrf_base_addr_set(dev_id, 0, 0);
#endif
#endif

	p_api = hsl_api_ptr_get (dev_id);
	if (p_api && p_api->port_flowctrl_thresh_set)
		p_api->port_flowctrl_thresh_set(dev_id, 0, SSDK_PORT0_FC_THRESH_ON_DFLT,
							SSDK_PORT0_FC_THRESH_OFF_DFLT);

	if (p_api && p_api->ip_glb_lock_time_set)
		p_api->ip_glb_lock_time_set(dev_id, FAL_GLB_LOCK_TIME_100US);


	/*config psgmii,sgmii or rgmii mode for Dakota*/
	ssdk_dess_mac_mode_init(dev_id, cfg->mac_mode);

#ifdef IN_LED
	/*add BGA Board led contorl*/
	ssdk_dess_led_init(cfg);
#endif
#ifdef IN_TRUNK
	SW_RTN_ON_ERROR(ssdk_dess_trunk_init(dev_id, cfg->port_cfg.wan_bmp));
#endif

	return SW_OK;
}
#endif
/*qca808x_start*/
static void ssdk_cfg_default_init(ssdk_init_cfg *cfg)
{
	memset(cfg, 0, sizeof(ssdk_init_cfg));
	cfg->cpu_mode = HSL_CPU_1;
	cfg->nl_prot = 30;
	cfg->reg_func.mdio_set = qca_ar8327_phy_write;
	cfg->reg_func.mdio_get = qca_ar8327_phy_read;
#if defined(IN_PHY_I2C_MODE)
	cfg->reg_func.i2c_set = qca_phy_i2c_write;
	cfg->reg_func.i2c_get = qca_phy_i2c_read;
#endif
/*qca808x_end*/

	cfg->reg_func.header_reg_set = qca_switch_reg_write;
	cfg->reg_func.header_reg_get = qca_switch_reg_read;
	cfg->reg_func.mii_reg_set = qca_mii_write;
	cfg->reg_func.mii_reg_get = qca_mii_read;
/*qca808x_start*/
}
/*qca808x_end*/

#ifdef IN_RFS
#if defined(CONFIG_RFS_ACCEL)
int ssdk_netdev_rfs_cb(
		struct net_device *dev,
		__be32 src, __be32 dst,
		__be16 sport, __be16 dport,
		u8 proto, u16 rxq_index, u32 action)
{
	return ssdk_rfs_ipct_rule_set(src, dst, sport, dport,
							proto, rxq_index, action);
}
#endif

#ifdef DESS
a_bool_t ssdk_intf_search(
	a_uint8_t *mac, a_uint16_t vid,
	a_uint8_t *ret_index, a_uint8_t *free_index)
{
	a_uint8_t  i = 0;

	for (i = 0; i < SSDK_RFS_INTF_MAX; i++) {
		if (rfs_intf_tbl[i].vid == 0)
			*free_index = i;
		if (!memcmp(rfs_intf_tbl[i].macaddr.uc, mac, 6) &&
			rfs_intf_tbl[i].vid == vid) {
			/* find it */
			*ret_index = i;
			return A_TRUE;
		}
	}

	/* Not find the same entry */
	return A_FALSE;
}

static a_bool_t ssdk_is_raw_dev(struct net_device *dev)
{
	struct device *pdev;

	pdev = dev->dev.parent;
	if (!pdev)
		return A_FALSE;

	if (!strstr(dev_name(pdev), "edma"))
		return A_FALSE;
	else
		return A_TRUE;
}

static a_uint16_t ssdk_raw_dev_vid_get(struct net_device *dev)
{
#ifdef CONFIG_RFS_ACCEL
	const struct net_device_ops *ops;

	ops = dev->netdev_ops;
	if (!ops ||
	    !ops->ndo_get_default_vlan_tag) {
		return 0;
	}
	return ops->ndo_get_default_vlan_tag(dev);
#else
	return 0;
#endif
}

static a_uint16_t ssdk_netdev_vid_get(struct net_device *dev)
{
	struct net_device *pdev;
	a_uint16_t vid = 0;

	if (is_vlan_dev(dev)) {
		pdev = vlan_dev_real_dev(dev);
		if (!ssdk_is_raw_dev(pdev)) {
			SSDK_DEBUG("The device %s is not expected!\n", dev->name);
			return 0;
		}
		vid = vlan_dev_vlan_id(dev);
	} else if (ssdk_is_raw_dev(dev)) {
		vid = ssdk_raw_dev_vid_get(dev);
	} else if (dev->priv_flags & IFF_EBRIDGE) {
		/* Do nothing for bridge */
	} else {
		SSDK_DEBUG("The device %s is not expected!\n", dev->name);
	}

	return vid;
}

static void ssdk_rfs_intf_add(struct net_device *dev)
{
	a_uint8_t *devmac = NULL;
	a_uint16_t vid = 0;
	fal_intf_mac_entry_t intf_entry;
	sw_error_t rv = 0;
	a_uint8_t index0, index1;

	/* get vid */
	vid = ssdk_netdev_vid_get(dev);
	if (vid == 0)
		return;

	/*get mac*/
	devmac = (a_uint8_t*)(dev->dev_addr);

	if (ssdk_intf_search(devmac, vid, &index0, &index1)) {
		/* already exist, ignore */
		return;
	}
	rfs_intf_tbl[index1].vid = vid;
	rfs_intf_tbl[index1].if_idx = dev->ifindex;
	memcpy(&rfs_intf_tbl[index1].macaddr, devmac, ETH_ALEN);

	memset(&intf_entry, 0, sizeof(intf_entry));
	intf_entry.ip4_route = 1;
	intf_entry.ip6_route = 1;
	intf_entry.vid_low = vid;
	intf_entry.vid_high = vid;
	memcpy(&intf_entry.mac_addr, devmac, 6);
	rv = fal_ip_intf_entry_add(0, &intf_entry);
	if (rv) {
		SSDK_ERROR("Faled to add intf entry, rv=%d\n", rv);
		memset(&rfs_intf_tbl[index1], 0, sizeof(ssdk_rfs_intf_t));
		return;
	}

	rfs_intf_tbl[index1].hw_idx = intf_entry.entry_id;

}

static void ssdk_rfs_intf_del(struct net_device *dev)
{
	a_uint8_t i = 0;
	fal_intf_mac_entry_t intf_entry;
	sw_error_t rv = 0;

	for (i = 0; i < SSDK_RFS_INTF_MAX; i++) {
		if ((rfs_intf_tbl[i].if_idx == dev->ifindex) &&
		    (rfs_intf_tbl[i].vid != 0)) {
			intf_entry.entry_id = rfs_intf_tbl[i].hw_idx;
			rv = fal_ip_intf_entry_del(0, 1, &intf_entry);
			if (rv) {
				SSDK_ERROR("Faled to del entry, rv=%d\n", rv);
			} else {
				memset(&rfs_intf_tbl[i], 0, sizeof(ssdk_rfs_intf_t));
			}
			return;
		}
	}
}

static int ssdk_inet_event(struct notifier_block *this, unsigned long event, void *ptr)
{
	struct net_device *dev = ((struct in_ifaddr *)ptr)->ifa_dev->dev;

	/* Ignore the wireless dev */
#ifdef CONFIG_WIRELESS_EXT
	if (dev->wireless_handlers)
		return NOTIFY_DONE;
	else
#endif
		if (dev->ieee80211_ptr)
			return NOTIFY_DONE;

	switch (event) {
		case NETDEV_DOWN:
			ssdk_rfs_intf_del(dev);
			break;
		case NETDEV_UP:
			ssdk_rfs_intf_add(dev);
			break;
	}
	return NOTIFY_DONE;
}
#endif
#endif

//#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0))
static int ssdk_dev_event(struct notifier_block *this, unsigned long event, void *ptr)
{
	int rv = 0;
	ssdk_init_cfg cfg;
#ifdef MP
	a_uint32_t port_id = 0, dev_id = 0;
	struct qca_phy_priv *priv = ssdk_phy_priv_data_get(dev_id);
	adpt_api_t *p_api = adpt_api_ptr_get(dev_id);
#endif

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0))
	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
#else
	struct net_device *dev = (struct net_device *)ptr;
#endif

	ssdk_cfg_default_init(&cfg);
	rv = chip_ver_get(0, &cfg);
	if (rv) {
		SSDK_ERROR("chip verfion get failed\n");
		return NOTIFY_DONE;
	}
	switch (event) {
#ifdef IN_RFS
#if defined(CONFIG_RFS_ACCEL)
		case NETDEV_UP:
			if (strstr(dev->name, "eth")) {
				if (dev->netdev_ops && dev->netdev_ops->ndo_register_rfs_filter) {
					dev->netdev_ops->ndo_register_rfs_filter(dev,
						ssdk_netdev_rfs_cb);
				}
			}
			break;
#endif
#endif
		case NETDEV_CHANGEMTU:
			if(dev->type == ARPHRD_ETHER) {
				if (cfg.chip_type == CHIP_DESS ||
					cfg.chip_type == CHIP_ISIS ||
					cfg.chip_type == CHIP_ISISC) {
					struct net_device *eth_dev = NULL;
					unsigned int mtu= 0;

					if(!strcmp(dev->name, "eth0")) {
						eth_dev = dev_get_by_name(&init_net, "eth1");
					} else if (!strcmp(dev->name, "eth1")) {
						eth_dev = dev_get_by_name(&init_net, "eth0");
					} else {
						return NOTIFY_DONE;
					}
					if (!eth_dev) {
						return NOTIFY_DONE;
					}
					mtu = dev->mtu > eth_dev->mtu ? dev->mtu : eth_dev->mtu;
#ifdef IN_MISC
					fal_frame_max_size_set(0, mtu + 18);
#endif
					dev_put(eth_dev);
				}
			}
			break;
#ifdef MP
		case NETDEV_CHANGE:
			if ((cfg.chip_type == CHIP_SCOMPHY) &&
				(cfg.phy_id == MP_GEPHY)) {
				if ((p_api == NULL) || (p_api->adpt_port_netdev_notify_set == NULL)
					|| (priv == NULL)) {
					SSDK_ERROR("Failed to get pointer\n");
					return NOTIFY_DONE;
				}
				if (dev->phydev != NULL) {
					port_id = qca_ssdk_phydev_to_port(priv->device_id,
						dev->phydev);
					rv = p_api->adpt_port_netdev_notify_set(priv, port_id);
					if (rv) {
						SSDK_ERROR("netdev change notify failed\n");
						return NOTIFY_DONE;
					}
				}
			}
			break;
#endif
	}

	return NOTIFY_DONE;
}

#ifdef DESS
static void qca_dess_rfs_remove(void)
{
	/* ssdk_dt_global->switch_reg_access_mode == HSL_REG_LOCAL_BUS */
	if(qca_dess_rfs_registered){
#if defined (CONFIG_NF_FLOW_COOKIE)
#ifdef IN_NAT
#ifdef IN_SFE
		sfe_unregister_flow_cookie_cb(ssdk_flow_cookie_set);
#endif
#endif
#endif
#ifdef IN_RFS
		rfs_ess_device_unregister(&rfs_dev);
		unregister_inetaddr_notifier(&ssdk_inet_notifier);
#if defined(CONFIG_RFS_ACCEL)
#endif
#endif
		qca_dess_rfs_registered = false;
	}

}

static void qca_dess_rfs_init(void)
{
	if (!qca_dess_rfs_registered) {
#if defined (CONFIG_NF_FLOW_COOKIE)
#ifdef IN_NAT
#ifdef IN_SFE
		sfe_register_flow_cookie_cb(ssdk_flow_cookie_set);
#endif
#endif
#endif

#ifdef IN_RFS
		memset(&rfs_dev, 0, sizeof(rfs_dev));
		rfs_dev.name = NULL;
#ifdef IN_FDB
		rfs_dev.mac_rule_cb = ssdk_rfs_mac_rule_set;
#endif
#ifdef IN_IP
		rfs_dev.ip4_rule_cb = ssdk_rfs_ip4_rule_set;
		rfs_dev.ip6_rule_cb = ssdk_rfs_ip6_rule_set;
#endif
		rfs_ess_device_register(&rfs_dev);
#if defined(CONFIG_RFS_ACCEL)
#endif
		ssdk_inet_notifier.notifier_call = ssdk_inet_event;
		ssdk_inet_notifier.priority = 1;
		register_inetaddr_notifier(&ssdk_inet_notifier);
#endif
		qca_dess_rfs_registered = true;
	}
}
#endif
/*qca808x_start*/
static void ssdk_free_priv(void)
{
	a_uint32_t dev_id, dev_num = 1;

	if(!qca_phy_priv_global) {
		return;
	}
/*qca808x_end*/
	dev_num = ssdk_switch_device_num_get();
/*qca808x_start*/
	for (dev_id = 0; dev_id < dev_num; dev_id++) {
		if (qca_phy_priv_global[dev_id]) {
			kfree(qca_phy_priv_global[dev_id]);
		}

		qca_phy_priv_global[dev_id] = NULL;
	}

	kfree(qca_phy_priv_global);

	qca_phy_priv_global = NULL;
/*qca808x_end*/
	ssdk_switch_device_num_exit();
/*qca808x_start*/
}

static int ssdk_alloc_priv(a_uint32_t dev_num)
{
	int rev = SW_OK;
	a_uint32_t dev_id = 0;

	qca_phy_priv_global = kzalloc(dev_num * sizeof(struct qca_phy_priv *), GFP_KERNEL);
	if (qca_phy_priv_global == NULL) {
		return -ENOMEM;
	}

	for (dev_id = 0; dev_id < dev_num; dev_id++) {
		qca_phy_priv_global[dev_id] = kzalloc(sizeof(struct qca_phy_priv), GFP_KERNEL);
		if (qca_phy_priv_global[dev_id] == NULL) {
			return -ENOMEM;
		}
/*qca808x_end*/
		qca_phy_priv_global[dev_id]->qca_ssdk_sw_dev_registered = A_FALSE;
		qca_phy_priv_global[dev_id]->ess_switch_flag = A_FALSE;
/*qca808x_start*/
		qca_ssdk_port_bmp_init(dev_id);
		qca_ssdk_phy_info_init(dev_id);
	}

	return rev;
}

#ifndef SSDK_STR
#define SSDK_STR "ssdk"
#endif
#if defined (ISISC) || defined (ISIS)
static void qca_ar8327_gpio_reset(struct qca_phy_priv *priv)
{
	struct device_node *np = NULL;
	const __be32 *reset_gpio;
	a_int32_t len;
	int gpio_num = 0, ret = 0;

	if (priv->ess_switch_flag == A_TRUE)
		np = priv->of_node;
	else
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,9,0))
		np = priv->phy->mdio.dev.of_node;
#else
		np = priv->phy->dev.of_node;
#endif
	if(!np)
		return;
	gpio_num = of_get_named_gpio(np, "reset_gpio", 0);
	if(gpio_num < 0)
	{
		reset_gpio = of_get_property(np, "reset_gpio", &len);
		if (!reset_gpio )
		{
			SSDK_INFO("reset_gpio node does not exist\n");
			return;
		}
		gpio_num = be32_to_cpup(reset_gpio);
		if(gpio_num <= 0)
		{
			SSDK_INFO("reset gpio doesn't exist\n ");
			return;
		}
	}
	ret = gpio_request(gpio_num, "reset_gpio");
	if(ret)
	{
		SSDK_ERROR("gpio%d request failed, ret:%d\n", gpio_num, ret);
		return;
	}
	gpio_direction_output(gpio_num, SSDK_GPIO_RESET);
	msleep(200);
	gpio_set_value(gpio_num, SSDK_GPIO_RELEASE);
	msleep(10);
	SSDK_INFO("GPIO%d reset switch done\n", gpio_num);

	gpio_free(gpio_num);

	return;
}
#endif
static int __init regi_init(void)
{
	a_uint32_t num = 0, dev_id = 0, dev_num = 1;
	ssdk_init_cfg cfg;
/*qca808x_end*/
	garuda_init_spec_cfg chip_spec_cfg;
/*qca808x_start*/
	int rv = 0;
/*qca808x_end*/
	/*init switch device num firstly*/
	ssdk_switch_device_num_init();

	dev_num = ssdk_switch_device_num_get();
/*qca808x_start*/
	rv = ssdk_alloc_priv(dev_num);
	if (rv)
		goto out;

	for (num = 0; num < dev_num; num++) {
		ssdk_cfg_default_init(&cfg);
/*qca808x_end*/
#ifndef BOARD_AR71XX
#if defined(CONFIG_OF) && (LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0))
		if(SW_DISABLE == ssdk_dt_parse(&cfg, num, &dev_id)) {
			SSDK_INFO("ess-switch node is unavalilable\n");
			continue;
		}
#endif
#endif

		/* device id is the array index */
		qca_phy_priv_global[dev_id]->device_id = ssdk_device_id_get(dev_id);
		qca_phy_priv_global[dev_id]->ess_switch_flag = ssdk_ess_switch_flag_get(dev_id);
		qca_phy_priv_global[dev_id]->of_node = ssdk_dts_node_get(dev_id);
		INIT_LIST_HEAD(&(qca_phy_priv_global[dev_id]->sw_fdb_tbl));
/*qca808x_start*/
		rv = ssdk_plat_init(&cfg, dev_id);
		SW_CNTU_ON_ERROR_AND_COND1_OR_GOTO_OUT(rv, -ENODEV);
/*qca808x_end*/
		ssdk_driver_register(dev_id);
/*qca808x_start*/
		rv = chip_ver_get(dev_id, &cfg);
		SW_CNTU_ON_ERROR_AND_COND1_OR_GOTO_OUT(rv, -ENODEV);
/*qca808x_end*/
#ifdef IN_AQUANTIA_PHY
		ssdk_miireg_ioctrl_register();
#endif
		memset(&chip_spec_cfg, 0, sizeof(garuda_init_spec_cfg));
		cfg.chip_spec_cfg = &chip_spec_cfg;
/*qca808x_start*/
		rv = ssdk_init(dev_id, &cfg);
		SW_CNTU_ON_ERROR_AND_COND1_OR_GOTO_OUT(rv, -ENODEV);
/*qca808x_end*/


		switch (cfg.chip_type)
		{
			case CHIP_ISIS:
			case CHIP_ISISC:
#if defined (ISISC) || defined (ISIS)
				if (qca_phy_priv_global[dev_id]->ess_switch_flag == A_TRUE) {
					qca_ar8327_gpio_reset(qca_phy_priv_global[dev_id]);
					rv = ssdk_switch_register(dev_id, cfg.chip_type);
					SW_CNTU_ON_ERROR_AND_COND1_OR_GOTO_OUT(rv, -ENODEV);
					rv = qca_ar8327_hw_init(qca_phy_priv_global[dev_id]);
					SSDK_INFO("Initializing ISISC Done!!\n");
				}
#endif
				break;
			case CHIP_MHT:
#if defined(MHT)
				rv = qca_mht_hw_init(&cfg, dev_id);
				SW_CNTU_ON_ERROR_AND_COND1_OR_GOTO_OUT(rv, -ENODEV);
				rv = ssdk_switch_register(dev_id, cfg.chip_type);
				SW_CNTU_ON_ERROR_AND_COND1_OR_GOTO_OUT(rv, -ENODEV);
				SSDK_INFO("Initializing MHT Done!!\n");
#endif
				break;
			case CHIP_APPE:
#if defined(APPE)
				qca_appe_hw_init(&cfg, dev_id);
				rv = ssdk_switch_register(dev_id, cfg.chip_type);
				SW_CNTU_ON_ERROR_AND_COND1_OR_GOTO_OUT(rv, -ENODEV);
				SSDK_INFO("Initializing APPE Done!!\n");
#endif
				break;
			case CHIP_HPPE:
#if defined(HPPE)
				qca_hppe_hw_init(&cfg, dev_id);
				rv = ssdk_switch_register(dev_id, cfg.chip_type);
				SW_CNTU_ON_ERROR_AND_COND1_OR_GOTO_OUT(rv, -ENODEV);
				SSDK_INFO("Initializing HPPE Done!!\n");
#endif
				break;

			case CHIP_DESS:
#if defined(DESS)
				qca_dess_hw_init(&cfg, dev_id);
				qca_dess_rfs_init();

				/* Setup Cpu port for Dakota platform. */
				switch_cpuport_setup(dev_id);
				rv = ssdk_switch_register(dev_id, cfg.chip_type);
				SW_CNTU_ON_ERROR_AND_COND1_OR_GOTO_OUT(rv, -ENODEV);
				SSDK_INFO("Initializing DESS Done!!\n");
#endif
				break;

			case CHIP_SHIVA:
			case CHIP_ATHENA:
			case CHIP_GARUDA:
			case CHIP_HORUS:
			case CHIP_UNSPECIFIED:
				break;
			case CHIP_SCOMPHY:
#if defined(SCOMPHY)
					rv = qca_scomphy_hw_init(&cfg, dev_id);
					SW_CNTU_ON_ERROR_AND_COND1_OR_GOTO_OUT(rv, -ENODEV);
#if defined(MP)
					if(cfg.phy_id == MP_GEPHY)
					{
						rv = ssdk_switch_register(dev_id, cfg.chip_type);
						SW_CNTU_ON_ERROR_AND_COND1_OR_GOTO_OUT(rv, -ENODEV);
					}
#endif
					SSDK_INFO("Initializing SCOMPHY Done!!\n");
#endif
				break;
		}

/*qca808x_start*/

	}
/*qca808x_end*/

	ssdk_sysfs_init();
#ifdef IN_NETLINK
	ssdk_genl_init();
#endif

	if (rv == 0){
		/* register the notifier later should be ok */
		ssdk_dev_notifier.notifier_call = ssdk_dev_event;
		ssdk_dev_notifier.priority = 1;
		register_netdevice_notifier(&ssdk_dev_notifier);
	}
/*qca808x_start*/

out:
	if (rv == 0)
		SSDK_INFO("qca-%s module init succeeded!\n", SSDK_STR);
	else {
		if (rv == -ENODEV) {
			rv = 0;
			SSDK_INFO("qca-%s module init, no device found!\n", SSDK_STR);
		} else {
			SSDK_INFO("qca-%s module init failed! (code: %d)\n", SSDK_STR, rv);
			ssdk_free_priv();
		}
	}

	return rv;
}

static void __exit
regi_exit(void)
{
	a_uint32_t dev_id, dev_num = 1;
	sw_error_t rv = SW_OK;
/*qca808x_end*/
	unregister_netdevice_notifier(&ssdk_dev_notifier);

	dev_num = ssdk_switch_device_num_get();
	for (dev_id = 0; dev_id < dev_num; dev_id++) {
		ssdk_driver_unregister(dev_id);
#if defined(DESS) || defined(HPPE) || defined(ISISC) || defined(ISIS) || defined(MP)
		if (qca_phy_priv_global[dev_id]->qca_ssdk_sw_dev_registered == A_TRUE)
			ssdk_switch_unregister(dev_id);
#endif
	}
/*qca808x_start*/
	for (dev_id = 0; dev_id < dev_num; dev_id++) {
		rv = ssdk_cleanup(dev_id);
	}

	if (rv == SW_OK)
		SSDK_INFO("qca-%s module exit  done!\n", SSDK_STR);
	else
		SSDK_ERROR("qca-%s module exit failed! (code: %d)\n", SSDK_STR, rv);
/*qca808x_end*/

#ifdef DESS
	qca_dess_rfs_remove();
#endif

	ssdk_sysfs_exit();
#ifdef IN_AQUANTIA_PHY
	ssdk_miireg_ioctrl_unregister();
#endif
	for (dev_id = 0; dev_id < dev_num; dev_id++) {
		ssdk_plat_exit(dev_id);
	}
#ifdef IN_NETLINK
	ssdk_genl_deinit();
#endif

/*qca808x_start*/
	ssdk_free_priv();
}

module_init(regi_init);
module_exit(regi_exit);

MODULE_DESCRIPTION("QCA SSDK Driver");
MODULE_LICENSE("Dual BSD/GPL");
/*qca808x_end*/
