Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/net/ethernet/marvell/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 106 kB image not shown  

Quelle  skge.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-only
/*
 * New driver for Marvell Yukon chipset and SysKonnect Gigabit
 * Ethernet adapters. Based on earlier sk98lin, e100 and
 * FreeBSD if_sk drivers.
 *
 * This driver intentionally does not support all the features
 * of the original driver such as link fail-over and link management because
 * those should be done at higher levels.
 *
 * Copyright (C) 2004, 2005 Stephen Hemminger <shemminger@osdl.org>
 */


#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/in.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/ethtool.h>
#include <linux/pci.h>
#include <linux/if_vlan.h>
#include <linux/ip.h>
#include <linux/delay.h>
#include <linux/crc32.h>
#include <linux/dma-mapping.h>
#include <linux/debugfs.h>
#include <linux/sched.h>
#include <linux/seq_file.h>
#include <linux/mii.h>
#include <linux/slab.h>
#include <linux/dmi.h>
#include <linux/prefetch.h>
#include <asm/irq.h>

#include "skge.h"

#define DRV_NAME  "skge"
#define DRV_VERSION  "1.14"

#define DEFAULT_TX_RING_SIZE 128
#define DEFAULT_RX_RING_SIZE 512
#define MAX_TX_RING_SIZE 1024
#define TX_LOW_WATER  (MAX_SKB_FRAGS + 1)
#define MAX_RX_RING_SIZE 4096
#define RX_COPY_THRESHOLD 128
#define RX_BUF_SIZE  1536
#define PHY_RETRIES         1000
#define ETH_JUMBO_MTU  9000
#define TX_WATCHDOG  (5 * HZ)
#define BLINK_MS  250
#define LINK_HZ   HZ

#define SKGE_EEPROM_MAGIC 0x9933aabb


MODULE_DESCRIPTION("SysKonnect Gigabit Ethernet driver");
MODULE_AUTHOR("Stephen Hemminger ");
MODULE_LICENSE("GPL");
MODULE_VERSION(DRV_VERSION);

static const u32 default_msg = (NETIF_MSG_DRV | NETIF_MSG_PROBE |
    NETIF_MSG_LINK | NETIF_MSG_IFUP |
    NETIF_MSG_IFDOWN);

static int debug = -1; /* defaults above */
module_param(debug, int, 0);
MODULE_PARM_DESC(debug, "Debug level (0=none,...,16=all)");

static const struct pci_device_id skge_id_table[] = {
 { PCI_DEVICE(PCI_VENDOR_ID_3COM, 0x1700) },   /* 3Com 3C940 */
 { PCI_DEVICE(PCI_VENDOR_ID_3COM, 0x80EB) },   /* 3Com 3C940B */
#ifdef CONFIG_SKGE_GENESIS
 { PCI_DEVICE(PCI_VENDOR_ID_SYSKONNECT, 0x4300) }, /* SK-9xx */
#endif
 { PCI_DEVICE(PCI_VENDOR_ID_SYSKONNECT, 0x4320) }, /* SK-98xx V2.0 */
 { PCI_DEVICE(PCI_VENDOR_ID_DLINK, 0x4b01) },   /* D-Link DGE-530T (rev.B) */
 { PCI_DEVICE(PCI_VENDOR_ID_DLINK, 0x4c00) },   /* D-Link DGE-530T */
 { PCI_DEVICE(PCI_VENDOR_ID_DLINK, 0x4302) },   /* D-Link DGE-530T Rev C1 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4320) },   /* Marvell Yukon 88E8001/8003/8010 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x5005) },   /* Belkin */
 { PCI_DEVICE(PCI_VENDOR_ID_CNET, 0x434E) },    /* CNet PowerG-2000 */
 { PCI_DEVICE(PCI_VENDOR_ID_LINKSYS, 0x1064) },   /* Linksys EG1064 v2 */
 { PCI_VENDOR_ID_LINKSYS, 0x1032, PCI_ANY_ID, 0x0015 }, /* Linksys EG1032 v2 */
 { 0 }
};
MODULE_DEVICE_TABLE(pci, skge_id_table);

static int skge_up(struct net_device *dev);
static int skge_down(struct net_device *dev);
static void skge_phy_reset(struct skge_port *skge);
static void skge_tx_clean(struct net_device *dev);
static int xm_phy_write(struct skge_hw *hw, int port, u16 reg, u16 val);
static int gm_phy_write(struct skge_hw *hw, int port, u16 reg, u16 val);
static void genesis_get_stats(struct skge_port *skge, u64 *data);
static void yukon_get_stats(struct skge_port *skge, u64 *data);
static void yukon_init(struct skge_hw *hw, int port);
static void genesis_mac_init(struct skge_hw *hw, int port);
static void genesis_link_up(struct skge_port *skge);
static void skge_set_multicast(struct net_device *dev);
static irqreturn_t skge_intr(int irq, void *dev_id);

/* Avoid conditionals by using array */
static const int txqaddr[] = { Q_XA1, Q_XA2 };
static const int rxqaddr[] = { Q_R1, Q_R2 };
static const u32 rxirqmask[] = { IS_R1_F, IS_R2_F };
static const u32 txirqmask[] = { IS_XA1_F, IS_XA2_F };
static const u32 napimask[] = { IS_R1_F|IS_XA1_F, IS_R2_F|IS_XA2_F };
static const u32 portmask[] = { IS_PORT_1, IS_PORT_2 };

static inline bool is_genesis(const struct skge_hw *hw)
{
#ifdef CONFIG_SKGE_GENESIS
 return hw->chip_id == CHIP_ID_GENESIS;
#else
 return false;
#endif
}

static int skge_get_regs_len(struct net_device *dev)
{
 return 0x4000;
}

/*
 * Returns copy of whole control register region
 * Note: skip RAM address register because accessing it will
 *   cause bus hangs!
 */

static void skge_get_regs(struct net_device *dev, struct ethtool_regs *regs,
     void *p)
{
 const struct skge_port *skge = netdev_priv(dev);
 const void __iomem *io = skge->hw->regs;

 regs->version = 1;
 memset(p, 0, regs->len);
 memcpy_fromio(p, io, B3_RAM_ADDR);

 if (regs->len > B3_RI_WTO_R1) {
  memcpy_fromio(p + B3_RI_WTO_R1, io + B3_RI_WTO_R1,
         regs->len - B3_RI_WTO_R1);
 }
}

/* Wake on Lan only supported on Yukon chips with rev 1 or above */
static u32 wol_supported(const struct skge_hw *hw)
{
 if (is_genesis(hw))
  return 0;

 if (hw->chip_id == CHIP_ID_YUKON && hw->chip_rev == 0)
  return 0;

 return WAKE_MAGIC | WAKE_PHY;
}

static void skge_wol_init(struct skge_port *skge)
{
 struct skge_hw *hw = skge->hw;
 int port = skge->port;
 u16 ctrl;

 skge_write16(hw, B0_CTST, CS_RST_CLR);
 skge_write16(hw, SK_REG(port, GMAC_LINK_CTRL), GMLC_RST_CLR);

 /* Turn on Vaux */
 skge_write8(hw, B0_POWER_CTRL,
      PC_VAUX_ENA | PC_VCC_ENA | PC_VAUX_ON | PC_VCC_OFF);

 /* WA code for COMA mode -- clear PHY reset */
 if (hw->chip_id == CHIP_ID_YUKON_LITE &&
     hw->chip_rev >= CHIP_REV_YU_LITE_A3) {
  u32 reg = skge_read32(hw, B2_GP_IO);
  reg |= GP_DIR_9;
  reg &= ~GP_IO_9;
  skge_write32(hw, B2_GP_IO, reg);
 }

 skge_write32(hw, SK_REG(port, GPHY_CTRL),
       GPC_DIS_SLEEP |
       GPC_HWCFG_M_3 | GPC_HWCFG_M_2 | GPC_HWCFG_M_1 | GPC_HWCFG_M_0 |
       GPC_ANEG_1 | GPC_RST_SET);

 skge_write32(hw, SK_REG(port, GPHY_CTRL),
       GPC_DIS_SLEEP |
       GPC_HWCFG_M_3 | GPC_HWCFG_M_2 | GPC_HWCFG_M_1 | GPC_HWCFG_M_0 |
       GPC_ANEG_1 | GPC_RST_CLR);

 skge_write32(hw, SK_REG(port, GMAC_CTRL), GMC_RST_CLR);

 /* Force to 10/100 skge_reset will re-enable on resume  */
 gm_phy_write(hw, port, PHY_MARV_AUNE_ADV,
       (PHY_AN_100FULL | PHY_AN_100HALF |
        PHY_AN_10FULL | PHY_AN_10HALF | PHY_AN_CSMA));
 /* no 1000 HD/FD */
 gm_phy_write(hw, port, PHY_MARV_1000T_CTRL, 0);
 gm_phy_write(hw, port, PHY_MARV_CTRL,
       PHY_CT_RESET | PHY_CT_SPS_LSB | PHY_CT_ANE |
       PHY_CT_RE_CFG | PHY_CT_DUP_MD);


 /* Set GMAC to no flow control and auto update for speed/duplex */
 gma_write16(hw, port, GM_GP_CTRL,
      GM_GPCR_FC_TX_DIS|GM_GPCR_TX_ENA|GM_GPCR_RX_ENA|
      GM_GPCR_DUP_FULL|GM_GPCR_FC_RX_DIS|GM_GPCR_AU_FCT_DIS);

 /* Set WOL address */
 memcpy_toio(hw->regs + WOL_REGS(port, WOL_MAC_ADDR),
      skge->netdev->dev_addr, ETH_ALEN);

 /* Turn on appropriate WOL control bits */
 skge_write16(hw, WOL_REGS(port, WOL_CTRL_STAT), WOL_CTL_CLEAR_RESULT);
 ctrl = 0;
 if (skge->wol & WAKE_PHY)
  ctrl |= WOL_CTL_ENA_PME_ON_LINK_CHG|WOL_CTL_ENA_LINK_CHG_UNIT;
 else
  ctrl |= WOL_CTL_DIS_PME_ON_LINK_CHG|WOL_CTL_DIS_LINK_CHG_UNIT;

 if (skge->wol & WAKE_MAGIC)
  ctrl |= WOL_CTL_ENA_PME_ON_MAGIC_PKT|WOL_CTL_ENA_MAGIC_PKT_UNIT;
 else
  ctrl |= WOL_CTL_DIS_PME_ON_MAGIC_PKT|WOL_CTL_DIS_MAGIC_PKT_UNIT;

 ctrl |= WOL_CTL_DIS_PME_ON_PATTERN|WOL_CTL_DIS_PATTERN_UNIT;
 skge_write16(hw, WOL_REGS(port, WOL_CTRL_STAT), ctrl);

 /* block receiver */
 skge_write8(hw, SK_REG(port, RX_GMF_CTRL_T), GMF_RST_SET);
}

static void skge_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
{
 struct skge_port *skge = netdev_priv(dev);

 wol->supported = wol_supported(skge->hw);
 wol->wolopts = skge->wol;
}

static int skge_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
{
 struct skge_port *skge = netdev_priv(dev);
 struct skge_hw *hw = skge->hw;

 if ((wol->wolopts & ~wol_supported(hw)) ||
     !device_can_wakeup(&hw->pdev->dev))
  return -EOPNOTSUPP;

 skge->wol = wol->wolopts;

 device_set_wakeup_enable(&hw->pdev->dev, skge->wol);

 return 0;
}

/* Determine supported/advertised modes based on hardware.
 * Note: ethtool ADVERTISED_xxx == SUPPORTED_xxx
 */

