ckfwq/linux-3.0.4/drivers/net/gsc3280mac/gsc3280mac_ethtool.c

317 lines
8.2 KiB
C

#include <linux/etherdevice.h>
#include <linux/ethtool.h>
#include <linux/mii.h>
#include <linux/phy.h>
#include "gsc3280mac.h"
#include "gsc3280mac_dma.h"
#define REG_SPACE_SIZE 0x1054
#define MAC100_ETHTOOL_NAME "st_mac100"
#define GSC3280MAC_ETHTOOL_NAME "st_gmac"
struct gsc3280mac_stats {
char stat_string[ETH_GSTRING_LEN];
int sizeof_stat;
int stat_offset;
};
#define GSC3280MAC_STAT(m) \
{ #m, FIELD_SIZEOF(struct gsc3280mac_extra_stats, m), \
offsetof(struct gsc3280mac_priv, xstats.m)}
static const struct gsc3280mac_stats gsc3280mac_gstrings_stats[] = {
GSC3280MAC_STAT(tx_underflow),
GSC3280MAC_STAT(tx_carrier),
GSC3280MAC_STAT(tx_losscarrier),
GSC3280MAC_STAT(tx_heartbeat),
GSC3280MAC_STAT(tx_deferred),
GSC3280MAC_STAT(tx_vlan),
GSC3280MAC_STAT(rx_vlan),
GSC3280MAC_STAT(tx_jabber),
GSC3280MAC_STAT(tx_frame_flushed),
GSC3280MAC_STAT(tx_payload_error),
GSC3280MAC_STAT(tx_ip_header_error),
GSC3280MAC_STAT(rx_desc),
GSC3280MAC_STAT(rx_partial),
GSC3280MAC_STAT(rx_runt),
GSC3280MAC_STAT(rx_toolong),
GSC3280MAC_STAT(rx_collision),
GSC3280MAC_STAT(rx_crc),
GSC3280MAC_STAT(rx_length),
GSC3280MAC_STAT(rx_mii),
GSC3280MAC_STAT(rx_multicast),
GSC3280MAC_STAT(rx_gmac_overflow),
GSC3280MAC_STAT(rx_watchdog),
GSC3280MAC_STAT(da_rx_filter_fail),
GSC3280MAC_STAT(sa_rx_filter_fail),
GSC3280MAC_STAT(rx_missed_cntr),
GSC3280MAC_STAT(rx_overflow_cntr),
GSC3280MAC_STAT(tx_undeflow_irq),
GSC3280MAC_STAT(tx_process_stopped_irq),
GSC3280MAC_STAT(tx_jabber_irq),
GSC3280MAC_STAT(rx_overflow_irq),
GSC3280MAC_STAT(rx_buf_unav_irq),
GSC3280MAC_STAT(rx_process_stopped_irq),
GSC3280MAC_STAT(rx_watchdog_irq),
GSC3280MAC_STAT(tx_early_irq),
GSC3280MAC_STAT(fatal_bus_error_irq),
GSC3280MAC_STAT(threshold),
GSC3280MAC_STAT(tx_pkt_n),
GSC3280MAC_STAT(rx_pkt_n),
GSC3280MAC_STAT(poll_n),
GSC3280MAC_STAT(sched_timer_n),
GSC3280MAC_STAT(normal_irq_n),
};
#define GSC3280MAC_STATS_LEN ARRAY_SIZE(gsc3280mac_gstrings_stats)
static void gsc3280mac_ethtool_getdrvinfo(struct net_device *dev,
struct ethtool_drvinfo *info)
{
strcpy(info->driver, GSC3280MAC_ETHTOOL_NAME);
strcpy(info->version, DRV_MODULE_VERSION);
info->fw_version[0] = '\0';
info->n_stats = GSC3280MAC_STATS_LEN;
}
static int gsc3280mac_ethtool_getsettings(struct net_device *dev,
struct ethtool_cmd *cmd)
{
struct gsc3280mac_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;
}
static int gsc3280mac_ethtool_setsettings(struct net_device *dev,
struct ethtool_cmd *cmd)
{
struct gsc3280mac_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;
}
static u32 gsc3280mac_ethtool_getmsglevel(struct net_device *dev)
{
struct gsc3280mac_priv *priv = netdev_priv(dev);
return priv->msg_enable;
}
static void gsc3280mac_ethtool_setmsglevel(struct net_device *dev, u32 level)
{
struct gsc3280mac_priv *priv = netdev_priv(dev);
priv->msg_enable = level;
}
static int gsc3280mac_check_if_running(struct net_device *dev)
{
if (!netif_running(dev))
return -EBUSY;
return 0;
}
static int gsc3280mac_ethtool_get_regs_len(struct net_device *dev)
{
return REG_SPACE_SIZE;
}
static void gsc3280mac_ethtool_gregs(struct net_device *dev,
struct ethtool_regs *regs, void *space)
{
int i;
u32 *reg_space = (u32 *) space;
struct gsc3280mac_priv *priv = netdev_priv(dev);
memset(reg_space, 0x0, REG_SPACE_SIZE);
/* 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)));
}
static void
gsc3280mac_get_pauseparam(struct net_device *netdev,
struct ethtool_pauseparam *pause)
{
struct gsc3280mac_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
gsc3280mac_set_pauseparam(struct net_device *netdev,
struct ethtool_pauseparam *pause)
{
struct gsc3280mac_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;
phy->autoneg = pause->autoneg;
if (phy->autoneg) {
if (netif_running(netdev))
ret = phy_start_aneg(phy);
} else
priv->hw->mac->flow_ctrl(priv->ioaddr, phy->duplex,
priv->flow_ctrl, priv->pause);
spin_unlock(&priv->lock);
return ret;
}
static void gsc3280mac_get_ethtool_stats(struct net_device *dev,
struct ethtool_stats *dummy, u64 *data)
{
struct gsc3280mac_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 < GSC3280MAC_STATS_LEN; i++) {
char *p = (char *)priv + gsc3280mac_gstrings_stats[i].stat_offset;
data[i] = (gsc3280mac_gstrings_stats[i].sizeof_stat ==
sizeof(u64)) ? (*(u64 *)p) : (*(u32 *)p);
}
}
static int gsc3280mac_get_sset_count(struct net_device *netdev, int sset)
{
switch (sset) {
case ETH_SS_STATS:
return GSC3280MAC_STATS_LEN;
default:
return -EOPNOTSUPP;
}
}
static void gsc3280mac_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 < GSC3280MAC_STATS_LEN; i++) {
memcpy(p, gsc3280mac_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 gsc3280mac_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
{
struct gsc3280mac_priv *priv = netdev_priv(dev);
spin_lock_irq(&priv->lock);
if (device_can_wakeup(priv->device)) {
wol->supported = WAKE_MAGIC | WAKE_UCAST;
wol->wolopts = priv->wolopts;
}
spin_unlock_irq(&priv->lock);
}
static int gsc3280mac_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
{
struct gsc3280mac_priv *priv = netdev_priv(dev);
u32 support = WAKE_MAGIC | WAKE_UCAST;
if (!device_can_wakeup(priv->device))
return -EINVAL;
if (wol->wolopts & ~support)
return -EINVAL;
if (wol->wolopts) {
pr_info("gsc3280mac: wakeup enable\n");
device_set_wakeup_enable(priv->device, 1);
enable_irq_wake(dev->irq);
} else {
device_set_wakeup_enable(priv->device, 0);
disable_irq_wake(dev->irq);
}
spin_lock_irq(&priv->lock);
priv->wolopts = wol->wolopts;
spin_unlock_irq(&priv->lock);
return 0;
}
static struct ethtool_ops gsc3280mac_ethtool_ops = {
.begin = gsc3280mac_check_if_running,
.get_drvinfo = gsc3280mac_ethtool_getdrvinfo,
.get_settings = gsc3280mac_ethtool_getsettings,
.set_settings = gsc3280mac_ethtool_setsettings,
.get_msglevel = gsc3280mac_ethtool_getmsglevel,
.set_msglevel = gsc3280mac_ethtool_setmsglevel,
.get_regs = gsc3280mac_ethtool_gregs,
.get_regs_len = gsc3280mac_ethtool_get_regs_len,
.get_link = ethtool_op_get_link,
.get_pauseparam = gsc3280mac_get_pauseparam,
.set_pauseparam = gsc3280mac_set_pauseparam,
.get_ethtool_stats = gsc3280mac_get_ethtool_stats,
.get_strings = gsc3280mac_get_strings,
.get_wol = gsc3280mac_get_wol,
.set_wol = gsc3280mac_set_wol,
.get_sset_count = gsc3280mac_get_sset_count,
};
void gsc3280mac_set_ethtool_ops(struct net_device *netdev)
{
SET_ETHTOOL_OPS(netdev, &gsc3280mac_ethtool_ops);
}