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 134 kB image not shown  

Quelle  sky2.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-only
/*
 * New driver for Marvell Yukon 2 chipset.
 * Based on earlier sk98lin, and skge driver.
 *
 * 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) 2005 Stephen Hemminger <shemminger@osdl.org>
 */


#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/crc32.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/dma-mapping.h>
#include <linux/etherdevice.h>
#include <linux/ethtool.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/ip.h>
#include <linux/slab.h>
#include <net/ip.h>
#include <linux/tcp.h>
#include <linux/in.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/if_vlan.h>
#include <linux/prefetch.h>
#include <linux/debugfs.h>
#include <linux/mii.h>
#include <linux/of_net.h>
#include <linux/dmi.h>
#include <linux/skbuff_ref.h>

#include <asm/irq.h>

#include "sky2.h"

#define DRV_NAME  "sky2"
#define DRV_VERSION  "1.30"

/*
 * The Yukon II chipset takes 64 bit command blocks (called list elements)
 * that are organized into three (receive, transmit, status) different rings
 * similar to Tigon3.
 */


#define RX_LE_SIZE      1024
#define RX_LE_BYTES  (RX_LE_SIZE*sizeof(struct sky2_rx_le))
#define RX_MAX_PENDING  (RX_LE_SIZE/6 - 2)
#define RX_DEF_PENDING  RX_MAX_PENDING

/* This is the worst case number of transmit list elements for a single skb:
 * VLAN:GSO + CKSUM + Data + skb_frags * DMA
 */

#define MAX_SKB_TX_LE (2 + (sizeof(dma_addr_t)/sizeof(u32))*(MAX_SKB_FRAGS+1))
#define TX_MIN_PENDING  (MAX_SKB_TX_LE+1)
#define TX_MAX_PENDING  1024
#define TX_DEF_PENDING  63

#define TX_WATCHDOG  (5 * HZ)
#define PHY_RETRIES  1000

#define SKY2_EEPROM_MAGIC 0x9955aabb

#define RING_NEXT(x, s) (((x)+1) & ((s)-1))

static const u32 default_msg =
    NETIF_MSG_DRV | NETIF_MSG_PROBE | NETIF_MSG_LINK
    | NETIF_MSG_TIMER | NETIF_MSG_TX_ERR | NETIF_MSG_RX_ERR
    | 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 int copybreak __read_mostly = 128;
module_param(copybreak, int, 0);
MODULE_PARM_DESC(copybreak, "Receive copy threshold");

static int disable_msi = -1;
module_param(disable_msi, int, 0);
MODULE_PARM_DESC(disable_msi, "Disable Message Signaled Interrupt (MSI)");

static int legacy_pme = 0;
module_param(legacy_pme, int, 0);
MODULE_PARM_DESC(legacy_pme, "Legacy power management");

static const struct pci_device_id sky2_id_table[] = {
 { PCI_DEVICE(PCI_VENDOR_ID_SYSKONNECT, 0x9000) }, /* SK-9Sxx */
 { PCI_DEVICE(PCI_VENDOR_ID_SYSKONNECT, 0x9E00) }, /* SK-9Exx */
 { PCI_DEVICE(PCI_VENDOR_ID_SYSKONNECT, 0x9E01) }, /* SK-9E21M */
 { PCI_DEVICE(PCI_VENDOR_ID_DLINK, 0x4b00) }, /* DGE-560T */
 { PCI_DEVICE(PCI_VENDOR_ID_DLINK, 0x4001) },  /* DGE-550SX */
 { PCI_DEVICE(PCI_VENDOR_ID_DLINK, 0x4B02) }, /* DGE-560SX */
 { PCI_DEVICE(PCI_VENDOR_ID_DLINK, 0x4B03) }, /* DGE-550T */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4340) }, /* 88E8021 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4341) }, /* 88E8022 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4342) }, /* 88E8061 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4343) }, /* 88E8062 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4344) }, /* 88E8021 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4345) }, /* 88E8022 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4346) }, /* 88E8061 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4347) }, /* 88E8062 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4350) }, /* 88E8035 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4351) }, /* 88E8036 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4352) }, /* 88E8038 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4353) }, /* 88E8039 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4354) }, /* 88E8040 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4355) }, /* 88E8040T */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4356) }, /* 88EC033 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4357) }, /* 88E8042 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x435A) }, /* 88E8048 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4360) }, /* 88E8052 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4361) }, /* 88E8050 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4362) }, /* 88E8053 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4363) }, /* 88E8055 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4364) }, /* 88E8056 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4365) }, /* 88E8070 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4366) }, /* 88EC036 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4367) }, /* 88EC032 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4368) }, /* 88EC034 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4369) }, /* 88EC042 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x436A) }, /* 88E8058 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x436B) }, /* 88E8071 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x436C) }, /* 88E8072 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x436D) }, /* 88E8055 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4370) }, /* 88E8075 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4373) }, /* 88E8075 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4380) }, /* 88E8057 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4381) }, /* 88E8059 */
 { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4382) }, /* 88E8079 */
 { 0 }
};

MODULE_DEVICE_TABLE(pci, sky2_id_table);

/* Avoid conditionals by using array */
static const unsigned txqaddr[] = { Q_XA1, Q_XA2 };
static const unsigned rxqaddr[] = { Q_R1, Q_R2 };
static const u32 portirq_msk[] = { Y2_IS_PORT_1, Y2_IS_PORT_2 };

static void sky2_set_multicast(struct net_device *dev);
static irqreturn_t sky2_intr(int irq, void *dev_id);

/* Access to PHY via serial interconnect */
static int gm_phy_write(struct sky2_hw *hw, unsigned 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(PHY_ADDR_MARV) | GM_SMI_CT_REG_AD(reg));

 for (i = 0; i < PHY_RETRIES; i++) {
  u16 ctrl = gma_read16(hw, port, GM_SMI_CTRL);
  if (ctrl == 0xffff)
   goto io_error;

  if (!(ctrl & GM_SMI_CT_BUSY))
   return 0;

  udelay(10);
 }

 dev_warn(&hw->pdev->dev, "%s: phy write timeout\n", hw->dev[port]->name);
 return -ETIMEDOUT;

io_error:
 dev_err(&hw->pdev->dev, "%s: phy I/O error\n", hw->dev[port]->name);
 return -EIO;
}

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

 gma_write16(hw, port, GM_SMI_CTRL, GM_SMI_CT_PHY_AD(PHY_ADDR_MARV)
      | GM_SMI_CT_REG_AD(reg) | GM_SMI_CT_OP_RD);

 for (i = 0; i < PHY_RETRIES; i++) {
  u16 ctrl = gma_read16(hw, port, GM_SMI_CTRL);
  if (ctrl == 0xffff)
   goto io_error;

  if (ctrl & GM_SMI_CT_RD_VAL) {
   *val = gma_read16(hw, port, GM_SMI_DATA);
   return 0;
  }

  udelay(10);
 }

 dev_warn(&hw->pdev->dev, "%s: phy read timeout\n", hw->dev[port]->name);
 return -ETIMEDOUT;
io_error:
 dev_err(&hw->pdev->dev, "%s: phy I/O error\n", hw->dev[port]->name);
 return -EIO;
}

static inline u16 gm_phy_read(struct sky2_hw *hw, unsigned port, u16 reg)
{
 u16 v = 0;
 __gm_phy_read(hw, port, reg, &v);
 return v;
}


static void sky2_power_on(struct sky2_hw *hw)
{
 /* switch power to VCC (WA for VAUX problem) */
 sky2_write8(hw, B0_POWER_CTRL,
      PC_VAUX_ENA | PC_VCC_ENA | PC_VAUX_OFF | PC_VCC_ON);

 /* disable Core Clock Division, */
 sky2_write32(hw, B2_Y2_CLK_CTRL, Y2_CLK_DIV_DIS);

 if (hw->chip_id == CHIP_ID_YUKON_XL && hw->chip_rev > CHIP_REV_YU_XL_A1)
  /* enable bits are inverted */
  sky2_write8(hw, B2_Y2_CLK_GATE,
       Y2_PCI_CLK_LNK1_DIS | Y2_COR_CLK_LNK1_DIS |
       Y2_CLK_GAT_LNK1_DIS | Y2_PCI_CLK_LNK2_DIS |
       Y2_COR_CLK_LNK2_DIS | Y2_CLK_GAT_LNK2_DIS);
 else
  sky2_write8(hw, B2_Y2_CLK_GATE, 0);

 if (hw->flags & SKY2_HW_ADV_POWER_CTL) {
  u32 reg;

  sky2_pci_write32(hw, PCI_DEV_REG3, 0);

  reg = sky2_pci_read32(hw, PCI_DEV_REG4);
  /* set all bits to 0 except bits 15..12 and 8 */
  reg &= P_ASPM_CONTROL_MSK;
  sky2_pci_write32(hw, PCI_DEV_REG4, reg);

  reg = sky2_pci_read32(hw, PCI_DEV_REG5);
  /* set all bits to 0 except bits 28 & 27 */
  reg &= P_CTL_TIM_VMAIN_AV_MSK;
  sky2_pci_write32(hw, PCI_DEV_REG5, reg);

  sky2_pci_write32(hw, PCI_CFG_REG_1, 0);

  sky2_write16(hw, B0_CTST, Y2_HW_WOL_ON);

  /* Enable workaround for dev 4.107 on Yukon-Ultra & Extreme */
  reg = sky2_read32(hw, B2_GP_IO);
  reg |= GLB_GPIO_STAT_RACE_DIS;
  sky2_write32(hw, B2_GP_IO, reg);

  sky2_read32(hw, B2_GP_IO);
 }

 /* Turn on "driver loaded" LED */
 sky2_write16(hw, B0_CTST, Y2_LED_STAT_ON);
}

static void sky2_power_aux(struct sky2_hw *hw)
{
 if (hw->chip_id == CHIP_ID_YUKON_XL && hw->chip_rev > CHIP_REV_YU_XL_A1)
  sky2_write8(hw, B2_Y2_CLK_GATE, 0);
 else
  /* enable bits are inverted */
  sky2_write8(hw, B2_Y2_CLK_GATE,
       Y2_PCI_CLK_LNK1_DIS | Y2_COR_CLK_LNK1_DIS |
       Y2_CLK_GAT_LNK1_DIS | Y2_PCI_CLK_LNK2_DIS |
       Y2_COR_CLK_LNK2_DIS | Y2_CLK_GAT_LNK2_DIS);

 /* switch power to VAUX if supported and PME from D3cold */
 if ( (sky2_read32(hw, B0_CTST) & Y2_VAUX_AVAIL) &&
      pci_pme_capable(hw->pdev, PCI_D3cold))
  sky2_write8(hw, B0_POWER_CTRL,
       (PC_VAUX_ENA | PC_VCC_ENA |
        PC_VAUX_ON | PC_VCC_OFF));

 /* turn off "driver loaded LED" */
 sky2_write16(hw, B0_CTST, Y2_LED_STAT_OFF);
}

static void sky2_gmac_reset(struct sky2_hw *hw, unsigned port)
{
 u16 reg;

 /* disable all GMAC IRQ's */
 sky2_write8(hw, SK_REG(port, GMAC_IRQ_MSK), 0);

 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);

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