static u32 skge_supported_modes(const struct skge_hw *hw)
{
 u32 supported;

 if (hw->copper) {
  supported = (SUPPORTED_10baseT_Half |
        SUPPORTED_10baseT_Full |
        SUPPORTED_100baseT_Half |
        SUPPORTED_100baseT_Full |
        SUPPORTED_1000baseT_Half |
        SUPPORTED_1000baseT_Full |
        SUPPORTED_Autoneg |
        SUPPORTED_TP);

  if (is_genesis(hw))
   supported &= ~(SUPPORTED_10baseT_Half |
           SUPPORTED_10baseT_Full |
           SUPPORTED_100baseT_Half |
           SUPPORTED_100baseT_Full);

  else if (hw->chip_id == CHIP_ID_YUKON)
   supported &= ~SUPPORTED_1000baseT_Half;
 } else
  supported = (SUPPORTED_1000baseT_Full |
        SUPPORTED_1000baseT_Half |
        SUPPORTED_FIBRE |
        SUPPORTED_Autoneg);

 return supported;
}

static int skge_get_link_ksettings(struct net_device *dev,
       struct ethtool_link_ksettings *cmd)
{
 struct skge_port *skge = netdev_priv(dev);
 struct skge_hw *hw = skge->hw;
 u32 supported, advertising;

 supported = skge_supported_modes(hw);

 if (hw->copper) {
  cmd->base.port = PORT_TP;
  cmd->base.phy_address = hw->phy_addr;
 } else
  cmd->base.port = PORT_FIBRE;

 advertising = skge->advertising;
 cmd->base.autoneg = skge->autoneg;
 cmd->base.speed = skge->speed;
 cmd->base.duplex = skge->duplex;

 ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.supported,
      supported);
 ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.advertising,
      advertising);

 return 0;
}

static int skge_set_link_ksettings(struct net_device *dev,
       const struct ethtool_link_ksettings *cmd)
{
 struct skge_port *skge = netdev_priv(dev);
 const struct skge_hw *hw = skge->hw;
 u32 supported = skge_supported_modes(hw);
 int err = 0;
 u32 advertising;

 ethtool_convert_link_mode_to_legacy_u32(&advertising,
      cmd->link_modes.advertising);

 if (cmd->base.autoneg == AUTONEG_ENABLE) {
  advertising = supported;
  skge->duplex = -1;
  skge->speed = -1;
 } else {
  u32 setting;
  u32 speed = cmd->base.speed;

  switch (speed) {
  case SPEED_1000:
   if (cmd->base.duplex == DUPLEX_FULL)
    setting = SUPPORTED_1000baseT_Full;
   else if (cmd->base.duplex == DUPLEX_HALF)
    setting = SUPPORTED_1000baseT_Half;
   else
    return -EINVAL;
   break;
  case SPEED_100:
   if (cmd->base.duplex == DUPLEX_FULL)
    setting = SUPPORTED_100baseT_Full;
   else if (cmd->base.duplex == DUPLEX_HALF)
    setting = SUPPORTED_100baseT_Half;
   else
    return -EINVAL;
   break;

  case SPEED_10:
   if (cmd->base.duplex == DUPLEX_FULL)
    setting = SUPPORTED_10baseT_Full;
   else if (cmd->base.duplex == DUPLEX_HALF)
    setting = SUPPORTED_10baseT_Half;
   else
    return -EINVAL;
   break;
  default:
   return -EINVAL;
  }

  if ((setting & supported) == 0)
   return -EINVAL;

  skge->speed = speed;
  skge->duplex = cmd->base.duplex;
 }

 skge->autoneg = cmd->base.autoneg;
 skge->advertising = advertising;

 if (netif_running(dev)) {
  skge_down(dev);
  err = skge_up(dev);
  if (err) {
   dev_close(dev);
   return err;
  }
 }

 return 0;
}

static void skge_get_drvinfo(struct net_device *dev,
        struct ethtool_drvinfo *info)
{
 struct skge_port *skge = netdev_priv(dev);

 strscpy(info->driver, DRV_NAME, sizeof(info->driver));
 strscpy(info->version, DRV_VERSION, sizeof(info->version));
 strscpy(info->bus_info, pci_name(skge->hw->pdev),
  sizeof(info->bus_info));
}

static const struct skge_stat {
 char     name[ETH_GSTRING_LEN];
 u16    xmac_offset;
 u16    gma_offset;
} skge_stats[] = {
 { "tx_bytes",  XM_TXO_OK_HI,  GM_TXO_OK_HI },
 { "rx_bytes",  XM_RXO_OK_HI,  GM_RXO_OK_HI },

 { "tx_broadcast", XM_TXF_BC_OK,  GM_TXF_BC_OK },
 { "rx_broadcast", XM_RXF_BC_OK,  GM_RXF_BC_OK },
 { "tx_multicast", XM_TXF_MC_OK,  GM_TXF_MC_OK },
 { "rx_multicast", XM_RXF_MC_OK,  GM_RXF_MC_OK },
 { "tx_unicast",  XM_TXF_UC_OK,  GM_TXF_UC_OK },
 { "rx_unicast",  XM_RXF_UC_OK,  GM_RXF_UC_OK },
 { "tx_mac_pause", XM_TXF_MPAUSE, GM_TXF_MPAUSE },
 { "rx_mac_pause", XM_RXF_MPAUSE, GM_RXF_MPAUSE },

 { "collisions",  XM_TXF_SNG_COL, GM_TXF_SNG_COL },
 { "multi_collisions", XM_TXF_MUL_COL, GM_TXF_MUL_COL },
 { "aborted",  XM_TXF_ABO_COL, GM_TXF_ABO_COL },
 { "late_collision", XM_TXF_LAT_COL, GM_TXF_LAT_COL },
 { "fifo_underrun", XM_TXE_FIFO_UR, GM_TXE_FIFO_UR },
 { "fifo_overflow", XM_RXE_FIFO_OV, GM_RXE_FIFO_OV },

 { "rx_toolong",  XM_RXF_LNG_ERR, GM_RXF_LNG_ERR },
 { "rx_jabber",  XM_RXF_JAB_PKT, GM_RXF_JAB_PKT },
 { "rx_runt",  XM_RXE_RUNT,  GM_RXE_FRAG },
 { "rx_too_long", XM_RXF_LNG_ERR, GM_RXF_LNG_ERR },
 { "rx_fcs_error", XM_RXF_FCS_ERR, GM_RXF_FCS_ERR },
};

static int skge_get_sset_count(struct net_device *dev, int sset)
{
 switch (sset) {
 case ETH_SS_STATS:
  return ARRAY_SIZE(skge_stats);
 default:
  return -EOPNOTSUPP;
 }
}

static void skge_get_ethtool_stats(struct net_device *dev,
       struct ethtool_stats *stats, u64 *data)
{
 struct skge_port *skge = netdev_priv(dev);

 if (is_genesis(skge->hw))
  genesis_get_stats(skge, data);
 else
  yukon_get_stats(skge, data);
}

/* Use hardware MIB variables for critical path statistics and
 * transmit feedback not reported at interrupt.
 * Other errors are accounted for in interrupt handler.
 */

static struct net_device_stats *skge_get_stats(struct net_device *dev)
{
 struct skge_port *skge = netdev_priv(dev);
 u64 data[ARRAY_SIZE(skge_stats)];

 if (is_genesis(skge->hw))
  genesis_get_stats(skge, data);
 else
  yukon_get_stats(skge, data);

 dev->stats.tx_bytes = data[0];
 dev->stats.rx_bytes = data[1];
 dev->stats.tx_packets = data[2] + data[4] + data[6];
 dev->stats.rx_packets = data[3] + data[5] + data[7];
 dev->stats.multicast = data[3] + data[5];
 dev->stats.collisions = data[10];
 dev->stats.tx_aborted_errors = data[12];

 return &dev->stats;
}

static void skge_get_strings(struct net_device *dev, u32 stringset, u8 *data)
{
 int i;

 switch (stringset) {
 case ETH_SS_STATS:
  for (i = 0; i < ARRAY_SIZE(skge_stats); i++)
   ethtool_puts(&data, skge_stats[i].name);
  break;
 }
}

static void skge_get_ring_param(struct net_device *dev,
    struct ethtool_ringparam *p,
    struct kernel_ethtool_ringparam *kernel_p,
    struct netlink_ext_ack *extack)
{
 struct skge_port *skge = netdev_priv(dev);

 p->rx_max_pending = MAX_RX_RING_SIZE;
 p->tx_max_pending = MAX_TX_RING_SIZE;

 p->rx_pending = skge->rx_ring.count;
 p->tx_pending = skge->tx_ring.count;
}

static int skge_set_ring_param(struct net_device *dev,
          struct ethtool_ringparam *p,
          struct kernel_ethtool_ringparam *kernel_p,
          struct netlink_ext_ack *extack)
{
 struct skge_port *skge = netdev_priv(dev);
 int err = 0;

 if (p->rx_pending == 0 || p->rx_pending > MAX_RX_RING_SIZE ||
     p->tx_pending < TX_LOW_WATER || p->tx_pending > MAX_TX_RING_SIZE)
  return -EINVAL;

 skge->rx_ring.count = p->rx_pending;
 skge->tx_ring.count = p->tx_pending;

 if (netif_running(dev)) {
  skge_down(dev);
  err = skge_up(dev);
  if (err)
   dev_close(dev);
 }

 return err;
}

static u32 skge_get_msglevel(struct net_device *netdev)
{
 struct skge_port *skge = netdev_priv(netdev);
 return skge->msg_enable;
}

static void skge_set_msglevel(struct net_device *netdev, u32 value)
{
 struct skge_port *skge = netdev_priv(netdev);
 skge->msg_enable = value;
}

static int skge_nway_reset(struct net_device *dev)
{
 struct skge_port *skge = netdev_priv(dev);

 if (skge->autoneg != AUTONEG_ENABLE || !netif_running(dev))
  return -EINVAL;

 skge_phy_reset(skge);
 return 0;
}

static void skge_get_pauseparam(struct net_device *dev,
    struct ethtool_pauseparam *ecmd)
{
 struct skge_port *skge = netdev_priv(dev);

 ecmd->rx_pause = ((skge->flow_control == FLOW_MODE_SYMMETRIC) ||
     (skge->flow_control == FLOW_MODE_SYM_OR_REM));
 ecmd->tx_pause = (ecmd->rx_pause ||
     (skge->flow_control == FLOW_MODE_LOC_SEND));

 ecmd->autoneg = ecmd->rx_pause || ecmd->tx_pause;
}

static int skge_set_pauseparam(struct net_device *dev,
          struct ethtool_pauseparam *ecmd)
{
 struct skge_port *skge = netdev_priv(dev);
 struct ethtool_pauseparam old;
 int err = 0;

 skge_get_pauseparam(dev, &old);

 if (ecmd->autoneg != old.autoneg)
  skge->flow_control = ecmd->autoneg ? FLOW_MODE_NONE : FLOW_MODE_SYMMETRIC;
 else {
  if (ecmd->rx_pause && ecmd->tx_pause)
   skge->flow_control = FLOW_MODE_SYMMETRIC;
  else if (ecmd->rx_pause && !ecmd->tx_pause)
   skge->flow_control = FLOW_MODE_SYM_OR_REM;
  else if (!ecmd->rx_pause && ecmd->tx_pause)
   skge->flow_control = FLOW_MODE_LOC_SEND;
  else
   skge->flow_control = FLOW_MODE_NONE;
 }

 if (netif_running(dev)) {
  skge_down(dev);
  err = skge_up(dev);
  if (err) {
   dev_close(dev);
   return err;
  }
 }

 return 0;
}

/* Chip internal frequency for clock calculations */
static inline u32 hwkhz(const struct skge_hw *hw)
{
 return is_genesis(hw) ? 53125 : 78125;
}

/* Chip HZ to microseconds */
static inline u32 skge_clk2usec(const struct skge_hw *hw, u32 ticks)
{
 return (ticks * 1000) / hwkhz(hw);
}

/* Microseconds to chip HZ */
static inline u32 skge_usecs2clk(const struct skge_hw *hw, u32 usec)
{
 return hwkhz(hw) * usec / 1000;
}

