diff options
Diffstat (limited to 'drivers/net/ethernet/freescale/ucc_geth_ethtool.c')
| -rw-r--r-- | drivers/net/ethernet/freescale/ucc_geth_ethtool.c | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/drivers/net/ethernet/freescale/ucc_geth_ethtool.c b/drivers/net/ethernet/freescale/ucc_geth_ethtool.c new file mode 100644 index 000000000000..a97257f91a3d --- /dev/null +++ b/drivers/net/ethernet/freescale/ucc_geth_ethtool.c | |||
| @@ -0,0 +1,423 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 2007 Freescale Semiconductor, Inc. All rights reserved. | ||
| 3 | * | ||
| 4 | * Description: QE UCC Gigabit Ethernet Ethtool API Set | ||
| 5 | * | ||
| 6 | * Author: Li Yang <leoli@freescale.com> | ||
| 7 | * | ||
| 8 | * Limitation: | ||
| 9 | * Can only get/set settings of the first queue. | ||
| 10 | * Need to re-open the interface manually after changing some parameters. | ||
| 11 | * | ||
| 12 | * This program is free software; you can redistribute it and/or modify it | ||
| 13 | * under the terms of the GNU General Public License as published by the | ||
| 14 | * Free Software Foundation; either version 2 of the License, or (at your | ||
| 15 | * option) any later version. | ||
| 16 | */ | ||
| 17 | |||
| 18 | #include <linux/kernel.h> | ||
| 19 | #include <linux/init.h> | ||
| 20 | #include <linux/errno.h> | ||
| 21 | #include <linux/stddef.h> | ||
| 22 | #include <linux/interrupt.h> | ||
| 23 | #include <linux/netdevice.h> | ||
| 24 | #include <linux/etherdevice.h> | ||
| 25 | #include <linux/skbuff.h> | ||
| 26 | #include <linux/spinlock.h> | ||
| 27 | #include <linux/mm.h> | ||
| 28 | #include <linux/delay.h> | ||
| 29 | #include <linux/dma-mapping.h> | ||
| 30 | #include <linux/ethtool.h> | ||
| 31 | #include <linux/mii.h> | ||
| 32 | #include <linux/phy.h> | ||
| 33 | |||
| 34 | #include <asm/io.h> | ||
| 35 | #include <asm/irq.h> | ||
| 36 | #include <asm/uaccess.h> | ||
| 37 | #include <asm/types.h> | ||
| 38 | |||
| 39 | #include "ucc_geth.h" | ||
| 40 | |||
| 41 | static char hw_stat_gstrings[][ETH_GSTRING_LEN] = { | ||
| 42 | "tx-64-frames", | ||
| 43 | "tx-65-127-frames", | ||
| 44 | "tx-128-255-frames", | ||
| 45 | "rx-64-frames", | ||
| 46 | "rx-65-127-frames", | ||
| 47 | "rx-128-255-frames", | ||
| 48 | "tx-bytes-ok", | ||
| 49 | "tx-pause-frames", | ||
| 50 | "tx-multicast-frames", | ||
| 51 | "tx-broadcast-frames", | ||
| 52 | "rx-frames", | ||
| 53 | "rx-bytes-ok", | ||
| 54 | "rx-bytes-all", | ||
| 55 | "rx-multicast-frames", | ||
| 56 | "rx-broadcast-frames", | ||
| 57 | "stats-counter-carry", | ||
| 58 | "stats-counter-mask", | ||
| 59 | "rx-dropped-frames", | ||
| 60 | }; | ||
| 61 | |||
| 62 | static char tx_fw_stat_gstrings[][ETH_GSTRING_LEN] = { | ||
| 63 | "tx-single-collision", | ||
| 64 | "tx-multiple-collision", | ||
| 65 | "tx-late-collsion", | ||
| 66 | "tx-aborted-frames", | ||
| 67 | "tx-lost-frames", | ||
| 68 | "tx-carrier-sense-errors", | ||
| 69 | "tx-frames-ok", | ||
| 70 | "tx-excessive-differ-frames", | ||
| 71 | "tx-256-511-frames", | ||
| 72 | "tx-512-1023-frames", | ||
| 73 | "tx-1024-1518-frames", | ||
| 74 | "tx-jumbo-frames", | ||
| 75 | }; | ||
| 76 | |||
| 77 | static char rx_fw_stat_gstrings[][ETH_GSTRING_LEN] = { | ||
| 78 | "rx-crc-errors", | ||
| 79 | "rx-alignment-errors", | ||
| 80 | "rx-in-range-length-errors", | ||
| 81 | "rx-out-of-range-length-errors", | ||
| 82 | "rx-too-long-frames", | ||
| 83 | "rx-runt", | ||
| 84 | "rx-very-long-event", | ||
| 85 | "rx-symbol-errors", | ||
| 86 | "rx-busy-drop-frames", | ||
| 87 | "reserved", | ||
| 88 | "reserved", | ||
| 89 | "rx-mismatch-drop-frames", | ||
| 90 | "rx-small-than-64", | ||
| 91 | "rx-256-511-frames", | ||
| 92 | "rx-512-1023-frames", | ||
| 93 | "rx-1024-1518-frames", | ||
| 94 | "rx-jumbo-frames", | ||
| 95 | "rx-mac-error-loss", | ||
| 96 | "rx-pause-frames", | ||
| 97 | "reserved", | ||
| 98 | "rx-vlan-removed", | ||
| 99 | "rx-vlan-replaced", | ||
| 100 | "rx-vlan-inserted", | ||
| 101 | "rx-ip-checksum-errors", | ||
| 102 | }; | ||
| 103 | |||
| 104 | #define UEC_HW_STATS_LEN ARRAY_SIZE(hw_stat_gstrings) | ||
| 105 | #define UEC_TX_FW_STATS_LEN ARRAY_SIZE(tx_fw_stat_gstrings) | ||
| 106 | #define UEC_RX_FW_STATS_LEN ARRAY_SIZE(rx_fw_stat_gstrings) | ||
| 107 | |||
| 108 | static int | ||
| 109 | uec_get_settings(struct net_device *netdev, struct ethtool_cmd *ecmd) | ||
| 110 | { | ||
| 111 | struct ucc_geth_private *ugeth = netdev_priv(netdev); | ||
| 112 | struct phy_device *phydev = ugeth->phydev; | ||
| 113 | struct ucc_geth_info *ug_info = ugeth->ug_info; | ||
| 114 | |||
| 115 | if (!phydev) | ||
| 116 | return -ENODEV; | ||
| 117 | |||
| 118 | ecmd->maxtxpkt = 1; | ||
| 119 | ecmd->maxrxpkt = ug_info->interruptcoalescingmaxvalue[0]; | ||
| 120 | |||
| 121 | return phy_ethtool_gset(phydev, ecmd); | ||
| 122 | } | ||
| 123 | |||
| 124 | static int | ||
| 125 | uec_set_settings(struct net_device *netdev, struct ethtool_cmd *ecmd) | ||
| 126 | { | ||
| 127 | struct ucc_geth_private *ugeth = netdev_priv(netdev); | ||
| 128 | struct phy_device *phydev = ugeth->phydev; | ||
| 129 | |||
| 130 | if (!phydev) | ||
| 131 | return -ENODEV; | ||
| 132 | |||
| 133 | return phy_ethtool_sset(phydev, ecmd); | ||
| 134 | } | ||
| 135 | |||
| 136 | static void | ||
| 137 | uec_get_pauseparam(struct net_device *netdev, | ||
| 138 | struct ethtool_pauseparam *pause) | ||
| 139 | { | ||
| 140 | struct ucc_geth_private *ugeth = netdev_priv(netdev); | ||
| 141 | |||
| 142 | pause->autoneg = ugeth->phydev->autoneg; | ||
| 143 | |||
| 144 | if (ugeth->ug_info->receiveFlowControl) | ||
| 145 | pause->rx_pause = 1; | ||
| 146 | if (ugeth->ug_info->transmitFlowControl) | ||
| 147 | pause->tx_pause = 1; | ||
| 148 | } | ||
| 149 | |||
| 150 | static int | ||
| 151 | uec_set_pauseparam(struct net_device *netdev, | ||
| 152 | struct ethtool_pauseparam *pause) | ||
| 153 | { | ||
| 154 | struct ucc_geth_private *ugeth = netdev_priv(netdev); | ||
| 155 | int ret = 0; | ||
| 156 | |||
| 157 | ugeth->ug_info->receiveFlowControl = pause->rx_pause; | ||
| 158 | ugeth->ug_info->transmitFlowControl = pause->tx_pause; | ||
| 159 | |||
| 160 | if (ugeth->phydev->autoneg) { | ||
| 161 | if (netif_running(netdev)) { | ||
| 162 | /* FIXME: automatically restart */ | ||
| 163 | printk(KERN_INFO | ||
| 164 | "Please re-open the interface.\n"); | ||
| 165 | } | ||
| 166 | } else { | ||
| 167 | struct ucc_geth_info *ug_info = ugeth->ug_info; | ||
| 168 | |||
| 169 | ret = init_flow_control_params(ug_info->aufc, | ||
| 170 | ug_info->receiveFlowControl, | ||
| 171 | ug_info->transmitFlowControl, | ||
| 172 | ug_info->pausePeriod, | ||
| 173 | ug_info->extensionField, | ||
| 174 | &ugeth->uccf->uf_regs->upsmr, | ||
| 175 | &ugeth->ug_regs->uempr, | ||
| 176 | &ugeth->ug_regs->maccfg1); | ||
| 177 | } | ||
| 178 | |||
| 179 | return ret; | ||
| 180 | } | ||
| 181 | |||
| 182 | static uint32_t | ||
| 183 | uec_get_msglevel(struct net_device *netdev) | ||
| 184 | { | ||
| 185 | struct ucc_geth_private *ugeth = netdev_priv(netdev); | ||
| 186 | return ugeth->msg_enable; | ||
| 187 | } | ||
| 188 | |||
| 189 | static void | ||
| 190 | uec_set_msglevel(struct net_device *netdev, uint32_t data) | ||
| 191 | { | ||
| 192 | struct ucc_geth_private *ugeth = netdev_priv(netdev); | ||
| 193 | ugeth->msg_enable = data; | ||
| 194 | } | ||
| 195 | |||
| 196 | static int | ||
| 197 | uec_get_regs_len(struct net_device *netdev) | ||
| 198 | { | ||
| 199 | return sizeof(struct ucc_geth); | ||
| 200 | } | ||
| 201 | |||
| 202 | static void | ||
| 203 | uec_get_regs(struct net_device *netdev, | ||
| 204 | struct ethtool_regs *regs, void *p) | ||
| 205 | { | ||
| 206 | int i; | ||
| 207 | struct ucc_geth_private *ugeth = netdev_priv(netdev); | ||
| 208 | u32 __iomem *ug_regs = (u32 __iomem *)ugeth->ug_regs; | ||
| 209 | u32 *buff = p; | ||
| 210 | |||
| 211 | for (i = 0; i < sizeof(struct ucc_geth) / sizeof(u32); i++) | ||
| 212 | buff[i] = in_be32(&ug_regs[i]); | ||
| 213 | } | ||
| 214 | |||
| 215 | static void | ||
| 216 | uec_get_ringparam(struct net_device *netdev, | ||
| 217 | struct ethtool_ringparam *ring) | ||
| 218 | { | ||
| 219 | struct ucc_geth_private *ugeth = netdev_priv(netdev); | ||
| 220 | struct ucc_geth_info *ug_info = ugeth->ug_info; | ||
| 221 | int queue = 0; | ||
| 222 | |||
| 223 | ring->rx_max_pending = UCC_GETH_BD_RING_SIZE_MAX; | ||
| 224 | ring->rx_mini_max_pending = UCC_GETH_BD_RING_SIZE_MAX; | ||
| 225 | ring->rx_jumbo_max_pending = UCC_GETH_BD_RING_SIZE_MAX; | ||
| 226 | ring->tx_max_pending = UCC_GETH_BD_RING_SIZE_MAX; | ||
| 227 | |||
| 228 | ring->rx_pending = ug_info->bdRingLenRx[queue]; | ||
| 229 | ring->rx_mini_pending = ug_info->bdRingLenRx[queue]; | ||
| 230 | ring->rx_jumbo_pending = ug_info->bdRingLenRx[queue]; | ||
| 231 | ring->tx_pending = ug_info->bdRingLenTx[queue]; | ||
| 232 | } | ||
| 233 | |||
| 234 | static int | ||
| 235 | uec_set_ringparam(struct net_device *netdev, | ||
| 236 | struct ethtool_ringparam *ring) | ||
| 237 | { | ||
| 238 | struct ucc_geth_private *ugeth = netdev_priv(netdev); | ||
| 239 | struct ucc_geth_info *ug_info = ugeth->ug_info; | ||
| 240 | int queue = 0, ret = 0; | ||
| 241 | |||
| 242 | if (ring->rx_pending < UCC_GETH_RX_BD_RING_SIZE_MIN) { | ||
| 243 | printk("%s: RxBD ring size must be no smaller than %d.\n", | ||
| 244 | netdev->name, UCC_GETH_RX_BD_RING_SIZE_MIN); | ||
| 245 | return -EINVAL; | ||
| 246 | } | ||
| 247 | if (ring->rx_pending % UCC_GETH_RX_BD_RING_SIZE_ALIGNMENT) { | ||
| 248 | printk("%s: RxBD ring size must be multiple of %d.\n", | ||
| 249 | netdev->name, UCC_GETH_RX_BD_RING_SIZE_ALIGNMENT); | ||
| 250 | return -EINVAL; | ||
| 251 | } | ||
| 252 | if (ring->tx_pending < UCC_GETH_TX_BD_RING_SIZE_MIN) { | ||
| 253 | printk("%s: TxBD ring size must be no smaller than %d.\n", | ||
| 254 | netdev->name, UCC_GETH_TX_BD_RING_SIZE_MIN); | ||
| 255 | return -EINVAL; | ||
| 256 | } | ||
| 257 | |||
| 258 | ug_info->bdRingLenRx[queue] = ring->rx_pending; | ||
| 259 | ug_info->bdRingLenTx[queue] = ring->tx_pending; | ||
| 260 | |||
| 261 | if (netif_running(netdev)) { | ||
| 262 | /* FIXME: restart automatically */ | ||
| 263 | printk(KERN_INFO | ||
| 264 | "Please re-open the interface.\n"); | ||
| 265 | } | ||
| 266 | |||
| 267 | return ret; | ||
| 268 | } | ||
| 269 | |||
| 270 | static int uec_get_sset_count(struct net_device *netdev, int sset) | ||
| 271 | { | ||
| 272 | struct ucc_geth_private *ugeth = netdev_priv(netdev); | ||
| 273 | u32 stats_mode = ugeth->ug_info->statisticsMode; | ||
| 274 | int len = 0; | ||
| 275 | |||
| 276 | switch (sset) { | ||
| 277 | case ETH_SS_STATS: | ||
| 278 | if (stats_mode & UCC_GETH_STATISTICS_GATHERING_MODE_HARDWARE) | ||
| 279 | len += UEC_HW_STATS_LEN; | ||
| 280 | if (stats_mode & UCC_GETH_STATISTICS_GATHERING_MODE_FIRMWARE_TX) | ||
| 281 | len += UEC_TX_FW_STATS_LEN; | ||
| 282 | if (stats_mode & UCC_GETH_STATISTICS_GATHERING_MODE_FIRMWARE_RX) | ||
| 283 | len += UEC_RX_FW_STATS_LEN; | ||
| 284 | |||
| 285 | return len; | ||
| 286 | |||
| 287 | default: | ||
| 288 | return -EOPNOTSUPP; | ||
| 289 | } | ||
| 290 | } | ||
| 291 | |||
| 292 | static void uec_get_strings(struct net_device *netdev, u32 stringset, u8 *buf) | ||
| 293 | { | ||
| 294 | struct ucc_geth_private *ugeth = netdev_priv(netdev); | ||
| 295 | u32 stats_mode = ugeth->ug_info->statisticsMode; | ||
| 296 | |||
| 297 | if (stats_mode & UCC_GETH_STATISTICS_GATHERING_MODE_HARDWARE) { | ||
| 298 | memcpy(buf, hw_stat_gstrings, UEC_HW_STATS_LEN * | ||
| 299 | ETH_GSTRING_LEN); | ||
| 300 | buf += UEC_HW_STATS_LEN * ETH_GSTRING_LEN; | ||
| 301 | } | ||
| 302 | if (stats_mode & UCC_GETH_STATISTICS_GATHERING_MODE_FIRMWARE_TX) { | ||
| 303 | memcpy(buf, tx_fw_stat_gstrings, UEC_TX_FW_STATS_LEN * | ||
| 304 | ETH_GSTRING_LEN); | ||
| 305 | buf += UEC_TX_FW_STATS_LEN * ETH_GSTRING_LEN; | ||
| 306 | } | ||
| 307 | if (stats_mode & UCC_GETH_STATISTICS_GATHERING_MODE_FIRMWARE_RX) | ||
| 308 | memcpy(buf, rx_fw_stat_gstrings, UEC_RX_FW_STATS_LEN * | ||
| 309 | ETH_GSTRING_LEN); | ||
| 310 | } | ||
| 311 | |||
| 312 | static void uec_get_ethtool_stats(struct net_device *netdev, | ||
| 313 | struct ethtool_stats *stats, uint64_t *data) | ||
| 314 | { | ||
| 315 | struct ucc_geth_private *ugeth = netdev_priv(netdev); | ||
| 316 | u32 stats_mode = ugeth->ug_info->statisticsMode; | ||
| 317 | u32 __iomem *base; | ||
| 318 | int i, j = 0; | ||
| 319 | |||
| 320 | if (stats_mode & UCC_GETH_STATISTICS_GATHERING_MODE_HARDWARE) { | ||
| 321 | if (ugeth->ug_regs) | ||
| 322 | base = (u32 __iomem *)&ugeth->ug_regs->tx64; | ||
| 323 | else | ||
| 324 | base = NULL; | ||
| 325 | |||
| 326 | for (i = 0; i < UEC_HW_STATS_LEN; i++) | ||
| 327 | data[j++] = base ? in_be32(&base[i]) : 0; | ||
| 328 | } | ||
| 329 | if (stats_mode & UCC_GETH_STATISTICS_GATHERING_MODE_FIRMWARE_TX) { | ||
| 330 | base = (u32 __iomem *)ugeth->p_tx_fw_statistics_pram; | ||
| 331 | for (i = 0; i < UEC_TX_FW_STATS_LEN; i++) | ||
| 332 | data[j++] = base ? in_be32(&base[i]) : 0; | ||
| 333 | } | ||
| 334 | if (stats_mode & UCC_GETH_STATISTICS_GATHERING_MODE_FIRMWARE_RX) { | ||
| 335 | base = (u32 __iomem *)ugeth->p_rx_fw_statistics_pram; | ||
| 336 | for (i = 0; i < UEC_RX_FW_STATS_LEN; i++) | ||
| 337 | data[j++] = base ? in_be32(&base[i]) : 0; | ||
| 338 | } | ||
| 339 | } | ||
| 340 | |||
| 341 | static int uec_nway_reset(struct net_device *netdev) | ||
| 342 | { | ||
| 343 | struct ucc_geth_private *ugeth = netdev_priv(netdev); | ||
| 344 | |||
| 345 | return phy_start_aneg(ugeth->phydev); | ||
| 346 | } | ||
| 347 | |||
| 348 | /* Report driver information */ | ||
| 349 | static void | ||
| 350 | uec_get_drvinfo(struct net_device *netdev, | ||
| 351 | struct ethtool_drvinfo *drvinfo) | ||
| 352 | { | ||
| 353 | strncpy(drvinfo->driver, DRV_NAME, 32); | ||
| 354 | strncpy(drvinfo->version, DRV_VERSION, 32); | ||
| 355 | strncpy(drvinfo->fw_version, "N/A", 32); | ||
| 356 | strncpy(drvinfo->bus_info, "QUICC ENGINE", 32); | ||
| 357 | drvinfo->eedump_len = 0; | ||
| 358 | drvinfo->regdump_len = uec_get_regs_len(netdev); | ||
| 359 | } | ||
| 360 | |||
| 361 | #ifdef CONFIG_PM | ||
| 362 | |||
| 363 | static void uec_get_wol(struct net_device *netdev, struct ethtool_wolinfo *wol) | ||
| 364 | { | ||
| 365 | struct ucc_geth_private *ugeth = netdev_priv(netdev); | ||
| 366 | struct phy_device *phydev = ugeth->phydev; | ||
| 367 | |||
| 368 | if (phydev && phydev->irq) | ||
| 369 | wol->supported |= WAKE_PHY; | ||
| 370 | if (qe_alive_during_sleep()) | ||
| 371 | wol->supported |= WAKE_MAGIC; | ||
| 372 | |||
| 373 | wol->wolopts = ugeth->wol_en; | ||
| 374 | } | ||
| 375 | |||
| 376 | static int uec_set_wol(struct net_device *netdev, struct ethtool_wolinfo *wol) | ||
| 377 | { | ||
| 378 | struct ucc_geth_private *ugeth = netdev_priv(netdev); | ||
| 379 | struct phy_device *phydev = ugeth->phydev; | ||
| 380 | |||
| 381 | if (wol->wolopts & ~(WAKE_PHY | WAKE_MAGIC)) | ||
| 382 | return -EINVAL; | ||
| 383 | else if (wol->wolopts & WAKE_PHY && (!phydev || !phydev->irq)) | ||
| 384 | return -EINVAL; | ||
| 385 | else if (wol->wolopts & WAKE_MAGIC && !qe_alive_during_sleep()) | ||
| 386 | return -EINVAL; | ||
| 387 | |||
| 388 | ugeth->wol_en = wol->wolopts; | ||
| 389 | device_set_wakeup_enable(&netdev->dev, ugeth->wol_en); | ||
| 390 | |||
| 391 | return 0; | ||
| 392 | } | ||
| 393 | |||
| 394 | #else | ||
| 395 | #define uec_get_wol NULL | ||
| 396 | #define uec_set_wol NULL | ||
| 397 | #endif /* CONFIG_PM */ | ||
| 398 | |||
| 399 | static const struct ethtool_ops uec_ethtool_ops = { | ||
| 400 | .get_settings = uec_get_settings, | ||
| 401 | .set_settings = uec_set_settings, | ||
| 402 | .get_drvinfo = uec_get_drvinfo, | ||
| 403 | .get_regs_len = uec_get_regs_len, | ||
| 404 | .get_regs = uec_get_regs, | ||
| 405 | .get_msglevel = uec_get_msglevel, | ||
| 406 | .set_msglevel = uec_set_msglevel, | ||
| 407 | .nway_reset = uec_nway_reset, | ||
| 408 | .get_link = ethtool_op_get_link, | ||
| 409 | .get_ringparam = uec_get_ringparam, | ||
| 410 | .set_ringparam = uec_set_ringparam, | ||
| 411 | .get_pauseparam = uec_get_pauseparam, | ||
| 412 | .set_pauseparam = uec_set_pauseparam, | ||
| 413 | .get_sset_count = uec_get_sset_count, | ||
| 414 | .get_strings = uec_get_strings, | ||
| 415 | .get_ethtool_stats = uec_get_ethtool_stats, | ||
| 416 | .get_wol = uec_get_wol, | ||
| 417 | .set_wol = uec_set_wol, | ||
| 418 | }; | ||
| 419 | |||
| 420 | void uec_set_ethtool_ops(struct net_device *netdev) | ||
| 421 | { | ||
| 422 | SET_ETHTOOL_OPS(netdev, &uec_ethtool_ops); | ||
| 423 | } | ||
