1/* 2 * Copyright(c) 2009 - 2009 Atheros Corporation. All rights reserved. 3 * 4 * Derived from Intel e1000 driver 5 * Copyright(c) 1999 - 2005 Intel Corporation. All rights reserved. 6 * 7 * This program is free software; you can redistribute it and/or modify it 8 * under the terms of the GNU General Public License as published by the Free 9 * Software Foundation; either version 2 of the License, or (at your option) 10 * any later version. 11 * 12 * This program is distributed in the hope that it will be useful, but WITHOUT 13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 15 * more details. 16 * 17 * You should have received a copy of the GNU General Public License along with 18 * this program; if not, write to the Free Software Foundation, Inc., 59 19 * Temple Place - Suite 330, Boston, MA 02111-1307, USA. 20 * 21 */ 22 23#include <linux/netdevice.h> 24#include <linux/ethtool.h> 25#include <linux/slab.h> 26 27#include "atl1c.h" 28 29static int atl1c_get_settings(struct net_device *netdev, 30 struct ethtool_cmd *ecmd) 31{ 32 struct atl1c_adapter *adapter = netdev_priv(netdev); 33 struct atl1c_hw *hw = &adapter->hw; 34 35 ecmd->supported = (SUPPORTED_10baseT_Half | 36 SUPPORTED_10baseT_Full | 37 SUPPORTED_100baseT_Half | 38 SUPPORTED_100baseT_Full | 39 SUPPORTED_Autoneg | 40 SUPPORTED_TP); 41 if (hw->link_cap_flags & ATL1C_LINK_CAP_1000M) 42 ecmd->supported |= SUPPORTED_1000baseT_Full; 43 44 ecmd->advertising = ADVERTISED_TP; 45 46 ecmd->advertising |= hw->autoneg_advertised; 47 48 ecmd->port = PORT_TP; 49 ecmd->phy_address = 0; 50 ecmd->transceiver = XCVR_INTERNAL; 51 52 if (adapter->link_speed != SPEED_0) { 53 ethtool_cmd_speed_set(ecmd, adapter->link_speed); 54 if (adapter->link_duplex == FULL_DUPLEX) 55 ecmd->duplex = DUPLEX_FULL; 56 else 57 ecmd->duplex = DUPLEX_HALF; 58 } else { 59 ethtool_cmd_speed_set(ecmd, SPEED_UNKNOWN); 60 ecmd->duplex = DUPLEX_UNKNOWN; 61 } 62 63 ecmd->autoneg = AUTONEG_ENABLE; 64 return 0; 65} 66 67static int atl1c_set_settings(struct net_device *netdev, 68 struct ethtool_cmd *ecmd) 69{ 70 struct atl1c_adapter *adapter = netdev_priv(netdev); 71 struct atl1c_hw *hw = &adapter->hw; 72 u16 autoneg_advertised; 73 74 while (test_and_set_bit(__AT_RESETTING, &adapter->flags)) 75 msleep(1); 76 77 if (ecmd->autoneg == AUTONEG_ENABLE) { 78 autoneg_advertised = ADVERTISED_Autoneg; 79 } else { 80 u32 speed = ethtool_cmd_speed(ecmd); 81 if (speed == SPEED_1000) { 82 if (ecmd->duplex != DUPLEX_FULL) { 83 if (netif_msg_link(adapter)) 84 dev_warn(&adapter->pdev->dev, 85 "1000M half is invalid\n"); 86 clear_bit(__AT_RESETTING, &adapter->flags); 87 return -EINVAL; 88 } 89 autoneg_advertised = ADVERTISED_1000baseT_Full; 90 } else if (speed == SPEED_100) { 91 if (ecmd->duplex == DUPLEX_FULL) 92 autoneg_advertised = ADVERTISED_100baseT_Full; 93 else 94 autoneg_advertised = ADVERTISED_100baseT_Half; 95 } else { 96 if (ecmd->duplex == DUPLEX_FULL) 97 autoneg_advertised = ADVERTISED_10baseT_Full; 98 else 99 autoneg_advertised = ADVERTISED_10baseT_Half; 100 } 101 } 102 103 if (hw->autoneg_advertised != autoneg_advertised) { 104 hw->autoneg_advertised = autoneg_advertised; 105 if (atl1c_restart_autoneg(hw) != 0) { 106 if (netif_msg_link(adapter)) 107 dev_warn(&adapter->pdev->dev, 108 "ethtool speed/duplex setting failed\n"); 109 clear_bit(__AT_RESETTING, &adapter->flags); 110 return -EINVAL; 111 } 112 } 113 clear_bit(__AT_RESETTING, &adapter->flags); 114 return 0; 115} 116 117static u32 atl1c_get_msglevel(struct net_device *netdev) 118{ 119 struct atl1c_adapter *adapter = netdev_priv(netdev); 120 return adapter->msg_enable; 121} 122 123static void atl1c_set_msglevel(struct net_device *netdev, u32 data) 124{ 125 struct atl1c_adapter *adapter = netdev_priv(netdev); 126 adapter->msg_enable = data; 127} 128 129static int atl1c_get_regs_len(struct net_device *netdev) 130{ 131 return AT_REGS_LEN; 132} 133 134static void atl1c_get_regs(struct net_device *netdev, 135 struct ethtool_regs *regs, void *p) 136{ 137 struct atl1c_adapter *adapter = netdev_priv(netdev); 138 struct atl1c_hw *hw = &adapter->hw; 139 u32 *regs_buff = p; 140 u16 phy_data; 141 142 memset(p, 0, AT_REGS_LEN); 143 144 regs->version = 1; 145 AT_READ_REG(hw, REG_PM_CTRL, p++); 146 AT_READ_REG(hw, REG_MAC_HALF_DUPLX_CTRL, p++); 147 AT_READ_REG(hw, REG_TWSI_CTRL, p++); 148 AT_READ_REG(hw, REG_PCIE_DEV_MISC_CTRL, p++); 149 AT_READ_REG(hw, REG_MASTER_CTRL, p++); 150 AT_READ_REG(hw, REG_MANUAL_TIMER_INIT, p++); 151 AT_READ_REG(hw, REG_IRQ_MODRT_TIMER_INIT, p++); 152 AT_READ_REG(hw, REG_GPHY_CTRL, p++); 153 AT_READ_REG(hw, REG_LINK_CTRL, p++); 154 AT_READ_REG(hw, REG_IDLE_STATUS, p++); 155 AT_READ_REG(hw, REG_MDIO_CTRL, p++); 156 AT_READ_REG(hw, REG_SERDES, p++); 157 AT_READ_REG(hw, REG_MAC_CTRL, p++); 158 AT_READ_REG(hw, REG_MAC_IPG_IFG, p++); 159 AT_READ_REG(hw, REG_MAC_STA_ADDR, p++); 160 AT_READ_REG(hw, REG_MAC_STA_ADDR+4, p++); 161 AT_READ_REG(hw, REG_RX_HASH_TABLE, p++); 162 AT_READ_REG(hw, REG_RX_HASH_TABLE+4, p++); 163 AT_READ_REG(hw, REG_RXQ_CTRL, p++); 164 AT_READ_REG(hw, REG_TXQ_CTRL, p++); 165 AT_READ_REG(hw, REG_MTU, p++); 166 AT_READ_REG(hw, REG_WOL_CTRL, p++); 167 168 atl1c_read_phy_reg(hw, MII_BMCR, &phy_data); 169 regs_buff[AT_REGS_LEN/sizeof(u32) - 2] = (u32) phy_data; 170 atl1c_read_phy_reg(hw, MII_BMSR, &phy_data); 171 regs_buff[AT_REGS_LEN/sizeof(u32) - 1] = (u32) phy_data; 172} 173 174static int atl1c_get_eeprom_len(struct net_device *netdev) 175{ 176 struct atl1c_adapter *adapter = netdev_priv(netdev); 177 178 if (atl1c_check_eeprom_exist(&adapter->hw)) 179 return AT_EEPROM_LEN; 180 else 181 return 0; 182} 183 184static int atl1c_get_eeprom(struct net_device *netdev, 185 struct ethtool_eeprom *eeprom, u8 *bytes) 186{ 187 struct atl1c_adapter *adapter = netdev_priv(netdev); 188 struct atl1c_hw *hw = &adapter->hw; 189 u32 *eeprom_buff; 190 int first_dword, last_dword; 191 int ret_val = 0; 192 int i; 193 194 if (eeprom->len == 0) 195 return -EINVAL; 196 197 if (!atl1c_check_eeprom_exist(hw)) /* not exist */ 198 return -EINVAL; 199 200 eeprom->magic = adapter->pdev->vendor | 201 (adapter->pdev->device << 16); 202 203 first_dword = eeprom->offset >> 2; 204 last_dword = (eeprom->offset + eeprom->len - 1) >> 2; 205 206 eeprom_buff = kmalloc(sizeof(u32) * 207 (last_dword - first_dword + 1), GFP_KERNEL); 208 if (eeprom_buff == NULL) 209 return -ENOMEM; 210 211 for (i = first_dword; i < last_dword; i++) { 212 if (!atl1c_read_eeprom(hw, i * 4, &(eeprom_buff[i-first_dword]))) { 213 kfree(eeprom_buff); 214 return -EIO; 215 } 216 } 217 218 memcpy(bytes, (u8 *)eeprom_buff + (eeprom->offset & 3), 219 eeprom->len); 220 kfree(eeprom_buff); 221 222 return ret_val; 223 return 0; 224} 225 226static void atl1c_get_drvinfo(struct net_device *netdev, 227 struct ethtool_drvinfo *drvinfo) 228{ 229 struct atl1c_adapter *adapter = netdev_priv(netdev); 230 231 strlcpy(drvinfo->driver, atl1c_driver_name, sizeof(drvinfo->driver)); 232 strlcpy(drvinfo->version, atl1c_driver_version, 233 sizeof(drvinfo->version)); 234 strlcpy(drvinfo->bus_info, pci_name(adapter->pdev), 235 sizeof(drvinfo->bus_info)); 236} 237 238static void atl1c_get_wol(struct net_device *netdev, 239 struct ethtool_wolinfo *wol) 240{ 241 struct atl1c_adapter *adapter = netdev_priv(netdev); 242 243 wol->supported = WAKE_MAGIC | WAKE_PHY; 244 wol->wolopts = 0; 245 246 if (adapter->wol & AT_WUFC_EX) 247 wol->wolopts |= WAKE_UCAST; 248 if (adapter->wol & AT_WUFC_MC) 249 wol->wolopts |= WAKE_MCAST; 250 if (adapter->wol & AT_WUFC_BC) 251 wol->wolopts |= WAKE_BCAST; 252 if (adapter->wol & AT_WUFC_MAG) 253 wol->wolopts |= WAKE_MAGIC; 254 if (adapter->wol & AT_WUFC_LNKC) 255 wol->wolopts |= WAKE_PHY; 256} 257 258static int atl1c_set_wol(struct net_device *netdev, struct ethtool_wolinfo *wol) 259{ 260 struct atl1c_adapter *adapter = netdev_priv(netdev); 261 262 if (wol->wolopts & (WAKE_ARP | WAKE_MAGICSECURE | 263 WAKE_UCAST | WAKE_BCAST | WAKE_MCAST)) 264 return -EOPNOTSUPP; 265 /* these settings will always override what we currently have */ 266 adapter->wol = 0; 267 268 if (wol->wolopts & WAKE_MAGIC) 269 adapter->wol |= AT_WUFC_MAG; 270 if (wol->wolopts & WAKE_PHY) 271 adapter->wol |= AT_WUFC_LNKC; 272 273 device_set_wakeup_enable(&adapter->pdev->dev, adapter->wol); 274 275 return 0; 276} 277 278static int atl1c_nway_reset(struct net_device *netdev) 279{ 280 struct atl1c_adapter *adapter = netdev_priv(netdev); 281 if (netif_running(netdev)) 282 atl1c_reinit_locked(adapter); 283 return 0; 284} 285 286static const struct ethtool_ops atl1c_ethtool_ops = { 287 .get_settings = atl1c_get_settings, 288 .set_settings = atl1c_set_settings, 289 .get_drvinfo = atl1c_get_drvinfo, 290 .get_regs_len = atl1c_get_regs_len, 291 .get_regs = atl1c_get_regs, 292 .get_wol = atl1c_get_wol, 293 .set_wol = atl1c_set_wol, 294 .get_msglevel = atl1c_get_msglevel, 295 .set_msglevel = atl1c_set_msglevel, 296 .nway_reset = atl1c_nway_reset, 297 .get_link = ethtool_op_get_link, 298 .get_eeprom_len = atl1c_get_eeprom_len, 299 .get_eeprom = atl1c_get_eeprom, 300}; 301 302void atl1c_set_ethtool_ops(struct net_device *netdev) 303{ 304 netdev->ethtool_ops = &atl1c_ethtool_ops; 305} 306