static int skge_get_coalesce(struct net_device *dev,
        struct ethtool_coalesce *ecmd,
        struct kernel_ethtool_coalesce *kernel_coal,
        struct netlink_ext_ack *extack)
{
 struct skge_port *skge = netdev_priv(dev);
 struct skge_hw *hw = skge->hw;
 int port = skge->port;

 ecmd->rx_coalesce_usecs = 0;
 ecmd->tx_coalesce_usecs = 0;

 if (skge_read32(hw, B2_IRQM_CTRL) & TIM_START) {
  u32 delay = skge_clk2usec(hw, skge_read32(hw, B2_IRQM_INI));
  u32 msk = skge_read32(hw, B2_IRQM_MSK);

  if (msk & rxirqmask[port])
   ecmd->rx_coalesce_usecs = delay;
  if (msk & txirqmask[port])
   ecmd->tx_coalesce_usecs = delay;
 }

 return 0;
}

/* Note: interrupt timer is per board, but can turn on/off per port */
static int skge_set_coalesce(struct net_device *dev,
        struct ethtool_coalesce *ecmd,
        struct kernel_ethtool_coalesce *kernel_coal,
        struct netlink_ext_ack *extack)
{
 struct skge_port *skge = netdev_priv(dev);
 struct skge_hw *hw = skge->hw;
 int port = skge->port;
 u32 msk = skge_read32(hw, B2_IRQM_MSK);
 u32 delay = 25;

 if (ecmd->rx_coalesce_usecs == 0)
  msk &= ~rxirqmask[port];
 else if (ecmd->rx_coalesce_usecs < 25 ||
   ecmd->rx_coalesce_usecs > 33333)
  return -EINVAL;
 else {
  msk |= rxirqmask[port];
  delay = ecmd->rx_coalesce_usecs;
 }

 if (ecmd->tx_coalesce_usecs == 0)
  msk &= ~txirqmask[port];
 else if (ecmd->tx_coalesce_usecs < 25 ||
   ecmd->tx_coalesce_usecs > 33333)
  return -EINVAL;
 else {
  msk |= txirqmask[port];
  delay = min(delay, ecmd->rx_coalesce_usecs);
 }

 skge_write32(hw, B2_IRQM_MSK, msk);
 if (msk == 0)
  skge_write32(hw, B2_IRQM_CTRL, TIM_STOP);
 else {
  skge_write32(hw, B2_IRQM_INI, skge_usecs2clk(hw, delay));
  skge_write32(hw, B2_IRQM_CTRL, TIM_START);
 }
 return 0;
}

enum led_mode { LED_MODE_OFF, LED_MODE_ON, LED_MODE_TST };
static void skge_led(struct skge_port *skge, enum led_mode mode)
{
 struct skge_hw *hw = skge->hw;
 int port = skge->port;

 spin_lock_bh(&hw->phy_lock);
 if (is_genesis(hw)) {
  switch (mode) {
  case LED_MODE_OFF:
   if (hw->phy_type == SK_PHY_BCOM)
    xm_phy_write(hw, port, PHY_BCOM_P_EXT_CTRL, PHY_B_PEC_LED_OFF);
   else {
    skge_write32(hw, SK_REG(port, TX_LED_VAL), 0);
    skge_write8(hw, SK_REG(port, TX_LED_CTRL), LED_T_OFF);
   }
   skge_write8(hw, SK_REG(port, LNK_LED_REG), LINKLED_OFF);
   skge_write32(hw, SK_REG(port, RX_LED_VAL), 0);
   skge_write8(hw, SK_REG(port, RX_LED_CTRL), LED_T_OFF);
   break;

  case LED_MODE_ON:
   skge_write8(hw, SK_REG(port, LNK_LED_REG), LINKLED_ON);
   skge_write8(hw, SK_REG(port, LNK_LED_REG), LINKLED_LINKSYNC_ON);

   skge_write8(hw, SK_REG(port, RX_LED_CTRL), LED_START);
   skge_write8(hw, SK_REG(port, TX_LED_CTRL), LED_START);

   break;

  case LED_MODE_TST:
   skge_write8(hw, SK_REG(port, RX_LED_TST), LED_T_ON);
   skge_write32(hw, SK_REG(port, RX_LED_VAL), 100);
   skge_write8(hw, SK_REG(port, RX_LED_CTRL), LED_START);

   if (hw->phy_type == SK_PHY_BCOM)
    xm_phy_write(hw, port, PHY_BCOM_P_EXT_CTRL, PHY_B_PEC_LED_ON);
   else {
    skge_write8(hw, SK_REG(port, TX_LED_TST), LED_T_ON);
    skge_write32(hw, SK_REG(port, TX_LED_VAL), 100);
    skge_write8(hw, SK_REG(port, TX_LED_CTRL), LED_START);
   }

  }
 } else {
  switch (mode) {
  case LED_MODE_OFF:
   gm_phy_write(hw, port, PHY_MARV_LED_CTRL, 0);
   gm_phy_write(hw, port, PHY_MARV_LED_OVER,
         PHY_M_LED_MO_DUP(MO_LED_OFF)  |
         PHY_M_LED_MO_10(MO_LED_OFF)   |
         PHY_M_LED_MO_100(MO_LED_OFF)  |
         PHY_M_LED_MO_1000(MO_LED_OFF) |
         PHY_M_LED_MO_RX(MO_LED_OFF));
   break;
  case LED_MODE_ON:
   gm_phy_write(hw, port, PHY_MARV_LED_CTRL,
         PHY_M_LED_PULS_DUR(PULS_170MS) |
         PHY_M_LED_BLINK_RT(BLINK_84MS) |
         PHY_M_LEDC_TX_CTRL |
         PHY_M_LEDC_DP_CTRL);

   gm_phy_write(hw, port, PHY_MARV_LED_OVER,
         PHY_M_LED_MO_RX(MO_LED_OFF) |
         (skge->speed == SPEED_100 ?
          PHY_M_LED_MO_100(MO_LED_ON) : 0));
   break;
  case LED_MODE_TST:
   gm_phy_write(hw, port, PHY_MARV_LED_CTRL, 0);
   gm_phy_write(hw, port, PHY_MARV_LED_OVER,
         PHY_M_LED_MO_DUP(MO_LED_ON)  |
         PHY_M_LED_MO_10(MO_LED_ON)   |
         PHY_M_LED_MO_100(MO_LED_ON)  |
         PHY_M_LED_MO_1000(MO_LED_ON) |
         PHY_M_LED_MO_RX(MO_LED_ON));
  }
 }
 spin_unlock_bh(&hw->phy_lock);
}

/* blink LED's for finding board */
static int skge_set_phys_id(struct net_device *dev,
       enum ethtool_phys_id_state state)
{
 struct skge_port *skge = netdev_priv(dev);

 switch (state) {
 case ETHTOOL_ID_ACTIVE:
  return 2; /* cycle on/off twice per second */

 case ETHTOOL_ID_ON:
  skge_led(skge, LED_MODE_TST);
  break;

 case ETHTOOL_ID_OFF:
  skge_led(skge, LED_MODE_OFF);
  break;

 case ETHTOOL_ID_INACTIVE:
  /* back to regular LED state */
  skge_led(skge, netif_running(dev) ? LED_MODE_ON : LED_MODE_OFF);
 }

 return 0;
}

static int skge_get_eeprom_len(struct net_device *dev)
{
 struct skge_port *skge = netdev_priv(dev);
 u32 reg2;

 pci_read_config_dword(skge->hw->pdev, PCI_DEV_REG2, ®2);
 return 1 << (((reg2 & PCI_VPD_ROM_SZ) >> 14) + 8);
}

static u32 skge_vpd_read(struct pci_dev *pdev, int cap, u16 offset)
{
 u32 val;

 pci_write_config_word(pdev, cap + PCI_VPD_ADDR, offset);

 do {
  pci_read_config_word(pdev, cap + PCI_VPD_ADDR, &offset);
 } while (!(offset & PCI_VPD_ADDR_F));

 pci_read_config_dword(pdev, cap + PCI_VPD_DATA, &val);
 return val;
}

static void skge_vpd_write(struct pci_dev *pdev, int cap, u16 offset, u32 val)
{
 pci_write_config_dword(pdev, cap + PCI_VPD_DATA, val);
 pci_write_config_word(pdev, cap + PCI_VPD_ADDR,
         offset | PCI_VPD_ADDR_F);

 do {
  pci_read_config_word(pdev, cap + PCI_VPD_ADDR, &offset);
 } while (offset & PCI_VPD_ADDR_F);
}

static int skge_get_eeprom(struct net_device *dev, struct ethtool_eeprom *eeprom,
      u8 *data)
{
 struct skge_port *skge = netdev_priv(dev);
 struct pci_dev *pdev = skge->hw->pdev;
 int cap = pci_find_capability(pdev, PCI_CAP_ID_VPD);
 int length = eeprom->len;
 u16 offset = eeprom->offset;

 if (!cap)
  return -EINVAL;

 eeprom->magic = SKGE_EEPROM_MAGIC;

 while (length > 0) {
  u32 val = skge_vpd_read(pdev, cap, offset);
  int n = min_t(int, length, sizeof(val));

  memcpy(data, &val, n);
  length -= n;
  data += n;
  offset += n;
 }
 return 0;
}

static int skge_set_eeprom(struct net_device *dev, struct ethtool_eeprom *eeprom,
      u8 *data)
{
 struct skge_port *skge = netdev_priv(dev);
 struct pci_dev *pdev = skge->hw->pdev;
 int cap = pci_find_capability(pdev, PCI_CAP_ID_VPD);
 int length = eeprom->len;
 u16 offset = eeprom->offset;

 if (!cap)
  return -EINVAL;

 if (eeprom->magic != SKGE_EEPROM_MAGIC)
  return -EINVAL;

 while (length > 0) {
  u32 val;
  int n = min_t(int, length, sizeof(val));

  if (n < sizeof(val))
   val = skge_vpd_read(pdev, cap, offset);
  memcpy(&val, data, n);

  skge_vpd_write(pdev, cap, offset, val);

  length -= n;
  data += n;
  offset += n;
 }
 return 0;
}

static const struct ethtool_ops skge_ethtool_ops = {
 .supported_coalesce_params = ETHTOOL_COALESCE_USECS,
 .get_drvinfo = skge_get_drvinfo,
 .get_regs_len = skge_get_regs_len,
 .get_regs = skge_get_regs,
 .get_wol = skge_get_wol,
 .set_wol = skge_set_wol,
 .get_msglevel = skge_get_msglevel,
 .set_msglevel = skge_set_msglevel,
 .nway_reset = skge_nway_reset,
 .get_link = ethtool_op_get_link,
 .get_eeprom_len = skge_get_eeprom_len,
 .get_eeprom = skge_get_eeprom,
 .set_eeprom = skge_set_eeprom,
 .get_ringparam = skge_get_ring_param,
 .set_ringparam = skge_set_ring_param,
 .get_pauseparam = skge_get_pauseparam,
 .set_pauseparam = skge_set_pauseparam,
 .get_coalesce = skge_get_coalesce,
 .set_coalesce = skge_set_coalesce,
 .get_strings = skge_get_strings,
 .set_phys_id = skge_set_phys_id,
 .get_sset_count = skge_get_sset_count,
 .get_ethtool_stats = skge_get_ethtool_stats,
 .get_link_ksettings = skge_get_link_ksettings,
 .set_link_ksettings = skge_set_link_ksettings,
};

/*
 * Allocate ring elements and chain them together
 * One-to-one association of board descriptors with ring elements
 */

static int skge_ring_alloc(struct skge_ring *ring, void *vaddr, u32 base)
{
 struct skge_tx_desc *d;
 struct skge_element *e;
 int i;

 ring->start = kcalloc(ring->count, sizeof(*e), GFP_KERNEL);
 if (!ring->start)
  return -ENOMEM;

 for (i = 0, e = ring->start, d = vaddr; i < ring->count; i++, e++, d++) {
  e->desc = d;
  if (i == ring->count - 1) {
   e->next = ring->start;
   d->next_offset = base;
  } else {
   e->next = e + 1;
   d->next_offset = base + (i+1) * sizeof(*d);
  }
 }
 ring->to_use = ring->to_clean = ring->start;

 return 0;
}

