root/drivers/net/ethernet/atheros/alx/hw.c
/*
 * Copyright (c) 2013 Johannes Berg <johannes@sipsolutions.net>
 *
 *  This file is free software: you may copy, redistribute and/or modify it
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation, either version 2 of the License, or (at your
 *  option) any later version.
 *
 *  This file is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * This file incorporates work covered by the following copyright and
 * permission notice:
 *
 * 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/etherdevice.h>
#include <linux/delay.h>
#include <linux/pci.h>
#include <linux/mdio.h>
#include "reg.h"
#include "hw.h"

static inline bool alx_is_rev_a(u8 rev)
{
        return rev == ALX_REV_A0 || rev == ALX_REV_A1;
}

static int alx_wait_mdio_idle(struct alx_hw *hw)
{
        u32 val;
        int i;

        for (i = 0; i < ALX_MDIO_MAX_AC_TO; i++) {
                val = alx_read_mem32(hw, ALX_MDIO);
                if (!(val & ALX_MDIO_BUSY))
                        return 0;
                udelay(10);
        }

        return -ETIMEDOUT;
}

static int alx_read_phy_core(struct alx_hw *hw, bool ext, u8 dev,
                             u16 reg, u16 *phy_data)
{
        u32 val, clk_sel;
        int err;

        *phy_data = 0;

        /* use slow clock when it's in hibernation status */
        clk_sel = hw->link_speed != SPEED_UNKNOWN ?
                        ALX_MDIO_CLK_SEL_25MD4 :
                        ALX_MDIO_CLK_SEL_25MD128;

        if (ext) {
                val = dev << ALX_MDIO_EXTN_DEVAD_SHIFT |
                      reg << ALX_MDIO_EXTN_REG_SHIFT;
                alx_write_mem32(hw, ALX_MDIO_EXTN, val);

                val = ALX_MDIO_SPRES_PRMBL | ALX_MDIO_START |
                      ALX_MDIO_MODE_EXT | ALX_MDIO_OP_READ |
                      clk_sel << ALX_MDIO_CLK_SEL_SHIFT;
        } else {
                val = ALX_MDIO_SPRES_PRMBL |
                      clk_sel << ALX_MDIO_CLK_SEL_SHIFT |
                      reg << ALX_MDIO_REG_SHIFT |
                      ALX_MDIO_START | ALX_MDIO_OP_READ;
        }
        alx_write_mem32(hw, ALX_MDIO, val);

        err = alx_wait_mdio_idle(hw);
        if (err)
                return err;
        val = alx_read_mem32(hw, ALX_MDIO);
        *phy_data = ALX_GET_FIELD(val, ALX_MDIO_DATA);
        return 0;
}

static int alx_write_phy_core(struct alx_hw *hw, bool ext, u8 dev,
                              u16 reg, u16 phy_data)
{
        u32 val, clk_sel;

        /* use slow clock when it's in hibernation status */
        clk_sel = hw->link_speed != SPEED_UNKNOWN ?
                        ALX_MDIO_CLK_SEL_25MD4 :
                        ALX_MDIO_CLK_SEL_25MD128;

        if (ext) {
                val = dev << ALX_MDIO_EXTN_DEVAD_SHIFT |
                      reg << ALX_MDIO_EXTN_REG_SHIFT;
                alx_write_mem32(hw, ALX_MDIO_EXTN, val);

                val = ALX_MDIO_SPRES_PRMBL |
                      clk_sel << ALX_MDIO_CLK_SEL_SHIFT |
                      phy_data << ALX_MDIO_DATA_SHIFT |
                      ALX_MDIO_START | ALX_MDIO_MODE_EXT;
        } else {
                val = ALX_MDIO_SPRES_PRMBL |
                      clk_sel << ALX_MDIO_CLK_SEL_SHIFT |
                      reg << ALX_MDIO_REG_SHIFT |
                      phy_data << ALX_MDIO_DATA_SHIFT |
                      ALX_MDIO_START;
        }
        alx_write_mem32(hw, ALX_MDIO, val);

        return alx_wait_mdio_idle(hw);
}

static 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);
}

static 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);
}

static 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);
}

static 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);
}

static 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 (err)
                return err;

        return __alx_read_phy_reg(hw, ALX_MII_DBG_DATA, pdata);
}

static 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 (err)
                return err;

        return __alx_write_phy_reg(hw, ALX_MII_DBG_DATA, data);
}

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;
}

static 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;
}

static 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;
}

