| /* bnx2.c: Broadcom NX2 network driver. |
| * |
| * Copyright (c) 2004-2010 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) |
| */ |
| |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| |
| #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 <linux/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> |
| #include <linux/if_vlan.h> |
| #include <net/ip.h> |
| #include <net/tcp.h> |
| #include <net/checksum.h> |
| #include <linux/workqueue.h> |
| #include <linux/crc32.h> |
| #include <linux/prefetch.h> |
| #include <linux/cache.h> |
| #include <linux/firmware.h> |
| #include <linux/log2.h> |
| #include <linux/aer.h> |
| |
| #if defined(CONFIG_CNIC) || defined(CONFIG_CNIC_MODULE) |
| #define BCM_CNIC 1 |
| #include "cnic_if.h" |
| #endif |
| #include "bnx2.h" |
| #include "bnx2_fw.h" |
| |
| #define DRV_MODULE_NAME "bnx2" |
| #define DRV_MODULE_VERSION "2.0.18" |
| #define DRV_MODULE_RELDATE "Oct 7, 2010" |
| #define FW_MIPS_FILE_06 "bnx2/bnx2-mips-06-6.0.15.fw" |
| #define FW_RV2P_FILE_06 "bnx2/bnx2-rv2p-06-6.0.15.fw" |
| #define FW_MIPS_FILE_09 "bnx2/bnx2-mips-09-6.0.17.fw" |
| #define FW_RV2P_FILE_09_Ax "bnx2/bnx2-rv2p-09ax-6.0.17.fw" |
| #define FW_RV2P_FILE_09 "bnx2/bnx2-rv2p-09-6.0.17.fw" |
| |
| #define RUN_AT(x) (jiffies + (x)) |
| |
| /* Time in jiffies before concluding the transmitter is hung. */ |
| #define TX_TIMEOUT (5*HZ) |
| |
| static 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/5709/5716 Driver"); |
| MODULE_LICENSE("GPL"); |
| MODULE_VERSION(DRV_MODULE_VERSION); |
| MODULE_FIRMWARE(FW_MIPS_FILE_06); |
| MODULE_FIRMWARE(FW_RV2P_FILE_06); |
| MODULE_FIRMWARE(FW_MIPS_FILE_09); |
| MODULE_FIRMWARE(FW_RV2P_FILE_09); |
| MODULE_FIRMWARE(FW_RV2P_FILE_09_Ax); |
| |
| 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, |
| BCM5709, |
| BCM5709S, |
| BCM5716, |
| BCM5716S, |
| } board_t; |
| |
| /* indexed by board_t, above */ |
| static 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" }, |
| { "Broadcom NetXtreme II BCM5709 1000Base-T" }, |
| { "Broadcom NetXtreme II BCM5709 1000Base-SX" }, |
| { "Broadcom NetXtreme II BCM5716 1000Base-T" }, |
| { "Broadcom NetXtreme II BCM5716 1000Base-SX" }, |
| }; |
| |
| static DEFINE_PCI_DEVICE_TABLE(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 }, |
| { PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_5709, |
| PCI_ANY_ID, PCI_ANY_ID, 0, 0, BCM5709 }, |
| { PCI_VENDOR_ID_BROADCOM, PCI_DEVICE_ID_NX2_5709S, |
| PCI_ANY_ID, PCI_ANY_ID, 0, 0, BCM5709S }, |
| { PCI_VENDOR_ID_BROADCOM, 0x163b, |
| PCI_ANY_ID, PCI_ANY_ID, 0, 0, BCM5716 }, |
| { PCI_VENDOR_ID_BROADCOM, 0x163c, |
| PCI_ANY_ID, PCI_ANY_ID, 0, 0, BCM5716S }, |
| { 0, } |
| }; |
| |
| static const struct flash_spec flash_table[] = |
| { |
| #define BUFFERED_FLAGS (BNX2_NV_BUFFERED | BNX2_NV_TRANSLATE) |
| #define NONBUFFERED_FLAGS (BNX2_NV_WREN) |
| /* Slow EEPROM */ |
| {0x00000000, 0x40830380, 0x009f0081, 0xa184a053, 0xaf000400, |
| BUFFERED_FLAGS, SEEPROM_PAGE_BITS, SEEPROM_PAGE_SIZE, |
| SEEPROM_BYTE_ADDR_MASK, SEEPROM_TOTAL_SIZE, |
| "EEPROM - slow"}, |
| /* Expansion entry 0001 */ |
| {0x08000002, 0x4b808201, 0x00050081, 0x03840253, 0xaf020406, |
| NONBUFFERED_FLAGS, 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, |
| NONBUFFERED_FLAGS, 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, |
| NONBUFFERED_FLAGS, 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, |
| NONBUFFERED_FLAGS, 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, |
| NONBUFFERED_FLAGS, 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, |
| NONBUFFERED_FLAGS, 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, |
| NONBUFFERED_FLAGS, 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, |
| BUFFERED_FLAGS, SEEPROM_PAGE_BITS, SEEPROM_PAGE_SIZE, |
| SEEPROM_BYTE_ADDR_MASK, SEEPROM_TOTAL_SIZE, |
| "EEPROM - fast"}, |
| /* Expansion entry 1001 */ |
| {0x2a000002, 0x6b808201, 0x00050081, 0x03840253, 0xaf020406, |
| NONBUFFERED_FLAGS, SAIFUN_FLASH_PAGE_BITS, SAIFUN_FLASH_PAGE_SIZE, |
| SAIFUN_FLASH_BYTE_ADDR_MASK, 0, |
| "Entry 1001"}, |
| /* Expansion entry 1010 */ |
| {0x26000001, 0x67808201, 0x00050081, 0x03840253, 0xaf020406, |
| NONBUFFERED_FLAGS, 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, |
| BUFFERED_FLAGS, 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, |
| NONBUFFERED_FLAGS, SAIFUN_FLASH_PAGE_BITS, SAIFUN_FLASH_PAGE_SIZE, |
| SAIFUN_FLASH_BYTE_ADDR_MASK, 0, |
| "Entry 1100"}, |
| /* Expansion entry 1101 */ |
| {0x3b000002, 0x7b808201, 0x00050081, 0x03840253, 0xaf020406, |
| NONBUFFERED_FLAGS, 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, |
| BUFFERED_FLAGS, 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, |
| BUFFERED_FLAGS, BUFFERED_FLASH_PAGE_BITS, BUFFERED_FLASH_PAGE_SIZE, |
| BUFFERED_FLASH_BYTE_ADDR_MASK, BUFFERED_FLASH_TOTAL_SIZE*2, |
| "Buffered flash (256kB)"}, |
| }; |
| |
| static const struct flash_spec flash_5709 = { |
| .flags = BNX2_NV_BUFFERED, |
| .page_bits = BCM5709_FLASH_PAGE_BITS, |
| .page_size = BCM5709_FLASH_PAGE_SIZE, |
| .addr_mask = BCM5709_FLASH_BYTE_ADDR_MASK, |
| .total_size = BUFFERED_FLASH_TOTAL_SIZE*2, |
| .name = "5709 Buffered flash (256kB)", |
| }; |
| |
| MODULE_DEVICE_TABLE(pci, bnx2_pci_tbl); |
| |
| static void bnx2_init_napi(struct bnx2 *bp); |
| static void bnx2_del_napi(struct bnx2 *bp); |
| |
| static inline u32 bnx2_tx_avail(struct bnx2 *bp, struct bnx2_tx_ring_info *txr) |
| { |
| u32 diff; |
| |
| /* Tell compiler to fetch tx_prod and tx_cons from memory. */ |
| barrier(); |
| |
| /* The ring uses 256 indices for 255 entries, one of them |
| * needs to be skipped. |
| */ |
| diff = txr->tx_prod - txr->tx_cons; |
| if (unlikely(diff >= TX_DESC_CNT)) { |
| diff &= 0xffff; |
| if (diff == TX_DESC_CNT) |
| diff = MAX_TX_DESC_CNT; |
| } |
| return bp->tx_ring_size - diff; |
| } |
| |
| static u32 |
| bnx2_reg_rd_ind(struct bnx2 *bp, u32 offset) |
| { |
| u32 val; |
| |
| spin_lock_bh(&bp->indirect_lock); |
| REG_WR(bp, BNX2_PCICFG_REG_WINDOW_ADDRESS, offset); |
| val = REG_RD(bp, BNX2_PCICFG_REG_WINDOW); |
| spin_unlock_bh(&bp->indirect_lock); |
| return val; |
| } |
| |
| static void |
| bnx2_reg_wr_ind(struct bnx2 *bp, u32 offset, u32 val) |
| { |
| spin_lock_bh(&bp->indirect_lock); |
| REG_WR(bp, BNX2_PCICFG_REG_WINDOW_ADDRESS, offset); |
| REG_WR(bp, BNX2_PCICFG_REG_WINDOW, val); |
| spin_unlock_bh(&bp->indirect_lock); |
| } |
| |
| static void |
| bnx2_shmem_wr(struct bnx2 *bp, u32 offset, u32 val) |
| { |
| bnx2_reg_wr_ind(bp, bp->shmem_base + offset, val); |
| } |
| |
| static u32 |
| bnx2_shmem_rd(struct bnx2 *bp, u32 offset) |
| { |
| return bnx2_reg_rd_ind(bp, bp->shmem_base + offset); |
| } |
| |
| static void |
| bnx2_ctx_wr(struct bnx2 *bp, u32 cid_addr, u32 offset, u32 val) |
| { |
| offset += cid_addr; |
| spin_lock_bh(&bp->indirect_lock); |
| if (CHIP_NUM(bp) == CHIP_NUM_5709) { |
| int i; |
| |
| REG_WR(bp, BNX2_CTX_CTX_DATA, val); |
| REG_WR(bp, BNX2_CTX_CTX_CTRL, |
| offset | BNX2_CTX_CTX_CTRL_WRITE_REQ); |
| for (i = 0; i < 5; i++) { |
| val = REG_RD(bp, BNX2_CTX_CTX_CTRL); |
| if ((val & BNX2_CTX_CTX_CTRL_WRITE_REQ) == 0) |
| break; |
| udelay(5); |
| } |
| } else { |
| REG_WR(bp, BNX2_CTX_DATA_ADR, offset); |
| REG_WR(bp, BNX2_CTX_DATA, val); |
| } |
| spin_unlock_bh(&bp->indirect_lock); |
| } |
| |
| #ifdef BCM_CNIC |
| static int |
| bnx2_drv_ctl(struct net_device *dev, struct drv_ctl_info *info) |
| { |
| struct bnx2 *bp = netdev_priv(dev); |
| struct drv_ctl_io *io = &info->data.io; |
| |
| switch (info->cmd) { |
| case DRV_CTL_IO_WR_CMD: |
| bnx2_reg_wr_ind(bp, io->offset, io->data); |
| break; |
| case DRV_CTL_IO_RD_CMD: |
| io->data = bnx2_reg_rd_ind(bp, io->offset); |
| break; |
| case DRV_CTL_CTX_WR_CMD: |
| bnx2_ctx_wr(bp, io->cid_addr, io->offset, io->data); |
| break; |
| default: |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static void bnx2_setup_cnic_irq_info(struct bnx2 *bp) |
| { |
| struct cnic_eth_dev *cp = &bp->cnic_eth_dev; |
| struct bnx2_napi *bnapi = &bp->bnx2_napi[0]; |
| int sb_id; |
| |
| if (bp->flags & BNX2_FLAG_USING_MSIX) { |
| cp->drv_state |= CNIC_DRV_STATE_USING_MSIX; |
| bnapi->cnic_present = 0; |
| sb_id = bp->irq_nvecs; |
| cp->irq_arr[0].irq_flags |= CNIC_IRQ_FL_MSIX; |
| } else { |
| cp->drv_state &= ~CNIC_DRV_STATE_USING_MSIX; |
| bnapi->cnic_tag = bnapi->last_status_idx; |
| bnapi->cnic_present = 1; |
| sb_id = 0; |
| cp->irq_arr[0].irq_flags &= ~CNIC_IRQ_FL_MSIX; |
| } |
| |
| cp->irq_arr[0].vector = bp->irq_tbl[sb_id].vector; |
| cp->irq_arr[0].status_blk = (void *) |
| ((unsigned long) bnapi->status_blk.msi + |
| (BNX2_SBLK_MSIX_ALIGN_SIZE * sb_id)); |
| cp->irq_arr[0].status_blk_num = sb_id; |
| cp->num_irq = 1; |
| } |
| |
| static int bnx2_register_cnic(struct net_device *dev, struct cnic_ops *ops, |
| void *data) |
| { |
| struct bnx2 *bp = netdev_priv(dev); |
| struct cnic_eth_dev *cp = &bp->cnic_eth_dev; |
| |
| if (ops == NULL) |
| return -EINVAL; |
| |
| if (cp->drv_state & CNIC_DRV_STATE_REGD) |
| return -EBUSY; |
| |
| bp->cnic_data = data; |
| rcu_assign_pointer(bp->cnic_ops, ops); |
| |
| cp->num_irq = 0; |
| cp->drv_state = CNIC_DRV_STATE_REGD; |
| |
| bnx2_setup_cnic_irq_info(bp); |
| |
| return 0; |
| } |
| |
| static int bnx2_unregister_cnic(struct net_device *dev) |
| { |
| struct bnx2 *bp = netdev_priv(dev); |
| struct bnx2_napi *bnapi = &bp->bnx2_napi[0]; |
| struct cnic_eth_dev *cp = &bp->cnic_eth_dev; |
| |
| mutex_lock(&bp->cnic_lock); |
| cp->drv_state = 0; |
| bnapi->cnic_present = 0; |
| rcu_assign_pointer(bp->cnic_ops, NULL); |
| mutex_unlock(&bp->cnic_lock); |
| synchronize_rcu(); |
| return 0; |
| } |
| |
| struct cnic_eth_dev *bnx2_cnic_probe(struct net_device *dev) |
| { |
| struct bnx2 *bp = netdev_priv(dev); |
| struct cnic_eth_dev *cp = &bp->cnic_eth_dev; |
| |
| cp->drv_owner = THIS_MODULE; |
| cp->chip_id = bp->chip_id; |
| cp->pdev = bp->pdev; |
| cp->io_base = bp->regview; |
| cp->drv_ctl = bnx2_drv_ctl; |
| cp->drv_register_cnic = bnx2_register_cnic; |
| cp->drv_unregister_cnic = bnx2_unregister_cnic; |
| |
| return cp; |
| } |
| EXPORT_SYMBOL(bnx2_cnic_probe); |
| |
| static void |
| bnx2_cnic_stop(struct bnx2 *bp) |
| { |
| struct cnic_ops *c_ops; |
| struct cnic_ctl_info info; |
| |
| mutex_lock(&bp->cnic_lock); |
| c_ops = bp->cnic_ops; |
| if (c_ops) { |
| info.cmd = CNIC_CTL_STOP_CMD; |
| c_ops->cnic_ctl(bp->cnic_data, &info); |
| } |
| mutex_unlock(&bp->cnic_lock); |
| } |
| |
| static void |
| bnx2_cnic_start(struct bnx2 *bp) |
| { |
| struct cnic_ops *c_ops; |
| struct cnic_ctl_info info; |
| |
| mutex_lock(&bp->cnic_lock); |
| c_ops = bp->cnic_ops; |
| if (c_ops) { |
| if (!(bp->flags & BNX2_FLAG_USING_MSIX)) { |
| struct bnx2_napi *bnapi = &bp->bnx2_napi[0]; |
| |
| bnapi->cnic_tag = bnapi->last_status_idx; |
| } |
| info.cmd = CNIC_CTL_START_CMD; |
| c_ops->cnic_ctl(bp->cnic_data, &info); |
| } |
| mutex_unlock(&bp->cnic_lock); |
| } |
| |
| #else |
| |
| static void |
| bnx2_cnic_stop(struct bnx2 *bp) |
| { |
| } |
| |
| static void |
| bnx2_cnic_start(struct bnx2 *bp) |
| { |
| } |
| |
| #endif |
| |
| static int |
| bnx2_read_phy(struct bnx2 *bp, u32 reg, u32 *val) |
| { |
| u32 val1; |
| int i, ret; |
| |
| if (bp->phy_flags & BNX2_PHY_FLAG_INT_MODE_AUTO_POLLING) { |
| 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 & BNX2_PHY_FLAG_INT_MODE_AUTO_POLLING) { |
| 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 & BNX2_PHY_FLAG_INT_MODE_AUTO_POLLING) { |
| 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 & BNX2_PHY_FLAG_INT_MODE_AUTO_POLLING) { |
| 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) |
| { |
| int i; |
| struct bnx2_napi *bnapi; |
| |
| for (i = 0; i < bp->irq_nvecs; i++) { |
| bnapi = &bp->bnx2_napi[i]; |
| REG_WR(bp, BNX2_PCICFG_INT_ACK_CMD, bnapi->int_num | |
| BNX2_PCICFG_INT_ACK_CMD_MASK_INT); |
| } |
| REG_RD(bp, BNX2_PCICFG_INT_ACK_CMD); |
| } |
| |
| static void |
| bnx2_enable_int(struct bnx2 *bp) |
| { |
| int i; |
| struct bnx2_napi *bnapi; |
| |
| for (i = 0; i < bp->irq_nvecs; i++) { |
| bnapi = &bp->bnx2_napi[i]; |
| |
| REG_WR(bp, BNX2_PCICFG_INT_ACK_CMD, bnapi->int_num | |
| BNX2_PCICFG_INT_ACK_CMD_INDEX_VALID | |
| BNX2_PCICFG_INT_ACK_CMD_MASK_INT | |
| bnapi->last_status_idx); |
| |
| REG_WR(bp, BNX2_PCICFG_INT_ACK_CMD, bnapi->int_num | |
| BNX2_PCICFG_INT_ACK_CMD_INDEX_VALID | |
| bnapi->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) |
| { |
| int i; |
| |
| atomic_inc(&bp->intr_sem); |
| if (!netif_running(bp->dev)) |
| return; |
| |
| bnx2_disable_int(bp); |
| for (i = 0; i < bp->irq_nvecs; i++) |
| synchronize_irq(bp->irq_tbl[i].vector); |
| } |
| |
| static void |
| bnx2_napi_disable(struct bnx2 *bp) |
| { |
| int i; |
| |
| for (i = 0; i < bp->irq_nvecs; i++) |
| napi_disable(&bp->bnx2_napi[i].napi); |
| } |
| |
| static void |
| bnx2_napi_enable(struct bnx2 *bp) |
| { |
| int i; |
| |
| for (i = 0; i < bp->irq_nvecs; i++) |
| napi_enable(&bp->bnx2_napi[i].napi); |
| } |
| |
| static void |
| bnx2_netif_stop(struct bnx2 *bp, bool stop_cnic) |
| { |
| if (stop_cnic) |
| bnx2_cnic_stop(bp); |
| if (netif_running(bp->dev)) { |
| bnx2_napi_disable(bp); |
| netif_tx_disable(bp->dev); |
| } |
| bnx2_disable_int_sync(bp); |
| netif_carrier_off(bp->dev); /* prevent tx timeout */ |
| } |
| |
| static void |
| bnx2_netif_start(struct bnx2 *bp, bool start_cnic) |
| { |
| if (atomic_dec_and_test(&bp->intr_sem)) { |
| if (netif_running(bp->dev)) { |
| netif_tx_wake_all_queues(bp->dev); |
| spin_lock_bh(&bp->phy_lock); |
| if (bp->link_up) |
| netif_carrier_on(bp->dev); |
| spin_unlock_bh(&bp->phy_lock); |
| bnx2_napi_enable(bp); |
| bnx2_enable_int(bp); |
| if (start_cnic) |
| bnx2_cnic_start(bp); |
| } |
| } |
| } |
| |
| static void |
| bnx2_free_tx_mem(struct bnx2 *bp) |
| { |
| int i; |
| |
| for (i = 0; i < bp->num_tx_rings; i++) { |
| struct bnx2_napi *bnapi = &bp->bnx2_napi[i]; |
| struct bnx2_tx_ring_info *txr = &bnapi->tx_ring; |
| |
| if (txr->tx_desc_ring) { |
| dma_free_coherent(&bp->pdev->dev, TXBD_RING_SIZE, |
| txr->tx_desc_ring, |
| txr->tx_desc_mapping); |
| txr->tx_desc_ring = NULL; |
| } |
| kfree(txr->tx_buf_ring); |
| txr->tx_buf_ring = NULL; |
| } |
| } |
| |
| static void |
| bnx2_free_rx_mem(struct bnx2 *bp) |
| { |
| int i; |
| |
| for (i = 0; i < bp->num_rx_rings; i++) { |
| struct bnx2_napi *bnapi = &bp->bnx2_napi[i]; |
| struct bnx2_rx_ring_info *rxr = &bnapi->rx_ring; |
| int j; |
| |
| for (j = 0; j < bp->rx_max_ring; j++) { |
| if (rxr->rx_desc_ring[j]) |
| dma_free_coherent(&bp->pdev->dev, RXBD_RING_SIZE, |
| rxr->rx_desc_ring[j], |
| rxr->rx_desc_mapping[j]); |
| rxr->rx_desc_ring[j] = NULL; |
| } |
| vfree(rxr->rx_buf_ring); |
| rxr->rx_buf_ring = NULL; |
| |
| for (j = 0; j < bp->rx_max_pg_ring; j++) { |
| if (rxr->rx_pg_desc_ring[j]) |
| dma_free_coherent(&bp->pdev->dev, RXBD_RING_SIZE, |
| rxr->rx_pg_desc_ring[j], |
| rxr->rx_pg_desc_mapping[j]); |
| rxr->rx_pg_desc_ring[j] = NULL; |
| } |
| vfree(rxr->rx_pg_ring); |
| rxr->rx_pg_ring = NULL; |
| } |
| } |
| |
| static int |
| bnx2_alloc_tx_mem(struct bnx2 *bp) |
| { |
| int i; |
| |
| for (i = 0; i < bp->num_tx_rings; i++) { |
| struct bnx2_napi *bnapi = &bp->bnx2_napi[i]; |
| struct bnx2_tx_ring_info *txr = &bnapi->tx_ring; |
| |
| txr->tx_buf_ring = kzalloc(SW_TXBD_RING_SIZE, GFP_KERNEL); |
| if (txr->tx_buf_ring == NULL) |
| return -ENOMEM; |
| |
| txr->tx_desc_ring = |
| dma_alloc_coherent(&bp->pdev->dev, TXBD_RING_SIZE, |
| &txr->tx_desc_mapping, GFP_KERNEL); |
| if (txr->tx_desc_ring == NULL) |
| return -ENOMEM; |
| } |
| return 0; |
| } |
| |
| static int |
| bnx2_alloc_rx_mem(struct bnx2 *bp) |
| { |
| int i; |
| |
| for (i = 0; i < bp->num_rx_rings; i++) { |
| struct bnx2_napi *bnapi = &bp->bnx2_napi[i]; |
| struct bnx2_rx_ring_info *rxr = &bnapi->rx_ring; |
| int j; |
| |
| rxr->rx_buf_ring = |
| vmalloc(SW_RXBD_RING_SIZE * bp->rx_max_ring); |
| if (rxr->rx_buf_ring == NULL) |
| return -ENOMEM; |
| |
| memset(rxr->rx_buf_ring, 0, |
| SW_RXBD_RING_SIZE * bp->rx_max_ring); |
| |
| for (j = 0; j < bp->rx_max_ring; j++) { |
| rxr->rx_desc_ring[j] = |
| dma_alloc_coherent(&bp->pdev->dev, |
| RXBD_RING_SIZE, |
| &rxr->rx_desc_mapping[j], |
| GFP_KERNEL); |
| if (rxr->rx_desc_ring[j] == NULL) |
| return -ENOMEM; |
| |
| } |
| |
| if (bp->rx_pg_ring_size) { |
| rxr->rx_pg_ring = vmalloc(SW_RXPG_RING_SIZE * |
| bp->rx_max_pg_ring); |
| if (rxr->rx_pg_ring == NULL) |
| return -ENOMEM; |
| |
| memset(rxr->rx_pg_ring, 0, SW_RXPG_RING_SIZE * |
| bp->rx_max_pg_ring); |
| } |
| |
| for (j = 0; j < bp->rx_max_pg_ring; j++) { |
| rxr->rx_pg_desc_ring[j] = |
| dma_alloc_coherent(&bp->pdev->dev, |
| RXBD_RING_SIZE, |
| &rxr->rx_pg_desc_mapping[j], |
| GFP_KERNEL); |
| if (rxr->rx_pg_desc_ring[j] == NULL) |
| return -ENOMEM; |
| |
| } |
| } |
| return 0; |
| } |
| |
| static void |
| bnx2_free_mem(struct bnx2 *bp) |
| { |
| int i; |
| struct bnx2_napi *bnapi = &bp->bnx2_napi[0]; |
| |
| bnx2_free_tx_mem(bp); |
| bnx2_free_rx_mem(bp); |
| |
| for (i = 0; i < bp->ctx_pages; i++) { |
| if (bp->ctx_blk[i]) { |
| dma_free_coherent(&bp->pdev->dev, BCM_PAGE_SIZE, |
| bp->ctx_blk[i], |
| bp->ctx_blk_mapping[i]); |
| bp->ctx_blk[i] = NULL; |
| } |
| } |
| if (bnapi->status_blk.msi) { |
| dma_free_coherent(&bp->pdev->dev, bp->status_stats_size, |
| bnapi->status_blk.msi, |
| bp->status_blk_mapping); |
| bnapi->status_blk.msi = NULL; |
| bp->stats_blk = NULL; |
| } |
| } |
| |
| static int |
| bnx2_alloc_mem(struct bnx2 *bp) |
| { |
| int i, status_blk_size, err; |
| struct bnx2_napi *bnapi; |
| void *status_blk; |
| |
| /* Combine status and statistics blocks into one allocation. */ |
| status_blk_size = L1_CACHE_ALIGN(sizeof(struct status_block)); |
| if (bp->flags & BNX2_FLAG_MSIX_CAP) |
| status_blk_size = L1_CACHE_ALIGN(BNX2_MAX_MSIX_HW_VEC * |
| BNX2_SBLK_MSIX_ALIGN_SIZE); |
| bp->status_stats_size = status_blk_size + |
| sizeof(struct statistics_block); |
| |
| status_blk = dma_alloc_coherent(&bp->pdev->dev, bp->status_stats_size, |
| &bp->status_blk_mapping, GFP_KERNEL); |
| if (status_blk == NULL) |
| goto alloc_mem_err; |
| |
| memset(status_blk, 0, bp->status_stats_size); |
| |
| bnapi = &bp->bnx2_napi[0]; |
| bnapi->status_blk.msi = status_blk; |
| bnapi->hw_tx_cons_ptr = |
| &bnapi->status_blk.msi->status_tx_quick_consumer_index0; |
| bnapi->hw_rx_cons_ptr = |
| &bnapi->status_blk.msi->status_rx_quick_consumer_index0; |
| if (bp->flags & BNX2_FLAG_MSIX_CAP) { |
| for (i = 1; i < bp->irq_nvecs; i++) { |
| struct status_block_msix *sblk; |
| |
| bnapi = &bp->bnx2_napi[i]; |
| |
| sblk = (void *) (status_blk + |
| BNX2_SBLK_MSIX_ALIGN_SIZE * i); |
| bnapi->status_blk.msix = sblk; |
| bnapi->hw_tx_cons_ptr = |
| &sblk->status_tx_quick_consumer_index; |
| bnapi->hw_rx_cons_ptr = |
| &sblk->status_rx_quick_consumer_index; |
| bnapi->int_num = i << 24; |
| } |
| } |
| |
| bp->stats_blk = status_blk + status_blk_size; |
| |
| bp->stats_blk_mapping = bp->status_blk_mapping + status_blk_size; |
| |
| if (CHIP_NUM(bp) == CHIP_NUM_5709) { |
| bp->ctx_pages = 0x2000 / BCM_PAGE_SIZE; |
| if (bp->ctx_pages == 0) |
| bp->ctx_pages = 1; |
| for (i = 0; i < bp->ctx_pages; i++) { |
| bp->ctx_blk[i] = dma_alloc_coherent(&bp->pdev->dev, |
| BCM_PAGE_SIZE, |
| &bp->ctx_blk_mapping[i], |
| GFP_KERNEL); |
| if (bp->ctx_blk[i] == NULL) |
| goto alloc_mem_err; |
| } |
| } |
| |
| err = bnx2_alloc_rx_mem(bp); |
| if (err) |
| goto alloc_mem_err; |
| |
| err = bnx2_alloc_tx_mem(bp); |
| if (err) |
| goto alloc_mem_err; |
| |
| 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->phy_flags & BNX2_PHY_FLAG_REMOTE_PHY_CAP) |
| return; |
| |
| 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, bp->mii_bmsr, &bmsr); |
| bnx2_read_phy(bp, bp->mii_bmsr, &bmsr); |
| |
| if (!(bmsr & BMSR_ANEGCOMPLETE) || |
| bp->phy_flags & BNX2_PHY_FLAG_PARALLEL_DETECT) |
| 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; |
| |
| bnx2_shmem_wr(bp, BNX2_LINK_STATUS, fw_link_status); |
| } |
| |
| static char * |
| bnx2_xceiver_str(struct bnx2 *bp) |
| { |
| return (bp->phy_port == PORT_FIBRE) ? "SerDes" : |
| ((bp->phy_flags & BNX2_PHY_FLAG_SERDES) ? "Remote Copper" : |
| "Copper"); |
| } |
| |
| static void |
| bnx2_report_link(struct bnx2 *bp) |
| { |
| if (bp->link_up) { |
| netif_carrier_on(bp->dev); |
| netdev_info(bp->dev, "NIC %s Link is Up, %d Mbps %s duplex", |
| bnx2_xceiver_str(bp), |
| bp->line_speed, |
| bp->duplex == DUPLEX_FULL ? "full" : "half"); |
| |
| if (bp->flow_ctrl) { |
| if (bp->flow_ctrl & FLOW_CTRL_RX) { |
| pr_cont(", receive "); |
| if (bp->flow_ctrl & FLOW_CTRL_TX) |
| pr_cont("& transmit "); |
| } |
| else { |
| pr_cont(", transmit "); |
| } |
| pr_cont("flow control ON"); |
| } |
| pr_cont("\n"); |
| } else { |
| netif_carrier_off(bp->dev); |
| netdev_err(bp->dev, "NIC %s Link is Down\n", |
| bnx2_xceiver_str(bp)); |
| } |
| |
| 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 & BNX2_PHY_FLAG_SERDES) && |
| (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, bp->mii_adv, &local_adv); |
| bnx2_read_phy(bp, bp->mii_lpa, &remote_adv); |
| |
| if (bp->phy_flags & BNX2_PHY_FLAG_SERDES) { |
| 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_5709s_linkup(struct bnx2 *bp) |
| { |
| u32 val, speed; |
| |
| bp->link_up = 1; |
| |
| bnx2_write_phy(bp, MII_BNX2_BLK_ADDR, MII_BNX2_BLK_ADDR_GP_STATUS); |
| bnx2_read_phy(bp, MII_BNX2_GP_TOP_AN_STATUS1, &val); |
| bnx2_write_phy(bp, MII_BNX2_BLK_ADDR, MII_BNX2_BLK_ADDR_COMBO_IEEEB0); |
| |
| if ((bp->autoneg & AUTONEG_SPEED) == 0) { |
| bp->line_speed = bp->req_line_speed; |
| bp->duplex = bp->req_duplex; |
| return 0; |
| } |
| speed = val & MII_BNX2_GP_TOP_AN_SPEED_MSK; |
| switch (speed) { |
| case MII_BNX2_GP_TOP_AN_SPEED_10: |
| bp->line_speed = SPEED_10; |
| break; |
| case MII_BNX2_GP_TOP_AN_SPEED_100: |
| bp->line_speed = SPEED_100; |
| break; |
| case MII_BNX2_GP_TOP_AN_SPEED_1G: |
| case MII_BNX2_GP_TOP_AN_SPEED_1GKV: |
| bp->line_speed = SPEED_1000; |
| break; |
| case MII_BNX2_GP_TOP_AN_SPEED_2_5G: |
| bp->line_speed = SPEED_2500; |
| break; |
| } |
| if (val & MII_BNX2_GP_TOP_AN_FD) |
| bp->duplex = DUPLEX_FULL; |
| else |
| bp->duplex = DUPLEX_HALF; |
| return 0; |
| } |
| |
| 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, 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, bp->mii_adv, &local_adv); |
| bnx2_read_phy(bp, 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, 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, bp->mii_adv, &local_adv); |
| bnx2_read_phy(bp, 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 void |
| bnx2_init_rx_context(struct bnx2 *bp, u32 cid) |
| { |
| u32 val, rx_cid_addr = GET_CID_ADDR(cid); |
| |
| val = BNX2_L2CTX_CTX_TYPE_CTX_BD_CHN_TYPE_VALUE; |
| val |= BNX2_L2CTX_CTX_TYPE_SIZE_L2; |
| val |= 0x02 << 8; |
| |
| if (bp->flow_ctrl & FLOW_CTRL_TX) |
| val |= BNX2_L2CTX_FLOW_CTRL_ENABLE; |
| |
| bnx2_ctx_wr(bp, rx_cid_addr, BNX2_L2CTX_CTX_TYPE, val); |
| } |
| |
| static void |
| bnx2_init_all_rx_contexts(struct bnx2 *bp) |
| { |
| int i; |
| u32 cid; |
| |
| for (i = 0, cid = RX_CID; i < bp->num_rx_rings; i++, cid++) { |
| if (i == 1) |
| cid = RX_RSS_CID; |
| bnx2_init_rx_context(bp, cid); |
| } |
| } |
| |
| static void |
| 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_MODE); |
| |
| if (bp->link_up) { |
| switch (bp->line_speed) { |
| case SPEED_10: |
| if (CHIP_NUM(bp) != CHIP_NUM_5706) { |
| val |= BNX2_EMAC_MODE_PORT_MII_10M; |
| break; |
| } |
| /* fall through */ |
| case SPEED_100: |
| val |= BNX2_EMAC_MODE_PORT_MII; |
| break; |
| case SPEED_2500: |
| val |= BNX2_EMAC_MODE_25G_MODE; |
| /* 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); |
| |
| bnx2_init_all_rx_contexts(bp); |
| } |
| |
| static void |
| bnx2_enable_bmsr1(struct bnx2 *bp) |
| { |
| if ((bp->phy_flags & BNX2_PHY_FLAG_SERDES) && |
| (CHIP_NUM(bp) == CHIP_NUM_5709)) |
| bnx2_write_phy(bp, MII_BNX2_BLK_ADDR, |
| MII_BNX2_BLK_ADDR_GP_STATUS); |
| } |
| |
| static void |
| bnx2_disable_bmsr1(struct bnx2 *bp) |
| { |
| if ((bp->phy_flags & BNX2_PHY_FLAG_SERDES) && |
| (CHIP_NUM(bp) == CHIP_NUM_5709)) |
| bnx2_write_phy(bp, MII_BNX2_BLK_ADDR, |
| MII_BNX2_BLK_ADDR_COMBO_IEEEB0); |
| } |
| |
| static int |
| bnx2_test_and_enable_2g5(struct bnx2 *bp) |
| { |
| u32 up1; |
| int ret = 1; |
| |
| if (!(bp->phy_flags & BNX2_PHY_FLAG_2_5G_CAPABLE)) |
| return 0; |
| |
| if (bp->autoneg & AUTONEG_SPEED) |
| bp->advertising |= ADVERTISED_2500baseX_Full; |
| |
| if (CHIP_NUM(bp) == CHIP_NUM_5709) |
| bnx2_write_phy(bp, MII_BNX2_BLK_ADDR, MII_BNX2_BLK_ADDR_OVER1G); |
| |
| bnx2_read_phy(bp, bp->mii_up1, &up1); |
| if (!(up1 & BCM5708S_UP1_2G5)) { |
| up1 |= BCM5708S_UP1_2G5; |
| bnx2_write_phy(bp, bp->mii_up1, up1); |
| ret = 0; |
| } |
| |
| if (CHIP_NUM(bp) == CHIP_NUM_5709) |
| bnx2_write_phy(bp, MII_BNX2_BLK_ADDR, |
| MII_BNX2_BLK_ADDR_COMBO_IEEEB0); |
| |
| return ret; |
| } |
| |
| static int |
| bnx2_test_and_disable_2g5(struct bnx2 *bp) |
| { |
| u32 up1; |
| int ret = 0; |
| |
| if (!(bp->phy_flags & BNX2_PHY_FLAG_2_5G_CAPABLE)) |
| return 0; |
| |
| if (CHIP_NUM(bp) == CHIP_NUM_5709) |
| bnx2_write_phy(bp, MII_BNX2_BLK_ADDR, MII_BNX2_BLK_ADDR_OVER1G); |
| |
| bnx2_read_phy(bp, bp->mii_up1, &up1); |
| if (up1 & BCM5708S_UP1_2G5) { |
| up1 &= ~BCM5708S_UP1_2G5; |
| bnx2_write_phy(bp, bp->mii_up1, up1); |
| ret = 1; |
| } |
| |
| if (CHIP_NUM(bp) == CHIP_NUM_5709) |
| bnx2_write_phy(bp, MII_BNX2_BLK_ADDR, |
| MII_BNX2_BLK_ADDR_COMBO_IEEEB0); |
| |
| return ret; |
| } |
| |
| static void |
| bnx2_enable_forced_2g5(struct bnx2 *bp) |
| { |
| u32 uninitialized_var(bmcr); |
| int err; |
| |
| if (!(bp->phy_flags & BNX2_PHY_FLAG_2_5G_CAPABLE)) |
| return; |
| |
| if (CHIP_NUM(bp) == CHIP_NUM_5709) { |
| u32 val; |
| |
| bnx2_write_phy(bp, MII_BNX2_BLK_ADDR, |
| MII_BNX2_BLK_ADDR_SERDES_DIG); |
| if (!bnx2_read_phy(bp, MII_BNX2_SERDES_DIG_MISC1, &val)) { |
| val &= ~MII_BNX2_SD_MISC1_FORCE_MSK; |
| val |= MII_BNX2_SD_MISC1_FORCE | |
| MII_BNX2_SD_MISC1_FORCE_2_5G; |
| bnx2_write_phy(bp, MII_BNX2_SERDES_DIG_MISC1, val); |
| } |
| |
| bnx2_write_phy(bp, MII_BNX2_BLK_ADDR, |
| MII_BNX2_BLK_ADDR_COMBO_IEEEB0); |
| err = bnx2_read_phy(bp, bp->mii_bmcr, &bmcr); |
| |
| } else if (CHIP_NUM(bp) == CHIP_NUM_5708) { |
| err = bnx2_read_phy(bp, bp->mii_bmcr, &bmcr); |
| if (!err) |
| bmcr |= BCM5708S_BMCR_FORCE_2500; |
| } else { |
| return; |
| } |
| |
| if (err) |
| return; |
| |
| if (bp->autoneg & AUTONEG_SPEED) { |
| bmcr &= ~BMCR_ANENABLE; |
| if (bp->req_duplex == DUPLEX_FULL) |
| bmcr |= BMCR_FULLDPLX; |
| } |
| bnx2_write_phy(bp, bp->mii_bmcr, bmcr); |
| } |
| |
| static void |
| bnx2_disable_forced_2g5(struct bnx2 *bp) |
| { |
| u32 uninitialized_var(bmcr); |
| int err; |
| |
| if (!(bp->phy_flags & BNX2_PHY_FLAG_2_5G_CAPABLE)) |
| return; |
| |
| if (CHIP_NUM(bp) == CHIP_NUM_5709) { |
| u32 val; |
| |
| bnx2_write_phy(bp, MII_BNX2_BLK_ADDR, |
| MII_BNX2_BLK_ADDR_SERDES_DIG); |
| if (!bnx2_read_phy(bp, MII_BNX2_SERDES_DIG_MISC1, &val)) { |
| val &= ~MII_BNX2_SD_MISC1_FORCE; |
| bnx2_write_phy(bp, MII_BNX2_SERDES_DIG_MISC1, val); |
| } |
| |
| bnx2_write_phy(bp, MII_BNX2_BLK_ADDR, |
| MII_BNX2_BLK_ADDR_COMBO_IEEEB0); |
| err = bnx2_read_phy(bp, bp->mii_bmcr, &bmcr); |
| |
| } else if (CHIP_NUM(bp) == CHIP_NUM_5708) { |
| err = bnx2_read_phy(bp, bp->mii_bmcr, &bmcr); |
| if (!err) |
| bmcr &= ~BCM5708S_BMCR_FORCE_2500; |
| } else { |
| return; |
| } |
| |
| if (err) |
| return; |
| |
| if (bp->autoneg & AUTONEG_SPEED) |
| bmcr |= BMCR_SPEED1000 | BMCR_ANENABLE | BMCR_ANRESTART; |
| bnx2_write_phy(bp, bp->mii_bmcr, bmcr); |
| } |
| |
| static void |
| bnx2_5706s_force_link_dn(struct bnx2 *bp, int start) |
| { |
| u32 val; |
| |
| bnx2_write_phy(bp, MII_BNX2_DSP_ADDRESS, MII_EXPAND_SERDES_CTL); |
| bnx2_read_phy(bp, MII_BNX2_DSP_RW_PORT, &val); |
| if (start) |
| bnx2_write_phy(bp, MII_BNX2_DSP_RW_PORT, val & 0xff0f); |
| else |
| bnx2_write_phy(bp, MII_BNX2_DSP_RW_PORT, val | 0xc0); |
| } |
| |
| static int |
| bnx2_set_link(struct bnx2 *bp) |
| { |
| u32 bmsr; |
| u8 link_up; |
| |
| if (bp->loopback == MAC_LOOPBACK || bp->loopback == PHY_LOOPBACK) { |
| bp->link_up = 1; |
| return 0; |
| } |
| |
| if (bp->phy_flags & BNX2_PHY_FLAG_REMOTE_PHY_CAP) |
| return 0; |
| |
| link_up = bp->link_up; |
| |
| bnx2_enable_bmsr1(bp); |
| bnx2_read_phy(bp, bp->mii_bmsr1, &bmsr); |
| bnx2_read_phy(bp, bp->mii_bmsr1, &bmsr); |
| bnx2_disable_bmsr1(bp); |
| |
| if ((bp->phy_flags & BNX2_PHY_FLAG_SERDES) && |
| (CHIP_NUM(bp) == CHIP_NUM_5706)) { |
| u32 val, an_dbg; |
| |
| if (bp->phy_flags & BNX2_PHY_FLAG_FORCED_DOWN) { |
| bnx2_5706s_force_link_dn(bp, 0); |
| bp->phy_flags &= ~BNX2_PHY_FLAG_FORCED_DOWN; |
| } |
| val = REG_RD(bp, BNX2_EMAC_STATUS); |
| |
| bnx2_write_phy(bp, MII_BNX2_MISC_SHADOW, MISC_SHDW_AN_DBG); |
| bnx2_read_phy(bp, MII_BNX2_MISC_SHADOW, &an_dbg); |
| bnx2_read_phy(bp, MII_BNX2_MISC_SHADOW, &an_dbg); |
| |
| if ((val & BNX2_EMAC_STATUS_LINK) && |
| !(an_dbg & MISC_SHDW_AN_DBG_NOSYNC)) |
| bmsr |= BMSR_LSTATUS; |
| else |
| bmsr &= ~BMSR_LSTATUS; |
| } |
| |
| if (bmsr & BMSR_LSTATUS) { |
| bp->link_up = 1; |
| |
| if (bp->phy_flags & BNX2_PHY_FLAG_SERDES) { |
| if (CHIP_NUM(bp) == CHIP_NUM_5706) |
| bnx2_5706s_linkup(bp); |
| else if (CHIP_NUM(bp) == CHIP_NUM_5708) |
| bnx2_5708s_linkup(bp); |
| else if (CHIP_NUM(bp) == CHIP_NUM_5709) |
| bnx2_5709s_linkup(bp); |
| } |
| else { |
| bnx2_copper_linkup(bp); |
| } |
| bnx2_resolve_flow_ctrl(bp); |
| } |
| else { |
| if ((bp->phy_flags & BNX2_PHY_FLAG_SERDES) && |
| (bp->autoneg & AUTONEG_SPEED)) |
| bnx2_disable_forced_2g5(bp); |
| |
| if (bp->phy_flags & BNX2_PHY_FLAG_PARALLEL_DETECT) { |
| u32 bmcr; |
| |
| bnx2_read_phy(bp, bp->mii_bmcr, &bmcr); |
| bmcr |= BMCR_ANENABLE; |
| bnx2_write_phy(bp, bp->mii_bmcr, bmcr); |
| |
| bp->phy_flags &= ~BNX2_PHY_FLAG_PARALLEL_DETECT; |
| } |
| 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, 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, bp->mii_bmcr, ®); |
| 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 & BNX2_PHY_FLAG_SERDES) { |
| adv = ADVERTISE_1000XPAUSE; |
| } |
| else { |
| adv = ADVERTISE_PAUSE_CAP; |
| } |
| } |
| else if (bp->req_flow_ctrl & FLOW_CTRL_TX) { |
| if (bp->phy_flags & BNX2_PHY_FLAG_SERDES) { |
| adv = ADVERTISE_1000XPSE_ASYM; |
| } |
| else { |
| adv = ADVERTISE_PAUSE_ASYM; |
| } |
| } |
| else if (bp->req_flow_ctrl & FLOW_CTRL_RX) { |
| if (bp->phy_flags & BNX2_PHY_FLAG_SERDES) { |
| adv = ADVERTISE_1000XPAUSE | ADVERTISE_1000XPSE_ASYM; |
| } |
| else { |
| adv = ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM; |
| } |
| } |
| return adv; |
| } |
| |
| static int bnx2_fw_sync(struct bnx2 *, u32, int, int); |
| |
| static int |
| bnx2_setup_remote_phy(struct bnx2 *bp, u8 port) |
| __releases(&bp->phy_lock) |
| __acquires(&bp->phy_lock) |
| { |
| u32 speed_arg = 0, pause_adv; |
| |
| pause_adv = bnx2_phy_get_pause_adv(bp); |
| |
| if (bp->autoneg & AUTONEG_SPEED) { |
| speed_arg |= BNX2_NETLINK_SET_LINK_ENABLE_AUTONEG; |
| if (bp->advertising & ADVERTISED_10baseT_Half) |
| speed_arg |= BNX2_NETLINK_SET_LINK_SPEED_10HALF; |
| if (bp->advertising & ADVERTISED_10baseT_Full) |
| speed_arg |= BNX2_NETLINK_SET_LINK_SPEED_10FULL; |
| if (bp->advertising & ADVERTISED_100baseT_Half) |
| speed_arg |= BNX2_NETLINK_SET_LINK_SPEED_100HALF; |
| if (bp->advertising & ADVERTISED_100baseT_Full) |
| speed_arg |= BNX2_NETLINK_SET_LINK_SPEED_100FULL; |
| if (bp->advertising & ADVERTISED_1000baseT_Full) |
| speed_arg |= BNX2_NETLINK_SET_LINK_SPEED_1GFULL; |
| if (bp->advertising & ADVERTISED_2500baseX_Full) |
| speed_arg |= BNX2_NETLINK_SET_LINK_SPEED_2G5FULL; |
| } else { |
| if (bp->req_line_speed == SPEED_2500) |
| speed_arg = BNX2_NETLINK_SET_LINK_SPEED_2G5FULL; |
| else if (bp->req_line_speed == SPEED_1000) |
| speed_arg = BNX2_NETLINK_SET_LINK_SPEED_1GFULL; |
| else if (bp->req_line_speed == SPEED_100) { |
| if (bp->req_duplex == DUPLEX_FULL) |
| speed_arg = BNX2_NETLINK_SET_LINK_SPEED_100FULL; |
| else |
| speed_arg = BNX2_NETLINK_SET_LINK_SPEED_100HALF; |
| } else if (bp->req_line_speed == SPEED_10) { |
| if (bp->req_duplex == DUPLEX_FULL) |
| speed_arg = BNX2_NETLINK_SET_LINK_SPEED_10FULL; |
| else |
| speed_arg = BNX2_NETLINK_SET_LINK_SPEED_10HALF; |
| } |
| } |
| |
| if (pause_adv & (ADVERTISE_1000XPAUSE | ADVERTISE_PAUSE_CAP)) |
| speed_arg |= BNX2_NETLINK_SET_LINK_FC_SYM_PAUSE; |
| if (pause_adv & (ADVERTISE_1000XPSE_ASYM | ADVERTISE_PAUSE_ASYM)) |
| speed_arg |= BNX2_NETLINK_SET_LINK_FC_ASYM_PAUSE; |
| |
| if (port == PORT_TP) |
| speed_arg |= BNX2_NETLINK_SET_LINK_PHY_APP_REMOTE | |
| BNX2_NETLINK_SET_LINK_ETH_AT_WIRESPEED; |
| |
| bnx2_shmem_wr(bp, BNX2_DRV_MB_ARG0, speed_arg); |
| |
| spin_unlock_bh(&bp->phy_lock); |
| bnx2_fw_sync(bp, BNX2_DRV_MSG_CODE_CMD_SET_LINK, 1, 0); |
| spin_lock_bh(&bp->phy_lock); |
| |
| return 0; |
| } |
| |
| static int |
| bnx2_setup_serdes_phy(struct bnx2 *bp, u8 port) |
| __releases(&bp->phy_lock) |
| __acquires(&bp->phy_lock) |
| { |
| u32 adv, bmcr; |
| u32 new_adv = 0; |
| |
| if (bp->phy_flags & BNX2_PHY_FLAG_REMOTE_PHY_CAP) |
| return bnx2_setup_remote_phy(bp, port); |
| |
| if (!(bp->autoneg & AUTONEG_SPEED)) { |
| u32 new_bmcr; |
| int force_link_down = 0; |
| |
| if (bp->req_line_speed == SPEED_2500) { |
| if (!bnx2_test_and_enable_2g5(bp)) |
| force_link_down = 1; |
| } else if (bp->req_line_speed == SPEED_1000) { |
| if (bnx2_test_and_disable_2g5(bp)) |
| force_link_down = 1; |
| } |
| bnx2_read_phy(bp, bp->mii_adv, &adv); |
| adv &= ~(ADVERTISE_1000XFULL | ADVERTISE_1000XHALF); |
| |
| bnx2_read_phy(bp, bp->mii_bmcr, &bmcr); |
| new_bmcr = bmcr & ~BMCR_ANENABLE; |
| new_bmcr |= BMCR_SPEED1000; |
| |
| if (CHIP_NUM(bp) == CHIP_NUM_5709) { |
| if (bp->req_line_speed == SPEED_2500) |
| bnx2_enable_forced_2g5(bp); |
| else if (bp->req_line_speed == SPEED_1000) { |
| bnx2_disable_forced_2g5(bp); |
| new_bmcr &= ~0x2000; |
| } |
| |
| } else if (CHIP_NUM(bp) == CHIP_NUM_5708) { |
| if (bp->req_line_speed == SPEED_2500) |
| new_bmcr |= BCM5708S_BMCR_FORCE_2500; |
| else |
| new_bmcr = bmcr & ~BCM5708S_BMCR_FORCE_2500; |
| } |
| |
| 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, bp->mii_adv, adv & |
| ~(ADVERTISE_1000XFULL | |
| ADVERTISE_1000XHALF)); |
| bnx2_write_phy(bp, bp->mii_bmcr, bmcr | |
| BMCR_ANRESTART | BMCR_ANENABLE); |
| |
| bp->link_up = 0; |
| netif_carrier_off(bp->dev); |
| bnx2_write_phy(bp, bp->mii_bmcr, new_bmcr); |
| bnx2_report_link(bp); |
| } |
| bnx2_write_phy(bp, bp->mii_adv, adv); |
| bnx2_write_phy(bp, bp->mii_bmcr, new_bmcr); |
| } else { |
| bnx2_resolve_flow_ctrl(bp); |
| bnx2_set_mac_link(bp); |
| } |
| return 0; |
| } |
| |
| bnx2_test_and_enable_2g5(bp); |
| |
| if (bp->advertising & ADVERTISED_1000baseT_Full) |
| new_adv |= ADVERTISE_1000XFULL; |
| |
| new_adv |= bnx2_phy_get_pause_adv(bp); |
| |
| bnx2_read_phy(bp, bp->mii_adv, &adv); |
| bnx2_read_phy(bp, 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) { |
| bnx2_write_phy(bp, bp->mii_bmcr, BMCR_LOOPBACK); |
| spin_unlock_bh(&bp->phy_lock); |
| msleep(20); |
| spin_lock_bh(&bp->phy_lock); |
| } |
| |
| bnx2_write_phy(bp, bp->mii_adv, new_adv); |
| bnx2_write_phy(bp, bp->mii_bmcr, bmcr | BMCR_ANRESTART | |
| BMCR_ANENABLE); |
| /* 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 = BNX2_SERDES_AN_TIMEOUT; |
| bp->serdes_an_pending = 1; |
| mod_timer(&bp->timer, jiffies + bp->current_interval); |
| } else { |
| bnx2_resolve_flow_ctrl(bp); |
| bnx2_set_mac_link(bp); |
| } |
| |
| return 0; |
| } |
| |
| #define ETHTOOL_ALL_FIBRE_SPEED \ |
| (bp->phy_flags & BNX2_PHY_FLAG_2_5G_CAPABLE) ? \ |
| (ADVERTISED_2500baseX_Full | ADVERTISED_1000baseT_Full) :\ |
| (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 void |
| bnx2_set_default_remote_link(struct bnx2 *bp) |
| { |
| u32 link; |
| |
| if (bp->phy_port == PORT_TP) |
| link = bnx2_shmem_rd(bp, BNX2_RPHY_COPPER_LINK); |
| else |
| link = bnx2_shmem_rd(bp, BNX2_RPHY_SERDES_LINK); |
| |
| if (link & BNX2_NETLINK_SET_LINK_ENABLE_AUTONEG) { |
| bp->req_line_speed = 0; |
| bp->autoneg |= AUTONEG_SPEED; |
| bp->advertising = ADVERTISED_Autoneg; |
| if (link & BNX2_NETLINK_SET_LINK_SPEED_10HALF) |
| bp->advertising |= ADVERTISED_10baseT_Half; |
| if (link & BNX2_NETLINK_SET_LINK_SPEED_10FULL) |
| bp->advertising |= ADVERTISED_10baseT_Full; |
| if (link & BNX2_NETLINK_SET_LINK_SPEED_100HALF) |
| bp->advertising |= ADVERTISED_100baseT_Half; |
| if (link & BNX2_NETLINK_SET_LINK_SPEED_100FULL) |
| bp->advertising |= ADVERTISED_100baseT_Full; |
| if (link & BNX2_NETLINK_SET_LINK_SPEED_1GFULL) |
| bp->advertising |= ADVERTISED_1000baseT_Full; |
| if (link & BNX2_NETLINK_SET_LINK_SPEED_2G5FULL) |
| bp->advertising |= ADVERTISED_2500baseX_Full; |
| } else { |
| bp->autoneg = 0; |
| bp->advertising = 0; |
| bp->req_duplex = DUPLEX_FULL; |
| if (link & BNX2_NETLINK_SET_LINK_SPEED_10) { |
| bp->req_line_speed = SPEED_10; |
| if (link & BNX2_NETLINK_SET_LINK_SPEED_10HALF) |
| bp->req_duplex = DUPLEX_HALF; |
| } |
| if (link & BNX2_NETLINK_SET_LINK_SPEED_100) { |
| bp->req_line_speed = SPEED_100; |
| if (link & BNX2_NETLINK_SET_LINK_SPEED_100HALF) |
| bp->req_duplex = DUPLEX_HALF; |
| } |
| if (link & BNX2_NETLINK_SET_LINK_SPEED_1GFULL) |
| bp->req_line_speed = SPEED_1000; |
| if (link & BNX2_NETLINK_SET_LINK_SPEED_2G5FULL) |
| bp->req_line_speed = SPEED_2500; |
| } |
| } |
| |
| static void |
| bnx2_set_default_link(struct bnx2 *bp) |
| { |
| if (bp->phy_flags & BNX2_PHY_FLAG_REMOTE_PHY_CAP) { |
| bnx2_set_default_remote_link(bp); |
| return; |
| } |
| |
| bp->autoneg = AUTONEG_SPEED | AUTONEG_FLOW_CTRL; |
| bp->req_line_speed = 0; |
| if (bp->phy_flags & BNX2_PHY_FLAG_SERDES) { |
| u32 reg; |
| |
| bp->advertising = ETHTOOL_ALL_FIBRE_SPEED | ADVERTISED_Autoneg; |
| |
| reg = bnx2_shmem_rd(bp, BNX2_PORT_HW_CFG_CONFIG); |
| reg &= BNX2_PORT_HW_CFG_CFG_DFLT_LINK_MASK; |
| if (reg == BNX2_PORT_HW_CFG_CFG_DFLT_LINK_1G) { |
| bp->autoneg = 0; |
| bp->req_line_speed = bp->line_speed = SPEED_1000; |
| bp->req_duplex = DUPLEX_FULL; |
| } |
| } else |
| bp->advertising = ETHTOOL_ALL_COPPER_SPEED | ADVERTISED_Autoneg; |
| } |
| |
| static void |
| bnx2_send_heart_beat(struct bnx2 *bp) |
| { |
| u32 msg; |
| u32 addr; |
| |
| spin_lock(&bp->indirect_lock); |
| msg = (u32) (++bp->fw_drv_pulse_wr_seq & BNX2_DRV_PULSE_SEQ_MASK); |
| addr = bp->shmem_base + BNX2_DRV_PULSE_MB; |
| REG_WR(bp, BNX2_PCICFG_REG_WINDOW_ADDRESS, addr); |
| REG_WR(bp, BNX2_PCICFG_REG_WINDOW, msg); |
| spin_unlock(&bp->indirect_lock); |
| } |
| |
| static void |
| bnx2_remote_phy_event(struct bnx2 *bp) |
| { |
| u32 msg; |
| u8 link_up = bp->link_up; |
| u8 old_port; |
| |
| msg = bnx2_shmem_rd(bp, BNX2_LINK_STATUS); |
| |
| if (msg & BNX2_LINK_STATUS_HEART_BEAT_EXPIRED) |
| bnx2_send_heart_beat(bp); |
| |
| msg &= ~BNX2_LINK_STATUS_HEART_BEAT_EXPIRED; |
| |
| if ((msg & BNX2_LINK_STATUS_LINK_UP) == BNX2_LINK_STATUS_LINK_DOWN) |
| bp->link_up = 0; |
| else { |
| u32 speed; |
| |
| bp->link_up = 1; |
| speed = msg & BNX2_LINK_STATUS_SPEED_MASK; |
| bp->duplex = DUPLEX_FULL; |
| switch (speed) { |
| case BNX2_LINK_STATUS_10HALF: |
| bp->duplex = DUPLEX_HALF; |
| case BNX2_LINK_STATUS_10FULL: |
| bp->line_speed = SPEED_10; |
| break; |
| case BNX2_LINK_STATUS_100HALF: |
| bp->duplex = DUPLEX_HALF; |
| case BNX2_LINK_STATUS_100BASE_T4: |
| case BNX2_LINK_STATUS_100FULL: |
| bp->line_speed = SPEED_100; |
| break; |
| case BNX2_LINK_STATUS_1000HALF: |
| bp->duplex = DUPLEX_HALF; |
| case BNX2_LINK_STATUS_1000FULL: |
| bp->line_speed = SPEED_1000; |
| break; |
| case BNX2_LINK_STATUS_2500HALF: |
| bp->duplex = DUPLEX_HALF; |
| case BNX2_LINK_STATUS_2500FULL: |
| bp->line_speed = SPEED_2500; |
| break; |
| default: |
| bp->line_speed = 0; |
| break; |
| } |
| |
| 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; |
| } else { |
| if (msg & BNX2_LINK_STATUS_TX_FC_ENABLED) |
| bp->flow_ctrl |= FLOW_CTRL_TX; |
| if (msg & BNX2_LINK_STATUS_RX_FC_ENABLED) |
| bp->flow_ctrl |= FLOW_CTRL_RX; |
| } |
| |
| old_port = bp->phy_port; |
| if (msg & BNX2_LINK_STATUS_SERDES_LINK) |
| bp->phy_port = PORT_FIBRE; |
| else |
| bp->phy_port = PORT_TP; |
| |
| if (old_port != bp->phy_port) |
| bnx2_set_default_link(bp); |
| |
| } |
| if (bp->link_up != link_up) |
| bnx2_report_link(bp); |
| |
| bnx2_set_mac_link(bp); |
| } |
| |
| static int |
| bnx2_set_remote_link(struct bnx2 *bp) |
| { |
| u32 evt_code; |
| |
| evt_code = bnx2_shmem_rd(bp, BNX2_FW_EVT_CODE_MB); |
| switch (evt_code) { |
| case BNX2_FW_EVT_CODE_LINK_EVENT: |
| bnx2_remote_phy_event(bp); |
| break; |
| case BNX2_FW_EVT_CODE_SW_TIMER_EXPIRATION_EVENT: |
| default: |
| bnx2_send_heart_beat(bp); |
| break; |
| } |
| return 0; |
| } |
| |
| static int |
| bnx2_setup_copper_phy(struct bnx2 *bp) |
| __releases(&bp->phy_lock) |
| __acquires(&bp->phy_lock) |
| { |
| u32 bmcr; |
| u32 new_bmcr; |
| |
| bnx2_read_phy(bp, 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, bp->mii_adv, &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, bp->mii_adv, new_adv_reg); |
| bnx2_write_phy(bp, MII_CTRL1000, new_adv1000_reg); |
| bnx2_write_phy(bp, 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; |
| |
| bnx2_read_phy(bp, bp->mii_bmsr, &bmsr); |
| bnx2_read_phy(bp, bp->mii_bmsr, &bmsr); |
| |
| if (bmsr & BMSR_LSTATUS) { |
| /* Force link down */ |
| bnx2_write_phy(bp, bp->mii_bmcr, BMCR_LOOPBACK); |
| spin_unlock_bh(&bp->phy_lock); |
| msleep(50); |
| spin_lock_bh(&bp->phy_lock); |
| |
| bnx2_read_phy(bp, bp->mii_bmsr, &bmsr); |
| bnx2_read_phy(bp, bp->mii_bmsr, &bmsr); |
| } |
| |
| bnx2_write_phy(bp, 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); |
| } |
| } else { |
| bnx2_resolve_flow_ctrl(bp); |
| bnx2_set_mac_link(bp); |
| } |
| return 0; |
| } |
| |
| static int |
| bnx2_setup_phy(struct bnx2 *bp, u8 port) |
| __releases(&bp->phy_lock) |
| __acquires(&bp->phy_lock) |
| { |
| if (bp->loopback == MAC_LOOPBACK) |
| return 0; |
| |
| if (bp->phy_flags & BNX2_PHY_FLAG_SERDES) { |
| return bnx2_setup_serdes_phy(bp, port); |
| } |
| else { |
| return bnx2_setup_copper_phy(bp); |
| } |
| } |
| |
| static int |
| bnx2_init_5709s_phy(struct bnx2 *bp, int reset_phy) |
| { |
| u32 val; |
| |
| bp->mii_bmcr = MII_BMCR + 0x10; |
| bp->mii_bmsr = MII_BMSR + 0x10; |
| bp->mii_bmsr1 = MII_BNX2_GP_TOP_AN_STATUS1; |
| bp->mii_adv = MII_ADVERTISE + 0x10; |
| bp->mii_lpa = MII_LPA + 0x10; |
| bp->mii_up1 = MII_BNX2_OVER1G_UP1; |
| |
| bnx2_write_phy(bp, MII_BNX2_BLK_ADDR, MII_BNX2_BLK_ADDR_AER); |
| bnx2_write_phy(bp, MII_BNX2_AER_AER, MII_BNX2_AER_AER_AN_MMD); |
| |
| bnx2_write_phy(bp, MII_BNX2_BLK_ADDR, MII_BNX2_BLK_ADDR_COMBO_IEEEB0); |
| if (reset_phy) |
| bnx2_reset_phy(bp); |
| |
| bnx2_write_phy(bp, MII_BNX2_BLK_ADDR, MII_BNX2_BLK_ADDR_SERDES_DIG); |
| |
| bnx2_read_phy(bp, MII_BNX2_SERDES_DIG_1000XCTL1, &val); |
| val &= ~MII_BNX2_SD_1000XCTL1_AUTODET; |
| val |= MII_BNX2_SD_1000XCTL1_FIBER; |
| bnx2_write_phy(bp, MII_BNX2_SERDES_DIG_1000XCTL1, val); |
| |
| bnx2_write_phy(bp, MII_BNX2_BLK_ADDR, MII_BNX2_BLK_ADDR_OVER1G); |
| bnx2_read_phy(bp, MII_BNX2_OVER1G_UP1, &val); |
| if (bp->phy_flags & BNX2_PHY_FLAG_2_5G_CAPABLE) |
| val |= BCM5708S_UP1_2G5; |
| else |
| val &= ~BCM5708S_UP1_2G5; |
| bnx2_write_phy(bp, MII_BNX2_OVER1G_UP1, val); |
| |
| bnx2_write_phy(bp, MII_BNX2_BLK_ADDR, MII_BNX2_BLK_ADDR_BAM_NXTPG); |
| bnx2_read_phy(bp, MII_BNX2_BAM_NXTPG_CTL, &val); |
| val |= MII_BNX2_NXTPG_CTL_T2 | MII_BNX2_NXTPG_CTL_BAM; |
| bnx2_write_phy(bp, MII_BNX2_BAM_NXTPG_CTL, val); |
| |
| bnx2_write_phy(bp, MII_BNX2_BLK_ADDR, MII_BNX2_BLK_ADDR_CL73_USERB0); |
| |
| val = MII_BNX2_CL73_BAM_EN | MII_BNX2_CL73_BAM_STA_MGR_EN | |
| MII_BNX2_CL73_BAM_NP_AFT_BP_EN; |
| bnx2_write_phy(bp, MII_BNX2_CL73_BAM_CTL1, val); |
| |
| bnx2_write_phy(bp, MII_BNX2_BLK_ADDR, MII_BNX2_BLK_ADDR_COMBO_IEEEB0); |
| |
| return 0; |
| } |
| |
| static int |
| bnx2_init_5708s_phy(struct bnx2 *bp, int reset_phy) |
| { |
| u32 val; |
| |
| if (reset_phy) |
| bnx2_reset_phy(bp); |
| |
| bp->mii_up1 = BCM5708S_UP1; |
| |
| 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 & BNX2_PHY_FLAG_2_5G_CAPABLE) { |
| 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 = bnx2_shmem_rd(bp, BNX2_PORT_HW_CFG_CONFIG) & |
| BNX2_PORT_HW_CFG_CFG_TXCTL3_MASK; |
| |
| if (val) { |
| u32 is_backplane; |
| |
| is_backplane = bnx2_shmem_rd(bp, 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, int reset_phy) |
| { |
| if (reset_phy) |
| bnx2_reset_phy(bp); |
| |
| bp->phy_flags &= ~BNX2_PHY_FLAG_PARALLEL_DETECT; |
| |
| if (CHIP_NUM(bp) == CHIP_NUM_5706) |
| REG_WR(bp, BNX2_MISC_GP_HW_CTL0, 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, int reset_phy) |
| { |
| u32 val; |
| |
| if (reset_phy) |
| bnx2_reset_phy(bp); |
| |
| if (bp->phy_flags & BNX2_PHY_FLAG_CRC_FIX) { |
| 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->phy_flags & BNX2_PHY_FLAG_DIS_EARLY_DAC) { |
| bnx2_write_phy(bp, MII_BNX2_DSP_ADDRESS, |
| MII_BNX2_DSP_EXPAND_REG | 0x8); |
| bnx2_read_phy(bp, MII_BNX2_DSP_RW_PORT, &val); |
| val &= ~(1 << 8); |
| bnx2_write_phy(bp, MII_BNX2_DSP_RW_PORT, val); |
| } |
| |
| 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, int reset_phy) |
| __releases(&bp->phy_lock) |
| __acquires(&bp->phy_lock) |
| { |
| u32 val; |
| int rc = 0; |
| |
| bp->phy_flags &= ~BNX2_PHY_FLAG_INT_MODE_MASK; |
| bp->phy_flags |= BNX2_PHY_FLAG_INT_MODE_LINK_READY; |
| |
| bp->mii_bmcr = MII_BMCR; |
| bp->mii_bmsr = MII_BMSR; |
| bp->mii_bmsr1 = MII_BMSR; |
| bp->mii_adv = MII_ADVERTISE; |
| bp->mii_lpa = MII_LPA; |
| |
| REG_WR(bp, BNX2_EMAC_ATTENTION_ENA, BNX2_EMAC_ATTENTION_ENA_LINK); |
| |
| if (bp->phy_flags & BNX2_PHY_FLAG_REMOTE_PHY_CAP) |
| goto setup_phy; |
| |
| 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 & BNX2_PHY_FLAG_SERDES) { |
| if (CHIP_NUM(bp) == CHIP_NUM_5706) |
| rc = bnx2_init_5706s_phy(bp, reset_phy); |
| else if (CHIP_NUM(bp) == CHIP_NUM_5708) |
| rc = bnx2_init_5708s_phy(bp, reset_phy); |
| else if (CHIP_NUM(bp) == CHIP_NUM_5709) |
| rc = bnx2_init_5709s_phy(bp, reset_phy); |
| } |
| else { |
| rc = bnx2_init_copper_phy(bp, reset_phy); |
| } |
| |
| setup_phy: |
| if (!rc) |
| rc = bnx2_setup_phy(bp, bp->phy_port); |
| |
| 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, 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; |
| msleep(100); |
| } |
| |
| 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_MODE); |
| |
| 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 ack, int silent) |
| { |
| int i; |
| u32 val; |
| |
| bp->fw_wr_seq++; |
| msg_data |= bp->fw_wr_seq; |
| |
| bnx2_shmem_wr(bp, BNX2_DRV_MB, msg_data); |
| |
| if (!ack) |
| return 0; |
| |
| /* wait for an acknowledgement. */ |
| for (i = 0; i < (BNX2_FW_ACK_TIME_OUT_MS / 10); i++) { |
| msleep(10); |
| |
| val = bnx2_shmem_rd(bp, 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) |
| pr_err("fw sync timeout, reset code = %x\n", msg_data); |
| |
| msg_data &= ~BNX2_DRV_MSG_CODE; |
| msg_data |= BNX2_DRV_MSG_CODE_FW_TIMEOUT; |
| |
| bnx2_shmem_wr(bp, BNX2_DRV_MB, msg_data); |
| |
| return -EBUSY; |
| } |
| |
| if ((val & BNX2_FW_MSG_STATUS_MASK) != BNX2_FW_MSG_STATUS_OK) |
| return -EIO; |
| |
| return 0; |
| } |
| |
| static int |
| bnx2_init_5709_context(struct bnx2 *bp) |
| { |
| int i, ret = 0; |
| u32 val; |
| |
| val = BNX2_CTX_COMMAND_ENABLED | BNX2_CTX_COMMAND_MEM_INIT | (1 << 12); |
| val |= (BCM_PAGE_BITS - 8) << 16; |
| REG_WR(bp, BNX2_CTX_COMMAND, val); |
| for (i = 0; i < 10; i++) { |
| val = REG_RD(bp, BNX2_CTX_COMMAND); |
| if (!(val & BNX2_CTX_COMMAND_MEM_INIT)) |
| break; |
| udelay(2); |
| } |
| if (val & BNX2_CTX_COMMAND_MEM_INIT) |
| return -EBUSY; |
| |
| for (i = 0; i < bp->ctx_pages; i++) { |
| int j; |
| |
| if (bp->ctx_blk[i]) |
| memset(bp->ctx_blk[i], 0, BCM_PAGE_SIZE); |
| else |
| return -ENOMEM; |
| |
| REG_WR(bp, BNX2_CTX_HOST_PAGE_TBL_DATA0, |
| (bp->ctx_blk_mapping[i] & 0xffffffff) | |
| BNX2_CTX_HOST_PAGE_TBL_DATA0_VALID); |
| REG_WR(bp, BNX2_CTX_HOST_PAGE_TBL_DATA1, |
| (u64) bp->ctx_blk_mapping[i] >> 32); |
| REG_WR(bp, BNX2_CTX_HOST_PAGE_TBL_CTRL, i | |
| BNX2_CTX_HOST_PAGE_TBL_CTRL_WRITE_REQ); |
| for (j = 0; j < 10; j++) { |
| |
| val = REG_RD(bp, BNX2_CTX_HOST_PAGE_TBL_CTRL); |
| if (!(val & BNX2_CTX_HOST_PAGE_TBL_CTRL_WRITE_REQ)) |
| break; |
| udelay(5); |
| } |
| if (val & BNX2_CTX_HOST_PAGE_TBL_CTRL_WRITE_REQ) { |
| ret = -EBUSY; |
| break; |
| } |
| } |
| return ret; |
| } |
| |
| static void |
| bnx2_init_context(struct bnx2 *bp) |
| { |
| u32 vcid; |
| |
| vcid = 96; |
| while (vcid) { |
| u32 vcid_addr, pcid_addr, offset; |
| int i; |
| |
| 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; |
| } |
| |
| for (i = 0; i < (CTX_SIZE / PHY_CTX_SIZE); i++) { |
| vcid_addr += (i << PHY_CTX_SHIFT); |
| pcid_addr += (i << PHY_CTX_SHIFT); |
| |
| REG_WR(bp, BNX2_CTX_VIRT_ADDR, vcid_addr); |
| REG_WR(bp, BNX2_CTX_PAGE_TBL, pcid_addr); |
| |
| /* Zero out the context. */ |
| for (offset = 0; offset < PHY_CTX_SIZE; offset += 4) |
| bnx2_ctx_wr(bp, vcid_addr, offset, 0); |
| } |
| } |
| } |
| |
| 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) { |
| pr_err("Failed to allocate memory in %s\n", __func__); |
| 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 = bnx2_reg_rd_ind(bp, BNX2_RBUF_STATUS1); |
| while (val & BNX2_RBUF_STATUS1_FREE_COUNT) { |
| bnx2_reg_wr_ind(bp, BNX2_RBUF_COMMAND, |
| BNX2_RBUF_COMMAND_ALLOC_REQ); |
| |
| val = bnx2_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 = bnx2_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; |
| |
| bnx2_reg_wr_ind(bp, BNX2_RBUF_FW_BUF_FREE, val); |
| } |
| kfree(good_mbuf); |
| return 0; |
| } |
| |
| static void |
| bnx2_set_mac_addr(struct bnx2 *bp, u8 *mac_addr, u32 pos) |
| { |
| u32 val; |
| |
| val = (mac_addr[0] << 8) | mac_addr[1]; |
| |
| REG_WR(bp, BNX2_EMAC_MAC_MATCH0 + (pos * 8), val); |
| |
| val = (mac_addr[2] << 24) | (mac_addr[3] << 16) | |
| (mac_addr[4] << 8) | mac_addr[5]; |
| |
| REG_WR(bp, BNX2_EMAC_MAC_MATCH1 + (pos * 8), val); |
| } |
| |
| static inline int |
| bnx2_alloc_rx_page(struct bnx2 *bp, struct bnx2_rx_ring_info *rxr, u16 index, gfp_t gfp) |
| { |
| dma_addr_t mapping; |
| struct sw_pg *rx_pg = &rxr->rx_pg_ring[index]; |
| struct rx_bd *rxbd = |
| &rxr->rx_pg_desc_ring[RX_RING(index)][RX_IDX(index)]; |
| struct page *page = alloc_page(gfp); |
| |
| if (!page) |
| return -ENOMEM; |
| mapping = dma_map_page(&bp->pdev->dev, page, 0, PAGE_SIZE, |
| PCI_DMA_FROMDEVICE); |
| if (dma_mapping_error(&bp->pdev->dev, mapping)) { |
| __free_page(page); |
| return -EIO; |
| } |
| |
| rx_pg->page = page; |
| dma_unmap_addr_set(rx_pg, mapping, mapping); |
| rxbd->rx_bd_haddr_hi = (u64) mapping >> 32; |
| rxbd->rx_bd_haddr_lo = (u64) mapping & 0xffffffff; |
| return 0; |
| } |
| |
| static void |
| bnx2_free_rx_page(struct bnx2 *bp, struct bnx2_rx_ring_info *rxr, u16 index) |
| { |
| struct sw_pg *rx_pg = &rxr->rx_pg_ring[index]; |
| struct page *page = rx_pg->page; |
| |
| if (!page) |
| return; |
| |
| dma_unmap_page(&bp->pdev->dev, dma_unmap_addr(rx_pg, mapping), |
| PAGE_SIZE, PCI_DMA_FROMDEVICE); |
| |
| __free_page(page); |
| rx_pg->page = NULL; |
| } |
| |
| static inline int |
| bnx2_alloc_rx_skb(struct bnx2 *bp, struct bnx2_rx_ring_info *rxr, u16 index, gfp_t gfp) |
| { |
| struct sk_buff *skb; |
| struct sw_bd *rx_buf = &rxr->rx_buf_ring[index]; |
| dma_addr_t mapping; |
| struct rx_bd *rxbd = &rxr->rx_desc_ring[RX_RING(index)][RX_IDX(index)]; |
| unsigned long align; |
| |
| skb = __netdev_alloc_skb(bp->dev, bp->rx_buf_size, gfp); |
| if (skb == NULL) { |
| return -ENOMEM; |
| } |
| |
| if (unlikely((align = (unsigned long) skb->data & (BNX2_RX_ALIGN - 1)))) |
| skb_reserve(skb, BNX2_RX_ALIGN - align); |
| |
| mapping = dma_map_single(&bp->pdev->dev, skb->data, bp->rx_buf_use_size, |
| PCI_DMA_FROMDEVICE); |
| if (dma_mapping_error(&bp->pdev->dev, mapping)) { |
| dev_kfree_skb(skb); |
| return -EIO; |
| } |
| |
| rx_buf->skb = skb; |
| rx_buf->desc = (struct l2_fhdr *) skb->data; |
| dma_unmap_addr_set(rx_buf, mapping, mapping); |
| |
| rxbd->rx_bd_haddr_hi = (u64) mapping >> 32; |
| rxbd->rx_bd_haddr_lo = (u64) mapping & 0xffffffff; |
| |
| rxr->rx_prod_bseq += bp->rx_buf_use_size; |
| |
| return 0; |
| } |
| |
| static int |
| bnx2_phy_event_is_set(struct bnx2 *bp, struct bnx2_napi *bnapi, u32 event) |
| { |
| struct status_block *sblk = bnapi->status_blk.msi; |
| u32 new_link_state, old_link_state; |
| int is_set = 1; |
| |
| new_link_state = sblk->status_attn_bits & event; |
| old_link_state = sblk->status_attn_bits_ack & event; |
| if (new_link_state != old_link_state) { |
| if (new_link_state) |
| REG_WR(bp, BNX2_PCICFG_STATUS_BIT_SET_CMD, event); |
| else |
| REG_WR(bp, BNX2_PCICFG_STATUS_BIT_CLEAR_CMD, event); |
| } else |
| is_set = 0; |
| |
| return is_set; |
| } |
| |
| static void |
| bnx2_phy_int(struct bnx2 *bp, struct bnx2_napi *bnapi) |
| { |
| spin_lock(&bp->phy_lock); |
| |
| if (bnx2_phy_event_is_set(bp, bnapi, STATUS_ATTN_BITS_LINK_STATE)) |
| bnx2_set_link(bp); |
| if (bnx2_phy_event_is_set(bp, bnapi, STATUS_ATTN_BITS_TIMER_ABORT)) |
| bnx2_set_remote_link(bp); |
| |
| spin_unlock(&bp->phy_lock); |
| |
| } |
| |
| static inline u16 |
| bnx2_get_hw_tx_cons(struct bnx2_napi *bnapi) |
| { |
| u16 cons; |
| |
| /* Tell compiler that status block fields can change. */ |
| barrier(); |
| cons = *bnapi->hw_tx_cons_ptr; |
| barrier(); |
| if (unlikely((cons & MAX_TX_DESC_CNT) == MAX_TX_DESC_CNT)) |
| cons++; |
| return cons; |
| } |
| |
| static int |
| bnx2_tx_int(struct bnx2 *bp, struct bnx2_napi *bnapi, int budget) |
| { |
| struct bnx2_tx_ring_info *txr = &bnapi->tx_ring; |
| u16 hw_cons, sw_cons, sw_ring_cons; |
| int tx_pkt = 0, index; |
| struct netdev_queue *txq; |
| |
| index = (bnapi - bp->bnx2_napi); |
| txq = netdev_get_tx_queue(bp->dev, index); |
| |
| hw_cons = bnx2_get_hw_tx_cons(bnapi); |
| sw_cons = txr->tx_cons; |
| |
| while (sw_cons != hw_cons) { |
| struct sw_tx_bd *tx_buf; |
| struct sk_buff *skb; |
| int i, last; |
| |
| sw_ring_cons = TX_RING_IDX(sw_cons); |
| |
| tx_buf = &txr->tx_buf_ring[sw_ring_cons]; |
| skb = tx_buf->skb; |
| |
| /* prefetch skb_end_pointer() to speedup skb_shinfo(skb) */ |
| prefetch(&skb->end); |
| |
| /* partial BD completions possible with TSO packets */ |
| if (tx_buf->is_gso) { |
| u16 last_idx, last_ring_idx; |
| |
| last_idx = sw_cons + tx_buf->nr_frags + 1; |
| last_ring_idx = sw_ring_cons + tx_buf->nr_frags + 1; |
| if (unlikely(last_ring_idx >= MAX_TX_DESC_CNT)) { |
| last_idx++; |
| } |
| if (((s16) ((s16) last_idx - (s16) hw_cons)) > 0) { |
| break; |
| } |
| } |
| |
| dma_unmap_single(&bp->pdev->dev, dma_unmap_addr(tx_buf, mapping), |
| skb_headlen(skb), PCI_DMA_TODEVICE); |
| |
| tx_buf->skb = NULL; |
| last = tx_buf->nr_frags; |
| |
| for (i = 0; i < last; i++) { |
| sw_cons = NEXT_TX_BD(sw_cons); |
| |
| dma_unmap_page(&bp->pdev->dev, |
| dma_unmap_addr( |
| &txr->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); |
| |
| dev_kfree_skb(skb); |
| tx_pkt++; |
| if (tx_pkt == budget) |
| break; |
| |
| if (hw_cons == sw_cons) |
| hw_cons = bnx2_get_hw_tx_cons(bnapi); |
| } |
| |
| txr->hw_tx_cons = hw_cons; |
| txr->tx_cons = sw_cons; |
| |
| /* Need to make the tx_cons update visible to bnx2_start_xmit() |
| * before checking for netif_tx_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_tx_queue_stopped(txq)) && |
| (bnx2_tx_avail(bp, txr) > bp->tx_wake_thresh)) { |
| __netif_tx_lock(txq, smp_processor_id()); |
| if ((netif_tx_queue_stopped(txq)) && |
| (bnx2_tx_avail(bp, txr) > bp->tx_wake_thresh)) |
| netif_tx_wake_queue(txq); |
| __netif_tx_unlock(txq); |
| } |
| |
| return tx_pkt; |
| } |
| |
| static void |
| bnx2_reuse_rx_skb_pages(struct bnx2 *bp, struct bnx2_rx_ring_info *rxr, |
| struct sk_buff *skb, int count) |
| { |
| struct sw_pg *cons_rx_pg, *prod_rx_pg; |
| struct rx_bd *cons_bd, *prod_bd; |
| int i; |
| u16 hw_prod, prod; |
| u16 cons = rxr->rx_pg_cons; |
| |
| cons_rx_pg = &rxr->rx_pg_ring[cons]; |
| |
| /* The caller was unable to allocate a new page to replace the |
| * last one in the frags array, so we need to recycle that page |
| * and then free the skb. |
| */ |
| if (skb) { |
| struct page *page; |
| struct skb_shared_info *shinfo; |
| |
| shinfo = skb_shinfo(skb); |
| shinfo->nr_frags--; |
| page = shinfo->frags[shinfo->nr_frags].page; |
| shinfo->frags[shinfo->nr_frags].page = NULL; |
| |
| cons_rx_pg->page = page; |
| dev_kfree_skb(skb); |
| } |
| |
| hw_prod = rxr->rx_pg_prod; |
| |
| for (i = 0; i < count; i++) { |
| prod = RX_PG_RING_IDX(hw_prod); |
| |
| prod_rx_pg = &rxr->rx_pg_ring[prod]; |
| cons_rx_pg = &rxr->rx_pg_ring[cons]; |
| cons_bd = &rxr->rx_pg_desc_ring[RX_RING(cons)][RX_IDX(cons)]; |
| prod_bd = &rxr->rx_pg_desc_ring[RX_RING(prod)][RX_IDX(prod)]; |
| |
| if (prod != cons) { |
| prod_rx_pg->page = cons_rx_pg->page; |
| cons_rx_pg->page = NULL; |
| dma_unmap_addr_set(prod_rx_pg, mapping, |
| dma_unmap_addr(cons_rx_pg, mapping)); |
| |
| prod_bd->rx_bd_haddr_hi = cons_bd->rx_bd_haddr_hi; |
| prod_bd->rx_bd_haddr_lo = cons_bd->rx_bd_haddr_lo; |
| |
| } |
| cons = RX_PG_RING_IDX(NEXT_RX_BD(cons)); |
| hw_prod = NEXT_RX_BD(hw_prod); |
| } |
| rxr->rx_pg_prod = hw_prod; |
| rxr->rx_pg_cons = cons; |
| } |
| |
| static inline void |
| bnx2_reuse_rx_skb(struct bnx2 *bp, struct bnx2_rx_ring_info *rxr, |
| 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 = &rxr->rx_buf_ring[cons]; |
| prod_rx_buf = &rxr->rx_buf_ring[prod]; |
| |
| dma_sync_single_for_device(&bp->pdev->dev, |
| dma_unmap_addr(cons_rx_buf, mapping), |
| BNX2_RX_OFFSET + BNX2_RX_COPY_THRESH, PCI_DMA_FROMDEVICE); |
| |
| rxr->rx_prod_bseq += bp->rx_buf_use_size; |
| |
| prod_rx_buf->skb = skb; |
| prod_rx_buf->desc = (struct l2_fhdr *) skb->data; |
| |
| if (cons == prod) |
| return; |
| |
| dma_unmap_addr_set(prod_rx_buf, mapping, |
| dma_unmap_addr(cons_rx_buf, mapping)); |
| |
| cons_bd = &rxr->rx_desc_ring[RX_RING(cons)][RX_IDX(cons)]; |
| prod_bd = &rxr->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_skb(struct bnx2 *bp, struct bnx2_rx_ring_info *rxr, struct sk_buff *skb, |
| unsigned int len, unsigned int hdr_len, dma_addr_t dma_addr, |
| u32 ring_idx) |
| { |
| int err; |
| u16 prod = ring_idx & 0xffff; |
| |
| err = bnx2_alloc_rx_skb(bp, rxr, prod, GFP_ATOMIC); |
| if (unlikely(err)) { |
| bnx2_reuse_rx_skb(bp, rxr, skb, (u16) (ring_idx >> 16), prod); |
| if (hdr_len) { |
| unsigned int raw_len = len + 4; |
| int pages = PAGE_ALIGN(raw_len - hdr_len) >> PAGE_SHIFT; |
| |
| bnx2_reuse_rx_skb_pages(bp, rxr, NULL, pages); |
| } |
| return err; |
| } |
| |
| skb_reserve(skb, BNX2_RX_OFFSET); |
| dma_unmap_single(&bp->pdev->dev, dma_addr, bp->rx_buf_use_size, |
| PCI_DMA_FROMDEVICE); |
| |
| if (hdr_len == 0) { |
| skb_put(skb, len); |
| return 0; |
| } else { |
| unsigned int i, frag_len, frag_size, pages; |
| struct sw_pg *rx_pg; |
| u16 pg_cons = rxr->rx_pg_cons; |
| u16 pg_prod = rxr->rx_pg_prod; |
| |
| frag_size = len + 4 - hdr_len; |
| pages = PAGE_ALIGN(frag_size) >> PAGE_SHIFT; |
| skb_put(skb, hdr_len); |
| |
| for (i = 0; i < pages; i++) { |
| dma_addr_t mapping_old; |
| |
| frag_len = min(frag_size, (unsigned int) PAGE_SIZE); |
| if (unlikely(frag_len <= 4)) { |
| unsigned int tail = 4 - frag_len; |
| |
| rxr->rx_pg_cons = pg_cons; |
| rxr->rx_pg_prod = pg_prod; |
| bnx2_reuse_rx_skb_pages(bp, rxr, NULL, |
| pages - i); |
| skb->len -= tail; |
| if (i == 0) { |
| skb->tail -= tail; |
| } else { |
| skb_frag_t *frag = |
| &skb_shinfo(skb)->frags[i - 1]; |
| frag->size -= tail; |
| skb->data_len -= tail; |
| skb->truesize -= tail; |
| } |
| return 0; |
| } |
| rx_pg = &rxr->rx_pg_ring[pg_cons]; |
| |
| /* Don't unmap yet. If we're unable to allocate a new |
| * page, we need to recycle the page and the DMA addr. |
| */ |
| mapping_old = dma_unmap_addr(rx_pg, mapping); |
| if (i == pages - 1) |
| frag_len -= 4; |
| |
| skb_fill_page_desc(skb, i, rx_pg->page, 0, frag_len); |
| rx_pg->page = NULL; |
| |
| err = bnx2_alloc_rx_page(bp, rxr, |
| RX_PG_RING_IDX(pg_prod), |
| GFP_ATOMIC); |
| if (unlikely(err)) { |
| rxr->rx_pg_cons = pg_cons; |
| rxr->rx_pg_prod = pg_prod; |
| bnx2_reuse_rx_skb_pages(bp, rxr, skb, |
| pages - i); |
| return err; |
| } |
| |
| dma_unmap_page(&bp->pdev->dev, mapping_old, |
| PAGE_SIZE, PCI_DMA_FROMDEVICE); |
| |
| frag_size -= frag_len; |
| skb->data_len += frag_len; |
| skb->truesize += frag_len; |
| skb->len += frag_len; |
| |
| pg_prod = NEXT_RX_BD(pg_prod); |
| pg_cons = RX_PG_RING_IDX(NEXT_RX_BD(pg_cons)); |
| } |
|