/* flow control to advertise bits */
static const u16 copper_fc_adv[] = {
 [FC_NONE] = 0,
 [FC_TX]  = PHY_M_AN_ASP,
 [FC_RX]  = PHY_M_AN_PC,
 [FC_BOTH] = PHY_M_AN_PC | PHY_M_AN_ASP,
};

/* flow control to advertise bits when using 1000BaseX */
static const u16 fiber_fc_adv[] = {
 [FC_NONE] = PHY_M_P_NO_PAUSE_X,
 [FC_TX]   = PHY_M_P_ASYM_MD_X,
 [FC_RX]   = PHY_M_P_SYM_MD_X,
 [FC_BOTH] = PHY_M_P_BOTH_MD_X,
};

/* flow control to GMA disable bits */
static const u16 gm_fc_disable[] = {
 [FC_NONE] = GM_GPCR_FC_RX_DIS | GM_GPCR_FC_TX_DIS,
 [FC_TX]   = GM_GPCR_FC_RX_DIS,
 [FC_RX]   = GM_GPCR_FC_TX_DIS,
 [FC_BOTH] = 0,
};


static void sky2_phy_init(struct sky2_hw *hw, unsigned port)
{
 struct sky2_port *sky2 = netdev_priv(hw->dev[port]);
 u16 ctrl, ct1000, adv, pg, ledctrl, ledover, reg;

 if ( (sky2->flags & SKY2_FLAG_AUTO_SPEED) &&
     !(hw->flags & SKY2_HW_NEWER_PHY)) {
  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);

  /* on PHY 88E1040 Rev.D0 (and newer) downshift control changed */
  if (hw->chip_id == CHIP_ID_YUKON_EC)
   /* set downshift counter to 3x and enable downshift */
   ectrl |= PHY_M_EC_DSC_2(2) | PHY_M_EC_DOWN_S_ENA;
  else
   /* set master & slave downshift counter to 1x */
   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_PHY_CTRL);
 if (sky2_is_copper(hw)) {
  if (!(hw->flags & SKY2_HW_GIGABIT)) {
   /* enable automatic crossover */
   ctrl |= PHY_M_PC_MDI_XMODE(PHY_M_PC_ENA_AUTO) >> 1;

   if (hw->chip_id == CHIP_ID_YUKON_FE_P &&
       hw->chip_rev == CHIP_REV_YU_FE2_A0) {
    u16 spec;

    /* Enable Class A driver for FE+ A0 */
    spec = gm_phy_read(hw, port, PHY_MARV_FE_SPEC_2);
    spec |= PHY_M_FESC_SEL_CL_A;
    gm_phy_write(hw, port, PHY_MARV_FE_SPEC_2, spec);
   }
  } else {
   /* disable energy detect */
   ctrl &= ~PHY_M_PC_EN_DET_MSK;

   /* enable automatic crossover */
   ctrl |= PHY_M_PC_MDI_XMODE(PHY_M_PC_ENA_AUTO);

   /* downshift on PHY 88E1112 and 88E1149 is changed */
   if ( (sky2->flags & SKY2_FLAG_AUTO_SPEED) &&
        (hw->flags & SKY2_HW_NEWER_PHY)) {
    /* set downshift counter to 3x and enable downshift */
    ctrl &= ~PHY_M_PC_DSC_MSK;
    ctrl |= PHY_M_PC_DSC(2) | PHY_M_PC_DOWN_S_ENA;
   }
  }
 } else {
  /* workaround for deviation #4.88 (CRC errors) */
  /* disable Automatic Crossover */

  ctrl &= ~PHY_M_PC_MDIX_MSK;
 }

 gm_phy_write(hw, port, PHY_MARV_PHY_CTRL, ctrl);

 /* special setup for PHY 88E1112 Fiber */
 if (hw->chip_id == CHIP_ID_YUKON_XL && (hw->flags & SKY2_HW_FIBRE_PHY)) {
  pg = gm_phy_read(hw, port, PHY_MARV_EXT_ADR);

  /* Fiber: select 1000BASE-X only mode MAC Specific Ctrl Reg. */
  gm_phy_write(hw, port, PHY_MARV_EXT_ADR, 2);
  ctrl = gm_phy_read(hw, port, PHY_MARV_PHY_CTRL);
  ctrl &= ~PHY_M_MAC_MD_MSK;
  ctrl |= PHY_M_MAC_MODE_SEL(PHY_M_MAC_MD_1000BX);
  gm_phy_write(hw, port, PHY_MARV_PHY_CTRL, ctrl);

  if (hw->pmd_type  == 'P') {
   /* select page 1 to access Fiber registers */
   gm_phy_write(hw, port, PHY_MARV_EXT_ADR, 1);

   /* for SFP-module set SIGDET polarity to low */
   ctrl = gm_phy_read(hw, port, PHY_MARV_PHY_CTRL);
   ctrl |= PHY_M_FIB_SIGD_POL;
   gm_phy_write(hw, port, PHY_MARV_PHY_CTRL, ctrl);
  }

  gm_phy_write(hw, port, PHY_MARV_EXT_ADR, pg);
 }

 ctrl = PHY_CT_RESET;
 ct1000 = 0;
 adv = PHY_AN_CSMA;
 reg = 0;

 if (sky2->flags & SKY2_FLAG_AUTO_SPEED) {
  if (sky2_is_copper(hw)) {
   if (sky2->advertising & ADVERTISED_1000baseT_Full)
    ct1000 |= PHY_M_1000C_AFD;
   if (sky2->advertising & ADVERTISED_1000baseT_Half)
    ct1000 |= PHY_M_1000C_AHD;
   if (sky2->advertising & ADVERTISED_100baseT_Full)
    adv |= PHY_M_AN_100_FD;
   if (sky2->advertising & ADVERTISED_100baseT_Half)
    adv |= PHY_M_AN_100_HD;
   if (sky2->advertising & ADVERTISED_10baseT_Full)
    adv |= PHY_M_AN_10_FD;
   if (sky2->advertising & ADVERTISED_10baseT_Half)
    adv |= PHY_M_AN_10_HD;

  } else { /* special defines for FIBER (88E1040S only) */
   if (sky2->advertising & ADVERTISED_1000baseT_Full)
    adv |= PHY_M_AN_1000X_AFD;
   if (sky2->advertising & ADVERTISED_1000baseT_Half)
    adv |= PHY_M_AN_1000X_AHD;
  }

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

  /* Disable auto update for duplex flow control and duplex */
  reg |= GM_GPCR_AU_DUP_DIS | GM_GPCR_AU_SPD_DIS;

  switch (sky2->speed) {
  case SPEED_1000:
   ctrl |= PHY_CT_SP1000;
   reg |= GM_GPCR_SPEED_1000;
   break;
  case SPEED_100:
   ctrl |= PHY_CT_SP100;
   reg |= GM_GPCR_SPEED_100;
   break;
  }

  if (sky2->duplex == DUPLEX_FULL) {
   reg |= GM_GPCR_DUP_FULL;
   ctrl |= PHY_CT_DUP_MD;
  } else if (sky2->speed < SPEED_1000)
   sky2->flow_mode = FC_NONE;
 }

 if (sky2->flags & SKY2_FLAG_AUTO_PAUSE) {
  if (sky2_is_copper(hw))
   adv |= copper_fc_adv[sky2->flow_mode];
  else
   adv |= fiber_fc_adv[sky2->flow_mode];
 } else {
  reg |= GM_GPCR_AU_FCT_DIS;
  reg |= gm_fc_disable[sky2->flow_mode];

  /* Forward pause packets to GMAC? */
  if (sky2->flow_mode & FC_RX)
   sky2_write8(hw, SK_REG(port, GMAC_CTRL), GMC_PAUSE_ON);
  else
   sky2_write8(hw, SK_REG(port, GMAC_CTRL), GMC_PAUSE_OFF);
 }

 gma_write16(hw, port, GM_GP_CTRL, reg);

 if (hw->flags & SKY2_HW_GIGABIT)
  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);

 /* Setup Phy LED's */
 ledctrl = PHY_M_LED_PULS_DUR(PULS_170MS);
 ledover = 0;

 switch (hw->chip_id) {
 case CHIP_ID_YUKON_FE:
  /* on 88E3082 these bits are at 11..9 (shifted left) */
  ledctrl |= PHY_M_LED_BLINK_RT(BLINK_84MS) << 1;

  ctrl = gm_phy_read(hw, port, PHY_MARV_FE_LED_PAR);

  /* delete ACT LED control bits */
  ctrl &= ~PHY_M_FELP_LED1_MSK;
  /* change ACT LED control to blink mode */
  ctrl |= PHY_M_FELP_LED1_CTRL(LED_PAR_CTRL_ACT_BL);
  gm_phy_write(hw, port, PHY_MARV_FE_LED_PAR, ctrl);
  break;

 case CHIP_ID_YUKON_FE_P:
  /* Enable Link Partner Next Page */
  ctrl = gm_phy_read(hw, port, PHY_MARV_PHY_CTRL);
  ctrl |= PHY_M_PC_ENA_LIP_NP;

  /* disable Energy Detect and enable scrambler */
  ctrl &= ~(PHY_M_PC_ENA_ENE_DT | PHY_M_PC_DIS_SCRAMB);
  gm_phy_write(hw, port, PHY_MARV_PHY_CTRL, ctrl);

  /* set LED2 -> ACT, LED1 -> LINK, LED0 -> SPEED */
  ctrl = PHY_M_FELP_LED2_CTRL(LED_PAR_CTRL_ACT_BL) |
   PHY_M_FELP_LED1_CTRL(LED_PAR_CTRL_LINK) |
   PHY_M_FELP_LED0_CTRL(LED_PAR_CTRL_SPEED);

  gm_phy_write(hw, port, PHY_MARV_FE_LED_PAR, ctrl);
  break;

 case CHIP_ID_YUKON_XL:
  pg = gm_phy_read(hw, port, PHY_MARV_EXT_ADR);

  /* select page 3 to access LED control register */
  gm_phy_write(hw, port, PHY_MARV_EXT_ADR, 3);

  /* set LED Function Control register */
  gm_phy_write(hw, port, PHY_MARV_PHY_CTRL,
        (PHY_M_LEDC_LOS_CTRL(1) | /* LINK/ACT */
         PHY_M_LEDC_INIT_CTRL(7) | /* 10 Mbps */
         PHY_M_LEDC_STA1_CTRL(7) | /* 100 Mbps */
         PHY_M_LEDC_STA0_CTRL(7))); /* 1000 Mbps */

  /* set Polarity Control register */
  gm_phy_write(hw, port, PHY_MARV_PHY_STAT,
        (PHY_M_POLC_LS1_P_MIX(4) |
         PHY_M_POLC_IS0_P_MIX(4) |
         PHY_M_POLC_LOS_CTRL(2) |
         PHY_M_POLC_INIT_CTRL(2) |
         PHY_M_POLC_STA1_CTRL(2) |
         PHY_M_POLC_STA0_CTRL(2)));

  /* restore page register */
  gm_phy_write(hw, port, PHY_MARV_EXT_ADR, pg);
  break;

 case CHIP_ID_YUKON_EC_U:
 case CHIP_ID_YUKON_EX:
 case CHIP_ID_YUKON_SUPR:
  pg = gm_phy_read(hw, port, PHY_MARV_EXT_ADR);

  /* select page 3 to access LED control register */
  gm_phy_write(hw, port, PHY_MARV_EXT_ADR, 3);

  /* set LED Function Control register */
  gm_phy_write(hw, port, PHY_MARV_PHY_CTRL,
        (PHY_M_LEDC_LOS_CTRL(1) | /* LINK/ACT */
         PHY_M_LEDC_INIT_CTRL(8) | /* 10 Mbps */
         PHY_M_LEDC_STA1_CTRL(7) | /* 100 Mbps */
         PHY_M_LEDC_STA0_CTRL(7)));/* 1000 Mbps */

  /* set Blink Rate in LED Timer Control Register */
  gm_phy_write(hw, port, PHY_MARV_INT_MASK,
        ledctrl | PHY_M_LED_BLINK_RT(BLINK_84MS));
  /* restore page register */
  gm_phy_write(hw, port, PHY_MARV_EXT_ADR, pg);
  break;

 default:
  /* set Tx LED (LED_TX) to blink mode on Rx OR Tx activity */
  ledctrl |= PHY_M_LED_BLINK_RT(BLINK_84MS) | PHY_M_LEDC_TX_CTRL;

  /* turn off the Rx LED (LED_RX) */
  ledover |= PHY_M_LED_MO_RX(MO_LED_OFF);
 }

 if (hw->chip_id == CHIP_ID_YUKON_EC_U || hw->chip_id == CHIP_ID_YUKON_UL_2) {
  /* apply fixes in PHY AFE */
  gm_phy_write(hw, port, PHY_MARV_EXT_ADR, 255);

  /* increase differential signal amplitude in 10BASE-T */
  gm_phy_write(hw, port, 0x18, 0xaa99);
  gm_phy_write(hw, port, 0x17, 0x2011);

  if (hw->chip_id == CHIP_ID_YUKON_EC_U) {
   /* fix for IEEE A/B Symmetry failure in 1000BASE-T */
   gm_phy_write(hw, port, 0x18, 0xa204);
   gm_phy_write(hw, port, 0x17, 0x2002);
  }

  /* set page register to 0 */
  gm_phy_write(hw, port, PHY_MARV_EXT_ADR, 0);
 } else if (hw->chip_id == CHIP_ID_YUKON_FE_P &&
     hw->chip_rev == CHIP_REV_YU_FE2_A0) {
  /* apply workaround for integrated resistors calibration */
  gm_phy_write(hw, port, PHY_MARV_PAGE_ADDR, 17);
  gm_phy_write(hw, port, PHY_MARV_PAGE_DATA, 0x3f60);
 } else if (hw->chip_id == CHIP_ID_YUKON_OPT && hw->chip_rev == 0) {
  /* apply fixes in PHY AFE */
  gm_phy_write(hw, port, PHY_MARV_EXT_ADR, 0x00ff);

  /* apply RDAC termination workaround */
  gm_phy_write(hw, port, 24, 0x2800);
  gm_phy_write(hw, port, 23, 0x2001);

  /* set page register back to 0 */
  gm_phy_write(hw, port, PHY_MARV_EXT_ADR, 0);
 } else if (hw->chip_id != CHIP_ID_YUKON_EX &&
     hw->chip_id < CHIP_ID_YUKON_SUPR) {
  /* no effect on Yukon-XL */
  gm_phy_write(hw, port, PHY_MARV_LED_CTRL, ledctrl);

  if (!(sky2->flags & SKY2_FLAG_AUTO_SPEED) ||
      sky2->speed == SPEED_100) {
   /* turn on 100 Mbps LED (LED_LINK100) */
   ledover |= PHY_M_LED_MO_100(MO_LED_ON);
  }

  if (ledover)
   gm_phy_write(hw, port, PHY_MARV_LED_OVER, ledover);

 } else if (hw->chip_id == CHIP_ID_YUKON_PRM &&
     (sky2_read8(hw, B2_MAC_CFG) & 0xf) == 0x7) {
  int i;
  /* This a phy register setup workaround copied from vendor driver. */
  static const struct {
   u16 reg, val;
  } eee_afe[] = {
   { 0x156, 0x58ce },
   { 0x153, 0x99eb },
   { 0x141, 0x8064 },
   /* { 0x155, 0x130b },*/
   { 0x000, 0x0000 },
   { 0x151, 0x8433 },
   { 0x14b, 0x8c44 },
   { 0x14c, 0x0f90 },
   { 0x14f, 0x39aa },
   /* { 0x154, 0x2f39 },*/
   { 0x14d, 0xba33 },
   { 0x144, 0x0048 },
   { 0x152, 0x2010 },
   /* { 0x158, 0x1223 },*/
   { 0x140, 0x4444 },
   { 0x154, 0x2f3b },
   { 0x158, 0xb203 },
   { 0x157, 0x2029 },
  };

  /* Start Workaround for OptimaEEE Rev.Z0 */
  gm_phy_write(hw, port, PHY_MARV_EXT_ADR, 0x00fb);

  gm_phy_write(hw, port,  1, 0x4099);
  gm_phy_write(hw, port,  3, 0x1120);
  gm_phy_write(hw, port, 11, 0x113c);
  gm_phy_write(hw, port, 14, 0x8100);
  gm_phy_write(hw, port, 15, 0x112a);
  gm_phy_write(hw, port, 17, 0x1008);

  gm_phy_write(hw, port, PHY_MARV_EXT_ADR, 0x00fc);
  gm_phy_write(hw, port,  1, 0x20b0);

  gm_phy_write(hw, port, PHY_MARV_EXT_ADR, 0x00ff);

  for (i = 0; i < ARRAY_SIZE(eee_afe); i++) {
   /* apply AFE settings */
   gm_phy_write(hw, port, 17, eee_afe[i].val);
   gm_phy_write(hw, port, 16, eee_afe[i].reg | 1u<<13);
  }

  /* End Workaround for OptimaEEE */
  gm_phy_write(hw, port, PHY_MARV_EXT_ADR, 0);

  /* Enable 10Base-Te (EEE) */
  if (hw->chip_id >= CHIP_ID_YUKON_PRM) {
   reg = gm_phy_read(hw, port, PHY_MARV_EXT_CTRL);
   gm_phy_write(hw, port, PHY_MARV_EXT_CTRL,
         reg | PHY_M_10B_TE_ENABLE);
  }
 }

 /* Enable phy interrupt on auto-negotiation complete (or link up) */
 if (sky2->flags & SKY2_FLAG_AUTO_SPEED)
  gm_phy_write(hw, port, PHY_MARV_INT_MASK, PHY_M_IS_AN_COMPL);
 else
  gm_phy_write(hw, port, PHY_MARV_INT_MASK, PHY_M_DEF_MSK);
}

