162306a36Sopenharmony_ci/* 262306a36Sopenharmony_ci * Copyright (c) 2013 Johannes Berg <johannes@sipsolutions.net> 362306a36Sopenharmony_ci * 462306a36Sopenharmony_ci * This file is free software: you may copy, redistribute and/or modify it 562306a36Sopenharmony_ci * under the terms of the GNU General Public License as published by the 662306a36Sopenharmony_ci * Free Software Foundation, either version 2 of the License, or (at your 762306a36Sopenharmony_ci * option) any later version. 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * This file is distributed in the hope that it will be useful, but 1062306a36Sopenharmony_ci * WITHOUT ANY WARRANTY; without even the implied warranty of 1162306a36Sopenharmony_ci * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 1262306a36Sopenharmony_ci * General Public License for more details. 1362306a36Sopenharmony_ci * 1462306a36Sopenharmony_ci * You should have received a copy of the GNU General Public License 1562306a36Sopenharmony_ci * along with this program. If not, see <http://www.gnu.org/licenses/>. 1662306a36Sopenharmony_ci * 1762306a36Sopenharmony_ci * This file incorporates work covered by the following copyright and 1862306a36Sopenharmony_ci * permission notice: 1962306a36Sopenharmony_ci * 2062306a36Sopenharmony_ci * Copyright (c) 2012 Qualcomm Atheros, Inc. 2162306a36Sopenharmony_ci * 2262306a36Sopenharmony_ci * Permission to use, copy, modify, and/or distribute this software for any 2362306a36Sopenharmony_ci * purpose with or without fee is hereby granted, provided that the above 2462306a36Sopenharmony_ci * copyright notice and this permission notice appear in all copies. 2562306a36Sopenharmony_ci * 2662306a36Sopenharmony_ci * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 2762306a36Sopenharmony_ci * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 2862306a36Sopenharmony_ci * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 2962306a36Sopenharmony_ci * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 3062306a36Sopenharmony_ci * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 3162306a36Sopenharmony_ci * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 3262306a36Sopenharmony_ci * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 3362306a36Sopenharmony_ci */ 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci#include <linux/pci.h> 3662306a36Sopenharmony_ci#include <linux/ip.h> 3762306a36Sopenharmony_ci#include <linux/tcp.h> 3862306a36Sopenharmony_ci#include <linux/netdevice.h> 3962306a36Sopenharmony_ci#include <linux/etherdevice.h> 4062306a36Sopenharmony_ci#include <linux/ethtool.h> 4162306a36Sopenharmony_ci#include <linux/mdio.h> 4262306a36Sopenharmony_ci#include <linux/interrupt.h> 4362306a36Sopenharmony_ci#include <asm/byteorder.h> 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci#include "alx.h" 4662306a36Sopenharmony_ci#include "reg.h" 4762306a36Sopenharmony_ci#include "hw.h" 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci/* The order of these strings must match the order of the fields in 5062306a36Sopenharmony_ci * struct alx_hw_stats 5162306a36Sopenharmony_ci * See hw.h 5262306a36Sopenharmony_ci */ 5362306a36Sopenharmony_cistatic const char alx_gstrings_stats[][ETH_GSTRING_LEN] = { 5462306a36Sopenharmony_ci "rx_packets", 5562306a36Sopenharmony_ci "rx_bcast_packets", 5662306a36Sopenharmony_ci "rx_mcast_packets", 5762306a36Sopenharmony_ci "rx_pause_packets", 5862306a36Sopenharmony_ci "rx_ctrl_packets", 5962306a36Sopenharmony_ci "rx_fcs_errors", 6062306a36Sopenharmony_ci "rx_length_errors", 6162306a36Sopenharmony_ci "rx_bytes", 6262306a36Sopenharmony_ci "rx_runt_packets", 6362306a36Sopenharmony_ci "rx_fragments", 6462306a36Sopenharmony_ci "rx_64B_or_less_packets", 6562306a36Sopenharmony_ci "rx_65B_to_127B_packets", 6662306a36Sopenharmony_ci "rx_128B_to_255B_packets", 6762306a36Sopenharmony_ci "rx_256B_to_511B_packets", 6862306a36Sopenharmony_ci "rx_512B_to_1023B_packets", 6962306a36Sopenharmony_ci "rx_1024B_to_1518B_packets", 7062306a36Sopenharmony_ci "rx_1519B_to_mtu_packets", 7162306a36Sopenharmony_ci "rx_oversize_packets", 7262306a36Sopenharmony_ci "rx_rxf_ov_drop_packets", 7362306a36Sopenharmony_ci "rx_rrd_ov_drop_packets", 7462306a36Sopenharmony_ci "rx_align_errors", 7562306a36Sopenharmony_ci "rx_bcast_bytes", 7662306a36Sopenharmony_ci "rx_mcast_bytes", 7762306a36Sopenharmony_ci "rx_address_errors", 7862306a36Sopenharmony_ci "tx_packets", 7962306a36Sopenharmony_ci "tx_bcast_packets", 8062306a36Sopenharmony_ci "tx_mcast_packets", 8162306a36Sopenharmony_ci "tx_pause_packets", 8262306a36Sopenharmony_ci "tx_exc_defer_packets", 8362306a36Sopenharmony_ci "tx_ctrl_packets", 8462306a36Sopenharmony_ci "tx_defer_packets", 8562306a36Sopenharmony_ci "tx_bytes", 8662306a36Sopenharmony_ci "tx_64B_or_less_packets", 8762306a36Sopenharmony_ci "tx_65B_to_127B_packets", 8862306a36Sopenharmony_ci "tx_128B_to_255B_packets", 8962306a36Sopenharmony_ci "tx_256B_to_511B_packets", 9062306a36Sopenharmony_ci "tx_512B_to_1023B_packets", 9162306a36Sopenharmony_ci "tx_1024B_to_1518B_packets", 9262306a36Sopenharmony_ci "tx_1519B_to_mtu_packets", 9362306a36Sopenharmony_ci "tx_single_collision", 9462306a36Sopenharmony_ci "tx_multiple_collisions", 9562306a36Sopenharmony_ci "tx_late_collision", 9662306a36Sopenharmony_ci "tx_abort_collision", 9762306a36Sopenharmony_ci "tx_underrun", 9862306a36Sopenharmony_ci "tx_trd_eop", 9962306a36Sopenharmony_ci "tx_length_errors", 10062306a36Sopenharmony_ci "tx_trunc_packets", 10162306a36Sopenharmony_ci "tx_bcast_bytes", 10262306a36Sopenharmony_ci "tx_mcast_bytes", 10362306a36Sopenharmony_ci "tx_update", 10462306a36Sopenharmony_ci}; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci#define ALX_NUM_STATS ARRAY_SIZE(alx_gstrings_stats) 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_cistatic u32 alx_get_supported_speeds(struct alx_hw *hw) 11062306a36Sopenharmony_ci{ 11162306a36Sopenharmony_ci u32 supported = SUPPORTED_10baseT_Half | 11262306a36Sopenharmony_ci SUPPORTED_10baseT_Full | 11362306a36Sopenharmony_ci SUPPORTED_100baseT_Half | 11462306a36Sopenharmony_ci SUPPORTED_100baseT_Full; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci if (alx_hw_giga(hw)) 11762306a36Sopenharmony_ci supported |= SUPPORTED_1000baseT_Full; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci BUILD_BUG_ON(SUPPORTED_10baseT_Half != ADVERTISED_10baseT_Half); 12062306a36Sopenharmony_ci BUILD_BUG_ON(SUPPORTED_10baseT_Full != ADVERTISED_10baseT_Full); 12162306a36Sopenharmony_ci BUILD_BUG_ON(SUPPORTED_100baseT_Half != ADVERTISED_100baseT_Half); 12262306a36Sopenharmony_ci BUILD_BUG_ON(SUPPORTED_100baseT_Full != ADVERTISED_100baseT_Full); 12362306a36Sopenharmony_ci BUILD_BUG_ON(SUPPORTED_1000baseT_Full != ADVERTISED_1000baseT_Full); 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci return supported; 12662306a36Sopenharmony_ci} 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_cistatic int alx_get_link_ksettings(struct net_device *netdev, 12962306a36Sopenharmony_ci struct ethtool_link_ksettings *cmd) 13062306a36Sopenharmony_ci{ 13162306a36Sopenharmony_ci struct alx_priv *alx = netdev_priv(netdev); 13262306a36Sopenharmony_ci struct alx_hw *hw = &alx->hw; 13362306a36Sopenharmony_ci u32 supported, advertising; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci supported = SUPPORTED_Autoneg | 13662306a36Sopenharmony_ci SUPPORTED_TP | 13762306a36Sopenharmony_ci SUPPORTED_Pause | 13862306a36Sopenharmony_ci SUPPORTED_Asym_Pause; 13962306a36Sopenharmony_ci if (alx_hw_giga(hw)) 14062306a36Sopenharmony_ci supported |= SUPPORTED_1000baseT_Full; 14162306a36Sopenharmony_ci supported |= alx_get_supported_speeds(hw); 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci advertising = ADVERTISED_TP; 14462306a36Sopenharmony_ci if (hw->adv_cfg & ADVERTISED_Autoneg) 14562306a36Sopenharmony_ci advertising |= hw->adv_cfg; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci cmd->base.port = PORT_TP; 14862306a36Sopenharmony_ci cmd->base.phy_address = 0; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci if (hw->adv_cfg & ADVERTISED_Autoneg) 15162306a36Sopenharmony_ci cmd->base.autoneg = AUTONEG_ENABLE; 15262306a36Sopenharmony_ci else 15362306a36Sopenharmony_ci cmd->base.autoneg = AUTONEG_DISABLE; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci if (hw->flowctrl & ALX_FC_ANEG && hw->adv_cfg & ADVERTISED_Autoneg) { 15662306a36Sopenharmony_ci if (hw->flowctrl & ALX_FC_RX) { 15762306a36Sopenharmony_ci advertising |= ADVERTISED_Pause; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci if (!(hw->flowctrl & ALX_FC_TX)) 16062306a36Sopenharmony_ci advertising |= ADVERTISED_Asym_Pause; 16162306a36Sopenharmony_ci } else if (hw->flowctrl & ALX_FC_TX) { 16262306a36Sopenharmony_ci advertising |= ADVERTISED_Asym_Pause; 16362306a36Sopenharmony_ci } 16462306a36Sopenharmony_ci } 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci mutex_lock(&alx->mtx); 16762306a36Sopenharmony_ci cmd->base.speed = hw->link_speed; 16862306a36Sopenharmony_ci cmd->base.duplex = hw->duplex; 16962306a36Sopenharmony_ci mutex_unlock(&alx->mtx); 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.supported, 17262306a36Sopenharmony_ci supported); 17362306a36Sopenharmony_ci ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.advertising, 17462306a36Sopenharmony_ci advertising); 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci return 0; 17762306a36Sopenharmony_ci} 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_cistatic int alx_set_link_ksettings(struct net_device *netdev, 18062306a36Sopenharmony_ci const struct ethtool_link_ksettings *cmd) 18162306a36Sopenharmony_ci{ 18262306a36Sopenharmony_ci struct alx_priv *alx = netdev_priv(netdev); 18362306a36Sopenharmony_ci struct alx_hw *hw = &alx->hw; 18462306a36Sopenharmony_ci u32 adv_cfg; 18562306a36Sopenharmony_ci u32 advertising; 18662306a36Sopenharmony_ci int ret; 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci ethtool_convert_link_mode_to_legacy_u32(&advertising, 18962306a36Sopenharmony_ci cmd->link_modes.advertising); 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci if (cmd->base.autoneg == AUTONEG_ENABLE) { 19262306a36Sopenharmony_ci if (advertising & ~alx_get_supported_speeds(hw)) 19362306a36Sopenharmony_ci return -EINVAL; 19462306a36Sopenharmony_ci adv_cfg = advertising | ADVERTISED_Autoneg; 19562306a36Sopenharmony_ci } else { 19662306a36Sopenharmony_ci adv_cfg = alx_speed_to_ethadv(cmd->base.speed, 19762306a36Sopenharmony_ci cmd->base.duplex); 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci if (!adv_cfg || adv_cfg == ADVERTISED_1000baseT_Full) 20062306a36Sopenharmony_ci return -EINVAL; 20162306a36Sopenharmony_ci } 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci hw->adv_cfg = adv_cfg; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci mutex_lock(&alx->mtx); 20662306a36Sopenharmony_ci ret = alx_setup_speed_duplex(hw, adv_cfg, hw->flowctrl); 20762306a36Sopenharmony_ci mutex_unlock(&alx->mtx); 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci return ret; 21062306a36Sopenharmony_ci} 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_cistatic void alx_get_pauseparam(struct net_device *netdev, 21362306a36Sopenharmony_ci struct ethtool_pauseparam *pause) 21462306a36Sopenharmony_ci{ 21562306a36Sopenharmony_ci struct alx_priv *alx = netdev_priv(netdev); 21662306a36Sopenharmony_ci struct alx_hw *hw = &alx->hw; 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci mutex_lock(&alx->mtx); 21962306a36Sopenharmony_ci pause->autoneg = !!(hw->flowctrl & ALX_FC_ANEG && 22062306a36Sopenharmony_ci hw->adv_cfg & ADVERTISED_Autoneg); 22162306a36Sopenharmony_ci pause->tx_pause = !!(hw->flowctrl & ALX_FC_TX); 22262306a36Sopenharmony_ci pause->rx_pause = !!(hw->flowctrl & ALX_FC_RX); 22362306a36Sopenharmony_ci mutex_unlock(&alx->mtx); 22462306a36Sopenharmony_ci} 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_cistatic int alx_set_pauseparam(struct net_device *netdev, 22862306a36Sopenharmony_ci struct ethtool_pauseparam *pause) 22962306a36Sopenharmony_ci{ 23062306a36Sopenharmony_ci struct alx_priv *alx = netdev_priv(netdev); 23162306a36Sopenharmony_ci struct alx_hw *hw = &alx->hw; 23262306a36Sopenharmony_ci int err = 0; 23362306a36Sopenharmony_ci bool reconfig_phy = false; 23462306a36Sopenharmony_ci u8 fc = 0; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci if (pause->tx_pause) 23762306a36Sopenharmony_ci fc |= ALX_FC_TX; 23862306a36Sopenharmony_ci if (pause->rx_pause) 23962306a36Sopenharmony_ci fc |= ALX_FC_RX; 24062306a36Sopenharmony_ci if (pause->autoneg) 24162306a36Sopenharmony_ci fc |= ALX_FC_ANEG; 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci mutex_lock(&alx->mtx); 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci /* restart auto-neg for auto-mode */ 24662306a36Sopenharmony_ci if (hw->adv_cfg & ADVERTISED_Autoneg) { 24762306a36Sopenharmony_ci if (!((fc ^ hw->flowctrl) & ALX_FC_ANEG)) 24862306a36Sopenharmony_ci reconfig_phy = true; 24962306a36Sopenharmony_ci if (fc & hw->flowctrl & ALX_FC_ANEG && 25062306a36Sopenharmony_ci (fc ^ hw->flowctrl) & (ALX_FC_RX | ALX_FC_TX)) 25162306a36Sopenharmony_ci reconfig_phy = true; 25262306a36Sopenharmony_ci } 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci if (reconfig_phy) { 25562306a36Sopenharmony_ci err = alx_setup_speed_duplex(hw, hw->adv_cfg, fc); 25662306a36Sopenharmony_ci if (err) { 25762306a36Sopenharmony_ci mutex_unlock(&alx->mtx); 25862306a36Sopenharmony_ci return err; 25962306a36Sopenharmony_ci } 26062306a36Sopenharmony_ci } 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci /* flow control on mac */ 26362306a36Sopenharmony_ci if ((fc ^ hw->flowctrl) & (ALX_FC_RX | ALX_FC_TX)) 26462306a36Sopenharmony_ci alx_cfg_mac_flowcontrol(hw, fc); 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci hw->flowctrl = fc; 26762306a36Sopenharmony_ci mutex_unlock(&alx->mtx); 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci return 0; 27062306a36Sopenharmony_ci} 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_cistatic u32 alx_get_msglevel(struct net_device *netdev) 27362306a36Sopenharmony_ci{ 27462306a36Sopenharmony_ci struct alx_priv *alx = netdev_priv(netdev); 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci return alx->msg_enable; 27762306a36Sopenharmony_ci} 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_cistatic void alx_set_msglevel(struct net_device *netdev, u32 data) 28062306a36Sopenharmony_ci{ 28162306a36Sopenharmony_ci struct alx_priv *alx = netdev_priv(netdev); 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci alx->msg_enable = data; 28462306a36Sopenharmony_ci} 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_cistatic void alx_get_ethtool_stats(struct net_device *netdev, 28762306a36Sopenharmony_ci struct ethtool_stats *estats, u64 *data) 28862306a36Sopenharmony_ci{ 28962306a36Sopenharmony_ci struct alx_priv *alx = netdev_priv(netdev); 29062306a36Sopenharmony_ci struct alx_hw *hw = &alx->hw; 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci spin_lock(&alx->stats_lock); 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci alx_update_hw_stats(hw); 29562306a36Sopenharmony_ci BUILD_BUG_ON(sizeof(hw->stats) != ALX_NUM_STATS * sizeof(u64)); 29662306a36Sopenharmony_ci memcpy(data, &hw->stats, sizeof(hw->stats)); 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci spin_unlock(&alx->stats_lock); 29962306a36Sopenharmony_ci} 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_cistatic void alx_get_strings(struct net_device *netdev, u32 stringset, u8 *buf) 30262306a36Sopenharmony_ci{ 30362306a36Sopenharmony_ci switch (stringset) { 30462306a36Sopenharmony_ci case ETH_SS_STATS: 30562306a36Sopenharmony_ci memcpy(buf, &alx_gstrings_stats, sizeof(alx_gstrings_stats)); 30662306a36Sopenharmony_ci break; 30762306a36Sopenharmony_ci default: 30862306a36Sopenharmony_ci WARN_ON(1); 30962306a36Sopenharmony_ci break; 31062306a36Sopenharmony_ci } 31162306a36Sopenharmony_ci} 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_cistatic int alx_get_sset_count(struct net_device *netdev, int sset) 31462306a36Sopenharmony_ci{ 31562306a36Sopenharmony_ci switch (sset) { 31662306a36Sopenharmony_ci case ETH_SS_STATS: 31762306a36Sopenharmony_ci return ALX_NUM_STATS; 31862306a36Sopenharmony_ci default: 31962306a36Sopenharmony_ci return -EINVAL; 32062306a36Sopenharmony_ci } 32162306a36Sopenharmony_ci} 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ciconst struct ethtool_ops alx_ethtool_ops = { 32462306a36Sopenharmony_ci .get_pauseparam = alx_get_pauseparam, 32562306a36Sopenharmony_ci .set_pauseparam = alx_set_pauseparam, 32662306a36Sopenharmony_ci .get_msglevel = alx_get_msglevel, 32762306a36Sopenharmony_ci .set_msglevel = alx_set_msglevel, 32862306a36Sopenharmony_ci .get_link = ethtool_op_get_link, 32962306a36Sopenharmony_ci .get_strings = alx_get_strings, 33062306a36Sopenharmony_ci .get_sset_count = alx_get_sset_count, 33162306a36Sopenharmony_ci .get_ethtool_stats = alx_get_ethtool_stats, 33262306a36Sopenharmony_ci .get_link_ksettings = alx_get_link_ksettings, 33362306a36Sopenharmony_ci .set_link_ksettings = alx_set_link_ksettings, 33462306a36Sopenharmony_ci}; 335