text: /*
   * Copyright (c) 2012 Qualcomm Atheros, Inc.
   *
   * 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.
   */
  #include <linux/pci.h>
  #include <linux/crc32.h>
  #include <linux/etherdevice.h>
  #include <linux/mdio.h>
  
  #include "alx_reg.h"
  #include "alx_hw.h"
  
  #define ALX_REV_A(_r) ((_r) == ALX_REV_A0 || (_r) == ALX_REV_A1)
  
  /* get permanent mac address from */
  int alx_get_perm_macaddr(struct alx_hw *hw, u8 *addr)
  {
  	u32 val, mac0, mac1;
  	u16 flag, i;
  
  #define INTN_LOADED 0x1
  #define EXTN_LOADED 0x2
  
  	flag = 0;
  	val = 0;
  
  read_mcadr:
  
  	/* get it from register first */
  	ALX_MEM_R32(hw, ALX_STAD0, &mac0);
  	ALX_MEM_R32(hw, ALX_STAD1, &mac1);
  
  	/* addr should be big-endian */
  	*(__be32 *)(addr + 2) = cpu_to_be32(mac0);
  	*(__be16 *)addr = cpu_to_be16((u16)mac1);
  
  	if (is_valid_ether_addr(addr))
  		return 0;
  
  	if ((flag & INTN_LOADED) == 0) {
  		/* load from efuse ? */
  		for (i = 0; i < ALX_SLD_MAX_TO; i++) {
  			ALX_MEM_R32(hw, ALX_SLD, &val);
  			if ((val & (ALX_SLD_STAT | ALX_SLD_START)) == 0)
  				break;
  			mdelay(1);
  		}
  		if (i == ALX_SLD_MAX_TO)
  			goto out;
  		ALX_MEM_W32(hw, ALX_SLD, val | ALX_SLD_START);
  		for (i = 0; i < ALX_SLD_MAX_TO; i++) {
  			mdelay(1);
  			ALX_MEM_R32(hw, ALX_SLD, &val);
  			if ((val & ALX_SLD_START) == 0)
  				break;
  		}
  		if (i == ALX_SLD_MAX_TO)
  			goto out;
  		flag |= INTN_LOADED;
  		goto read_mcadr;
  	}
  
  	if ((flag & EXTN_LOADED) == 0) {
  		ALX_MEM_R32(hw, ALX_EFLD, &val);
  		if ((val & (ALX_EFLD_F_EXIST | ALX_EFLD_E_EXIST)) != 0) {
  			/* load from eeprom/flash ? */
  			for (i = 0; i < ALX_SLD_MAX_TO; i++) {
  				ALX_MEM_R32(hw, ALX_EFLD, &val);
  				if ((val & (ALX_EFLD_STAT |
  					    ALX_EFLD_START)) == 0) {
  					break;
  				}
  				mdelay(1);
  			}
  			if (i == ALX_SLD_MAX_TO)
  				goto out;
  			ALX_MEM_W32(hw, ALX_EFLD, val | ALX_EFLD_START);
  			for (i = 0; i < ALX_SLD_MAX_TO; i++) {
  				mdelay(1);
  				ALX_MEM_R32(hw, ALX_EFLD, &val);
  				if ((val & ALX_EFLD_START) == 0)
  					break;
  			}
  			if (i == ALX_SLD_MAX_TO)
  				goto out;
  			flag |= EXTN_LOADED;
  			goto read_mcadr;
  		}
  	}
  
  out:
  	return ALX_ERR_ALOAD;
  }
  
  void alx_set_macaddr(struct alx_hw *hw, u8 *addr)
  {
  	u32 val;
  
  	/* for example: 00-0B-6A-F6-00-DC * STAD0=6AF600DC, STAD1=000B */
  	val = be32_to_cpu(*(__be32 *)(addr + 2));
  	ALX_MEM_W32(hw, ALX_STAD0, val);
  	val = be16_to_cpu(*(__be16 *)addr);
  	ALX_MEM_W32(hw, ALX_STAD1, val);
  }
  
  void alx_add_mc_addr(struct alx_hw *hw, u8 *addr)
  {
  	u32 crc32, bit, reg;
  
  	crc32 = ether_crc(ETH_ALEN, addr);
  
  	/* The HASH Table  is a register array of 2 32-bit registers.
  	 * It is treated like an array of 64 bits.  We want to set
  	 * bit BitArray[hash_value]. So we figure out what register
  	 * the bit is in, read it, OR in the new bit, then write
  	 * back the new value.  The register is determined by the
  	 * upper 7 bits of the hash value and the bit within that
  	 * register are determined by the lower 5 bits of the value.
  	 */
  	reg = (crc32 >> 31) & 0x1;
  	bit = (crc32 >> 26) & 0x1F;
  
  	hw->mc_hash[reg] |= (0x1 << bit);
  }
  
  void alx_enable_osc(struct alx_hw *hw)
  {
  	u32 val;
  
  	/* rising edge */
  	ALX_MEM_R32(hw, ALX_MISC, &val);
  	ALX_MEM_W32(hw, ALX_MISC, val & ~ALX_MISC_INTNLOSC_OPEN);
  	ALX_MEM_W32(hw, ALX_MISC, val | ALX_MISC_INTNLOSC_OPEN);
  }
  
  void alx_reset_osc(struct alx_hw *hw, u8 rev)
  {
  	u32 val, val2;
  
  	/* clear Internal OSC settings, switching OSC by hw itself */
  	ALX_MEM_R32(hw, ALX_MISC3, &val);
  	ALX_MEM_W32(hw, ALX_MISC3,
  		(val & ~ALX_MISC3_25M_BY_SW) | ALX_MISC3_25M_NOTO_INTNL);
  
  	/* 25M clk from chipset may be unstable 1s after de-assert of
  	 * PERST, driver need re-calibrate before enter Sleep for WoL
  	 */
  	ALX_MEM_R32(hw, ALX_MISC, &val);
  	if (rev >= ALX_REV_B0) {
  		/* restore over current protection def-val,
  		 * this val could be reset by MAC-RST
  		 */
  		FIELD_SET32(val, ALX_MISC_PSW_OCP, ALX_MISC_PSW_OCP_DEF);
  		/* a 0->1 change will update the internal val of osc */
  		val &= ~ALX_MISC_INTNLOSC_OPEN;
  		ALX_MEM_W32(hw, ALX_MISC, val);
  		ALX_MEM_W32(hw, ALX_MISC, val | ALX_MISC_INTNLOSC_OPEN);
  		/* hw will automatically dis OSC after cab. */
  		ALX_MEM_R32(hw, ALX_MSIC2, &val2);
  		val2 &= ~ALX_MSIC2_CALB_START;
  		ALX_MEM_W32(hw, ALX_MSIC2, val2);
  		ALX_MEM_W32(hw, ALX_MSIC2, val2 | ALX_MSIC2_CALB_START);
  	} else {
  		val &= ~ALX_MISC_INTNLOSC_OPEN;
  		/* disable isoloate for A0 */
  		if (ALX_REV_A(rev))
  			val &= ~ALX_MISC_ISO_EN;
  
  		ALX_MEM_W32(hw, ALX_MISC, val | ALX_MISC_INTNLOSC_OPEN);
  		ALX_MEM_W32(hw, ALX_MISC, val);
  	}
  
  	udelay(20);
  }
  
  int alx_reset_mac(struct alx_hw *hw)
  {
  	u32 val, pmctrl;
  	int i, ret;
  	u8 rev;
  	bool a_cr;
  
  	pmctrl = 0;
  	rev = (u8)ALX_REVID(hw);
  	a_cr = ALX_REV_A(rev) && ALX_WITH_CR(hw);
  
  	/* disable all interrupts, RXQ/TXQ */
  	ALX_MEM_W32(hw, ALX_MSIX_MASK, 0xFFFFFFFF);
  	ALX_MEM_W32(hw, ALX_IMR, 0);
  	ALX_MEM_W32(hw, ALX_ISR, ALX_ISR_DIS);
  
  	ret = alx_stop_mac(hw);
  	if (ret)
  		return ret;
  
  	/* mac reset workaroud */
  	ALX_MEM_W32(hw, ALX_RFD_PIDX, 1);
  
  	/* dis l0s/l1 before mac reset */
  	if (a_cr) {
  		ALX_MEM_R32(hw, ALX_PMCTRL, &pmctrl);
  		if ((pmctrl & (ALX_PMCTRL_L1_EN | ALX_PMCTRL_L0S_EN)) != 0) {
  			ALX_MEM_W32(hw, ALX_PMCTRL,
  				    pmctrl & ~(ALX_PMCTRL_L1_EN |
  					       ALX_PMCTRL_L0S_EN));
  		}
  	}
  
  	/* reset whole mac safely */
  	ALX_MEM_R32(hw, ALX_MASTER, &val);
  	ALX_MEM_W32(hw, ALX_MASTER,
  		    val | ALX_MASTER_DMA_MAC_RST | ALX_MASTER_OOB_DIS);
  
  	/* make sure it's real idle */
  	udelay(10);
  	for (i = 0; i < ALX_DMA_MAC_RST_TO; i++) {
  		ALX_MEM_R32(hw, ALX_RFD_PIDX, &val);
  		if (val == 0)
  			break;
  		udelay(10);
  	}
  	for (; i < ALX_DMA_MAC_RST_TO; i++) {
  		ALX_MEM_R32(hw, ALX_MASTER, &val);
  		if ((val & ALX_MASTER_DMA_MAC_RST) == 0)
  			break;
  		udelay(10);
  	}
  	if (i == ALX_DMA_MAC_RST_TO)
  		return ALX_ERR_RSTMAC;
  	udelay(10);
  
  	if (a_cr) {
  		/* set ALX_MASTER_PCLKSEL_SRDS (affect by soft-rst, PERST) */
  		ALX_MEM_W32(hw, ALX_MASTER, val | ALX_MASTER_PCLKSEL_SRDS);
  		/* resoter l0s / l1 */
  		if (pmctrl & (ALX_PMCTRL_L1_EN | ALX_PMCTRL_L0S_EN))
  			ALX_MEM_W32(hw, ALX_PMCTRL, pmctrl);
  	}
  
  	alx_reset_osc(hw, rev);
  	/* clear Internal OSC settings, switching OSC by hw itself,
  	 * disable isoloate for A version
  	 */
  	ALX_MEM_R32(hw, ALX_MISC3, &val);
  	ALX_MEM_W32(hw, ALX_MISC3,
  		    (val & ~ALX_MISC3_25M_BY_SW) | ALX_MISC3_25M_NOTO_INTNL);
  	ALX_MEM_R32(hw, ALX_MISC, &val);
  	val &= ~ALX_MISC_INTNLOSC_OPEN;
  	if (ALX_REV_A(rev))
  		val &= ~ALX_MISC_ISO_EN;
  	ALX_MEM_W32(hw, ALX_MISC, val);
  	udelay(20);
  
  	/* driver control speed/duplex, hash-alg */
  	ALX_MEM_W32(hw, ALX_MAC_CTRL, hw->rx_ctrl);
  
  	/* clk sw */
  	ALX_MEM_R32(hw, ALX_SERDES, &val);
  	ALX_MEM_W32(hw, ALX_SERDES,
  		val | ALX_SERDES_MACCLK_SLWDWN | ALX_SERDES_PHYCLK_SLWDWN);
  
  	/* mac reset cause MDIO ctrl restore non-polling status */
  	if (hw->is_fpga)
  		__alx_start_phy_polling(hw, ALX_MDIO_CLK_SEL_25MD128);
  
  
  	return ret;
  }
  
  /* alx_reset_phy
   *     completely reset phy, all settings/workaround will be re-configureed
   *     hib_en: enable/disable hibernation on PHY
   */
  void alx_reset_phy(struct alx_hw *hw, bool hib_en)
  {
  	int i;
  	u32 val;
  	u16 phy_val;
  
  	/* (DSP)reset PHY core */
  	ALX_MEM_R32(hw, ALX_PHY_CTRL, &val);
  	val &= ~(ALX_PHY_CTRL_DSPRST_OUT | ALX_PHY_CTRL_IDDQ |
  		 ALX_PHY_CTRL_GATE_25M | ALX_PHY_CTRL_POWER_DOWN |
  		 ALX_PHY_CTRL_CLS);
  	val |= ALX_PHY_CTRL_RST_ANALOG;
  
  	if (hib_en)
  		val |= (ALX_PHY_CTRL_HIB_PULSE | ALX_PHY_CTRL_HIB_EN);
  	else
  		val &= ~(ALX_PHY_CTRL_HIB_PULSE | ALX_PHY_CTRL_HIB_EN);
  	ALX_MEM_W32(hw, ALX_PHY_CTRL, val);
  	udelay(10);
  	ALX_MEM_W32(hw, ALX_PHY_CTRL, val | ALX_PHY_CTRL_DSPRST_OUT);
  
  	/* delay 800us */
  	for (i = 0; i < ALX_PHY_CTRL_DSPRST_TO; i++)
  		udelay(10);
  
  	if (hw->is_fpga)
  		goto set_imr;
  
  	/* phy power saving & hib */
  	if (hib_en) {
  		alx_write_phy_dbg(hw, ALX_MIIDBG_LEGCYPS, ALX_LEGCYPS_DEF);
  		alx_write_phy_dbg(hw, ALX_MIIDBG_SYSMODCTRL,
  			ALX_SYSMODCTRL_IECHOADJ_DEF);
  		alx_write_phy_ext(hw, ALX_MIIEXT_PCS, ALX_MIIEXT_VDRVBIAS,
  			ALX_VDRVBIAS_DEF);
  	} else {
  		alx_write_phy_dbg(hw, ALX_MIIDBG_LEGCYPS,
  			ALX_LEGCYPS_DEF & ~ALX_LEGCYPS_EN);
  		alx_write_phy_dbg(hw, ALX_MIIDBG_HIBNEG, ALX_HIBNEG_NOHIB);
  		alx_write_phy_dbg(hw, ALX_MIIDBG_GREENCFG, ALX_GREENCFG_DEF);
  	}
  
  	/* EEE advertisement */
  	if (ALX_CAP(hw, AZ)) {
  		alx_write_phy_ext(hw, ALX_MIIEXT_ANEG,
  			ALX_MIIEXT_LOCAL_EEEADV,
  			ALX_CAP(hw, GIGA) ?
  			ALX_LOCAL_EEEADV_1000BT | ALX_LOCAL_EEEADV_100BT :
  			ALX_LOCAL_EEEADV_100BT);
  		/* half amplify */
  		alx_write_phy_dbg(hw, ALX_MIIDBG_AZ_ANADECT,
  			ALX_AZ_ANADECT_DEF);
  	} else {
  		ALX_MEM_R32(hw, ALX_LPI_CTRL, &val);
  		ALX_MEM_W32(hw, ALX_LPI_CTRL, val & ~ALX_LPI_CTRL_EN);
  		alx_write_phy_ext(hw, ALX_MIIEXT_ANEG,
  			ALX_MIIEXT_LOCAL_EEEADV, 0);
  	}
  
  	/* phy power saving */
  	alx_write_phy_dbg(hw, ALX_MIIDBG_TST10BTCFG, ALX_TST10BTCFG_DEF);
  	alx_write_phy_dbg(hw, ALX_MIIDBG_SRDSYSMOD, ALX_SRDSYSMOD_DEF);
  	alx_write_phy_dbg(hw, ALX_MIIDBG_TST100BTCFG, ALX_TST100BTCFG_DEF);
  	alx_write_phy_dbg(hw, ALX_MIIDBG_ANACTRL, ALX_ANACTRL_DEF);
  	alx_read_phy_dbg(hw, ALX_MIIDBG_GREENCFG2, &phy_val);
  	alx_write_phy_dbg(hw, ALX_MIIDBG_GREENCFG2,
  		phy_val & ~ALX_GREENCFG2_GATE_DFSE_EN);
  	/* rtl8139c, 120m issue */
  	alx_write_phy_ext(hw, ALX_MIIEXT_ANEG, ALX_MIIEXT_NLP78,
  		ALX_MIIEXT_NLP78_120M_DEF);
  	alx_write_phy_ext(hw, ALX_MIIEXT_ANEG, ALX_MIIEXT_S3DIG10,
  		ALX_MIIEXT_S3DIG10_DEF);
  
  	if (hw->lnk_patch) {
  		/* Turn off half amplitude */
  		alx_read_phy_ext(hw, ALX_MIIEXT_PCS, ALX_MIIEXT_CLDCTRL3,
  			&phy_val);
  		alx_write_phy_ext(hw, ALX_MIIEXT_PCS, ALX_MIIEXT_CLDCTRL3,
  			      phy_val | ALX_CLDCTRL3_BP_CABLE1TH_DET_GT);
  		/* Turn off Green feature */
  		alx_read_phy_dbg(hw, ALX_MIIDBG_GREENCFG2, &phy_val);
  		alx_write_phy_dbg(hw, ALX_MIIDBG_GREENCFG2,
  				 phy_val | ALX_GREENCFG2_BP_GREEN);
  		/* Turn off half Bias */
  		alx_read_phy_ext(hw, ALX_MIIEXT_PCS, ALX_MIIEXT_CLDCTRL5,
  			&phy_val);
  		alx_write_phy_ext(hw, ALX_MIIEXT_PCS, ALX_MIIEXT_CLDCTRL5,
  			      phy_val | ALX_CLDCTRL5_BP_VD_HLFBIAS);
  	}
  
  set_imr:
  	/* set phy interrupt mask */
  	alx_write_phy_reg(hw, ALX_MII_IER,
  		ALX_IER_LINK_UP | ALX_IER_LINK_DOWN);
  }
  
  #define ALX_PCI_CMD	(\
  	PCI_COMMAND_MASTER |\
  	PCI_COMMAND_MEMORY |\
  	PCI_COMMAND_IO)
  /*
   * alx_reset_pcie
   *   reset pcie relative registers (pci command, clk, aspm...)
   */
  void alx_reset_pcie(struct alx_hw *hw)
  {
  	u32 val;
  	u16 val16;
  	u8 rev = (u8)ALX_REVID(hw);
  
  	/* Workaround for PCI problem when BIOS sets MMRBC incorrectly. */
  	ALX_CFG_R16(hw, PCI_COMMAND, &val16);
  	if (!(val16 & ALX_PCI_CMD) || (val16 & PCI_COMMAND_INTX_DISABLE)) {
  		val16 = (val16 | ALX_PCI_CMD) & ~PCI_COMMAND_INTX_DISABLE;
  		ALX_CFG_W16(hw, PCI_COMMAND, val16);
  	}
  
  	/* clear WoL setting/status */
  	ALX_MEM_R32(hw, ALX_WOL0, &val);
  	ALX_MEM_W32(hw, ALX_WOL0, 0);
  
  	/* deflt val of PDLL D3PLLOFF */
  	ALX_MEM_R32(hw, ALX_PDLL_TRNS1, &val);
  	ALX_MEM_W32(hw, ALX_PDLL_TRNS1, val & ~ALX_PDLL_TRNS1_D3PLLOFF_EN);
  
  	/* mask some pcie error bits */
  	ALX_MEM_R32(hw, ALX_UE_SVRT, &val);
  	val &= ~(ALX_UE_SVRT_DLPROTERR | ALX_UE_SVRT_FCPROTERR);
  	ALX_MEM_W32(hw, ALX_UE_SVRT, val);
  
  	/* wol 25M  & pclk */
  	ALX_MEM_R32(hw, ALX_MASTER, &val);
  	if (ALX_REV_A(rev) && ALX_WITH_CR(hw)) {
  		if ((val & ALX_MASTER_WAKEN_25M) == 0 ||
  		    (val & ALX_MASTER_PCLKSEL_SRDS) == 0) {
  			ALX_MEM_W32(hw, ALX_MASTER,
  				    val | ALX_MASTER_PCLKSEL_SRDS |
  				    ALX_MASTER_WAKEN_25M);
  		}
  	} else {
  		if ((val & ALX_MASTER_WAKEN_25M) == 0 ||
  		    (val & ALX_MASTER_PCLKSEL_SRDS) != 0) {
  			ALX_MEM_W32(hw, ALX_MASTER,
  				    (val & ~ALX_MASTER_PCLKSEL_SRDS) |
  				    ALX_MASTER_WAKEN_25M);
  		}
  	}
  
  	/* ASPM setting */
  	alx_enable_aspm(hw, ALX_CAP(hw, L0S), ALX_CAP(hw, L1));
  
  	udelay(10);
  }
  
  /* alx_stop_mac
   *     stop the mac, transmit & receive modules
   * return : 0 if ok, none-0 if busy
   */
  int alx_stop_mac(struct alx_hw *hw)
  {
  	u32 rxq, txq, val;
  	u16 i;
  
  	ALX_MEM_R32(hw, ALX_RXQ0, &rxq);
  	ALX_MEM_W32(hw, ALX_RXQ0, rxq & ~ALX_RXQ0_EN);
  	ALX_MEM_R32(hw, ALX_TXQ0, &txq);
  	ALX_MEM_W32(hw, ALX_TXQ0, txq & ~ALX_TXQ0_EN);
  
  	udelay(40);
  
  	hw->rx_ctrl &= ~(ALX_MAC_CTRL_RX_EN | ALX_MAC_CTRL_TX_EN);
  	ALX_MEM_W32(hw, ALX_MAC_CTRL, hw->rx_ctrl);
  
  	for (i = 0; i < ALX_DMA_MAC_RST_TO; i++) {
  		ALX_MEM_R32(hw, ALX_MAC_STS, &val);
  		if (!(val & ALX_MAC_STS_IDLE))
  			break;
  		udelay(10);
  	}
  
  	return (ALX_DMA_MAC_RST_TO == i) ? ALX_ERR_RSTMAC : 0;
  }
  
  /* alx_start_mac
   *     enable rx/tx MAC module
   */
  void alx_start_mac(struct alx_hw *hw)
  {
  	u32 mac, txq, rxq;
  
  	ALX_MEM_R32(hw, ALX_RXQ0, &rxq);
  	ALX_MEM_W32(hw, ALX_RXQ0, rxq | ALX_RXQ0_EN);
  	ALX_MEM_R32(hw, ALX_TXQ0, &txq);
  	ALX_MEM_W32(hw, ALX_TXQ0, txq | ALX_TXQ0_EN);
  
  	mac = hw->rx_ctrl;
  	if (hw->link_duplex == FULL_DUPLEX)
  		mac |= ALX_MAC_CTRL_FULLD;
  	else
  		mac &= ~ALX_MAC_CTRL_FULLD;
  	FIELD_SET32(mac, ALX_MAC_CTRL_SPEED, hw->link_speed == SPEED_1000 ?
  		   ALX_MAC_CTRL_SPEED_1000 : ALX_MAC_CTRL_SPEED_10_100);
  	mac |= ALX_MAC_CTRL_TX_EN | ALX_MAC_CTRL_RX_EN;
  	hw->rx_ctrl = mac;
  	ALX_MEM_W32(hw, ALX_MAC_CTRL, mac);
  }
  
  /* set flow control on MAC side */
  void alx_cfg_mac_fc(struct alx_hw *hw, u8 fc)
  {
  	if (fc & ALX_FC_RX)
  		hw->rx_ctrl |= ALX_MAC_CTRL_RXFC_EN;
  	else
  		hw->rx_ctrl &= ~ALX_MAC_CTRL_RXFC_EN;
  
  	if (fc & ALX_FC_TX)
  		hw->rx_ctrl |= ALX_MAC_CTRL_TXFC_EN;
  	else
  		hw->rx_ctrl &= ~ALX_MAC_CTRL_TXFC_EN;
  
  	ALX_MEM_W32(hw, ALX_MAC_CTRL, hw->rx_ctrl);
  }
  
  /* enable/disable aspm support */
  void alx_enable_aspm(struct alx_hw *hw, bool l0s_en, bool l1_en)
  {
  	u32 pmctrl;
  	u8 rev = (u8)ALX_REVID(hw);
  
  	ALX_MEM_R32(hw, ALX_PMCTRL, &pmctrl);
  
  	FIELD_SET32(pmctrl, ALX_PMCTRL_LCKDET_TIMER,
  		   ALX_PMCTRL_LCKDET_TIMER_DEF);
  	pmctrl |= ALX_PMCTRL_RCVR_WT_1US    |
  		  ALX_PMCTRL_L1_CLKSW_EN    |
  		  ALX_PMCTRL_L1_SRDSRX_PWD  ;
  	FIELD_SET32(pmctrl, ALX_PMCTRL_L1REQ_TO, ALX_PMCTRL_L1REG_TO_DEF);
  	FIELD_SET32(pmctrl, ALX_PMCTRL_L1_TIMER, ALX_PMCTRL_L1_TIMER_16US);
  	pmctrl &= ~(ALX_PMCTRL_L1_SRDS_EN |
  		    ALX_PMCTRL_L1_SRDSPLL_EN |
  		    ALX_PMCTRL_L1_BUFSRX_EN |
  		    ALX_PMCTRL_SADLY_EN |
  		    ALX_PMCTRL_HOTRST_WTEN|
  		    ALX_PMCTRL_L0S_EN |
  		    ALX_PMCTRL_L1_EN |
  		    ALX_PMCTRL_ASPM_FCEN |
  		    ALX_PMCTRL_TXL1_AFTER_L0S |
  		    ALX_PMCTRL_RXL1_AFTER_L0S
  		    );
  	if (ALX_REV_A(rev) && ALX_WITH_CR(hw))
  		pmctrl |= ALX_PMCTRL_L1_SRDS_EN | ALX_PMCTRL_L1_SRDSPLL_EN;
  
  	if (l0s_en)
  		pmctrl |= (ALX_PMCTRL_L0S_EN | ALX_PMCTRL_ASPM_FCEN);
  	if (l1_en)
  		pmctrl |= (ALX_PMCTRL_L1_EN | ALX_PMCTRL_ASPM_FCEN);
  
  	ALX_MEM_W32(hw, ALX_PMCTRL, pmctrl);
  }
  
  
  /* translate ethtool adv /speed/duplex settting to hw specific value */
  u32 ethadv_to_hw_cfg(struct alx_hw *hw, u32 ethadv_cfg)
  {
  	u32 cfg = 0;
  
  	if (ethadv_cfg & ADVERTISED_Autoneg) {
  		cfg |= ALX_DRV_PHY_AUTO;
  		if (ethadv_cfg & ADVERTISED_10baseT_Half)
  			cfg |= ALX_DRV_PHY_10;
  		if (ethadv_cfg & ADVERTISED_10baseT_Full)
  			cfg |= ALX_DRV_PHY_10 | ALX_DRV_PHY_DUPLEX;
  		if (ethadv_cfg & ADVERTISED_100baseT_Half)
  			cfg |= ALX_DRV_PHY_100;
  		if (ethadv_cfg & ADVERTISED_100baseT_Full)
  			cfg |= ALX_DRV_PHY_100 | ALX_DRV_PHY_DUPLEX;
  		if (ethadv_cfg & ADVERTISED_1000baseT_Half)
  			cfg |= ALX_DRV_PHY_1000;
  		if (ethadv_cfg & ADVERTISED_1000baseT_Full)
  			cfg |= ALX_DRV_PHY_100 | ALX_DRV_PHY_DUPLEX;
  		if (ethadv_cfg & ADVERTISED_Pause)
  			cfg |= ADVERTISE_PAUSE_CAP;
  		if (ethadv_cfg & ADVERTISED_Asym_Pause)
  			cfg |= ADVERTISE_PAUSE_ASYM;
  		if (ALX_CAP(hw, AZ))
  			cfg |= ALX_DRV_PHY_EEE;
  	} else {
  		switch (ethadv_cfg) {
  		case ADVERTISED_10baseT_Half:
  			cfg |= ALX_DRV_PHY_10;
  			break;
  		case ADVERTISED_100baseT_Half:
  			cfg |= ALX_DRV_PHY_100;
  			break;
  		case ADVERTISED_10baseT_Full:
  			cfg |= ALX_DRV_PHY_10 | ALX_DRV_PHY_DUPLEX;
  			break;
  		case ADVERTISED_100baseT_Full:
  			cfg |= ALX_DRV_PHY_100 | ALX_DRV_PHY_DUPLEX;
  			break;
  		}
  	}
  
  	return cfg;
  }
  
  /* initialize phy for speed / flow control
   * ethadv:
   *    format from ethtool, we use it for both autoneg and force mode
   */
  int alx_setup_speed_duplex(struct alx_hw *hw, u32 ethadv, u8 flowctrl)
  {
  	u16 adv, giga, cr;
  	u32 val;
  	int err = 0;
  
  	/* clear flag */
  	alx_write_phy_reg(hw, ALX_MII_DBG_ADDR, 0);
  	ALX_MEM_R32(hw, ALX_DRV, &val);
  	FIELD_SET32(val, ALX_DRV_PHY, 0);
  
  	if (ethadv & ADVERTISED_Autoneg) {
  		adv = ADVERTISE_CSMA;
  		adv |= ethtool_adv_to_mii_adv_t(ethadv);
  
  		if (flowctrl & ALX_FC_ANEG) {
  			if (flowctrl & ALX_FC_RX) {
  				adv |= ADVERTISED_Pause;
  				if (!(flowctrl & ALX_FC_TX))
  					adv |= ADVERTISED_Asym_Pause;
  			} else if (flowctrl & ALX_FC_TX)
  				adv |= ADVERTISED_Asym_Pause;
  		}
  		giga = 0;
  		if (ALX_CAP(hw, GIGA))
  			giga = ethtool_adv_to_mii_ctrl1000_t(ethadv);
  
  		cr = BMCR_RESET | BMCR_ANENABLE | BMCR_ANRESTART;
  
  		if (alx_write_phy_reg(hw, MII_ADVERTISE, adv) ||
  		    alx_write_phy_reg(hw, MII_CTRL1000, giga) ||
  		    alx_write_phy_reg(hw, MII_BMCR, cr))
  			err = ALX_ERR_MIIBUSY;
  	} else {
  		cr = BMCR_RESET;
  		if (ethadv == ADVERTISED_100baseT_Half ||
  		    ethadv == ADVERTISED_100baseT_Full)
  			cr |= BMCR_SPEED100;
  		if (ethadv == ADVERTISED_10baseT_Full ||
  		    ethadv == ADVERTISED_100baseT_Full)
  			cr |= BMCR_FULLDPLX;
  
  		err = alx_write_phy_reg(hw, MII_BMCR, cr);
  	}
  
  	if (!err) {
  		alx_write_phy_reg(hw, ALX_MII_DBG_ADDR, ALX_PHY_INITED);
  		/* save config to HW */
  		val |= ethadv_to_hw_cfg(hw, ethadv);
  	}
  
  	ALX_MEM_W32(hw, ALX_DRV, val);
  
  	return err;
  }
  
  
  /* do post setting on phy if link up/down event occur */
  void alx_post_phy_link(struct alx_hw *hw, u16 speed, bool az_en)
  {
  	u16 phy_val, len, agc;
  	u8 revid = (u8)ALX_REVID(hw);
  	bool adj_th;
  
  	if (revid != ALX_REV_B0 &&
  	    revid != ALX_REV_A1 &&
  	    revid != ALX_REV_A0) {
  		return;
  	}
  	adj_th = (revid == ALX_REV_B0) ? true : false;
  
  	/* 1000BT/AZ, wrong cable length */
  	if (speed != SPEED_0) {
  		alx_read_phy_ext(hw, ALX_MIIEXT_PCS, ALX_MIIEXT_CLDCTRL6,
  				 &phy_val);
  		len = FIELD_GETX(phy_val, ALX_CLDCTRL6_CAB_LEN);
  		alx_read_phy_dbg(hw, ALX_MIIDBG_AGC, &phy_val);
  		agc = FIELD_GETX(phy_val, ALX_AGC_2_VGA);
  
  		if ((speed == SPEED_1000 &&
  		    (len > ALX_CLDCTRL6_CAB_LEN_SHORT1G ||
  		    (0 == len && agc > ALX_AGC_LONG1G_LIMT))) ||
  		    (speed == SPEED_100 &&
  		    (len > ALX_CLDCTRL6_CAB_LEN_SHORT100M ||
  		    (0 == len && agc > ALX_AGC_LONG100M_LIMT)))) {
  			alx_write_phy_dbg(hw, ALX_MIIDBG_AZ_ANADECT,
  					  ALX_AZ_ANADECT_LONG);
  			alx_read_phy_ext(hw, ALX_MIIEXT_ANEG, ALX_MIIEXT_AFE,
  					 &phy_val);
  			alx_write_phy_ext(hw, ALX_MIIEXT_ANEG, ALX_MIIEXT_AFE,
  					  phy_val | ALX_AFE_10BT_100M_TH);
  		} else {
  			alx_write_phy_dbg(hw, ALX_MIIDBG_AZ_ANADECT,
  					  ALX_AZ_ANADECT_DEF);
  			alx_read_phy_ext(hw, ALX_MIIEXT_ANEG,
  					 ALX_MIIEXT_AFE, &phy_val);
  			alx_write_phy_ext(hw, ALX_MIIEXT_ANEG, ALX_MIIEXT_AFE,
  					  phy_val & ~ALX_AFE_10BT_100M_TH);
  		}
  
  		/* threashold adjust */
  		if (adj_th && hw->lnk_patch) {
  			if (speed == SPEED_100) {
  				alx_write_phy_dbg(hw, ALX_MIIDBG_MSE16DB,
  						  ALX_MSE16DB_UP);
  			} else if (speed == SPEED_1000) {
  				/*
  				 * Giga link threshold, raise the tolerance of
  				 * noise 50%
  				 */
  				alx_read_phy_dbg(hw, ALX_MIIDBG_MSE20DB,
  						 &phy_val);
  				FIELD_SETS(phy_val, ALX_MSE20DB_TH,
  					   ALX_MSE20DB_TH_HI);
  				alx_write_phy_dbg(hw, ALX_MIIDBG_MSE20DB,
  						  phy_val);
  			}
  		}
  		/* phy link-down in 1000BT/AZ mode */
  		if (az_en && revid == ALX_REV_B0 && speed == SPEED_1000) {
  			alx_write_phy_dbg(hw, ALX_MIIDBG_SRDSYSMOD,
  				ALX_SRDSYSMOD_DEF & ~ALX_SRDSYSMOD_DEEMP_EN);
  		}
  	} else {
  		alx_read_phy_ext(hw, ALX_MIIEXT_ANEG, ALX_MIIEXT_AFE,
  				 &phy_val);
  		alx_write_phy_ext(hw, ALX_MIIEXT_ANEG, ALX_MIIEXT_AFE,
  				  phy_val & ~ALX_AFE_10BT_100M_TH);
  
  		if (adj_th && hw->lnk_patch) {
  			alx_write_phy_dbg(hw, ALX_MIIDBG_MSE16DB,
  					  ALX_MSE16DB_DOWN);
  			alx_read_phy_dbg(hw, ALX_MIIDBG_MSE20DB, &phy_val);
  			FIELD_SETS(phy_val, ALX_MSE20DB_TH, ALX_MSE20DB_TH_DEF);
  			alx_write_phy_dbg(hw, ALX_MIIDBG_MSE20DB, phy_val);
  		}
  		if (az_en && revid == ALX_REV_B0) {
  			alx_write_phy_dbg(hw, ALX_MIIDBG_SRDSYSMOD,
  					  ALX_SRDSYSMOD_DEF);
  		}
  	}
  }
  
  
  /* do power saving setting befor enter suspend mode
   * NOTE:
   *    1. phy link must be established before calling this function
   *    2. wol option (pattern,magic,link,etc.) is configed before call it.
   */
  int alx_pre_suspend(struct alx_hw *hw, u16 speed)
  {
  	u32 master, mac, phy, val;
  	int err = 0;
  
  	ALX_MEM_R32(hw, ALX_MASTER, &master);
  	master &= ~ALX_MASTER_PCLKSEL_SRDS;
  	mac = hw->rx_ctrl;
  	/* 10/100 half */
  	FIELD_SET32(mac, ALX_MAC_CTRL_SPEED,  ALX_MAC_CTRL_SPEED_10_100);
  	mac &= ~(ALX_MAC_CTRL_FULLD | ALX_MAC_CTRL_RX_EN | ALX_MAC_CTRL_TX_EN);
  
  	ALX_MEM_R32(hw, ALX_PHY_CTRL, &phy);
  	phy &= ~(ALX_PHY_CTRL_DSPRST_OUT | ALX_PHY_CTRL_CLS);
  	phy |= ALX_PHY_CTRL_RST_ANALOG | ALX_PHY_CTRL_HIB_PULSE |
  	       ALX_PHY_CTRL_HIB_EN;
  
  	/* without any activity  */
  	if (!(hw->sleep_ctrl & ALX_SLEEP_ACTIVE)) {
  		err = alx_write_phy_reg(hw, ALX_MII_IER, 0);
  		phy |= ALX_PHY_CTRL_IDDQ | ALX_PHY_CTRL_POWER_DOWN;
  		goto config_reg;
  	}
  
  	if (hw->sleep_ctrl & (ALX_SLEEP_WOL_MAGIC | ALX_SLEEP_CIFS))
  		mac |= ALX_MAC_CTRL_RX_EN | ALX_MAC_CTRL_BRD_EN;
  	if (hw->sleep_ctrl & ALX_SLEEP_CIFS)
  		mac |= ALX_MAC_CTRL_TX_EN;
  	if (speed % 10 == FULL_DUPLEX)
  		mac |= ALX_MAC_CTRL_FULLD;
  	if (speed >= SPEED_1000)
  		FIELD_SET32(mac, ALX_MAC_CTRL_SPEED, ALX_MAC_CTRL_SPEED_1000);
  	phy |= ALX_PHY_CTRL_DSPRST_OUT;
  	err = alx_write_phy_ext(hw, ALX_MIIEXT_ANEG,
  		ALX_MIIEXT_S3DIG10, ALX_MIIEXT_S3DIG10_SL);
  config_reg:
  
  	if (!err) {
  		alx_enable_osc(hw);
  		hw->rx_ctrl = mac;
  		ALX_MEM_W32(hw, ALX_MASTER, master);
  		ALX_MEM_W32(hw, ALX_MAC_CTRL, mac);
  		ALX_MEM_W32(hw, ALX_PHY_CTRL, phy);
  
  		/* set val of PDLL D3PLLOFF */
  		ALX_MEM_R32(hw, ALX_PDLL_TRNS1, &val);
  		val |= ALX_PDLL_TRNS1_D3PLLOFF_EN;
  		ALX_MEM_W32(hw, ALX_PDLL_TRNS1, val);
  	}
  
  	return err;
  }
  
  /* wait mdio module to be idle */
  bool __alx_wait_mdio_idle(struct alx_hw *hw)
  {
  	u32 val;
  	int i;
  
  	for (i = 0; i < ALX_MDIO_MAX_AC_TO; i++) {
  		ALX_MEM_R32(hw, ALX_MDIO, &val);
  		if (!(val & ALX_MDIO_BUSY))
  			break;
  		udelay(10);
  	}
  	return i != ALX_MDIO_MAX_AC_TO;
  }
  
  void __alx_stop_phy_polling(struct alx_hw *hw)
  {
  	if (!hw->is_fpga)
  		return;
  
  	ALX_MEM_W32(hw, ALX_MDIO, 0);
  	__alx_wait_mdio_idle(hw);
  }
  
  void __alx_start_phy_polling(struct alx_hw *hw, u16 clk_sel)
  {
  	u32 val;
  
  	if (!hw->is_fpga)
  		return;
  
  	val = ALX_MDIO_SPRES_PRMBL |
  	      FIELDX(ALX_MDIO_CLK_SEL, clk_sel) |
  	      FIELDX(ALX_MDIO_REG, 1) |
  	      ALX_MDIO_START |
  	      ALX_MDIO_OP_READ;
  	ALX_MEM_W32(hw, ALX_MDIO, val);
  	__alx_wait_mdio_idle(hw);
  	val |= ALX_MDIO_AUTO_POLLING;
  	val &= ~ALX_MDIO_START;
  	ALX_MEM_W32(hw, ALX_MDIO, val);
  	udelay(30);
  }
  
  /* __alx_read_phy_core
   *     core function to read register in PHY via MDIO interface
   * ext: extension register (see IEEE 802.3)
   * dev: device address (see IEEE 802.3 DEVAD, PRTAD is fixed to 0)
   * reg: register to read
   */
  int __alx_read_phy_core(struct alx_hw *hw, bool ext, u8 dev,
  			     u16 reg, u16 *phy_data)
  {
  	u32 val, clk_sel;
  	int err;
  
  	__alx_stop_phy_polling(hw);
  
  	*phy_data = 0;
  
  	/* use slow clock when it's in hibernation status */
  	clk_sel = !hw->link_up ?
  		ALX_MDIO_CLK_SEL_25MD128 : ALX_MDIO_CLK_SEL_25MD4;
  
  	if (ext) {
  		val = FIELDX(ALX_MDIO_EXTN_DEVAD, dev) |
  		      FIELDX(ALX_MDIO_EXTN_REG, reg);
  		ALX_MEM_W32(hw, ALX_MDIO_EXTN, val);
  
  		val = ALX_MDIO_SPRES_PRMBL |
  		      FIELDX(ALX_MDIO_CLK_SEL, clk_sel) |
  		      ALX_MDIO_START |
  		      ALX_MDIO_MODE_EXT |
  		      ALX_MDIO_OP_READ;
  	} else {
  		val = ALX_MDIO_SPRES_PRMBL |
  		      FIELDX(ALX_MDIO_CLK_SEL, clk_sel) |
  		      FIELDX(ALX_MDIO_REG, reg) |
  		      ALX_MDIO_START |
  		      ALX_MDIO_OP_READ;
  	}
  	ALX_MEM_W32(hw, ALX_MDIO, val);
  
  	if (unlikely(!__alx_wait_mdio_idle(hw)))
  		err = ALX_ERR_MIIBUSY;
  	else {
  		ALX_MEM_R32(hw, ALX_MDIO, &val);
  		*phy_data = (u16)FIELD_GETX(val, ALX_MDIO_DATA);
  		err = 0;
  	}
  
  	__alx_start_phy_polling(hw, clk_sel);
  
  	return err;
  }
  
  /* __alx_write_phy_core
   *     core function to write to register in PHY via MDIO interface
   * ext: extension register (see IEEE 802.3)
   * dev: device address (see IEEE 802.3 DEVAD, PRTAD is fixed to 0)
   * reg: register to write
   */
  int __alx_write_phy_core(struct alx_hw *hw, bool ext, u8 dev,
  		       u16 reg, u16 phy_data)
  {
  	u32 val, clk_sel;
  	int err = 0;
  
  	__alx_stop_phy_polling(hw);
  
  	/* use slow clock when it's in hibernation status */
  	clk_sel = !hw->link_up ?
  		ALX_MDIO_CLK_SEL_25MD128 : ALX_MDIO_CLK_SEL_25MD4;
  
  	if (ext) {
  		val = FIELDX(ALX_MDIO_EXTN_DEVAD, dev) |
  		      FIELDX(ALX_MDIO_EXTN_REG, reg);
  		ALX_MEM_W32(hw, ALX_MDIO_EXTN, val);
  
  		val = ALX_MDIO_SPRES_PRMBL |
  		      FIELDX(ALX_MDIO_CLK_SEL, clk_sel) |
  		      FIELDX(ALX_MDIO_DATA, phy_data) |
  		      ALX_MDIO_START |
  		      ALX_MDIO_MODE_EXT;
  	} else {
  		val = ALX_MDIO_SPRES_PRMBL |
  		      FIELDX(ALX_MDIO_CLK_SEL, clk_sel) |
  		      FIELDX(ALX_MDIO_REG, reg) |
  		      FIELDX(ALX_MDIO_DATA, phy_data) |
  		      ALX_MDIO_START;
  	}
  	ALX_MEM_W32(hw, ALX_MDIO, val);
  
  	if (unlikely(!__alx_wait_mdio_idle(hw)))
  		err = ALX_ERR_MIIBUSY;
  
  	__alx_start_phy_polling(hw, clk_sel);
  
  	return err;
  }
  
  /* read from PHY normal register */
  int __alx_read_phy_reg(struct alx_hw *hw, u16 reg, u16 *phy_data)
  {
  	return __alx_read_phy_core(hw, false, 0, reg, phy_data);
  }
  
  /* write to PHY normal register */
  int __alx_write_phy_reg(struct alx_hw *hw, u16 reg, u16 phy_data)
  {
  	return __alx_write_phy_core(hw, false, 0, reg, phy_data);
  }
  
  /* read from PHY extension register */
  int __alx_read_phy_ext(struct alx_hw *hw, u8 dev, u16 reg, u16 *pdata)
  {
  	return __alx_read_phy_core(hw, true, dev, reg, pdata);
  }
  
  /* write to PHY extension register */
  int __alx_write_phy_ext(struct alx_hw *hw, u8 dev, u16 reg, u16 data)
  {
  	return __alx_write_phy_core(hw, true, dev, reg, data);
  }
  
  /* read from PHY debug port */
  int __alx_read_phy_dbg(struct alx_hw *hw, u16 reg, u16 *pdata)
  {
  	int err;
  
  	err = __alx_write_phy_reg(hw, ALX_MII_DBG_ADDR, reg);
  	if (unlikely(err))
  		return err;
  	else
  		err = __alx_read_phy_reg(hw, ALX_MII_DBG_DATA, pdata);
  
  	return err;
  }
  
  /* write to PHY debug port */
  int __alx_write_phy_dbg(struct alx_hw *hw, u16 reg, u16 data)
  {
  	int err;
  
  	err = __alx_write_phy_reg(hw, ALX_MII_DBG_ADDR, reg);
  	if (unlikely(err))
  		return err;
  	else
  		err = __alx_write_phy_reg(hw, ALX_MII_DBG_DATA, data);
  
  	return err;
  }
  
  int alx_read_phy_reg(struct alx_hw *hw, u16 reg, u16 *phy_data)
  {
  	int err;
  
  	spin_lock(&hw->mdio_lock);
  	err = __alx_read_phy_reg(hw, reg, phy_data);
  	spin_unlock(&hw->mdio_lock);
  
  	return err;
  }
  
  int alx_write_phy_reg(struct alx_hw *hw, u16 reg, u16 phy_data)
  {
  	int err;
  
  	spin_lock(&hw->mdio_lock);
  	err = __alx_write_phy_reg(hw, reg, phy_data);
  	spin_unlock(&hw->mdio_lock);
  
  	return err;
  }
  
  int alx_read_phy_ext(struct alx_hw *hw, u8 dev, u16 reg, u16 *pdata)
  {
  	int err;
  
  	spin_lock(&hw->mdio_lock);
  	err = __alx_read_phy_ext(hw, dev, reg, pdata);
  	spin_unlock(&hw->mdio_lock);
  
  	return err;
  }
  
  int alx_write_phy_ext(struct alx_hw *hw, u8 dev, u16 reg, u16 data)
  {
  	int err;
  
  	spin_lock(&hw->mdio_lock);
  	err = __alx_write_phy_ext(hw, dev, reg, data);
  	spin_unlock(&hw->mdio_lock);
  
  	return err;
  }
  
  int alx_read_phy_dbg(struct alx_hw *hw, u16 reg, u16 *pdata)
  {
  	int err;
  
  	spin_lock(&hw->mdio_lock);
  	err = __alx_read_phy_dbg(hw, reg, pdata);
  	spin_unlock(&hw->mdio_lock);
  
  	return err;
  }
  
  int alx_write_phy_dbg(struct alx_hw *hw, u16 reg, u16 data)
  {
  	int err;
  
  	spin_lock(&hw->mdio_lock);
  	err = __alx_write_phy_dbg(hw, reg, data);
  	spin_unlock(&hw->mdio_lock);
  
  	return err;
  }
  
  u16 alx_get_phy_config(struct alx_hw *hw)
  {
  	u32 val;
  	u16 phy_val;
  
  	ALX_MEM_R32(hw, ALX_PHY_CTRL, &val);
  	/* phy in rst */
  	if ((val & ALX_PHY_CTRL_DSPRST_OUT) == 0)
  		return ALX_DRV_PHY_UNKNOWN;
  
  	ALX_MEM_R32(hw, ALX_DRV, &val);
  	val = FIELD_GETX(val, ALX_DRV_PHY);
  	if (ALX_DRV_PHY_UNKNOWN == val)
  		return ALX_DRV_PHY_UNKNOWN;
  
  	alx_read_phy_reg(hw, ALX_MII_DBG_ADDR, &phy_val);
  	if (ALX_PHY_INITED == phy_val)
  		return (u16) val;
  
  	return ALX_DRV_PHY_UNKNOWN;
  }
  
  bool alx_phy_configed(struct alx_hw *hw)
  {
  	u32 cfg, hw_cfg;
  
  	cfg = ethadv_to_hw_cfg(hw, hw->adv_cfg);
  	cfg = FIELD_GETX(cfg, ALX_DRV_PHY);
  	hw_cfg = alx_get_phy_config(hw);
  	if (hw_cfg == ALX_DRV_PHY_UNKNOWN)
  		return false;
  
  	return cfg == hw_cfg;
  }
  
  int alx_get_phy_link(struct alx_hw *hw, bool *link_up, u16 *speed)
  {
  	struct pci_dev *pdev = hw->pdev;
  	u16 bmsr, giga;
  	int err;
  
  	err = alx_read_phy_reg(hw, MII_BMSR, &bmsr);
  	err = alx_read_phy_reg(hw, MII_BMSR, &bmsr);
  	if (unlikely(err))
  		goto out;
  
  	if (!(bmsr & BMSR_LSTATUS)) {
  		*link_up = false;
  		goto out;
  	}
  
  	*link_up = true;
  
  	/* speed/duplex result is saved in PHY Specific Status Register */
  	err = alx_read_phy_reg(hw, ALX_MII_GIGA_PSSR, &giga);
  	if (unlikely(err))
  		goto out;
  
  	if (!(giga & ALX_GIGA_PSSR_SPD_DPLX_RESOLVED))
  		goto wrong_spd_out;
  
  	switch (giga & ALX_GIGA_PSSR_SPEED) {
  	case ALX_GIGA_PSSR_1000MBS:
  		*speed = SPEED_1000;
  		break;
  	case ALX_GIGA_PSSR_100MBS:
  		*speed = SPEED_100;
  		break;
  	case ALX_GIGA_PSSR_10MBS:
  		*speed = SPEED_10;
  		break;
  	default:
  		goto wrong_spd_out;
  	}
  	*speed += (giga & ALX_GIGA_PSSR_DPLX) ? FULL_DUPLEX : HALF_DUPLEX;
  	goto out;
  
  wrong_spd_out:
  	dev_err(&pdev->dev, "PHY SPD/DPLX unresolved :%x\n", giga);
  	err = -EINVAL;
  out:
  	return err;
  }
  
  int alx_clear_phy_intr(struct alx_hw *hw)
  {
  	u16 isr;
  
  	/* clear interrupt status by read it */
  	return alx_read_phy_reg(hw, ALX_MII_ISR, &isr);
  }
  
  int alx_config_wol(struct alx_hw *hw)
  {
  	u32 wol;
  	int err = 0;
  
  	wol = 0;
  	/* turn on magic packet event */
  	if (hw->sleep_ctrl & ALX_SLEEP_WOL_MAGIC) {
  		wol |= ALX_WOL0_MAGIC_EN | ALX_WOL0_PME_MAGIC_EN;
  		/* magic packet maybe Broadcast&multicast&Unicast frame */
  		/* mac |= MAC_CTRL_BC_EN; */
  	}
  
  	/* turn on link up event */
  	if (hw->sleep_ctrl & ALX_SLEEP_WOL_PHY) {
  		wol |=  ALX_WOL0_LINK_EN | ALX_WOL0_PME_LINK;
  		/* only link up can wake up */
  		err = alx_write_phy_reg(hw, ALX_MII_IER, ALX_IER_LINK_UP);
  	}
  	ALX_MEM_W32(hw, ALX_WOL0, wol);
  
  	return err;
  }
  
  void alx_configure_rss(struct alx_hw *hw, bool en)
  {
  	u32 ctrl;
  	int i;
  
  	ALX_MEM_R32(hw, ALX_RXQ0, &ctrl);
  
  	if (en) {
  		for (i = 0; i < sizeof(hw->rss_key); i++) {
  			/* rss key should be saved in chip with
  			 * reversed order.
  			 */
  			int j = sizeof(hw->rss_key) - i - 1;
  
  			ALX_MEM_W8(hw, ALX_RSS_KEY0 + j, hw->rss_key[i]);
  		}
  
  		for (i = 0; i < ARRAY_SIZE(hw->rss_idt); i++)
  			ALX_MEM_W32(hw, ALX_RSS_IDT_TBL0 + i * 4,
  				    hw->rss_idt[i]);
  
  		FIELD_SET32(ctrl, ALX_RXQ0_RSS_HSTYP, hw->rss_hash_type);
  		FIELD_SET32(ctrl, ALX_RXQ0_RSS_MODE, ALX_RXQ0_RSS_MODE_MQMI);
  		FIELD_SET32(ctrl, ALX_RXQ0_IDT_TBL_SIZE, hw->rss_idt_size);
  		ctrl |= ALX_RXQ0_RSS_HASH_EN;
  	} else {
  		ctrl &= ~ALX_RXQ0_RSS_HASH_EN;
  	}
  
  	ALX_MEM_W32(hw, ALX_RXQ0, ctrl);
  }
  
  void alx_configure_basic(struct alx_hw *hw)
  {
  	u32 val, raw_mtu, max_payload;
  	u16 val16;
  	u8 chip_rev = ALX_REVID(hw);
  
  	/* mac address */
  	alx_set_macaddr(hw, hw->mac_addr);
  
  	/* clk gating */
  	ALX_MEM_W32(hw, ALX_CLK_GATE, ALX_CLK_GATE_ALL_A0);
  
  	/* idle timeout to switch clk_125M */
  	if (chip_rev >= ALX_REV_B0) {
  		ALX_MEM_W32(hw, ALX_IDLE_DECISN_TIMER,
  			ALX_IDLE_DECISN_TIMER_DEF);
  	}
  
  	/* stats refresh timeout */
  	ALX_MEM_W32(hw, ALX_SMB_TIMER, hw->smb_timer * 500UL);
  
  	/* intr moduration */
  	ALX_MEM_R32(hw, ALX_MASTER, &val);
  	val = val | ALX_MASTER_IRQMOD2_EN |
  		    ALX_MASTER_IRQMOD1_EN |
  		    ALX_MASTER_SYSALVTIMER_EN;
  	ALX_MEM_W32(hw, ALX_MASTER, val);
  	ALX_MEM_W32(hw, ALX_IRQ_MODU_TIMER,
  		    FIELDX(ALX_IRQ_MODU_TIMER1, hw->imt >> 1));
  	/* intr re-trig timeout */
  	ALX_MEM_W32(hw, ALX_INT_RETRIG, ALX_INT_RETRIG_TO);
  	/* tpd threshold to trig int */
  	ALX_MEM_W32(hw, ALX_TINT_TPD_THRSHLD, hw->ith_tpd);
  	ALX_MEM_W32(hw, ALX_TINT_TIMER, hw->imt);
  
  	/* mtu, 8:fcs+vlan */
  	raw_mtu = hw->mtu + ETH_HLEN;
  	ALX_MEM_W32(hw, ALX_MTU, raw_mtu + 8);
  	if (raw_mtu > ALX_MTU_JUMBO_TH)
  		hw->rx_ctrl &= ~ALX_MAC_CTRL_FAST_PAUSE;
  
  	/* txq */
  	if ((raw_mtu + 8) < ALX_TXQ1_JUMBO_TSO_TH)
  		val = (raw_mtu + 8 + 7) >> 3;
  	else
  		val = ALX_TXQ1_JUMBO_TSO_TH >> 3;
  	ALX_MEM_W32(hw, ALX_TXQ1, val | ALX_TXQ1_ERRLGPKT_DROP_EN);
  	max_payload = alx_get_readrq(hw) >> 8;
  	/*
  	 * if BIOS had changed the default dma read max length,
  	 * restore it to default value
  	 */
  	if (max_payload < ALX_DEV_CTRL_MAXRRS_MIN)
  		alx_set_readrq(hw, 128 << ALX_DEV_CTRL_MAXRRS_MIN);
  
  	val = FIELDX(ALX_TXQ0_TPD_BURSTPREF, ALX_TXQ_TPD_BURSTPREF_DEF) |
  	      ALX_TXQ0_MODE_ENHANCE |
  	      ALX_TXQ0_LSO_8023_EN |
  	      ALX_TXQ0_SUPT_IPOPT |
  	      FIELDX(ALX_TXQ0_TXF_BURST_PREF, ALX_TXQ_TXF_BURST_PREF_DEF);
  	ALX_MEM_W32(hw, ALX_TXQ0, val);
  	val = FIELDX(ALX_HQTPD_Q1_NUMPREF, ALX_TXQ_TPD_BURSTPREF_DEF) |
  	      FIELDX(ALX_HQTPD_Q2_NUMPREF, ALX_TXQ_TPD_BURSTPREF_DEF) |
  	      FIELDX(ALX_HQTPD_Q3_NUMPREF, ALX_TXQ_TPD_BURSTPREF_DEF) |
  	      ALX_HQTPD_BURST_EN;
  	ALX_MEM_W32(hw, ALX_HQTPD, val);
  
  	/* rxq, flow control */
  	ALX_MEM_R32(hw, ALX_SRAM5, &val);
  	val = FIELD_GETX(val, ALX_SRAM_RXF_LEN) << 3;
  	if (val > ALX_SRAM_RXF_LEN_8K) {
  		val16 = ALX_MTU_STD_ALGN >> 3;
  		val = (val - ALX_RXQ2_RXF_FLOW_CTRL_RSVD) >> 3;
  	} else {
  		val16 = ALX_MTU_STD_ALGN >> 3;
  		val = (val - ALX_MTU_STD_ALGN) >> 3;
  	}
  	ALX_MEM_W32(hw, ALX_RXQ2,
  		    FIELDX(ALX_RXQ2_RXF_XOFF_THRESH, val16) |
  		    FIELDX(ALX_RXQ2_RXF_XON_THRESH, val));
  	val = FIELDX(ALX_RXQ0_NUM_RFD_PREF, ALX_RXQ0_NUM_RFD_PREF_DEF) |
  	      FIELDX(ALX_RXQ0_RSS_MODE, ALX_RXQ0_RSS_MODE_DIS) |
  	      FIELDX(ALX_RXQ0_IDT_TBL_SIZE, ALX_RXQ0_IDT_TBL_SIZE_DEF) |
  	      ALX_RXQ0_RSS_HSTYP_ALL |
  	      ALX_RXQ0_RSS_HASH_EN |
  	      ALX_RXQ0_IPV6_PARSE_EN;
  	if (ALX_CAP(hw, GIGA)) {
  		FIELD_SET32(val, ALX_RXQ0_ASPM_THRESH,
  			    ALX_RXQ0_ASPM_THRESH_100M);
  	}
  	ALX_MEM_W32(hw, ALX_RXQ0, val);
  
  	/* DMA */
  	ALX_MEM_R32(hw, ALX_DMA, &val);
  	val = FIELDX(ALX_DMA_RORDER_MODE, ALX_DMA_RORDER_MODE_OUT) |
  	      ALX_DMA_RREQ_PRI_DATA |
  	      FIELDX(ALX_DMA_RREQ_BLEN, max_payload) |
  	      FIELDX(ALX_DMA_WDLY_CNT, ALX_DMA_WDLY_CNT_DEF) |
  	      FIELDX(ALX_DMA_RDLY_CNT, ALX_DMA_RDLY_CNT_DEF) |
  	      FIELDX(ALX_DMA_RCHNL_SEL, hw->dma_chnl - 1);
  	ALX_MEM_W32(hw, ALX_DMA, val);
  
  	/* multi-tx-q weight */
  	if (ALX_CAP(hw, MTQ)) {
  		val = FIELDX(ALX_WRR_PRI, hw->wrr_ctrl) |
  		      FIELDX(ALX_WRR_PRI0, hw->wrr[0]) |
  		      FIELDX(ALX_WRR_PRI1, hw->wrr[1]) |
  		      FIELDX(ALX_WRR_PRI2, hw->wrr[2]) |
  		      FIELDX(ALX_WRR_PRI3, hw->wrr[3]);
  		ALX_MEM_W32(hw, ALX_WRR, val);
  	}
  }
  
  void alx_mask_msix(struct alx_hw *hw, int index, bool mask)
  {
  	u32 reg, val;
  
  	reg = ALX_MSIX_ENTRY_BASE + index * PCI_MSIX_ENTRY_SIZE +
  		   PCI_MSIX_ENTRY_VECTOR_CTRL;
  
  	val = mask ? PCI_MSIX_ENTRY_CTRL_MASKBIT : 0;
  
  	ALX_MEM_W32(hw, reg, val);
  	ALX_MEM_FLUSH(hw);
  }
  
  int alx_select_powersaving_speed(struct alx_hw *hw, u16 *speed)
  {
  	int i, err;
  	u16 spd, lpa;
  	bool linkup;
  
  	err = alx_get_phy_link(hw, &linkup, &spd);
  	if (err)
  		goto out;
  
  	if (!linkup) {
  		*speed = SPEED_0;
  		goto out;
  	}
  
  	err = alx_read_phy_reg(hw, MII_LPA, &lpa);
  	if (err)
  		goto out;
  
  	if (!(lpa & LPA_LPACK)) {
  		*speed = spd;
  		goto out;
  	}
  	if (lpa & LPA_10FULL)
  		*speed = SPEED_10 + FULL_DUPLEX;
  	else if (lpa & LPA_10HALF)
  		*speed = SPEED_10 + HALF_DUPLEX;
  	else if (lpa & LPA_100FULL)
  		*speed = SPEED_100 + FULL_DUPLEX;
  	else
  		*speed = SPEED_100 + HALF_DUPLEX;
  
  	if (*speed != spd) {
  		err = alx_write_phy_reg(hw, ALX_MII_IER, 0);
  		if (err)
  			goto out;
  		err = alx_setup_speed_duplex(hw,
  			ALX_SPEED_TO_ETHADV(*speed) | ADVERTISED_Autoneg,
  			ALX_FC_ANEG | ALX_FC_RX | ALX_FC_TX);
  		if (err)
  			goto out;
  
  		/* wait for linkup */
  		for (i = 0; i < ALX_MAX_SETUP_LNK_CYCLE; i++) {
  			u16 speed2;
  			bool link_on;
  
  			msleep(100);
  			err = alx_get_phy_link(hw, &link_on, &speed2);
  			if (err)
  				goto out;
  			if (link_on)
  				break;
  		}
  		if (i == ALX_MAX_SETUP_LNK_CYCLE) {
  			err = ALX_LINK_TIMEOUT;
  			goto out;
  		}
  	}
  
  out:
  	return err;
  }
  
  void __alx_update_hw_stats(struct alx_hw *hw)
  {
  	u16 reg;
  	u32 data;
  	unsigned long *p;
  
  	/* RX stats */
  	reg = ALX_RX_STATS_BIN;
  	p = &hw->stats.rx_ok;
  	while (reg <=  ALX_RX_STATS_END) {
  		ALX_MEM_R32(hw, reg, &data);
  		*p++ += data;
  		reg += 4;
  	}
  
  	/* TX stats */
  	reg = ALX_TX_STATS_BIN;
  	p = &hw->stats.tx_ok;
  	while (reg <= ALX_TX_STATS_END) {
  		ALX_MEM_R32(hw, reg, &data);
  		*p++ += data;
  		reg += 4;
  	}
  }
  
  static const struct alx_platform_patch plats[] = {
  {0x1091, 0x00, 0x1969, 0x0091, 0x1001},
  {0},
  };
  
  void alx_patch_assign(struct alx_hw *hw)
  {
  	int i = 0;
  
  	while (plats[i].pci_did != 0) {
  		if (plats[i].pci_did == ALX_DID(hw) &&
  		    plats[i].subsystem_vid == ALX_SUB_VID(hw) &&
  		    plats[i].subsystem_did == ALX_SUB_DID(hw) &&
  		    (plats[i].pflag & ALX_PF_ANY_REV ||
  		     plats[i].pci_rev == hw->revision)) {
  			if (plats[i].pflag & ALX_PF_LINK)
  				hw->lnk_patch = true;
  			if (plats[i].pflag & ALX_PF_HIB)
  				hw->hib_patch = true;
  		}
  		i++;
  	}
  }
  
  bool alx_get_phy_info(struct alx_hw *hw)
  {
  	u16  devs1, devs2;
  
  	if (alx_read_phy_reg(hw, MII_PHYSID1, &hw->phy_id[0]) ||
  	    alx_read_phy_reg(hw, MII_PHYSID2, &hw->phy_id[1]))
  		return false;
  
  	/* since we haven't PMA/PMD status2 register, we can't
  	 * use mdio45_probe function for prtad and mmds.
  	 * use fixed MMD3 to get mmds.
  	 */
  	if (alx_read_phy_ext(hw, 3, MDIO_DEVS1, &devs1) ||
  	    alx_read_phy_ext(hw, 3, MDIO_DEVS2, &devs2))
  		return false;
  	hw->mdio.mmds = devs1 | devs2 << 16;
  
  	return true;
  }
  