1 /* 2 * Copyright (c) 2013 Johannes Berg <johannes@sipsolutions.net> 3 * 4 * This file is free software: you may copy, redistribute and/or modify it 5 * under the terms of the GNU General Public License as published by the 6 * Free Software Foundation, either version 2 of the License, or (at your 7 * option) any later version. 8 * 9 * This file is distributed in the hope that it will be useful, but 10 * WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 * General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 * 17 * This file incorporates work covered by the following copyright and 18 * permission notice: 19 * 20 * Copyright (c) 2012 Qualcomm Atheros, Inc. 21 * 22 * Permission to use, copy, modify, and/or distribute this software for any 23 * purpose with or without fee is hereby granted, provided that the above 24 * copyright notice and this permission notice appear in all copies. 25 * 26 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 27 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 28 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 29 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 30 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 31 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 32 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 33 */ 34 35 #include <linux/pci.h> 36 #include <linux/ip.h> 37 #include <linux/tcp.h> 38 #include <linux/netdevice.h> 39 #include <linux/etherdevice.h> 40 #include <linux/ethtool.h> 41 #include <linux/mdio.h> 42 #include <linux/interrupt.h> 43 #include <asm/byteorder.h> 44 45 #include "alx.h" 46 #include "reg.h" 47 #include "hw.h" 48 49 static u32 alx_get_supported_speeds(struct alx_hw *hw) 50 { 51 u32 supported = SUPPORTED_10baseT_Half | 52 SUPPORTED_10baseT_Full | 53 SUPPORTED_100baseT_Half | 54 SUPPORTED_100baseT_Full; 55 56 if (alx_hw_giga(hw)) 57 supported |= SUPPORTED_1000baseT_Full; 58 59 BUILD_BUG_ON(SUPPORTED_10baseT_Half != ADVERTISED_10baseT_Half); 60 BUILD_BUG_ON(SUPPORTED_10baseT_Full != ADVERTISED_10baseT_Full); 61 BUILD_BUG_ON(SUPPORTED_100baseT_Half != ADVERTISED_100baseT_Half); 62 BUILD_BUG_ON(SUPPORTED_100baseT_Full != ADVERTISED_100baseT_Full); 63 BUILD_BUG_ON(SUPPORTED_1000baseT_Full != ADVERTISED_1000baseT_Full); 64 65 return supported; 66 } 67 68 static int alx_get_settings(struct net_device *netdev, struct ethtool_cmd *ecmd) 69 { 70 struct alx_priv *alx = netdev_priv(netdev); 71 struct alx_hw *hw = &alx->hw; 72 73 ecmd->supported = SUPPORTED_Autoneg | 74 SUPPORTED_TP | 75 SUPPORTED_Pause | 76 SUPPORTED_Asym_Pause; 77 if (alx_hw_giga(hw)) 78 ecmd->supported |= SUPPORTED_1000baseT_Full; 79 ecmd->supported |= alx_get_supported_speeds(hw); 80 81 ecmd->advertising = ADVERTISED_TP; 82 if (hw->adv_cfg & ADVERTISED_Autoneg) 83 ecmd->advertising |= hw->adv_cfg; 84 85 ecmd->port = PORT_TP; 86 ecmd->phy_address = 0; 87 88 if (hw->adv_cfg & ADVERTISED_Autoneg) 89 ecmd->autoneg = AUTONEG_ENABLE; 90 else 91 ecmd->autoneg = AUTONEG_DISABLE; 92 ecmd->transceiver = XCVR_INTERNAL; 93 94 if (hw->flowctrl & ALX_FC_ANEG && hw->adv_cfg & ADVERTISED_Autoneg) { 95 if (hw->flowctrl & ALX_FC_RX) { 96 ecmd->advertising |= ADVERTISED_Pause; 97 98 if (!(hw->flowctrl & ALX_FC_TX)) 99 ecmd->advertising |= ADVERTISED_Asym_Pause; 100 } else if (hw->flowctrl & ALX_FC_TX) { 101 ecmd->advertising |= ADVERTISED_Asym_Pause; 102 } 103 } 104 105 ethtool_cmd_speed_set(ecmd, hw->link_speed); 106 ecmd->duplex = hw->duplex; 107 108 return 0; 109 } 110 111 static int alx_set_settings(struct net_device *netdev, struct ethtool_cmd *ecmd) 112 { 113 struct alx_priv *alx = netdev_priv(netdev); 114 struct alx_hw *hw = &alx->hw; 115 u32 adv_cfg; 116 117 ASSERT_RTNL(); 118 119 if (ecmd->autoneg == AUTONEG_ENABLE) { 120 if (ecmd->advertising & ~alx_get_supported_speeds(hw)) 121 return -EINVAL; 122 adv_cfg = ecmd->advertising | ADVERTISED_Autoneg; 123 } else { 124 adv_cfg = alx_speed_to_ethadv(ethtool_cmd_speed(ecmd), 125 ecmd->duplex); 126 127 if (!adv_cfg || adv_cfg == ADVERTISED_1000baseT_Full) 128 return -EINVAL; 129 } 130 131 hw->adv_cfg = adv_cfg; 132 return alx_setup_speed_duplex(hw, adv_cfg, hw->flowctrl); 133 } 134 135 static void alx_get_pauseparam(struct net_device *netdev, 136 struct ethtool_pauseparam *pause) 137 { 138 struct alx_priv *alx = netdev_priv(netdev); 139 struct alx_hw *hw = &alx->hw; 140 141 pause->autoneg = !!(hw->flowctrl & ALX_FC_ANEG && 142 hw->adv_cfg & ADVERTISED_Autoneg); 143 pause->tx_pause = !!(hw->flowctrl & ALX_FC_TX); 144 pause->rx_pause = !!(hw->flowctrl & ALX_FC_RX); 145 } 146 147 148 static int alx_set_pauseparam(struct net_device *netdev, 149 struct ethtool_pauseparam *pause) 150 { 151 struct alx_priv *alx = netdev_priv(netdev); 152 struct alx_hw *hw = &alx->hw; 153 int err = 0; 154 bool reconfig_phy = false; 155 u8 fc = 0; 156 157 if (pause->tx_pause) 158 fc |= ALX_FC_TX; 159 if (pause->rx_pause) 160 fc |= ALX_FC_RX; 161 if (pause->autoneg) 162 fc |= ALX_FC_ANEG; 163 164 ASSERT_RTNL(); 165 166 /* restart auto-neg for auto-mode */ 167 if (hw->adv_cfg & ADVERTISED_Autoneg) { 168 if (!((fc ^ hw->flowctrl) & ALX_FC_ANEG)) 169 reconfig_phy = true; 170 if (fc & hw->flowctrl & ALX_FC_ANEG && 171 (fc ^ hw->flowctrl) & (ALX_FC_RX | ALX_FC_TX)) 172 reconfig_phy = true; 173 } 174 175 if (reconfig_phy) { 176 err = alx_setup_speed_duplex(hw, hw->adv_cfg, fc); 177 if (err) 178 return err; 179 } 180 181 /* flow control on mac */ 182 if ((fc ^ hw->flowctrl) & (ALX_FC_RX | ALX_FC_TX)) 183 alx_cfg_mac_flowcontrol(hw, fc); 184 185 hw->flowctrl = fc; 186 187 return 0; 188 } 189 190 static u32 alx_get_msglevel(struct net_device *netdev) 191 { 192 struct alx_priv *alx = netdev_priv(netdev); 193 194 return alx->msg_enable; 195 } 196 197 static void alx_set_msglevel(struct net_device *netdev, u32 data) 198 { 199 struct alx_priv *alx = netdev_priv(netdev); 200 201 alx->msg_enable = data; 202 } 203 204 const struct ethtool_ops alx_ethtool_ops = { 205 .get_settings = alx_get_settings, 206 .set_settings = alx_set_settings, 207 .get_pauseparam = alx_get_pauseparam, 208 .set_pauseparam = alx_set_pauseparam, 209 .get_msglevel = alx_get_msglevel, 210 .set_msglevel = alx_set_msglevel, 211 .get_link = ethtool_op_get_link, 212 }; 213