static const u32 phy_power[] = { PCI_Y2_PHY1_POWD, PCI_Y2_PHY2_POWD };
static const u32 coma_mode[] = { PCI_Y2_PHY1_COMA, PCI_Y2_PHY2_COMA };

static void sky2_phy_power_up(struct sky2_hw *hw, unsigned port)
{
 u32 reg1;

 sky2_write8(hw, B2_TST_CTRL1, TST_CFG_WRITE_ON);
 reg1 = sky2_pci_read32(hw, PCI_DEV_REG1);
 reg1 &= ~phy_power[port];

 if (hw->chip_id == CHIP_ID_YUKON_XL && hw->chip_rev > CHIP_REV_YU_XL_A1)
  reg1 |= coma_mode[port];

 sky2_pci_write32(hw, PCI_DEV_REG1, reg1);
 sky2_write8(hw, B2_TST_CTRL1, TST_CFG_WRITE_OFF);
 sky2_pci_read32(hw, PCI_DEV_REG1);

 if (hw->chip_id == CHIP_ID_YUKON_FE)
  gm_phy_write(hw, port, PHY_MARV_CTRL, PHY_CT_ANE);
 else if (hw->flags & SKY2_HW_ADV_POWER_CTL)
  sky2_write8(hw, SK_REG(port, GPHY_CTRL), GPC_RST_CLR);
}

static void sky2_phy_power_down(struct sky2_hw *hw, unsigned port)
{
 u32 reg1;
 u16 ctrl;

 /* release GPHY Control reset */
 sky2_write8(hw, SK_REG(port, GPHY_CTRL), GPC_RST_CLR);

 /* release GMAC reset */
 sky2_write8(hw, SK_REG(port, GMAC_CTRL), GMC_RST_CLR);

 if (hw->flags & SKY2_HW_NEWER_PHY) {
  /* select page 2 to access MAC control register */
  gm_phy_write(hw, port, PHY_MARV_EXT_ADR, 2);

  ctrl = gm_phy_read(hw, port, PHY_MARV_PHY_CTRL);
  /* allow GMII Power Down */
  ctrl &= ~PHY_M_MAC_GMIF_PUP;
  gm_phy_write(hw, port, PHY_MARV_PHY_CTRL, ctrl);

  /* set page register back to 0 */
  gm_phy_write(hw, port, PHY_MARV_EXT_ADR, 0);
 }

 /* setup General Purpose Control Register */
 gma_write16(hw, port, GM_GP_CTRL,
      GM_GPCR_FL_PASS | GM_GPCR_SPEED_100 |
      GM_GPCR_AU_DUP_DIS | GM_GPCR_AU_FCT_DIS |
      GM_GPCR_AU_SPD_DIS);

 if (hw->chip_id != CHIP_ID_YUKON_EC) {
  if (hw->chip_id == CHIP_ID_YUKON_EC_U) {
   /* select page 2 to access MAC control register */
   gm_phy_write(hw, port, PHY_MARV_EXT_ADR, 2);

   ctrl = gm_phy_read(hw, port, PHY_MARV_PHY_CTRL);
   /* enable Power Down */
   ctrl |= PHY_M_PC_POW_D_ENA;
   gm_phy_write(hw, port, PHY_MARV_PHY_CTRL, ctrl);

   /* set page register back to 0 */
   gm_phy_write(hw, port, PHY_MARV_EXT_ADR, 0);
  }

  /* set IEEE compatible Power Down Mode (dev. #4.99) */
  gm_phy_write(hw, port, PHY_MARV_CTRL, PHY_CT_PDOWN);
 }

 sky2_write8(hw, B2_TST_CTRL1, TST_CFG_WRITE_ON);
 reg1 = sky2_pci_read32(hw, PCI_DEV_REG1);
 reg1 |= phy_power[port];  /* set PHY to PowerDown/COMA Mode */
 sky2_pci_write32(hw, PCI_DEV_REG1, reg1);
 sky2_write8(hw, B2_TST_CTRL1, TST_CFG_WRITE_OFF);
}

/* configure IPG according to used link speed */
static void sky2_set_ipg(struct sky2_port *sky2)
{
 u16 reg;

 reg = gma_read16(sky2->hw, sky2->port, GM_SERIAL_MODE);
 reg &= ~GM_SMOD_IPG_MSK;
 if (sky2->speed > SPEED_100)
  reg |= IPG_DATA_VAL(IPG_DATA_DEF_1000);
 else
  reg |= IPG_DATA_VAL(IPG_DATA_DEF_10_100);
 gma_write16(sky2->hw, sky2->port, GM_SERIAL_MODE, reg);
}