/* Allocate and setup a new buffer for receiving */
static int skge_rx_setup(struct skge_port *skge, struct skge_element *e,
    struct sk_buff *skb, unsigned int bufsize)
{
 struct skge_rx_desc *rd = e->desc;
 dma_addr_t map;

 map = dma_map_single(&skge->hw->pdev->dev, skb->data, bufsize,
        DMA_FROM_DEVICE);

 if (dma_mapping_error(&skge->hw->pdev->dev, map))
  return -1;

 rd->dma_lo = lower_32_bits(map);
 rd->dma_hi = upper_32_bits(map);
 e->skb = skb;
 rd->csum1_start = ETH_HLEN;
 rd->csum2_start = ETH_HLEN;
 rd->csum1 = 0;
 rd->csum2 = 0;

 wmb();

 rd->control = BMU_OWN | BMU_STF | BMU_IRQ_EOF | BMU_TCP_CHECK | bufsize;
 dma_unmap_addr_set(e, mapaddr, map);
 dma_unmap_len_set(e, maplen, bufsize);
 return 0;
}

/* Resume receiving using existing skb,
 * Note: DMA address is not changed by chip.
 *   MTU not changed while receiver active.
 */

static inline void skge_rx_reuse(struct skge_element *e, unsigned int size)
{
 struct skge_rx_desc *rd = e->desc;

 rd->csum2 = 0;
 rd->csum2_start = ETH_HLEN;

 wmb();

 rd->control = BMU_OWN | BMU_STF | BMU_IRQ_EOF | BMU_TCP_CHECK | size;
}


/* Free all  buffers in receive ring, assumes receiver stopped */
static void skge_rx_clean(struct skge_port *skge)
{
 struct skge_hw *hw = skge->hw;
 struct skge_ring *ring = &skge->rx_ring;
 struct skge_element *e;

 e = ring->start;
 do {
  struct skge_rx_desc *rd = e->desc;
  rd->control = 0;
  if (e->skb) {
   dma_unmap_single(&hw->pdev->dev,
      dma_unmap_addr(e, mapaddr),
      dma_unmap_len(e, maplen),
      DMA_FROM_DEVICE);
   dev_kfree_skb(e->skb);
   e->skb = NULL;
  }
 } while ((e = e->next) != ring->start);
}


/* Allocate buffers for receive ring
 * For receive:  to_clean is next received frame.
 */

static int skge_rx_fill(struct net_device *dev)
{
 struct skge_port *skge = netdev_priv(dev);
 struct skge_ring *ring = &skge->rx_ring;
 struct skge_element *e;

 e = ring->start;
 do {
  struct sk_buff *skb;

  skb = __netdev_alloc_skb(dev, skge->rx_buf_size + NET_IP_ALIGN,
      GFP_KERNEL);
  if (!skb)
   return -ENOMEM;

  skb_reserve(skb, NET_IP_ALIGN);
  if (skge_rx_setup(skge, e, skb, skge->rx_buf_size) < 0) {
   dev_kfree_skb(skb);
   return -EIO;
  }
 } while ((e = e->next) != ring->start);

 ring->to_clean = ring->start;
 return 0;
}

static const char *skge_pause(enum pause_status status)
{
 switch (status) {
 case FLOW_STAT_NONE:
  return "none";
 case FLOW_STAT_REM_SEND:
  return "rx only";
 case FLOW_STAT_LOC_SEND:
  return "tx_only";
 case FLOW_STAT_SYMMETRIC:  /* Both station may send PAUSE */
  return "both";
 default:
  return "indeterminated";
 }
}


static void skge_link_up(struct skge_port *skge)
{
 skge_write8(skge->hw, SK_REG(skge->port, LNK_LED_REG),
      LED_BLK_OFF|LED_SYNC_OFF|LED_REG_ON);

 netif_carrier_on(skge->netdev);
 netif_wake_queue(skge->netdev);

 netif_info(skge, link, skge->netdev,
     "Link is up at %d Mbps, %s duplex, flow control %s\n",
     skge->speed,
     skge->duplex == DUPLEX_FULL ? "full" : "half",
     skge_pause(skge->flow_status));
}

static void skge_link_down(struct skge_port *skge)
{
 skge_write8(skge->hw, SK_REG(skge->port, LNK_LED_REG), LED_REG_OFF);
 netif_carrier_off(skge->netdev);
 netif_stop_queue(skge->netdev);

 netif_info(skge, link, skge->netdev, "Link is down\n");
}

static void xm_link_down(struct skge_hw *hw, int port)
{
 struct net_device *dev = hw->dev[port];
 struct skge_port *skge = netdev_priv(dev);

 xm_write16(hw, port, XM_IMSK, XM_IMSK_DISABLE);

 if (netif_carrier_ok(dev))
  skge_link_down(skge);
}

static int __xm_phy_read(struct skge_hw *hw, int port, u16 reg, u16 *val)
{
 int i;

 xm_write16(hw, port, XM_PHY_ADDR, reg | hw->phy_addr);
 *val = xm_read16(hw, port, XM_PHY_DATA);

 if (hw->phy_type == SK_PHY_XMAC)
  goto ready;

 for (i = 0; i < PHY_RETRIES; i++) {
  if (xm_read16(hw, port, XM_MMU_CMD) & XM_MMU_PHY_RDY)
   goto ready;
  udelay(1);
 }

 return -ETIMEDOUT;
 ready:
 *val = xm_read16(hw, port, XM_PHY_DATA);

 return 0;
}

static u16 xm_phy_read(struct skge_hw *hw, int port, u16 reg)
{
 u16 v = 0;
 if (__xm_phy_read(hw, port, reg, &v))
  pr_warn("%s: phy read timed out\n", hw->dev[port]->name);
 return v;
}

static int xm_phy_write(struct skge_hw *hw, int port, u16 reg, u16 val)
{
 int i;

 xm_write16(hw, port, XM_PHY_ADDR, reg | hw->phy_addr);
 for (i = 0; i < PHY_RETRIES; i++) {
  if (!(xm_read16(hw, port, XM_MMU_CMD) & XM_MMU_PHY_BUSY))
   goto ready;
  udelay(1);
 }
 return -EIO;

 ready:
 xm_write16(hw, port, XM_PHY_DATA, val);
 for (i = 0; i < PHY_RETRIES; i++) {
  if (!(xm_read16(hw, port, XM_MMU_CMD) & XM_MMU_PHY_BUSY))
   return 0;
  udelay(1);
 }
 return -ETIMEDOUT;
}

static void genesis_init(struct skge_hw *hw)
{
 /* set blink source counter */
 skge_write32(hw, B2_BSC_INI, (SK_BLK_DUR * SK_FACT_53) / 100);
 skge_write8(hw, B2_BSC_CTRL, BSC_START);

 /* configure mac arbiter */
 skge_write16(hw, B3_MA_TO_CTRL, MA_RST_CLR);

 /* configure mac arbiter timeout values */
 skge_write8(hw, B3_MA_TOINI_RX1, SK_MAC_TO_53);
 skge_write8(hw, B3_MA_TOINI_RX2, SK_MAC_TO_53);
 skge_write8(hw, B3_MA_TOINI_TX1, SK_MAC_TO_53);
 skge_write8(hw, B3_MA_TOINI_TX2, SK_MAC_TO_53);

 skge_write8(hw, B3_MA_RCINI_RX1, 0);
 skge_write8(hw, B3_MA_RCINI_RX2, 0);
 skge_write8(hw, B3_MA_RCINI_TX1, 0);
 skge_write8(hw, B3_MA_RCINI_TX2, 0);

 /* configure packet arbiter timeout */
 skge_write16(hw, B3_PA_CTRL, PA_RST_CLR);
 skge_write16(hw, B3_PA_TOINI_RX1, SK_PKT_TO_MAX);
 skge_write16(hw, B3_PA_TOINI_TX1, SK_PKT_TO_MAX);
 skge_write16(hw, B3_PA_TOINI_RX2, SK_PKT_TO_MAX);
 skge_write16(hw, B3_PA_TOINI_TX2, SK_PKT_TO_MAX);
}

static void genesis_reset(struct skge_hw *hw, int port)
{
 static const u8 zero[8]  = { 0 };
 u32 reg;

 skge_write8(hw, SK_REG(port, GMAC_IRQ_MSK), 0);

 /* reset the statistics module */
 xm_write32(hw, port, XM_GP_PORT, XM_GP_RES_STAT);
 xm_write16(hw, port, XM_IMSK, XM_IMSK_DISABLE);
 xm_write32(hw, port, XM_MODE, 0);  /* clear Mode Reg */
 xm_write16(hw, port, XM_TX_CMD, 0); /* reset TX CMD Reg */
 xm_write16(hw, port, XM_RX_CMD, 0); /* reset RX CMD Reg */

 /* disable Broadcom PHY IRQ */
 if (hw->phy_type == SK_PHY_BCOM)
  xm_write16(hw, port, PHY_BCOM_INT_MASK, 0xffff);

 xm_outhash(hw, port, XM_HSM, zero);

 /* Flush TX and RX fifo */
 reg = xm_read32(hw, port, XM_MODE);
 xm_write32(hw, port, XM_MODE, reg | XM_MD_FTF);
 xm_write32(hw, port, XM_MODE, reg | XM_MD_FRF);
}

/* Convert mode to MII values  */
static const u16 phy_pause_map[] = {
 [FLOW_MODE_NONE] = 0,
 [FLOW_MODE_LOC_SEND] = PHY_AN_PAUSE_ASYM,
 [FLOW_MODE_SYMMETRIC] = PHY_AN_PAUSE_CAP,
 [FLOW_MODE_SYM_OR_REM]  = PHY_AN_PAUSE_CAP | PHY_AN_PAUSE_ASYM,
};

/* special defines for FIBER (88E1011S only) */
static const u16 fiber_pause_map[] = {
 [FLOW_MODE_NONE] = PHY_X_P_NO_PAUSE,
 [FLOW_MODE_LOC_SEND] = PHY_X_P_ASYM_MD,
 [FLOW_MODE_SYMMETRIC] = PHY_X_P_SYM_MD,
 [FLOW_MODE_SYM_OR_REM] = PHY_X_P_BOTH_MD,
};


/* Check status of Broadcom phy link */
static void bcom_check_link(struct skge_hw *hw, int port)
{
 struct net_device *dev = hw->dev[port];
 struct skge_port *skge = netdev_priv(dev);
 u16 status;

 /* read twice because of latch */
 xm_phy_read(hw, port, PHY_BCOM_STAT);
 status = xm_phy_read(hw, port, PHY_BCOM_STAT);

 if ((status & PHY_ST_LSYNC) == 0) {
  xm_link_down(hw, port);
  return;
 }

 if (skge->autoneg == AUTONEG_ENABLE) {
  u16 lpa, aux;

  if (!(status & PHY_ST_AN_OVER))
   return;

  lpa = xm_phy_read(hw, port, PHY_XMAC_AUNE_LP);
  if (lpa & PHY_B_AN_RF) {
   netdev_notice(dev, "remote fault\n");
   return;
  }

  aux = xm_phy_read(hw, port, PHY_BCOM_AUX_STAT);

  /* Check Duplex mismatch */
  switch (aux & PHY_B_AS_AN_RES_MSK) {
  case PHY_B_RES_1000FD:
   skge->duplex = DUPLEX_FULL;
   break;
  case PHY_B_RES_1000HD:
   skge->duplex = DUPLEX_HALF;
   break;
  default:
   netdev_notice(dev, "duplex mismatch\n");
   return;
  }

  /* We are using IEEE 802.3z/D5.0 Table 37-4 */
  switch (aux & PHY_B_AS_PAUSE_MSK) {
  case PHY_B_AS_PAUSE_MSK:
   skge->flow_status = FLOW_STAT_SYMMETRIC;
   break;
  case PHY_B_AS_PRR:
   skge->flow_status = FLOW_STAT_REM_SEND;
   break;
  case PHY_B_AS_PRT:
   skge->flow_status = FLOW_STAT_LOC_SEND;
   break;
  default:
   skge->flow_status = FLOW_STAT_NONE;
  }
  skge->speed = SPEED_1000;
 }

 if (!netif_carrier_ok(dev))
  genesis_link_up(skge);
}

