/* * 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 #include #include "alf_hw.h" #define ALF_REV_ID_AR8161_B0 0x10 /* definition for MSIX */ #define ALF_MSIX_ENTRY_BASE 0x2000 #define ALF_MSIX_ENTRY_SIZE 16 #define ALF_MSIX_MSG_LOADDR_OFF 0 #define ALF_MSIX_MSG_HIADDR_OFF 4 #define ALF_MSIX_MSG_DATA_OFF 8 #define ALF_MSIX_MSG_CTRL_OFF 12 #define ALF_MSIX_INDEX_RXQ0 0 #define ALF_MSIX_INDEX_RXQ1 1 #define ALF_MSIX_INDEX_RXQ2 2 #define ALF_MSIX_INDEX_RXQ3 3 #define ALF_MSIX_INDEX_RXQ4 4 #define ALF_MSIX_INDEX_RXQ5 5 #define ALF_MSIX_INDEX_RXQ6 6 #define ALF_MSIX_INDEX_RXQ7 7 #define ALF_MSIX_INDEX_TXQ0 8 #define ALF_MSIX_INDEX_TXQ1 9 #define ALF_MSIX_INDEX_TXQ2 10 #define ALF_MSIX_INDEX_TXQ3 11 #define ALF_MSIX_INDEX_TIMER 12 #define ALF_MSIX_INDEX_ALERT 13 #define ALF_MSIX_INDEX_SMB 14 #define ALF_MSIX_INDEX_PHY 15 #define ALF_SRAM_BASE L1F_SRAM0 #define ALF_SRAM(_i, _type) \ (ALF_SRAM_BASE + ((_i) * sizeof(_type))) #define ALF_MIB_BASE L1F_MIB_BASE #define ALF_MIB(_i, _type) \ (ALF_MIB_BASE + ((_i) * sizeof(_type))) /* definition for RSS */ #define ALF_RSS_KEY_BASE L1F_RSS_KEY0 #define ALF_RSS_IDT_BASE L1F_RSS_IDT_TBL0 #define ALF_RSS_KEY(_i, _type) \ (ALF_RSS_KEY_BASE + ((_i) * sizeof(_type))) #define ALF_RSS_TBL(_i, _type) \ (L1F_RSS_IDT_TBL0 + ((_i) * sizeof(_type))) /* NIC */ static int alf_identify_nic(struct alx_hw *hw) { u32 drv; if (hw->pci_revid < ALX_REV_ID_AR8161_V2_0) return 0; /* check from V2_0(b0) to ... */ switch (hw->pci_revid) { default: alx_mem_r32(hw, L1F_DRV, &drv); if (drv & LX_DRV_DISABLE) return -EINVAL; break; } return 0; } /* PHY */ static int alf_read_phy_reg(struct alx_hw *hw, u16 reg_addr, u16 *phy_data) { unsigned long flags; int retval = 0; spin_lock_irqsave(&hw->mdio_lock, flags); if (l1f_read_phy(hw, false, ALX_MDIO_DEV_TYPE_NORM, false, reg_addr, phy_data)) { alx_hw_err(hw, "error when read phy reg\n"); retval = -EINVAL; } spin_unlock_irqrestore(&hw->mdio_lock, flags); return retval; } static int alf_write_phy_reg(struct alx_hw *hw, u16 reg_addr, u16 phy_data) { unsigned long flags; int retval = 0; spin_lock_irqsave(&hw->mdio_lock, flags); if (l1f_write_phy(hw, false, ALX_MDIO_DEV_TYPE_NORM, false, reg_addr, phy_data)) { alx_hw_err(hw, "error when write phy reg\n"); retval = -EINVAL; } spin_unlock_irqrestore(&hw->mdio_lock, flags); return retval; } static int alf_init_phy(struct alx_hw *hw) { u16 phy_id[2]; int retval; spin_lock_init(&hw->mdio_lock); retval = alf_read_phy_reg(hw, MII_PHYSID1, &phy_id[0]); if (retval) return retval; retval = alf_read_phy_reg(hw, MII_PHYSID2, &phy_id[1]); if (retval) return retval; memcpy(&hw->phy_id, phy_id, sizeof(hw->phy_id)); hw->autoneg_advertised = ALX_LINK_SPEED_1GB_FULL | ALX_LINK_SPEED_10_HALF | ALX_LINK_SPEED_10_FULL | ALX_LINK_SPEED_100_HALF | ALX_LINK_SPEED_100_FULL; return retval; } static int alf_reset_phy(struct alx_hw *hw) { int retval = 0; bool pws_en, az_en, ptp_en; pws_en = az_en = ptp_en = false; CLI_HW_FLAG(PWSAVE_EN); CLI_HW_FLAG(AZ_EN); CLI_HW_FLAG(PTP_EN); if (CHK_HW_FLAG(PWSAVE_CAP)) { pws_en = true; SET_HW_FLAG(PWSAVE_EN); } if (CHK_HW_FLAG(AZ_CAP)) { az_en = true; SET_HW_FLAG(AZ_EN); } if (CHK_HW_FLAG(PTP_CAP)) { ptp_en = true; SET_HW_FLAG(PTP_EN); } alx_hw_info(hw, "reset PHY, pws = %d, az = %d, ptp = %d\n", pws_en, az_en, ptp_en); if (l1f_reset_phy(hw, pws_en, az_en, ptp_en)) { alx_hw_err(hw, "error when reset phy\n"); retval = -EINVAL; } return retval; } /* LINK */ static int alf_setup_phy_link(struct alx_hw *hw, u32 speed, bool autoneg, bool fc) { u8 link_cap = 0; int retval = 0; alx_hw_info(hw, "speed = 0x%x, autoneg = %d\n", speed, autoneg); 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; if (l1f_init_phy_spdfc(hw, autoneg, link_cap, fc)) { alx_hw_err(hw, "error when init phy speed and fc\n"); retval = -EINVAL; } return retval; } static int alf_setup_phy_link_speed(struct alx_hw *hw, u32 speed, bool autoneg, bool fc) { /* * Clear autoneg_advertised and set new values based on input link * speed. */ hw->autoneg_advertised = 0; if (speed & ALX_LINK_SPEED_1GB_FULL) hw->autoneg_advertised |= ALX_LINK_SPEED_1GB_FULL; if (speed & ALX_LINK_SPEED_100_FULL) hw->autoneg_advertised |= ALX_LINK_SPEED_100_FULL; if (speed & ALX_LINK_SPEED_100_HALF) hw->autoneg_advertised |= ALX_LINK_SPEED_100_HALF; if (speed & ALX_LINK_SPEED_10_FULL) hw->autoneg_advertised |= ALX_LINK_SPEED_10_FULL; if (speed & ALX_LINK_SPEED_10_HALF) hw->autoneg_advertised |= ALX_LINK_SPEED_10_HALF; return alf_setup_phy_link(hw, hw->autoneg_advertised, autoneg, fc); } static int alf_check_phy_link(struct alx_hw *hw, u32 *speed, bool *link_up) { u16 bmsr, giga; int retval; alf_read_phy_reg(hw, MII_BMSR, &bmsr); retval = alf_read_phy_reg(hw, MII_BMSR, &bmsr); if (retval) return retval; if (!(bmsr & BMSR_LSTATUS)) { *link_up = false; *speed = ALX_LINK_SPEED_UNKNOWN; return 0; } *link_up = true; /* Read PHY Specific Status Register (17) */ retval = alf_read_phy_reg(hw, L1F_MII_GIGA_PSSR, &giga); if (retval) return retval; if (!(giga & L1F_GIGA_PSSR_SPD_DPLX_RESOLVED)) { alx_hw_err(hw, "error for speed duplex resolved\n"); return -EINVAL; } switch (giga & L1F_GIGA_PSSR_SPEED) { case L1F_GIGA_PSSR_1000MBS: if (giga & L1F_GIGA_PSSR_DPLX) *speed = ALX_LINK_SPEED_1GB_FULL; else alx_hw_err(hw, "1000M half is invalid"); break; case L1F_GIGA_PSSR_100MBS: if (giga & L1F_GIGA_PSSR_DPLX) *speed = ALX_LINK_SPEED_100_FULL; else *speed = ALX_LINK_SPEED_100_HALF; break; case L1F_GIGA_PSSR_10MBS: if (giga & L1F_GIGA_PSSR_DPLX) *speed = ALX_LINK_SPEED_10_FULL; else *speed = ALX_LINK_SPEED_10_HALF; break; default: *speed = ALX_LINK_SPEED_UNKNOWN; retval = -EINVAL; break; } return retval; } /* * 1. stop_mac * 2. reset mac & dma by reg1400(MASTER) * 3. control speed/duplex, hash-alg * 4. clock switch setting */ static int alf_reset_mac(struct alx_hw *hw) { int retval = 0; if (l1f_reset_mac(hw)) { alx_hw_err(hw, "error when reset mac\n"); retval = -EINVAL; } return retval; } static int alf_start_mac(struct alx_hw *hw) { u16 en_ctrl = 0; int retval = 0; /* set link speed param */ switch (hw->link_speed) { case ALX_LINK_SPEED_1GB_FULL: en_ctrl |= LX_MACSPEED_1000; /* fall through */ case ALX_LINK_SPEED_100_FULL: case ALX_LINK_SPEED_10_FULL: en_ctrl |= LX_MACDUPLEX_FULL; break; } /* set fc param*/ switch (hw->cur_fc_mode) { case alx_fc_full: en_ctrl |= LX_FC_RXEN; /* Flow Control RX Enable */ en_ctrl |= LX_FC_TXEN; /* Flow Control TX Enable */ break; case alx_fc_rx_pause: en_ctrl |= LX_FC_RXEN; /* Flow Control RX Enable */ break; case alx_fc_tx_pause: en_ctrl |= LX_FC_TXEN; /* Flow Control TX Enable */ break; default: break; } if (hw->fc_single_pause) en_ctrl |= LX_SINGLE_PAUSE; en_ctrl |= LX_FLT_DIRECT; /* RX Enable; and TX Always Enable */ en_ctrl |= LX_FLT_BROADCAST; /* RX Broadcast Enable */ en_ctrl |= LX_ADD_FCS; if (CHK_HW_FLAG(VLANSTRIP_EN)) en_ctrl |= LX_VLAN_STRIP; if (CHK_HW_FLAG(PROMISC_EN)) en_ctrl |= LX_FLT_PROMISC; if (CHK_HW_FLAG(MULTIALL_EN)) en_ctrl |= LX_FLT_MULTI_ALL; if (CHK_HW_FLAG(LOOPBACK_EN)) en_ctrl |= LX_LOOPBACK; if (l1f_enable_mac(hw, true, en_ctrl)) { alx_hw_err(hw, "error when start mac\n"); retval = -EINVAL; } return retval; } /* * 1. stop RXQ (reg15A0) and TXQ (reg1590) * 2. stop MAC (reg1480) */ static int alf_stop_mac(struct alx_hw *hw) { int retval = 0; if (l1f_enable_mac(hw, false, 0)) { alx_hw_err(hw, "error when stop mac\n"); retval = -EINVAL; } return retval; } static int alf_config_mac(struct alx_hw *hw, u16 rxbuf_sz, u16 rx_qnum, u16 rxring_sz, u16 tx_qnum, u16 txring_sz) { u8 *addr; u32 txmem_hi, txmem_lo[4]; u32 rxmem_hi, rfdmem_lo, rrdmem_lo; u16 smb_timer, mtu_with_eth, int_mod; bool hash_legacy; int i; int retval = 0; addr = hw->mac_addr; txmem_hi = ALX_DMA_ADDR_HI(hw->tpdma[0]); for (i = 0; i < tx_qnum; i++) txmem_lo[i] = ALX_DMA_ADDR_LO(hw->tpdma[i]); rxmem_hi = ALX_DMA_ADDR_HI(hw->rfdma[0]); rfdmem_lo = ALX_DMA_ADDR_LO(hw->rfdma[0]); rrdmem_lo = ALX_DMA_ADDR_LO(hw->rrdma[0]); smb_timer = (u16)hw->smb_timer; mtu_with_eth = hw->mtu + ALX_ETH_LENGTH_OF_HEADER; int_mod = hw->imt; hash_legacy = true; if (l1f_init_mac(hw, addr, txmem_hi, txmem_lo, tx_qnum, txring_sz, rxmem_hi, rfdmem_lo, rrdmem_lo, rxring_sz, rxbuf_sz, smb_timer, mtu_with_eth, int_mod, hash_legacy)) { alx_hw_err(hw, "error when config mac\n"); retval = -EINVAL; } return retval; } /** * alf_get_mac_addr * @hw: pointer to hardware structure **/ static int alf_get_mac_addr(struct alx_hw *hw, u8 *addr) { int retval = 0; if (l1f_get_perm_macaddr(hw, addr)) { alx_hw_err(hw, "error when get permanent mac address\n"); retval = -EINVAL; } return retval; } static int alf_reset_pcie(struct alx_hw *hw, bool l0s_en, bool l1_en) { int retval = 0; if (!CHK_HW_FLAG(L0S_CAP)) l0s_en = false; if (l0s_en) SET_HW_FLAG(L0S_EN); else CLI_HW_FLAG(L0S_EN); if (!CHK_HW_FLAG(L1_CAP)) l1_en = false; if (l1_en) SET_HW_FLAG(L1_EN); else CLI_HW_FLAG(L1_EN); if (l1f_reset_pcie(hw, l0s_en, l1_en)) { alx_hw_err(hw, "error when reset pcie\n"); retval = -EINVAL; } return retval; } static int alf_config_aspm(struct alx_hw *hw, bool l0s_en, bool l1_en) { int retval = 0; if (!CHK_HW_FLAG(L0S_CAP)) l0s_en = false; if (l0s_en) SET_HW_FLAG(L0S_EN); else CLI_HW_FLAG(L0S_EN); if (!CHK_HW_FLAG(L1_CAP)) l1_en = false; if (l1_en) SET_HW_FLAG(L1_EN); else CLI_HW_FLAG(L1_EN); if (l1f_enable_aspm(hw, l0s_en, l1_en, 0)) { alx_hw_err(hw, "error when enable aspm\n"); retval = -EINVAL; } return retval; } static int alf_config_wol(struct alx_hw *hw, u32 wufc) { u32 wol; int retval = 0; wol = 0; /* turn on magic packet event */ if (wufc & ALX_WOL_MAGIC) { wol |= L1F_WOL0_MAGIC_EN | L1F_WOL0_PME_MAGIC_EN; /* magic packet maybe Broadcast&multicast&Unicast frame */ /* mac |= MAC_CTRL_BC_EN; */ } /* turn on link up event */ if (wufc & ALX_WOL_PHY) { wol |= L1F_WOL0_LINK_EN | L1F_WOL0_PME_LINK; /* only link up can wake up */ retval = alf_write_phy_reg(hw, L1F_MII_IER, L1F_IER_LINK_UP); } alx_mem_w32(hw, L1F_WOL0, wol); return retval; } static int alf_config_mac_ctrl(struct alx_hw *hw) { u32 mac; alx_mem_r32(hw, L1F_MAC_CTRL, &mac); /* enable/disable VLAN tag insert,strip */ if (CHK_HW_FLAG(VLANSTRIP_EN)) mac |= L1F_MAC_CTRL_VLANSTRIP; else mac &= ~L1F_MAC_CTRL_VLANSTRIP; if (CHK_HW_FLAG(PROMISC_EN)) mac |= L1F_MAC_CTRL_PROMISC_EN; else mac &= ~L1F_MAC_CTRL_PROMISC_EN; if (CHK_HW_FLAG(MULTIALL_EN)) mac |= L1F_MAC_CTRL_MULTIALL_EN; else mac &= ~L1F_MAC_CTRL_MULTIALL_EN; if (CHK_HW_FLAG(LOOPBACK_EN)) mac |= L1F_MAC_CTRL_LPBACK_EN; else mac &= ~L1F_MAC_CTRL_LPBACK_EN; alx_mem_w32(hw, L1F_MAC_CTRL, mac); return 0; } static int alf_config_pow_save(struct alx_hw *hw, u32 speed, bool wol_en, bool tx_en, bool rx_en, bool pws_en) { u8 wire_spd = LX_LC_10H; int retval = 0; switch (speed) { case ALX_LINK_SPEED_UNKNOWN: case ALX_LINK_SPEED_10_HALF: wire_spd = LX_LC_10H; break; case ALX_LINK_SPEED_10_FULL: wire_spd = LX_LC_10F; break; case ALX_LINK_SPEED_100_HALF: wire_spd = LX_LC_100H; break; case ALX_LINK_SPEED_100_FULL: wire_spd = LX_LC_100F; break; case ALX_LINK_SPEED_1GB_FULL: wire_spd = LX_LC_1000F; break; } if (l1f_powersaving(hw, wire_spd, wol_en, tx_en, rx_en, pws_en)) { alx_hw_err(hw, "error when set power saving\n"); retval = -EINVAL; } return retval; } /* RAR, Multicast, VLAN */ static int alf_set_mac_addr(struct alx_hw *hw, u8 *addr) { u32 sta; /* * for example: 00-0B-6A-F6-00-DC * 0<-->6AF600DC, 1<-->000B. */ /* low dword */ sta = (((u32)addr[2]) << 24) | (((u32)addr[3]) << 16) | (((u32)addr[4]) << 8) | (((u32)addr[5])) ; alx_mem_w32(hw, L1F_STAD0, sta); /* hight dword */ sta = (((u32)addr[0]) << 8) | (((u32)addr[1])) ; alx_mem_w32(hw, L1F_STAD1, sta); return 0; } static int alf_set_mc_addr(struct alx_hw *hw, u8 *addr) { u32 crc32, bit, reg, mta; /* * set hash value for a multicast address hash calcu processing. * 1. calcu 32bit CRC for multicast address * 2. reverse crc with MSB to LSB */ crc32 = ALX_ETH_CRC(addr, ALX_ETH_LENGTH_OF_ADDRESS); /* * 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; alx_mem_r32(hw, L1F_HASH_TBL0 + (reg<<2), &mta); mta |= (0x1 << bit); alx_mem_w32(hw, L1F_HASH_TBL0 + (reg<<2), mta); return 0; } static int alf_clear_mc_addr(struct alx_hw *hw) { alx_mem_w32(hw, L1F_HASH_TBL0, 0); alx_mem_w32(hw, L1F_HASH_TBL1, 0); return 0; } /* RTX, IRQ */ static int alf_config_tx(struct alx_hw *hw) { u32 wrr; alx_mem_r32(hw, L1F_WRR, &wrr); switch (hw->wrr_mode) { case alx_wrr_mode_none: FIELD_SETL(wrr, L1F_WRR_PRI, L1F_WRR_PRI_RESTRICT_NONE); break; case alx_wrr_mode_high: FIELD_SETL(wrr, L1F_WRR_PRI, L1F_WRR_PRI_RESTRICT_HI); break; case alx_wrr_mode_high2: FIELD_SETL(wrr, L1F_WRR_PRI, L1F_WRR_PRI_RESTRICT_HI2); break; case alx_wrr_mode_all: FIELD_SETL(wrr, L1F_WRR_PRI, L1F_WRR_PRI_RESTRICT_ALL); break; } FIELD_SETL(wrr, L1F_WRR_PRI0, hw->wrr_prio0); FIELD_SETL(wrr, L1F_WRR_PRI1, hw->wrr_prio1); FIELD_SETL(wrr, L1F_WRR_PRI2, hw->wrr_prio2); FIELD_SETL(wrr, L1F_WRR_PRI3, hw->wrr_prio3); alx_mem_w32(hw, L1F_WRR, wrr); return 0; } static int alf_config_msix(struct alx_hw *hw, u16 num_intrs, bool msix_en, bool msi_en) { u32 map[2]; u32 type; int msix_idx; if (!msix_en) goto configure_legacy; memset(map, 0, sizeof(map)); for (msix_idx = 0; msix_idx < num_intrs; msix_idx++) { switch (msix_idx) { case ALF_MSIX_INDEX_RXQ0: FIELD_SETL(map[0], L1F_MSI_MAP_TBL1_RXQ0, ALF_MSIX_INDEX_RXQ0); break; case ALF_MSIX_INDEX_RXQ1: FIELD_SETL(map[0], L1F_MSI_MAP_TBL1_RXQ1, ALF_MSIX_INDEX_RXQ1); break; case ALF_MSIX_INDEX_RXQ2: FIELD_SETL(map[0], L1F_MSI_MAP_TBL1_RXQ2, ALF_MSIX_INDEX_RXQ2); break; case ALF_MSIX_INDEX_RXQ3: FIELD_SETL(map[0], L1F_MSI_MAP_TBL1_RXQ3, ALF_MSIX_INDEX_RXQ3); break; case ALF_MSIX_INDEX_RXQ4: FIELD_SETL(map[1], L1F_MSI_MAP_TBL2_RXQ4, ALF_MSIX_INDEX_RXQ4); break; case ALF_MSIX_INDEX_RXQ5: FIELD_SETL(map[1], L1F_MSI_MAP_TBL2_RXQ5, ALF_MSIX_INDEX_RXQ5); break; case ALF_MSIX_INDEX_RXQ6: FIELD_SETL(map[1], L1F_MSI_MAP_TBL2_RXQ6, ALF_MSIX_INDEX_RXQ6); break; case ALF_MSIX_INDEX_RXQ7: FIELD_SETL(map[1], L1F_MSI_MAP_TBL2_RXQ7, ALF_MSIX_INDEX_RXQ7); break; case ALF_MSIX_INDEX_TXQ0: FIELD_SETL(map[0], L1F_MSI_MAP_TBL1_TXQ0, ALF_MSIX_INDEX_TXQ0); break; case ALF_MSIX_INDEX_TXQ1: FIELD_SETL(map[0], L1F_MSI_MAP_TBL1_TXQ1, ALF_MSIX_INDEX_TXQ1); break; case ALF_MSIX_INDEX_TXQ2: FIELD_SETL(map[1], L1F_MSI_MAP_TBL2_TXQ2, ALF_MSIX_INDEX_TXQ2); break; case ALF_MSIX_INDEX_TXQ3: FIELD_SETL(map[1], L1F_MSI_MAP_TBL2_TXQ3, ALF_MSIX_INDEX_TXQ3); break; case ALF_MSIX_INDEX_TIMER: FIELD_SETL(map[0], L1F_MSI_MAP_TBL1_TIMER, ALF_MSIX_INDEX_TIMER); break; case ALF_MSIX_INDEX_ALERT: FIELD_SETL(map[0], L1F_MSI_MAP_TBL1_ALERT, ALF_MSIX_INDEX_ALERT); break; case ALF_MSIX_INDEX_SMB: FIELD_SETL(map[1], L1F_MSI_MAP_TBL2_SMB, ALF_MSIX_INDEX_SMB); break; case ALF_MSIX_INDEX_PHY: FIELD_SETL(map[1], L1F_MSI_MAP_TBL2_PHY, ALF_MSIX_INDEX_PHY); break; default: break; } } alx_mem_w32(hw, L1F_MSI_MAP_TBL1, map[0]); alx_mem_w32(hw, L1F_MSI_MAP_TBL2, map[1]); /* 0 to alert, 1 to timer */ type = (L1F_MSI_ID_MAP_DMAW | L1F_MSI_ID_MAP_DMAR | L1F_MSI_ID_MAP_PCIELNKDW | L1F_MSI_ID_MAP_PCIECERR | L1F_MSI_ID_MAP_PCIENFERR | L1F_MSI_ID_MAP_PCIEFERR | L1F_MSI_ID_MAP_PCIEUR); alx_mem_w32(hw, L1F_MSI_ID_MAP, type); return 0; configure_legacy: alx_mem_w32(hw, L1F_MSI_MAP_TBL1, 0x0); alx_mem_w32(hw, L1F_MSI_MAP_TBL2, 0x0); alx_mem_w32(hw, L1F_MSI_ID_MAP, 0x0); if (msi_en) { u32 msi; alx_mem_r32(hw, 0x1920, &msi); msi |= 0x10000; alx_mem_w32(hw, 0x1920, msi); } return 0; } /* * Interrupt */ static int alf_ack_phy_intr(struct alx_hw *hw) { u16 isr; return alf_read_phy_reg(hw, L1F_MII_ISR, &isr); } static int alf_enable_legacy_intr(struct alx_hw *hw) { u16 cmd; alx_cfg_r16(hw, PCI_COMMAND, &cmd); cmd &= ~PCI_COMMAND_INTX_DISABLE; alx_cfg_w16(hw, PCI_COMMAND, cmd); alx_mem_w32(hw, L1F_ISR, ~((u32) L1F_ISR_DIS)); alx_mem_w32(hw, L1F_IMR, hw->intr_mask); return 0; } static int alf_disable_legacy_intr(struct alx_hw *hw) { alx_mem_w32(hw, L1F_ISR, L1F_ISR_DIS); alx_mem_w32(hw, L1F_IMR, 0); alx_mem_flush(hw); return 0; } static int alf_enable_msix_intr(struct alx_hw *hw, u8 entry_idx) { u32 ctrl_reg; ctrl_reg = ALF_MSIX_ENTRY_BASE + (entry_idx * ALF_MSIX_ENTRY_SIZE) + ALF_MSIX_MSG_CTRL_OFF; alx_mem_w32(hw, ctrl_reg, 0x0); alx_mem_flush(hw); return 0; } static int alf_disable_msix_intr(struct alx_hw *hw, u8 entry_idx) { u32 ctrl_reg; ctrl_reg = ALF_MSIX_ENTRY_BASE + (entry_idx * ALF_MSIX_ENTRY_SIZE) + ALF_MSIX_MSG_CTRL_OFF; alx_mem_w32(hw, ctrl_reg, 0x1); alx_mem_flush(hw); return 0; } /* RSS */ static int alf_config_rss(struct alx_hw *hw, bool rss_en) { int key_len_by_u8 = sizeof(hw->rss_key); int idt_len_by_u32 = sizeof(hw->rss_idt) / sizeof(u32); u32 rxq0; int i; /* Fill out hash function keys */ for (i = 0; i < key_len_by_u8; i++) { alx_mem_w8(hw, ALF_RSS_KEY(i, u8), hw->rss_key[key_len_by_u8 - i - 1]); } /* Fill out redirection table */ for (i = 0; i < idt_len_by_u32; i++) alx_mem_w32(hw, ALF_RSS_TBL(i, u32), hw->rss_idt[i]); alx_mem_w32(hw, L1F_RSS_BASE_CPU_NUM, hw->rss_base_cpu); alx_mem_r32(hw, L1F_RXQ0, &rxq0); if (hw->rss_hstype & ALX_RSS_HSTYP_IPV4_EN) rxq0 |= L1F_RXQ0_RSS_HSTYP_IPV4_EN; else rxq0 &= ~L1F_RXQ0_RSS_HSTYP_IPV4_EN; if (hw->rss_hstype & ALX_RSS_HSTYP_TCP4_EN) rxq0 |= L1F_RXQ0_RSS_HSTYP_IPV4_TCP_EN; else rxq0 &= ~L1F_RXQ0_RSS_HSTYP_IPV4_TCP_EN; if (hw->rss_hstype & ALX_RSS_HSTYP_IPV6_EN) rxq0 |= L1F_RXQ0_RSS_HSTYP_IPV6_EN; else rxq0 &= ~L1F_RXQ0_RSS_HSTYP_IPV6_EN; if (hw->rss_hstype & ALX_RSS_HSTYP_TCP6_EN) rxq0 |= L1F_RXQ0_RSS_HSTYP_IPV6_TCP_EN; else rxq0 &= ~L1F_RXQ0_RSS_HSTYP_IPV6_TCP_EN; FIELD_SETL(rxq0, L1F_RXQ0_RSS_MODE, hw->rss_mode); FIELD_SETL(rxq0, L1F_RXQ0_IDT_TBL_SIZE, hw->rss_idt_size); if (rss_en) rxq0 |= L1F_RXQ0_RSS_HASH_EN; else rxq0 &= ~L1F_RXQ0_RSS_HASH_EN; alx_mem_w32(hw, L1F_RXQ0, rxq0); return 0; } /* fc */ static int alf_get_fc_mode(struct alx_hw *hw, enum alx_fc_mode *mode) { u16 bmsr, giga; int i; int retval = 0; for (i = 0; i < ALX_MAX_SETUP_LNK_CYCLE; i++) { alf_read_phy_reg(hw, MII_BMSR, &bmsr); alf_read_phy_reg(hw, MII_BMSR, &bmsr); if (bmsr & BMSR_LSTATUS) { /* Read phy Specific Status Register (17) */ retval = alf_read_phy_reg(hw, L1F_MII_GIGA_PSSR, &giga); if (retval) return retval; if (!(giga & L1F_GIGA_PSSR_SPD_DPLX_RESOLVED)) { alx_hw_err(hw, "error for speed duplex resolved\n"); return -EINVAL; } if ((giga & L1F_GIGA_PSSR_FC_TXEN) && (giga & L1F_GIGA_PSSR_FC_RXEN)) { *mode = alx_fc_full; } else if (giga & L1F_GIGA_PSSR_FC_TXEN) { *mode = alx_fc_tx_pause; } else if (giga & L1F_GIGA_PSSR_FC_RXEN) { *mode = alx_fc_rx_pause; } else { *mode = alx_fc_none; } break; } mdelay(100); } if (i == ALX_MAX_SETUP_LNK_CYCLE) { alx_hw_err(hw, "error when get flow control mode\n"); retval = -EINVAL; } return retval; } static int alf_config_fc(struct alx_hw *hw) { u32 mac; int retval = 0; if (hw->disable_fc_autoneg) { hw->fc_was_autonegged = false; hw->cur_fc_mode = hw->req_fc_mode; } else { hw->fc_was_autonegged = true; retval = alf_get_fc_mode(hw, &hw->cur_fc_mode); if (retval) return retval; } alx_mem_r32(hw, L1F_MAC_CTRL, &mac); switch (hw->cur_fc_mode) { case alx_fc_none: /* 0 */ mac &= ~(L1F_MAC_CTRL_RXFC_EN | L1F_MAC_CTRL_TXFC_EN); break; case alx_fc_rx_pause: /* 1 */ mac &= ~L1F_MAC_CTRL_TXFC_EN; mac |= L1F_MAC_CTRL_RXFC_EN; break; case alx_fc_tx_pause: /* 2 */ mac |= L1F_MAC_CTRL_TXFC_EN; mac &= ~L1F_MAC_CTRL_RXFC_EN; break; case alx_fc_full: /* 3 */ case alx_fc_default: /* 4 */ mac |= (L1F_MAC_CTRL_TXFC_EN | L1F_MAC_CTRL_RXFC_EN); break; default: alx_hw_err(hw, "flow control param set incorrectly\n"); return -EINVAL; break; } alx_mem_w32(hw, L1F_MAC_CTRL, mac); return retval; } /* * NVRam */ static int alf_check_nvram(struct alx_hw *hw, bool *exist) { *exist = false; return 0; } /* ethtool */ static int alf_get_ethtool_regs(struct alx_hw *hw, void *buff) { int i; u32 *val = buff; static const u32 reg[] = { /* 0 */ L1F_DEV_CAP, L1F_DEV_CTRL, L1F_LNK_CAP, L1F_LNK_CTRL, L1F_UE_SVRT, L1F_EFLD, L1F_SLD, L1F_PPHY_MISC1, L1F_PPHY_MISC2, L1F_PDLL_TRNS1, /* 10 */ L1F_TLEXTN_STATS, L1F_EFUSE_CTRL, L1F_EFUSE_DATA, L1F_SPI_OP1, L1F_SPI_OP2, L1F_SPI_OP3, L1F_EF_CTRL, L1F_EF_ADDR, L1F_EF_DATA, L1F_SPI_ID, /* 20 */ L1F_SPI_CFG_START, L1F_PMCTRL, L1F_LTSSM_CTRL, L1F_MASTER, L1F_MANU_TIMER, L1F_IRQ_MODU_TIMER, L1F_PHY_CTRL, L1F_MAC_STS, L1F_MDIO, L1F_MDIO_EXTN, /* 30 */ L1F_PHY_STS, L1F_BIST0, L1F_BIST1, L1F_SERDES, L1F_LED_CTRL, L1F_LED_PATN, L1F_LED_PATN2, L1F_SYSALV, L1F_PCIERR_INST, L1F_LPI_DECISN_TIMER, /* 40 */ L1F_LPI_CTRL, L1F_LPI_WAIT, L1F_HRTBT_VLAN, L1F_HRTBT_CTRL, L1F_RXPARSE, L1F_MAC_CTRL, L1F_GAP, L1F_STAD1, L1F_LED_CTRL, L1F_HASH_TBL0, /* 50 */ L1F_HASH_TBL1, L1F_HALFD, L1F_DMA, L1F_WOL0, L1F_WOL1, L1F_WOL2, L1F_WRR, L1F_HQTPD, L1F_CPUMAP1, L1F_CPUMAP2, /* 60 */ L1F_MISC, L1F_RX_BASE_ADDR_HI, L1F_RFD_ADDR_LO, L1F_RFD_RING_SZ, L1F_RFD_BUF_SZ, L1F_RRD_ADDR_LO, L1F_RRD_RING_SZ, L1F_RFD_PIDX, L1F_RFD_CIDX, L1F_RXQ0, /* 70 */ L1F_RXQ1, L1F_RXQ2, L1F_RXQ3, L1F_TX_BASE_ADDR_HI, L1F_TPD_PRI0_ADDR_LO, L1F_TPD_PRI1_ADDR_LO, L1F_TPD_PRI2_ADDR_LO, L1F_TPD_PRI3_ADDR_LO, L1F_TPD_PRI0_PIDX, L1F_TPD_PRI1_PIDX, /* 80 */ L1F_TPD_PRI2_PIDX, L1F_TPD_PRI3_PIDX, L1F_TPD_PRI0_CIDX, L1F_TPD_PRI1_CIDX, L1F_TPD_PRI2_CIDX, L1F_TPD_PRI3_CIDX, L1F_TPD_RING_SZ, L1F_TXQ0, L1F_TXQ1, L1F_TXQ2, /* 90 */ L1F_MSI_MAP_TBL1, L1F_MSI_MAP_TBL2, L1F_MSI_ID_MAP, L1F_MSIX_MASK, L1F_MSIX_PENDING, }; for (i = 0; i < ARRAY_SIZE(reg); i++) alx_mem_r32(hw, reg[i], &val[i]); /* SRAM */ for (i = 0; i < 16; i++) alx_mem_r32(hw, ALF_SRAM(i, u32), &val[100 + i]); /* RSS */ for (i = 0; i < 10; i++) alx_mem_r32(hw, ALF_RSS_KEY(i, u32), &val[120 + i]); for (i = 0; i < 32; i++) alx_mem_r32(hw, ALF_RSS_TBL(i, u32), &val[130 + i]); alx_mem_r32(hw, L1F_RSS_HASH_VAL, &val[162]); alx_mem_r32(hw, L1F_RSS_HASH_FLAG, &val[163]); alx_mem_r32(hw, L1F_RSS_BASE_CPU_NUM, &val[164]); /* MIB */ for (i = 0; i < 48; i++) alx_mem_r32(hw, ALF_MIB(i, u32), &val[170 + i]); return 0; } /******************************************************************************/ static int alf_set_hw_capabilities(struct alx_hw *hw) { SET_HW_FLAG(L0S_CAP); SET_HW_FLAG(L1_CAP); if (hw->mac_type == alx_mac_l1f) SET_HW_FLAG(GIGA_CAP); /* set flags of alx_phy_info */ SET_HW_FLAG(PWSAVE_CAP); return 0; } /* alc_set_hw_info */ static int alf_set_hw_infos(struct alx_hw *hw) { hw->rxstat_reg = L1F_MIB_RX_OK; hw->rxstat_sz = 0x60; hw->txstat_reg = L1F_MIB_TX_OK; hw->txstat_sz = 0x68; hw->rx_prod_reg[0] = L1F_RFD_PIDX; hw->rx_cons_reg[0] = L1F_RFD_CIDX; hw->tx_prod_reg[0] = L1F_TPD_PRI0_PIDX; hw->tx_cons_reg[0] = L1F_TPD_PRI0_CIDX; hw->tx_prod_reg[1] = L1F_TPD_PRI1_PIDX; hw->tx_cons_reg[1] = L1F_TPD_PRI1_CIDX; hw->tx_prod_reg[2] = L1F_TPD_PRI2_PIDX; hw->tx_cons_reg[2] = L1F_TPD_PRI2_CIDX; hw->tx_prod_reg[3] = L1F_TPD_PRI3_PIDX; hw->tx_cons_reg[3] = L1F_TPD_PRI3_CIDX; hw->hwreg_sz = 0x200; hw->eeprom_sz = 0; return 0; } /* * alf_init_hw_callbacks */ int alf_init_hw_callbacks(struct alx_hw *hw) { /* NIC */ hw->cbs.identify_nic = &alf_identify_nic; /* MAC */ hw->cbs.reset_mac = &alf_reset_mac; hw->cbs.start_mac = &alf_start_mac; hw->cbs.stop_mac = &alf_stop_mac; hw->cbs.config_mac = &alf_config_mac; hw->cbs.get_mac_addr = &alf_get_mac_addr; hw->cbs.set_mac_addr = &alf_set_mac_addr; hw->cbs.set_mc_addr = &alf_set_mc_addr; hw->cbs.clear_mc_addr = &alf_clear_mc_addr; /* PHY */ hw->cbs.init_phy = &alf_init_phy; hw->cbs.reset_phy = &alf_reset_phy; hw->cbs.read_phy_reg = &alf_read_phy_reg; hw->cbs.write_phy_reg = &alf_write_phy_reg; hw->cbs.check_phy_link = &alf_check_phy_link; hw->cbs.setup_phy_link = &alf_setup_phy_link; hw->cbs.setup_phy_link_speed = &alf_setup_phy_link_speed; /* Interrupt */ hw->cbs.ack_phy_intr = &alf_ack_phy_intr; hw->cbs.enable_legacy_intr = &alf_enable_legacy_intr; hw->cbs.disable_legacy_intr = &alf_disable_legacy_intr; hw->cbs.enable_msix_intr = &alf_enable_msix_intr; hw->cbs.disable_msix_intr = &alf_disable_msix_intr; /* Configure */ hw->cbs.config_tx = &alf_config_tx; hw->cbs.config_fc = &alf_config_fc; hw->cbs.config_rss = &alf_config_rss; hw->cbs.config_msix = &alf_config_msix; hw->cbs.config_wol = &alf_config_wol; hw->cbs.config_aspm = &alf_config_aspm; hw->cbs.config_mac_ctrl = &alf_config_mac_ctrl; hw->cbs.config_pow_save = &alf_config_pow_save; hw->cbs.reset_pcie = &alf_reset_pcie; /* NVRam */ hw->cbs.check_nvram = &alf_check_nvram; /* Others */ hw->cbs.get_ethtool_regs = alf_get_ethtool_regs; alf_set_hw_capabilities(hw); alf_set_hw_infos(hw); alx_hw_info(hw, "HW Flags = 0x%x\n", hw->flags); return 0; }