/* Enable Rx/Tx */
static void sky2_enable_rx_tx(struct sky2_port *sky2)
{
 struct sky2_hw *hw = sky2->hw;
 unsigned port = sky2->port;
 u16 reg;

 reg = gma_read16(hw, port, GM_GP_CTRL);
 reg |= GM_GPCR_RX_ENA | GM_GPCR_TX_ENA;
 gma_write16(hw, port, GM_GP_CTRL, reg);
}

/* Force a renegotiation */
static void sky2_phy_reinit(struct sky2_port *sky2)
{
 spin_lock_bh(&sky2->phy_lock);
 sky2_phy_init(sky2->hw, sky2->port);
 sky2_enable_rx_tx(sky2);
 spin_unlock_bh(&sky2->phy_lock);
}

/* Put device in state to listen for Wake On Lan */
static void sky2_wol_init(struct sky2_port *sky2)
{
 struct sky2_hw *hw = sky2->hw;
 unsigned port = sky2->port;
 enum flow_control save_mode;
 u16 ctrl;

 /* Bring hardware out of reset */
 sky2_write16(hw, B0_CTST, CS_RST_CLR);
 sky2_write16(hw, SK_REG(port, GMAC_LINK_CTRL), GMLC_RST_CLR);

 sky2_write8(hw, SK_REG(port, GPHY_CTRL), GPC_RST_CLR);
 sky2_write8(hw, SK_REG(port, GMAC_CTRL), GMC_RST_CLR);

 /* Force to 10/100
 * sky2_reset will re-enable on resume
 */

 save_mode = sky2->flow_mode;
 ctrl = sky2->advertising;

 sky2->advertising &= ~(ADVERTISED_1000baseT_Half|ADVERTISED_1000baseT_Full);
 sky2->flow_mode = FC_NONE;

 spin_lock_bh(&sky2->phy_lock);
 sky2_phy_power_up(hw, port);
 sky2_phy_init(hw, port);
 spin_unlock_bh(&sky2->phy_lock);

 sky2->flow_mode = save_mode;
 sky2->advertising = ctrl;

 /* 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),
      sky2->netdev->dev_addr, ETH_ALEN);

 /* Turn on appropriate WOL control bits */
 sky2_write16(hw, WOL_REGS(port, WOL_CTRL_STAT), WOL_CTL_CLEAR_RESULT);
 ctrl = 0;
 if (sky2->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 (sky2->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;
 sky2_write16(hw, WOL_REGS(port, WOL_CTRL_STAT), ctrl);

 /* Disable PiG firmware */
 sky2_write16(hw, B0_CTST, Y2_HW_WOL_OFF);

 /* Needed by some broken BIOSes, use PCI rather than PCI-e for WOL */
 if (legacy_pme) {
  u32 reg1 = sky2_pci_read32(hw, PCI_DEV_REG1);
  reg1 |= PCI_Y2_PME_LEGACY;
  sky2_pci_write32(hw, PCI_DEV_REG1, reg1);
 }

 /* block receiver */
 sky2_write8(hw, SK_REG(port, RX_GMF_CTRL_T), GMF_RST_SET);
 sky2_read32(hw, B0_CTST);
}

static void sky2_set_tx_stfwd(struct sky2_hw *hw, unsigned port)
{
 struct net_device *dev = hw->dev[port];

 if ( (hw->chip_id == CHIP_ID_YUKON_EX &&
       hw->chip_rev != CHIP_REV_YU_EX_A0) ||
      hw->chip_id >= CHIP_ID_YUKON_FE_P) {
  /* Yukon-Extreme B0 and further Extreme devices */
  sky2_write32(hw, SK_REG(port, TX_GMF_CTRL_T), TX_STFW_ENA);
 } else if (dev->mtu > ETH_DATA_LEN) {
  /* set Tx GMAC FIFO Almost Empty Threshold */
  sky2_write32(hw, SK_REG(port, TX_GMF_AE_THR),
        (ECU_JUMBO_WM << 16) | ECU_AE_THR);

  sky2_write32(hw, SK_REG(port, TX_GMF_CTRL_T), TX_STFW_DIS);
 } else
  sky2_write32(hw, SK_REG(port, TX_GMF_CTRL_T), TX_STFW_ENA);
}

static void sky2_mac_init(struct sky2_hw *hw, unsigned port)
{
 struct sky2_port *sky2 = netdev_priv(hw->dev[port]);
 u16 reg;
 u32 rx_reg;
 int i;
 const u8 *addr = hw->dev[port]->dev_addr;

 sky2_write8(hw, SK_REG(port, GPHY_CTRL), GPC_RST_SET);
 sky2_write8(hw, SK_REG(port, GPHY_CTRL), GPC_RST_CLR);

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

 if (hw->chip_id == CHIP_ID_YUKON_XL &&
     hw->chip_rev == CHIP_REV_YU_XL_A0 &&
     port == 1) {
  /* WA DEV_472 -- looks like crossed wires on port 2 */
  /* clear GMAC 1 Control reset */
  sky2_write8(hw, SK_REG(0, GMAC_CTRL), GMC_RST_CLR);
  do {
   sky2_write8(hw, SK_REG(1, GMAC_CTRL), GMC_RST_SET);
   sky2_write8(hw, SK_REG(1, GMAC_CTRL), GMC_RST_CLR);
  } while (gm_phy_read(hw, 1, PHY_MARV_ID0) != PHY_MARV_ID0_VAL ||
    gm_phy_read(hw, 1, PHY_MARV_ID1) != PHY_MARV_ID1_Y2 ||
    gm_phy_read(hw, 1, PHY_MARV_INT_MASK) != 0);
 }

 sky2_read16(hw, SK_REG(port, GMAC_IRQ_SRC));

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

 spin_lock_bh(&sky2->phy_lock);
 sky2_phy_power_up(hw, port);
 sky2_phy_init(hw, port);
 spin_unlock_bh(&sky2->phy_lock);

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

 for (i = GM_MIB_CNT_BASE; i <= GM_MIB_CNT_END; i += 4)
  gma_read16(hw, port, 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) |
      TX_BACK_OFF_LIM(TX_BOF_LIM_DEF));

 /* serial mode register */
 reg = DATA_BLIND_VAL(DATA_BLIND_DEF) |
  GM_SMOD_VLAN_ENA | IPG_DATA_VAL(IPG_DATA_DEF_1000);

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

 if (hw->chip_id == CHIP_ID_YUKON_EC_U &&
     hw->chip_rev == CHIP_REV_YU_EC_U_B1)
  reg |= GM_NEW_FLOW_CTRL;

 gma_write16(hw, port, GM_SERIAL_MODE, reg);

 /* virtual address for data */
 gma_set_addr(hw, port, GM_SRC_ADDR_2L, addr);

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

 /* ignore 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);

 /* Configure Rx MAC FIFO */
 sky2_write8(hw, SK_REG(port, RX_GMF_CTRL_T), GMF_RST_CLR);
 rx_reg = GMF_OPER_ON | GMF_RX_F_FL_ON;
 if (hw->chip_id == CHIP_ID_YUKON_EX ||
     hw->chip_id == CHIP_ID_YUKON_FE_P)
  rx_reg |= GMF_RX_OVER_ON;

 sky2_write32(hw, SK_REG(port, RX_GMF_CTRL_T), rx_reg);

 if (hw->chip_id == CHIP_ID_YUKON_XL) {
  /* Hardware errata - clear flush mask */
  sky2_write16(hw, SK_REG(port, RX_GMF_FL_MSK), 0);
 } else {
  /* Flush Rx MAC FIFO on any flow control or error */
  sky2_write16(hw, SK_REG(port, RX_GMF_FL_MSK), GMR_FS_ANY_ERR);
 }

 /* Set threshold to 0xa (64 bytes) + 1 to workaround pause bug  */
 reg = RX_GMF_FL_THR_DEF + 1;
 /* Another magic mystery workaround from sk98lin */
 if (hw->chip_id == CHIP_ID_YUKON_FE_P &&
     hw->chip_rev == CHIP_REV_YU_FE2_A0)
  reg = 0x178;
 sky2_write16(hw, SK_REG(port, RX_GMF_FL_THR), reg);

 /* Configure Tx MAC FIFO */
 sky2_write8(hw, SK_REG(port, TX_GMF_CTRL_T), GMF_RST_CLR);
 sky2_write16(hw, SK_REG(port, TX_GMF_CTRL_T), GMF_OPER_ON);

 /* On chips without ram buffer, pause is controlled by MAC level */
 if (!(hw->flags & SKY2_HW_RAM_BUFFER)) {
  /* Pause threshold is scaled by 8 in bytes */
  if (hw->chip_id == CHIP_ID_YUKON_FE_P &&
      hw->chip_rev == CHIP_REV_YU_FE2_A0)
   reg = 1568 / 8;
  else
   reg = 1024 / 8;
  sky2_write16(hw, SK_REG(port, RX_GMF_UP_THR), reg);
  sky2_write16(hw, SK_REG(port, RX_GMF_LP_THR), 768 / 8);

  sky2_set_tx_stfwd(hw, port);
 }

 if (hw->chip_id == CHIP_ID_YUKON_FE_P &&
     hw->chip_rev == CHIP_REV_YU_FE2_A0) {
  /* disable dynamic watermark */
  reg = sky2_read16(hw, SK_REG(port, TX_GMF_EA));
  reg &= ~TX_DYN_WM_ENA;
  sky2_write16(hw, SK_REG(port, TX_GMF_EA), reg);
 }
}

/* Assign Ram Buffer allocation to queue */
static void sky2_ramset(struct sky2_hw *hw, u16 q, u32 start, u32 space)
{
 u32 end;

 /* convert from K bytes to qwords used for hw register */
 start *= 1024/8;
 space *= 1024/8;
 end = start + space - 1;

 sky2_write8(hw, RB_ADDR(q, RB_CTRL), RB_RST_CLR);
 sky2_write32(hw, RB_ADDR(q, RB_START), start);
 sky2_write32(hw, RB_ADDR(q, RB_END), end);
 sky2_write32(hw, RB_ADDR(q, RB_WP), start);
 sky2_write32(hw, RB_ADDR(q, RB_RP), start);

 if (q == Q_R1 || q == Q_R2) {
  u32 tp = space - space/4;

  /* On receive queue's set the thresholds
 * give receiver priority when > 3/4 full
 * send pause when down to 2K
 */

  sky2_write32(hw, RB_ADDR(q, RB_RX_UTHP), tp);
  sky2_write32(hw, RB_ADDR(q, RB_RX_LTHP), space/2);

  tp = space - 8192/8;
  sky2_write32(hw, RB_ADDR(q, RB_RX_UTPP), tp);
  sky2_write32(hw, RB_ADDR(q, RB_RX_LTPP), space/4);
 } else {
  /* Enable store & forward on Tx queue's because
 * Tx FIFO is only 1K on Yukon
 */

  sky2_write8(hw, RB_ADDR(q, RB_CTRL), RB_ENA_STFWD);
 }

 sky2_write8(hw, RB_ADDR(q, RB_CTRL), RB_ENA_OP_MD);
 sky2_read8(hw, RB_ADDR(q, RB_CTRL));
}

/* Setup Bus Memory Interface */
static void sky2_qset(struct sky2_hw *hw, u16 q)
{
 sky2_write32(hw, Q_ADDR(q, Q_CSR), BMU_CLR_RESET);
 sky2_write32(hw, Q_ADDR(q, Q_CSR), BMU_OPER_INIT);
 sky2_write32(hw, Q_ADDR(q, Q_CSR), BMU_FIFO_OP_ON);
 sky2_write32(hw, Q_ADDR(q, Q_WM),  BMU_WM_DEFAULT);
}

/* Setup prefetch unit registers. This is the interface between
 * hardware and driver list elements
 */

static void sky2_prefetch_init(struct sky2_hw *hw, u32 qaddr,
          dma_addr_t addr, u32 last)
{
 sky2_write32(hw, Y2_QADDR(qaddr, PREF_UNIT_CTRL), PREF_UNIT_RST_SET);
 sky2_write32(hw, Y2_QADDR(qaddr, PREF_UNIT_CTRL), PREF_UNIT_RST_CLR);
 sky2_write32(hw, Y2_QADDR(qaddr, PREF_UNIT_ADDR_HI), upper_32_bits(addr));
 sky2_write32(hw, Y2_QADDR(qaddr, PREF_UNIT_ADDR_LO), lower_32_bits(addr));
 sky2_write16(hw, Y2_QADDR(qaddr, PREF_UNIT_LAST_IDX), last);
 sky2_write32(hw, Y2_QADDR(qaddr, PREF_UNIT_CTRL), PREF_UNIT_OP_ON);

 sky2_read32(hw, Y2_QADDR(qaddr, PREF_UNIT_CTRL));
}

static inline struct sky2_tx_le *get_tx_le(struct sky2_port *sky2, u16 *slot)
{
 struct sky2_tx_le *le = sky2->tx_le + *slot;

 *slot = RING_NEXT(*slot, sky2->tx_ring_size);
 le->ctrl = 0;
 return le;
}

static void tx_init(struct sky2_port *sky2)
{
 struct sky2_tx_le *le;

 sky2->tx_prod = sky2->tx_cons = 0;
 sky2->tx_tcpsum = 0;
 sky2->tx_last_mss = 0;
 netdev_reset_queue(sky2->netdev);

 le = get_tx_le(sky2, &sky2->tx_prod);
 le->addr = 0;
 le->opcode = OP_ADDR64 | HW_OWNER;
 sky2->tx_last_upper = 0;
}

/* Update chip's next pointer */
static inline void sky2_put_idx(struct sky2_hw *hw, unsigned q, u16 idx)
{
 /* Make sure write' to descriptors are complete before we tell hardware */
 wmb();
 sky2_write16(hw, Y2_QADDR(q, PREF_UNIT_PUT_IDX), idx);
}


static inline struct sky2_rx_le *sky2_next_rx(struct sky2_port *sky2)
{
 struct sky2_rx_le *le = sky2->rx_le + sky2->rx_put;
 sky2->rx_put = RING_NEXT(sky2->rx_put, RX_LE_SIZE);
 le->ctrl = 0;
 return le;
}

static unsigned sky2_get_rx_threshold(struct sky2_port *sky2)
{
 unsigned size;

 /* Space needed for frame data + headers rounded up */
 size = roundup(sky2->netdev->mtu + ETH_HLEN + VLAN_HLEN, 8);

 /* Stopping point for hardware truncation */
 return (size - 8) / sizeof(u32);
}

static unsigned sky2_get_rx_data_size(struct sky2_port *sky2)
{
 struct rx_ring_info *re;
 unsigned size;

 /* Space needed for frame data + headers rounded up */
 size = roundup(sky2->netdev->mtu + ETH_HLEN + VLAN_HLEN, 8);

 sky2->rx_nfrags = size >> PAGE_SHIFT;
 BUG_ON(sky2->rx_nfrags > ARRAY_SIZE(re->frag_addr));

 /* Compute residue after pages */
 size -= sky2->rx_nfrags << PAGE_SHIFT;

 /* Optimize to handle small packets and headers */
 if (size < copybreak)
  size = copybreak;
 if (size < ETH_HLEN)
  size = ETH_HLEN;

 return size;
}

/* Build description to hardware for one receive segment */
static void sky2_rx_add(struct sky2_port *sky2, u8 op,
   dma_addr_t map, unsigned len)
{
 struct sky2_rx_le *le;

 if (sizeof(dma_addr_t) > sizeof(u32)) {
  le = sky2_next_rx(sky2);
  le->addr = cpu_to_le32(upper_32_bits(map));
  le->opcode = OP_ADDR64 | HW_OWNER;
 }

 le = sky2_next_rx(sky2);
 le->addr = cpu_to_le32(lower_32_bits(map));
 le->length = cpu_to_le16(len);
 le->opcode = op | HW_OWNER;
}

/* Build description to hardware for one possibly fragmented skb */
static void sky2_rx_submit(struct sky2_port *sky2,
      const struct rx_ring_info *re)
{
 int i;

 sky2_rx_add(sky2, OP_PACKET, re->data_addr, sky2->rx_data_size);

 for (i = 0; i < skb_shinfo(re->skb)->nr_frags; i++)
  sky2_rx_add(sky2, OP_BUFFER, re->frag_addr[i], PAGE_SIZE);
}


static int sky2_rx_map_skb(struct pci_dev *pdev, struct rx_ring_info *re,
       unsigned size)
{
 struct sk_buff *skb = re->skb;
 int i;

 re->data_addr = dma_map_single(&pdev->dev, skb->data, size,
           DMA_FROM_DEVICE);
 if (dma_mapping_error(&pdev->dev, re->data_addr))
  goto mapping_error;

 dma_unmap_len_set(re, data_size, size);

 for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
  const skb_frag_t *frag = &skb_shinfo(skb)->frags[i];

  re->frag_addr[i] = skb_frag_dma_map(&pdev->dev, frag, 0,
          skb_frag_size(frag),
          DMA_FROM_DEVICE);

  if (dma_mapping_error(&pdev->dev, re->frag_addr[i]))
   goto map_page_error;
 }
 return 0;

map_page_error:
 while (--i >= 0) {
  dma_unmap_page(&pdev->dev, re->frag_addr[i],
          skb_frag_size(&skb_shinfo(skb)->frags[i]),
          DMA_FROM_DEVICE);
 }

 dma_unmap_single(&pdev->dev, re->data_addr,
    dma_unmap_len(re, data_size), DMA_FROM_DEVICE);

mapping_error:
 if (net_ratelimit())
  dev_warn(&pdev->dev, "%s: rx mapping error\n",
    skb->dev->name);
 return -EIO;
}