/* Broadcom 5400 only supports giagabit! SysKonnect did not put an additional
 * Phy on for 100 or 10Mbit operation
 */

static void bcom_phy_init(struct skge_port *skge)
{
 struct skge_hw *hw = skge->hw;
 int port = skge->port;
 int i;
 u16 id1, r, ext, ctl;

 /* magic workaround patterns for Broadcom */
 static const struct {
  u16 reg;
  u16 val;
 } A1hack[] = {
  { 0x18, 0x0c20 }, { 0x17, 0x0012 }, { 0x15, 0x1104 },
  { 0x17, 0x0013 }, { 0x15, 0x0404 }, { 0x17, 0x8006 },
  { 0x15, 0x0132 }, { 0x17, 0x8006 }, { 0x15, 0x0232 },
  { 0x17, 0x800D }, { 0x15, 0x000F }, { 0x18, 0x0420 },
 }, C0hack[] = {
  { 0x18, 0x0c20 }, { 0x17, 0x0012 }, { 0x15, 0x1204 },
  { 0x17, 0x0013 }, { 0x15, 0x0A04 }, { 0x18, 0x0420 },
 };

 /* read Id from external PHY (all have the same address) */
 id1 = xm_phy_read(hw, port, PHY_XMAC_ID1);

 /* Optimize MDIO transfer by suppressing preamble. */
 r = xm_read16(hw, port, XM_MMU_CMD);
 r |=  XM_MMU_NO_PRE;
 xm_write16(hw, port, XM_MMU_CMD, r);

 switch (id1) {
 case PHY_BCOM_ID1_C0:
  /*
 * Workaround BCOM Errata for the C0 type.
 * Write magic patterns to reserved registers.
 */

  for (i = 0; i < ARRAY_SIZE(C0hack); i++)
   xm_phy_write(hw, port,
         C0hack[i].reg, C0hack[i].val);

  break;
 case PHY_BCOM_ID1_A1:
  /*
 * Workaround BCOM Errata for the A1 type.
 * Write magic patterns to reserved registers.
 */

  for (i = 0; i < ARRAY_SIZE(A1hack); i++)
   xm_phy_write(hw, port,
         A1hack[i].reg, A1hack[i].val);
  break;
 }

 /*
 * Workaround BCOM Errata (#10523) for all BCom PHYs.
 * Disable Power Management after reset.
 */

 r = xm_phy_read(hw, port, PHY_BCOM_AUX_CTRL);
 r |= PHY_B_AC_DIS_PM;
 xm_phy_write(hw, port, PHY_BCOM_AUX_CTRL, r);

 /* Dummy read */
 xm_read16(hw, port, XM_ISRC);

 ext = PHY_B_PEC_EN_LTR; /* enable tx led */
 ctl = PHY_CT_SP1000; /* always 1000mbit */

 if (skge->autoneg == AUTONEG_ENABLE) {
  /*
 * Workaround BCOM Errata #1 for the C5 type.
 * 1000Base-T Link Acquisition Failure in Slave Mode
 * Set Repeater/DTE bit 10 of the 1000Base-T Control Register
 */

  u16 adv = PHY_B_1000C_RD;
  if (skge->advertising & ADVERTISED_1000baseT_Half)
   adv |= PHY_B_1000C_AHD;
  if (skge->advertising & ADVERTISED_1000baseT_Full)
   adv |= PHY_B_1000C_AFD;
  xm_phy_write(hw, port, PHY_BCOM_1000T_CTRL, adv);

  ctl |= PHY_CT_ANE | PHY_CT_RE_CFG;
 } else {
  if (skge->duplex == DUPLEX_FULL)
   ctl |= PHY_CT_DUP_MD;
  /* Force to slave */
  xm_phy_write(hw, port, PHY_BCOM_1000T_CTRL, PHY_B_1000C_MSE);
 }

 /* Set autonegotiation pause parameters */
 xm_phy_write(hw, port, PHY_BCOM_AUNE_ADV,
       phy_pause_map[skge->flow_control] | PHY_AN_CSMA);

 /* Handle Jumbo frames */
 if (hw->dev[port]->mtu > ETH_DATA_LEN) {
  xm_phy_write(hw, port, PHY_BCOM_AUX_CTRL,
        PHY_B_AC_TX_TST | PHY_B_AC_LONG_PACK);

  ext |= PHY_B_PEC_HIGH_LA;

 }

 xm_phy_write(hw, port, PHY_BCOM_P_EXT_CTRL, ext);
 xm_phy_write(hw, port, PHY_BCOM_CTRL, ctl);

 /* Use link status change interrupt */
 xm_phy_write(hw, port, PHY_BCOM_INT_MASK, PHY_B_DEF_MSK);
}

static void xm_phy_init(struct skge_port *skge)
{
 struct skge_hw *hw = skge->hw;
 int port = skge->port;
 u16 ctrl = 0;

 if (skge->autoneg == AUTONEG_ENABLE) {
  if (skge->advertising & ADVERTISED_1000baseT_Half)
   ctrl |= PHY_X_AN_HD;
  if (skge->advertising & ADVERTISED_1000baseT_Full)
   ctrl |= PHY_X_AN_FD;

  ctrl |= fiber_pause_map[skge->flow_control];

  xm_phy_write(hw, port, PHY_XMAC_AUNE_ADV, ctrl);

  /* Restart Auto-negotiation */
  ctrl = PHY_CT_ANE | PHY_CT_RE_CFG;
 } else {
  /* Set DuplexMode in Config register */
  if (skge->duplex == DUPLEX_FULL)
   ctrl |= PHY_CT_DUP_MD;
  /*
 * Do NOT enable Auto-negotiation here. This would hold
 * the link down because no IDLEs are transmitted
 */

 }

 xm_phy_write(hw, port, PHY_XMAC_CTRL, ctrl);

 /* Poll PHY for status changes */
 mod_timer(&skge->link_timer, jiffies + LINK_HZ);
}

static int xm_check_link(struct net_device *dev)
{
 struct skge_port *skge = netdev_priv(dev);
 struct skge_hw *hw = skge->hw;
 int port = skge->port;
 u16 status;

 /* read twice because of latch */
 xm_phy_read(hw, port, PHY_XMAC_STAT);
 status = xm_phy_read(hw, port, PHY_XMAC_STAT);

 if ((status & PHY_ST_LSYNC) == 0) {
  xm_link_down(hw, port);
  return 0;
 }

 if (skge->autoneg == AUTONEG_ENABLE) {
  u16 lpa, res;

  if (!(status & PHY_ST_AN_OVER))
   return 0;

  lpa = xm_phy_read(hw, port, PHY_XMAC_AUNE_LP);
  if (lpa & PHY_B_AN_RF) {
   netdev_notice(dev, "remote fault\n");
   return 0;
  }

  res = xm_phy_read(hw, port, PHY_XMAC_RES_ABI);

  /* Check Duplex mismatch */
  switch (res & (PHY_X_RS_HD | PHY_X_RS_FD)) {
  case PHY_X_RS_FD:
   skge->duplex = DUPLEX_FULL;
   break;
  case PHY_X_RS_HD:
   skge->duplex = DUPLEX_HALF;
   break;
  default:
   netdev_notice(dev, "duplex mismatch\n");
   return 0;
  }

  /* We are using IEEE 802.3z/D5.0 Table 37-4 */
  if ((skge->flow_control == FLOW_MODE_SYMMETRIC ||
       skge->flow_control == FLOW_MODE_SYM_OR_REM) &&
      (lpa & PHY_X_P_SYM_MD))
   skge->flow_status = FLOW_STAT_SYMMETRIC;
  else if (skge->flow_control == FLOW_MODE_SYM_OR_REM &&
    (lpa & PHY_X_RS_PAUSE) == PHY_X_P_ASYM_MD)
   /* Enable PAUSE receive, disable PAUSE transmit */
   skge->flow_status  = FLOW_STAT_REM_SEND;
  else if (skge->flow_control == FLOW_MODE_LOC_SEND &&
    (lpa & PHY_X_RS_PAUSE) == PHY_X_P_BOTH_MD)
   /* Disable PAUSE receive, enable PAUSE transmit */
   skge->flow_status = FLOW_STAT_LOC_SEND;
  else
   skge->flow_status = FLOW_STAT_NONE;

  skge->speed = SPEED_1000;
 }

 if (!netif_carrier_ok(dev))
  genesis_link_up(skge);
 return 1;
}

/* Poll to check for link coming up.
 *
 * Since internal PHY is wired to a level triggered pin, can't
 * get an interrupt when carrier is detected, need to poll for
 * link coming up.
 */

static void xm_link_timer(struct timer_list *t)
{
 struct skge_port *skge = timer_container_of(skge, t, link_timer);
 struct net_device *dev = skge->netdev;
 struct skge_hw *hw = skge->hw;
 int port = skge->port;
 int i;
 unsigned long flags;

 if (!netif_running(dev))
  return;

 spin_lock_irqsave(&hw->phy_lock, flags);

 /*
 * Verify that the link by checking GPIO register three times.
 * This pin has the signal from the link_sync pin connected to it.
 */

 for (i = 0; i < 3; i++) {
  if (xm_read16(hw, port, XM_GP_PORT) & XM_GP_INP_ASS)
   goto link_down;
 }

 /* Re-enable interrupt to detect link down */
 if (xm_check_link(dev)) {
  u16 msk = xm_read16(hw, port, XM_IMSK);
  msk &= ~XM_IS_INP_ASS;
  xm_write16(hw, port, XM_IMSK, msk);
  xm_read16(hw, port, XM_ISRC);
 } else {
link_down:
  mod_timer(&skge->link_timer,
     round_jiffies(jiffies + LINK_HZ));
 }
 spin_unlock_irqrestore(&hw->phy_lock, flags);
}

