Commit 6cfb3bcc authored by Charles-Antoine Couret's avatar Charles-Antoine Couret Committed by David S. Miller

Marvell phy: check link status in case of fiber link.

For concerned phy, the fiber link is checked before the copper link.
According to datasheet, the link which is up is enabled.

If both links are down, copper link would be used.
To detect fiber link status, we used the real time status
because of troubles with the copper method.

Tested with Marvell 88E1512.
Reviewed-by: default avatarAndrew Lunn <andrew@lunn.ch>
Signed-off-by: default avatarCharles-Antoine Couret <charles-antoine.couret@nexvision.fr>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent bcf77d5b
...@@ -138,6 +138,20 @@ ...@@ -138,6 +138,20 @@
#define MII_88E1510_GEN_CTRL_REG_1_MODE_SGMII 0x1 /* SGMII to copper */ #define MII_88E1510_GEN_CTRL_REG_1_MODE_SGMII 0x1 /* SGMII to copper */
#define MII_88E1510_GEN_CTRL_REG_1_RESET 0x8000 /* Soft reset */ #define MII_88E1510_GEN_CTRL_REG_1_RESET 0x8000 /* Soft reset */
#define LPA_FIBER_1000HALF 0x40
#define LPA_FIBER_1000FULL 0x20
#define LPA_PAUSE_FIBER 0x180
#define LPA_PAUSE_ASYM_FIBER 0x100
#define ADVERTISE_FIBER_1000HALF 0x40
#define ADVERTISE_FIBER_1000FULL 0x20
#define ADVERTISE_PAUSE_FIBER 0x180
#define ADVERTISE_PAUSE_ASYM_FIBER 0x100
#define REGISTER_LINK_STATUS 0x400
MODULE_DESCRIPTION("Marvell PHY driver"); MODULE_DESCRIPTION("Marvell PHY driver");
MODULE_AUTHOR("Andy Fleming"); MODULE_AUTHOR("Andy Fleming");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
...@@ -890,26 +904,79 @@ static int m88e1145_config_init(struct phy_device *phydev) ...@@ -890,26 +904,79 @@ static int m88e1145_config_init(struct phy_device *phydev)
return 0; return 0;
} }
/* marvell_read_status /**
* fiber_lpa_to_ethtool_lpa_t
* @lpa: value of the MII_LPA register for fiber link
*
* A small helper function that translates MII_LPA
* bits to ethtool LP advertisement settings.
*/
static u32 fiber_lpa_to_ethtool_lpa_t(u32 lpa)
{
u32 result = 0;
if (lpa & LPA_FIBER_1000HALF)
result |= ADVERTISED_1000baseT_Half;
if (lpa & LPA_FIBER_1000FULL)
result |= ADVERTISED_1000baseT_Full;
return result;
}
/**
* marvell_update_link - update link status in real time in @phydev
* @phydev: target phy_device struct
*
* Description: Update the value in phydev->link to reflect the
* current link value.
*/
static int marvell_update_link(struct phy_device *phydev, int fiber)
{
int status;
/* Use the generic register for copper link, or specific
* register for fiber case */
if (fiber) {
status = phy_read(phydev, MII_M1011_PHY_STATUS);
if (status < 0)
return status;
if ((status & REGISTER_LINK_STATUS) == 0)
phydev->link = 0;
else
phydev->link = 1;
} else {
return genphy_update_link(phydev);
}
return 0;
}
/* marvell_read_status_page
* *
* Generic status code does not detect Fiber correctly!
* Description: * Description:
* Check the link, then figure out the current state * Check the link, then figure out the current state
* by comparing what we advertise with what the link partner * by comparing what we advertise with what the link partner
* advertises. Start by checking the gigabit possibilities, * advertises. Start by checking the gigabit possibilities,
* then move on to 10/100. * then move on to 10/100.
*/ */
static int marvell_read_status(struct phy_device *phydev) static int marvell_read_status_page(struct phy_device *phydev, int page)
{ {
int adv; int adv;
int err; int err;
int lpa; int lpa;
int lpagb; int lpagb;
int status = 0; int status = 0;
int fiber;
/* Update the link, but return if there /* Detect and update the link, but return if there
* was an error */ * was an error */
err = genphy_update_link(phydev); if (page == MII_M1111_FIBER)
fiber = 1;
else
fiber = 0;
err = marvell_update_link(phydev, fiber);
if (err) if (err)
return err; return err;
...@@ -930,9 +997,6 @@ static int marvell_read_status(struct phy_device *phydev) ...@@ -930,9 +997,6 @@ static int marvell_read_status(struct phy_device *phydev)
if (adv < 0) if (adv < 0)
return adv; return adv;
phydev->lp_advertising = mii_stat1000_to_ethtool_lpa_t(lpagb) |
mii_lpa_to_ethtool_lpa_t(lpa);
lpa &= adv; lpa &= adv;
if (status & MII_M1011_PHY_STATUS_FULLDUPLEX) if (status & MII_M1011_PHY_STATUS_FULLDUPLEX)
...@@ -957,10 +1021,31 @@ static int marvell_read_status(struct phy_device *phydev) ...@@ -957,10 +1021,31 @@ static int marvell_read_status(struct phy_device *phydev)
break; break;
} }
if (!fiber) {
phydev->lp_advertising = mii_stat1000_to_ethtool_lpa_t(lpagb) |
mii_lpa_to_ethtool_lpa_t(lpa);
if (phydev->duplex == DUPLEX_FULL) { if (phydev->duplex == DUPLEX_FULL) {
phydev->pause = lpa & LPA_PAUSE_CAP ? 1 : 0; phydev->pause = lpa & LPA_PAUSE_CAP ? 1 : 0;
phydev->asym_pause = lpa & LPA_PAUSE_ASYM ? 1 : 0; phydev->asym_pause = lpa & LPA_PAUSE_ASYM ? 1 : 0;
} }
} else {
/* The fiber link is only 1000M capable */
phydev->lp_advertising = fiber_lpa_to_ethtool_lpa_t(lpa);
if (phydev->duplex == DUPLEX_FULL) {
if (!(lpa & LPA_PAUSE_FIBER)) {
phydev->pause = 0;
phydev->asym_pause = 0;
} else if ((lpa & LPA_PAUSE_ASYM_FIBER)) {
phydev->pause = 1;
phydev->asym_pause = 1;
} else {
phydev->pause = 1;
phydev->asym_pause = 0;
}
}
}
} else { } else {
int bmcr = phy_read(phydev, MII_BMCR); int bmcr = phy_read(phydev, MII_BMCR);
...@@ -986,6 +1071,50 @@ static int marvell_read_status(struct phy_device *phydev) ...@@ -986,6 +1071,50 @@ static int marvell_read_status(struct phy_device *phydev)
return 0; return 0;
} }
/* marvell_read_status
*
* Some Marvell's phys have two modes: fiber and copper.
* Both need status checked.
* Description:
* First, check the fiber link and status.
* If the fiber link is down, check the copper link and status which
* will be the default value if both link are down.
*/
static int marvell_read_status(struct phy_device *phydev)
{
int err;
/* Check the fiber mode first */
if (phydev->supported & SUPPORTED_FIBRE) {
err = phy_write(phydev, MII_MARVELL_PHY_PAGE, MII_M1111_FIBER);
if (err < 0)
goto error;
err = marvell_read_status_page(phydev, MII_M1111_FIBER);
if (err < 0)
goto error;
/* If the fiber link is up, it is the selected and used link.
* In this case, we need to stay in the fiber page.
* Please to be careful about that, avoid to restore Copper page
* in other functions which could break the behaviour
* for some fiber phy like 88E1512.
* */
if (phydev->link)
return 0;
/* If fiber link is down, check and save copper mode state */
err = phy_write(phydev, MII_MARVELL_PHY_PAGE, MII_M1111_COPPER);
if (err < 0)
goto error;
}
return marvell_read_status_page(phydev, MII_M1111_COPPER);
error:
phy_write(phydev, MII_MARVELL_PHY_PAGE, MII_M1111_COPPER);
return err;
}
static int marvell_aneg_done(struct phy_device *phydev) static int marvell_aneg_done(struct phy_device *phydev)
{ {
int retval = phy_read(phydev, MII_M1011_PHY_STATUS); int retval = phy_read(phydev, MII_M1011_PHY_STATUS);
...@@ -1361,7 +1490,7 @@ static struct phy_driver marvell_drivers[] = { ...@@ -1361,7 +1490,7 @@ static struct phy_driver marvell_drivers[] = {
.phy_id = MARVELL_PHY_ID_88E1510, .phy_id = MARVELL_PHY_ID_88E1510,
.phy_id_mask = MARVELL_PHY_ID_MASK, .phy_id_mask = MARVELL_PHY_ID_MASK,
.name = "Marvell 88E1510", .name = "Marvell 88E1510",
.features = PHY_GBIT_FEATURES, .features = PHY_GBIT_FEATURES | SUPPORTED_FIBRE,
.flags = PHY_HAS_INTERRUPT, .flags = PHY_HAS_INTERRUPT,
.probe = marvell_probe, .probe = marvell_probe,
.config_init = &m88e1510_config_init, .config_init = &m88e1510_config_init,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment