[BNX2]: Add support for remote PHY.

In blade servers, the Serdes PHY in 5708S can control the remote
copper PHY through autonegotiation on the backplane.  This patch adds
the logic to interface with the firmware to control the remote PHY
autonegotiation and to handle remote PHY link events.

When remote PHY is present, the 5708S Serdes device practically
becomes a copper device with full control over the 1000Base-T
link settings.

Signed-off-by: Michael Chan <mchan@broadcom.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Michael Chan 2007-07-07 22:49:43 -07:00 committed by David S. Miller
parent 9700e6befe
commit 0d8a657105
1 changed files with 269 additions and 8 deletions

View File

@ -550,6 +550,9 @@ bnx2_report_fw_link(struct bnx2 *bp)
{
u32 fw_link_status = 0;
if (bp->phy_flags & REMOTE_PHY_CAP_FLAG)
return;
if (bp->link_up) {
u32 bmsr;
@ -1100,6 +1103,9 @@ bnx2_set_link(struct bnx2 *bp)
return 0;
}
if (bp->phy_flags & REMOTE_PHY_CAP_FLAG)
return 0;
link_up = bp->link_up;
bnx2_enable_bmsr1(bp);
@ -1210,12 +1216,74 @@ bnx2_phy_get_pause_adv(struct bnx2 *bp)
return adv;
}
static int bnx2_fw_sync(struct bnx2 *, u32, int);
static int
bnx2_setup_serdes_phy(struct bnx2 *bp)
bnx2_setup_remote_phy(struct bnx2 *bp, u8 port)
{
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_1000XPSE_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;
REG_WR_IND(bp, bp->shmem_base + BNX2_DRV_MB_ARG0, speed_arg);
spin_unlock_bh(&bp->phy_lock);
bnx2_fw_sync(bp, BNX2_DRV_MSG_CODE_CMD_SET_LINK, 0);
spin_lock_bh(&bp->phy_lock);
return 0;
}
static int
bnx2_setup_serdes_phy(struct bnx2 *bp, u8 port)
{
u32 adv, bmcr;
u32 new_adv = 0;
if (bp->phy_flags & REMOTE_PHY_CAP_FLAG)
return (bnx2_setup_remote_phy(bp, port));
if (!(bp->autoneg & AUTONEG_SPEED)) {
u32 new_bmcr;
int force_link_down = 0;
@ -1337,9 +1405,59 @@ bnx2_setup_serdes_phy(struct bnx2 *bp)
#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 = REG_RD_IND(bp, bp->shmem_base + BNX2_RPHY_COPPER_LINK);
else
link = REG_RD_IND(bp, bp->shmem_base + 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 & REMOTE_PHY_CAP_FLAG)
return bnx2_set_default_remote_link(bp);
bp->autoneg = AUTONEG_SPEED | AUTONEG_FLOW_CTRL;
bp->req_line_speed = 0;
if (bp->phy_flags & PHY_SERDES_FLAG) {
@ -1358,6 +1476,97 @@ bnx2_set_default_link(struct bnx2 *bp)
bp->advertising = ETHTOOL_ALL_COPPER_SPEED | ADVERTISED_Autoneg;
}
static void
bnx2_remote_phy_event(struct bnx2 *bp)
{
u32 msg;
u8 link_up = bp->link_up;
u8 old_port;
msg = REG_RD_IND(bp, bp->shmem_base + BNX2_LINK_STATUS);
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;
}
spin_lock(&bp->phy_lock);
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);
spin_unlock(&bp->phy_lock);
}
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 = REG_RD_IND(bp, bp->shmem_base + 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:
break;
}
return 0;
}
static int
bnx2_setup_copper_phy(struct bnx2 *bp)
{
@ -1456,13 +1665,13 @@ bnx2_setup_copper_phy(struct bnx2 *bp)
}
static int
bnx2_setup_phy(struct bnx2 *bp)
bnx2_setup_phy(struct bnx2 *bp, u8 port)
{
if (bp->loopback == MAC_LOOPBACK)
return 0;
if (bp->phy_flags & PHY_SERDES_FLAG) {
return (bnx2_setup_serdes_phy(bp));
return (bnx2_setup_serdes_phy(bp, port));
}
else {
return (bnx2_setup_copper_phy(bp));
@ -1682,6 +1891,9 @@ bnx2_init_phy(struct bnx2 *bp)
REG_WR(bp, BNX2_EMAC_ATTENTION_ENA, BNX2_EMAC_ATTENTION_ENA_LINK);
if (bp->phy_flags & REMOTE_PHY_CAP_FLAG)
goto setup_phy;
bnx2_read_phy(bp, MII_PHYSID1, &val);
bp->phy_id = val << 16;
bnx2_read_phy(bp, MII_PHYSID2, &val);
@ -1699,7 +1911,9 @@ bnx2_init_phy(struct bnx2 *bp)
rc = bnx2_init_copper_phy(bp);
}
bnx2_setup_phy(bp);
setup_phy:
if (!rc)
rc = bnx2_setup_phy(bp, bp->phy_port);
return rc;
}
@ -2007,6 +2221,9 @@ bnx2_phy_int(struct bnx2 *bp)
bnx2_set_link(bp);
spin_unlock(&bp->phy_lock);
}
if (bnx2_phy_event_is_set(bp, STATUS_ATTN_BITS_TIMER_ABORT))
bnx2_set_remote_link(bp);
}
static void
@ -2345,7 +2562,8 @@ bnx2_interrupt(int irq, void *dev_instance)
return IRQ_HANDLED;
}
#define STATUS_ATTN_EVENTS STATUS_ATTN_BITS_LINK_STATE
#define STATUS_ATTN_EVENTS (STATUS_ATTN_BITS_LINK_STATE | \
STATUS_ATTN_BITS_TIMER_ABORT)
static inline int
bnx2_has_work(struct bnx2 *bp)
@ -3585,6 +3803,36 @@ nvram_write_end:
return rc;
}
static void
bnx2_init_remote_phy(struct bnx2 *bp)
{
u32 val;
bp->phy_flags &= ~REMOTE_PHY_CAP_FLAG;
if (!(bp->phy_flags & PHY_SERDES_FLAG))
return;
val = REG_RD_IND(bp, bp->shmem_base + BNX2_FW_CAP_MB);
if ((val & BNX2_FW_CAP_SIGNATURE_MASK) != BNX2_FW_CAP_SIGNATURE)
return;
if (val & BNX2_FW_CAP_REMOTE_PHY_CAPABLE) {
if (netif_running(bp->dev)) {
val = BNX2_DRV_ACK_CAP_SIGNATURE |
BNX2_FW_CAP_REMOTE_PHY_CAPABLE;
REG_WR_IND(bp, bp->shmem_base + BNX2_DRV_ACK_CAP_MB,
val);
}
bp->phy_flags |= REMOTE_PHY_CAP_FLAG;
val = REG_RD_IND(bp, bp->shmem_base + BNX2_LINK_STATUS);
if (val & BNX2_LINK_STATUS_SERDES_LINK)
bp->phy_port = PORT_FIBRE;
else
bp->phy_port = PORT_TP;
}
}
static int
bnx2_reset_chip(struct bnx2 *bp, u32 reset_code)
{
@ -3665,6 +3913,12 @@ bnx2_reset_chip(struct bnx2 *bp, u32 reset_code)
if (rc)
return rc;
spin_lock_bh(&bp->phy_lock);
bnx2_init_remote_phy(bp);
if (bp->phy_flags & REMOTE_PHY_CAP_FLAG)
bnx2_set_default_remote_link(bp);
spin_unlock_bh(&bp->phy_lock);
if (CHIP_ID(bp) == CHIP_ID_5706_A0) {
/* Adjust the voltage regular to two steps lower. The default
* of this register is 0x0000000e. */
@ -4092,8 +4346,8 @@ bnx2_init_nic(struct bnx2 *bp)
spin_lock_bh(&bp->phy_lock);
bnx2_init_phy(bp);
spin_unlock_bh(&bp->phy_lock);
bnx2_set_link(bp);
spin_unlock_bh(&bp->phy_lock);
return 0;
}
@ -4623,6 +4877,9 @@ bnx2_5706_serdes_timer(struct bnx2 *bp)
static void
bnx2_5708_serdes_timer(struct bnx2 *bp)
{
if (bp->phy_flags & REMOTE_PHY_CAP_FLAG)
return;
if ((bp->phy_flags & PHY_2_5G_CAPABLE_FLAG) == 0) {
bp->serdes_an_pending = 0;
return;
@ -5222,7 +5479,7 @@ bnx2_set_settings(struct net_device *dev, struct ethtool_cmd *cmd)
spin_lock_bh(&bp->phy_lock);
bnx2_setup_phy(bp);
bnx2_setup_phy(bp, bp->phy_port);
spin_unlock_bh(&bp->phy_lock);
@ -5566,7 +5823,7 @@ bnx2_set_pauseparam(struct net_device *dev, struct ethtool_pauseparam *epause)
spin_lock_bh(&bp->phy_lock);
bnx2_setup_phy(bp);
bnx2_setup_phy(bp, bp->phy_port);
spin_unlock_bh(&bp->phy_lock);
@ -6338,7 +6595,9 @@ bnx2_init_board(struct pci_dev *pdev, struct net_device *dev)
else if (CHIP_BOND_ID(bp) & CHIP_BOND_ID_SERDES_BIT)
bp->phy_flags |= PHY_SERDES_FLAG;
bp->phy_port = PORT_TP;
if (bp->phy_flags & PHY_SERDES_FLAG) {
bp->phy_port = PORT_FIBRE;
bp->flags |= NO_WOL_FLAG;
if (CHIP_NUM(bp) != CHIP_NUM_5706) {
bp->phy_addr = 2;
@ -6347,6 +6606,8 @@ bnx2_init_board(struct pci_dev *pdev, struct net_device *dev)
if (reg & BNX2_SHARED_HW_CFG_PHY_2_5G)
bp->phy_flags |= PHY_2_5G_CAPABLE_FLAG;
}
bnx2_init_remote_phy(bp);
} else if (CHIP_NUM(bp) == CHIP_NUM_5706 ||
CHIP_NUM(bp) == CHIP_NUM_5708)
bp->phy_flags |= PHY_CRC_FIX_FLAG;