static void genesis_mac_init(struct skge_hw *hw, int port)
{
 struct net_device *dev = hw->dev[port];
 struct skge_port *skge = netdev_priv(dev);
 int jumbo = hw->dev[port]->mtu > ETH_DATA_LEN;
 int i;
 u32 r;
 static const u8 zero[6]  = { 0 };

 for (i = 0; i < 10; i++) {
  skge_write16(hw, SK_REG(port, TX_MFF_CTRL1),
        MFF_SET_MAC_RST);
  if (skge_read16(hw, SK_REG(port, TX_MFF_CTRL1)) & MFF_SET_MAC_RST)
   goto reset_ok;
  udelay(1);
 }

 netdev_warn(dev, "genesis reset failed\n");

 reset_ok:
 /* Unreset the XMAC. */
 skge_write16(hw, SK_REG(port, TX_MFF_CTRL1), MFF_CLR_MAC_RST);

 /*
 * Perform additional initialization for external PHYs,
 * namely for the 1000baseTX cards that use the XMAC's
 * GMII mode.
 */

 if (hw->phy_type != SK_PHY_XMAC) {
  /* Take external Phy out of reset */
  r = skge_read32(hw, B2_GP_IO);
  if (port == 0)
   r |= GP_DIR_0|GP_IO_0;
  else
   r |= GP_DIR_2|GP_IO_2;

  skge_write32(hw, B2_GP_IO, r);

  /* Enable GMII interface */
  xm_write16(hw, port, XM_HW_CFG, XM_HW_GMII_MD);
 }


 switch (hw->phy_type) {
 case SK_PHY_XMAC:
  xm_phy_init(skge);
  break;
 case SK_PHY_BCOM:
  bcom_phy_init(skge);
  bcom_check_link(hw, port);
 }

 /* Set Station Address */
 xm_outaddr(hw, port, XM_SA, dev->dev_addr);

 /* We don't use match addresses so clear */
 for (i = 1; i < 16; i++)
  xm_outaddr(hw, port, XM_EXM(i), zero);

 /* Clear MIB counters */
 xm_write16(hw, port, XM_STAT_CMD,
   XM_SC_CLR_RXC | XM_SC_CLR_TXC);
 /* Clear two times according to Errata #3 */
 xm_write16(hw, port, XM_STAT_CMD,
   XM_SC_CLR_RXC | XM_SC_CLR_TXC);

 /* configure Rx High Water Mark (XM_RX_HI_WM) */
 xm_write16(hw, port, XM_RX_HI_WM, 1450);

 /* We don't need the FCS appended to the packet. */
 r = XM_RX_LENERR_OK | XM_RX_STRIP_FCS;
 if (jumbo)
  r |= XM_RX_BIG_PK_OK;

 if (skge->duplex == DUPLEX_HALF) {
  /*
 * If in manual half duplex mode the other side might be in
 * full duplex mode, so ignore if a carrier extension is not seen
 * on frames received
 */

  r |= XM_RX_DIS_CEXT;
 }
 xm_write16(hw, port, XM_RX_CMD, r);

 /* We want short frames padded to 60 bytes. */
 xm_write16(hw, port, XM_TX_CMD, XM_TX_AUTO_PAD);

 /* Increase threshold for jumbo frames on dual port */
 if (hw->ports > 1 && jumbo)
  xm_write16(hw, port, XM_TX_THR, 1020);
 else
  xm_write16(hw, port, XM_TX_THR, 512);

 /*
 * Enable the reception of all error frames. This is
 * a necessary evil due to the design of the XMAC. The
 * XMAC's receive FIFO is only 8K in size, however jumbo
 * frames can be up to 9000 bytes in length. When bad
 * frame filtering is enabled, the XMAC's RX FIFO operates
 * in 'store and forward' mode. For this to work, the
 * entire frame has to fit into the FIFO, but that means
 * that jumbo frames larger than 8192 bytes will be
 * truncated. Disabling all bad frame filtering causes
 * the RX FIFO to operate in streaming mode, in which
 * case the XMAC will start transferring frames out of the
 * RX FIFO as soon as the FIFO threshold is reached.
 */

 xm_write32(hw, port, XM_MODE, XM_DEF_MODE);


 /*
 * Initialize the Receive Counter Event Mask (XM_RX_EV_MSK)
 * - Enable all bits excepting 'Octets Rx OK Low CntOv'
 *   and 'Octets Rx OK Hi Cnt Ov'.
 */

 xm_write32(hw, port, XM_RX_EV_MSK, XMR_DEF_MSK);

 /*
 * Initialize the Transmit Counter Event Mask (XM_TX_EV_MSK)
 * - Enable all bits excepting 'Octets Tx OK Low CntOv'
 *   and 'Octets Tx OK Hi Cnt Ov'.
 */

 xm_write32(hw, port, XM_TX_EV_MSK, XMT_DEF_MSK);

 /* Configure MAC arbiter */
 skge_write16(hw, B3_MA_TO_CTRL, MA_RST_CLR);

 /* configure timeout values */
 skge_write8(hw, B3_MA_TOINI_RX1, 72);
 skge_write8(hw, B3_MA_TOINI_RX2, 72);
 skge_write8(hw, B3_MA_TOINI_TX1, 72);
 skge_write8(hw, B3_MA_TOINI_TX2, 72);

 skge_write8(hw, B3_MA_RCINI_RX1, 0);
 skge_write8(hw, B3_MA_RCINI_RX2, 0);
 skge_write8(hw, B3_MA_RCINI_TX1, 0);
 skge_write8(hw, B3_MA_RCINI_TX2, 0);

 /* Configure Rx MAC FIFO */
 skge_write8(hw, SK_REG(port, RX_MFF_CTRL2), MFF_RST_CLR);
 skge_write16(hw, SK_REG(port, RX_MFF_CTRL1), MFF_ENA_TIM_PAT);
 skge_write8(hw, SK_REG(port, RX_MFF_CTRL2), MFF_ENA_OP_MD);

 /* Configure Tx MAC FIFO */
 skge_write8(hw, SK_REG(port, TX_MFF_CTRL2), MFF_RST_CLR);
 skge_write16(hw, SK_REG(port, TX_MFF_CTRL1), MFF_TX_CTRL_DEF);
 skge_write8(hw, SK_REG(port, TX_MFF_CTRL2), MFF_ENA_OP_MD);

 if (jumbo) {
  /* Enable frame flushing if jumbo frames used */
  skge_write16(hw, SK_REG(port, RX_MFF_CTRL1), MFF_ENA_FLUSH);
 } else {
  /* enable timeout timers if normal frames */
  skge_write16(hw, B3_PA_CTRL,
        (port == 0) ? PA_ENA_TO_TX1 : PA_ENA_TO_TX2);
 }
}

static void genesis_stop(struct skge_port *skge)
{
 struct skge_hw *hw = skge->hw;
 int port = skge->port;
 unsigned retries = 1000;
 u16 cmd;

 /* Disable Tx and Rx */
 cmd = xm_read16(hw, port, XM_MMU_CMD);
 cmd &= ~(XM_MMU_ENA_RX | XM_MMU_ENA_TX);
 xm_write16(hw, port, XM_MMU_CMD, cmd);

 genesis_reset(hw, port);

 /* Clear Tx packet arbiter timeout IRQ */
 skge_write16(hw, B3_PA_CTRL,
       port == 0 ? PA_CLR_TO_TX1 : PA_CLR_TO_TX2);

 /* Reset the MAC */
 skge_write16(hw, SK_REG(port, TX_MFF_CTRL1), MFF_CLR_MAC_RST);
 do {
  skge_write16(hw, SK_REG(port, TX_MFF_CTRL1), MFF_SET_MAC_RST);
  if (!(skge_read16(hw, SK_REG(port, TX_MFF_CTRL1)) & MFF_SET_MAC_RST))
   break;
 } while (--retries > 0);

 /* For external PHYs there must be special handling */
 if (hw->phy_type != SK_PHY_XMAC) {
  u32 reg = skge_read32(hw, B2_GP_IO);
  if (port == 0) {
   reg |= GP_DIR_0;
   reg &= ~GP_IO_0;
  } else {
   reg |= GP_DIR_2;
   reg &= ~GP_IO_2;
  }
  skge_write32(hw, B2_GP_IO, reg);
  skge_read32(hw, B2_GP_IO);
 }

 xm_write16(hw, port, XM_MMU_CMD,
   xm_read16(hw, port, XM_MMU_CMD)
   & ~(XM_MMU_ENA_RX | XM_MMU_ENA_TX));

 xm_read16(hw, port, XM_MMU_CMD);
}


static void genesis_get_stats(struct skge_port *skge, u64 *data)
{
 struct skge_hw *hw = skge->hw;
 int port = skge->port;
 int i;
 unsigned long timeout = jiffies + HZ;

 xm_write16(hw, port,
   XM_STAT_CMD, XM_SC_SNP_TXC | XM_SC_SNP_RXC);

 /* wait for update to complete */
 while (xm_read16(hw, port, XM_STAT_CMD)
        & (XM_SC_SNP_TXC | XM_SC_SNP_RXC)) {
  if (time_after(jiffies, timeout))
   break;
  udelay(10);
 }

 /* special case for 64 bit octet counter */
 data[0] = (u64) xm_read32(hw, port, XM_TXO_OK_HI) << 32
  | xm_read32(hw, port, XM_TXO_OK_LO);
 data[1] = (u64) xm_read32(hw, port, XM_RXO_OK_HI) << 32
  | xm_read32(hw, port, XM_RXO_OK_LO);

 for (i = 2; i < ARRAY_SIZE(skge_stats); i++)
  data[i] = xm_read32(hw, port, skge_stats[i].xmac_offset);
}

static void genesis_mac_intr(struct skge_hw *hw, int port)
{
 struct net_device *dev = hw->dev[port];
 struct skge_port *skge = netdev_priv(dev);
 u16 status = xm_read16(hw, port, XM_ISRC);

 netif_printk(skge, intr, KERN_DEBUG, skge->netdev,
       "mac interrupt status 0x%x\n", status);

 if (hw->phy_type == SK_PHY_XMAC && (status & XM_IS_INP_ASS)) {
  xm_link_down(hw, port);
  mod_timer(&skge->link_timer, jiffies + 1);
 }

 if (status & XM_IS_TXF_UR) {
  xm_write32(hw, port, XM_MODE, XM_MD_FTF);
  ++dev->stats.tx_fifo_errors;
 }
}

static void genesis_link_up(struct skge_port *skge)
{
 struct skge_hw *hw = skge->hw;
 int port = skge->port;
 u16 cmd, msk;
 u32 mode;

 cmd = xm_read16(hw, port, XM_MMU_CMD);

 /*
 * enabling pause frame reception is required for 1000BT
 * because the XMAC is not reset if the link is going down
 */

 if (skge->flow_status == FLOW_STAT_NONE ||
     skge->flow_status == FLOW_STAT_LOC_SEND)
  /* Disable Pause Frame Reception */
  cmd |= XM_MMU_IGN_PF;
 else
  /* Enable Pause Frame Reception */
  cmd &= ~XM_MMU_IGN_PF;

 xm_write16(hw, port, XM_MMU_CMD, cmd);

 mode = xm_read32(hw, port, XM_MODE);
 if (skge->flow_status == FLOW_STAT_SYMMETRIC ||
     skge->flow_status == FLOW_STAT_LOC_SEND) {
  /*
 * Configure Pause Frame Generation
 * Use internal and external Pause Frame Generation.
 * Sending pause frames is edge triggered.
 * Send a Pause frame with the maximum pause time if
 * internal oder external FIFO full condition occurs.
 * Send a zero pause time frame to re-start transmission.
 */

  /* XM_PAUSE_DA = '010000C28001' (default) */
  /* XM_MAC_PTIME = 0xffff (maximum) */
  /* remember this value is defined in big endian (!) */
  xm_write16(hw, port, XM_MAC_PTIME, 0xffff);

  mode |= XM_PAUSE_MODE;
  skge_write16(hw, SK_REG(port, RX_MFF_CTRL1), MFF_ENA_PAUSE);
 } else {
  /*
 * disable pause frame generation is required for 1000BT
 * because the XMAC is not reset if the link is going down
 */

  /* Disable Pause Mode in Mode Register */
  mode &= ~XM_PAUSE_MODE;

  skge_write16(hw, SK_REG(port, RX_MFF_CTRL1), MFF_DIS_PAUSE);
 }

 xm_write32(hw, port, XM_MODE, mode);

 /* Turn on detection of Tx underrun */
 msk = xm_read16(hw, port, XM_IMSK);
 msk &= ~XM_IS_TXF_UR;
 xm_write16(hw, port, XM_IMSK, msk);

 xm_read16(hw, port, XM_ISRC);

 /* get MMU Command Reg. */
 cmd = xm_read16(hw, port, XM_MMU_CMD);
 if (hw->phy_type != SK_PHY_XMAC && skge->duplex == DUPLEX_FULL)
  cmd |= XM_MMU_GMII_FD;

 /*
 * Workaround BCOM Errata (#10523) for all BCom Phys
 * Enable Power Management after link up
 */

 if (hw->phy_type == SK_PHY_BCOM) {
  xm_phy_write(hw, port, PHY_BCOM_AUX_CTRL,
        xm_phy_read(hw, port, PHY_BCOM_AUX_CTRL)
        & ~PHY_B_AC_DIS_PM);
  xm_phy_write(hw, port, PHY_BCOM_INT_MASK, PHY_B_DEF_MSK);
 }

 /* enable Rx/Tx */
 xm_write16(hw, port, XM_MMU_CMD,
   cmd | XM_MMU_ENA_RX | XM_MMU_ENA_TX);
 skge_link_up(skge);
}


