| /* Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program 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. |
| */ |
| |
| /* MSM EMAC Ethernet Controller ethtool support |
| */ |
| |
| #include <linux/ethtool.h> |
| |
| #include "emac.h" |
| #include "emac_hw.h" |
| |
| #define EMAC_MAX_REG_SIZE 10 |
| #define EMAC_STATS_LEN 51 |
| static const char *const emac_ethtool_stat_strings[] = { |
| "rx ok", |
| "rx bcast", |
| "rx mcast", |
| "rx pause", |
| "rx ctrl", |
| "rx fcs err", |
| "rx len err", |
| "rx byte cnt", |
| "rx runt", |
| "rx frag", |
| "rx sz 64", |
| "rx sz 65 127", |
| "rx sz 128 255", |
| "rx sz 256 511", |
| "rx sz 512 1023", |
| "rx sz 1024 1518", |
| "rx sz 1519 max", |
| "rx sz ov", |
| "rx rxf ov", |
| "rx align err", |
| "rx bcast byte cnt", |
| "rx mcast byte cnt", |
| "rx err addr", |
| "rx crc allign", |
| "rx jubbers", |
| "tx ok", |
| "tx bcast", |
| "tx mcast", |
| "tx pause", |
| "tx exc defer", |
| "tx ctrl", |
| "tx defer", |
| "tx byte cnt", |
| "tx sz 64", |
| "tx sz 65 127", |
| "tx sz 128 255", |
| "tx sz 256 511", |
| "tx sz 512 1023", |
| "tx sz 1024 1518", |
| "tx sz 1519 max", |
| "tx 1 col", |
| "tx 2 col", |
| "tx late col", |
| "tx abort col", |
| "tx underrun", |
| "tx rd eop", |
| "tx len err", |
| "tx trunc", |
| "tx bcast byte", |
| "tx mcast byte", |
| "tx col", |
| }; |
| |
| static int emac_get_settings(struct net_device *netdev, |
| struct ethtool_cmd *ecmd) |
| { |
| struct emac_adapter *adpt = netdev_priv(netdev); |
| struct emac_hw *hw = &adpt->hw; |
| |
| ecmd->supported = (SUPPORTED_10baseT_Half | |
| SUPPORTED_10baseT_Full | |
| SUPPORTED_100baseT_Half | |
| SUPPORTED_100baseT_Full | |
| SUPPORTED_1000baseT_Full | |
| SUPPORTED_Autoneg | |
| SUPPORTED_TP); |
| |
| ecmd->advertising = ADVERTISED_TP; |
| if (hw->autoneg) { |
| ecmd->advertising |= ADVERTISED_Autoneg; |
| ecmd->advertising |= hw->autoneg_advertised; |
| ecmd->autoneg = AUTONEG_ENABLE; |
| } else { |
| ecmd->autoneg = AUTONEG_DISABLE; |
| } |
| |
| ecmd->port = PORT_TP; |
| ecmd->phy_address = hw->phy_addr; |
| ecmd->transceiver = XCVR_INTERNAL; |
| |
| if (hw->link_up) { |
| switch (hw->link_speed) { |
| case EMAC_LINK_SPEED_10_HALF: |
| ecmd->speed = SPEED_10; |
| ecmd->duplex = DUPLEX_HALF; |
| break; |
| case EMAC_LINK_SPEED_10_FULL: |
| ecmd->speed = SPEED_10; |
| ecmd->duplex = DUPLEX_FULL; |
| break; |
| case EMAC_LINK_SPEED_100_HALF: |
| ecmd->speed = SPEED_100; |
| ecmd->duplex = DUPLEX_HALF; |
| break; |
| case EMAC_LINK_SPEED_100_FULL: |
| ecmd->speed = SPEED_100; |
| ecmd->duplex = DUPLEX_FULL; |
| break; |
| case EMAC_LINK_SPEED_1GB_FULL: |
| ecmd->speed = SPEED_1000; |
| ecmd->duplex = DUPLEX_FULL; |
| break; |
| default: |
| ecmd->speed = -1; |
| ecmd->duplex = -1; |
| break; |
| } |
| } else { |
| ecmd->speed = -1; |
| ecmd->duplex = -1; |
| } |
| |
| return 0; |
| } |
| |
| static int emac_set_settings(struct net_device *netdev, |
| struct ethtool_cmd *ecmd) |
| { |
| struct emac_adapter *adpt = netdev_priv(netdev); |
| struct emac_hw *hw = &adpt->hw; |
| u32 advertised, old; |
| int retval = 0; |
| bool autoneg; |
| |
| emac_info(adpt, link, "ethtool cmd autoneg %d, speed %d, duplex %d\n", |
| ecmd->autoneg, ecmd->speed, ecmd->duplex); |
| |
| while (TEST_N_SET_FLAG(adpt, ADPT_STATE_RESETTING)) |
| msleep(20); /* Reset might take few 10s of ms */ |
| |
| old = hw->autoneg_advertised; |
| advertised = 0; |
| if (ecmd->autoneg == AUTONEG_ENABLE) { |
| advertised = EMAC_LINK_SPEED_DEFAULT; |
| autoneg = true; |
| } else { |
| u32 speed = ecmd->speed; |
| autoneg = false; |
| if (speed == SPEED_1000) { |
| if (ecmd->duplex != DUPLEX_FULL) { |
| emac_warn(adpt, hw, |
| "1000M half is invalid\n"); |
| CLR_FLAG(adpt, ADPT_STATE_RESETTING); |
| return -EINVAL; |
| } |
| advertised = EMAC_LINK_SPEED_1GB_FULL; |
| } else if (speed == SPEED_100) { |
| if (ecmd->duplex == DUPLEX_FULL) |
| advertised = EMAC_LINK_SPEED_100_FULL; |
| else |
| advertised = EMAC_LINK_SPEED_100_HALF; |
| } else { |
| if (ecmd->duplex == DUPLEX_FULL) |
| advertised = EMAC_LINK_SPEED_10_FULL; |
| else |
| advertised = EMAC_LINK_SPEED_10_HALF; |
| } |
| } |
| |
| if ((hw->autoneg == autoneg) && (hw->autoneg_advertised == advertised)) |
| goto done; |
| |
| retval = emac_setup_phy_link_speed(hw, advertised, autoneg, |
| !hw->disable_fc_autoneg); |
| if (retval) { |
| emac_setup_phy_link_speed(hw, old, autoneg, |
| !hw->disable_fc_autoneg); |
| } |
| |
| if (netif_running(adpt->netdev)) { |
| /* If there is no EPHY, the EMAC internal PHY may get reset in |
| * emac_setup_phy_link_speed. Reset the MAC to avoid the memory |
| * corruption. |
| */ |
| if (adpt->no_ephy) { |
| emac_down(adpt, EMAC_HW_CTRL_RESET_MAC); |
| emac_up(adpt); |
| } |
| } |
| |
| done: |
| CLR_FLAG(adpt, ADPT_STATE_RESETTING); |
| return retval; |
| } |
| |
| static void emac_get_pauseparam(struct net_device *netdev, |
| struct ethtool_pauseparam *pause) |
| { |
| struct emac_adapter *adpt = netdev_priv(netdev); |
| struct emac_hw *hw = &adpt->hw; |
| |
| if (hw->disable_fc_autoneg) |
| pause->autoneg = 0; |
| else |
| pause->autoneg = 1; |
| |
| if (hw->cur_fc_mode == emac_fc_rx_pause) { |
| pause->rx_pause = 1; |
| } else if (hw->cur_fc_mode == emac_fc_tx_pause) { |
| pause->tx_pause = 1; |
| } else if (hw->cur_fc_mode == emac_fc_full) { |
| pause->rx_pause = 1; |
| pause->tx_pause = 1; |
| } |
| } |
| |
| static int emac_set_pauseparam(struct net_device *netdev, |
| struct ethtool_pauseparam *pause) |
| { |
| struct emac_adapter *adpt = netdev_priv(netdev); |
| struct emac_hw *hw = &adpt->hw; |
| enum emac_fc_mode req_fc_mode; |
| bool disable_fc_autoneg; |
| int retval = 0; |
| |
| while (TEST_N_SET_FLAG(adpt, ADPT_STATE_RESETTING)) |
| msleep(20); /* Reset might take few 10s of ms */ |
| |
| req_fc_mode = hw->req_fc_mode; |
| disable_fc_autoneg = hw->disable_fc_autoneg; |
| |
| if (pause->autoneg != AUTONEG_ENABLE) |
| disable_fc_autoneg = true; |
| else |
| disable_fc_autoneg = false; |
| |
| if (pause->rx_pause && pause->tx_pause) |
| req_fc_mode = emac_fc_full; |
| else if (pause->rx_pause && !pause->tx_pause) |
| req_fc_mode = emac_fc_rx_pause; |
| else if (!pause->rx_pause && pause->tx_pause) |
| req_fc_mode = emac_fc_tx_pause; |
| else if (!pause->rx_pause && !pause->tx_pause) |
| req_fc_mode = emac_fc_none; |
| else { |
| CLR_FLAG(adpt, ADPT_STATE_RESETTING); |
| return -EINVAL; |
| } |
| |
| if ((hw->req_fc_mode != req_fc_mode) || |
| (hw->disable_fc_autoneg != disable_fc_autoneg)) { |
| hw->req_fc_mode = req_fc_mode; |
| hw->disable_fc_autoneg = disable_fc_autoneg; |
| if (!adpt->no_ephy) |
| retval = emac_setup_phy_link(hw, |
| hw->autoneg_advertised, |
| hw->autoneg, |
| !disable_fc_autoneg); |
| if (!retval) |
| emac_hw_config_fc(hw); |
| } |
| |
| CLR_FLAG(adpt, ADPT_STATE_RESETTING); |
| return retval; |
| } |
| |
| static u32 emac_get_msglevel(struct net_device *netdev) |
| { |
| struct emac_adapter *adpt = netdev_priv(netdev); |
| |
| return adpt->msg_enable; |
| } |
| |
| static void emac_set_msglevel(struct net_device *netdev, u32 data) |
| { |
| struct emac_adapter *adpt = netdev_priv(netdev); |
| |
| adpt->msg_enable = data; |
| } |
| |
| static int emac_get_regs_len(struct net_device *netdev) |
| { |
| return EMAC_MAX_REG_SIZE * sizeof(32); |
| } |
| |
| static void emac_get_regs(struct net_device *netdev, |
| struct ethtool_regs *regs, void *buff) |
| { |
| struct emac_adapter *adpt = netdev_priv(netdev); |
| struct emac_hw *hw = &adpt->hw; |
| u16 i; |
| u32 *val = buff; |
| static const u32 reg[EMAC_MAX_REG_SIZE] = { |
| EMAC_DMA_MAS_CTRL, EMAC_MAC_CTRL, EMAC_WOL_CTRL0, |
| EMAC_TXQ_CTRL_0, EMAC_RXQ_CTRL_0, EMAC_DMA_CTRL, EMAC_INT_MASK, |
| EMAC_AXI_MAST_CTRL, EMAC_CORE_HW_VERSION, EMAC_MISC_CTRL, |
| }; |
| |
| regs->version = 0; |
| regs->len = EMAC_MAX_REG_SIZE * sizeof(u32); |
| |
| memset(val, 0, EMAC_MAX_REG_SIZE * sizeof(u32)); |
| for (i = 0; i < ARRAY_SIZE(reg); i++) |
| val[i] = emac_reg_r32(hw, EMAC, reg[i]); |
| |
| return; |
| } |
| |
| static void emac_get_drvinfo(struct net_device *netdev, |
| struct ethtool_drvinfo *drvinfo) |
| { |
| strlcpy(drvinfo->driver, emac_drv_name, sizeof(drvinfo->driver)); |
| strlcpy(drvinfo->version, emac_drv_version, sizeof(drvinfo->version)); |
| strlcpy(drvinfo->bus_info, "axi", sizeof(drvinfo->bus_info)); |
| |
| drvinfo->regdump_len = emac_get_regs_len(netdev); |
| } |
| |
| static int emac_wol_exclusion(struct emac_adapter *adpt, |
| struct ethtool_wolinfo *wol) |
| { |
| struct emac_hw *hw = &adpt->hw; |
| |
| /* WOL not supported except for the following */ |
| switch (hw->devid) { |
| case EMAC_DEV_ID: |
| return 0; |
| default: |
| wol->supported = 0; |
| return -EINVAL; |
| } |
| } |
| |
| static void emac_get_wol(struct net_device *netdev, |
| struct ethtool_wolinfo *wol) |
| { |
| struct emac_adapter *adpt = netdev_priv(netdev); |
| |
| wol->supported = WAKE_MAGIC | WAKE_PHY; |
| wol->wolopts = 0; |
| |
| if (adpt->wol & EMAC_WOL_MAGIC) |
| wol->wolopts |= WAKE_MAGIC; |
| if (adpt->wol & EMAC_WOL_PHY) |
| wol->wolopts |= WAKE_PHY; |
| } |
| |
| static int emac_set_wol(struct net_device *netdev, struct ethtool_wolinfo *wol) |
| { |
| struct emac_adapter *adpt = netdev_priv(netdev); |
| |
| if (wol->wolopts & (WAKE_ARP | WAKE_MAGICSECURE | |
| WAKE_UCAST | WAKE_BCAST | WAKE_MCAST)) |
| return -EOPNOTSUPP; |
| |
| if (emac_wol_exclusion(adpt, wol)) |
| return wol->wolopts ? -EOPNOTSUPP : 0; |
| |
| adpt->wol = 0; |
| if (wol->wolopts & WAKE_MAGIC) |
| adpt->wol |= EMAC_WOL_MAGIC; |
| if (wol->wolopts & WAKE_PHY) |
| adpt->wol |= EMAC_WOL_PHY; |
| |
| return 0; |
| } |
| |
| static int emac_get_intr_coalesce(struct net_device *netdev, |
| struct ethtool_coalesce *ec) |
| { |
| struct emac_adapter *adpt = netdev_priv(netdev); |
| struct emac_hw *hw = &adpt->hw; |
| |
| /* irq moderator timers have resolution of 2us */ |
| ec->tx_coalesce_usecs = ((hw->irq_mod & IRQ_MODERATOR_INIT_BMSK) >> |
| IRQ_MODERATOR_INIT_SHFT) * 2; |
| ec->rx_coalesce_usecs = ((hw->irq_mod & IRQ_MODERATOR2_INIT_BMSK) >> |
| IRQ_MODERATOR2_INIT_SHFT) * 2; |
| |
| return 0; |
| } |
| |
| static int emac_set_intr_coalesce(struct net_device *netdev, |
| struct ethtool_coalesce *ec) |
| { |
| struct emac_adapter *adpt = netdev_priv(netdev); |
| struct emac_hw *hw = &adpt->hw; |
| u32 val; |
| |
| /* irq moderator timers have resolution of 2us */ |
| val = ((ec->rx_coalesce_usecs / 2) << IRQ_MODERATOR2_INIT_SHFT) & |
| IRQ_MODERATOR2_INIT_BMSK; |
| val |= ((ec->tx_coalesce_usecs / 2) << IRQ_MODERATOR_INIT_SHFT) & |
| IRQ_MODERATOR_INIT_BMSK; |
| |
| emac_reg_w32(hw, EMAC, EMAC_IRQ_MOD_TIM_INIT, val); |
| hw->irq_mod = val; |
| |
| return 0; |
| } |
| |
| static void emac_get_ringparam(struct net_device *netdev, |
| struct ethtool_ringparam *ring) |
| { |
| struct emac_adapter *adpt = netdev_priv(netdev); |
| |
| ring->rx_max_pending = EMAC_MAX_RX_DESCS; |
| ring->tx_max_pending = EMAC_MAX_TX_DESCS; |
| ring->rx_pending = adpt->num_rxdescs; |
| ring->tx_pending = adpt->num_txdescs; |
| } |
| |
| static int emac_set_ringparam(struct net_device *netdev, |
| struct ethtool_ringparam *ring) |
| { |
| struct emac_adapter *adpt = netdev_priv(netdev); |
| int retval = 0; |
| |
| if ((ring->rx_mini_pending) || (ring->rx_jumbo_pending)) |
| return -EINVAL; |
| |
| adpt->num_txdescs = clamp_t(u32, ring->tx_pending, |
| EMAC_MIN_TX_DESCS, EMAC_MAX_TX_DESCS); |
| |
| adpt->num_rxdescs = clamp_t(u32, ring->rx_pending, |
| EMAC_MIN_RX_DESCS, EMAC_MAX_RX_DESCS); |
| |
| if (netif_running(netdev)) |
| retval = emac_resize_rings(netdev); |
| |
| return retval; |
| } |
| |
| static int emac_nway_reset(struct net_device *netdev) |
| { |
| struct emac_adapter *adpt = netdev_priv(netdev); |
| |
| if (netif_running(netdev)) |
| emac_reinit_locked(adpt); |
| return 0; |
| } |
| |
| static int emac_get_sset_count(struct net_device *netdev, int sset) |
| { |
| switch (sset) { |
| case ETH_SS_TEST: |
| return 0; |
| case ETH_SS_STATS: |
| return EMAC_STATS_LEN; |
| default: |
| return -EOPNOTSUPP; |
| } |
| } |
| |
| static void emac_get_strings(struct net_device *netdev, u32 stringset, u8 *data) |
| { |
| u16 i; |
| |
| switch (stringset) { |
| case ETH_SS_TEST: |
| break; |
| case ETH_SS_STATS: |
| for (i = 0; i < EMAC_STATS_LEN; i++) { |
| strlcpy(data, emac_ethtool_stat_strings[i], |
| ETH_GSTRING_LEN); |
| data += ETH_GSTRING_LEN; |
| } |
| break; |
| } |
| } |
| |
| static void emac_get_ethtool_stats(struct net_device *netdev, |
| struct ethtool_stats *stats, |
| u64 *data) |
| { |
| struct emac_adapter *adpt = netdev_priv(netdev); |
| |
| emac_update_hw_stats(adpt); |
| memcpy(data, &adpt->hw_stats, EMAC_STATS_LEN * sizeof(u64)); |
| } |
| |
| static const struct ethtool_ops emac_ethtool_ops = { |
| .get_settings = emac_get_settings, |
| .set_settings = emac_set_settings, |
| .get_pauseparam = emac_get_pauseparam, |
| .set_pauseparam = emac_set_pauseparam, |
| .get_drvinfo = emac_get_drvinfo, |
| .get_regs_len = emac_get_regs_len, |
| .get_regs = emac_get_regs, |
| .get_wol = emac_get_wol, |
| .set_wol = emac_set_wol, |
| .get_msglevel = emac_get_msglevel, |
| .set_msglevel = emac_set_msglevel, |
| .get_coalesce = emac_get_intr_coalesce, |
| .set_coalesce = emac_set_intr_coalesce, |
| .get_ringparam = emac_get_ringparam, |
| .set_ringparam = emac_set_ringparam, |
| .nway_reset = emac_nway_reset, |
| .get_link = ethtool_op_get_link, |
| .get_sset_count = emac_get_sset_count, |
| .get_strings = emac_get_strings, |
| .get_ethtool_stats = emac_get_ethtool_stats, |
| }; |
| |
| /* Set ethtool operations */ |
| void emac_set_ethtool_ops(struct net_device *netdev) |
| { |
| SET_ETHTOOL_OPS(netdev, &emac_ethtool_ops); |
| } |