blob: 652eb05a6c2df6c7e406eef8ecbfa82cfcb03564 [file] [log] [blame]
/* bnx2.c: Broadcom NX2 network driver.
*
* Copyright (c) 2004, 2005, 2006 Broadcom Corporation
*
* 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.
*
* Written by: Michael Chan (mchan@broadcom.com)
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/errno.h>
#include <linux/ioport.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/interrupt.h>
#include <linux/pci.h>
#include <linux/init.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/dma-mapping.h>
#include <asm/bitops.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <linux/delay.h>
#include <asm/byteorder.h>
#include <asm/page.h>
#include <linux/time.h>
#include <linux/ethtool.h>
#include <linux/mii.h>
#ifdef NETIF_F_HW_VLAN_TX
#include <linux/if_vlan.h>
#define BCM_VLAN 1
#endif
#ifdef NETIF_F_TSO
#include <net/ip.h>
#include <net/tcp.h>
#include <net/checksum.h>
#define BCM_TSO 1
#endif
#include <linux/workqueue.h>
#include <linux/crc32.h>
#include <linux/prefetch.h>
#include <linux/cache.h>
#include <linux/zlib.h>
#include "bnx2.h"
#include "bnx2_fw.h"
#define DRV_MODULE_NAME "bnx2"
#define PFX DRV_MODULE_NAME ": "
#define DRV_MODULE_VERSION "1.4.44"
#define DRV_MODULE_RELDATE "August 10, 2006"
#define RUN_AT(x) (jiffies + (x))
/* Time in jiffies before concluding the transmitter is hung. */
#define TX_TIMEOUT (5*HZ)
static const char version[] __devinitdata =
"Broadcom NetXtreme II Gigabit Ethernet Driver " DRV_MODULE_NAME " v" DRV_MODULE_VERSION " (" DRV_MODULE_RELDATE ")\n";
MODULE_AUTHOR("Michael Chan <mchan@broadcom.com>");
MODULE_DESCRIPTION("Broadcom NetXtreme II BCM5706/5708 Driver");
MODULE_LICENSE("GPL");
MODULE_VERSION(DRV_MODULE_VERSION);
static int disable_msi = 0;
module_param(disable_msi, int, 0);
MODULE_PARM_DESC(disable_msi, "Disable Message Signaled Interrupt (MSI)");
typedef enum {
BCM5706 = 0,
NC370T,
NC370I,
BCM5706S,
NC370F,
BCM5708,
BCM5708S,
} board_t;
/* indexed by board_t, above */
static const struct {
char *name;
} board_info[] __devinitdata = {
{ "Broadcom NetXtreme II BCM5706 1000Base-T" },
{ "HP NC370T Multifunction Gigabit Server Adapter" },
{ "HP NC370i Multifunction Gigabit Server Adapter" },
{ "Broadcom NetXtreme II BCM5706 1000Base-SX" },
{ "HP NC370F Multifunction Gigabit Server Adapter" },
{ "Broadcom NetXtreme II BCM5708 1000Base-T" },
{ "Broadcom NetXtreme II BCM5708 1000Base-SX" },
};
static struct pci_device_id bnx2_pci_tbl[] = {
{ PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_5706,
PCI_VENDOR_ID_HP, 0x3101, 0, 0, NC370T },
{ PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_5706,
PCI_VENDOR_ID_HP, 0x3106, 0, 0, NC370I },
{ PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_5706,
PCI_ANY_ID, PCI_ANY_ID, 0, 0, BCM5706 },
{ PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_5708,
PCI_ANY_ID, PCI_ANY_ID, 0, 0, BCM5708 },
{ PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_5706S,
PCI_VENDOR_ID_HP, 0x3102, 0, 0, NC370F },
{ PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_5706S,
PCI_ANY_ID, PCI_ANY_ID, 0, 0, BCM5706S },
{ PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_5708S,
PCI_ANY_ID, PCI_ANY_ID, 0, 0, BCM5708S },
{ 0, }
};
static struct flash_spec flash_table[] =
{
/* Slow EEPROM */
{0x00000000, 0x40830380, 0x009f0081, 0xa184a053, 0xaf000400,
1, SEEPROM_PAGE_BITS, SEEPROM_PAGE_SIZE,
SEEPROM_BYTE_ADDR_MASK, SEEPROM_TOTAL_SIZE,
"EEPROM - slow"},
/* Expansion entry 0001 */
{0x08000002, 0x4b808201, 0x00050081, 0x03840253, 0xaf020406,
0, SAIFUN_FLASH_PAGE_BITS, SAIFUN_FLASH_PAGE_SIZE,
SAIFUN_FLASH_BYTE_ADDR_MASK, 0,
"Entry 0001"},
/* Saifun SA25F010 (non-buffered flash) */
/* strap, cfg1, & write1 need updates */
{0x04000001, 0x47808201, 0x00050081, 0x03840253, 0xaf020406,
0, SAIFUN_FLASH_PAGE_BITS, SAIFUN_FLASH_PAGE_SIZE,
SAIFUN_FLASH_BYTE_ADDR_MASK, SAIFUN_FLASH_BASE_TOTAL_SIZE*2,
"Non-buffered flash (128kB)"},
/* Saifun SA25F020 (non-buffered flash) */
/* strap, cfg1, & write1 need updates */
{0x0c000003, 0x4f808201, 0x00050081, 0x03840253, 0xaf020406,
0, SAIFUN_FLASH_PAGE_BITS, SAIFUN_FLASH_PAGE_SIZE,
SAIFUN_FLASH_BYTE_ADDR_MASK, SAIFUN_FLASH_BASE_TOTAL_SIZE*4,
"Non-buffered flash (256kB)"},
/* Expansion entry 0100 */
{0x11000000, 0x53808201, 0x00050081, 0x03840253, 0xaf020406,
0, SAIFUN_FLASH_PAGE_BITS, SAIFUN_FLASH_PAGE_SIZE,
SAIFUN_FLASH_BYTE_ADDR_MASK, 0,
"Entry 0100"},
/* Entry 0101: ST M45PE10 (non-buffered flash, TetonII B0) */
{0x19000002, 0x5b808201, 0x000500db, 0x03840253, 0xaf020406,
0, ST_MICRO_FLASH_PAGE_BITS, ST_MICRO_FLASH_PAGE_SIZE,
ST_MICRO_FLASH_BYTE_ADDR_MASK, ST_MICRO_FLASH_BASE_TOTAL_SIZE*2,
"Entry 0101: ST M45PE10 (128kB non-bufferred)"},
/* Entry 0110: ST M45PE20 (non-buffered flash)*/
{0x15000001, 0x57808201, 0x000500db, 0x03840253, 0xaf020406,
0, ST_MICRO_FLASH_PAGE_BITS, ST_MICRO_FLASH_PAGE_SIZE,
ST_MICRO_FLASH_BYTE_ADDR_MASK, ST_MICRO_FLASH_BASE_TOTAL_SIZE*4,
"Entry 0110: ST M45PE20 (256kB non-bufferred)"},
/* Saifun SA25F005 (non-buffered flash) */
/* strap, cfg1, & write1 need updates */
{0x1d000003, 0x5f808201, 0x00050081, 0x03840253, 0xaf020406,
0, SAIFUN_FLASH_PAGE_BITS, SAIFUN_FLASH_PAGE_SIZE,
SAIFUN_FLASH_BYTE_ADDR_MASK, SAIFUN_FLASH_BASE_TOTAL_SIZE,
"Non-buffered flash (64kB)"},
/* Fast EEPROM */
{0x22000000, 0x62808380, 0x009f0081, 0xa184a053, 0xaf000400,
1, SEEPROM_PAGE_BITS, SEEPROM_PAGE_SIZE,
SEEPROM_BYTE_ADDR_MASK, SEEPROM_TOTAL_SIZE,
"EEPROM - fast"},
/* Expansion entry 1001 */
{0x2a000002, 0x6b808201, 0x00050081, 0x03840253, 0xaf020406,
0, SAIFUN_FLASH_PAGE_BITS, SAIFUN_FLASH_PAGE_SIZE,
SAIFUN_FLASH_BYTE_ADDR_MASK, 0,
"Entry 1001"},
/* Expansion entry 1010 */
{0x26000001, 0x67808201, 0x00050081, 0x03840253, 0xaf020406,
0, SAIFUN_FLASH_PAGE_BITS, SAIFUN_FLASH_PAGE_SIZE,
SAIFUN_FLASH_BYTE_ADDR_MASK, 0,
"Entry 1010"},
/* ATMEL AT45DB011B (buffered flash) */
{0x2e000003, 0x6e808273, 0x00570081, 0x68848353, 0xaf000400,
1, BUFFERED_FLASH_PAGE_BITS, BUFFERED_FLASH_PAGE_SIZE,
BUFFERED_FLASH_BYTE_ADDR_MASK, BUFFERED_FLASH_TOTAL_SIZE,
"Buffered flash (128kB)"},
/* Expansion entry 1100 */
{0x33000000, 0x73808201, 0x00050081, 0x03840253, 0xaf020406,
0, SAIFUN_FLASH_PAGE_BITS, SAIFUN_FLASH_PAGE_SIZE,
SAIFUN_FLASH_BYTE_ADDR_MASK, 0,
"Entry 1100"},
/* Expansion entry 1101 */
{0x3b000002, 0x7b808201, 0x00050081, 0x03840253, 0xaf020406,
0, SAIFUN_FLASH_PAGE_BITS, SAIFUN_FLASH_PAGE_SIZE,
SAIFUN_FLASH_BYTE_ADDR_MASK, 0,
"Entry 1101"},
/* Ateml Expansion entry 1110 */
{0x37000001, 0x76808273, 0x00570081, 0x68848353, 0xaf000400,
1, BUFFERED_FLASH_PAGE_BITS, BUFFERED_FLASH_PAGE_SIZE,
BUFFERED_FLASH_BYTE_ADDR_MASK, 0,
"Entry 1110 (Atmel)"},
/* ATMEL AT45DB021B (buffered flash) */
{0x3f000003, 0x7e808273, 0x00570081, 0x68848353, 0xaf000400,
1, BUFFERED_FLASH_PAGE_BITS, BUFFERED_FLASH_PAGE_SIZE,
BUFFERED_FLASH_BYTE_ADDR_MASK, BUFFERED_FLASH_TOTAL_SIZE*2,
"Buffered flash (256kB)"},
};
MODULE_DEVICE_TABLE(pci, bnx2_pci_tbl);
static inline u32 bnx2_tx_avail(struct bnx2 *bp)
{
u32 diff;
smp_mb();
diff = TX_RING_IDX(bp->tx_prod) - TX_RING_IDX(bp->tx_cons);
if (diff > MAX_TX_DESC_CNT)
diff = (diff & MAX_TX_DESC_CNT) - 1;
return (bp->tx_ring_size - diff);
}
static u32
bnx2_reg_rd_ind(struct bnx2 *bp, u32 offset)
{
REG_WR(bp, BNX2_PCICFG_REG_WINDOW_ADDRESS, offset);
return (REG_RD(bp, BNX2_PCICFG_REG_WINDOW));
}
static void
bnx2_reg_wr_ind(struct bnx2 *bp, u32 offset, u32 val)
{
REG_WR(bp, BNX2_PCICFG_REG_WINDOW_ADDRESS, offset);
REG_WR(bp, BNX2_PCICFG_REG_WINDOW, val);
}
static void
bnx2_ctx_wr(struct bnx2 *bp, u32 cid_addr, u32 offset, u32 val)
{
offset += cid_addr;
REG_WR(bp, BNX2_CTX_DATA_ADR, offset);
REG_WR(bp, BNX2_CTX_DATA, val);
}
static int
bnx2_read_phy(struct bnx2 *bp, u32 reg, u32 *val)
{
u32 val1;
int i, ret;
if (bp->phy_flags & PHY_INT_MODE_AUTO_POLLING_FLAG) {
val1 = REG_RD(bp, BNX2_EMAC_MDIO_MODE);
val1 &= ~BNX2_EMAC_MDIO_MODE_AUTO_POLL;
REG_WR(bp, BNX2_EMAC_MDIO_MODE, val1);
REG_RD(bp, BNX2_EMAC_MDIO_MODE);
udelay(40);
}
val1 = (bp->phy_addr << 21) | (reg << 16) |
BNX2_EMAC_MDIO_COMM_COMMAND_READ | BNX2_EMAC_MDIO_COMM_DISEXT |
BNX2_EMAC_MDIO_COMM_START_BUSY;
REG_WR(bp, BNX2_EMAC_MDIO_COMM, val1);
for (i = 0; i < 50; i++) {
udelay(10);
val1 = REG_RD(bp, BNX2_EMAC_MDIO_COMM);
if (!(val1 & BNX2_EMAC_MDIO_COMM_START_BUSY)) {
udelay(5);
val1 = REG_RD(bp, BNX2_EMAC_MDIO_COMM);
val1 &= BNX2_EMAC_MDIO_COMM_DATA;
break;
}
}
if (val1 & BNX2_EMAC_MDIO_COMM_START_BUSY) {
*val = 0x0;
ret = -EBUSY;
}
else {
*val = val1;
ret = 0;
}
if (bp->phy_flags & PHY_INT_MODE_AUTO_POLLING_FLAG) {
val1 = REG_RD(bp, BNX2_EMAC_MDIO_MODE);
val1 |= BNX2_EMAC_MDIO_MODE_AUTO_POLL;
REG_WR(bp, BNX2_EMAC_MDIO_MODE, val1);
REG_RD(bp, BNX2_EMAC_MDIO_MODE);
udelay(40);
}
return ret;
}
static int
bnx2_write_phy(struct bnx2 *bp, u32 reg, u32 val)
{
u32 val1;
int i, ret;
if (bp->phy_flags & PHY_INT_MODE_AUTO_POLLING_FLAG) {
val1 = REG_RD(bp, BNX2_EMAC_MDIO_MODE);
val1 &= ~BNX2_EMAC_MDIO_MODE_AUTO_POLL;
REG_WR(bp, BNX2_EMAC_MDIO_MODE, val1);
REG_RD(bp, BNX2_EMAC_MDIO_MODE);
udelay(40);
}
val1 = (bp->phy_addr << 21) | (reg << 16) | val |
BNX2_EMAC_MDIO_COMM_COMMAND_WRITE |
BNX2_EMAC_MDIO_COMM_START_BUSY | BNX2_EMAC_MDIO_COMM_DISEXT;
REG_WR(bp, BNX2_EMAC_MDIO_COMM, val1);
for (i = 0; i < 50; i++) {
udelay(10);
val1 = REG_RD(bp, BNX2_EMAC_MDIO_COMM);
if (!(val1 & BNX2_EMAC_MDIO_COMM_START_BUSY)) {
udelay(5);
break;
}
}
if (val1 & BNX2_EMAC_MDIO_COMM_START_BUSY)
ret = -EBUSY;
else
ret = 0;
if (bp->phy_flags & PHY_INT_MODE_AUTO_POLLING_FLAG) {
val1 = REG_RD(bp, BNX2_EMAC_MDIO_MODE);
val1 |= BNX2_EMAC_MDIO_MODE_AUTO_POLL;
REG_WR(bp, BNX2_EMAC_MDIO_MODE, val1);
REG_RD(bp, BNX2_EMAC_MDIO_MODE);
udelay(40);
}
return ret;
}
static void
bnx2_disable_int(struct bnx2 *bp)
{
REG_WR(bp, BNX2_PCICFG_INT_ACK_CMD,
BNX2_PCICFG_INT_ACK_CMD_MASK_INT);
REG_RD(bp, BNX2_PCICFG_INT_ACK_CMD);
}
static void
bnx2_enable_int(struct bnx2 *bp)
{
REG_WR(bp, BNX2_PCICFG_INT_ACK_CMD,
BNX2_PCICFG_INT_ACK_CMD_INDEX_VALID |
BNX2_PCICFG_INT_ACK_CMD_MASK_INT | bp->last_status_idx);
REG_WR(bp, BNX2_PCICFG_INT_ACK_CMD,
BNX2_PCICFG_INT_ACK_CMD_INDEX_VALID | bp->last_status_idx);
REG_WR(bp, BNX2_HC_COMMAND, bp->hc_cmd | BNX2_HC_COMMAND_COAL_NOW);
}
static void
bnx2_disable_int_sync(struct bnx2 *bp)
{
atomic_inc(&bp->intr_sem);
bnx2_disable_int(bp);
synchronize_irq(bp->pdev->irq);
}
static void
bnx2_netif_stop(struct bnx2 *bp)
{
bnx2_disable_int_sync(bp);
if (netif_running(bp->dev)) {
netif_poll_disable(bp->dev);
netif_tx_disable(bp->dev);
bp->dev->trans_start = jiffies; /* prevent tx timeout */
}
}
static void
bnx2_netif_start(struct bnx2 *bp)
{
if (atomic_dec_and_test(&bp->intr_sem)) {
if (netif_running(bp->dev)) {
netif_wake_queue(bp->dev);
netif_poll_enable(bp->dev);
bnx2_enable_int(bp);
}
}
}
static void
bnx2_free_mem(struct bnx2 *bp)
{
int i;
if (bp->status_blk) {
pci_free_consistent(bp->pdev, bp->status_stats_size,
bp->status_blk, bp->status_blk_mapping);
bp->status_blk = NULL;
bp->stats_blk = NULL;
}
if (bp->tx_desc_ring) {
pci_free_consistent(bp->pdev,
sizeof(struct tx_bd) * TX_DESC_CNT,
bp->tx_desc_ring, bp->tx_desc_mapping);
bp->tx_desc_ring = NULL;
}
kfree(bp->tx_buf_ring);
bp->tx_buf_ring = NULL;
for (i = 0; i < bp->rx_max_ring; i++) {
if (bp->rx_desc_ring[i])
pci_free_consistent(bp->pdev,
sizeof(struct rx_bd) * RX_DESC_CNT,
bp->rx_desc_ring[i],
bp->rx_desc_mapping[i]);
bp->rx_desc_ring[i] = NULL;
}
vfree(bp->rx_buf_ring);
bp->rx_buf_ring = NULL;
}
static int
bnx2_alloc_mem(struct bnx2 *bp)
{
int i, status_blk_size;
bp->tx_buf_ring = kzalloc(sizeof(struct sw_bd) * TX_DESC_CNT,
GFP_KERNEL);
if (bp->tx_buf_ring == NULL)
return -ENOMEM;
bp->tx_desc_ring = pci_alloc_consistent(bp->pdev,
sizeof(struct tx_bd) *
TX_DESC_CNT,
&bp->tx_desc_mapping);
if (bp->tx_desc_ring == NULL)
goto alloc_mem_err;
bp->rx_buf_ring = vmalloc(sizeof(struct sw_bd) * RX_DESC_CNT *
bp->rx_max_ring);
if (bp->rx_buf_ring == NULL)
goto alloc_mem_err;
memset(bp->rx_buf_ring, 0, sizeof(struct sw_bd) * RX_DESC_CNT *
bp->rx_max_ring);
for (i = 0; i < bp->rx_max_ring; i++) {
bp->rx_desc_ring[i] =
pci_alloc_consistent(bp->pdev,
sizeof(struct rx_bd) * RX_DESC_CNT,
&bp->rx_desc_mapping[i]);
if (bp->rx_desc_ring[i] == NULL)
goto alloc_mem_err;
}
/* Combine status and statistics blocks into one allocation. */
status_blk_size = L1_CACHE_ALIGN(sizeof(struct status_block));
bp->status_stats_size = status_blk_size +
sizeof(struct statistics_block);
bp->status_blk = pci_alloc_consistent(bp->pdev, bp->status_stats_size,
&bp->status_blk_mapping);
if (bp->status_blk == NULL)
goto alloc_mem_err;
memset(bp->status_blk, 0, bp->status_stats_size);
bp->stats_blk = (void *) ((unsigned long) bp->status_blk +
status_blk_size);
bp->stats_blk_mapping = bp->status_blk_mapping + status_blk_size;
return 0;
alloc_mem_err:
bnx2_free_mem(bp);
return -ENOMEM;
}
static void
bnx2_report_fw_link(struct bnx2 *bp)
{
u32 fw_link_status = 0;
if (bp->link_up) {
u32 bmsr;
switch (bp->line_speed) {
case SPEED_10:
if (bp->duplex == DUPLEX_HALF)
fw_link_status = BNX2_LINK_STATUS_10HALF;
else
fw_link_status = BNX2_LINK_STATUS_10FULL;
break;
case SPEED_100:
if (bp->duplex == DUPLEX_HALF)
fw_link_status = BNX2_LINK_STATUS_100HALF;
else
fw_link_status = BNX2_LINK_STATUS_100FULL;
break;
case SPEED_1000:
if (bp->duplex == DUPLEX_HALF)
fw_link_status = BNX2_LINK_STATUS_1000HALF;
else
fw_link_status = BNX2_LINK_STATUS_1000FULL;
break;
case SPEED_2500:
if (bp->duplex == DUPLEX_HALF)
fw_link_status = BNX2_LINK_STATUS_2500HALF;
else
fw_link_status = BNX2_LINK_STATUS_2500FULL;
break;
}
fw_link_status |= BNX2_LINK_STATUS_LINK_UP;
if (bp->autoneg) {
fw_link_status |= BNX2_LINK_STATUS_AN_ENABLED;
bnx2_read_phy(bp, MII_BMSR, &bmsr);
bnx2_read_phy(bp, MII_BMSR, &bmsr);
if (!(bmsr & BMSR_ANEGCOMPLETE) ||
bp->phy_flags & PHY_PARALLEL_DETECT_FLAG)
fw_link_status |= BNX2_LINK_STATUS_PARALLEL_DET;
else
fw_link_status |= BNX2_LINK_STATUS_AN_COMPLETE;
}
}
else
fw_link_status = BNX2_LINK_STATUS_LINK_DOWN;
REG_WR_IND(bp, bp->shmem_base + BNX2_LINK_STATUS, fw_link_status);
}
static void
bnx2_report_link(struct bnx2 *bp)
{
if (bp->link_up) {
netif_carrier_on(bp->dev);
printk(KERN_INFO PFX "%s NIC Link is Up, ", bp->dev->name);
printk("%d Mbps ", bp->line_speed);
if (bp->duplex == DUPLEX_FULL)
printk("full duplex");
else
printk("half duplex");
if (bp->flow_ctrl) {
if (bp->flow_ctrl & FLOW_CTRL_RX) {
printk(", receive ");
if (bp->flow_ctrl & FLOW_CTRL_TX)
printk("& transmit ");
}
else {
printk(", transmit ");
}
printk("flow control ON");
}
printk("\n");
}
else {
netif_carrier_off(bp->dev);
printk(KERN_ERR PFX "%s NIC Link is Down\n", bp->dev->name);
}
bnx2_report_fw_link(bp);
}
static void
bnx2_resolve_flow_ctrl(struct bnx2 *bp)
{
u32 local_adv, remote_adv;
bp->flow_ctrl = 0;
if ((bp->autoneg & (AUTONEG_SPEED | AUTONEG_FLOW_CTRL)) !=
(AUTONEG_SPEED | AUTONEG_FLOW_CTRL)) {
if (bp->duplex == DUPLEX_FULL) {
bp->flow_ctrl = bp->req_flow_ctrl;
}
return;
}
if (bp->duplex != DUPLEX_FULL) {
return;
}
if ((bp->phy_flags & PHY_SERDES_FLAG) &&
(CHIP_NUM(bp) == CHIP_NUM_5708)) {
u32 val;
bnx2_read_phy(bp, BCM5708S_1000X_STAT1, &val);
if (val & BCM5708S_1000X_STAT1_TX_PAUSE)
bp->flow_ctrl |= FLOW_CTRL_TX;
if (val & BCM5708S_1000X_STAT1_RX_PAUSE)
bp->flow_ctrl |= FLOW_CTRL_RX;
return;
}
bnx2_read_phy(bp, MII_ADVERTISE, &local_adv);
bnx2_read_phy(bp, MII_LPA, &remote_adv);
if (bp->phy_flags & PHY_SERDES_FLAG) {
u32 new_local_adv = 0;
u32 new_remote_adv = 0;
if (local_adv & ADVERTISE_1000XPAUSE)
new_local_adv |= ADVERTISE_PAUSE_CAP;
if (local_adv & ADVERTISE_1000XPSE_ASYM)
new_local_adv |= ADVERTISE_PAUSE_ASYM;
if (remote_adv & ADVERTISE_1000XPAUSE)
new_remote_adv |= ADVERTISE_PAUSE_CAP;
if (remote_adv & ADVERTISE_1000XPSE_ASYM)
new_remote_adv |= ADVERTISE_PAUSE_ASYM;
local_adv = new_local_adv;
remote_adv = new_remote_adv;
}
/* See Table 28B-3 of 802.3ab-1999 spec. */
if (local_adv & ADVERTISE_PAUSE_CAP) {
if(local_adv & ADVERTISE_PAUSE_ASYM) {
if (remote_adv & ADVERTISE_PAUSE_CAP) {
bp->flow_ctrl = FLOW_CTRL_TX | FLOW_CTRL_RX;
}
else if (remote_adv & ADVERTISE_PAUSE_ASYM) {
bp->flow_ctrl = FLOW_CTRL_RX;
}
}
else {
if (remote_adv & ADVERTISE_PAUSE_CAP) {
bp->flow_ctrl = FLOW_CTRL_TX | FLOW_CTRL_RX;
}
}
}
else if (local_adv & ADVERTISE_PAUSE_ASYM) {
if ((remote_adv & ADVERTISE_PAUSE_CAP) &&
(remote_adv & ADVERTISE_PAUSE_ASYM)) {
bp->flow_ctrl = FLOW_CTRL_TX;
}
}
}
static int
bnx2_5708s_linkup(struct bnx2 *bp)
{
u32 val;
bp->link_up = 1;
bnx2_read_phy(bp, BCM5708S_1000X_STAT1, &val);
switch (val & BCM5708S_1000X_STAT1_SPEED_MASK) {
case BCM5708S_1000X_STAT1_SPEED_10:
bp->line_speed = SPEED_10;
break;
case BCM5708S_1000X_STAT1_SPEED_100:
bp->line_speed = SPEED_100;
break;
case BCM5708S_1000X_STAT1_SPEED_1G:
bp->line_speed = SPEED_1000;
break;
case BCM5708S_1000X_STAT1_SPEED_2G5:
bp->line_speed = SPEED_2500;
break;
}
if (val & BCM5708S_1000X_STAT1_FD)
bp->duplex = DUPLEX_FULL;
else
bp->duplex = DUPLEX_HALF;
return 0;
}
static int
bnx2_5706s_linkup(struct bnx2 *bp)
{
u32 bmcr, local_adv, remote_adv, common;
bp->link_up = 1;
bp->line_speed = SPEED_1000;
bnx2_read_phy(bp, MII_BMCR, &bmcr);
if (bmcr & BMCR_FULLDPLX) {
bp->duplex = DUPLEX_FULL;
}
else {
bp->duplex = DUPLEX_HALF;
}
if (!(bmcr & BMCR_ANENABLE)) {
return 0;
}
bnx2_read_phy(bp, MII_ADVERTISE, &local_adv);
bnx2_read_phy(bp, MII_LPA, &remote_adv);
common = local_adv & remote_adv;
if (common & (ADVERTISE_1000XHALF | ADVERTISE_1000XFULL)) {
if (common & ADVERTISE_1000XFULL) {
bp->duplex = DUPLEX_FULL;
}
else {
bp->duplex = DUPLEX_HALF;
}
}
return 0;
}
static int
bnx2_copper_linkup(struct bnx2 *bp)
{
u32 bmcr;
bnx2_read_phy(bp, MII_BMCR, &bmcr);
if (bmcr & BMCR_ANENABLE) {
u32 local_adv, remote_adv, common;
bnx2_read_phy(bp, MII_CTRL1000, &local_adv);
bnx2_read_phy(bp, MII_STAT1000, &remote_adv);
common = local_adv & (remote_adv >> 2);
if (common & ADVERTISE_1000FULL) {
bp->line_speed = SPEED_1000;
bp->duplex = DUPLEX_FULL;
}
else if (common & ADVERTISE_1000HALF) {
bp->line_speed = SPEED_1000;
bp->duplex = DUPLEX_HALF;
}
else {
bnx2_read_phy(bp, MII_ADVERTISE, &local_adv);
bnx2_read_phy(bp, MII_LPA, &remote_adv);
common = local_adv & remote_adv;
if (common & ADVERTISE_100FULL) {
bp->line_speed = SPEED_100;
bp->duplex = DUPLEX_FULL;
}
else if (common & ADVERTISE_100HALF) {
bp->line_speed = SPEED_100;
bp->duplex = DUPLEX_HALF;
}
else if (common & ADVERTISE_10FULL) {
bp->line_speed = SPEED_10;
bp->duplex = DUPLEX_FULL;
}
else if (common & ADVERTISE_10HALF) {
bp->line_speed = SPEED_10;
bp->duplex = DUPLEX_HALF;
}
else {
bp->line_speed = 0;
bp->link_up = 0;
}
}
}
else {
if (bmcr & BMCR_SPEED100) {
bp->line_speed = SPEED_100;
}
else {
bp->line_speed = SPEED_10;
}
if (bmcr & BMCR_FULLDPLX) {
bp->duplex = DUPLEX_FULL;
}
else {
bp->duplex = DUPLEX_HALF;
}
}
return 0;
}
static int
bnx2_set_mac_link(struct bnx2 *bp)
{
u32 val;
REG_WR(bp, BNX2_EMAC_TX_LENGTHS, 0x2620);
if (bp->link_up && (bp->line_speed == SPEED_1000) &&
(bp->duplex == DUPLEX_HALF)) {
REG_WR(bp, BNX2_EMAC_TX_LENGTHS, 0x26ff);
}
/* Configure the EMAC mode register. */
val = REG_RD(bp, BNX2_EMAC_MODE);
val &= ~(BNX2_EMAC_MODE_PORT | BNX2_EMAC_MODE_HALF_DUPLEX |
BNX2_EMAC_MODE_MAC_LOOP | BNX2_EMAC_MODE_FORCE_LINK |
BNX2_EMAC_MODE_25G);
if (bp->link_up) {
switch (bp->line_speed) {
case SPEED_10:
if (CHIP_NUM(bp) == CHIP_NUM_5708) {
val |= BNX2_EMAC_MODE_PORT_MII_10;
break;
}
/* fall through */
case SPEED_100:
val |= BNX2_EMAC_MODE_PORT_MII;
break;
case SPEED_2500:
val |= BNX2_EMAC_MODE_25G;
/* fall through */
case SPEED_1000:
val |= BNX2_EMAC_MODE_PORT_GMII;
break;
}
}
else {
val |= BNX2_EMAC_MODE_PORT_GMII;
}
/* Set the MAC to operate in the appropriate duplex mode. */
if (bp->duplex == DUPLEX_HALF)
val |= BNX2_EMAC_MODE_HALF_DUPLEX;
REG_WR(bp, BNX2_EMAC_MODE, val);
/* Enable/disable rx PAUSE. */
bp->rx_mode &= ~BNX2_EMAC_RX_MODE_FLOW_EN;
if (bp->flow_ctrl & FLOW_CTRL_RX)
bp->rx_mode |= BNX2_EMAC_RX_MODE_FLOW_EN;
REG_WR(bp, BNX2_EMAC_RX_MODE, bp->rx_mode);
/* Enable/disable tx PAUSE. */
val = REG_RD(bp, BNX2_EMAC_TX_MODE);
val &= ~BNX2_EMAC_TX_MODE_FLOW_EN;
if (bp->flow_ctrl & FLOW_CTRL_TX)
val |= BNX2_EMAC_TX_MODE_FLOW_EN;
REG_WR(bp, BNX2_EMAC_TX_MODE, val);
/* Acknowledge the interrupt. */
REG_WR(bp, BNX2_EMAC_STATUS, BNX2_EMAC_STATUS_LINK_CHANGE);
return 0;
}
static int
bnx2_set_link(struct bnx2 *bp)
{
u32 bmsr;
u8 link_up;
if (bp->loopback == MAC_LOOPBACK) {
bp->link_up = 1;
return 0;
}
link_up = bp->link_up;
bnx2_read_phy(bp, MII_BMSR, &bmsr);
bnx2_read_phy(bp, MII_BMSR, &bmsr);
if ((bp->phy_flags & PHY_SERDES_FLAG) &&
(CHIP_NUM(bp) == CHIP_NUM_5706)) {
u32 val;
val = REG_RD(bp, BNX2_EMAC_STATUS);
if (val & BNX2_EMAC_STATUS_LINK)
bmsr |= BMSR_LSTATUS;
else
bmsr &= ~BMSR_LSTATUS;
}
if (bmsr & BMSR_LSTATUS) {
bp->link_up = 1;
if (bp->phy_flags & PHY_SERDES_FLAG) {
if (CHIP_NUM(bp) == CHIP_NUM_5706)
bnx2_5706s_linkup(bp);
else if (CHIP_NUM(bp) == CHIP_NUM_5708)
bnx2_5708s_linkup(bp);
}
else {
bnx2_copper_linkup(bp);
}
bnx2_resolve_flow_ctrl(bp);
}
else {
if ((bp->phy_flags & PHY_SERDES_FLAG) &&
(bp->autoneg & AUTONEG_SPEED)) {
u32 bmcr;
bnx2_read_phy(bp, MII_BMCR, &bmcr);
if (!(bmcr & BMCR_ANENABLE)) {
bnx2_write_phy(bp, MII_BMCR, bmcr |
BMCR_ANENABLE);
}
}
bp->phy_flags &= ~PHY_PARALLEL_DETECT_FLAG;
bp->link_up = 0;
}
if (bp->link_up != link_up) {
bnx2_report_link(bp);
}
bnx2_set_mac_link(bp);
return 0;
}
static int
bnx2_reset_phy(struct bnx2 *bp)
{
int i;
u32 reg;
bnx2_write_phy(bp, MII_BMCR, BMCR_RESET);
#define PHY_RESET_MAX_WAIT 100
for (i = 0; i < PHY_RESET_MAX_WAIT; i++) {
udelay(10);
bnx2_read_phy(bp, MII_BMCR, &reg);
if (!(reg & BMCR_RESET)) {
udelay(20);
break;
}
}
if (i == PHY_RESET_MAX_WAIT) {
return -EBUSY;
}
return 0;
}
static u32
bnx2_phy_get_pause_adv(struct bnx2 *bp)
{
u32 adv = 0;
if ((bp->req_flow_ctrl & (FLOW_CTRL_RX | FLOW_CTRL_TX)) ==
(FLOW_CTRL_RX | FLOW_CTRL_TX)) {
if (bp->phy_flags & PHY_SERDES_FLAG) {
adv = ADVERTISE_1000XPAUSE;
}
else {
adv = ADVERTISE_PAUSE_CAP;
}
}
else if (bp->req_flow_ctrl & FLOW_CTRL_TX) {
if (bp->phy_flags & PHY_SERDES_FLAG) {
adv = ADVERTISE_1000XPSE_ASYM;
}
else {
adv = ADVERTISE_PAUSE_ASYM;
}
}
else if (bp->req_flow_ctrl & FLOW_CTRL_RX) {
if (bp->phy_flags & PHY_SERDES_FLAG) {
adv = ADVERTISE_1000XPAUSE | ADVERTISE_1000XPSE_ASYM;
}
else {
adv = ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM;
}
}
return adv;
}
static int
bnx2_setup_serdes_phy(struct bnx2 *bp)
{
u32 adv, bmcr, up1;
u32 new_adv = 0;
if (!(bp->autoneg & AUTONEG_SPEED)) {
u32 new_bmcr;
int force_link_down = 0;
if (CHIP_NUM(bp) == CHIP_NUM_5708) {
bnx2_read_phy(bp, BCM5708S_UP1, &up1);
if (up1 & BCM5708S_UP1_2G5) {
up1 &= ~BCM5708S_UP1_2G5;
bnx2_write_phy(bp, BCM5708S_UP1, up1);
force_link_down = 1;
}
}
bnx2_read_phy(bp, MII_ADVERTISE, &adv);
adv &= ~(ADVERTISE_1000XFULL | ADVERTISE_1000XHALF);
bnx2_read_phy(bp, MII_BMCR, &bmcr);
new_bmcr = bmcr & ~BMCR_ANENABLE;
new_bmcr |= BMCR_SPEED1000;
if (bp->req_duplex == DUPLEX_FULL) {
adv |= ADVERTISE_1000XFULL;
new_bmcr |= BMCR_FULLDPLX;
}
else {
adv |= ADVERTISE_1000XHALF;
new_bmcr &= ~BMCR_FULLDPLX;
}
if ((new_bmcr != bmcr) || (force_link_down)) {
/* Force a link down visible on the other side */
if (bp->link_up) {
bnx2_write_phy(bp, MII_ADVERTISE, adv &
~(ADVERTISE_1000XFULL |
ADVERTISE_1000XHALF));
bnx2_write_phy(bp, MII_BMCR, bmcr |
BMCR_ANRESTART | BMCR_ANENABLE);
bp->link_up = 0;
netif_carrier_off(bp->dev);
bnx2_write_phy(bp, MII_BMCR, new_bmcr);
}
bnx2_write_phy(bp, MII_ADVERTISE, adv);
bnx2_write_phy(bp, MII_BMCR, new_bmcr);
}
return 0;
}
if (bp->phy_flags & PHY_2_5G_CAPABLE_FLAG) {
bnx2_read_phy(bp, BCM5708S_UP1, &up1);
up1 |= BCM5708S_UP1_2G5;
bnx2_write_phy(bp, BCM5708S_UP1, up1);
}
if (bp->advertising & ADVERTISED_1000baseT_Full)
new_adv |= ADVERTISE_1000XFULL;
new_adv |= bnx2_phy_get_pause_adv(bp);
bnx2_read_phy(bp, MII_ADVERTISE, &adv);
bnx2_read_phy(bp, MII_BMCR, &bmcr);
bp->serdes_an_pending = 0;
if ((adv != new_adv) || ((bmcr & BMCR_ANENABLE) == 0)) {
/* Force a link down visible on the other side */
if (bp->link_up) {
int i;
bnx2_write_phy(bp, MII_BMCR, BMCR_LOOPBACK);
for (i = 0; i < 110; i++) {
udelay(100);
}
}
bnx2_write_phy(bp, MII_ADVERTISE, new_adv);
bnx2_write_phy(bp, MII_BMCR, bmcr | BMCR_ANRESTART |
BMCR_ANENABLE);
if (CHIP_NUM(bp) == CHIP_NUM_5706) {
/* Speed up link-up time when the link partner
* does not autonegotiate which is very common
* in blade servers. Some blade servers use
* IPMI for kerboard input and it's important
* to minimize link disruptions. Autoneg. involves
* exchanging base pages plus 3 next pages and
* normally completes in about 120 msec.
*/
bp->current_interval = SERDES_AN_TIMEOUT;
bp->serdes_an_pending = 1;
mod_timer(&bp->timer, jiffies + bp->current_interval);
}
}
return 0;
}
#define ETHTOOL_ALL_FIBRE_SPEED \
(ADVERTISED_1000baseT_Full)
#define ETHTOOL_ALL_COPPER_SPEED \
(ADVERTISED_10baseT_Half | ADVERTISED_10baseT_Full | \
ADVERTISED_100baseT_Half | ADVERTISED_100baseT_Full | \
ADVERTISED_1000baseT_Full)
#define PHY_ALL_10_100_SPEED (ADVERTISE_10HALF | ADVERTISE_10FULL | \
ADVERTISE_100HALF | ADVERTISE_100FULL | ADVERTISE_CSMA)
#define PHY_ALL_1000_SPEED (ADVERTISE_1000HALF | ADVERTISE_1000FULL)
static int
bnx2_setup_copper_phy(struct bnx2 *bp)
{
u32 bmcr;
u32 new_bmcr;
bnx2_read_phy(bp, MII_BMCR, &bmcr);
if (bp->autoneg & AUTONEG_SPEED) {
u32 adv_reg, adv1000_reg;
u32 new_adv_reg = 0;
u32 new_adv1000_reg = 0;
bnx2_read_phy(bp, MII_ADVERTISE, &adv_reg);
adv_reg &= (PHY_ALL_10_100_SPEED | ADVERTISE_PAUSE_CAP |
ADVERTISE_PAUSE_ASYM);
bnx2_read_phy(bp, MII_CTRL1000, &adv1000_reg);
adv1000_reg &= PHY_ALL_1000_SPEED;
if (bp->advertising & ADVERTISED_10baseT_Half)
new_adv_reg |= ADVERTISE_10HALF;
if (bp->advertising & ADVERTISED_10baseT_Full)
new_adv_reg |= ADVERTISE_10FULL;
if (bp->advertising & ADVERTISED_100baseT_Half)
new_adv_reg |= ADVERTISE_100HALF;
if (bp->advertising & ADVERTISED_100baseT_Full)
new_adv_reg |= ADVERTISE_100FULL;
if (bp->advertising & ADVERTISED_1000baseT_Full)
new_adv1000_reg |= ADVERTISE_1000FULL;
new_adv_reg |= ADVERTISE_CSMA;
new_adv_reg |= bnx2_phy_get_pause_adv(bp);
if ((adv1000_reg != new_adv1000_reg) ||
(adv_reg != new_adv_reg) ||
((bmcr & BMCR_ANENABLE) == 0)) {
bnx2_write_phy(bp, MII_ADVERTISE, new_adv_reg);
bnx2_write_phy(bp, MII_CTRL1000, new_adv1000_reg);
bnx2_write_phy(bp, MII_BMCR, BMCR_ANRESTART |
BMCR_ANENABLE);
}
else if (bp->link_up) {
/* Flow ctrl may have changed from auto to forced */
/* or vice-versa. */
bnx2_resolve_flow_ctrl(bp);
bnx2_set_mac_link(bp);
}
return 0;
}
new_bmcr = 0;
if (bp->req_line_speed == SPEED_100) {
new_bmcr |= BMCR_SPEED100;
}
if (bp->req_duplex == DUPLEX_FULL) {
new_bmcr |= BMCR_FULLDPLX;
}
if (new_bmcr != bmcr) {
u32 bmsr;
int i = 0;
bnx2_read_phy(bp, MII_BMSR, &bmsr);
bnx2_read_phy(bp, MII_BMSR, &bmsr);
if (bmsr & BMSR_LSTATUS) {
/* Force link down */
bnx2_write_phy(bp, MII_BMCR, BMCR_LOOPBACK);
do {
udelay(100);
bnx2_read_phy(bp, MII_BMSR, &bmsr);
bnx2_read_phy(bp, MII_BMSR, &bmsr);
i++;
} while ((bmsr & BMSR_LSTATUS) && (i < 620));
}
bnx2_write_phy(bp, MII_BMCR, new_bmcr);
/* Normally, the new speed is setup after the link has
* gone down and up again. In some cases, link will not go
* down so we need to set up the new speed here.
*/
if (bmsr & BMSR_LSTATUS) {
bp->line_speed = bp->req_line_speed;
bp->duplex = bp->req_duplex;
bnx2_resolve_flow_ctrl(bp);
bnx2_set_mac_link(bp);
}
}
return 0;
}
static int
bnx2_setup_phy(struct bnx2 *bp)
{
if (bp->loopback == MAC_LOOPBACK)
return 0;
if (bp->phy_flags & PHY_SERDES_FLAG) {
return (bnx2_setup_serdes_phy(bp));
}
else {
return (bnx2_setup_copper_phy(bp));
}
}
static int
bnx2_init_5708s_phy(struct bnx2 *bp)
{
u32 val;
bnx2_write_phy(bp, BCM5708S_BLK_ADDR, BCM5708S_BLK_ADDR_DIG3);
bnx2_write_phy(bp, BCM5708S_DIG_3_0, BCM5708S_DIG_3_0_USE_IEEE);
bnx2_write_phy(bp, BCM5708S_BLK_ADDR, BCM5708S_BLK_ADDR_DIG);
bnx2_read_phy(bp, BCM5708S_1000X_CTL1, &val);
val |= BCM5708S_1000X_CTL1_FIBER_MODE | BCM5708S_1000X_CTL1_AUTODET_EN;
bnx2_write_phy(bp, BCM5708S_1000X_CTL1, val);
bnx2_read_phy(bp, BCM5708S_1000X_CTL2, &val);
val |= BCM5708S_1000X_CTL2_PLLEL_DET_EN;
bnx2_write_phy(bp, BCM5708S_1000X_CTL2, val);
if (bp->phy_flags & PHY_2_5G_CAPABLE_FLAG) {
bnx2_read_phy(bp, BCM5708S_UP1, &val);
val |= BCM5708S_UP1_2G5;
bnx2_write_phy(bp, BCM5708S_UP1, val);
}
if ((CHIP_ID(bp) == CHIP_ID_5708_A0) ||
(CHIP_ID(bp) == CHIP_ID_5708_B0) ||
(CHIP_ID(bp) == CHIP_ID_5708_B1)) {
/* increase tx signal amplitude */
bnx2_write_phy(bp, BCM5708S_BLK_ADDR,
BCM5708S_BLK_ADDR_TX_MISC);
bnx2_read_phy(bp, BCM5708S_TX_ACTL1, &val);
val &= ~BCM5708S_TX_ACTL1_DRIVER_VCM;
bnx2_write_phy(bp, BCM5708S_TX_ACTL1, val);
bnx2_write_phy(bp, BCM5708S_BLK_ADDR, BCM5708S_BLK_ADDR_DIG);
}
val = REG_RD_IND(bp, bp->shmem_base + BNX2_PORT_HW_CFG_CONFIG) &
BNX2_PORT_HW_CFG_CFG_TXCTL3_MASK;
if (val) {
u32 is_backplane;
is_backplane = REG_RD_IND(bp, bp->shmem_base +
BNX2_SHARED_HW_CFG_CONFIG);
if (is_backplane & BNX2_SHARED_HW_CFG_PHY_BACKPLANE) {
bnx2_write_phy(bp, BCM5708S_BLK_ADDR,
BCM5708S_BLK_ADDR_TX_MISC);
bnx2_write_phy(bp, BCM5708S_TX_ACTL3, val);
bnx2_write_phy(bp, BCM5708S_BLK_ADDR,
BCM5708S_BLK_ADDR_DIG);
}
}
return 0;
}
static int
bnx2_init_5706s_phy(struct bnx2 *bp)
{
bp->phy_flags &= ~PHY_PARALLEL_DETECT_FLAG;
if (CHIP_NUM(bp) == CHIP_NUM_5706) {
REG_WR(bp, BNX2_MISC_UNUSED0, 0x300);
}
if (bp->dev->mtu > 1500) {
u32 val;
/* Set extended packet length bit */
bnx2_write_phy(bp, 0x18, 0x7);
bnx2_read_phy(bp, 0x18, &val);
bnx2_write_phy(bp, 0x18, (val & 0xfff8) | 0x4000);
bnx2_write_phy(bp, 0x1c, 0x6c00);
bnx2_read_phy(bp, 0x1c, &val);
bnx2_write_phy(bp, 0x1c, (val & 0x3ff) | 0xec02);
}
else {
u32 val;
bnx2_write_phy(bp, 0x18, 0x7);
bnx2_read_phy(bp, 0x18, &val);
bnx2_write_phy(bp, 0x18, val & ~0x4007);
bnx2_write_phy(bp, 0x1c, 0x6c00);
bnx2_read_phy(bp, 0x1c, &val);
bnx2_write_phy(bp, 0x1c, (val & 0x3fd) | 0xec00);
}
return 0;
}
static int
bnx2_init_copper_phy(struct bnx2 *bp)
{
u32 val;
bp->phy_flags |= PHY_CRC_FIX_FLAG;
if (bp->phy_flags & PHY_CRC_FIX_FLAG) {
bnx2_write_phy(bp, 0x18, 0x0c00);
bnx2_write_phy(bp, 0x17, 0x000a);
bnx2_write_phy(bp, 0x15, 0x310b);
bnx2_write_phy(bp, 0x17, 0x201f);
bnx2_write_phy(bp, 0x15, 0x9506);
bnx2_write_phy(bp, 0x17, 0x401f);
bnx2_write_phy(bp, 0x15, 0x14e2);
bnx2_write_phy(bp, 0x18, 0x0400);
}
if (bp->dev->mtu > 1500) {
/* Set extended packet length bit */
bnx2_write_phy(bp, 0x18, 0x7);
bnx2_read_phy(bp, 0x18, &val);
bnx2_write_phy(bp, 0x18, val | 0x4000);
bnx2_read_phy(bp, 0x10, &val);
bnx2_write_phy(bp, 0x10, val | 0x1);
}
else {
bnx2_write_phy(bp, 0x18, 0x7);
bnx2_read_phy(bp, 0x18, &val);
bnx2_write_phy(bp, 0x18, val & ~0x4007);
bnx2_read_phy(bp, 0x10, &val);
bnx2_write_phy(bp, 0x10, val & ~0x1);
}
/* ethernet@wirespeed */
bnx2_write_phy(bp, 0x18, 0x7007);
bnx2_read_phy(bp, 0x18, &val);
bnx2_write_phy(bp, 0x18, val | (1 << 15) | (1 << 4));
return 0;
}
static int
bnx2_init_phy(struct bnx2 *bp)
{
u32 val;
int rc = 0;
bp->phy_flags &= ~PHY_INT_MODE_MASK_FLAG;
bp->phy_flags |= PHY_INT_MODE_LINK_READY_FLAG;
REG_WR(bp, BNX2_EMAC_ATTENTION_ENA, BNX2_EMAC_ATTENTION_ENA_LINK);
bnx2_reset_phy(bp);
bnx2_read_phy(bp, MII_PHYSID1, &val);
bp->phy_id = val << 16;
bnx2_read_phy(bp, MII_PHYSID2, &val);
bp->phy_id |= val & 0xffff;
if (bp->phy_flags & PHY_SERDES_FLAG) {
if (CHIP_NUM(bp) == CHIP_NUM_5706)
rc = bnx2_init_5706s_phy(bp);
else if (CHIP_NUM(bp) == CHIP_NUM_5708)
rc = bnx2_init_5708s_phy(bp);
}
else {
rc = bnx2_init_copper_phy(bp);
}
bnx2_setup_phy(bp);
return rc;
}
static int
bnx2_set_mac_loopback(struct bnx2 *bp)
{
u32 mac_mode;
mac_mode = REG_RD(bp, BNX2_EMAC_MODE);
mac_mode &= ~BNX2_EMAC_MODE_PORT;
mac_mode |= BNX2_EMAC_MODE_MAC_LOOP | BNX2_EMAC_MODE_FORCE_LINK;
REG_WR(bp, BNX2_EMAC_MODE, mac_mode);
bp->link_up = 1;
return 0;
}
static int bnx2_test_link(struct bnx2 *);
static int
bnx2_set_phy_loopback(struct bnx2 *bp)
{
u32 mac_mode;
int rc, i;
spin_lock_bh(&bp->phy_lock);
rc = bnx2_write_phy(bp, MII_BMCR, BMCR_LOOPBACK | BMCR_FULLDPLX |
BMCR_SPEED1000);
spin_unlock_bh(&bp->phy_lock);
if (rc)
return rc;
for (i = 0; i < 10; i++) {
if (bnx2_test_link(bp) == 0)
break;
udelay(10);
}
mac_mode = REG_RD(bp, BNX2_EMAC_MODE);
mac_mode &= ~(BNX2_EMAC_MODE_PORT | BNX2_EMAC_MODE_HALF_DUPLEX |
BNX2_EMAC_MODE_MAC_LOOP | BNX2_EMAC_MODE_FORCE_LINK |
BNX2_EMAC_MODE_25G);
mac_mode |= BNX2_EMAC_MODE_PORT_GMII;
REG_WR(bp, BNX2_EMAC_MODE, mac_mode);
bp->link_up = 1;
return 0;
}
static int
bnx2_fw_sync(struct bnx2 *bp, u32 msg_data, int silent)
{
int i;
u32 val;
bp->fw_wr_seq++;
msg_data |= bp->fw_wr_seq;
REG_WR_IND(bp, bp->shmem_base + BNX2_DRV_MB, msg_data);
/* wait for an acknowledgement. */
for (i = 0; i < (FW_ACK_TIME_OUT_MS / 10); i++) {
msleep(10);
val = REG_RD_IND(bp, bp->shmem_base + BNX2_FW_MB);
if ((val & BNX2_FW_MSG_ACK) == (msg_data & BNX2_DRV_MSG_SEQ))
break;
}
if ((msg_data & BNX2_DRV_MSG_DATA) == BNX2_DRV_MSG_DATA_WAIT0)
return 0;
/* If we timed out, inform the firmware that this is the case. */
if ((val & BNX2_FW_MSG_ACK) != (msg_data & BNX2_DRV_MSG_SEQ)) {
if (!silent)
printk(KERN_ERR PFX "fw sync timeout, reset code = "
"%x\n", msg_data);
msg_data &= ~BNX2_DRV_MSG_CODE;
msg_data |= BNX2_DRV_MSG_CODE_FW_TIMEOUT;
REG_WR_IND(bp, bp->shmem_base + BNX2_DRV_MB, msg_data);
return -EBUSY;
}
if ((val & BNX2_FW_MSG_STATUS_MASK) != BNX2_FW_MSG_STATUS_OK)
return -EIO;
return 0;
}
static void
bnx2_init_context(struct bnx2 *bp)
{
u32 vcid;
vcid = 96;
while (vcid) {
u32 vcid_addr, pcid_addr, offset;
vcid--;
if (CHIP_ID(bp) == CHIP_ID_5706_A0) {
u32 new_vcid;
vcid_addr = GET_PCID_ADDR(vcid);
if (vcid & 0x8) {
new_vcid = 0x60 + (vcid & 0xf0) + (vcid & 0x7);
}
else {
new_vcid = vcid;
}
pcid_addr = GET_PCID_ADDR(new_vcid);
}
else {
vcid_addr = GET_CID_ADDR(vcid);
pcid_addr = vcid_addr;
}
REG_WR(bp, BNX2_CTX_VIRT_ADDR, 0x00);
REG_WR(bp, BNX2_CTX_PAGE_TBL, pcid_addr);
/* Zero out the context. */
for (offset = 0; offset < PHY_CTX_SIZE; offset += 4) {
CTX_WR(bp, 0x00, offset, 0);
}
REG_WR(bp, BNX2_CTX_VIRT_ADDR, vcid_addr);
REG_WR(bp, BNX2_CTX_PAGE_TBL, pcid_addr);
}
}
static int
bnx2_alloc_bad_rbuf(struct bnx2 *bp)
{
u16 *good_mbuf;
u32 good_mbuf_cnt;
u32 val;
good_mbuf = kmalloc(512 * sizeof(u16), GFP_KERNEL);
if (good_mbuf == NULL) {
printk(KERN_ERR PFX "Failed to allocate memory in "
"bnx2_alloc_bad_rbuf\n");
return -ENOMEM;
}
REG_WR(bp, BNX2_MISC_ENABLE_SET_BITS,
BNX2_MISC_ENABLE_SET_BITS_RX_MBUF_ENABLE);
good_mbuf_cnt = 0;
/* Allocate a bunch of mbufs and save the good ones in an array. */
val = REG_RD_IND(bp, BNX2_RBUF_STATUS1);
while (val & BNX2_RBUF_STATUS1_FREE_COUNT) {
REG_WR_IND(bp, BNX2_RBUF_COMMAND, BNX2_RBUF_COMMAND_ALLOC_REQ);
val = REG_RD_IND(bp, BNX2_RBUF_FW_BUF_ALLOC);
val &= BNX2_RBUF_FW_BUF_ALLOC_VALUE;
/* The addresses with Bit 9 set are bad memory blocks. */
if (!(val & (1 << 9))) {
good_mbuf[good_mbuf_cnt] = (u16) val;
good_mbuf_cnt++;
}
val = REG_RD_IND(bp, BNX2_RBUF_STATUS1);
}
/* Free the good ones back to the mbuf pool thus discarding
* all the bad ones. */
while (good_mbuf_cnt) {
good_mbuf_cnt--;
val = good_mbuf[good_mbuf_cnt];
val = (val << 9) | val | 1;
REG_WR_IND(bp, BNX2_RBUF_FW_BUF_FREE, val);
}
kfree(good_mbuf);
return 0;
}
static void
bnx2_set_mac_addr(struct bnx2 *bp)
{
u32 val;
u8 *mac_addr = bp->dev->dev_addr;
val = (mac_addr[0] << 8) | mac_addr[1];
REG_WR(bp, BNX2_EMAC_MAC_MATCH0, val);
val = (mac_addr[2] << 24) | (mac_addr[3] << 16) |
(mac_addr[4] << 8) | mac_addr[5];
REG_WR(bp, BNX2_EMAC_MAC_MATCH1, val);
}
static inline int
bnx2_alloc_rx_skb(struct bnx2 *bp, u16 index)
{
struct sk_buff *skb;
struct sw_bd *rx_buf = &bp->rx_buf_ring[index];
dma_addr_t mapping;
struct rx_bd *rxbd = &bp->rx_desc_ring[RX_RING(index)][RX_IDX(index)];
unsigned long align;
skb = netdev_alloc_skb(bp->dev, bp->rx_buf_size);
if (skb == NULL) {
return -ENOMEM;
}
if (unlikely((align = (unsigned long) skb->data & 0x7))) {
skb_reserve(skb, 8 - align);
}
mapping = pci_map_single(bp->pdev, skb->data, bp->rx_buf_use_size,
PCI_DMA_FROMDEVICE);
rx_buf->skb = skb;
pci_unmap_addr_set(rx_buf, mapping, mapping);
rxbd->rx_bd_haddr_hi = (u64) mapping >> 32;
rxbd->rx_bd_haddr_lo = (u64) mapping & 0xffffffff;
bp->rx_prod_bseq += bp->rx_buf_use_size;
return 0;
}
static void
bnx2_phy_int(struct bnx2 *bp)
{
u32 new_link_state, old_link_state;
new_link_state = bp->status_blk->status_attn_bits &
STATUS_ATTN_BITS_LINK_STATE;
old_link_state = bp->status_blk->status_attn_bits_ack &
STATUS_ATTN_BITS_LINK_STATE;
if (new_link_state != old_link_state) {
if (new_link_state) {
REG_WR(bp, BNX2_PCICFG_STATUS_BIT_SET_CMD,
STATUS_ATTN_BITS_LINK_STATE);
}
else {
REG_WR(bp, BNX2_PCICFG_STATUS_BIT_CLEAR_CMD,
STATUS_ATTN_BITS_LINK_STATE);
}
bnx2_set_link(bp);
}
}
static void
bnx2_tx_int(struct bnx2 *bp)
{
struct status_block *sblk = bp->status_blk;
u16 hw_cons, sw_cons, sw_ring_cons;
int tx_free_bd = 0;
hw_cons = bp->hw_tx_cons = sblk->status_tx_quick_consumer_index0;
if ((hw_cons & MAX_TX_DESC_CNT) == MAX_TX_DESC_CNT) {
hw_cons++;
}
sw_cons = bp->tx_cons;
while (sw_cons != hw_cons) {
struct sw_bd *tx_buf;
struct sk_buff *skb;
int i, last;
sw_ring_cons = TX_RING_IDX(sw_cons);
tx_buf = &bp->tx_buf_ring[sw_ring_cons];
skb = tx_buf->skb;
#ifdef BCM_TSO
/* partial BD completions possible with TSO packets */
if (skb_is_gso(skb)) {
u16 last_idx, last_ring_idx;
last_idx = sw_cons +
skb_shinfo(skb)->nr_frags + 1;
last_ring_idx = sw_ring_cons +
skb_shinfo(skb)->nr_frags + 1;
if (unlikely(last_ring_idx >= MAX_TX_DESC_CNT)) {
last_idx++;
}
if (((s16) ((s16) last_idx - (s16) hw_cons)) > 0) {
break;
}
}
#endif
pci_unmap_single(bp->pdev, pci_unmap_addr(tx_buf, mapping),
skb_headlen(skb), PCI_DMA_TODEVICE);
tx_buf->skb = NULL;
last = skb_shinfo(skb)->nr_frags;
for (i = 0; i < last; i++) {
sw_cons = NEXT_TX_BD(sw_cons);
pci_unmap_page(bp->pdev,
pci_unmap_addr(
&bp->tx_buf_ring[TX_RING_IDX(sw_cons)],
mapping),
skb_shinfo(skb)->frags[i].size,
PCI_DMA_TODEVICE);
}
sw_cons = NEXT_TX_BD(sw_cons);
tx_free_bd += last + 1;
dev_kfree_skb(skb);
hw_cons = bp->hw_tx_cons =
sblk->status_tx_quick_consumer_index0;
if ((hw_cons & MAX_TX_DESC_CNT) == MAX_TX_DESC_CNT) {
hw_cons++;
}
}
bp->tx_cons = sw_cons;
/* Need to make the tx_cons update visible to bnx2_start_xmit()
* before checking for netif_queue_stopped(). Without the
* memory barrier, there is a small possibility that bnx2_start_xmit()
* will miss it and cause the queue to be stopped forever.
*/
smp_mb();
if (unlikely(netif_queue_stopped(bp->dev)) &&
(bnx2_tx_avail(bp) > bp->tx_wake_thresh)) {
netif_tx_lock(bp->dev);
if ((netif_queue_stopped(bp->dev)) &&
(bnx2_tx_avail(bp) > bp->tx_wake_thresh))
netif_wake_queue(bp->dev);
netif_tx_unlock(bp->dev);
}
}
static inline void
bnx2_reuse_rx_skb(struct bnx2 *bp, struct sk_buff *skb,
u16 cons, u16 prod)
{
struct sw_bd *cons_rx_buf, *prod_rx_buf;
struct rx_bd *cons_bd, *prod_bd;
cons_rx_buf = &bp->rx_buf_ring[cons];
prod_rx_buf = &bp->rx_buf_ring[prod];
pci_dma_sync_single_for_device(bp->pdev,
pci_unmap_addr(cons_rx_buf, mapping),
bp->rx_offset + RX_COPY_THRESH, PCI_DMA_FROMDEVICE);
bp->rx_prod_bseq += bp->rx_buf_use_size;
prod_rx_buf->skb = skb;
if (cons == prod)
return;
pci_unmap_addr_set(prod_rx_buf, mapping,
pci_unmap_addr(cons_rx_buf, mapping));
cons_bd = &bp->rx_desc_ring[RX_RING(cons)][RX_IDX(cons)];
prod_bd = &bp->rx_desc_ring[RX_RING(prod)][RX_IDX(prod)];
prod_bd->rx_bd_haddr_hi = cons_bd->rx_bd_haddr_hi;
prod_bd->rx_bd_haddr_lo = cons_bd->rx_bd_haddr_lo;
}
static int
bnx2_rx_int(struct bnx2 *bp, int budget)
{
struct status_block *sblk = bp->status_blk;
u16 hw_cons, sw_cons, sw_ring_cons, sw_prod, sw_ring_prod;
struct l2_fhdr *rx_hdr;
int rx_pkt = 0;
hw_cons = bp->hw_rx_cons = sblk->status_rx_quick_consumer_index0;
if ((hw_cons & MAX_RX_DESC_CNT) == MAX_RX_DESC_CNT) {
hw_cons++;
}
sw_cons = bp->rx_cons;
sw_prod = bp->rx_prod;
/* Memory barrier necessary as speculative reads of the rx
* buffer can be ahead of the index in the status block
*/
rmb();
while (sw_cons != hw_cons) {
unsigned int len;
u32 status;
struct sw_bd *rx_buf;
struct sk_buff *skb;
dma_addr_t dma_addr;
sw_ring_cons = RX_RING_IDX(sw_cons);
sw_ring_prod = RX_RING_IDX(sw_prod);
rx_buf = &bp->rx_buf_ring[sw_ring_cons];
skb = rx_buf->skb;
rx_buf->skb = NULL;
dma_addr = pci_unmap_addr(rx_buf, mapping);
pci_dma_sync_single_for_cpu(bp->pdev, dma_addr,
bp->rx_offset + RX_COPY_THRESH, PCI_DMA_FROMDEVICE);
rx_hdr = (struct l2_fhdr *) skb->data;
len = rx_hdr->l2_fhdr_pkt_len - 4;
if ((status = rx_hdr->l2_fhdr_status) &
(L2_FHDR_ERRORS_BAD_CRC |
L2_FHDR_ERRORS_PHY_DECODE |
L2_FHDR_ERRORS_ALIGNMENT |
L2_FHDR_ERRORS_TOO_SHORT |
L2_FHDR_ERRORS_GIANT_FRAME)) {
goto reuse_rx;
}
/* Since we don't have a jumbo ring, copy small packets
* if mtu > 1500
*/
if ((bp->dev->mtu > 1500) && (len <= RX_COPY_THRESH)) {
struct sk_buff *new_skb;
new_skb = netdev_alloc_skb(bp->dev, len + 2);
if (new_skb == NULL)
goto reuse_rx;
/* aligned copy */
memcpy(new_skb->data,
skb->data + bp->rx_offset - 2,
len + 2);
skb_reserve(new_skb, 2);
skb_put(new_skb, len);
bnx2_reuse_rx_skb(bp, skb,
sw_ring_cons, sw_ring_prod);
skb = new_skb;
}
else if (bnx2_alloc_rx_skb(bp, sw_ring_prod) == 0) {
pci_unmap_single(bp->pdev, dma_addr,
bp->rx_buf_use_size, PCI_DMA_FROMDEVICE);
skb_reserve(skb, bp->rx_offset);
skb_put(skb, len);
}
else {
reuse_rx:
bnx2_reuse_rx_skb(bp, skb,
sw_ring_cons, sw_ring_prod);
goto next_rx;
}
skb->protocol = eth_type_trans(skb, bp->dev);
if ((len > (bp->dev->mtu + ETH_HLEN)) &&
(ntohs(skb->protocol) != 0x8100)) {
dev_kfree_skb(skb);
goto next_rx;
}
skb->ip_summed = CHECKSUM_NONE;
if (bp->rx_csum &&
(status & (L2_FHDR_STATUS_TCP_SEGMENT |
L2_FHDR_STATUS_UDP_DATAGRAM))) {
if (likely((status & (L2_FHDR_ERRORS_TCP_XSUM |
L2_FHDR_ERRORS_UDP_XSUM)) == 0))
skb->ip_summed = CHECKSUM_UNNECESSARY;
}
#ifdef BCM_VLAN
if ((status & L2_FHDR_STATUS_L2_VLAN_TAG) && (bp->vlgrp != 0)) {
vlan_hwaccel_receive_skb(skb, bp->vlgrp,
rx_hdr->l2_fhdr_vlan_tag);
}
else
#endif
netif_receive_skb(skb);
bp->dev->last_rx = jiffies;
rx_pkt++;
next_rx:
sw_cons = NEXT_RX_BD(sw_cons);
sw_prod = NEXT_RX_BD(sw_prod);
if ((rx_pkt == budget))
break;
/* Refresh hw_cons to see if there is new work */
if (sw_cons == hw_cons) {
hw_cons = bp->hw_rx_cons =
sblk->status_rx_quick_consumer_index0;
if ((hw_cons & MAX_RX_DESC_CNT) == MAX_RX_DESC_CNT)
hw_cons++;
rmb();
}
}
bp->rx_cons = sw_cons;
bp->rx_prod = sw_prod;
REG_WR16(bp, MB_RX_CID_ADDR + BNX2_L2CTX_HOST_BDIDX, sw_prod);
REG_WR(bp, MB_RX_CID_ADDR + BNX2_L2CTX_HOST_BSEQ, bp->rx_prod_bseq);
mmiowb();
return rx_pkt;
}
/* MSI ISR - The only difference between this and the INTx ISR
* is that the MSI interrupt is always serviced.
*/
static irqreturn_t
bnx2_msi(int irq, void *dev_instance, struct pt_regs *regs)
{
struct net_device *dev = dev_instance;
struct bnx2 *bp = netdev_priv(dev);
prefetch(bp->status_blk);
REG_WR(bp, BNX2_PCICFG_INT_ACK_CMD,
BNX2_PCICFG_INT_ACK_CMD_USE_INT_HC_PARAM |
BNX2_PCICFG_INT_ACK_CMD_MASK_INT);
/* Return here if interrupt is disabled. */
if (unlikely(atomic_read(&bp->intr_sem) != 0))
return IRQ_HANDLED;
netif_rx_schedule(dev);
return IRQ_HANDLED;
}
static irqreturn_t
bnx2_interrupt(int irq, void *dev_instance, struct pt_regs *regs)
{
struct net_device *dev = dev_instance;
struct bnx2 *bp = netdev_priv(dev);
/* When using INTx, it is possible for the interrupt to arrive
* at the CPU before the status block posted prior to the
* interrupt. Reading a register will flush the status block.
* When using MSI, the MSI message will always complete after
* the status block write.
*/
if ((bp->status_blk->status_idx == bp->last_status_idx) &&
(REG_RD(bp, BNX2_PCICFG_MISC_STATUS) &
BNX2_PCICFG_MISC_STATUS_INTA_VALUE))
return IRQ_NONE;
REG_WR(bp, BNX2_PCICFG_INT_ACK_CMD,
BNX2_PCICFG_INT_ACK_CMD_USE_INT_HC_PARAM |
BNX2_PCICFG_INT_ACK_CMD_MASK_INT);
/* Return here if interrupt is shared and is disabled. */
if (unlikely(atomic_read(&bp->intr_sem) != 0))
return IRQ_HANDLED;
netif_rx_schedule(dev);
return IRQ_HANDLED;
}
static inline int
bnx2_has_work(struct bnx2 *bp)
{
struct status_block *sblk = bp->status_blk;
if ((sblk->status_rx_quick_consumer_index0 != bp->hw_rx_cons) ||
(sblk->status_tx_quick_consumer_index0 != bp->hw_tx_cons))
return 1;
if (((sblk->status_attn_bits & STATUS_ATTN_BITS_LINK_STATE) != 0) !=
bp->link_up)
return 1;
return 0;
}
static int
bnx2_poll(struct net_device *dev, int *budget)
{
struct bnx2 *bp = netdev_priv(dev);
if ((bp->status_blk->status_attn_bits &
STATUS_ATTN_BITS_LINK_STATE) !=
(bp->status_blk->status_attn_bits_ack &
STATUS_ATTN_BITS_LINK_STATE)) {
spin_lock(&bp->phy_lock);
bnx2_phy_int(bp);
spin_unlock(&bp->phy_lock);
/* This is needed to take care of transient status
* during link changes.
*/
REG_WR(bp, BNX2_HC_COMMAND,
bp->hc_cmd | BNX2_HC_COMMAND_COAL_NOW_WO_INT);
REG_RD(bp, BNX2_HC_COMMAND);
}
if (bp->status_blk->status_tx_quick_consumer_index0 != bp->hw_tx_cons)
bnx2_tx_int(bp);
if (bp->status_blk->status_rx_quick_consumer_index0 != bp->hw_rx_cons) {
int orig_budget = *budget;
int work_done;
if (orig_budget > dev->quota)
orig_budget = dev->quota;
work_done = bnx2_rx_int(bp, orig_budget);
*budget -= work_done;
dev->quota -= work_done;
}
bp->last_status_idx = bp->status_blk->status_idx;
rmb();
if (!bnx2_has_work(bp)) {
netif_rx_complete(dev);
if (likely(bp->flags & USING_MSI_FLAG)) {
REG_WR(bp, BNX2_PCICFG_INT_ACK_CMD,
BNX2_PCICFG_INT_ACK_CMD_INDEX_VALID |
bp->last_status_idx);
return 0;
}
REG_WR(bp, BNX2_PCICFG_INT_ACK_CMD,
BNX2_PCICFG_INT_ACK_CMD_INDEX_VALID |
BNX2_PCICFG_INT_ACK_CMD_MASK_INT |
bp->last_status_idx);
REG_WR(bp, BNX2_PCICFG_INT_ACK_CMD,
BNX2_PCICFG_INT_ACK_CMD_INDEX_VALID |
bp->last_status_idx);
return 0;
}
return 1;
}
/* Called with rtnl_lock from vlan functions and also netif_tx_lock
* from set_multicast.
*/
static void
bnx2_set_rx_mode(struct net_device *dev)
{
struct bnx2 *bp = netdev_priv(dev);
u32 rx_mode, sort_mode;
int i;
spin_lock_bh(&bp->phy_lock);
rx_mode = bp->rx_mode & ~(BNX2_EMAC_RX_MODE_PROMISCUOUS |
BNX2_EMAC_RX_MODE_KEEP_VLAN_TAG);
sort_mode = 1 | BNX2_RPM_SORT_USER0_BC_EN;
#ifdef BCM_VLAN
if (!bp->vlgrp && !(bp->flags & ASF_ENABLE_FLAG))
rx_mode |= BNX2_EMAC_RX_MODE_KEEP_VLAN_TAG;
#else
if (!(bp->flags & ASF_ENABLE_FLAG))
rx_mode |= BNX2_EMAC_RX_MODE_KEEP_VLAN_TAG;
#endif
if (dev->flags & IFF_PROMISC) {
/* Promiscuous mode. */
rx_mode |= BNX2_EMAC_RX_MODE_PROMISCUOUS;
sort_mode |= BNX2_RPM_SORT_USER0_PROM_EN;
}
else if (dev->flags & IFF_ALLMULTI) {
for (i = 0; i < NUM_MC_HASH_REGISTERS; i++) {
REG_WR(bp, BNX2_EMAC_MULTICAST_HASH0 + (i * 4),
0xffffffff);
}
sort_mode |= BNX2_RPM_SORT_USER0_MC_EN;
}
else {
/* Accept one or more multicast(s). */
struct dev_mc_list *mclist;
u32 mc_filter[NUM_MC_HASH_REGISTERS];
u32 regidx;
u32 bit;
u32 crc;
memset(mc_filter, 0, 4 * NUM_MC_HASH_REGISTERS);
for (i = 0, mclist = dev->mc_list; mclist && i < dev->mc_count;
i++, mclist = mclist->next) {
crc = ether_crc_le(ETH_ALEN, mclist->dmi_addr);
bit = crc & 0xff;
regidx = (bit & 0xe0) >> 5;
bit &= 0x1f;
mc_filter[regidx] |= (1 << bit);
}
for (i = 0; i < NUM_MC_HASH_REGISTERS; i++) {
REG_WR(bp, BNX2_EMAC_MULTICAST_HASH0 + (i * 4),
mc_filter[i]);
}
sort_mode |= BNX2_RPM_SORT_USER0_MC_HSH_EN;
}
if (rx_mode != bp->rx_mode) {
bp->rx_mode = rx_mode;
REG_WR(bp, BNX2_EMAC_RX_MODE, rx_mode);
}
REG_WR(bp, BNX2_RPM_SORT_USER0, 0x0);
REG_WR(bp, BNX2_RPM_SORT_USER0, sort_mode);
REG_WR(bp, BNX2_RPM_SORT_USER0, sort_mode | BNX2_RPM_SORT_USER0_ENA);
spin_unlock_bh(&bp->phy_lock);
}
#define FW_BUF_SIZE 0x8000
static int
bnx2_gunzip_init(struct bnx2 *bp)
{
if ((bp->gunzip_buf = vmalloc(FW_BUF_SIZE)) == NULL)
goto gunzip_nomem1;
if ((bp->strm = kmalloc(sizeof(*bp->strm), GFP_KERNEL)) == NULL)
goto gunzip_nomem2;
bp->strm->workspace = kmalloc(zlib_inflate_workspacesize(), GFP_KERNEL);
if (bp->strm->workspace == NULL)
goto gunzip_nomem3;
return 0;
gunzip_nomem3:
kfree(bp->strm);
bp->strm = NULL;
gunzip_nomem2:
vfree(bp->gunzip_buf);
bp->gunzip_buf = NULL;
gunzip_nomem1:
printk(KERN_ERR PFX "%s: Cannot allocate firmware buffer for "
"uncompression.\n", bp->dev->name);
return -ENOMEM;
}
static void
bnx2_gunzip_end(struct bnx2 *bp)
{
kfree(bp->strm->workspace);
kfree(bp->strm);
bp->strm = NULL;
if (bp->gunzip_buf) {
vfree(bp->gunzip_buf);
bp->gunzip_buf = NULL;
}
}
static int
bnx2_gunzip(struct bnx2 *bp, u8 *zbuf, int len, void **outbuf, int *outlen)
{
int n, rc;
/* check gzip header */
if ((zbuf[0] != 0x1f) || (zbuf[1] != 0x8b) || (zbuf[2] != Z_DEFLATED))
return -EINVAL;
n = 10;
#define FNAME 0x8
if (zbuf[3] & FNAME)
while ((zbuf[n++] != 0) && (n < len));
bp->strm->next_in = zbuf + n;
bp->strm->avail_in = len - n;
bp->strm->next_out = bp->gunzip_buf;
bp->strm->avail_out = FW_BUF_SIZE;
rc = zlib_inflateInit2(bp->strm, -MAX_WBITS);
if (rc != Z_OK)
return rc;
rc = zlib_inflate(bp->strm, Z_FINISH);
*outlen = FW_BUF_SIZE - bp->strm->avail_out;
*outbuf = bp->gunzip_buf;
if ((rc != Z_OK) && (rc != Z_STREAM_END))
printk(KERN_ERR PFX "%s: Firmware decompression error: %s\n",
bp->dev->name, bp->strm->msg);
zlib_inflateEnd(bp->strm);
if (rc == Z_STREAM_END)
return 0;
return rc;
}
static void
load_rv2p_fw(struct bnx2 *bp, u32 *rv2p_code, u32 rv2p_code_len,
u32 rv2p_proc)
{
int i;
u32 val;
for (i = 0; i < rv2p_code_len; i += 8) {
REG_WR(bp, BNX2_RV2P_INSTR_HIGH, cpu_to_le32(*rv2p_code));
rv2p_code++;
REG_WR(bp, BNX2_RV2P_INSTR_LOW, cpu_to_le32(*rv2p_code));
rv2p_code++;
if (rv2p_proc == RV2P_PROC1) {
val = (i / 8) | BNX2_RV2P_PROC1_ADDR_CMD_RDWR;
REG_WR(bp, BNX2_RV2P_PROC1_ADDR_CMD, val);
}
else {
val = (i / 8) | BNX2_RV2P_PROC2_ADDR_CMD_RDWR;
REG_WR(bp, BNX2_RV2P_PROC2_ADDR_CMD, val);
}
}
/* Reset the processor, un-stall is done later. */
if (rv2p_proc == RV2P_PROC1) {
REG_WR(bp, BNX2_RV2P_COMMAND, BNX2_RV2P_COMMAND_PROC1_RESET);
}
else {
REG_WR(bp, BNX2_RV2P_COMMAND, BNX2_RV2P_COMMAND_PROC2_RESET);
}
}
static void
load_cpu_fw(struct bnx2 *bp, struct cpu_reg *cpu_reg, struct fw_info *fw)
{
u32 offset;
u32 val;
/* Halt the CPU. */
val = REG_RD_IND(bp, cpu_reg->mode);
val |= cpu_reg->mode_value_halt;
REG_WR_IND(bp, cpu_reg->mode, val);
REG_WR_IND(bp, cpu_reg->state, cpu_reg->state_value_clear);
/* Load the Text area. */
offset = cpu_reg->spad_base + (fw->text_addr - cpu_reg->mips_view_base);
if (fw->text) {
int j;
for (j = 0; j < (fw->text_len / 4); j++, offset += 4) {
REG_WR_IND(bp, offset, cpu_to_le32(fw->text[j]));
}
}
/* Load the Data area. */
offset = cpu_reg->spad_base + (fw->data_addr - cpu_reg->mips_view_base);
if (fw->data) {
int j;
for (j = 0; j < (fw->data_len / 4); j++, offset += 4) {
REG_WR_IND(bp, offset, fw->data[j]);
}
}
/* Load the SBSS area. */
offset = cpu_reg->spad_base + (fw->sbss_addr - cpu_reg->mips_view_base);
if (fw->sbss) {
int j;
for (j = 0; j < (fw->sbss_len / 4); j++, offset += 4) {
REG_WR_IND(bp, offset, fw->sbss[j]);
}
}
/* Load the BSS area. */
offset = cpu_reg->spad_base + (fw->bss_addr - cpu_reg->mips_view_base);
if (fw->bss) {
int j;
for (j = 0; j < (fw->bss_len/4); j++, offset += 4) {
REG_WR_IND(bp, offset, fw->bss[j]);
}
}
/* Load the Read-Only area. */
offset = cpu_reg->spad_base +
(fw->rodata_addr - cpu_reg->mips_view_base);
if (fw->rodata) {
int j;
for (j = 0; j < (fw->rodata_len / 4); j++, offset += 4) {
REG_WR_IND(bp, offset, fw->rodata[j]);
}
}
/* Clear the pre-fetch instruction. */
REG_WR_IND(bp, cpu_reg->inst, 0);
REG_WR_IND(bp, cpu_reg->pc, fw->start_addr);
/* Start the CPU. */
val = REG_RD_IND(bp, cpu_reg->mode);
val &= ~cpu_reg->mode_value_halt;
REG_WR_IND(bp, cpu_reg->state, cpu_reg->state_value_clear);
REG_WR_IND(bp, cpu_reg->mode, val);
}
static int
bnx2_init_cpus(struct bnx2 *bp)
{
struct cpu_reg cpu_reg;
struct fw_info fw;
int rc = 0;
void *text;
u32 text_len;
if ((rc = bnx2_gunzip_init(bp)) != 0)
return rc;
/* Initialize the RV2P processor. */
rc = bnx2_gunzip(bp, bnx2_rv2p_proc1, sizeof(bnx2_rv2p_proc1), &text,
&text_len);
if (rc)
goto init_cpu_err;
load_rv2p_fw(bp, text, text_len, RV2P_PROC1);
rc = bnx2_gunzip(bp, bnx2_rv2p_proc2, sizeof(bnx2_rv2p_proc2), &text,
&text_len);
if (rc)
goto init_cpu_err;
load_rv2p_fw(bp, text, text_len, RV2P_PROC2);
/* Initialize the RX Processor. */
cpu_reg.mode = BNX2_RXP_CPU_MODE;
cpu_reg.mode_value_halt = BNX2_RXP_CPU_MODE_SOFT_HALT;
cpu_reg.mode_value_sstep = BNX2_RXP_CPU_MODE_STEP_ENA;
cpu_reg.state = BNX2_RXP_CPU_STATE;
cpu_reg.state_value_clear = 0xffffff;
cpu_reg.gpr0 = BNX2_RXP_CPU_REG_FILE;
cpu_reg.evmask = BNX2_RXP_CPU_EVENT_MASK;
cpu_reg.pc = BNX2_RXP_CPU_PROGRAM_COUNTER;
cpu_reg.inst = BNX2_RXP_CPU_INSTRUCTION;
cpu_reg.bp = BNX2_RXP_CPU_HW_BREAKPOINT;
cpu_reg.spad_base = BNX2_RXP_SCRATCH;
cpu_reg.mips_view_base = 0x8000000;
fw.ver_major = bnx2_RXP_b06FwReleaseMajor;
fw.ver_minor = bnx2_RXP_b06FwReleaseMinor;
fw.ver_fix = bnx2_RXP_b06FwReleaseFix;
fw.start_addr = bnx2_RXP_b06FwStartAddr;
fw.text_addr = bnx2_RXP_b06FwTextAddr;
fw.text_len = bnx2_RXP_b06FwTextLen;
fw.text_index = 0;
rc = bnx2_gunzip(bp, bnx2_RXP_b06FwText, sizeof(bnx2_RXP_b06FwText),
&text, &text_len);
if (rc)
goto init_cpu_err;
fw.text = text;
fw.data_addr = bnx2_RXP_b06FwDataAddr;
fw.data_len = bnx2_RXP_b06FwDataLen;
fw.data_index = 0;
fw.data = bnx2_RXP_b06FwData;
fw.sbss_addr = bnx2_RXP_b06FwSbssAddr;
fw.sbss_len = bnx2_RXP_b06FwSbssLen;
fw.sbss_index = 0;
fw.sbss = bnx2_RXP_b06FwSbss;
fw.bss_addr = bnx2_RXP_b06FwBssAddr;
fw.bss_len = bnx2_RXP_b06FwBssLen;
fw.bss_index = 0;
fw.bss = bnx2_RXP_b06FwBss;
fw.rodata_addr = bnx2_RXP_b06FwRodataAddr;
fw.rodata_len = bnx2_RXP_b06FwRodataLen;
fw.rodata_index = 0;
fw.rodata = bnx2_RXP_b06FwRodata;
load_cpu_fw(bp, &cpu_reg, &fw);
/* Initialize the TX Processor. */
cpu_reg.mode = BNX2_TXP_CPU_MODE;
cpu_reg.mode_value_halt = BNX2_TXP_CPU_MODE_SOFT_HALT;
cpu_reg.mode_value_sstep = BNX2_TXP_CPU_MODE_STEP_ENA;
cpu_reg.state = BNX2_TXP_CPU_STATE;
cpu_reg.state_value_clear = 0xffffff;
cpu_reg.gpr0 = BNX2_TXP_CPU_REG_FILE;
cpu_reg.evmask = BNX2_TXP_CPU_EVENT_MASK;
cpu_reg.pc = BNX2_TXP_CPU_PROGRAM_COUNTER;
cpu_reg.inst = BNX2_TXP_CPU_INSTRUCTION;
cpu_reg.bp = BNX2_TXP_CPU_HW_BREAKPOINT;
cpu_reg.spad_base = BNX2_TXP_SCRATCH;
cpu_reg.mips_view_base = 0x8000000;
fw.ver_major = bnx2_TXP_b06FwReleaseMajor;
fw.ver_minor = bnx2_TXP_b06FwReleaseMinor;
fw.ver_fix = bnx2_TXP_b06FwReleaseFix;
fw.start_addr = bnx2_TXP_b06FwStartAddr;
fw.text_addr = bnx2_TXP_b06FwTextAddr;
fw.text_len = bnx2_TXP_b06FwTextLen;
fw.text_index = 0;
rc = bnx2_gunzip(bp, bnx2_TXP_b06FwText, sizeof(bnx2_TXP_b06FwText),
&text, &text_len);
if (rc)
goto init_cpu_err;
fw.text = text;
fw.data_addr = bnx2_TXP_b06FwDataAddr;
fw.data_len = bnx2_TXP_b06FwDataLen;
fw.data_index = 0;
fw.data = bnx2_TXP_b06FwData;
fw.sbss_addr = bnx2_TXP_b06FwSbssAddr;
fw.sbss_len = bnx2_TXP_b06FwSbssLen;
fw.sbss_index = 0;
fw.sbss = bnx2_TXP_b06FwSbss;
fw.bss_addr = bnx2_TXP_b06FwBssAddr;
fw.bss_len = bnx2_TXP_b06FwBssLen;
fw.bss_index = 0;
fw.bss = bnx2_TXP_b06FwBss;
fw.rodata_addr = bnx2_TXP_b06FwRodataAddr;
fw.rodata_len = bnx2_TXP_b06FwRodataLen;
fw.rodata_index = 0;
fw.rodata = bnx2_TXP_b06FwRodata;
load_cpu_fw(bp, &cpu_reg, &fw);
/* Initialize the TX Patch-up Processor. */
cpu_reg.mode = BNX2_TPAT_CPU_MODE;
cpu_reg.mode_value_halt = BNX2_TPAT_CPU_MODE_SOFT_HALT;
cpu_reg.mode_value_sstep = BNX2_TPAT_CPU_MODE_STEP_ENA;
cpu_reg.state = BNX2_TPAT_CPU_STATE;
cpu_reg.state_value_clear = 0xffffff;
cpu_reg.gpr0 = BNX2_TPAT_CPU_REG_FILE;
cpu_reg.evmask = BNX2_TPAT_CPU_EVENT_MASK;
cpu_reg.pc = BNX2_TPAT_CPU_PROGRAM_COUNTER;
cpu_reg.inst = BNX2_TPAT_CPU_INSTRUCTION;
cpu_reg.bp = BNX2_TPAT_CPU_HW_BREAKPOINT;
cpu_reg.spad_base = BNX2_TPAT_SCRATCH;
cpu_reg.mips_view_base = 0x8000000;
fw.ver_major = bnx2_TPAT_b06FwReleaseMajor;
fw.ver_minor = bnx2_TPAT_b06FwReleaseMinor;
fw.ver_fix = bnx2_TPAT_b06FwReleaseFix;
fw.start_addr = bnx2_TPAT_b06FwStartAddr;
fw.text_addr = bnx2_TPAT_b06FwTextAddr;
fw.text_len = bnx2_TPAT_b06FwTextLen;
fw.text_index = 0;
rc = bnx2_gunzip(bp, bnx2_TPAT_b06FwText, sizeof(bnx2_TPAT_b06FwText),
&text, &text_len);
if (rc)
goto init_cpu_err;
fw.text = text;
fw.data_addr = bnx2_TPAT_b06FwDataAddr;
fw.data_len = bnx2_TPAT_b06FwDataLen;
fw.data_index = 0;
fw.data = bnx2_TPAT_b06FwData;
fw.sbss_addr = bnx2_TPAT_b06FwSbssAddr;
fw.sbss_len = bnx2_TPAT_b06FwSbssLen;
fw.sbss_index = 0;
fw.sbss = bnx2_TPAT_b06FwSbss;
fw.bss_addr = bnx2_TPAT_b06FwBssAddr;
fw.bss_len = bnx2_TPAT_b06FwBssLen;
fw.bss_index = 0;
fw.bss = bnx2_TPAT_b06FwBss;
fw.rodata_addr = bnx2_TPAT_b06FwRodataAddr;
fw.rodata_len = bnx2_TPAT_b06FwRodataLen;
fw.rodata_index = 0;
fw.rodata = bnx2_TPAT_b06FwRodata;
load_cpu_fw(bp, &cpu_reg, &fw);
/* Initialize the Completion Processor. */
cpu_reg.mode = BNX2_COM_CPU_MODE;
cpu_reg.mode_value_halt = BNX2_COM_CPU_MODE_SOFT_HALT;
cpu_reg.mode_value_sstep = BNX2_COM_CPU_MODE_STEP_ENA;
cpu_reg.state = BNX2_COM_CPU_STATE;
cpu_reg.state_value_clear = 0xffffff;
cpu_reg.gpr0 = BNX2_COM_CPU_REG_FILE;
cpu_reg.evmask = BNX2_COM_CPU_EVENT_MASK;
cpu_reg.pc = BNX2_COM_CPU_PROGRAM_COUNTER;
cpu_reg.inst = BNX2_COM_CPU_INSTRUCTION;
cpu_reg.bp = BNX2_COM_CPU_HW_BREAKPOINT;
cpu_reg.spad_base = BNX2_COM_SCRATCH;
cpu_reg.mips_view_base = 0x8000000;
fw.ver_major = bnx2_COM_b06FwReleaseMajor;
fw.ver_minor = bnx2_COM_b06FwReleaseMinor;
fw.ver_fix = bnx2_COM_b06FwReleaseFix;
fw.start_addr = bnx2_COM_b06FwStartAddr;
fw.text_addr = bnx2_COM_b06FwTextAddr;
fw.text_len = bnx2_COM_b06FwTextLen;
fw.text_index = 0;
rc = bnx2_gunzip(bp, bnx2_COM_b06FwText, sizeof(bnx2_COM_b06FwText),
&text, &text_len);
if (rc)
goto init_cpu_err;
fw.text = text;
fw.data_addr = bnx2_COM_b06FwDataAddr;
fw.data_len = bnx2_COM_b06FwDataLen;
fw.data_index = 0;
fw.data = bnx2_COM_b06FwData;
fw.sbss_addr = bnx2_COM_b06FwSbssAddr;
fw.sbss_len = bnx2_COM_b06FwSbssLen;
fw.sbss_index = 0;
fw.sbss = bnx2_COM_b06FwSbss;
fw.bss_addr = bnx2_COM_b06FwBssAddr;
fw.bss_len = bnx2_COM_b06FwBssLen;
fw.bss_index = 0;
fw.bss = bnx2_COM_b06FwBss;
fw.rodata_addr = bnx2_COM_b06FwRodataAddr;
fw.rodata_len = bnx2_COM_b06FwRodataLen;
fw.rodata_index = 0;
fw.rodata = bnx2_COM_b06FwRodata;
load_cpu_fw(bp, &cpu_reg, &fw);
init_cpu_err:
bnx2_gunzip_end(bp);
return rc;
}
static int
bnx2_set_power_state(struct bnx2 *bp, pci_power_t state)
{
u16 pmcsr;
pci_read_config_word(bp->pdev, bp->pm_cap + PCI_PM_CTRL, &pmcsr);
switch (state) {
case PCI_D0: {
u32 val;
pci_write_config_word(bp->pdev, bp->pm_cap + PCI_PM_CTRL,
(pmcsr & ~PCI_PM_CTRL_STATE_MASK) |
PCI_PM_CTRL_PME_STATUS);
if (pmcsr & PCI_PM_CTRL_STATE_MASK)
/* delay required during transition out of D3hot */
msleep(20);
val = REG_RD(bp, BNX2_EMAC_MODE);
val |= BNX2_EMAC_MODE_MPKT_RCVD | BNX2_EMAC_MODE_ACPI_RCVD;
val &= ~BNX2_EMAC_MODE_MPKT;
REG_WR(bp, BNX2_EMAC_MODE, val);
val = REG_RD(bp, BNX2_RPM_CONFIG);
val &= ~BNX2_RPM_CONFIG_ACPI_ENA;
REG_WR(bp, BNX2_RPM_CONFIG, val);
break;
}
case PCI_D3hot: {
int i;
u32 val, wol_msg;
if (bp->wol) {
u32 advertising;
u8 autoneg;
autoneg = bp->autoneg;
advertising = bp->advertising;
bp->autoneg = AUTONEG_SPEED;
bp->advertising = ADVERTISED_10baseT_Half |
ADVERTISED_10baseT_Full |
ADVERTISED_100baseT_Half |
ADVERTISED_100baseT_Full |
ADVERTISED_Autoneg;
bnx2_setup_copper_phy(bp);
bp->autoneg = autoneg;
bp->advertising = advertising;
bnx2_set_mac_addr(bp);
val = REG_RD(bp, BNX2_EMAC_MODE);
/* Enable port mode. */
val &= ~BNX2_EMAC_MODE_PORT;
val |= BNX2_EMAC_MODE_PORT_MII |
BNX2_EMAC_MODE_MPKT_RCVD |
BNX2_EMAC_MODE_ACPI_RCVD |
BNX2_EMAC_MODE_MPKT;
REG_WR(bp, BNX2_EMAC_MODE, val);
/* receive all multicast */
for (i = 0; i < NUM_MC_HASH_REGISTERS; i++) {
REG_WR(bp, BNX2_EMAC_MULTICAST_HASH0 + (i * 4),
0xffffffff);
}
REG_WR(bp, BNX2_EMAC_RX_MODE,
BNX2_EMAC_RX_MODE_SORT_MODE);
val = 1 | BNX2_RPM_SORT_USER0_BC_EN |
BNX2_RPM_SORT_USER0_MC_EN;
REG_WR(bp, BNX2_RPM_SORT_USER0, 0x0);
REG_WR(bp, BNX2_RPM_SORT_USER0, val);
REG_WR(bp, BNX2_RPM_SORT_USER0, val |
BNX2_RPM_SORT_USER0_ENA);
/* Need to enable EMAC and RPM for WOL. */
REG_WR(bp, BNX2_MISC_ENABLE_SET_BITS,
BNX2_MISC_ENABLE_SET_BITS_RX_PARSER_MAC_ENABLE |
BNX2_MISC_ENABLE_SET_BITS_TX_HEADER_Q_ENABLE |
BNX2_MISC_ENABLE_SET_BITS_EMAC_ENABLE);
val = REG_RD(bp, BNX2_RPM_CONFIG);
val &= ~BNX2_RPM_CONFIG_ACPI_ENA;
REG_WR(bp, BNX2_RPM_CONFIG, val);
wol_msg = BNX2_DRV_MSG_CODE_SUSPEND_WOL;
}
else {
wol_msg = BNX2_DRV_MSG_CODE_SUSPEND_NO_WOL;
}
if (!(bp->flags & NO_WOL_FLAG))
bnx2_fw_sync(bp, BNX2_DRV_MSG_DATA_WAIT3 | wol_msg, 0);
pmcsr &= ~PCI_PM_CTRL_STATE_MASK;
if ((CHIP_ID(bp) == CHIP_ID_5706_A0) ||
(CHIP_ID(bp) == CHIP_ID_5706_A1)) {
if (bp->wol)
pmcsr |= 3;
}
else {
pmcsr |= 3;
}
if (bp->wol) {
pmcsr |= PCI_PM_CTRL_PME_ENABLE;
}
pci_write_config_word(bp->pdev, bp->pm_cap + PCI_PM_CTRL,
pmcsr);
/* No more memory access after this point until
* device is brought back to D0.
*/
udelay(50);
break;
}
default:
return -EINVAL;
}
return 0;
}
static int
bnx2_acquire_nvram_lock(struct bnx2 *bp)
{
u32 val;
int j;
/* Request access to the flash interface. */
REG_WR(bp, BNX2_NVM_SW_ARB, BNX2_NVM_SW_ARB_ARB_REQ_SET2);
for (j = 0; j < NVRAM_TIMEOUT_COUNT; j++) {
val = REG_RD(bp, BNX2_NVM_SW_ARB);
if (val & BNX2_NVM_SW_ARB_ARB_ARB2)
break;
udelay(5);
}
if (j >= NVRAM_TIMEOUT_COUNT)
return -EBUSY;
return 0;
}
static int
bnx2_release_nvram_lock(struct bnx2 *bp)
{
int j;
u32 val;
/* Relinquish nvram interface. */
REG_WR(bp, BNX2_NVM_SW_ARB, BNX2_NVM_SW_ARB_ARB_REQ_CLR2);
for (j = 0; j < NVRAM_TIMEOUT_COUNT; j++) {
val = REG_RD(bp, BNX2_NVM_SW_ARB);
if (!(val & BNX2_NVM_SW_ARB_ARB_ARB2))
break;
udelay(5);
}
if (j >= NVRAM_TIMEOUT_COUNT)
return -EBUSY;
return 0;
}
static int
bnx2_enable_nvram_write(struct bnx2 *bp)
{
u32 val;
val = REG_RD(bp, BNX2_MISC_CFG);
REG_WR(bp, BNX2_MISC_CFG, val | BNX2_MISC_CFG_NVM_WR_EN_PCI);
if (!bp->flash_info->buffered) {
int j;
REG_WR(bp, BNX2_NVM_COMMAND, BNX2_NVM_COMMAND_DONE);
REG_WR(bp, BNX2_NVM_COMMAND,
BNX2_NVM_COMMAND_WREN | BNX2_NVM_COMMAND_DOIT);
for (j = 0; j < NVRAM_TIMEOUT_COUNT; j++) {
udelay(5);
val = REG_RD(bp, BNX2_NVM_COMMAND);
if (val & BNX2_NVM_COMMAND_DONE)
break;
}
if (j >= NVRAM_TIMEOUT_COUNT)
return -EBUSY;
}
return 0;
}
static void
bnx2_disable_nvram_write(struct bnx2 *bp)
{
u32 val;
val = REG_RD(bp, BNX2_MISC_CFG);
REG_WR(bp, BNX2_MISC_CFG, val & ~BNX2_MISC_CFG_NVM_WR_EN);
}
static void
bnx2_enable_nvram_access(struct bnx2 *bp)
{
u32 val;
val = REG_RD(bp, BNX2_NVM_ACCESS_ENABLE);
/* Enable both bits, even on read. */
REG_WR(bp, BNX2_NVM_ACCESS_ENABLE,
val | BNX2_NVM_ACCESS_ENABLE_EN | BNX2_NVM_ACCESS_ENABLE_WR_EN);
}
static void
bnx2_disable_nvram_access(struct bnx2 *bp)
{
u32 val;
val = REG_RD(bp, BNX2_NVM_ACCESS_ENABLE);
/* Disable both bits, even after read. */
REG_WR(bp, BNX2_NVM_ACCESS_ENABLE,
val & ~(BNX2_NVM_ACCESS_ENABLE_EN |
BNX2_NVM_ACCESS_ENABLE_WR_EN));
}
static int
bnx2_nvram_erase_page(struct bnx2 *bp, u32 offset)
{
u32 cmd;
int j;
if (bp->flash_info->buffered)
/* Buffered flash, no erase needed */
return 0;
/* Build an erase command */
cmd = BNX2_NVM_COMMAND_ERASE | BNX2_NVM_COMMAND_WR |
BNX2_NVM_COMMAND_DOIT;
/* Need to clear DONE bit separately. */
REG_WR(bp, BNX2_NVM_COMMAND, BNX2_NVM_COMMAND_DONE);
/* Address of the NVRAM to read from. */
REG_WR(bp, BNX2_NVM_ADDR, offset & BNX2_NVM_ADDR_NVM_ADDR_VALUE);
/* Issue an erase command. */
REG_WR(bp, BNX2_NVM_COMMAND, cmd);
/* Wait for completion. */
for (j = 0; j < NVRAM_TIMEOUT_COUNT; j++) {
u32 val;
udelay(5);
val = REG_RD(bp, BNX2_NVM_COMMAND);
if (val & BNX2_NVM_COMMAND_DONE)
break;
}
if (j >= NVRAM_TIMEOUT_COUNT)
return -EBUSY;
return 0;