diff options
-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) +{ + struct sky2_port *sky2 = netdev_priv(dev); + struct sky2_hw *hw = sky2->hw; + unsigned port = sky2->port; + u16 ctrl; + int i; + + if (netif_msg_ifdown(sky2)) + printk(KERN_INFO PFX "%s: disabling interface\n", dev->name); + + netif_stop_queue(dev); + + /* 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); + + /* 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_wr |