static void sky2_rx_unmap_skb(struct pci_dev *pdev, struct rx_ring_info *re)
{
 struct sk_buff *skb = re->skb;
 int i;

 dma_unmap_single(&pdev->dev, re->data_addr,
    dma_unmap_len(re, data_size), DMA_FROM_DEVICE);

 for (i = 0; i < skb_shinfo(skb)->nr_frags; i++)
  dma_unmap_page(&pdev->dev, re->frag_addr[i],
          skb_frag_size(&skb_shinfo(skb)->frags[i]),
          DMA_FROM_DEVICE);
}

/* Tell chip where to start receive checksum.
 * Actually has two checksums, but set both same to avoid possible byte
 * order problems.
 */

static void rx_set_checksum(struct sky2_port *sky2)
{
 struct sky2_rx_le *le = sky2_next_rx(sky2);

 le->addr = cpu_to_le32((ETH_HLEN << 16) | ETH_HLEN);
 le->ctrl = 0;
 le->opcode = OP_TCPSTART | HW_OWNER;

 sky2_write32(sky2->hw,
       Q_ADDR(rxqaddr[sky2->port], Q_CSR),
       (sky2->netdev->features & NETIF_F_RXCSUM)
       ? BMU_ENA_RX_CHKSUM : BMU_DIS_RX_CHKSUM);
}

/* Enable/disable receive hash calculation (RSS) */
static void rx_set_rss(struct net_device *dev, netdev_features_t features)
{
 struct sky2_port *sky2 = netdev_priv(dev);
 struct sky2_hw *hw = sky2->hw;
 int i, nkeys = 4;

 /* Supports IPv6 and other modes */
 if (hw->flags & SKY2_HW_NEW_LE) {
  nkeys = 10;
  sky2_write32(hw, SK_REG(sky2->port, RSS_CFG), HASH_ALL);
 }

 /* Program RSS initial values */
 if (features & NETIF_F_RXHASH) {
  u32 rss_key[10];

  netdev_rss_key_fill(rss_key, sizeof(rss_key));
  for (i = 0; i < nkeys; i++)
   sky2_write32(hw, SK_REG(sky2->port, RSS_KEY + i * 4),
         rss_key[i]);

  /* Need to turn on (undocumented) flag to make hashing work  */
  sky2_write32(hw, SK_REG(sky2->port, RX_GMF_CTRL_T),
        RX_STFW_ENA);

  sky2_write32(hw, Q_ADDR(rxqaddr[sky2->port], Q_CSR),
        BMU_ENA_RX_RSS_HASH);
 } else
  sky2_write32(hw, Q_ADDR(rxqaddr[sky2->port], Q_CSR),
        BMU_DIS_RX_RSS_HASH);
}

/*
 * The RX Stop command will not work for Yukon-2 if the BMU does not
 * reach the end of packet and since we can't make sure that we have
 * incoming data, we must reset the BMU while it is not doing a DMA
 * transfer. Since it is possible that the RX path is still active,
 * the RX RAM buffer will be stopped first, so any possible incoming
 * data will not trigger a DMA. After the RAM buffer is stopped, the
 * BMU is polled until any DMA in progress is ended and only then it
 * will be reset.
 */

static void sky2_rx_stop(struct sky2_port *sky2)
{
 struct sky2_hw *hw = sky2->hw;
 unsigned rxq = rxqaddr[sky2->port];
 int i;

 /* disable the RAM Buffer receive queue */
 sky2_write8(hw, RB_ADDR(rxq, RB_CTRL), RB_DIS_OP_MD);

 for (i = 0; i < 0xffff; i++)
  if (sky2_read8(hw, RB_ADDR(rxq, Q_RSL))
      == sky2_read8(hw, RB_ADDR(rxq, Q_RL)))
   goto stopped;

 netdev_warn(sky2->netdev, "receiver stop failed\n");
stopped:
 sky2_write32(hw, Q_ADDR(rxq, Q_CSR), BMU_RST_SET | BMU_FIFO_RST);

 /* reset the Rx prefetch unit */
 sky2_write32(hw, Y2_QADDR(rxq, PREF_UNIT_CTRL), PREF_UNIT_RST_SET);
}

/* Clean out receive buffer area, assumes receiver hardware stopped */
static void sky2_rx_clean(struct sky2_port *sky2)
{
 unsigned i;

 if (sky2->rx_le)
  memset(sky2->rx_le, 0, RX_LE_BYTES);

 for (i = 0; i < sky2->rx_pending; i++) {
  struct rx_ring_info *re = sky2->rx_ring + i;

  if (re->skb) {
   sky2_rx_unmap_skb(sky2->hw->pdev, re);
   kfree_skb(re->skb);
   re->skb = NULL;
  }
 }
}

