mirror of
https://github.com/FEX-Emu/linux.git
synced 2025-01-14 13:39:10 +00:00
ebbb293f8b
The first version of the driver had hard-coded the logic for handling the checksum offloading. This was designed according to the chips included in the STM platforms where: o MAC10/100 supports no COE at all. o GMAC fully supports RX/TX COE. This is not good for other chip configurations where, for example, the mac10/100 supports the tx csum in HW or when the GMAC has no IPC. Thanks to Johannes Stezenbach; he provided me a first draft of this patch that only reviewed the IPC for the GMAC devices. This patch also helps on SPEAr platforms where the MAC10/100 can perform the TX csum in HW. Thanks to Deepak SIKRI for his support on this. In the end, GMAC devices for STM platforms have a bugged Jumbo frame support that needs to have the Tx COE disabled for oversized frames (due to limited buffer sizes). This information is also passed through the driver's platform structure. Signed-off-by: Giuseppe Cavallaro <peppe.cavallaro@st.com> Signed-off-by: Johannes Stezenbach <js@sig21.net> Signed-off-by: Deepak SIKRI <deepak.sikri@st.com> Signed-off-by: David S. Miller <davem@davemloft.net>
387 lines
9.7 KiB
C
387 lines
9.7 KiB
C
/*******************************************************************************
|
|
STMMAC Ethtool support
|
|
|
|
Copyright (C) 2007-2009 STMicroelectronics Ltd
|
|
|
|
This program is free software; you can redistribute it and/or modify it
|
|
under the terms and conditions of the GNU General Public License,
|
|
version 2, as published by the Free Software Foundation.
|
|
|
|
This program is distributed in the hope 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.
|
|
|
|
You should have received a copy of the GNU General Public License along with
|
|
this program; if not, write to the Free Software Foundation, Inc.,
|
|
51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
The full GNU General Public License is included in this distribution in
|
|
the file called "COPYING".
|
|
|
|
Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
|
|
*******************************************************************************/
|
|
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/ethtool.h>
|
|
#include <linux/mii.h>
|
|
#include <linux/phy.h>
|
|
|
|
#include "stmmac.h"
|
|
#include "dwmac_dma.h"
|
|
|
|
#define REG_SPACE_SIZE 0x1054
|
|
#define MAC100_ETHTOOL_NAME "st_mac100"
|
|
#define GMAC_ETHTOOL_NAME "st_gmac"
|
|
|
|
struct stmmac_stats {
|
|
char stat_string[ETH_GSTRING_LEN];
|
|
int sizeof_stat;
|
|
int stat_offset;
|
|
};
|
|
|
|
#define STMMAC_STAT(m) \
|
|
{ #m, FIELD_SIZEOF(struct stmmac_extra_stats, m), \
|
|
offsetof(struct stmmac_priv, xstats.m)}
|
|
|
|
static const struct stmmac_stats stmmac_gstrings_stats[] = {
|
|
STMMAC_STAT(tx_underflow),
|
|
STMMAC_STAT(tx_carrier),
|
|
STMMAC_STAT(tx_losscarrier),
|
|
STMMAC_STAT(tx_heartbeat),
|
|
STMMAC_STAT(tx_deferred),
|
|
STMMAC_STAT(tx_vlan),
|
|
STMMAC_STAT(rx_vlan),
|
|
STMMAC_STAT(tx_jabber),
|
|
STMMAC_STAT(tx_frame_flushed),
|
|
STMMAC_STAT(tx_payload_error),
|
|
STMMAC_STAT(tx_ip_header_error),
|
|
STMMAC_STAT(rx_desc),
|
|
STMMAC_STAT(rx_partial),
|
|
STMMAC_STAT(rx_runt),
|
|
STMMAC_STAT(rx_toolong),
|
|
STMMAC_STAT(rx_collision),
|
|
STMMAC_STAT(rx_crc),
|
|
STMMAC_STAT(rx_length),
|
|
STMMAC_STAT(rx_mii),
|
|
STMMAC_STAT(rx_multicast),
|
|
STMMAC_STAT(rx_gmac_overflow),
|
|
STMMAC_STAT(rx_watchdog),
|
|
STMMAC_STAT(da_rx_filter_fail),
|
|
STMMAC_STAT(sa_rx_filter_fail),
|
|
STMMAC_STAT(rx_missed_cntr),
|
|
STMMAC_STAT(rx_overflow_cntr),
|
|
STMMAC_STAT(tx_undeflow_irq),
|
|
STMMAC_STAT(tx_process_stopped_irq),
|
|
STMMAC_STAT(tx_jabber_irq),
|
|
STMMAC_STAT(rx_overflow_irq),
|
|
STMMAC_STAT(rx_buf_unav_irq),
|
|
STMMAC_STAT(rx_process_stopped_irq),
|
|
STMMAC_STAT(rx_watchdog_irq),
|
|
STMMAC_STAT(tx_early_irq),
|
|
STMMAC_STAT(fatal_bus_error_irq),
|
|
STMMAC_STAT(threshold),
|
|
STMMAC_STAT(tx_pkt_n),
|
|
STMMAC_STAT(rx_pkt_n),
|
|
STMMAC_STAT(poll_n),
|
|
STMMAC_STAT(sched_timer_n),
|
|
STMMAC_STAT(normal_irq_n),
|
|
};
|
|
#define STMMAC_STATS_LEN ARRAY_SIZE(stmmac_gstrings_stats)
|
|
|
|
void stmmac_ethtool_getdrvinfo(struct net_device *dev,
|
|
struct ethtool_drvinfo *info)
|
|
{
|
|
struct stmmac_priv *priv = netdev_priv(dev);
|
|
|
|
if (!priv->is_gmac)
|
|
strcpy(info->driver, MAC100_ETHTOOL_NAME);
|
|
else
|
|
strcpy(info->driver, GMAC_ETHTOOL_NAME);
|
|
|
|
strcpy(info->version, DRV_MODULE_VERSION);
|
|
info->fw_version[0] = '\0';
|
|
info->n_stats = STMMAC_STATS_LEN;
|
|
}
|
|
|
|
int stmmac_ethtool_getsettings(struct net_device *dev, struct ethtool_cmd *cmd)
|
|
{
|
|
struct stmmac_priv *priv = netdev_priv(dev);
|
|
struct phy_device *phy = priv->phydev;
|
|
int rc;
|
|
if (phy == NULL) {
|
|
pr_err("%s: %s: PHY is not registered\n",
|
|
__func__, dev->name);
|
|
return -ENODEV;
|
|
}
|
|
if (!netif_running(dev)) {
|
|
pr_err("%s: interface is disabled: we cannot track "
|
|
"link speed / duplex setting\n", dev->name);
|
|
return -EBUSY;
|
|
}
|
|
cmd->transceiver = XCVR_INTERNAL;
|
|
spin_lock_irq(&priv->lock);
|
|
rc = phy_ethtool_gset(phy, cmd);
|
|
spin_unlock_irq(&priv->lock);
|
|
return rc;
|
|
}
|
|
|
|
int stmmac_ethtool_setsettings(struct net_device *dev, struct ethtool_cmd *cmd)
|
|
{
|
|
struct stmmac_priv *priv = netdev_priv(dev);
|
|
struct phy_device *phy = priv->phydev;
|
|
int rc;
|
|
|
|
spin_lock(&priv->lock);
|
|
rc = phy_ethtool_sset(phy, cmd);
|
|
spin_unlock(&priv->lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
u32 stmmac_ethtool_getmsglevel(struct net_device *dev)
|
|
{
|
|
struct stmmac_priv *priv = netdev_priv(dev);
|
|
return priv->msg_enable;
|
|
}
|
|
|
|
void stmmac_ethtool_setmsglevel(struct net_device *dev, u32 level)
|
|
{
|
|
struct stmmac_priv *priv = netdev_priv(dev);
|
|
priv->msg_enable = level;
|
|
|
|
}
|
|
|
|
int stmmac_check_if_running(struct net_device *dev)
|
|
{
|
|
if (!netif_running(dev))
|
|
return -EBUSY;
|
|
return 0;
|
|
}
|
|
|
|
int stmmac_ethtool_get_regs_len(struct net_device *dev)
|
|
{
|
|
return REG_SPACE_SIZE;
|
|
}
|
|
|
|
void stmmac_ethtool_gregs(struct net_device *dev,
|
|
struct ethtool_regs *regs, void *space)
|
|
{
|
|
int i;
|
|
u32 *reg_space = (u32 *) space;
|
|
|
|
struct stmmac_priv *priv = netdev_priv(dev);
|
|
|
|
memset(reg_space, 0x0, REG_SPACE_SIZE);
|
|
|
|
if (!priv->is_gmac) {
|
|
/* MAC registers */
|
|
for (i = 0; i < 12; i++)
|
|
reg_space[i] = readl(priv->ioaddr + (i * 4));
|
|
/* DMA registers */
|
|
for (i = 0; i < 9; i++)
|
|
reg_space[i + 12] =
|
|
readl(priv->ioaddr + (DMA_BUS_MODE + (i * 4)));
|
|
reg_space[22] = readl(priv->ioaddr + DMA_CUR_TX_BUF_ADDR);
|
|
reg_space[23] = readl(priv->ioaddr + DMA_CUR_RX_BUF_ADDR);
|
|
} else {
|
|
/* MAC registers */
|
|
for (i = 0; i < 55; i++)
|
|
reg_space[i] = readl(priv->ioaddr + (i * 4));
|
|
/* DMA registers */
|
|
for (i = 0; i < 22; i++)
|
|
reg_space[i + 55] =
|
|
readl(priv->ioaddr + (DMA_BUS_MODE + (i * 4)));
|
|
}
|
|
}
|
|
|
|
int stmmac_ethtool_set_tx_csum(struct net_device *netdev, u32 data)
|
|
{
|
|
if (data)
|
|
netdev->features |= NETIF_F_HW_CSUM;
|
|
else
|
|
netdev->features &= ~NETIF_F_HW_CSUM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
u32 stmmac_ethtool_get_rx_csum(struct net_device *dev)
|
|
{
|
|
struct stmmac_priv *priv = netdev_priv(dev);
|
|
|
|
return priv->rx_coe;
|
|
}
|
|
|
|
static void
|
|
stmmac_get_pauseparam(struct net_device *netdev,
|
|
struct ethtool_pauseparam *pause)
|
|
{
|
|
struct stmmac_priv *priv = netdev_priv(netdev);
|
|
|
|
spin_lock(&priv->lock);
|
|
|
|
pause->rx_pause = 0;
|
|
pause->tx_pause = 0;
|
|
pause->autoneg = priv->phydev->autoneg;
|
|
|
|
if (priv->flow_ctrl & FLOW_RX)
|
|
pause->rx_pause = 1;
|
|
if (priv->flow_ctrl & FLOW_TX)
|
|
pause->tx_pause = 1;
|
|
|
|
spin_unlock(&priv->lock);
|
|
}
|
|
|
|
static int
|
|
stmmac_set_pauseparam(struct net_device *netdev,
|
|
struct ethtool_pauseparam *pause)
|
|
{
|
|
struct stmmac_priv *priv = netdev_priv(netdev);
|
|
struct phy_device *phy = priv->phydev;
|
|
int new_pause = FLOW_OFF;
|
|
int ret = 0;
|
|
|
|
spin_lock(&priv->lock);
|
|
|
|
if (pause->rx_pause)
|
|
new_pause |= FLOW_RX;
|
|
if (pause->tx_pause)
|
|
new_pause |= FLOW_TX;
|
|
|
|
priv->flow_ctrl = new_pause;
|
|
|
|
if (phy->autoneg) {
|
|
if (netif_running(netdev)) {
|
|
struct ethtool_cmd cmd;
|
|
/* auto-negotiation automatically restarted */
|
|
cmd.cmd = ETHTOOL_NWAY_RST;
|
|
cmd.supported = phy->supported;
|
|
cmd.advertising = phy->advertising;
|
|
cmd.autoneg = phy->autoneg;
|
|
cmd.speed = phy->speed;
|
|
cmd.duplex = phy->duplex;
|
|
cmd.phy_address = phy->addr;
|
|
ret = phy_ethtool_sset(phy, &cmd);
|
|
}
|
|
} else
|
|
priv->hw->mac->flow_ctrl(priv->ioaddr, phy->duplex,
|
|
priv->flow_ctrl, priv->pause);
|
|
spin_unlock(&priv->lock);
|
|
return ret;
|
|
}
|
|
|
|
static void stmmac_get_ethtool_stats(struct net_device *dev,
|
|
struct ethtool_stats *dummy, u64 *data)
|
|
{
|
|
struct stmmac_priv *priv = netdev_priv(dev);
|
|
int i;
|
|
|
|
/* Update HW stats if supported */
|
|
priv->hw->dma->dma_diagnostic_fr(&dev->stats, (void *) &priv->xstats,
|
|
priv->ioaddr);
|
|
|
|
for (i = 0; i < STMMAC_STATS_LEN; i++) {
|
|
char *p = (char *)priv + stmmac_gstrings_stats[i].stat_offset;
|
|
data[i] = (stmmac_gstrings_stats[i].sizeof_stat ==
|
|
sizeof(u64)) ? (*(u64 *)p) : (*(u32 *)p);
|
|
}
|
|
}
|
|
|
|
static int stmmac_get_sset_count(struct net_device *netdev, int sset)
|
|
{
|
|
switch (sset) {
|
|
case ETH_SS_STATS:
|
|
return STMMAC_STATS_LEN;
|
|
default:
|
|
return -EOPNOTSUPP;
|
|
}
|
|
}
|
|
|
|
static void stmmac_get_strings(struct net_device *dev, u32 stringset, u8 *data)
|
|
{
|
|
int i;
|
|
u8 *p = data;
|
|
|
|
switch (stringset) {
|
|
case ETH_SS_STATS:
|
|
for (i = 0; i < STMMAC_STATS_LEN; i++) {
|
|
memcpy(p, stmmac_gstrings_stats[i].stat_string,
|
|
ETH_GSTRING_LEN);
|
|
p += ETH_GSTRING_LEN;
|
|
}
|
|
break;
|
|
default:
|
|
WARN_ON(1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Currently only support WOL through Magic packet. */
|
|
static void stmmac_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
|
|
{
|
|
struct stmmac_priv *priv = netdev_priv(dev);
|
|
|
|
spin_lock_irq(&priv->lock);
|
|
if (priv->wolenabled == PMT_SUPPORTED) {
|
|
wol->supported = WAKE_MAGIC;
|
|
wol->wolopts = priv->wolopts;
|
|
}
|
|
spin_unlock_irq(&priv->lock);
|
|
}
|
|
|
|
static int stmmac_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
|
|
{
|
|
struct stmmac_priv *priv = netdev_priv(dev);
|
|
u32 support = WAKE_MAGIC;
|
|
|
|
if (priv->wolenabled == PMT_NOT_SUPPORTED)
|
|
return -EINVAL;
|
|
|
|
if (wol->wolopts & ~support)
|
|
return -EINVAL;
|
|
|
|
if (wol->wolopts == 0)
|
|
device_set_wakeup_enable(priv->device, 0);
|
|
else
|
|
device_set_wakeup_enable(priv->device, 1);
|
|
|
|
spin_lock_irq(&priv->lock);
|
|
priv->wolopts = wol->wolopts;
|
|
spin_unlock_irq(&priv->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct ethtool_ops stmmac_ethtool_ops = {
|
|
.begin = stmmac_check_if_running,
|
|
.get_drvinfo = stmmac_ethtool_getdrvinfo,
|
|
.get_settings = stmmac_ethtool_getsettings,
|
|
.set_settings = stmmac_ethtool_setsettings,
|
|
.get_msglevel = stmmac_ethtool_getmsglevel,
|
|
.set_msglevel = stmmac_ethtool_setmsglevel,
|
|
.get_regs = stmmac_ethtool_gregs,
|
|
.get_regs_len = stmmac_ethtool_get_regs_len,
|
|
.get_link = ethtool_op_get_link,
|
|
.get_rx_csum = stmmac_ethtool_get_rx_csum,
|
|
.get_tx_csum = ethtool_op_get_tx_csum,
|
|
.set_tx_csum = stmmac_ethtool_set_tx_csum,
|
|
.get_sg = ethtool_op_get_sg,
|
|
.set_sg = ethtool_op_set_sg,
|
|
.get_pauseparam = stmmac_get_pauseparam,
|
|
.set_pauseparam = stmmac_set_pauseparam,
|
|
.get_ethtool_stats = stmmac_get_ethtool_stats,
|
|
.get_strings = stmmac_get_strings,
|
|
.get_wol = stmmac_get_wol,
|
|
.set_wol = stmmac_set_wol,
|
|
.get_sset_count = stmmac_get_sset_count,
|
|
#ifdef NETIF_F_TSO
|
|
.get_tso = ethtool_op_get_tso,
|
|
.set_tso = ethtool_op_set_tso,
|
|
#endif
|
|
};
|
|
|
|
void stmmac_set_ethtool_ops(struct net_device *netdev)
|
|
{
|
|
SET_ETHTOOL_OPS(netdev, &stmmac_ethtool_ops);
|
|
}
|