1168 lines
32 KiB
C
Raw Normal View History

2024-09-09 08:57:42 +00:00
/*
* 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_regs.h>
#include <linux/mii.h>
#include "alx.h"
#include "alc_hw.h"
/*
* get permanent mac address
* 0: success
* non-0:fail
*/
u16 l1c_get_perm_macaddr(struct alx_hw *hw, u8 *addr)
{
u32 val, otp_ctrl, otp_flag, mac0, mac1;
u16 i;
u16 phy_val;
/* get it from register first */
alx_mem_r32(hw, L1C_STAD0, &mac0);
alx_mem_r32(hw, L1C_STAD1, &mac1);
*(u32 *)(addr + 2) = LX_SWAP_DW(mac0);
*(u16 *)addr = (u16)LX_SWAP_W((u16)mac1);
if (macaddr_valid(addr))
return 0;
alx_mem_r32(hw, L1C_TWSI_DBG, &val);
alx_mem_r32(hw, L1C_EFUSE_CTRL2, &otp_ctrl);
alx_mem_r32(hw, L1C_MASTER, &otp_flag);
if ((val & L1C_TWSI_DBG_DEV_EXIST) != 0 ||
(otp_flag & L1C_MASTER_OTP_FLG) != 0) {
/* nov-memory exist, do software-autoload */
/* enable OTP_CLK for L1C */
if (hw->pci_devid == L1C_DEV_ID ||
hw->pci_devid == L2C_DEV_ID) {
if ((otp_ctrl & L1C_EFUSE_CTRL2_CLK_EN) != 0) {
alx_mem_w32(hw, L1C_EFUSE_CTRL2,
otp_ctrl | L1C_EFUSE_CTRL2_CLK_EN);
udelay(5);
}
}
/* raise voltage temporally for L2CB/L1D */
if (hw->pci_devid == L2CB_DEV_ID ||
hw->pci_devid == L2CB2_DEV_ID) {
/* clear bit[7] of debugport 00 */
l1c_read_phydbg(hw, true, L1C_MIIDBG_ANACTRL,
&phy_val);
l1c_write_phydbg(hw, true, L1C_MIIDBG_ANACTRL,
phy_val & ~L1C_ANACTRL_HB_EN);
/* set bit[3] of debugport 3B */
l1c_read_phydbg(hw, true, L1C_MIIDBG_VOLT_CTRL,
&phy_val);
l1c_write_phydbg(hw, true, L1C_MIIDBG_VOLT_CTRL,
phy_val | L1C_VOLT_CTRL_SWLOWEST);
udelay(20);
}
/* do load */
alx_mem_r32(hw, L1C_SLD, &val);
alx_mem_w32(hw, L1C_SLD, val | L1C_SLD_START);
for (i = 0; i < L1C_SLD_MAX_TO; i++) {
mdelay(1);
alx_mem_r32(hw, L1C_SLD, &val);
if ((val & L1C_SLD_START) == 0)
break;
}
/* disable OTP_CLK for L1C */
if (hw->pci_devid == L1C_DEV_ID ||
hw->pci_devid == L2C_DEV_ID) {
alx_mem_w32(hw, L1C_EFUSE_CTRL2,
otp_ctrl & ~L1C_EFUSE_CTRL2_CLK_EN);
udelay(5);
}
/* low voltage */
if (hw->pci_devid == L2CB_DEV_ID ||
hw->pci_devid == L2CB2_DEV_ID) {
/* set bit[7] of debugport 00 */
l1c_read_phydbg(hw, true, L1C_MIIDBG_ANACTRL,
&phy_val);
l1c_write_phydbg(hw, true, L1C_MIIDBG_ANACTRL,
phy_val | L1C_ANACTRL_HB_EN);
/* clear bit[3] of debugport 3B */
l1c_read_phydbg(hw, true, L1C_MIIDBG_VOLT_CTRL,
&phy_val);
l1c_write_phydbg(hw, true, L1C_MIIDBG_VOLT_CTRL,
phy_val & ~L1C_VOLT_CTRL_SWLOWEST);
udelay(20);
}
if (i == L1C_SLD_MAX_TO)
goto out;
} else {
if (hw->pci_devid == L1C_DEV_ID ||
hw->pci_devid == L2C_DEV_ID) {
alx_mem_w32(hw, L1C_EFUSE_CTRL2,
otp_ctrl & ~L1C_EFUSE_CTRL2_CLK_EN);
udelay(5);
}
}
alx_mem_r32(hw, L1C_STAD0, &mac0);
alx_mem_r32(hw, L1C_STAD1, &mac1);
*(u32 *)(addr + 2) = LX_SWAP_DW(mac0);
*(u16 *)addr = (u16)LX_SWAP_W((u16)mac1);
if (macaddr_valid(addr))
return 0;
out:
return LX_ERR_ALOAD;
}
/*
* reset mac & dma
* return
* 0: success
* non-0:fail
*/
u16 l1c_reset_mac(struct alx_hw *hw)
{
u32 val, mrst_val;
u16 ret;
u16 i;
/* disable all interrupts, RXQ/TXQ */
alx_mem_w32(hw, L1C_IMR, 0);
alx_mem_w32(hw, L1C_ISR, L1C_ISR_DIS);
ret = l1c_enable_mac(hw, false, 0);
if (ret != 0)
return ret;
/* reset whole mac safely. OOB is meaningful for L1D only */
alx_mem_r32(hw, L1C_MASTER, &mrst_val);
mrst_val |= L1C_MASTER_OOB_DIS;
alx_mem_w32(hw, L1C_MASTER, mrst_val | L1C_MASTER_DMA_MAC_RST);
/* make sure it's idle */
for (i = 0; i < L1C_DMA_MAC_RST_TO; i++) {
alx_mem_r32(hw, L1C_MASTER, &val);
if ((val & L1C_MASTER_DMA_MAC_RST) == 0)
break;
#ifdef ALX_LINK_DOWN_CONFIG
mdelay(20);
#else
udelay(20);
#endif
}
if (i == L1C_DMA_MAC_RST_TO)
return LX_ERR_RSTMAC;
/* keep the old value */
alx_mem_w32(hw, L1C_MASTER, mrst_val & ~L1C_MASTER_DMA_MAC_RST);
/* driver control speed/duplex, hash-alg */
alx_mem_r32(hw, L1C_MAC_CTRL, &val);
alx_mem_w32(hw, L1C_MAC_CTRL, val | L1C_MAC_CTRL_WOLSPED_SWEN);
/* clk switch setting */
alx_mem_r32(hw, L1C_SERDES, &val);
switch (hw->pci_devid) {
case L2CB_DEV_ID:
alx_mem_w32(hw, L1C_SERDES, val & ~L1C_SERDES_PHYCLK_SLWDWN);
break;
case L2CB2_DEV_ID:
case L1D2_DEV_ID:
alx_mem_w32(hw, L1C_SERDES,
val | L1C_SERDES_PHYCLK_SLWDWN |
L1C_SERDES_MACCLK_SLWDWN);
break;
default:
/* the defalut value of default product is OFF */;
}
return 0;
}
/* reset phy
* return
* 0: success
* non-0:fail
*/
u16 l1c_reset_phy(struct alx_hw *hw, bool pws_en, bool az_en, bool ptp_en)
{
u32 val;
u16 i, phy_val;
ptp_en = ptp_en;
/* reset PHY core */
alx_mem_r32(hw, L1C_PHY_CTRL, &val);
val &= ~(L1C_PHY_CTRL_DSPRST_OUT | L1C_PHY_CTRL_IDDQ |
L1C_PHY_CTRL_GATE_25M | L1C_PHY_CTRL_POWER_DOWN |
L1C_PHY_CTRL_CLS);
val |= L1C_PHY_CTRL_RST_ANALOG;
if (pws_en)
val |= (L1C_PHY_CTRL_HIB_PULSE | L1C_PHY_CTRL_HIB_EN);
else
val &= ~(L1C_PHY_CTRL_HIB_PULSE | L1C_PHY_CTRL_HIB_EN);
alx_mem_w32(hw, L1C_PHY_CTRL, val);
udelay(10); /* 5us is enough */
alx_mem_w32(hw, L1C_PHY_CTRL, val | L1C_PHY_CTRL_DSPRST_OUT);
/* delay 800us */
for (i = 0; i < L1C_PHY_CTRL_DSPRST_TO; i++)
udelay(10);
/* switch clock */
if (hw->pci_devid == L2CB_DEV_ID) {
l1c_read_phydbg(hw, true, L1C_MIIDBG_CFGLPSPD, &phy_val);
/* clear bit13 */
l1c_write_phydbg(hw, true, L1C_MIIDBG_CFGLPSPD,
phy_val & ~L1C_CFGLPSPD_RSTCNT_CLK125SW);
}
/* fix tx-half-amp issue */
if (hw->pci_devid == L2CB_DEV_ID || hw->pci_devid == L2CB2_DEV_ID) {
l1c_read_phydbg(hw, true, L1C_MIIDBG_CABLE1TH_DET, &phy_val);
l1c_write_phydbg(hw, true, L1C_MIIDBG_CABLE1TH_DET,
phy_val | L1C_CABLE1TH_DET_EN); /* set bit15 */
}
if (pws_en) {
/* clear bit[3] of debugport 3B to 0,
* lower voltage to save power */
if (hw->pci_devid == L2CB_DEV_ID ||
hw->pci_devid == L2CB2_DEV_ID) {
l1c_read_phydbg(hw, true, L1C_MIIDBG_VOLT_CTRL,
&phy_val);
l1c_write_phydbg(hw, true, L1C_MIIDBG_VOLT_CTRL,
phy_val & ~L1C_VOLT_CTRL_SWLOWEST);
}
/* power saving config */
l1c_write_phydbg(hw, true, L1C_MIIDBG_LEGCYPS,
(hw->pci_devid == L1D_DEV_ID ||
hw->pci_devid == L1D2_DEV_ID) ?
L1D_LEGCYPS_DEF : L1C_LEGCYPS_DEF_MPQ);
/* hib */
l1c_write_phydbg(hw, true, L1C_MIIDBG_SYSMODCTRL,
L1C_SYSMODCTRL_IECHOADJ_DEF);
} else {
/*dis powersaving */
l1c_read_phydbg(hw, true, L1C_MIIDBG_LEGCYPS, &phy_val);
l1c_write_phydbg(hw, true, L1C_MIIDBG_LEGCYPS,
phy_val & ~L1C_LEGCYPS_EN);
/* disable hibernate */
l1c_read_phydbg(hw, true, L1C_MIIDBG_HIBNEG, &phy_val);
l1c_write_phydbg(hw, true, L1C_MIIDBG_HIBNEG,
phy_val & ~L1C_HIBNEG_PSHIB_EN);
}
/* az is only for l2cbv2 / l1dv1 /l1dv2 */
if (hw->pci_devid == L1D_DEV_ID ||
hw->pci_devid == L1D2_DEV_ID ||
hw->pci_devid == L2CB2_DEV_ID) {
if (az_en) {
switch (hw->pci_devid) {
case L2CB2_DEV_ID:
alx_mem_w32(hw, L1C_LPI_DECISN_TIMER,
L1C_LPI_DESISN_TIMER_L2CB);
/* az enable 100M */
l1c_write_phy(hw, true, L1C_MIIEXT_ANEG, true,
L1C_MIIEXT_LOCAL_EEEADV,
L1C_LOCAL_EEEADV_100BT);
/* az long wake threshold */
l1c_write_phy(hw, true, L1C_MIIEXT_PCS, true,
L1C_MIIEXT_AZCTRL5,
L1C_AZCTRL5_WAKE_LTH_L2CB);
/* az short wake threshold */
l1c_write_phy(hw, true, L1C_MIIEXT_PCS, true,
L1C_MIIEXT_AZCTRL4,
L1C_AZCTRL4_WAKE_STH_L2CB);
l1c_write_phy(hw, true, L1C_MIIEXT_PCS, true,
L1C_MIIEXT_CLDCTRL3,
L1C_CLDCTRL3_L2CB);
/* bit7 set to 0, otherwise ping fail */
l1c_write_phy(hw, true, L1C_MIIEXT_PCS, true,
L1C_MIIEXT_CLDCTRL7,
L1C_CLDCTRL7_L2CB);
l1c_write_phy(hw, true, L1C_MIIEXT_PCS, true,
L1C_MIIEXT_AZCTRL2,
L1C_AZCTRL2_L2CB);
break;
case L1D_DEV_ID:
l1c_write_phydbg(hw, true,
L1C_MIIDBG_AZ_ANADECT, L1C_AZ_ANADECT_DEF);
phy_val = hw->long_cable ? L1C_CLDCTRL3_L1D :
(L1C_CLDCTRL3_L1D &
~(L1C_CLDCTRL3_BP_CABLE1TH_DET_GT |
L1C_CLDCTRL3_AZ_DISAMP));
l1c_write_phy(hw, true, L1C_MIIEXT_PCS, true,
L1C_MIIEXT_CLDCTRL3, phy_val);
l1c_write_phy(hw, true, L1C_MIIEXT_PCS, true,
L1C_MIIEXT_AZCTRL,
L1C_AZCTRL_L1D);
l1c_write_phy(hw, true, L1C_MIIEXT_PCS, true,
L1C_MIIEXT_AZCTRL2,
L1C_AZCTRL2_L2CB);
break;
case L1D2_DEV_ID:
l1c_write_phydbg(hw, true,
L1C_MIIDBG_AZ_ANADECT,
L1C_AZ_ANADECT_DEF);
phy_val = hw->long_cable ? L1C_CLDCTRL3_L1D :
(L1C_CLDCTRL3_L1D &
~L1C_CLDCTRL3_BP_CABLE1TH_DET_GT);
l1c_write_phy(hw, true, L1C_MIIEXT_PCS, true,
L1C_MIIEXT_CLDCTRL3, phy_val);
l1c_write_phy(hw, true, L1C_MIIEXT_PCS, true,
L1C_MIIEXT_AZCTRL,
L1C_AZCTRL_L1D);
l1c_write_phy(hw, true, L1C_MIIEXT_PCS, true,
L1C_MIIEXT_AZCTRL2,
L1C_AZCTRL2_L1D2);
l1c_write_phy(hw, true, L1C_MIIEXT_PCS, true,
L1C_MIIEXT_AZCTRL6,
L1C_AZCTRL6_L1D2);
break;
}
} else {
alx_mem_r32(hw, L1C_LPI_CTRL, &val);
alx_mem_w32(hw, L1C_LPI_CTRL, val & ~L1C_LPI_CTRL_EN);
l1c_write_phy(hw, true, L1C_MIIEXT_ANEG, true,
L1C_MIIEXT_LOCAL_EEEADV, 0);
l1c_write_phy(hw, true, L1C_MIIEXT_PCS, true,
L1C_MIIEXT_CLDCTRL3, L1C_CLDCTRL3_L2CB);
}
}
/* other debug port need to set */
l1c_write_phydbg(hw, true, L1C_MIIDBG_ANACTRL, L1C_ANACTRL_DEF);
l1c_write_phydbg(hw, true, L1C_MIIDBG_SRDSYSMOD, L1C_SRDSYSMOD_DEF);
l1c_write_phydbg(hw, true, L1C_MIIDBG_TST10BTCFG, L1C_TST10BTCFG_DEF);
/* L1c, L2c, L1d, L2cb link fail inhibit
timer issue of L1c UNH-IOL test fail, set bit7*/
l1c_write_phydbg(hw, true, L1C_MIIDBG_TST100BTCFG,
L1C_TST100BTCFG_DEF | L1C_TST100BTCFG_LITCH_EN);
/* set phy interrupt mask */
l1c_write_phy(hw, false, 0, true,
L1C_MII_IER, L1C_IER_LINK_UP | L1C_IER_LINK_DOWN);
return 0;
}
/* reset pcie
* just reset pcie relative registers (pci command, clk, aspm...)
* return
* 0:success
* non-0:fail
*/
u16 l1c_reset_pcie(struct alx_hw *hw, bool l0s_en, bool l1_en)
{
u32 val;
u16 val16;
u16 ret;
/* Workaround for PCI problem when BIOS sets MMRBC incorrectly. */
alx_cfg_r16(hw, PCI_COMMAND, &val16);
if ((val16 & (PCI_COMMAND_IO |
PCI_COMMAND_MEMORY |
PCI_COMMAND_MASTER)) == 0 ||
(val16 & PCI_COMMAND_INTX_DISABLE) != 0) {
val16 = (u16)((val16 | (PCI_COMMAND_IO |
PCI_COMMAND_MEMORY |
PCI_COMMAND_MASTER))
& ~PCI_COMMAND_INTX_DISABLE);
alx_cfg_w16(hw, PCI_COMMAND, val16);
}
/* Clear any PowerSaving Settings */
alx_cfg_w16(hw, L1C_PM_CSR, 0);
/* close write attr for some registes */
alx_mem_r32(hw, L1C_LTSSM_CTRL, &val);
alx_mem_w32(hw, L1C_LTSSM_CTRL, val & ~L1C_LTSSM_WRO_EN);
/* mask some pcie error bits */
alx_mem_r32(hw, L1C_UE_SVRT, &val);
val &= ~(L1C_UE_SVRT_DLPROTERR | L1C_UE_SVRT_FCPROTERR);
alx_mem_w32(hw, L1C_UE_SVRT, val);
/* pclk */
alx_mem_r32(hw, L1C_MASTER, &val);
val &= ~L1C_MASTER_PCLKSEL_SRDS;
alx_mem_w32(hw, L1C_MASTER, val);
/* Set 1000 bit 2, only used for L1c/L2c , WOL usage */
if (hw->pci_devid == L1C_DEV_ID || hw->pci_devid == L2C_DEV_ID) {
alx_mem_r32(hw, L1C_PPHY_MISC1, &val);
alx_mem_w32(hw, L1C_PPHY_MISC1, val | L1C_PPHY_MISC1_RCVDET);
} else { /* other device should set bit 5 of reg1400 for WOL */
if ((val & L1C_MASTER_WAKEN_25M) == 0)
alx_mem_w32(hw, L1C_MASTER, val | L1C_MASTER_WAKEN_25M);
}
/* l2cb 1.0*/
if (hw->pci_devid == L2CB_DEV_ID && hw->pci_revid == L2CB_V10) {
alx_mem_r32(hw, L1C_PPHY_MISC2, &val);
FIELD_SETL(val, L1C_PPHY_MISC2_L0S_TH,
L1C_PPHY_MISC2_L0S_TH_L2CB1);
FIELD_SETL(val, L1C_PPHY_MISC2_CDR_BW,
L1C_PPHY_MISC2_CDR_BW_L2CB1);
alx_mem_w32(hw, L1C_PPHY_MISC2, val);
/* extend L1 sync timer, this will use more power,
* only for L2cb v1.0*/
if (!hw->aps_en) {
alx_mem_r32(hw, L1C_LNK_CTRL, &val);
alx_mem_w32(hw, L1C_LNK_CTRL,
val | L1C_LNK_CTRL_EXTSYNC);
}
}
/* l2cbv1.x & l1dv1.x */
if (hw->pci_devid == L2CB_DEV_ID || hw->pci_devid == L1D_DEV_ID) {
alx_mem_r32(hw, L1C_PMCTRL, &val);
alx_mem_w32(hw, L1C_PMCTRL, val | L1C_PMCTRL_L0S_BUFSRX_EN);
/* clear vendor message for L1d & L2cb */
alx_mem_r32(hw, L1C_DMA_DBG, &val);
alx_mem_w32(hw, L1C_DMA_DBG, val & ~L1C_DMA_DBG_VENDOR_MSG);
}
/* hi-tx-perf */
if (hw->hi_txperf) {
alx_mem_r32(hw, L1C_PPHY_MISC1, &val);
FIELD_SETL(val, L1C_PPHY_MISC1_NFTS,
L1C_PPHY_MISC1_NFTS_HIPERF);
alx_mem_w32(hw, L1C_PPHY_MISC1, val);
}
/* l0s, l1 setting */
ret = l1c_enable_aspm(hw, l0s_en, l1_en, 0);
udelay(10);
return ret;
}
/* disable/enable MAC/RXQ/TXQ
* en
* true:enable
* false:disable
* return
* 0:success
* non-0-fail
*/
u16 l1c_enable_mac(struct alx_hw *hw, bool en, u16 en_ctrl)
{
u32 rxq, txq, mac, val;
u16 i;
alx_mem_r32(hw, L1C_RXQ0, &rxq);
alx_mem_r32(hw, L1C_TXQ0, &txq);
alx_mem_r32(hw, L1C_MAC_CTRL, &mac);
if (en) { /* enable */
alx_mem_w32(hw, L1C_RXQ0, rxq | L1C_RXQ0_EN);
alx_mem_w32(hw, L1C_TXQ0, txq | L1C_TXQ0_EN);
if ((en_ctrl & LX_MACSPEED_1000) != 0) {
FIELD_SETL(mac, L1C_MAC_CTRL_SPEED,
L1C_MAC_CTRL_SPEED_1000);
} else {
FIELD_SETL(mac, L1C_MAC_CTRL_SPEED,
L1C_MAC_CTRL_SPEED_10_100);
}
test_set_or_clear(mac, en_ctrl, LX_MACDUPLEX_FULL,
L1C_MAC_CTRL_FULLD);
/* rx filter */
test_set_or_clear(mac, en_ctrl, LX_FLT_PROMISC,
L1C_MAC_CTRL_PROMISC_EN);
test_set_or_clear(mac, en_ctrl, LX_FLT_MULTI_ALL,
L1C_MAC_CTRL_MULTIALL_EN);
test_set_or_clear(mac, en_ctrl, LX_FLT_BROADCAST,
L1C_MAC_CTRL_BRD_EN);
test_set_or_clear(mac, en_ctrl, LX_FLT_DIRECT,
L1C_MAC_CTRL_RX_EN);
test_set_or_clear(mac, en_ctrl, LX_FC_TXEN,
L1C_MAC_CTRL_TXFC_EN);
test_set_or_clear(mac, en_ctrl, LX_FC_RXEN,
L1C_MAC_CTRL_RXFC_EN);
test_set_or_clear(mac, en_ctrl, LX_VLAN_STRIP,
L1C_MAC_CTRL_VLANSTRIP);
test_set_or_clear(mac, en_ctrl, LX_LOOPBACK,
L1C_MAC_CTRL_LPBACK_EN);
test_set_or_clear(mac, en_ctrl, LX_SINGLE_PAUSE,
L1C_MAC_CTRL_SPAUSE_EN);
test_set_or_clear(mac, en_ctrl, LX_ADD_FCS,
(L1C_MAC_CTRL_PCRCE | L1C_MAC_CTRL_CRCE));
alx_mem_w32(hw, L1C_MAC_CTRL, mac | L1C_MAC_CTRL_TX_EN);
} else { /* disable mac */
alx_mem_w32(hw, L1C_RXQ0, rxq & ~L1C_RXQ0_EN);
alx_mem_w32(hw, L1C_TXQ0, txq & ~L1C_TXQ0_EN);
/* waiting for rxq/txq be idle */
for (i = 0; i < L1C_DMA_MAC_RST_TO; i++) {/* wait atmost 1ms */
alx_mem_r32(hw, L1C_MAC_STS, &val);
if ((val & (L1C_MAC_STS_TXQ_BUSY |
L1C_MAC_STS_RXQ_BUSY)) == 0) {
break;
}
#ifdef ALX_LINK_DOWN_CONFIG
mdelay(20);
#else
udelay(20);
#endif
}
if (L1C_DMA_MAC_RST_TO == i)
return LX_ERR_RSTMAC;
/* stop mac tx/rx */
alx_mem_w32(hw, L1C_MAC_CTRL,
mac & ~(L1C_MAC_CTRL_RX_EN | L1C_MAC_CTRL_TX_EN));
for (i = 0; i < L1C_DMA_MAC_RST_TO; i++) {
alx_mem_r32(hw, L1C_MAC_STS, &val);
if ((val & L1C_MAC_STS_IDLE) == 0)
break;
#ifdef ALX_LINK_DOWN_CONFIG
mdelay(20);
#else
udelay(10);
#endif
}
if (L1C_DMA_MAC_RST_TO == i)
return LX_ERR_RSTMAC;
}
return 0;
}
/* enable/disable aspm support
* that will change settings for phy/mac/pcie
*/
u16 l1c_enable_aspm(struct alx_hw *hw, bool l0s_en, bool l1_en, u8 lnk_stat)
{
u32 pmctrl;
bool linkon;
linkon = (lnk_stat == LX_LC_10H || lnk_stat == LX_LC_10F ||
lnk_stat == LX_LC_100H || lnk_stat == LX_LC_100F ||
lnk_stat == LX_LC_1000F) ? true : false;
alx_mem_r32(hw, L1C_PMCTRL, &pmctrl);
pmctrl &= ~(L1C_PMCTRL_L0S_EN |
L1C_PMCTRL_L1_EN |
L1C_PMCTRL_ASPM_FCEN);
FIELD_SETL(pmctrl, L1C_PMCTRL_LCKDET_TIMER,
L1C_PMCTRL_LCKDET_TIMER_DEF);
/* l1 timer */
if (hw->pci_devid == L2CB2_DEV_ID || hw->pci_devid == L1D2_DEV_ID) {
pmctrl &= ~L1D_PMCTRL_TXL1_AFTER_L0S;
FIELD_SETL(pmctrl, L1D_PMCTRL_L1_TIMER,
(lnk_stat == LX_LC_100H ||
lnk_stat == LX_LC_100F ||
lnk_stat == LX_LC_1000F) ?
L1D_PMCTRL_L1_TIMER_16US : 1);
} else {
FIELD_SETL(pmctrl, L1C_PMCTRL_L1_TIMER,
(lnk_stat == LX_LC_100H ||
lnk_stat == LX_LC_100F ||
lnk_stat == LX_LC_1000F) ?
((hw->pci_devid == L2CB_DEV_ID) ?
L1C_PMCTRL_L1_TIMER_L2CB1 : L1C_PMCTRL_L1_TIMER_DEF
) : 1);
}
if (l0s_en) { /* on/off l0s only if bios/system enable l0s */
pmctrl |= (L1C_PMCTRL_L0S_EN | L1C_PMCTRL_ASPM_FCEN);
}
if (l1_en) { /* on/off l1 only if bios/system enable l1 */
pmctrl |= (L1C_PMCTRL_L1_EN | L1C_PMCTRL_ASPM_FCEN);
}
if (hw->pci_devid == L2CB_DEV_ID || hw->pci_devid == L1D_DEV_ID ||
hw->pci_devid == L2CB2_DEV_ID || hw->pci_devid == L1D2_DEV_ID) {
/* If the pm_request_l1 time exceeds the value of this timer,
it will enter L0s instead of L1 for this ASPM request.*/
FIELD_SETL(pmctrl, L1C_PMCTRL_L1REQ_TO,
L1C_PMCTRL_L1REG_TO_DEF);
pmctrl |= L1C_PMCTRL_RCVR_WT_1US | /* wait 1us not 2ms */
L1C_PMCTRL_L1_SRDSRX_PWD | /* pwd serdes */
L1C_PMCTRL_L1_CLKSW_EN;
pmctrl &= ~(L1C_PMCTRL_L1_SRDS_EN |
L1C_PMCTRL_L1_SRDSPLL_EN|
L1C_PMCTRL_L1_BUFSRX_EN |
L1C_PMCTRL_SADLY_EN |
L1C_PMCTRL_HOTRST_WTEN);
/* disable l0s if linkdown or l2cbv1.x */
if (!linkon ||
(!hw->aps_en && hw->pci_devid == L2CB_DEV_ID)) {
pmctrl &= ~L1C_PMCTRL_L0S_EN;
}
} else { /* l1c */
FIELD_SETL(pmctrl, L1C_PMCTRL_L1_TIMER, 0);
if (linkon) {
pmctrl |= L1C_PMCTRL_L1_SRDS_EN |
L1C_PMCTRL_L1_SRDSPLL_EN |
L1C_PMCTRL_L1_BUFSRX_EN;
pmctrl &= ~(L1C_PMCTRL_L1_SRDSRX_PWD|
L1C_PMCTRL_L1_CLKSW_EN |
L1C_PMCTRL_L0S_EN |
L1C_PMCTRL_L1_EN);
} else {
pmctrl |= L1C_PMCTRL_L1_CLKSW_EN;
pmctrl &= ~(L1C_PMCTRL_L1_SRDS_EN |
L1C_PMCTRL_L1_SRDSPLL_EN|
L1C_PMCTRL_L1_BUFSRX_EN |
L1C_PMCTRL_L0S_EN);
}
}
alx_mem_w32(hw, L1C_PMCTRL, pmctrl);
return 0;
}
/* initialize phy for speed / flow control
* lnk_cap
* if autoNeg, is link capability to tell the peer
* if force mode, is forced speed/duplex
*/
u16 l1c_init_phy_spdfc(struct alx_hw *hw, bool auto_neg,
u8 lnk_cap, bool fc_en)
{
u16 adv, giga, cr;
u32 val;
u16 ret;
/* clear flag */
l1c_write_phy(hw, false, 0, false, L1C_MII_DBG_ADDR, 0);
alx_mem_r32(hw, L1C_DRV, &val);
FIELD_SETL(val, LX_DRV_PHY, 0);
if (auto_neg) {
adv = L1C_ADVERTISE_DEFAULT_CAP & ~L1C_ADVERTISE_SPEED_MASK;
giga = L1C_GIGA_CR_1000T_DEFAULT_CAP &
~L1C_GIGA_CR_1000T_SPEED_MASK;
val |= LX_DRV_PHY_AUTO;
if (!fc_en)
adv &= ~(ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM);
else
val |= LX_DRV_PHY_FC;
if ((LX_LC_10H & lnk_cap) != 0) {
adv |= ADVERTISE_10HALF;
val |= LX_DRV_PHY_10;
}
if ((LX_LC_10F & lnk_cap) != 0) {
adv |= ADVERTISE_10HALF |
ADVERTISE_10FULL;
val |= LX_DRV_PHY_10 | LX_DRV_PHY_DUPLEX;
}
if ((LX_LC_100H & lnk_cap) != 0) {
adv |= ADVERTISE_100HALF;
val |= LX_DRV_PHY_100;
}
if ((LX_LC_100F & lnk_cap) != 0) {
adv |= ADVERTISE_100HALF |
ADVERTISE_100FULL;
val |= LX_DRV_PHY_100 | LX_DRV_PHY_DUPLEX;
}
if ((LX_LC_1000F & lnk_cap) != 0) {
giga |= L1C_GIGA_CR_1000T_FD_CAPS;
val |= LX_DRV_PHY_1000 | LX_DRV_PHY_DUPLEX;
}
ret = l1c_write_phy(hw, false, 0, false, MII_ADVERTISE, adv);
ret = l1c_write_phy(hw, false, 0, false, MII_CTRL1000, giga);
cr = BMCR_RESET | BMCR_ANENABLE | BMCR_ANRESTART;
ret = l1c_write_phy(hw, false, 0, false, MII_BMCR, cr);
} else { /* force mode */
cr = BMCR_RESET;
switch (lnk_cap) {
case LX_LC_10H:
val |= LX_DRV_PHY_10;
break;
case LX_LC_10F:
cr |= BMCR_FULLDPLX;
val |= LX_DRV_PHY_10 | LX_DRV_PHY_DUPLEX;
break;
case LX_LC_100H:
cr |= BMCR_SPEED100;
val |= LX_DRV_PHY_100;
break;
case LX_LC_100F:
cr |= BMCR_SPEED100 | BMCR_FULLDPLX;
val |= LX_DRV_PHY_100 | LX_DRV_PHY_DUPLEX;
break;
default:
return LX_ERR_PARM;
}
ret = l1c_write_phy(hw, false, 0, false, MII_BMCR, cr);
}
if (!ret) {
l1c_write_phy(hw, false, 0, false, L1C_MII_DBG_ADDR,
LX_PHY_INITED);
}
alx_mem_w32(hw, L1C_DRV, val);
return ret;
}
/* 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.
*/
u16 l1c_powersaving(struct alx_hw *hw, u8 wire_spd, bool wol_en,
bool mac_txen, bool mac_rxen, bool pws_en)
{
u32 master_ctrl, mac_ctrl, phy_ctrl;
u16 pm_ctrl, ret = 0;
master_ctrl = 0;
mac_ctrl = 0;
phy_ctrl = 0;
pws_en = pws_en;
alx_mem_r32(hw, L1C_MASTER, &master_ctrl);
master_ctrl &= ~L1C_MASTER_PCLKSEL_SRDS;
alx_mem_r32(hw, L1C_MAC_CTRL, &mac_ctrl);
/* 10/100 half */
FIELD_SETL(mac_ctrl, L1C_MAC_CTRL_SPEED, L1C_MAC_CTRL_SPEED_10_100);
mac_ctrl &= ~(L1C_MAC_CTRL_FULLD |
L1C_MAC_CTRL_RX_EN |
L1C_MAC_CTRL_TX_EN);
alx_mem_r32(hw, L1C_PHY_CTRL, &phy_ctrl);
phy_ctrl &= ~(L1C_PHY_CTRL_DSPRST_OUT | L1C_PHY_CTRL_CLS);
/* if (pws_en) */
phy_ctrl |= (L1C_PHY_CTRL_RST_ANALOG | L1C_PHY_CTRL_HIB_PULSE |
L1C_PHY_CTRL_HIB_EN);
if (wol_en) { /* enable rx packet or tx packet */
if (mac_rxen)
mac_ctrl |= (L1C_MAC_CTRL_RX_EN | L1C_MAC_CTRL_BRD_EN);
if (mac_txen)
mac_ctrl |= L1C_MAC_CTRL_TX_EN;
if (LX_LC_1000F == wire_spd) {
FIELD_SETL(mac_ctrl, L1C_MAC_CTRL_SPEED,
L1C_MAC_CTRL_SPEED_1000);
}
if (LX_LC_10F == wire_spd || LX_LC_100F == wire_spd ||
LX_LC_100F == wire_spd) {
mac_ctrl |= L1C_MAC_CTRL_FULLD;
}
phy_ctrl |= L1C_PHY_CTRL_DSPRST_OUT;
ret = l1c_write_phy(hw, false, 0, false,
L1C_MII_IER, L1C_IER_LINK_UP);
} else {
master_ctrl |= L1C_MASTER_PCLKSEL_SRDS;
ret = l1c_write_phy(hw, false, 0, false, L1C_MII_IER, 0);
phy_ctrl |= (L1C_PHY_CTRL_IDDQ | L1C_PHY_CTRL_POWER_DOWN);
}
alx_mem_w32(hw, L1C_MASTER, master_ctrl);
alx_mem_w32(hw, L1C_MAC_CTRL, mac_ctrl);
alx_mem_w32(hw, L1C_PHY_CTRL, phy_ctrl);
/* set PME_EN ?? */
if (wol_en) {
alx_cfg_r16(hw, L1C_PM_CSR, &pm_ctrl);
pm_ctrl |= L1C_PM_CSR_PME_EN;
alx_cfg_w16(hw, L1C_PM_CSR, pm_ctrl);
}
return ret;
}
/* read phy register */
u16 l1c_read_phy(struct alx_hw *hw, bool ext, u8 dev, bool fast,
u16 reg, u16 *data)
{
u32 val;
u16 clk_sel, i, ret = 0;
*data = 0;
clk_sel = fast ?
(u16)L1C_MDIO_CLK_SEL_25MD4 : (u16)L1C_MDIO_CLK_SEL_25MD128;
if (ext) {
val = FIELDL(L1C_MDIO_EXTN_DEVAD, dev) |
FIELDL(L1C_MDIO_EXTN_REG, reg);
alx_mem_w32(hw, L1C_MDIO_EXTN, val);
val = L1C_MDIO_SPRES_PRMBL |
FIELDL(L1C_MDIO_CLK_SEL, clk_sel) |
L1C_MDIO_START |
L1C_MDIO_MODE_EXT |
L1C_MDIO_OP_READ;
} else {
val = L1C_MDIO_SPRES_PRMBL |
FIELDL(L1C_MDIO_CLK_SEL, clk_sel) |
FIELDL(L1C_MDIO_REG, reg) |
L1C_MDIO_START |
L1C_MDIO_OP_READ;
}
alx_mem_w32(hw, L1C_MDIO, val);
for (i = 0; i < L1C_MDIO_MAX_AC_TO; i++) {
alx_mem_r32(hw, L1C_MDIO, &val);
if ((val & L1C_MDIO_BUSY) == 0) {
*data = (u16)FIELD_GETX(val, L1C_MDIO_DATA);
break;
}
udelay(10);
}
if (L1C_MDIO_MAX_AC_TO == i)
ret = LX_ERR_MIIBUSY;
return ret;
}
/* write phy register */
u16 l1c_write_phy(struct alx_hw *hw, bool ext, u8 dev, bool fast,
u16 reg, u16 data)
{
u32 val;
u16 clk_sel, i, ret = 0;
clk_sel = fast ?
(u16)L1C_MDIO_CLK_SEL_25MD4 : (u16)L1C_MDIO_CLK_SEL_25MD128;
if (ext) {
val = FIELDL(L1C_MDIO_EXTN_DEVAD, dev) |
FIELDL(L1C_MDIO_EXTN_REG, reg);
alx_mem_w32(hw, L1C_MDIO_EXTN, val);
val = L1C_MDIO_SPRES_PRMBL |
FIELDL(L1C_MDIO_CLK_SEL, clk_sel) |
FIELDL(L1C_MDIO_DATA, data) |
L1C_MDIO_START |
L1C_MDIO_MODE_EXT;
} else {
val = L1C_MDIO_SPRES_PRMBL |
FIELDL(L1C_MDIO_CLK_SEL, clk_sel) |
FIELDL(L1C_MDIO_REG, reg) |
FIELDL(L1C_MDIO_DATA, data) |
L1C_MDIO_START;
}
alx_mem_w32(hw, L1C_MDIO, val);
for (i = 0; i < L1C_MDIO_MAX_AC_TO; i++) {
alx_mem_r32(hw, L1C_MDIO, &val);
if ((val & L1C_MDIO_BUSY) == 0)
break;
udelay(10);
}
if (L1C_MDIO_MAX_AC_TO == i)
ret = LX_ERR_MIIBUSY;
return ret;
}
u16 l1c_read_phydbg(struct alx_hw *hw, bool fast, u16 reg, u16 *data)
{
u16 ret;
ret = l1c_write_phy(hw, false, 0, fast, L1C_MII_DBG_ADDR, reg);
ret = l1c_read_phy(hw, false, 0, fast, L1C_MII_DBG_DATA, data);
return ret;
}
u16 l1c_write_phydbg(struct alx_hw *hw, bool fast, u16 reg, u16 data)
{
u16 ret;
ret = l1c_write_phy(hw, false, 0, fast, L1C_MII_DBG_ADDR, reg);
ret = l1c_write_phy(hw, false, 0, fast, L1C_MII_DBG_DATA, data);
return ret;
}
/*
* initialize mac basically
* most of hi-feature no init
* MAC/PHY should be reset before call this function
* smb_timer : million-second
* int_mod : micro-second
* disable RSS as default
*/
u16 l1c_init_mac(struct alx_hw *hw, u8 *addr, u32 txmem_hi,
u32 *tx_mem_lo, u8 tx_qnum, u16 txring_sz,
u32 rxmem_hi, u32 rfdmem_lo, u32 rrdmem_lo,
u16 rxring_sz, u16 rxbuf_sz, u16 smb_timer,
u16 mtu, u16 int_mod, bool hash_legacy)
{
u32 val;
u16 val16;
u8 dmar_len;
/* set mac-address */
val = *(u32 *)(addr + 2);
alx_mem_w32(hw, L1C_STAD0, LX_SWAP_DW(val));
val = *(u16 *)addr ;
alx_mem_w32(hw, L1C_STAD1, LX_SWAP_W((u16)val));
/* clear multicast hash table, algrithm */
alx_mem_w32(hw, L1C_HASH_TBL0, 0);
alx_mem_w32(hw, L1C_HASH_TBL1, 0);
alx_mem_r32(hw, L1C_MAC_CTRL, &val);
if (hash_legacy)
val |= L1C_MAC_CTRL_MHASH_ALG_HI5B;
else
val &= ~L1C_MAC_CTRL_MHASH_ALG_HI5B;
alx_mem_w32(hw, L1C_MAC_CTRL, val);
/* clear any wol setting/status */
alx_mem_r32(hw, L1C_WOL0, &val);
alx_mem_w32(hw, L1C_WOL0, 0);
/* clk gating */
alx_mem_w32(hw, L1C_CLK_GATE, (hw->pci_devid == L1D_DEV_ID) ? 0 :
(L1C_CLK_GATE_DMAR | L1C_CLK_GATE_DMAW |
L1C_CLK_GATE_TXQ | L1C_CLK_GATE_RXQ |
L1C_CLK_GATE_TXMAC));
/* descriptor ring base memory */
alx_mem_w32(hw, L1C_TX_BASE_ADDR_HI, txmem_hi);
alx_mem_w32(hw, L1C_TPD_RING_SZ, txring_sz);
switch (tx_qnum) {
case 2:
alx_mem_w32(hw, L1C_TPD_PRI1_ADDR_LO, tx_mem_lo[1]);
/* fall through */
case 1:
alx_mem_w32(hw, L1C_TPD_PRI0_ADDR_LO, tx_mem_lo[0]);
break;
default:
return LX_ERR_PARM;
}
alx_mem_w32(hw, L1C_RX_BASE_ADDR_HI, rxmem_hi);
alx_mem_w32(hw, L1C_RFD_ADDR_LO, rfdmem_lo);
alx_mem_w32(hw, L1C_RRD_ADDR_LO, rrdmem_lo);
alx_mem_w32(hw, L1C_RFD_BUF_SZ, rxbuf_sz);
alx_mem_w32(hw, L1C_RRD_RING_SZ, rxring_sz);
alx_mem_w32(hw, L1C_RFD_RING_SZ, rxring_sz);
alx_mem_w32(hw, L1C_SMB_TIMER, smb_timer * 500UL);
if (hw->pci_devid == L2CB_DEV_ID) {
/* revise SRAM configuration */
alx_mem_w32(hw, L1C_SRAM5, L1C_SRAM_RXF_LEN_L2CB1);
alx_mem_w32(hw, L1C_SRAM7, L1C_SRAM_TXF_LEN_L2CB1);
alx_mem_w32(hw, L1C_SRAM4, L1C_SRAM_RXF_HT_L2CB1);
alx_mem_w32(hw, L1C_SRAM0, L1C_SRAM_RFD_HT_L2CB1);
alx_mem_w32(hw, L1C_SRAM6, L1C_SRAM_TXF_HT_L2CB1);
alx_mem_w32(hw, L1C_SRAM2, L1C_SRAM_TRD_HT_L2CB1);
alx_mem_w32(hw, L1C_TXQ2, 0); /* TX watermark, goto L1 state.*/
alx_mem_w32(hw, L1C_RXQ3, 0); /* RXD threshold. */
}
alx_mem_w32(hw, L1C_SRAM9, L1C_SRAM_LOAD_PTR);
/* int moduration */
alx_mem_r32(hw, L1C_MASTER, &val);
val |= L1C_MASTER_IRQMOD2_EN | L1C_MASTER_IRQMOD1_EN |
L1C_MASTER_SYSALVTIMER_EN; /* sysalive */
alx_mem_w32(hw, L1C_MASTER, val);
/* set Interrupt Moderator Timer (max interrupt per sec)
* we use seperate time for rx/tx */
alx_mem_w32(hw, L1C_IRQ_MODU_TIMER,
FIELDL(L1C_IRQ_MODU_TIMER1, int_mod) |
FIELDL(L1C_IRQ_MODU_TIMER2, int_mod >> 1));
/* tpd threshold to trig int */
alx_mem_w32(hw, L1C_TINT_TPD_THRSHLD, (u32)txring_sz / 3);
alx_mem_w32(hw, L1C_TINT_TIMER, int_mod * 2);
/* re-send int */
alx_mem_w32(hw, L1C_INT_RETRIG, L1C_INT_RETRIG_TO);
/* mtu */
alx_mem_w32(hw, L1C_MTU, (u32)(mtu + 4 + 4)); /* crc + vlan */
/* txq */
if ((mtu + 8) < L1C_TXQ1_JUMBO_TSO_TH)
val = (u32)(mtu + 8 + 7); /* 7 for QWORD align */
else
val = L1C_TXQ1_JUMBO_TSO_TH;
alx_mem_w32(hw, L1C_TXQ1, val >> 3);
alx_mem_r32(hw, L1C_DEV_CTRL, &val);
dmar_len = (u8)FIELD_GETX(val, L1C_DEV_CTRL_MAXRRS);
/* if BIOS had changed the default dma read max length,
* restore it to default value */
if (dmar_len < L1C_DEV_CTRL_MAXRRS_MIN) {
FIELD_SETL(val, L1C_DEV_CTRL_MAXRRS, L1C_DEV_CTRL_MAXRRS_MIN);
alx_mem_w32(hw, L1C_DEV_CTRL, val);
dmar_len = L1C_DEV_CTRL_MAXRRS_MIN;
}
val = FIELDL(L1C_TXQ0_TPD_BURSTPREF, L1C_TXQ0_TPD_BURSTPREF_DEF) |
L1C_TXQ0_MODE_ENHANCE |
L1C_TXQ0_LSO_8023_EN |
L1C_TXQ0_SUPT_IPOPT |
FIELDL(L1C_TXQ0_TXF_BURST_PREF,
(hw->pci_devid == L2CB_DEV_ID ||
hw->pci_devid == L2CB2_DEV_ID) ?
L1C_TXQ0_TXF_BURST_PREF_L2CB :
L1C_TXQ0_TXF_BURST_PREF_DEF);
alx_mem_w32(hw, L1C_TXQ0, val);
/* fc */
alx_mem_r32(hw, L1C_SRAM5, &val);
val = FIELD_GETX(val, L1C_SRAM_RXF_LEN) << 3; /* bytes */
if (val > L1C_SRAM_RXF_LEN_8K) {
val16 = L1C_MTU_STD_ALGN;
val = (val - (2 * L1C_MTU_STD_ALGN + L1C_MTU_MIN));
} else {
val16 = L1C_MTU_STD_ALGN;
val = (val - L1C_MTU_STD_ALGN);
}
alx_mem_w32(hw, L1C_RXQ2,
FIELDL(L1C_RXQ2_RXF_XOFF_THRESH, val16 >> 3) |
FIELDL(L1C_RXQ2_RXF_XON_THRESH, val >> 3));
/* rxq */
val = FIELDL(L1C_RXQ0_NUM_RFD_PREF, L1C_RXQ0_NUM_RFD_PREF_DEF) |
L1C_RXQ0_IPV6_PARSE_EN;
if ((hw->pci_devid & 1) != 0) {
FIELD_SETL(val, L1C_RXQ0_ASPM_THRESH,
(hw->pci_devid == L1D2_DEV_ID) ?
L1C_RXQ0_ASPM_THRESH_NO :
L1C_RXQ0_ASPM_THRESH_100M);
}
alx_mem_w32(hw, L1C_RXQ0, val);
/* rfd producer index */
alx_mem_w32(hw, L1C_RFD_PIDX, (u32)rxring_sz - 1);
/* DMA */
val = FIELDL(L1C_DMA_RORDER_MODE, L1C_DMA_RORDER_MODE_OUT) |
L1C_DMA_RREQ_PRI_DATA |
FIELDL(L1C_DMA_RREQ_BLEN, dmar_len) |
FIELDL(L1C_DMA_WDLY_CNT, L1C_DMA_WDLY_CNT_DEF) |
FIELDL(L1C_DMA_RDLY_CNT, L1C_DMA_RDLY_CNT_DEF) ;
alx_mem_w32(hw, L1C_DMA, val);
return 0;
}
u16 l1c_get_phy_config(struct alx_hw *hw)
{
u32 val;
u16 phy_val;
alx_mem_r32(hw, L1C_PHY_CTRL, &val);
if ((val & L1C_PHY_CTRL_DSPRST_OUT) == 0) { /* phy in rst */
return LX_DRV_PHY_UNKNOWN;
}
alx_mem_r32(hw, L1C_DRV, &val);
val = FIELD_GETX(val, LX_DRV_PHY);
if (LX_DRV_PHY_UNKNOWN == val)
return LX_DRV_PHY_UNKNOWN;
l1c_read_phy(hw, false, 0, false, L1C_MII_DBG_ADDR, &phy_val);
if (LX_PHY_INITED == phy_val)
return (u16) val;
return LX_DRV_PHY_UNKNOWN;
}
u16 l1c_apply_phy_hib_patch(struct alx_hw *hw)
{
u16 Control, cr;
u8 link_cap = 0;
u32 speed = 0;
bool link_up = 0;
u16 i;
l1c_read_phydbg(hw, false, 0xc, &Control);
/*bit 11: 0 means in hibernation, 1 means not*/
if (Control & BIT(11))
hw->bInHibMode = false;
else
hw->bInHibMode = true;
if ((hw->bInHibMode) && (!hw->bHibPatched)) {
if ((hw->mac_type == alx_mac_l2cb_v1) ||
(hw->mac_type == alx_mac_l2cb_v20) ||
(hw->mac_type == alx_mac_l2cb_v21)) {
l1c_write_phy(hw, false, 0, false, MII_BMCR,
(BMCR_FULLDPLX | BMCR_SPEED100));
} else if ((hw->mac_type == alx_mac_l1d_v1) ||
(hw->mac_type == alx_mac_l1d_v2)) {
l1c_write_phy(hw, false, 0, false, MII_BMCR,
(BMCR_FULLDPLX | BMCR_SPEED1000));
}
hw->bHibPatched = true;
} else if (!hw->bInHibMode && hw->bHibPatched) {
for (i = 0; i < 10; i++) {
hw->cbs.check_phy_link(hw, &speed, &link_up);
if (link_up) {
if (speed & ALX_LINK_SPEED_1GB_FULL)
link_cap |= LX_LC_1000F;
if (speed & ALX_LINK_SPEED_100_FULL)
link_cap |= LX_LC_100F;
if (speed & ALX_LINK_SPEED_100_HALF)
link_cap |= LX_LC_100H;
if (speed & ALX_LINK_SPEED_10_FULL)
link_cap |= LX_LC_10F;
if (speed & ALX_LINK_SPEED_10_HALF)
link_cap |= LX_LC_10H;
l1c_init_phy_spdfc(hw, true, link_cap,
!hw->disable_fc_autoneg);
break;
}
mdelay(100);
}
if (!link_up) {
cr = BMCR_RESET | BMCR_ANENABLE | BMCR_ANRESTART;
l1c_write_phy(hw, false, 0, false, MII_BMCR, cr);
}
hw->bHibPatched = false;
}
return 0;
}