/* Basic MII support */
static int sky2_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
 struct mii_ioctl_data *data = if_mii(ifr);
 struct sky2_port *sky2 = netdev_priv(dev);
 struct sky2_hw *hw = sky2->hw;
 int err = -EOPNOTSUPP;

 if (!netif_running(dev))
  return -ENODEV; /* Phy still in reset */

 switch (cmd) {
 case SIOCGMIIPHY:
  data->phy_id = PHY_ADDR_MARV;

  fallthrough;
 case SIOCGMIIREG: {
  u16 val = 0;

  spin_lock_bh(&sky2->phy_lock);
  err = __gm_phy_read(hw, sky2->port, data->reg_num & 0x1f, &val);
  spin_unlock_bh(&sky2->phy_lock);

  data->val_out = val;
  break;
 }

 case SIOCSMIIREG:
  spin_lock_bh(&sky2->phy_lock);
  err = gm_phy_write(hw, sky2->port, data->reg_num & 0x1f,
       data->val_in);
  spin_unlock_bh(&sky2->phy_lock);
  break;
 }
 return err;
}

#define SKY2_VLAN_OFFLOADS (NETIF_F_IP_CSUM | NETIF_F_SG | NETIF_F_TSO)

static void sky2_vlan_mode(struct net_device *dev, netdev_features_t features)
{
 struct sky2_port *sky2 = netdev_priv(dev);
 struct sky2_hw *hw = sky2->hw;
 u16 port = sky2->port;

 if (features & NETIF_F_HW_VLAN_CTAG_RX)
  sky2_write32(hw, SK_REG(port, RX_GMF_CTRL_T),
        RX_VLAN_STRIP_ON);
 else
  sky2_write32(hw, SK_REG(port, RX_GMF_CTRL_T),
        RX_VLAN_STRIP_OFF);

 if (features & NETIF_F_HW_VLAN_CTAG_TX) {
  sky2_write32(hw, SK_REG(port, TX_GMF_CTRL_T),
        TX_VLAN_TAG_ON);

  dev->vlan_features |= SKY2_VLAN_OFFLOADS;
 } else {
  sky2_write32(hw, SK_REG(port, TX_GMF_CTRL_T),
        TX_VLAN_TAG_OFF);

  /* Can't do transmit offload of vlan without hw vlan */
  dev->vlan_features &= ~SKY2_VLAN_OFFLOADS;
 }
}

/* Amount of required worst case padding in rx buffer */
static inline unsigned sky2_rx_pad(const struct sky2_hw *hw)
{
 return (hw->flags & SKY2_HW_RAM_BUFFER) ? 8 : 2;
}

/*
 * Allocate an skb for receiving. If the MTU is large enough
 * make the skb non-linear with a fragment list of pages.
 */

static struct sk_buff *sky2_rx_alloc(struct sky2_port *sky2, gfp_t gfp)
{
 struct sk_buff *skb;
 int i;

 skb = __netdev_alloc_skb(sky2->netdev,
     sky2->rx_data_size + sky2_rx_pad(sky2->hw),
     gfp);
 if (!skb)
  goto nomem;

 if (sky2->hw->flags & SKY2_HW_RAM_BUFFER) {
  unsigned char *start;
  /*
 * Workaround for a bug in FIFO that cause hang
 * if the FIFO if the receive buffer is not 64 byte aligned.
 * The buffer returned from netdev_alloc_skb is
 * aligned except if slab debugging is enabled.
 */

  start = PTR_ALIGN(skb->data, 8);
  skb_reserve(skb, start - skb->data);
 } else
  skb_reserve(skb, NET_IP_ALIGN);

 for (i = 0; i < sky2->rx_nfrags; i++) {
  struct page *page = alloc_page(gfp);

  if (!page)
   goto free_partial;
  skb_fill_page_desc(skb, i, page, 0, PAGE_SIZE);
 }

 return skb;
free_partial:
 kfree_skb(skb);
nomem:
 return NULL;
}

static inline void sky2_rx_update(struct sky2_port *sky2, unsigned rxq)
{
 sky2_put_idx(sky2->hw, rxq, sky2->rx_put);
}

static int sky2_alloc_rx_skbs(struct sky2_port *sky2)
{
 struct sky2_hw *hw = sky2->hw;
 unsigned i;

 sky2->rx_data_size = sky2_get_rx_data_size(sky2);

 /* Fill Rx ring */
 for (i = 0; i < sky2->rx_pending; i++) {
  struct rx_ring_info *re = sky2->rx_ring + i;

  re->skb = sky2_rx_alloc(sky2, GFP_KERNEL);
  if (!re->skb)
   return -ENOMEM;

  if (sky2_rx_map_skb(hw->pdev, re, sky2->rx_data_size)) {
   dev_kfree_skb(re->skb);
   re->skb = NULL;
   return -ENOMEM;
  }
 }
 return 0;
}

/*
 * Setup receiver buffer pool.
 * Normal case this ends up creating one list element for skb
 * in the receive ring. Worst case if using large MTU and each
 * allocation falls on a different 64 bit region, that results
 * in 6 list elements per ring entry.
 * One element is used for checksum enable/disable, and one
 * extra to avoid wrap.
 */

static void sky2_rx_start(struct sky2_port *sky2)
{
 struct sky2_hw *hw = sky2->hw;
 struct rx_ring_info *re;
 unsigned rxq = rxqaddr[sky2->port];
 unsigned i, thresh;

 sky2->rx_put = sky2->rx_next = 0;
 sky2_qset(hw, rxq);

 /* On PCI express lowering the watermark gives better performance */
 if (pci_is_pcie(hw->pdev))
  sky2_write32(hw, Q_ADDR(rxq, Q_WM), BMU_WM_PEX);

 /* These chips have no ram buffer?
 * MAC Rx RAM Read is controlled by hardware
 */

 if (hw->chip_id == CHIP_ID_YUKON_EC_U &&
     hw->chip_rev > CHIP_REV_YU_EC_U_A0)
  sky2_write32(hw, Q_ADDR(rxq, Q_TEST), F_M_RX_RAM_DIS);

 sky2_prefetch_init(hw, rxq, sky2->rx_le_map, RX_LE_SIZE - 1);

 if (!(hw->flags & SKY2_HW_NEW_LE))
  rx_set_checksum(sky2);

 if (!(hw->flags & SKY2_HW_RSS_BROKEN))
  rx_set_rss(sky2->netdev, sky2->netdev->features);

 /* submit Rx ring */
 for (i = 0; i < sky2->rx_pending; i++) {
  re = sky2->rx_ring + i;
  sky2_rx_submit(sky2, re);
 }

 /*
 * The receiver hangs if it receives frames larger than the
 * packet buffer. As a workaround, truncate oversize frames, but
 * the register is limited to 9 bits, so if you do frames > 2052
 * you better get the MTU right!
 */

 thresh = sky2_get_rx_threshold(sky2);
 if (thresh > 0x1ff)
  sky2_write32(hw, SK_REG(sky2->port, RX_GMF_CTRL_T), RX_TRUNC_OFF);
 else {
  sky2_write16(hw, SK_REG(sky2->port, RX_GMF_TR_THR), thresh);
  sky2_write32(hw, SK_REG(sky2->port, RX_GMF_CTRL_T), RX_TRUNC_ON);
 }

 /* Tell chip about available buffers */
 sky2_rx_update(sky2, rxq);

 if (hw->chip_id == CHIP_ID_YUKON_EX ||
     hw->chip_id == CHIP_ID_YUKON_SUPR) {
  /*
 * Disable flushing of non ASF packets;
 * must be done after initializing the BMUs;
 * drivers without ASF support should do this too, otherwise
 * it may happen that they cannot run on ASF devices;
 * remember that the MAC FIFO isn't reset during initialization.
 */

  sky2_write32(hw, SK_REG(sky2->port, RX_GMF_CTRL_T), RX_MACSEC_FLUSH_OFF);
 }

 if (hw->chip_id >= CHIP_ID_YUKON_SUPR) {
  /* Enable RX Home Address & Routing Header checksum fix */
  sky2_write16(hw, SK_REG(sky2->port, RX_GMF_FL_CTRL),
        RX_IPV6_SA_MOB_ENA | RX_IPV6_DA_MOB_ENA);

  /* Enable TX Home Address & Routing Header checksum fix */
  sky2_write32(hw, Q_ADDR(txqaddr[sky2->port], Q_TEST),
        TBMU_TEST_HOME_ADD_FIX_EN | TBMU_TEST_ROUTING_ADD_FIX_EN);
 }
}

static int sky2_alloc_buffers(struct sky2_port *sky2)
{
 struct sky2_hw *hw = sky2->hw;

 /* must be power of 2 */
 sky2->tx_le = dma_alloc_coherent(&hw->pdev->dev,
      sky2->tx_ring_size * sizeof(struct sky2_tx_le),
      &sky2->tx_le_map, GFP_KERNEL);
 if (!sky2->tx_le)
  goto nomem;

 sky2->tx_ring = kcalloc(sky2->tx_ring_size, sizeof(struct tx_ring_info),
    GFP_KERNEL);
 if (!sky2->tx_ring)
  goto nomem;

 sky2->rx_le = dma_alloc_coherent(&hw->pdev->dev, RX_LE_BYTES,
      &sky2->rx_le_map, GFP_KERNEL);
 if (!sky2->rx_le)
  goto nomem;

 sky2->rx_ring = kcalloc(sky2->rx_pending, sizeof(struct rx_ring_info),
    GFP_KERNEL);
 if (!sky2->rx_ring)
  goto nomem;

 return sky2_alloc_rx_skbs(sky2);
nomem:
 return -ENOMEM;
}

static void sky2_free_buffers(struct sky2_port *sky2)
{
 struct sky2_hw *hw = sky2->hw;

 sky2_rx_clean(sky2);

 if (sky2->rx_le) {
  dma_free_coherent(&hw->pdev->dev, RX_LE_BYTES, sky2->rx_le,
      sky2->rx_le_map);
  sky2->rx_le = NULL;
 }
 if (sky2->tx_le) {
  dma_free_coherent(&hw->pdev->dev,
      sky2->tx_ring_size * sizeof(struct sky2_tx_le),
      sky2->tx_le, sky2->tx_le_map);
  sky2->tx_le = NULL;
 }
 kfree(sky2->tx_ring);
 kfree(sky2->rx_ring);

 sky2->tx_ring = NULL;
 sky2->rx_ring = NULL;
}