static u16 alx_get_phy_config(struct alx_hw *hw)
{
        u32 val;
        u16 phy_val;

        val = alx_read_mem32(hw, ALX_PHY_CTRL);
        /* phy in reset */
        if ((val & ALX_PHY_CTRL_DSPRST_OUT) == 0)
                return ALX_DRV_PHY_UNKNOWN;

        val = alx_read_mem32(hw, ALX_DRV);
        val = ALX_GET_FIELD(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 val;

        return ALX_DRV_PHY_UNKNOWN;
}

static bool alx_wait_reg(struct alx_hw *hw, u32 reg, u32 wait, u32 *val)
{
        u32 read;
        int i;

        for (i = 0; i < ALX_SLD_MAX_TO; i++) {
                read = alx_read_mem32(hw, reg);
                if ((read & wait) == 0) {
                        if (val)
                                *val = read;
                        return true;
                }
                mdelay(1);
        }

        return false;
}

static bool alx_read_macaddr(struct alx_hw *hw, u8 *addr)
{
        u32 mac0, mac1;

        mac0 = alx_read_mem32(hw, ALX_STAD0);
        mac1 = alx_read_mem32(hw, ALX_STAD1);

        /* addr should be big-endian */
        put_unaligned(cpu_to_be32(mac0), (__be32 *)(addr + 2));
        put_unaligned(cpu_to_be16(mac1), (__be16 *)addr);

        return is_valid_ether_addr(addr);
}

int alx_get_perm_macaddr(struct alx_hw *hw, u8 *addr)
{
        u32 val;

        /* try to get it from register first */
        if (alx_read_macaddr(hw, addr))
                return 0;

        /* try to load from efuse */
        if (!alx_wait_reg(hw, ALX_SLD, ALX_SLD_STAT | ALX_SLD_START, &val))
                return -EIO;
        alx_write_mem32(hw, ALX_SLD, val | ALX_SLD_START);
        if (!alx_wait_reg(hw, ALX_SLD, ALX_SLD_START, NULL))
                return -EIO;
        if (alx_read_macaddr(hw, addr))
                return 0;

        /* try to load from flash/eeprom (if present) */
        val = alx_read_mem32(hw, ALX_EFLD);
        if (val & (ALX_EFLD_F_EXIST | ALX_EFLD_E_EXIST)) {
                if (!alx_wait_reg(hw, ALX_EFLD,
                                  ALX_EFLD_STAT | ALX_EFLD_START, &val))
                        return -EIO;
                alx_write_mem32(hw, ALX_EFLD, val | ALX_EFLD_START);
                if (!alx_wait_reg(hw, ALX_EFLD, ALX_EFLD_START, NULL))
                        return -EIO;
                if (alx_read_macaddr(hw, addr))
                        return 0;
        }

        return -EIO;
}

void alx_set_macaddr(struct alx_hw *hw, const u8 *addr)
{
        u32 val;

        /* for example: 00-0B-6A-F6-00-DC * STAD0=6AF600DC, STAD1=000B */
        val = be32_to_cpu(get_unaligned((__be32 *)(addr + 2)));
        alx_write_mem32(hw, ALX_STAD0, val);
        val = be16_to_cpu(get_unaligned((__be16 *)addr));
        alx_write_mem32(hw, ALX_STAD1, val);
}

static void alx_reset_osc(struct alx_hw *hw, u8 rev)
{
        u32 val, val2;

        /* clear Internal OSC settings, switching OSC by hw itself */
        val = alx_read_mem32(hw, ALX_MISC3);
        alx_write_mem32(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
         */
        val = alx_read_mem32(hw, ALX_MISC);
        if (rev >= ALX_REV_B0) {
                /* restore over current protection def-val,
                 * this val could be reset by MAC-RST
                 */
                ALX_SET_FIELD(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_write_mem32(hw, ALX_MISC, val);
                alx_write_mem32(hw, ALX_MISC, val | ALX_MISC_INTNLOSC_OPEN);
                /* hw will automatically dis OSC after cab. */
                val2 = alx_read_mem32(hw, ALX_MSIC2);
                val2 &= ~ALX_MSIC2_CALB_START;
                alx_write_mem32(hw, ALX_MSIC2, val2);
                alx_write_mem32(hw, ALX_MSIC2, val2 | ALX_MSIC2_CALB_START);
        } else {
                val &= ~ALX_MISC_INTNLOSC_OPEN;
                /* disable isolate for rev A devices */
                if (alx_is_rev_a(rev))
                        val &= ~ALX_MISC_ISO_EN;

                alx_write_mem32(hw, ALX_MISC, val | ALX_MISC_INTNLOSC_OPEN);
                alx_write_mem32(hw, ALX_MISC, val);
        }

        udelay(20);
}

static int alx_stop_mac(struct alx_hw *hw)
{
        u32 rxq, txq, val;
        u16 i;

        rxq = alx_read_mem32(hw, ALX_RXQ0);
        alx_write_mem32(hw, ALX_RXQ0, rxq & ~ALX_RXQ0_EN);
        txq = alx_read_mem32(hw, ALX_TXQ0);
        alx_write_mem32(hw, ALX_TXQ0, txq & ~ALX_TXQ0_EN);

        udelay(40);

        hw->rx_ctrl &= ~(ALX_MAC_CTRL_RX_EN | ALX_MAC_CTRL_TX_EN);
        alx_write_mem32(hw, ALX_MAC_CTRL, hw->rx_ctrl);

        for (i = 0; i < ALX_DMA_MAC_RST_TO; i++) {
                val = alx_read_mem32(hw, ALX_MAC_STS);
                if (!(val & ALX_MAC_STS_IDLE))
                        return 0;
                udelay(10);
        }

        return -ETIMEDOUT;
}

int alx_reset_mac(struct alx_hw *hw)
{
        u32 val, pmctrl;
        int i, ret;
        u8 rev;
        bool a_cr;

        pmctrl = 0;
        rev = alx_hw_revision(hw);
        a_cr = alx_is_rev_a(rev) && alx_hw_with_cr(hw);

        /* disable all interrupts, RXQ/TXQ */
        alx_write_mem32(hw, ALX_MSIX_MASK, 0xFFFFFFFF);
        alx_write_mem32(hw, ALX_IMR, 0);
        alx_write_mem32(hw, ALX_ISR, ALX_ISR_DIS);

        ret = alx_stop_mac(hw);
        if (ret)
                return ret;

        /* mac reset workaroud */
        alx_write_mem32(hw, ALX_RFD_PIDX, 1);

        /* dis l0s/l1 before mac reset */
        if (a_cr) {
                pmctrl = alx_read_mem32(hw, ALX_PMCTRL);
                if (pmctrl & (ALX_PMCTRL_L1_EN | ALX_PMCTRL_L0S_EN))
                        alx_write_mem32(hw, ALX_PMCTRL,
                                        pmctrl & ~(ALX_PMCTRL_L1_EN |
                                                   ALX_PMCTRL_L0S_EN));
        }

        /* reset whole mac safely */
        val = alx_read_mem32(hw, ALX_MASTER);
        alx_write_mem32(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++) {
                val = alx_read_mem32(hw, ALX_RFD_PIDX);
                if (val == 0)
                        break;
                udelay(10);
        }
        for (; i < ALX_DMA_MAC_RST_TO; i++) {
                val = alx_read_mem32(hw, ALX_MASTER);
                if ((val & ALX_MASTER_DMA_MAC_RST) == 0)
                        break;
                udelay(10);
        }
        if (i == ALX_DMA_MAC_RST_TO)
                return -EIO;
        udelay(10);

        if (a_cr) {
                alx_write_mem32(hw, ALX_MASTER, val | ALX_MASTER_PCLKSEL_SRDS);
                /* restore l0s / l1 */
                if (pmctrl & (ALX_PMCTRL_L1_EN | ALX_PMCTRL_L0S_EN))
                        alx_write_mem32(hw, ALX_PMCTRL, pmctrl);
        }

        alx_reset_osc(hw, rev);

        /* clear Internal OSC settings, switching OSC by hw itself,
         * disable isolate for rev A devices
         */
        val = alx_read_mem32(hw, ALX_MISC3);
        alx_write_mem32(hw, ALX_MISC3,
                        (val & ~ALX_MISC3_25M_BY_SW) |
                        ALX_MISC3_25M_NOTO_INTNL);
        val = alx_read_mem32(hw, ALX_MISC);
        val &= ~ALX_MISC_INTNLOSC_OPEN;
        if (alx_is_rev_a(rev))
                val &= ~ALX_MISC_ISO_EN;
        alx_write_mem32(hw, ALX_MISC, val);
        udelay(20);

        /* driver control speed/duplex, hash-alg */
        alx_write_mem32(hw, ALX_MAC_CTRL, hw->rx_ctrl);

        val = alx_read_mem32(hw, ALX_SERDES);
        alx_write_mem32(hw, ALX_SERDES,
                        val | ALX_SERDES_MACCLK_SLWDWN |
                        ALX_SERDES_PHYCLK_SLWDWN);

        return 0;
}

void alx_reset_phy(struct alx_hw *hw)
{
        int i;
        u32 val;
        u16 phy_val;

        /* (DSP)reset PHY core */
        val = alx_read_mem32(hw, ALX_PHY_CTRL);
        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;

        val |= (ALX_PHY_CTRL_HIB_PULSE | ALX_PHY_CTRL_HIB_EN);
        alx_write_mem32(hw, ALX_PHY_CTRL, val);
        udelay(10);
        alx_write_mem32(hw, ALX_PHY_CTRL, val | ALX_PHY_CTRL_DSPRST_OUT);

        for (i = 0; i < ALX_PHY_CTRL_DSPRST_TO; i++)
                udelay(10);

        /* phy power saving & hib */
        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);

        /* EEE advertisement */
        val = alx_read_mem32(hw, ALX_LPI_CTRL);
        alx_write_mem32(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 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)

void alx_reset_pcie(struct alx_hw *hw)
{
        u8 rev = alx_hw_revision(hw);
        u32 val;
        u16 val16;

        /* Workaround for PCI problem when BIOS sets MMRBC incorrectly. */
        pci_read_config_word(hw->pdev, PCI_COMMAND, &val16);
        if (!(val16 & ALX_PCI_CMD) || (val16 & PCI_COMMAND_INTX_DISABLE)) {
                val16 = (val16 | ALX_PCI_CMD) & ~PCI_COMMAND_INTX_DISABLE;
                pci_write_config_word(hw->pdev, PCI_COMMAND, val16);
        }

        /* clear WoL setting/status */
        val = alx_read_mem32(hw, ALX_WOL0);
        alx_write_mem32(hw, ALX_WOL0, 0);

        val = alx_read_mem32(hw, ALX_PDLL_TRNS1);
        alx_write_mem32(hw, ALX_PDLL_TRNS1, val & ~ALX_PDLL_TRNS1_D3PLLOFF_EN);

        /* mask some pcie error bits */
        val = alx_read_mem32(hw, ALX_UE_SVRT);
        val &= ~(ALX_UE_SVRT_DLPROTERR | ALX_UE_SVRT_FCPROTERR);
        alx_write_mem32(hw, ALX_UE_SVRT, val);

        /* wol 25M & pclk */
        val = alx_read_mem32(hw, ALX_MASTER);
        if (alx_is_rev_a(rev) && alx_hw_with_cr(hw)) {
                if ((val & ALX_MASTER_WAKEN_25M) == 0 ||
                    (val & ALX_MASTER_PCLKSEL_SRDS) == 0)
                        alx_write_mem32(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_write_mem32(hw, ALX_MASTER,
                                        (val & ~ALX_MASTER_PCLKSEL_SRDS) |
                                        ALX_MASTER_WAKEN_25M);
        }

        /* ASPM setting */
        alx_enable_aspm(hw, true, true);

        udelay(10);
}

void alx_start_mac(struct alx_hw *hw)
{
        u32 mac, txq, rxq;

        rxq = alx_read_mem32(hw, ALX_RXQ0);
        alx_write_mem32(hw, ALX_RXQ0, rxq | ALX_RXQ0_EN);
        txq = alx_read_mem32(hw, ALX_TXQ0);
        alx_write_mem32(hw, ALX_TXQ0, txq | ALX_TXQ0_EN);

        mac = hw->rx_ctrl;
        if (hw->duplex == DUPLEX_FULL)
                mac |= ALX_MAC_CTRL_FULLD;
        else
                mac &= ~ALX_MAC_CTRL_FULLD;
        ALX_SET_FIELD(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_write_mem32(hw, ALX_MAC_CTRL, mac);
}

void alx_cfg_mac_flowcontrol(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_write_mem32(hw, ALX_MAC_CTRL, hw->rx_ctrl);
}

void alx_enable_aspm(struct alx_hw *hw, bool l0s_en, bool l1_en)
{
        u32 pmctrl;
        u8 rev = alx_hw_revision(hw);

        pmctrl = alx_read_mem32(hw, ALX_PMCTRL);

        ALX_SET_FIELD(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;
        ALX_SET_FIELD(pmctrl, ALX_PMCTRL_L1REQ_TO, ALX_PMCTRL_L1REG_TO_DEF);
        ALX_SET_FIELD(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_is_rev_a(rev) && alx_hw_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_write_mem32(hw, ALX_PMCTRL, pmctrl);
}


static 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;
        } 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;
}

int alx_setup_speed_duplex(struct alx_hw *hw, u32 ethadv, u8 flowctrl)
{
        u16 adv, giga, cr;
        u32 val;
        int err = 0;

        alx_write_phy_reg(hw, ALX_MII_DBG_ADDR, 0);
        val = alx_read_mem32(hw, ALX_DRV);
        ALX_SET_FIELD(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_hw_giga(hw))
                        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 = -EBUSY;
        } 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);
                val |= ethadv_to_hw_cfg(hw, ethadv);
        }

        alx_write_mem32(hw, ALX_DRV, val);

        return err;
}


void alx_post_phy_link(struct alx_hw *hw)
{
        u16 phy_val, len, agc;
        u8 revid = alx_hw_revision(hw);
        bool adj_th = revid == ALX_REV_B0;

        if (revid != ALX_REV_B0 && !alx_is_rev_a(revid))
                return;

        /* 1000BT/AZ, wrong cable length */
        if (hw->link_speed != SPEED_UNKNOWN) {
                alx_read_phy_ext(hw, ALX_MIIEXT_PCS, ALX_MIIEXT_CLDCTRL6,
                                 &phy_val);
                len = ALX_GET_FIELD(phy_val, ALX_CLDCTRL6_CAB_LEN);
                alx_read_phy_dbg(hw, ALX_MIIDBG_AGC, &phy_val);
                agc = ALX_GET_FIELD(phy_val, ALX_AGC_2_VGA);

                if ((hw->link_speed == SPEED_1000 &&
                     (len > ALX_CLDCTRL6_CAB_LEN_SHORT1G ||
                      (len == 0 && agc > ALX_AGC_LONG1G_LIMT))) ||
                    (hw->link_speed == SPEED_100 &&
                     (len > ALX_CLDCTRL6_CAB_LEN_SHORT100M ||
                      (len == 0 && 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);
                }

                /* threshold adjust */
                if (adj_th && hw->lnk_patch) {
                        if (hw->link_speed == SPEED_100) {
                                alx_write_phy_dbg(hw, ALX_MIIDBG_MSE16DB,
                                                  ALX_MSE16DB_UP);
                        } else if (hw->link_speed == SPEED_1000) {
                                /*
                                 * Giga link threshold, raise the tolerance of
                                 * noise 50%
                                 */
                                alx_read_phy_dbg(hw, ALX_MIIDBG_MSE20DB,
                                                 &phy_val);
                                ALX_SET_FIELD(phy_val, ALX_MSE20DB_TH,
                                              ALX_MSE20DB_TH_HI);
                                alx_write_phy_dbg(hw, ALX_MIIDBG_MSE20DB,
                                                  phy_val);
                        }
                }
        } 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);
                        ALX_SET_FIELD(phy_val, ALX_MSE20DB_TH,
                                      ALX_MSE20DB_TH_DEF);
                        alx_write_phy_dbg(hw, ALX_MIIDBG_MSE20DB, phy_val);
                }
        }
}

bool alx_phy_configured(struct alx_hw *hw)
{
        u32 cfg, hw_cfg;

        cfg = ethadv_to_hw_cfg(hw, hw->adv_cfg);
        cfg = ALX_GET_FIELD(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_read_phy_link(struct alx_hw *hw)
{
        struct pci_dev *pdev = hw->pdev;
        u16 bmsr, giga;
        int err;

        err = alx_read_phy_reg(hw, MII_BMSR, &bmsr);
        if (err)
                return err;

        err = alx_read_phy_reg(hw, MII_BMSR, &bmsr);
        if (err)
                return err;

        if (!(bmsr & BMSR_LSTATUS)) {
                hw->link_speed = SPEED_UNKNOWN;
                hw->duplex = DUPLEX_UNKNOWN;
                return 0;
        }

        /* speed/duplex result is saved in PHY Specific Status Register */
        err = alx_read_phy_reg(hw, ALX_MII_GIGA_PSSR, &giga);
        if (err)
                return err;

        if (!(giga & ALX_GIGA_PSSR_SPD_DPLX_RESOLVED))
                goto wrong_speed;

        switch (giga & ALX_GIGA_PSSR_SPEED) {
        case ALX_GIGA_PSSR_1000MBS:
                hw->link_speed = SPEED_1000;
                break;
        case ALX_GIGA_PSSR_100MBS:
                hw->link_speed = SPEED_100;
                break;
        case ALX_GIGA_PSSR_10MBS:
                hw->link_speed = SPEED_10;
                break;
        default:
                goto wrong_speed;
        }

        hw->duplex = (giga & ALX_GIGA_PSSR_DPLX) ? DUPLEX_FULL : DUPLEX_HALF;
        return 0;

wrong_speed:
        dev_err(&pdev->dev, "invalid PHY speed/duplex: 0x%x\n", giga);
        return -EINVAL;
}

int alx_clear_phy_intr(struct alx_hw *hw)
{
        u16 isr;

        /* clear interrupt status by reading it */
        return alx_read_phy_reg(hw, ALX_MII_ISR, &isr);
}

void alx_disable_rss(struct alx_hw *hw)
{
        u32 ctrl = alx_read_mem32(hw, ALX_RXQ0);

        ctrl &= ~ALX_RXQ0_RSS_HASH_EN;
        alx_write_mem32(hw, ALX_RXQ0, ctrl);
}

void alx_configure_basic(struct alx_hw *hw)
{
        u32 val, raw_mtu, max_payload;
        u16 val16;
        u8 chip_rev = alx_hw_revision(hw);

        alx_set_macaddr(hw, hw->mac_addr);

        alx_write_mem32(hw, ALX_CLK_GATE, ALX_CLK_GATE_ALL);

        /* idle timeout to switch clk_125M */
        if (chip_rev >= ALX_REV_B0)
                alx_write_mem32(hw, ALX_IDLE_DECISN_TIMER,
                                ALX_IDLE_DECISN_TIMER_DEF);

        alx_write_mem32(hw, ALX_SMB_TIMER, hw->smb_timer * 500UL);

        val = alx_read_mem32(hw, ALX_MASTER);
        val |= ALX_MASTER_IRQMOD2_EN |
               ALX_MASTER_IRQMOD1_EN |
               ALX_MASTER_SYSALVTIMER_EN;
        alx_write_mem32(hw, ALX_MASTER, val);
        alx_write_mem32(hw, ALX_IRQ_MODU_TIMER,
                        (hw->imt >> 1) << ALX_IRQ_MODU_TIMER1_SHIFT);
        /* intr re-trig timeout */
        alx_write_mem32(hw, ALX_INT_RETRIG, ALX_INT_RETRIG_TO);
        /* tpd threshold to trig int */
        alx_write_mem32(hw, ALX_TINT_TPD_THRSHLD, hw->ith_tpd);
        alx_write_mem32(hw, ALX_TINT_TIMER, hw->imt);

        raw_mtu = ALX_RAW_MTU(hw->mtu);
        alx_write_mem32(hw, ALX_MTU, raw_mtu);
        if (raw_mtu > (ALX_MTU_JUMBO_TH + ETH_FCS_LEN + VLAN_HLEN))
                hw->rx_ctrl &= ~ALX_MAC_CTRL_FAST_PAUSE;

        if (raw_mtu < ALX_TXQ1_JUMBO_TSO_TH)
                val = (raw_mtu + 7) >> 3;
        else
                val = ALX_TXQ1_JUMBO_TSO_TH >> 3;
        alx_write_mem32(hw, ALX_TXQ1, val | ALX_TXQ1_ERRLGPKT_DROP_EN);

        max_payload = pcie_get_readrq(hw->pdev) >> 8;
        /*
         * if BIOS had changed the default dma read max length,
         * restore it to default value
         */
        if (max_payload < ALX_DEV_CTRL_MAXRRS_MIN)
                pcie_set_readrq(hw->pdev, 128 << ALX_DEV_CTRL_MAXRRS_MIN);

        val = ALX_TXQ_TPD_BURSTPREF_DEF << ALX_TXQ0_TPD_BURSTPREF_SHIFT |
              ALX_TXQ0_MODE_ENHANCE | ALX_TXQ0_LSO_8023_EN |
              ALX_TXQ0_SUPT_IPOPT |
              ALX_TXQ_TXF_BURST_PREF_DEF << ALX_TXQ0_TXF_BURST_PREF_SHIFT;
        alx_write_mem32(hw, ALX_TXQ0, val);
        val = ALX_TXQ_TPD_BURSTPREF_DEF << ALX_HQTPD_Q1_NUMPREF_SHIFT |
              ALX_TXQ_TPD_BURSTPREF_DEF << ALX_HQTPD_Q2_NUMPREF_SHIFT |
              ALX_TXQ_TPD_BURSTPREF_DEF << ALX_HQTPD_Q3_NUMPREF_SHIFT |
              ALX_HQTPD_BURST_EN;
        alx_write_mem32(hw, ALX_HQTPD, val);

        /* rxq, flow control */
        val = alx_read_mem32(hw, ALX_SRAM5);
        val = ALX_GET_FIELD(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_write_mem32(hw, ALX_RXQ2,
                        val16 << ALX_RXQ2_RXF_XOFF_THRESH_SHIFT |
                        val << ALX_RXQ2_RXF_XON_THRESH_SHIFT);
        val = ALX_RXQ0_NUM_RFD_PREF_DEF << ALX_RXQ0_NUM_RFD_PREF_SHIFT |
              ALX_RXQ0_RSS_MODE_DIS << ALX_RXQ0_RSS_MODE_SHIFT |
              ALX_RXQ0_IDT_TBL_SIZE_DEF << ALX_RXQ0_IDT_TBL_SIZE_SHIFT |
              ALX_RXQ0_RSS_HSTYP_ALL | ALX_RXQ0_RSS_HASH_EN |
              ALX_RXQ0_IPV6_PARSE_EN;

        if (alx_hw_giga(hw))
                ALX_SET_FIELD(val, ALX_RXQ0_ASPM_THRESH,
                              ALX_RXQ0_ASPM_THRESH_100M);

        alx_write_mem32(hw, ALX_RXQ0, val);

        val = alx_read_mem32(hw, ALX_DMA);
        val = ALX_DMA_RORDER_MODE_OUT << ALX_DMA_RORDER_MODE_SHIFT |
              ALX_DMA_RREQ_PRI_DATA |
              max_payload << ALX_DMA_RREQ_BLEN_SHIFT |
              ALX_DMA_WDLY_CNT_DEF << ALX_DMA_WDLY_CNT_SHIFT |
              ALX_DMA_RDLY_CNT_DEF << ALX_DMA_RDLY_CNT_SHIFT |
              (hw->dma_chnl - 1) << ALX_DMA_RCHNL_SEL_SHIFT;
        alx_write_mem32(hw, ALX_DMA, val);

        /* default multi-tx-q weights */
        val = ALX_WRR_PRI_RESTRICT_NONE << ALX_WRR_PRI_SHIFT |
              4 << ALX_WRR_PRI0_SHIFT |
              4 << ALX_WRR_PRI1_SHIFT |
              4 << ALX_WRR_PRI2_SHIFT |
              4 << ALX_WRR_PRI3_SHIFT;
        alx_write_mem32(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_write_mem32(hw, reg, val);
        alx_post_write(hw);
}


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;
}

void alx_update_hw_stats(struct alx_hw *hw)
{
        /* RX stats */
        hw->stats.rx_ok          += alx_read_mem32(hw, ALX_MIB_RX_OK);
        hw->stats.rx_bcast       += alx_read_mem32(hw, ALX_MIB_RX_BCAST);
        hw->stats.rx_mcast       += alx_read_mem32(hw, ALX_MIB_RX_MCAST);
        hw->stats.rx_pause       += alx_read_mem32(hw, ALX_MIB_RX_PAUSE);
        hw->stats.rx_ctrl        += alx_read_mem32(hw, ALX_MIB_RX_CTRL);
        hw->stats.rx_fcs_err     += alx_read_mem32(hw, ALX_MIB_RX_FCS_ERR);
        hw->stats.rx_len_err     += alx_read_mem32(hw, ALX_MIB_RX_LEN_ERR);
        hw->stats.rx_byte_cnt    += alx_read_mem32(hw, ALX_MIB_RX_BYTE_CNT);
        hw->stats.rx_runt        += alx_read_mem32(hw, ALX_MIB_RX_RUNT);
        hw->stats.rx_frag        += alx_read_mem32(hw, ALX_MIB_RX_FRAG);
        hw->stats.rx_sz_64B      += alx_read_mem32(hw, ALX_MIB_RX_SZ_64B);
        hw->stats.rx_sz_127B     += alx_read_mem32(hw, ALX_MIB_RX_SZ_127B);
        hw->stats.rx_sz_255B     += alx_read_mem32(hw, ALX_MIB_RX_SZ_255B);
        hw->stats.rx_sz_511B     += alx_read_mem32(hw, ALX_MIB_RX_SZ_511B);
        hw->stats.rx_sz_1023B    += alx_read_mem32(hw, ALX_MIB_RX_SZ_1023B);
        hw->stats.rx_sz_1518B    += alx_read_mem32(hw, ALX_MIB_RX_SZ_1518B);
        hw->stats.rx_sz_max      += alx_read_mem32(hw, ALX_MIB_RX_SZ_MAX);
        hw->stats.rx_ov_sz       += alx_read_mem32(hw, ALX_MIB_RX_OV_SZ);
        hw->stats.rx_ov_rxf      += alx_read_mem32(hw, ALX_MIB_RX_OV_RXF);
        hw->stats.rx_ov_rrd      += alx_read_mem32(hw, ALX_MIB_RX_OV_RRD);
        hw->stats.rx_align_err   += alx_read_mem32(hw, ALX_MIB_RX_ALIGN_ERR);
        hw->stats.rx_bc_byte_cnt += alx_read_mem32(hw, ALX_MIB_RX_BCCNT);
        hw->stats.rx_mc_byte_cnt += alx_read_mem32(hw, ALX_MIB_RX_MCCNT);
        hw->stats.rx_err_addr    += alx_read_mem32(hw, ALX_MIB_RX_ERRADDR);

        /* TX stats */
        hw->stats.tx_ok          += alx_read_mem32(hw, ALX_MIB_TX_OK);
        hw->stats.tx_bcast       += alx_read_mem32(hw, ALX_MIB_TX_BCAST);
        hw->stats.tx_mcast       += alx_read_mem32(hw, ALX_MIB_TX_MCAST);
        hw->stats.tx_pause       += alx_read_mem32(hw, ALX_MIB_TX_PAUSE);
        hw->stats.tx_exc_defer   += alx_read_mem32(hw, ALX_MIB_TX_EXC_DEFER);
        hw->stats.tx_ctrl        += alx_read_mem32(hw, ALX_MIB_TX_CTRL);
        hw->stats.tx_defer       += alx_read_mem32(hw, ALX_MIB_TX_DEFER);
        hw->stats.tx_byte_cnt    += alx_read_mem32(hw, ALX_MIB_TX_BYTE_CNT);
        hw->stats.tx_sz_64B      += alx_read_mem32(hw, ALX_MIB_TX_SZ_64B);
        hw->stats.tx_sz_127B     += alx_read_mem32(hw, ALX_MIB_TX_SZ_127B);
        hw->stats.tx_sz_255B     += alx_read_mem32(hw, ALX_MIB_TX_SZ_255B);
        hw->stats.tx_sz_511B     += alx_read_mem32(hw, ALX_MIB_TX_SZ_511B);
        hw->stats.tx_sz_1023B    += alx_read_mem32(hw, ALX_MIB_TX_SZ_1023B);
        hw->stats.tx_sz_1518B    += alx_read_mem32(hw, ALX_MIB_TX_SZ_1518B);
        hw->stats.tx_sz_max      += alx_read_mem32(hw, ALX_MIB_TX_SZ_MAX);
        hw->stats.tx_single_col  += alx_read_mem32(hw, ALX_MIB_TX_SINGLE_COL);
        hw->stats.tx_multi_col   += alx_read_mem32(hw, ALX_MIB_TX_MULTI_COL);
        hw->stats.tx_late_col    += alx_read_mem32(hw, ALX_MIB_TX_LATE_COL);
        hw->stats.tx_abort_col   += alx_read_mem32(hw, ALX_MIB_TX_ABORT_COL);
        hw->stats.tx_underrun    += alx_read_mem32(hw, ALX_MIB_TX_UNDERRUN);
        hw->stats.tx_trd_eop     += alx_read_mem32(hw, ALX_MIB_TX_TRD_EOP);
        hw->stats.tx_len_err     += alx_read_mem32(hw, ALX_MIB_TX_LEN_ERR);
        hw->stats.tx_trunc       += alx_read_mem32(hw, ALX_MIB_TX_TRUNC);
        hw->stats.tx_bc_byte_cnt += alx_read_mem32(hw, ALX_MIB_TX_BCCNT);
        hw->stats.tx_mc_byte_cnt += alx_read_mem32(hw, ALX_MIB_TX_MCCNT);

        hw->stats.update         += alx_read_mem32(hw, ALX_MIB_UPDATE);
}