static inline void bcom_phy_intr(struct skge_port *skge)
{
 struct skge_hw *hw = skge->hw;
 int port = skge->port;
 u16 isrc;

 isrc = xm_phy_read(hw, port, PHY_BCOM_INT_STAT);
 netif_printk(skge, intr, KERN_DEBUG, skge->netdev,
       "phy interrupt status 0x%x\n", isrc);

 if (isrc & PHY_B_IS_PSE)
  pr_err("%s: uncorrectable pair swap error\n",
         hw->dev[port]->name);

 /* Workaround BCom Errata:
 * enable and disable loopback mode if "NO HCD" occurs.
 */

 if (isrc & PHY_B_IS_NO_HDCL) {
  u16 ctrl = xm_phy_read(hw, port, PHY_BCOM_CTRL);
  xm_phy_write(hw, port, PHY_BCOM_CTRL,
      ctrl | PHY_CT_LOOP);
  xm_phy_write(hw, port, PHY_BCOM_CTRL,
      ctrl & ~PHY_CT_LOOP);
 }

 if (isrc & (PHY_B_IS_AN_PR | PHY_B_IS_LST_CHANGE))
  bcom_check_link(hw, port);

}

static int gm_phy_write(struct skge_hw *hw, int port, u16 reg, u16 val)
{
 int i;

 gma_write16(hw, port, GM_SMI_DATA, val);
 gma_write16(hw, port, GM_SMI_CTRL,
    GM_SMI_CT_PHY_AD(hw->phy_addr) | GM_SMI_CT_REG_AD(reg));
 for (i = 0; i < PHY_RETRIES; i++) {
  udelay(1);

  if (!(gma_read16(hw, port, GM_SMI_CTRL) & GM_SMI_CT_BUSY))
   return 0;
 }

 pr_warn("%s: phy write timeout\n", hw->dev[port]->name);
 return -EIO;
}

static int __gm_phy_read(struct skge_hw *hw, int port, u16 reg, u16 *val)
{
 int i;

 gma_write16(hw, port, GM_SMI_CTRL,
    GM_SMI_CT_PHY_AD(hw->phy_addr)
    | GM_SMI_CT_REG_AD(reg) | GM_SMI_CT_OP_RD);

 for (i = 0; i < PHY_RETRIES; i++) {
  udelay(1);
  if (gma_read16(hw, port, GM_SMI_CTRL) & GM_SMI_CT_RD_VAL)
   goto ready;
 }

 return -ETIMEDOUT;
 ready:
 *val = gma_read16(hw, port, GM_SMI_DATA);
 return 0;
}

static u16 gm_phy_read(struct skge_hw *hw, int port, u16 reg)
{
 u16 v = 0;
 if (__gm_phy_read(hw, port, reg, &v))
  pr_warn("%s: phy read timeout\n", hw->dev[port]->name);
 return v;
}

/* Marvell Phy Initialization */
static void yukon_init(struct skge_hw *hw, int port)
{
 struct skge_port *skge = netdev_priv(hw->dev[port]);
 u16 ctrl, ct1000, adv;

 if (skge->autoneg == AUTONEG_ENABLE) {
  u16 ectrl = gm_phy_read(hw, port, PHY_MARV_EXT_CTRL);

  ectrl &= ~(PHY_M_EC_M_DSC_MSK | PHY_M_EC_S_DSC_MSK |
     PHY_M_EC_MAC_S_MSK);
  ectrl |= PHY_M_EC_MAC_S(MAC_TX_CLK_25_MHZ);

  ectrl |= PHY_M_EC_M_DSC(0) | PHY_M_EC_S_DSC(1);

  gm_phy_write(hw, port, PHY_MARV_EXT_CTRL, ectrl);
 }

 ctrl = gm_phy_read(hw, port, PHY_MARV_CTRL);
 if (skge->autoneg == AUTONEG_DISABLE)
  ctrl &= ~PHY_CT_ANE;

 ctrl |= PHY_CT_RESET;
 gm_phy_write(hw, port, PHY_MARV_CTRL, ctrl);

 ctrl = 0;
 ct1000 = 0;
 adv = PHY_AN_CSMA;

 if (skge->autoneg == AUTONEG_ENABLE) {
  if (hw->copper) {
   if (skge->advertising & ADVERTISED_1000baseT_Full)
    ct1000 |= PHY_M_1000C_AFD;
   if (skge->advertising & ADVERTISED_1000baseT_Half)
    ct1000 |= PHY_M_1000C_AHD;
   if (skge->advertising & ADVERTISED_100baseT_Full)
    adv |= PHY_M_AN_100_FD;
   if (skge->advertising & ADVERTISED_100baseT_Half)
    adv |= PHY_M_AN_100_HD;
   if (skge->advertising & ADVERTISED_10baseT_Full)
    adv |= PHY_M_AN_10_FD;
   if (skge->advertising & ADVERTISED_10baseT_Half)
    adv |= PHY_M_AN_10_HD;

   /* Set Flow-control capabilities */
   adv |= phy_pause_map[skge->flow_control];
  } else {
   if (skge->advertising & ADVERTISED_1000baseT_Full)
    adv |= PHY_M_AN_1000X_AFD;
   if (skge->advertising & ADVERTISED_1000baseT_Half)
    adv |= PHY_M_AN_1000X_AHD;

   adv |= fiber_pause_map[skge->flow_control];
  }

  /* Restart Auto-negotiation */
  ctrl |= PHY_CT_ANE | PHY_CT_RE_CFG;
 } else {
  /* forced speed/duplex settings */
  ct1000 = PHY_M_1000C_MSE;

  if (skge->duplex == DUPLEX_FULL)
   ctrl |= PHY_CT_DUP_MD;

  switch (skge->speed) {
  case SPEED_1000:
   ctrl |= PHY_CT_SP1000;
   break;
  case SPEED_100:
   ctrl |= PHY_CT_SP100;
   break;
  }

  ctrl |= PHY_CT_RESET;
 }

 gm_phy_write(hw, port, PHY_MARV_1000T_CTRL, ct1000);

 gm_phy_write(hw, port, PHY_MARV_AUNE_ADV, adv);
 gm_phy_write(hw, port, PHY_MARV_CTRL, ctrl);

 /* Enable phy interrupt on autonegotiation complete (or link up) */
 if (skge->autoneg == AUTONEG_ENABLE)
  gm_phy_write(hw, port, PHY_MARV_INT_MASK, PHY_M_IS_AN_MSK);
 else
  gm_phy_write(hw, port, PHY_MARV_INT_MASK, PHY_M_IS_DEF_MSK);
}

static void yukon_reset(struct skge_hw *hw, int port)
{
 gm_phy_write(hw, port, PHY_MARV_INT_MASK, 0);/* disable PHY IRQs */
 gma_write16(hw, port, GM_MC_ADDR_H1, 0); /* clear MC hash */
 gma_write16(hw, port, GM_MC_ADDR_H2, 0);
 gma_write16(hw, port, GM_MC_ADDR_H3, 0);
 gma_write16(hw, port, GM_MC_ADDR_H4, 0);

 gma_write16(hw, port, GM_RX_CTRL,
    gma_read16(hw, port, GM_RX_CTRL)
    | GM_RXCR_UCF_ENA | GM_RXCR_MCF_ENA);
}

/* Apparently, early versions of Yukon-Lite had wrong chip_id? */
static int is_yukon_lite_a0(struct skge_hw *hw)
{
 u32 reg;
 int ret;

 if (hw->chip_id != CHIP_ID_YUKON)
  return 0;

 reg = skge_read32(hw, B2_FAR);
 skge_write8(hw, B2_FAR + 3, 0xff);
 ret = (skge_read8(hw, B2_FAR + 3) != 0);
 skge_write32(hw, B2_FAR, reg);
 return ret;
}

static void yukon_mac_init(struct skge_hw *hw, int port)
{
 struct skge_port *skge = netdev_priv(hw->dev[port]);
 int i;
 u32 reg;
 const u8 *addr = hw->dev[port]->dev_addr;

 /* WA code for COMA mode -- set PHY reset */
 if (hw->chip_id == CHIP_ID_YUKON_LITE &&
     hw->chip_rev >= CHIP_REV_YU_LITE_A3) {
  reg = skge_read32(hw, B2_GP_IO);
  reg |= GP_DIR_9 | GP_IO_9;
  skge_write32(hw, B2_GP_IO, reg);
 }

 /* hard reset */
 skge_write32(hw, SK_REG(port, GPHY_CTRL), GPC_RST_SET);
 skge_write32(hw, SK_REG(port, GMAC_CTRL), GMC_RST_SET);

 /* WA code for COMA mode -- clear PHY reset */
 if (hw->chip_id == CHIP_ID_YUKON_LITE &&
     hw->chip_rev >= CHIP_REV_YU_LITE_A3) {
  reg = skge_read32(hw, B2_GP_IO);
  reg |= GP_DIR_9;
  reg &= ~GP_IO_9;
  skge_write32(hw, B2_GP_IO, reg);
 }

 /* Set hardware config mode */
 reg = GPC_INT_POL_HI | GPC_DIS_FC | GPC_DIS_SLEEP |
  GPC_ENA_XC | GPC_ANEG_ADV_ALL_M | GPC_ENA_PAUSE;
 reg |= hw->copper ? GPC_HWCFG_GMII_COP : GPC_HWCFG_GMII_FIB;

 /* Clear GMC reset */
 skge_write32(hw, SK_REG(port, GPHY_CTRL), reg | GPC_RST_SET);
 skge_write32(hw, SK_REG(port, GPHY_CTRL), reg | GPC_RST_CLR);
 skge_write32(hw, SK_REG(port, GMAC_CTRL), GMC_PAUSE_ON | GMC_RST_CLR);

 if (skge->autoneg == AUTONEG_DISABLE) {
  reg = GM_GPCR_AU_ALL_DIS;
  gma_write16(hw, port, GM_GP_CTRL,
     gma_read16(hw, port, GM_GP_CTRL) | reg);

  switch (skge->speed) {
  case SPEED_1000:
   reg &= ~GM_GPCR_SPEED_100;
   reg |= GM_GPCR_SPEED_1000;
   break;
  case SPEED_100:
   reg &= ~GM_GPCR_SPEED_1000;
   reg |= GM_GPCR_SPEED_100;
   break;
  case SPEED_10:
   reg &= ~(GM_GPCR_SPEED_1000 | GM_GPCR_SPEED_100);
   break;
  }

  if (skge->duplex == DUPLEX_FULL)
   reg |= GM_GPCR_DUP_FULL;
 } else
  reg = GM_GPCR_SPEED_1000 | GM_GPCR_SPEED_100 | GM_GPCR_DUP_FULL;

 switch (skge->flow_control) {
 case FLOW_MODE_NONE:
  skge_write32(hw, SK_REG(port, GMAC_CTRL), GMC_PAUSE_OFF);
  reg |= GM_GPCR_FC_TX_DIS | GM_GPCR_FC_RX_DIS | GM_GPCR_AU_FCT_DIS;
  break;
 case FLOW_MODE_LOC_SEND:
  /* disable Rx flow-control */
  reg |= GM_GPCR_FC_RX_DIS | GM_GPCR_AU_FCT_DIS;
  break;
 case FLOW_MODE_SYMMETRIC:
 case FLOW_MODE_SYM_OR_REM:
  /* enable Tx & Rx flow-control */
  break;
 }

 gma_write16(hw, port, GM_GP_CTRL, reg);
 skge_read16(hw, SK_REG(port, GMAC_IRQ_SRC));

 yukon_init(hw, port);

 /* MIB clear */
 reg = gma_read16(hw, port, GM_PHY_ADDR);
 gma_write16(hw, port, GM_PHY_ADDR, reg | GM_PAR_MIB_CLR);

 for (i = 0; i < GM_MIB_CNT_SIZE; i++)
  gma_read16(hw, port, GM_MIB_CNT_BASE + 8*i);
 gma_write16(hw, port, GM_PHY_ADDR, reg);

 /* transmit control */
 gma_write16(hw, port, GM_TX_CTRL, TX_COL_THR(TX_COL_DEF));

 /* receive control reg: unicast + multicast + no FCS  */
 gma_write16(hw, port, GM_RX_CTRL,
    GM_RXCR_UCF_ENA | GM_RXCR_CRC_DIS | GM_RXCR_MCF_ENA);

 /* transmit flow control */
 gma_write16(hw, port, GM_TX_FLOW_CTRL, 0xffff);

 /* transmit parameter */
 gma_write16(hw, port, GM_TX_PARAM,
    TX_JAM_LEN_VAL(TX_JAM_LEN_DEF) |
    TX_JAM_IPG_VAL(TX_JAM_IPG_DEF) |
    TX_IPG_JAM_DATA(TX_IPG_JAM_DEF));

 /* configure the Serial Mode Register */
 reg = DATA_BLIND_VAL(DATA_BLIND_DEF)
  | GM_SMOD_VLAN_ENA
  | IPG_DATA_VAL(IPG_DATA_DEF);

 if (hw->dev[port]->mtu > ETH_DATA_LEN)
  reg |= GM_SMOD_JUMBO_ENA;

 gma_write16(hw, port, GM_SERIAL_MODE, reg);

 /* physical address: used for pause frames */
 gma_set_addr(hw, port, GM_SRC_ADDR_1L, addr);
 /* virtual address for data */
 gma_set_addr(hw, port, GM_SRC_ADDR_2L, addr);

 /* enable interrupt mask for counter overflows */
 gma_write16(hw, port, GM_TX_IRQ_MSK, 0);
 gma_write16(hw, port, GM_RX_IRQ_MSK, 0);
 gma_write16(hw, port, GM_TR_IRQ_MSK, 0);

 /* Initialize Mac Fifo */

 /* Configure Rx MAC FIFO */
 skge_write16(hw, SK_REG(port, RX_GMF_FL_MSK), RX_FF_FL_DEF_MSK);
 reg = GMF_OPER_ON | GMF_RX_F_FL_ON;

 /* disable Rx GMAC FIFO Flush for YUKON-Lite Rev. A0 only */
 if (is_yukon_lite_a0(hw))
  reg &= ~GMF_RX_F_FL_ON;

 skge_write8(hw, SK_REG(port, RX_GMF_CTRL_T), GMF_RST_CLR);
 skge_write16(hw, SK_REG(port, RX_GMF_CTRL_T), reg);
 /*
 * because Pause Packet Truncation in GMAC is not working
 * we have to increase the Flush Threshold to 64 bytes
 * in order to flush pause packets in Rx FIFO on Yukon-1
 */

 skge_write16(hw, SK_REG(port, RX_GMF_FL_THR), RX_GMF_FL_THR_DEF+1);

 /* Configure Tx MAC FIFO */
 skge_write8(hw, SK_REG(port, TX_GMF_CTRL_T), GMF_RST_CLR);
 skge_write16(hw, SK_REG(port, TX_GMF_CTRL_T), GMF_OPER_ON);
}