static void sky2_hw_up(struct sky2_port *sky2)
{
 struct sky2_hw *hw = sky2->hw;
 unsigned port = sky2->port;
 u32 ramsize;
 int cap;
 struct net_device *otherdev = hw->dev[sky2->port^1];

 tx_init(sky2);

 /*
 * On dual port PCI-X card, there is an problem where status
 * can be received out of order due to split transactions
 */

 if (otherdev && netif_running(otherdev) &&
     (cap = pci_find_capability(hw->pdev, PCI_CAP_ID_PCIX))) {
  u16 cmd;

  cmd = sky2_pci_read16(hw, cap + PCI_X_CMD);
  cmd &= ~PCI_X_CMD_MAX_SPLIT;
  sky2_pci_write16(hw, cap + PCI_X_CMD, cmd);
 }

 sky2_mac_init(hw, port);

 /* Register is number of 4K blocks on internal RAM buffer. */
 ramsize = sky2_read8(hw, B2_E_0) * 4;
 if (ramsize > 0) {
  u32 rxspace;

  netdev_dbg(sky2->netdev, "ram buffer %dK\n", ramsize);
  if (ramsize < 16)
   rxspace = ramsize / 2;
  else
   rxspace = 8 + (2*(ramsize - 16))/3;

  sky2_ramset(hw, rxqaddr[port], 0, rxspace);
  sky2_ramset(hw, txqaddr[port], rxspace, ramsize - rxspace);

  /* Make sure SyncQ is disabled */
  sky2_write8(hw, RB_ADDR(port == 0 ? Q_XS1 : Q_XS2, RB_CTRL),
       RB_RST_SET);
 }

 sky2_qset(hw, txqaddr[port]);

 /* This is copied from sk98lin 10.0.5.3; no one tells me about erratta's */
 if (hw->chip_id == CHIP_ID_YUKON_EX && hw->chip_rev == CHIP_REV_YU_EX_B0)
  sky2_write32(hw, Q_ADDR(txqaddr[port], Q_TEST), F_TX_CHK_AUTO_OFF);

 /* Set almost empty threshold */
 if (hw->chip_id == CHIP_ID_YUKON_EC_U &&
     hw->chip_rev == CHIP_REV_YU_EC_U_A0)
  sky2_write16(hw, Q_ADDR(txqaddr[port], Q_AL), ECU_TXFF_LEV);

 sky2_prefetch_init(hw, txqaddr[port], sky2->tx_le_map,
      sky2->tx_ring_size - 1);

 sky2_vlan_mode(sky2->netdev, sky2->netdev->features);
 netdev_update_features(sky2->netdev);

 sky2_rx_start(sky2);
}

/* Setup device IRQ and enable napi to process */
static int sky2_setup_irq(struct sky2_hw *hw, const char *name)
{
 struct pci_dev *pdev = hw->pdev;
 int err;

 err = request_irq(pdev->irq, sky2_intr,
     (hw->flags & SKY2_HW_USE_MSI) ? 0 : IRQF_SHARED,
     name, hw);
 if (err)
  dev_err(&pdev->dev, "cannot assign irq %d\n", pdev->irq);
 else {
  hw->flags |= SKY2_HW_IRQ_SETUP;

  napi_enable(&hw->napi);
  sky2_write32(hw, B0_IMSK, Y2_IS_BASE);
  sky2_read32(hw, B0_IMSK);
 }

 return err;
}


/* Bring up network interface. */
static int sky2_open(struct net_device *dev)
{
 struct sky2_port *sky2 = netdev_priv(dev);
 struct sky2_hw *hw = sky2->hw;
 unsigned port = sky2->port;
 u32 imask;
 int err;

 netif_carrier_off(dev);

 err = sky2_alloc_buffers(sky2);
 if (err)
  goto err_out;

 /* With single port, IRQ is setup when device is brought up */
 if (hw->ports == 1 && (err = sky2_setup_irq(hw, dev->name)))
  goto err_out;

 sky2_hw_up(sky2);

 /* Enable interrupts from phy/mac for port */
 imask = sky2_read32(hw, B0_IMSK);

 if (hw->chip_id == CHIP_ID_YUKON_OPT ||
     hw->chip_id == CHIP_ID_YUKON_PRM ||
     hw->chip_id == CHIP_ID_YUKON_OP_2)
  imask |= Y2_IS_PHY_QLNK; /* enable PHY Quick Link */

 imask |= portirq_msk[port];
 sky2_write32(hw, B0_IMSK, imask);
 sky2_read32(hw, B0_IMSK);

 netif_info(sky2, ifup, dev, "enabling interface\n");

 return 0;

err_out:
 sky2_free_buffers(sky2);
 return err;
}

/* Modular subtraction in ring */
static inline int tx_inuse(const struct sky2_port *sky2)
{
 return (sky2->tx_prod - sky2->tx_cons) & (sky2->tx_ring_size - 1);
}

/* Number of list elements available for next tx */
static inline int tx_avail(const struct sky2_port *sky2)
{
 return sky2->tx_pending - tx_inuse(sky2);
}

/* Estimate of number of transmit list elements required */
static unsigned tx_le_req(const struct sk_buff *skb)
{
 unsigned count;

 count = (skb_shinfo(skb)->nr_frags + 1)
  * (sizeof(dma_addr_t) / sizeof(u32));

 if (skb_is_gso(skb))
  ++count;
 else if (sizeof(dma_addr_t) == sizeof(u32))
  ++count; /* possible vlan */

 if (skb->ip_summed == CHECKSUM_PARTIAL)
  ++count;

 return count;
}

static void sky2_tx_unmap(struct pci_dev *pdev, struct tx_ring_info *re)
{
 if (re->flags & TX_MAP_SINGLE)
  dma_unmap_single(&pdev->dev, dma_unmap_addr(re, mapaddr),
     dma_unmap_len(re, maplen), DMA_TO_DEVICE);
 else if (re->flags & TX_MAP_PAGE)
  dma_unmap_page(&pdev->dev, dma_unmap_addr(re, mapaddr),
          dma_unmap_len(re, maplen), DMA_TO_DEVICE);
 re->flags = 0;
}

/*
 * Put one packet in ring for transmit.
 * A single packet can generate multiple list elements, and
 * the number of ring elements will probably be less than the number
 * of list elements used.
 */

static netdev_tx_t sky2_xmit_frame(struct sk_buff *skb,
       struct net_device *dev)
{
 struct sky2_port *sky2 = netdev_priv(dev);
 struct sky2_hw *hw = sky2->hw;
 struct sky2_tx_le *le = NULL;
 struct tx_ring_info *re;
 unsigned i, len;
 dma_addr_t mapping;
 u32 upper;
 u16 slot;
 u16 mss;
 u8 ctrl;

 if (unlikely(tx_avail(sky2) < tx_le_req(skb)))
  return NETDEV_TX_BUSY;

 len = skb_headlen(skb);
 mapping = dma_map_single(&hw->pdev->dev, skb->data, len,
     DMA_TO_DEVICE);

 if (dma_mapping_error(&hw->pdev->dev, mapping))
  goto mapping_error;

 slot = sky2->tx_prod;
 netif_printk(sky2, tx_queued, KERN_DEBUG, dev,
       "tx queued, slot %u, len %d\n", slot, skb->len);

 /* Send high bits if needed */
 upper = upper_32_bits(mapping);
 if (upper != sky2->tx_last_upper) {
  le = get_tx_le(sky2, &slot);
  le->addr = cpu_to_le32(upper);
  sky2->tx_last_upper = upper;
  le->opcode = OP_ADDR64 | HW_OWNER;
 }

 /* Check for TCP Segmentation Offload */
 mss = skb_shinfo(skb)->gso_size;
 if (mss != 0) {

  if (!(hw->flags & SKY2_HW_NEW_LE))
   mss += skb_tcp_all_headers(skb);

  if (mss != sky2->tx_last_mss) {
   le = get_tx_le(sky2, &slot);
   le->addr = cpu_to_le32(mss);

   if (hw->flags & SKY2_HW_NEW_LE)
    le->opcode = OP_MSS | HW_OWNER;
   else
    le->opcode = OP_LRGLEN | HW_OWNER;
   sky2->tx_last_mss = mss;
  }
 }

 ctrl = 0;

 /* Add VLAN tag, can piggyback on LRGLEN or ADDR64 */
 if (skb_vlan_tag_present(skb)) {
  if (!le) {
   le = get_tx_le(sky2, &slot);
   le->addr = 0;
   le->opcode = OP_VLAN|HW_OWNER;
  } else
   le->opcode |= OP_VLAN;
  le->length = cpu_to_be16(skb_vlan_tag_get(skb));
  ctrl |= INS_VLAN;
 }

 /* Handle TCP checksum offload */
 if (skb->ip_summed == CHECKSUM_PARTIAL) {
  /* On Yukon EX (some versions) encoding change. */
  if (hw->flags & SKY2_HW_AUTO_TX_SUM)
   ctrl |= CALSUM; /* auto checksum */
  else {
   const unsigned offset = skb_transport_offset(skb);
   u32 tcpsum;

   tcpsum = offset << 16;   /* sum start */
   tcpsum |= offset + skb->csum_offset; /* sum write */

   ctrl |= CALSUM | WR_SUM | INIT_SUM | LOCK_SUM;
   if (ip_hdr(skb)->protocol == IPPROTO_UDP)
    ctrl |= UDPTCP;

   if (tcpsum != sky2->tx_tcpsum) {
    sky2->tx_tcpsum = tcpsum;

    le = get_tx_le(sky2, &slot);
    le->addr = cpu_to_le32(tcpsum);
    le->length = 0; /* initial checksum value */
    le->ctrl = 1; /* one packet */
    le->opcode = OP_TCPLISW | HW_OWNER;
   }
  }
 }

 re = sky2->tx_ring + slot;
 re->flags = TX_MAP_SINGLE;
 dma_unmap_addr_set(re, mapaddr, mapping);
 dma_unmap_len_set(re, maplen, len);

 le = get_tx_le(sky2, &slot);
 le->addr = cpu_to_le32(lower_32_bits(mapping));
 le->length = cpu_to_le16(len);
 le->ctrl = ctrl;
 le->opcode = mss ? (OP_LARGESEND | HW_OWNER) : (OP_PACKET | HW_OWNER);


 for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
  const skb_frag_t *frag = &skb_shinfo(skb)->frags[i];

  mapping = skb_frag_dma_map(&hw->pdev->dev, frag, 0,
        skb_frag_size(frag), DMA_TO_DEVICE);

  if (dma_mapping_error(&hw->pdev->dev, mapping))
   goto mapping_unwind;

  upper = upper_32_bits(mapping);
  if (upper != sky2->tx_last_upper) {
   le = get_tx_le(sky2, &slot);
   le->addr = cpu_to_le32(upper);
   sky2->tx_last_upper = upper;
   le->opcode = OP_ADDR64 | HW_OWNER;
  }

  re = sky2->tx_ring + slot;
  re->flags = TX_MAP_PAGE;
  dma_unmap_addr_set(re, mapaddr, mapping);
  dma_unmap_len_set(re, maplen, skb_frag_size(frag));

  le = get_tx_le(sky2, &slot);
  le->addr = cpu_to_le32(lower_32_bits(mapping));
  le->length = cpu_to_le16(skb_frag_size(frag));
  le->ctrl = ctrl;
  le->opcode = OP_BUFFER | HW_OWNER;
 }

 re->skb = skb;
 le->ctrl |= EOP;

 sky2->tx_prod = slot;

 if (tx_avail(sky2) <= MAX_SKB_TX_LE)
  netif_stop_queue(dev);

 netdev_sent_queue(dev, skb->len);
 sky2_put_idx(hw, txqaddr[sky2->port], sky2->tx_prod);

 return NETDEV_TX_OK;

mapping_unwind:
 for (i = sky2->tx_prod; i != slot; i = RING_NEXT(i, sky2->tx_ring_size)) {
  re = sky2->tx_ring + i;

  sky2_tx_unmap(hw->pdev, re);
 }

mapping_error:
 if (net_ratelimit())
  dev_warn(&hw->pdev->dev, "%s: tx mapping error\n", dev->name);
 dev_kfree_skb_any(skb);
 return NETDEV_TX_OK;
}

