/********************************************************************** * Author: Cavium Networks * * Contact: support@caviumnetworks.com * This file is part of the OCTEON SDK * * Copyright (c) 2003-2007 Cavium Networks * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, Version 2, as * published by the Free Software Foundation. * * This file is distributed in the hope that it will be useful, but * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or * NONINFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with this file; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * or visit http://www.gnu.org/licenses/. * * This file may also be available under a different license from Cavium. * Contact Cavium Networks for more information **********************************************************************/ #include <linux/kernel.h> #include <linux/ethtool.h> #include <linux/phy.h> #include <linux/ratelimit.h> #include <net/dst.h> #include <asm/octeon/octeon.h> #include "ethernet-defines.h" #include "octeon-ethernet.h" #include "ethernet-mdio.h" #include "ethernet-util.h" #include <asm/octeon/cvmx-helper-board.h> #include <asm/octeon/cvmx-smix-defs.h> static void cvm_oct_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info) { strcpy(info->driver, "cavium-ethernet"); strcpy(info->version, OCTEON_ETHERNET_VERSION); strcpy(info->bus_info, "Builtin"); } static int cvm_oct_get_settings(struct net_device *dev, struct ethtool_cmd *cmd) { struct octeon_ethernet *priv = netdev_priv(dev); if (priv->phydev) return phy_ethtool_gset(priv->phydev, cmd); return -EINVAL; } static int cvm_oct_set_settings(struct net_device *dev, struct ethtool_cmd *cmd) { struct octeon_ethernet *priv = netdev_priv(dev); if (!capable(CAP_NET_ADMIN)) return -EPERM; if (priv->phydev) return phy_ethtool_sset(priv->phydev, cmd); return -EINVAL; } static int cvm_oct_nway_reset(struct net_device *dev) { struct octeon_ethernet *priv = netdev_priv(dev); if (!capable(CAP_NET_ADMIN)) return -EPERM; if (priv->phydev) return phy_start_aneg(priv->phydev); return -EINVAL; } const struct ethtool_ops cvm_oct_ethtool_ops = { .get_drvinfo = cvm_oct_get_drvinfo, .get_settings = cvm_oct_get_settings, .set_settings = cvm_oct_set_settings, .nway_reset = cvm_oct_nway_reset, .get_link = ethtool_op_get_link, }; /** * cvm_oct_ioctl - IOCTL support for PHY control * @dev: Device to change * @rq: the request * @cmd: the command * * Returns Zero on success */ int cvm_oct_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) { struct octeon_ethernet *priv = netdev_priv(dev); if (!netif_running(dev)) return -EINVAL; if (!priv->phydev) return -EINVAL; return phy_mii_ioctl(priv->phydev, rq, cmd); } static void cvm_oct_adjust_link(struct net_device *dev) { struct octeon_ethernet *priv = netdev_priv(dev); cvmx_helper_link_info_t link_info; if (priv->last_link != priv->phydev->link) { priv->last_link = priv->phydev->link; link_info.u64 = 0; link_info.s.link_up = priv->last_link ? 1 : 0; link_info.s.full_duplex = priv->phydev->duplex ? 1 : 0; link_info.s.speed = priv->phydev->speed; cvmx_helper_link_set( priv->port, link_info); if (priv->last_link) { netif_carrier_on(dev); if (priv->queue != -1) printk_ratelimited("%s: %u Mbps %s duplex, " "port %2d, queue %2d\n", dev->name, priv->phydev->speed, priv->phydev->duplex ? "Full" : "Half", priv->port, priv->queue); else printk_ratelimited("%s: %u Mbps %s duplex, " "port %2d, POW\n", dev->name, priv->phydev->speed, priv->phydev->duplex ? "Full" : "Half", priv->port); } else { netif_carrier_off(dev); printk_ratelimited("%s: Link down\n", dev->name); } } } /** * cvm_oct_phy_setup_device - setup the PHY * * @dev: Device to setup * * Returns Zero on success, negative on failure */ int cvm_oct_phy_setup_device(struct net_device *dev) { struct octeon_ethernet *priv = netdev_priv(dev); int phy_addr = cvmx_helper_board_get_mii_address(priv->port); if (phy_addr != -1) { char phy_id[MII_BUS_ID_SIZE + 3]; snprintf(phy_id, sizeof(phy_id), PHY_ID_FMT, "mdio-octeon-0", phy_addr); priv->phydev = phy_connect(dev, phy_id, cvm_oct_adjust_link, 0, PHY_INTERFACE_MODE_GMII); if (IS_ERR(priv->phydev)) { priv->phydev = NULL; return -1; } priv->last_link = 0; phy_start_aneg(priv->phydev); } return 0; }