/* Go into power down mode */
static void yukon_suspend(struct skge_hw *hw, int port)
{
 u16 ctrl;

 ctrl = gm_phy_read(hw, port, PHY_MARV_PHY_CTRL);
 ctrl |= PHY_M_PC_POL_R_DIS;
 gm_phy_write(hw, port, PHY_MARV_PHY_CTRL, ctrl);

 ctrl = gm_phy_read(hw, port, PHY_MARV_CTRL);
 ctrl |= PHY_CT_RESET;
 gm_phy_write(hw, port, PHY_MARV_CTRL, ctrl);

 /* switch IEEE compatible power down mode on */
 ctrl = gm_phy_read(hw, port, PHY_MARV_CTRL);
 ctrl |= PHY_CT_PDOWN;
 gm_phy_write(hw, port, PHY_MARV_CTRL, ctrl);
}

static void yukon_stop(struct skge_port *skge)
{
 struct skge_hw *hw = skge->hw;
 int port = skge->port;

 skge_write8(hw, SK_REG(port, GMAC_IRQ_MSK), 0);
 yukon_reset(hw, port);

 gma_write16(hw, port, GM_GP_CTRL,
    gma_read16(hw, port, GM_GP_CTRL)
    & ~(GM_GPCR_TX_ENA|GM_GPCR_RX_ENA));
 gma_read16(hw, port, GM_GP_CTRL);

 yukon_suspend(hw, port);

 /* set GPHY Control reset */
 skge_write8(hw, SK_REG(port, GPHY_CTRL), GPC_RST_SET);
 skge_write8(hw, SK_REG(port, GMAC_CTRL), GMC_RST_SET);
}

static void yukon_get_stats(struct skge_port *skge, u64 *data)
{
 struct skge_hw *hw = skge->hw;
 int port = skge->port;
 int i;

 data[0] = (u64) gma_read32(hw, port, GM_TXO_OK_HI) << 32
  | gma_read32(hw, port, GM_TXO_OK_LO);
 data[1] = (u64) gma_read32(hw, port, GM_RXO_OK_HI) << 32
  | gma_read32(hw, port, GM_RXO_OK_LO);

 for (i = 2; i < ARRAY_SIZE(skge_stats); i++)
  data[i] = gma_read32(hw, port,
       skge_stats[i].gma_offset);
}

static void yukon_mac_intr(struct skge_hw *hw, int port)
{
 struct net_device *dev = hw->dev[port];
 struct skge_port *skge = netdev_priv(dev);
 u8 status = skge_read8(hw, SK_REG(port, GMAC_IRQ_SRC));

 netif_printk(skge, intr, KERN_DEBUG, skge->netdev,
       "mac interrupt status 0x%x\n", status);

 if (status & GM_IS_RX_FF_OR) {
  ++dev->stats.rx_fifo_errors;
  skge_write8(hw, SK_REG(port, RX_GMF_CTRL_T), GMF_CLI_RX_FO);
 }

 if (status & GM_IS_TX_FF_UR) {
  ++dev->stats.tx_fifo_errors;
  skge_write8(hw, SK_REG(port, TX_GMF_CTRL_T), GMF_CLI_TX_FU);
 }

}

static u16 yukon_speed(const struct skge_hw *hw, u16 aux)
{
 switch (aux & PHY_M_PS_SPEED_MSK) {
 case PHY_M_PS_SPEED_1000:
  return SPEED_1000;
 case PHY_M_PS_SPEED_100:
  return SPEED_100;
 default:
  return SPEED_10;
 }
}

static void yukon_link_up(struct skge_port *skge)
{
 struct skge_hw *hw = skge->hw;
 int port = skge->port;
 u16 reg;

 /* Enable Transmit FIFO Underrun */
 skge_write8(hw, SK_REG(port, GMAC_IRQ_MSK), GMAC_DEF_MSK);

 reg = gma_read16(hw, port, GM_GP_CTRL);
 if (skge->duplex == DUPLEX_FULL || skge->autoneg == AUTONEG_ENABLE)
  reg |= GM_GPCR_DUP_FULL;

 /* enable Rx/Tx */
 reg |= GM_GPCR_RX_ENA | GM_GPCR_TX_ENA;
 gma_write16(hw, port, GM_GP_CTRL, reg);

 gm_phy_write(hw, port, PHY_MARV_INT_MASK, PHY_M_IS_DEF_MSK);
 skge_link_up(skge);
}

static void yukon_link_down(struct skge_port *skge)
{
 struct skge_hw *hw = skge->hw;
 int port = skge->port;
 u16 ctrl;

 ctrl = gma_read16(hw, port, GM_GP_CTRL);
 ctrl &= ~(GM_GPCR_RX_ENA | GM_GPCR_TX_ENA);
 gma_write16(hw, port, GM_GP_CTRL, ctrl);

 if (skge->flow_status == FLOW_STAT_REM_SEND) {
  ctrl = gm_phy_read(hw, port, PHY_MARV_AUNE_ADV);
  ctrl |= PHY_M_AN_ASP;
  /* restore Asymmetric Pause bit */
  gm_phy_write(hw, port, PHY_MARV_AUNE_ADV, ctrl);
 }

 skge_link_down(skge);

 yukon_init(hw, port);
}

static void yukon_phy_intr(struct skge_port *skge)
{
 struct skge_hw *hw = skge->hw;
 int port = skge->port;
 const char *reason = NULL;
 u16 istatus, phystat;

 istatus = gm_phy_read(hw, port, PHY_MARV_INT_STAT);
 phystat = gm_phy_read(hw, port, PHY_MARV_PHY_STAT);

 netif_printk(skge, intr, KERN_DEBUG, skge->netdev,
       "phy interrupt status 0x%x 0x%x\n", istatus, phystat);

 if (istatus & PHY_M_IS_AN_COMPL) {
  if (gm_phy_read(hw, port, PHY_MARV_AUNE_LP)
      & PHY_M_AN_RF) {
   reason = "remote fault";
   goto failed;
  }

  if (gm_phy_read(hw, port, PHY_MARV_1000T_STAT) & PHY_B_1000S_MSF) {
   reason = "master/slave fault";
   goto failed;
  }

  if (!(phystat & PHY_M_PS_SPDUP_RES)) {
   reason = "speed/duplex";
   goto failed;
  }

  skge->duplex = (phystat & PHY_M_PS_FULL_DUP)
   ? DUPLEX_FULL : DUPLEX_HALF;
  skge->speed = yukon_speed(hw, phystat);

  /* We are using IEEE 802.3z/D5.0 Table 37-4 */
  switch (phystat & PHY_M_PS_PAUSE_MSK) {
  case PHY_M_PS_PAUSE_MSK:
   skge->flow_status = FLOW_STAT_SYMMETRIC;
   break;
  case PHY_M_PS_RX_P_EN:
   skge->flow_status = FLOW_STAT_REM_SEND;
   break;
  case PHY_M_PS_TX_P_EN:
   skge->flow_status = FLOW_STAT_LOC_SEND;
   break;
  default:
   skge->flow_status = FLOW_STAT_NONE;
  }

  if (skge->flow_status == FLOW_STAT_NONE ||
      (skge->speed < SPEED_1000 && skge->duplex == DUPLEX_HALF))
   skge_write8(hw, SK_REG(port, GMAC_CTRL), GMC_PAUSE_OFF);
  else
   skge_write8(hw, SK_REG(port, GMAC_CTRL), GMC_PAUSE_ON);
  yukon_link_up(skge);
  return;
 }

 if (istatus & PHY_M_IS_LSP_CHANGE)
  skge->speed = yukon_speed(hw, phystat);

 if (istatus & PHY_M_IS_DUP_CHANGE)
  skge->duplex = (phystat & PHY_M_PS_FULL_DUP) ? DUPLEX_FULL : DUPLEX_HALF;
 if (istatus & PHY_M_IS_LST_CHANGE) {
  if (phystat & PHY_M_PS_LINK_UP)
   yukon_link_up(skge);
  else
   yukon_link_down(skge);
 }
 return;
 failed:
 pr_err("%s: autonegotiation failed (%s)\n", skge->netdev->name, reason);

 /* XXX restart autonegotiation? */
}

static void skge_phy_reset(struct skge_port *skge)
{
 struct skge_hw *hw = skge->hw;
 int port = skge->port;
 struct net_device *dev = hw->dev[port];

 netif_stop_queue(skge->netdev);
 netif_carrier_off(skge->netdev);

 spin_lock_bh(&hw->phy_lock);
 if (is_genesis(hw)) {
  genesis_reset(hw, port);
  genesis_mac_init(hw, port);
 } else {
  yukon_reset(hw, port);
  yukon_init(hw, port);
 }
 spin_unlock_bh(&hw->phy_lock);

 skge_set_multicast(dev);
}

--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=95 H=91 G=92

¤ Dauer der Verarbeitung: 0.19 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.