/*
 * Free ring elements from starting at tx_cons until "done"
 *
 * NB:
 *  1. The hardware will tell us about partial completion of multi-part
 *     buffers so make sure not to free skb to early.
 *  2. This may run in parallel start_xmit because the it only
 *     looks at the tail of the queue of FIFO (tx_cons), not
 *     the head (tx_prod)
 */

static void sky2_tx_complete(struct sky2_port *sky2, u16 done)
{
 struct net_device *dev = sky2->netdev;
 u16 idx;
 unsigned int bytes_compl = 0, pkts_compl = 0;

 BUG_ON(done >= sky2->tx_ring_size);

 for (idx = sky2->tx_cons; idx != done;
      idx = RING_NEXT(idx, sky2->tx_ring_size)) {
  struct tx_ring_info *re = sky2->tx_ring + idx;
  struct sk_buff *skb = re->skb;

  sky2_tx_unmap(sky2->hw->pdev, re);

  if (skb) {
   netif_printk(sky2, tx_done, KERN_DEBUG, dev,
         "tx done %u\n", idx);

   pkts_compl++;
   bytes_compl += skb->len;

   re->skb = NULL;
   dev_kfree_skb_any(skb);

   sky2->tx_next = RING_NEXT(idx, sky2->tx_ring_size);
  }
 }

 sky2->tx_cons = idx;
 smp_mb();

 netdev_completed_queue(dev, pkts_compl, bytes_compl);

 u64_stats_update_begin(&sky2->tx_stats.syncp);
 sky2->tx_stats.packets += pkts_compl;
 sky2->tx_stats.bytes += bytes_compl;
 u64_stats_update_end(&sky2->tx_stats.syncp);
}

static void sky2_tx_reset(struct sky2_hw *hw, unsigned port)
{
 /* Disable Force Sync bit and Enable Alloc bit */
 sky2_write8(hw, SK_REG(port, TXA_CTRL),
      TXA_DIS_FSYNC | TXA_DIS_ALLOC | TXA_STOP_RC);

 /* Stop Interval Timer and Limit Counter of Tx Arbiter */
 sky2_write32(hw, SK_REG(port, TXA_ITI_INI), 0L);
 sky2_write32(hw, SK_REG(port, TXA_LIM_INI), 0L);

 /* Reset the PCI FIFO of the async Tx queue */
 sky2_write32(hw, Q_ADDR(txqaddr[port], Q_CSR),
       BMU_RST_SET | BMU_FIFO_RST);

 /* Reset the Tx prefetch units */
 sky2_write32(hw, Y2_QADDR(txqaddr[port], PREF_UNIT_CTRL),
       PREF_UNIT_RST_SET);

 sky2_write32(hw, RB_ADDR(txqaddr[port], RB_CTRL), RB_RST_SET);
 sky2_write8(hw, SK_REG(port, TX_GMF_CTRL_T), GMF_RST_SET);

 sky2_read32(hw, B0_CTST);
}

static void sky2_hw_down(struct sky2_port *sky2)
{
 struct sky2_hw *hw = sky2->hw;
 unsigned port = sky2->port;
 u16 ctrl;

 /* Force flow control off */
 sky2_write8(hw, SK_REG(port, GMAC_CTRL), GMC_PAUSE_OFF);

 /* Stop transmitter */
 sky2_write32(hw, Q_ADDR(txqaddr[port], Q_CSR), BMU_STOP);
 sky2_read32(hw, Q_ADDR(txqaddr[port], Q_CSR));

 sky2_write32(hw, RB_ADDR(txqaddr[port], RB_CTRL),
       RB_RST_SET | RB_DIS_OP_MD);

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

 sky2_write8(hw, SK_REG(port, GPHY_CTRL), GPC_RST_SET);

 /* Workaround shared GMAC reset */
 if (!(hw->chip_id == CHIP_ID_YUKON_XL && hw->chip_rev == 0 &&
       port == 0 && hw->dev[1] && netif_running(hw->dev[1])))
  sky2_write8(hw, SK_REG(port, GMAC_CTRL), GMC_RST_SET);

 sky2_write8(hw, SK_REG(port, RX_GMF_CTRL_T), GMF_RST_SET);

 /* Force any delayed status interrupt and NAPI */
 sky2_write32(hw, STAT_LEV_TIMER_CNT, 0);
 sky2_write32(hw, STAT_TX_TIMER_CNT, 0);
 sky2_write32(hw, STAT_ISR_TIMER_CNT, 0);
 sky2_read8(hw, STAT_ISR_TIMER_CTRL);

 sky2_rx_stop(sky2);

 spin_lock_bh(&sky2->phy_lock);
 sky2_phy_power_down(hw, port);
 spin_unlock_bh(&sky2->phy_lock);

 sky2_tx_reset(hw, port);

 /* Free any pending frames stuck in HW queue */
 sky2_tx_complete(sky2, sky2->tx_prod);
}

/* Network shutdown */
static int sky2_close(struct net_device *dev)
{
 struct sky2_port *sky2 = netdev_priv(dev);
 struct sky2_hw *hw = sky2->hw;

 /* Never really got started! */
 if (!sky2->tx_le)
  return 0;

 netif_info(sky2, ifdown, dev, "disabling interface\n");

 if (hw->ports == 1) {
  sky2_write32(hw, B0_IMSK, 0);
  sky2_read32(hw, B0_IMSK);

  napi_disable(&hw->napi);
  free_irq(hw->pdev->irq, hw);
  hw->flags &= ~SKY2_HW_IRQ_SETUP;
 } else {
  u32 imask;

  /* Disable port IRQ */
  imask  = sky2_read32(hw, B0_IMSK);
  imask &= ~portirq_msk[sky2->port];
  sky2_write32(hw, B0_IMSK, imask);
  sky2_read32(hw, B0_IMSK);

  synchronize_irq(hw->pdev->irq);
  napi_synchronize(&hw->napi);
 }

 sky2_hw_down(sky2);

 sky2_free_buffers(sky2);

 return 0;
}

static u16 sky2_phy_speed(const struct sky2_hw *hw, u16 aux)
{
 if (hw->flags & SKY2_HW_FIBRE_PHY)
  return SPEED_1000;

 if (!(hw->flags & SKY2_HW_GIGABIT)) {
  if (aux & PHY_M_PS_SPEED_100)
   return SPEED_100;
  else
   return SPEED_10;
 }

 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 sky2_link_up(struct sky2_port *sky2)
{
 struct sky2_hw *hw = sky2->hw;
 unsigned port = sky2->port;
 static const char *fc_name[] = {
  [FC_NONE] = "none",
  [FC_TX]  = "tx",
  [FC_RX]  = "rx",
  [FC_BOTH] = "both",
 };

 sky2_set_ipg(sky2);

 sky2_enable_rx_tx(sky2);

 gm_phy_write(hw, port, PHY_MARV_INT_MASK, PHY_M_DEF_MSK);

 netif_carrier_on(sky2->netdev);

 mod_timer(&hw->watchdog_timer, jiffies + 1);

 /* Turn on link LED */
 sky2_write8(hw, SK_REG(port, LNK_LED_REG),
      LINKLED_ON | LINKLED_BLINK_OFF | LINKLED_LINKSYNC_OFF);

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

static void sky2_link_down(struct sky2_port *sky2)
{
 struct sky2_hw *hw = sky2->hw;
 unsigned port = sky2->port;
 u16 reg;

 gm_phy_write(hw, port, PHY_MARV_INT_MASK, 0);

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

 netif_carrier_off(sky2->netdev);

 /* Turn off link LED */
 sky2_write8(hw, SK_REG(port, LNK_LED_REG), LINKLED_OFF);

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

 sky2_phy_init(hw, port);
}

static enum flow_control sky2_flow(int rx, int tx)
{
 if (rx)
  return tx ? FC_BOTH : FC_RX;
 else
  return tx ? FC_TX : FC_NONE;
}

static int sky2_autoneg_done(struct sky2_port *sky2, u16 aux)
{
 struct sky2_hw *hw = sky2->hw;
 unsigned port = sky2->port;
 u16 advert, lpa;

 advert = gm_phy_read(hw, port, PHY_MARV_AUNE_ADV);
 lpa = gm_phy_read(hw, port, PHY_MARV_AUNE_LP);
 if (lpa & PHY_M_AN_RF) {
  netdev_err(sky2->netdev, "remote fault\n");
  return -1;
 }

 if (!(aux & PHY_M_PS_SPDUP_RES)) {
  netdev_err(sky2->netdev, "speed/duplex mismatch\n");
  return -1;
 }

 sky2->speed = sky2_phy_speed(hw, aux);
 sky2->duplex = (aux & PHY_M_PS_FULL_DUP) ? DUPLEX_FULL : DUPLEX_HALF;

 /* Since the pause result bits seem to in different positions on
 * different chips. look at registers.
 */

 if (hw->flags & SKY2_HW_FIBRE_PHY) {
  /* Shift for bits in fiber PHY */
  advert &= ~(ADVERTISE_PAUSE_CAP|ADVERTISE_PAUSE_ASYM);
  lpa &= ~(LPA_PAUSE_CAP|LPA_PAUSE_ASYM);

  if (advert & ADVERTISE_1000XPAUSE)
   advert |= ADVERTISE_PAUSE_CAP;
  if (advert & ADVERTISE_1000XPSE_ASYM)
   advert |= ADVERTISE_PAUSE_ASYM;
  if (lpa & LPA_1000XPAUSE)
   lpa |= LPA_PAUSE_CAP;
  if (lpa & LPA_1000XPAUSE_ASYM)
   lpa |= LPA_PAUSE_ASYM;
 }

 sky2->flow_status = FC_NONE;
 if (advert & ADVERTISE_PAUSE_CAP) {
  if (lpa & LPA_PAUSE_CAP)
   sky2->flow_status = FC_BOTH;
  else if (advert & ADVERTISE_PAUSE_ASYM)
   sky2->flow_status = FC_RX;
 } else if (advert & ADVERTISE_PAUSE_ASYM) {
  if ((lpa & LPA_PAUSE_CAP) && (lpa & LPA_PAUSE_ASYM))
   sky2->flow_status = FC_TX;
 }

 if (sky2->duplex == DUPLEX_HALF && sky2->speed < SPEED_1000 &&
     !(hw->chip_id == CHIP_ID_YUKON_EC_U || hw->chip_id == CHIP_ID_YUKON_EX))
  sky2->flow_status = FC_NONE;

 if (sky2->flow_status & FC_TX)
  sky2_write8(hw, SK_REG(port, GMAC_CTRL), GMC_PAUSE_ON);
 else
  sky2_write8(hw, SK_REG(port, GMAC_CTRL), GMC_PAUSE_OFF);

 return 0;
}

/* Interrupt from PHY */
static void sky2_phy_intr(struct sky2_hw *hw, unsigned port)
{
 struct net_device *dev = hw->dev[port];
 struct sky2_port *sky2 = netdev_priv(dev);
 u16 istatus, phystat;

 if (!netif_running(dev))
  return;

 spin_lock(&sky2->phy_lock);
 istatus = gm_phy_read(hw, port, PHY_MARV_INT_STAT);
--> --------------------

--> maximum size reached

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

Messung V0.5
C=89 H=89 G=88

¤ Dauer der Verarbeitung: 0.25 Sekunden  (vorverarbeitet)  ¤

*© 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.