diff options
author | Stephen Hemminger <shemminger@osdl.org> | 2005-08-16 16:36:49 -0700 |
---|---|---|
committer | Jeff Garzik <jgarzik@pobox.com> | 2005-08-17 00:53:12 -0400 |
commit | cd28ab6a4e50a7601d22752aa7ce0c8197b10bdf (patch) | |
tree | 7030ba1742db21caebe491c9052f312e30c33628 /drivers/net | |
parent | 2ba84684e8cf6f980e4e95a2300f53a505eb794e (diff) |
[PATCH] sky2: new experimental Marvell Yukon2 driver
New driver for the Marvell Yukon2 Gigabit Ethernet chipset.
This driver is based on the skge driver, but using the logic
from the SysKonnect version of the sk98lin driver.
It should support all the Yukon2 chipsets that are available
in many current Intel and AMD motherboards.
The driver does support ethtool, tx and rx checksum, and
tcp segmentation offload. But it has only been tested for
a short while and is known to stop receiving under heavy load.
Signed-off-by: Stephen Hemminger <shemminger@osdl.org>
Signed-off-by: Jeff Garzik <jgarzik@pobox.com>
Diffstat (limited to 'drivers/net')
-rw-r--r-- | drivers/net/Kconfig | 20 | ||||
-rw-r--r-- | drivers/net/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/sky2.c | 2686 | ||||
-rw-r--r-- | drivers/net/sky2.h | 1935 |
4 files changed, 4641 insertions, 1 deletions
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 8a835eb5880..4d7489ae5e2 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -1932,7 +1932,25 @@ config SKGE It does not support the link failover and network management features that "portable" vendor supplied sk98lin driver does. - + + +config SKY2 + tristate "SysKonnect Yukon2 support (EXPERIMENTAL)" + depends on PCI && EXPERIMENTAL + select CRC32 + ---help--- + This driver support the Marvell Yukon 2 Gigabit Ethernet adapter. + + To compile this driver as a module, choose M here: the module + will be called sky2. This is recommended. + +config SKY2_EC_A1 + bool "Support old Yukon-EC A1 chipset" + depends on SKY2 + ---help--- + Include support for early revisions of the Yukon EC chipset + that required extra workarounds. If in doubt, say N. + config SK98LIN tristate "Marvell Yukon Chipset / SysKonnect SK-98xx Support" depends on PCI diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 63c6d1e6d4d..7e5c0d46d50 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -54,6 +54,7 @@ obj-$(CONFIG_TIGON3) += tg3.o obj-$(CONFIG_BNX2) += bnx2.o obj-$(CONFIG_TC35815) += tc35815.o obj-$(CONFIG_SKGE) += skge.o +obj-$(CONFIG_SKY2) += sky2.o obj-$(CONFIG_SK98LIN) += sk98lin/ obj-$(CONFIG_SKFP) += skfp/ obj-$(CONFIG_VIA_RHINE) += via-rhine.o diff --git a/drivers/net/sky2.c b/drivers/net/sky2.c new file mode 100644 index 00000000000..cfb2b41bc6b --- /dev/null +++ b/drivers/net/sky2.c @@ -0,0 +1,2686 @@ +/* + * 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> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * TODO + * - coalescing setting? + * - variable ring size? + * + * TOTEST + * - speed setting + * - power management + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/version.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/ethtool.h> +#include <linux/pci.h> +#include <linux/ip.h> +#include <linux/tcp.h> +#include <linux/in.h> +#include <linux/delay.h> +#include <linux/crc32.h> + +#include <asm/irq.h> + +#include "sky2.h" + +#define DRV_NAME "sky2" +#define DRV_VERSION "0.2" +#define PFX DRV_NAME " " + +/* + * 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. A transmit can require several elements; + * a receive requires one (or two if using 64 bit dma). + */ + +#ifdef CONFIG_SKY2_EC_A1 +#define is_ec_a1(hw) \ + ((hw)->chip_id == CHIP_ID_YUKON_EC && \ + (hw)->chip_rev == CHIP_REV_YU_EC_A1) +#else +#define is_ec_a1(hw) 0 +#endif + +#define RX_LE_SIZE 256 +#define MIN_RX_BUFFERS 8 +#define MAX_RX_BUFFERS 124 +#define RX_LE_BYTES (RX_LE_SIZE*sizeof(struct sky2_rx_le)) + +#define TX_RING_SIZE 256 // min 64 max 4096 +#define STATUS_RING_SIZE 1024 // pow2 > (2*Rx + Tx) +#define STATUS_LE_BYTES (STATUS_RING_SIZE*sizeof(struct sky2_status_le)) +#define ETH_JUMBO_MTU 9000 +#define TX_WATCHDOG (5 * HZ) +#define NAPI_WEIGHT 64 +#define PHY_RETRIES 1000 + +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 const struct pci_device_id sky2_id_table[] = { + { PCI_DEVICE(PCI_VENDOR_ID_SYSKONNECT, 0x9E00) }, + { PCI_DEVICE(PCI_VENDOR_ID_DLINK, 0x4b00) }, + { PCI_DEVICE(PCI_VENDOR_ID_DLINK, 0x4b01) }, + { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4340) }, + { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4341) }, + { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4342) }, + { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4343) }, + { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4344) }, + { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4345) }, + { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4346) }, + { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4347) }, + { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4350) }, + { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4351) }, + { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4360) }, + { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4361) }, + { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, 0x4362) }, + { 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 inline const char *chip_name(u8 chip_id) +{ + switch (chip_id) { + case CHIP_ID_GENESIS: + return "Genesis"; + case CHIP_ID_YUKON: + return "Yukon"; + case CHIP_ID_YUKON_LITE: + return "Yukon-Lite"; + case CHIP_ID_YUKON_LP: + return "Yukon-LP"; + case CHIP_ID_YUKON_XL: + return "Yukon-XL"; + case CHIP_ID_YUKON_EC: + return "Yukon-EC"; + case CHIP_ID_YUKON_FE: + return "Yukon-FE"; + default: + return "???"; + } +} + +static void 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++) { + udelay(1); + + if (!(gma_read16(hw, port, GM_SMI_CTRL) & GM_SMI_CT_BUSY)) + break; + } +} + +static u16 gm_phy_read(struct sky2_hw *hw, unsigned port, u16 reg) +{ + 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++) { + udelay(1); + if (gma_read16(hw, port, GM_SMI_CTRL) & GM_SMI_CT_RD_VAL) + goto ready; + } + + printk(KERN_WARNING PFX "%s: phy read timeout\n", + hw->dev[port]->name); + ready: + return gma_read16(hw, port, GM_SMI_DATA); +} + +static void sky2_phy_reset(struct sky2_hw *hw, unsigned port) +{ + u16 reg; + + /* disable all GMAC IRQ's */ + sky2_write8(hw, SK_REG(port, GMAC_IRQ_MSK), 0); + /* disable PHY IRQs */ + gm_phy_write(hw, port, PHY_MARV_INT_MASK, 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); +} + +static void sky2_phy_init(struct sky2_hw *hw, unsigned port) +{ + struct sky2_port *sky2 = netdev_priv(hw->dev[port]); + u16 ctrl, ct1000, adv; + u16 ledctrl, ledover; + + pr_debug("phy reset autoneg=%s advertising=0x%x pause rx=%s tx=%s\n", + sky2->autoneg == AUTONEG_ENABLE ? "enable" : "disable", + sky2->advertising, + sky2->rx_pause ? "on" : "off", + sky2->tx_pause ? "on" : "off"); + + if (sky2->autoneg == AUTONEG_ENABLE && + hw->chip_id != CHIP_ID_YUKON_XL) { + 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); + + if (hw->chip_id == CHIP_ID_YUKON_EC) + ectrl |= PHY_M_EC_DSC_2(2) | PHY_M_EC_DOWN_S_ENA; + else + ectrl |= PHY_M_EC_M_DSC(2) | PHY_M_EC_S_DSC(3); + + gm_phy_write(hw, port, PHY_MARV_EXT_CTRL, ectrl); + } + + ctrl = gm_phy_read(hw, port, PHY_MARV_PHY_CTRL); + if (hw->copper) { + if (hw->chip_id == CHIP_ID_YUKON_FE) { + /* enable automatic crossover */ + ctrl |= PHY_M_PC_MDI_XMODE(PHY_M_PC_ENA_AUTO) >> 1; + } 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); + + if (sky2->autoneg == AUTONEG_ENABLE && + hw->chip_id == CHIP_ID_YUKON_XL) { + ctrl &= ~PHY_M_PC_DSC_MSK; + ctrl |= PHY_M_PC_DSC(2) | PHY_M_PC_DOWN_S_ENA; + } + } + gm_phy_write(hw, port, PHY_MARV_PHY_CTRL, ctrl); + } 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); + + if (hw->chip_id == CHIP_ID_YUKON_XL) { + /* 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); + + /* select page 1 to access Fiber registers */ + gm_phy_write(hw, port, PHY_MARV_EXT_ADR, 1); + } + + ctrl &= ~(PHY_M_PC_MDIX_MSK | PHY_M_MAC_MD_MSK); + ctrl |= PHY_M_MAC_MODE_SEL(PHY_M_MAC_MD_1000BX); + } + + ctrl = gm_phy_read(hw, port, PHY_MARV_CTRL); + if (sky2->autoneg == AUTONEG_DISABLE) + ctrl &= ~PHY_CT_ANE; + else + 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 (sky2->autoneg == AUTONEG_ENABLE) { + if (hw->copper) { + 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 (88E1011S only) */ + adv |= PHY_M_AN_1000X_AHD | PHY_M_AN_1000X_AFD; + + /* Set Flow-control capabilities */ + if (sky2->tx_pause && sky2->rx_pause) + adv |= PHY_AN_PAUSE_CAP; /* symmetric */ + else if (sky2->rx_pause && !sky2->tx_pause) + adv |= PHY_AN_PAUSE_ASYM|PHY_AN_PAUSE_CAP; + else if (!sky2->rx_pause && sky2->tx_pause) + adv |= PHY_AN_PAUSE_ASYM; /* local */ + + /* Restart Auto-negotiation */ + ctrl |= PHY_CT_ANE | PHY_CT_RE_CFG; + } else { + /* forced speed/duplex settings */ + ct1000 = PHY_M_1000C_MSE; + + if (sky2->duplex == DUPLEX_FULL) + ctrl |= PHY_CT_DUP_MD; + + switch (sky2->speed) { + case SPEED_1000: + ctrl |= PHY_CT_SP1000; + break; + case SPEED_100: + ctrl |= PHY_CT_SP100; + break; + } + + ctrl |= PHY_CT_RESET; + } + + if (hw->chip_id != CHIP_ID_YUKON_FE) + 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_XL: + ctrl = 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, ctrl); + 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); + } + + gm_phy_write(hw, port, PHY_MARV_LED_CTRL, ledctrl); + + if (sky2->autoneg == AUTONEG_DISABLE || 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); + + /* Enable phy interrupt on autonegotiation complete (or link up) */ + if (sky2->autoneg == AUTONEG_ENABLE) + 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 void sky2_mac_init(struct sky2_hw *hw, unsigned port) +{ + struct sky2_port *sky2 = netdev_priv(hw->dev[port]); + u16 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 == 0 + && 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); + } + + + if (sky2->autoneg == AUTONEG_DISABLE) { + reg = gma_read16(hw, port, GM_GP_CTRL); + reg |= GM_GPCR_AU_ALL_DIS; + gma_write16(hw, port, GM_GP_CTRL, reg); + gma_read16(hw, port, GM_GP_CTRL); + + + switch (sky2->speed) { + case SPEED_1000: + reg |= GM_GPCR_SPEED_1000; + /* fallthru */ + case SPEED_100: + reg |= GM_GPCR_SPEED_100; + } + + if (sky2->duplex == DUPLEX_FULL) + reg |= GM_GPCR_DUP_FULL; + } else + reg = GM_GPCR_SPEED_1000 | GM_GPCR_SPEED_100 | GM_GPCR_DUP_FULL; + + if (!sky2->tx_pause && !sky2->rx_pause) { + sky2_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; + } else if (sky2->tx_pause &&!sky2->rx_pause) { + /* disable Rx flow-control */ + reg |= GM_GPCR_FC_RX_DIS | GM_GPCR_AU_FCT_DIS; + } + + gma_write16(hw, port, GM_GP_CTRL, reg); + + sky2_read16(hw, GMAC_IRQ_SRC); + + spin_lock_bh(&hw->phy_lock); + sky2_phy_init(hw, port); + spin_unlock_bh(&hw->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 = 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) | + 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); + + if (hw->dev[port]->mtu > 1500) + 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); + + /* Configure Rx MAC FIFO */ + sky2_write8(hw, SK_REG(port, RX_GMF_CTRL_T), GMF_RST_CLR); + sky2_write16(hw, SK_REG(port, RX_GMF_CTRL_T), + GMF_OPER_ON | GMF_RX_F_FL_ON); + + reg = RX_FF_FL_DEF_MSK; + if (hw->chip_id == CHIP_ID_YUKON_XL && hw->chip_rev <= 1) + reg = 0; /* WA Dev #4115 */ + + sky2_write16(hw, SK_REG(port, RX_GMF_FL_MSK), reg); + /* Set threshold to 0xa (64 bytes) + * ASF disabled so no need to do WA dev #4.30 + */ + sky2_write16(hw, SK_REG(port, RX_GMF_FL_THR), RX_GMF_FL_THR_DEF); + + /* 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); + + /* Turn off Rx fifo flush (per sk98lin) */ + sky2_write8(hw, SK_REG(port, RX_GMF_CTRL_T), GMF_RX_F_FL_OFF); +} + +static void sky2_ramset(struct sky2_hw *hw, u16 q, u32 start, size_t len) +{ + u32 end; + + start /= 8; + len /= 8; + end = start + len - 1; + pr_debug("ramset q=%d start=0x%x end=0x%x\n", q, start, end); + + 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) { + /* Set thresholds on receive queue's */ + sky2_write32(hw, RB_ADDR(q, RB_RX_UTPP), + start + (2*len)/3); + sky2_write32(hw, RB_ADDR(q, RB_RX_LTPP), + start + (len/3)); + } 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); +} + + +/* Setup Bus Memory Interface */ +static void sky2_qset(struct sky2_hw *hw, u16 q, u32 wm) +{ + 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), wm); +} + + +/* Setup prefetch unit registers. This is the interface between + * hardware and driver list elements + */ +static inline void sky2_prefetch_init(struct sky2_hw *hw, u32 qaddr, + u64 addr, u32 last) +{ + pr_debug("sky2 prefetch init q=%x addr=%llx last=%x\n", + Y2_QADDR(qaddr, 0), addr, 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), addr >> 32); + sky2_write32(hw, Y2_QADDR(qaddr, PREF_UNIT_ADDR_LO), (u32) 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); +} + + +/* + * This is a workaround code taken from syskonnect sk98lin driver + * to deal with chip bug in the wraparound case. + */ +static inline void sky2_put_idx(struct sky2_hw *hw, unsigned q, + u16 idx, u16 *last, u16 size) + +{ + BUG_ON(idx >= size); + + wmb(); + if (is_ec_a1(hw) && idx < *last) { + u16 hwget = sky2_read16(hw, Y2_QADDR(q, PREF_UNIT_GET_IDX)); + + if (hwget == 0) { + /* Start prefetching again */ + sky2_write8(hw, Y2_QADDR(q, PREF_UNIT_FIFO_WM), + 0xe0); + goto setnew; + } + + if (hwget == size-1) { + /* set watermark to one list element */ + sky2_write8(hw, Y2_QADDR(q, PREF_UNIT_FIFO_WM), 8); + + /* set put index to first list element */ + sky2_write16(hw, Y2_QADDR(q, PREF_UNIT_PUT_IDX), 0); + } else /* have hardware go to end of list */ + sky2_write16(hw, Y2_QADDR(q, PREF_UNIT_PUT_IDX), size-1); + } else { + setnew: + sky2_write16(hw, Y2_QADDR(q, PREF_UNIT_PUT_IDX), idx); + *last = 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 = (sky2->rx_put + 1) % RX_LE_SIZE; + return le; +} + +static inline void sky2_rx_add(struct sky2_port *sky2, dma_addr_t map, u16 len) +{ + struct sky2_rx_le *le; + + if (sizeof(map) > sizeof(u32)) { + le = sky2_next_rx(sky2); + le->rx.addr = cpu_to_le32((u64) map >> 32); + le->ctrl = 0; + le->opcode = OP_ADDR64 | HW_OWNER; + } + + le = sky2_next_rx(sky2); + le->rx.addr = cpu_to_le32((u32) map); + le->length = cpu_to_le16(len); + le->ctrl = 0; + le->opcode = OP_PACKET | HW_OWNER; +} + +/* Tell chip where to start receive checksum. + * Actually has two checksums, but set both same to avoid possible byte + * order problems. + */ +static void sky2_rx_set_offset(struct sky2_port *sky2) +{ + struct sky2_rx_le *le; + + sky2_write32(sky2->hw, + Q_ADDR(rxqaddr[sky2->port], Q_CSR), + sky2->rx_csum ? BMU_ENA_RX_CHKSUM : BMU_DIS_RX_CHKSUM); + + le = sky2_next_rx(sky2); + le->rx.csum.start1 = ETH_HLEN; + le->rx.csum.start2 = ETH_HLEN; + le->ctrl = 0; + le->opcode = OP_TCPSTART | HW_OWNER; + wmb(); + sky2_write16(sky2->hw, + Y2_QADDR(rxqaddr[sky2->port], PREF_UNIT_PUT_IDX), + sky2->rx_put); + +} + +/* Cleanout receive buffer area, assumes receiver hardware stopped */ +static void sky2_rx_clean(struct sky2_port *sky2) +{ + unsigned i; + + memset(sky2->rx_le, 0, RX_LE_BYTES); + for (i = 0; i < sky2->rx_ring_size; i++) { + struct ring_info *re = sky2->rx_ring + i; + + if (re->skb) { + pci_unmap_single(sky2->hw->pdev, + pci_unmap_addr(re, mapaddr), + pci_unmap_len(re, maplen), + PCI_DMA_FROMDEVICE); + kfree_skb(re->skb); + re->skb = NULL; + } + } +} + +static inline struct sk_buff *sky2_rx_alloc_skb(struct sky2_port *sky2, + unsigned int size, int gfp_mask) +{ + struct sk_buff *skb; + + skb = alloc_skb(size, gfp_mask); + if (likely(skb)) { + skb->dev = sky2->netdev; + skb_reserve(skb, NET_IP_ALIGN); + } + return skb; +} + +/* + * Allocate and setup receiver buffer pool. + * In case of 64 bit dma, there are 2X as many list elements + * available as ring entries + * and need to reserve one list element so we don't wrap around. + */ +static int sky2_rx_fill(struct sky2_port *sky2) +{ + unsigned i; + unsigned int rx_buf_size = sky2->netdev->mtu + ETH_HLEN + 8; + + pr_debug("sky2_rx_fill %d\n", sky2->rx_ring_size); + for (i = 0; i < sky2->rx_ring_size; i++) { + struct ring_info *re = sky2->rx_ring + i; + dma_addr_t paddr; + + re->skb = sky2_rx_alloc_skb(sky2, rx_buf_size, GFP_KERNEL); + if (!re->skb) + goto nomem; + + paddr = pci_map_single(sky2->hw->pdev, re->skb->data, + rx_buf_size, PCI_DMA_FROMDEVICE); + + pci_unmap_len_set(re, maplen, rx_buf_size); + pci_unmap_addr_set(re, mapaddr, paddr); + sky2_rx_add(sky2, paddr, rx_buf_size); + } + + sky2_write16(sky2->hw, + Y2_QADDR(rxqaddr[sky2->port], PREF_UNIT_PUT_IDX), + sky2->rx_put); + + return 0; +nomem: + sky2_rx_clean(sky2); + return -ENOMEM; +} + +/* Bring up network interface. */ +static int sky2_up(struct net_device *dev) +{ + struct sky2_port *sky2 = netdev_priv(dev); + struct sky2_hw *hw = sky2->hw; + unsigned port = sky2->port; + u32 ramsize, rxspace; + int err = -ENOMEM; + + if (netif_msg_ifup(sky2)) + printk(KERN_INFO PFX "%s: enabling interface\n", dev->name); + + /* must be power of 2 */ + sky2->tx_le = pci_alloc_consistent(hw->pdev, + TX_RING_SIZE * sizeof(struct sky2_tx_le), + &sky2->tx_le_map); + if (!sky2->tx_le) + goto err_out; + + sky2->tx_ring = kmalloc(TX_RING_SIZE * sizeof(struct ring_info), + GFP_KERNEL); + if (!sky2->tx_ring) + goto err_out; + sky2->tx_prod = sky2->tx_cons = 0; + memset(sky2->tx_ring, 0, TX_RING_SIZE * sizeof(struct ring_info)); + + sky2->rx_le = pci_alloc_consistent(hw->pdev, RX_LE_BYTES, + &sky2->rx_le_map); + if (!sky2->rx_le) + goto err_out; + memset(sky2->rx_le, 0, RX_LE_BYTES); + + sky2->rx_ring = kmalloc(sky2->rx_ring_size * sizeof(struct ring_info), + GFP_KERNEL); + if (!sky2->rx_ring) + goto err_out; + + sky2_mac_init(hw, port); + + /* Configure RAM buffers */ + if (hw->chip_id == CHIP_ID_YUKON_FE || + (hw->chip_id == CHIP_ID_YUKON_EC && hw->chip_rev == 2)) + ramsize = 4096; + else { + u8 e0 = sky2_read8(hw, B2_E_0); + ramsize = (e0 == 0) ? (128*1024) : (e0 * 4096); + } + + /* 2/3 for Rx */ + rxspace = (2 * ramsize) / 3; + sky2_ramset(hw, rxqaddr[port], 0, rxspace); + sky2_ramset(hw, txqaddr[port], rxspace, ramsize - rxspace); + + sky2_qset(hw, rxqaddr[port], is_pciex(hw) ? 0x80 : 0x600); + sky2_qset(hw, txqaddr[port], 0x600); + + sky2->rx_put = sky2->rx_next = 0; + sky2_prefetch_init(hw, rxqaddr[port], sky2->rx_le_map, RX_LE_SIZE-1); + + sky2_rx_set_offset(sky2); + + err = sky2_rx_fill(sky2); + if (err) + goto err_out; + + sky2_prefetch_init(hw, txqaddr[port], sky2->tx_le_map, + TX_RING_SIZE - 1); + + /* Enable interrupts from phy/mac for port */ + hw->intr_mask |= (port == 0) ? Y2_IS_PORT_1 : Y2_IS_PORT_2; + sky2_write32(hw, B0_IMSK, hw->intr_mask); + return 0; + +err_out: + if (sky2->rx_le) + pci_free_consistent(hw->pdev, RX_LE_BYTES, + sky2->rx_le, sky2->rx_le_map); + if (sky2->tx_le) + pci_free_consistent(hw->pdev, + TX_RING_SIZE * sizeof(struct sky2_tx_le), + sky2->tx_le, sky2->tx_le_map); + if (sky2->tx_ring) + kfree(sky2->tx_ring); + if (sky2->rx_ring) + kfree(sky2->rx_ring); + + return err; +} + +/* + * Worst case number of list elements is 36 + * TSO + CHKSUM + ADDR64 + BUFFER + (ADDR+BUFFER)*MAXFRAGS + */ +#define MAX_SKB_TX_LE (4 + 2*MAX_SKB_FRAGS) + +static inline int sky2_xmit_avail(const struct sky2_port *sky2) +{ + return (sky2->tx_cons > sky2->tx_prod ? 0 : TX_RING_SIZE) + + sky2->tx_cons - sky2->tx_prod - 1; +} + +static inline struct sky2_tx_le *get_tx_le(struct sky2_port *sky2) +{ + struct sky2_tx_le *le = sky2->tx_le + sky2->tx_prod; + sky2->tx_prod = (sky2->tx_prod + 1) % TX_RING_SIZE; + return le; +} + +/* Put one frame in ring for transmit. */ +static int 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; + struct ring_info *re; + unsigned i, len; + dma_addr_t mapping; + u32 addr64; + u16 mss; + u8 ctrl; + + skb = skb_padto(skb, ETH_ZLEN); + if (!skb) + return NETDEV_TX_OK; + + if (!spin_trylock(&sky2->tx_lock)) + return NETDEV_TX_LOCKED; + + if (unlikely(sky2_xmit_avail(sky2) < MAX_SKB_TX_LE)) { + netif_stop_queue(dev); + spin_unlock(&sky2->tx_lock); + + printk(KERN_WARNING PFX "%s: ring full when queue awake!\n", + dev->name); + return NETDEV_TX_BUSY; + } + + if (netif_msg_tx_queued(sky2)) + printk(KERN_DEBUG "%s: tx queued, slot %u, len %d\n", + dev->name, sky2->tx_prod, skb->len); + + + len = skb_headlen(skb); + mapping = pci_map_single(hw->pdev, skb->data, len, PCI_DMA_TODEVICE); + + /* Check for TCP Segmentation Offload */ + mss = skb_shinfo(skb)->tso_size; + if (mss) { + /* just drop the packet if non-linear expansion fails */ + if (skb_header_cloned(skb) && + pskb_expand_head(skb, 0, 0, GFP_ATOMIC)) { + dev_kfree_skb(skb); + return NETDEV_TX_OK; + } + + mss += ((skb->h.th->doff - 5) * 4); /* TCP options */ + mss += (skb->nh.iph->ihl * 4) + sizeof(struct tcphdr); + mss += ETH_HLEN; + + le = get_tx_le(sky2); + le->tx.tso.size = cpu_to_le16(mss); + le->ctrl = 0; + le->opcode = OP_LRGLEN | HW_OWNER; + } + + /* Handle Hi DMA */ + if (sizeof(mapping) > sizeof(u32)) { + addr64 = (u64)mapping >> 32; + + le = get_tx_le(sky2); + le->tx.addr = cpu_to_le32(addr64); + le->ctrl = 0; + le->opcode = OP_ADDR64 | HW_OWNER; + } + + /* Handle TCP checksum offload */ + ctrl = 0; + if (skb->ip_summed == CHECKSUM_HW) { + ptrdiff_t hdr = skb->h.raw - skb->data; + + ctrl = CALSUM | WR_SUM | INIT_SUM | LOCK_SUM; + if (skb->nh.iph->protocol == IPPROTO_UDP) + ctrl |= UDPTCP; + + le = get_tx_le(sky2); + le->tx.csum.start = cpu_to_le16(hdr); + le->tx.csum.offset = cpu_to_le16(hdr + skb->csum); + le->length = 0; + le->ctrl = 1; /* one packet */ + le->opcode = OP_TCPLISW|HW_OWNER; + } + + le = get_tx_le(sky2); + le->tx.addr = cpu_to_le32((u32) mapping); + le->length = cpu_to_le16(len); + le->ctrl = ctrl; + le->opcode = (mss ? OP_LARGESEND : OP_PACKET) |HW_OWNER; + + re = &sky2->tx_ring[le - sky2->tx_le]; + re->skb = skb; + pci_unmap_addr_set(re, mapaddr, mapping); + pci_unmap_len_set(re, maplen, len); + + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; + + mapping = pci_map_page(hw->pdev, frag->page, frag->page_offset, + frag->size, PCI_DMA_TODEVICE); + + if (sizeof(mapping) > sizeof(u32)) { + u32 hi = (u64) mapping >> 32; + if (hi != addr64) { + le = get_tx_le(sky2); + le->tx.addr = cpu_to_le32(hi); + le->ctrl = 0; + le->opcode = OP_ADDR64|HW_OWNER; + addr64 = hi; + } + } + + le = get_tx_le(sky2); + le->tx.addr = cpu_to_le32((u32) mapping); + le->length = cpu_to_le16(frag->size); + le->ctrl = ctrl; + le->opcode = OP_BUFFER|HW_OWNER; + + re = &sky2->tx_ring[le - sky2->tx_le]; + pci_unmap_addr_set(re, mapaddr, mapping); + pci_unmap_len_set(re, maplen, frag->size); + } + + le->ctrl |= EOP; + + sky2_put_idx(sky2->hw, txqaddr[sky2->port], sky2->tx_prod, + &sky2->tx_last_put, TX_RING_SIZE); + + if (sky2_xmit_avail(sky2) < MAX_SKB_TX_LE) { + pr_debug("%s: transmit queue full\n", dev->name); + netif_stop_queue(dev); + } + spin_unlock(&sky2->tx_lock); + + dev->trans_start = jiffies; + return NETDEV_TX_OK; +} + + +/* + * Free ring elements from starting at tx_cons until done + * This unwinds the elements based on the usage assigned + * xmit routine. + */ +static void sky2_tx_complete(struct net_device *dev, u16 done) +{ + struct sky2_port *sky2 = netdev_priv(dev); + unsigned idx = sky2->tx_cons; + struct sk_buff *skb = NULL; + + BUG_ON(done >= TX_RING_SIZE); + + spin_lock(&sky2->tx_lock); + while (idx != done) { + struct ring_info *re = sky2->tx_ring + idx; + struct sky2_tx_le *le = sky2->tx_le + idx; + + BUG_ON(le->opcode == 0); + + switch(le->opcode & ~HW_OWNER) { + case OP_LARGESEND: + case OP_PACKET: + if (skb) + dev_kfree_skb_any(skb); + skb = re->skb; + BUG_ON(!skb); + re->skb = NULL; + + pci_unmap_single(sky2->hw->pdev, + pci_unmap_addr(re, mapaddr), + pci_unmap_len(re, maplen), + PCI_DMA_TODEVICE); + break; + + case OP_BUFFER: + pci_unmap_page(sky2->hw->pdev, + pci_unmap_addr(re, mapaddr), + pci_unmap_len(re, maplen), + PCI_DMA_TODEVICE); + break; + } + + le->opcode = 0; + idx = (idx + 1) % TX_RING_SIZE; + } + + if (skb) + dev_kfree_skb_any(skb); + sky2->tx_cons = idx; + + if (sky2_xmit_avail(sky2) > MAX_SKB_TX_LE) + netif_wake_queue(dev); + spin_unlock(&sky2->tx_lock); +} + +/* Cleanup all untransmitted buffers, assume transmitter not running */ +static inline void sky2_tx_clean(struct sky2_port *sky2) +{ + sky2_tx_complete(sky2->netdev, sky2->tx_prod); +} + +/* Network shutdown */ +static int sky2_down(struct net_device *dev) +{ |