Commit 945c6ff8 authored by David S. Miller's avatar David S. Miller

Merge branch 'marvell-88x2222-improvements'

Ivan Bornyakov says:

====================
net: phy: marvell-88x2222: a couple of improvements

First, there are some SFP modules that only uses RX_LOS for link
indication. Add check that link is operational before actual read of
line-side status.

Second, it is invalid to set 10G speed without autonegotiation,
according to phy_ethtool_ksettings_set(). Implement switching between
10GBase-R and 1000Base-X/SGMII if autonegotiation can't complete but
there is signal in line.

Changelog:
  v1 -> v2:
    * make checking that link is operational more friendly for
      trancievers without SFP cages.
    * split swapping 1G/10G modes into non-functional and functional
      commits for the sake of easier review.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 87b7e5c0 d7029f55
......@@ -32,6 +32,10 @@
#define MV_HOST_RST_SW BIT(7)
#define MV_PORT_RST_SW (MV_LINE_RST_SW | MV_HOST_RST_SW)
/* PMD Receive Signal Detect */
#define MV_RX_SIGNAL_DETECT 0x000A
#define MV_RX_SIGNAL_DETECT_GLOBAL BIT(0)
/* 1000Base-X/SGMII Control Register */
#define MV_1GBX_CTRL (0x2000 + MII_BMCR)
......@@ -48,9 +52,12 @@
#define MV_1GBX_PHY_STAT_SPEED100 BIT(14)
#define MV_1GBX_PHY_STAT_SPEED1000 BIT(15)
#define AUTONEG_TIMEOUT 3
struct mv2222_data {
phy_interface_t line_interface;
__ETHTOOL_DECLARE_LINK_MODE_MASK(supported);
bool sfp_link;
};
/* SFI PMA transmit enable */
......@@ -81,86 +88,6 @@ static int mv2222_soft_reset(struct phy_device *phydev)
5000, 1000000, true);
}
/* Returns negative on error, 0 if link is down, 1 if link is up */
static int mv2222_read_status_10g(struct phy_device *phydev)
{
int val, link = 0;
val = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_STAT1);
if (val < 0)
return val;
if (val & MDIO_STAT1_LSTATUS) {
link = 1;
/* 10GBASE-R do not support auto-negotiation */
phydev->autoneg = AUTONEG_DISABLE;
phydev->speed = SPEED_10000;
phydev->duplex = DUPLEX_FULL;
}
return link;
}
/* Returns negative on error, 0 if link is down, 1 if link is up */
static int mv2222_read_status_1g(struct phy_device *phydev)
{
int val, link = 0;
val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_STAT);
if (val < 0)
return val;
if (!(val & BMSR_LSTATUS) ||
(phydev->autoneg == AUTONEG_ENABLE &&
!(val & BMSR_ANEGCOMPLETE)))
return 0;
link = 1;
val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_PHY_STAT);
if (val < 0)
return val;
if (val & MV_1GBX_PHY_STAT_AN_RESOLVED) {
if (val & MV_1GBX_PHY_STAT_DUPLEX)
phydev->duplex = DUPLEX_FULL;
else
phydev->duplex = DUPLEX_HALF;
if (val & MV_1GBX_PHY_STAT_SPEED1000)
phydev->speed = SPEED_1000;
else if (val & MV_1GBX_PHY_STAT_SPEED100)
phydev->speed = SPEED_100;
else
phydev->speed = SPEED_10;
}
return link;
}
static int mv2222_read_status(struct phy_device *phydev)
{
struct mv2222_data *priv = phydev->priv;
int link;
phydev->link = 0;
phydev->speed = SPEED_UNKNOWN;
phydev->duplex = DUPLEX_UNKNOWN;
if (priv->line_interface == PHY_INTERFACE_MODE_10GBASER)
link = mv2222_read_status_10g(phydev);
else
link = mv2222_read_status_1g(phydev);
if (link < 0)
return link;
phydev->link = link;
return 0;
}
static int mv2222_disable_aneg(struct phy_device *phydev)
{
int ret = phy_clear_bits_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_CTRL,
......@@ -248,6 +175,24 @@ static bool mv2222_is_1gbx_capable(struct phy_device *phydev)
priv->supported);
}
static bool mv2222_is_sgmii_capable(struct phy_device *phydev)
{
struct mv2222_data *priv = phydev->priv;
return (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
priv->supported) ||
linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
priv->supported) ||
linkmode_test_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT,
priv->supported) ||
linkmode_test_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT,
priv->supported) ||
linkmode_test_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT,
priv->supported) ||
linkmode_test_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT,
priv->supported));
}
static int mv2222_config_line(struct phy_device *phydev)
{
struct mv2222_data *priv = phydev->priv;
......@@ -267,7 +212,8 @@ static int mv2222_config_line(struct phy_device *phydev)
}
}
static int mv2222_setup_forced(struct phy_device *phydev)
/* Switch between 1G (1000Base-X/SGMII) and 10G (10GBase-R) modes */
static int mv2222_swap_line_type(struct phy_device *phydev)
{
struct mv2222_data *priv = phydev->priv;
bool changed = false;
......@@ -275,25 +221,23 @@ static int mv2222_setup_forced(struct phy_device *phydev)
switch (priv->line_interface) {
case PHY_INTERFACE_MODE_10GBASER:
if (phydev->speed == SPEED_1000 &&
mv2222_is_1gbx_capable(phydev)) {
if (mv2222_is_1gbx_capable(phydev)) {
priv->line_interface = PHY_INTERFACE_MODE_1000BASEX;
changed = true;
}
break;
case PHY_INTERFACE_MODE_1000BASEX:
if (phydev->speed == SPEED_10000 &&
mv2222_is_10g_capable(phydev)) {
priv->line_interface = PHY_INTERFACE_MODE_10GBASER;
if (mv2222_is_sgmii_capable(phydev)) {
priv->line_interface = PHY_INTERFACE_MODE_SGMII;
changed = true;
}
break;
case PHY_INTERFACE_MODE_1000BASEX:
case PHY_INTERFACE_MODE_SGMII:
ret = mv2222_set_sgmii_speed(phydev);
if (ret < 0)
return ret;
if (mv2222_is_10g_capable(phydev)) {
priv->line_interface = PHY_INTERFACE_MODE_10GBASER;
changed = true;
}
break;
default:
......@@ -306,6 +250,29 @@ static int mv2222_setup_forced(struct phy_device *phydev)
return ret;
}
return 0;
}
static int mv2222_setup_forced(struct phy_device *phydev)
{
struct mv2222_data *priv = phydev->priv;
int ret;
if (priv->line_interface == PHY_INTERFACE_MODE_10GBASER) {
if (phydev->speed < SPEED_10000 &&
phydev->speed != SPEED_UNKNOWN) {
ret = mv2222_swap_line_type(phydev);
if (ret < 0)
return ret;
}
}
if (priv->line_interface == PHY_INTERFACE_MODE_SGMII) {
ret = mv2222_set_sgmii_speed(phydev);
if (ret < 0)
return ret;
}
return mv2222_disable_aneg(phydev);
}
......@@ -319,17 +286,9 @@ static int mv2222_config_aneg(struct phy_device *phydev)
return 0;
if (phydev->autoneg == AUTONEG_DISABLE ||
phydev->speed == SPEED_10000)
priv->line_interface == PHY_INTERFACE_MODE_10GBASER)
return mv2222_setup_forced(phydev);
if (priv->line_interface == PHY_INTERFACE_MODE_10GBASER &&
mv2222_is_1gbx_capable(phydev)) {
priv->line_interface = PHY_INTERFACE_MODE_1000BASEX;
ret = mv2222_config_line(phydev);
if (ret < 0)
return ret;
}
adv = linkmode_adv_to_mii_adv_x(priv->supported,
ETHTOOL_LINK_MODE_1000baseX_Full_BIT);
......@@ -363,6 +322,135 @@ static int mv2222_aneg_done(struct phy_device *phydev)
return (ret & BMSR_ANEGCOMPLETE);
}
/* Returns negative on error, 0 if link is down, 1 if link is up */
static int mv2222_read_status_10g(struct phy_device *phydev)
{
static int timeout;
int val, link = 0;
val = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_STAT1);
if (val < 0)
return val;
if (val & MDIO_STAT1_LSTATUS) {
link = 1;
/* 10GBASE-R do not support auto-negotiation */
phydev->autoneg = AUTONEG_DISABLE;
phydev->speed = SPEED_10000;
phydev->duplex = DUPLEX_FULL;
} else {
if (phydev->autoneg == AUTONEG_ENABLE) {
timeout++;
if (timeout > AUTONEG_TIMEOUT) {
timeout = 0;
val = mv2222_swap_line_type(phydev);
if (val < 0)
return val;
return mv2222_config_aneg(phydev);
}
}
}
return link;
}
/* Returns negative on error, 0 if link is down, 1 if link is up */
static int mv2222_read_status_1g(struct phy_device *phydev)
{
static int timeout;
int val, link = 0;
val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_STAT);
if (val < 0)
return val;
if (phydev->autoneg == AUTONEG_ENABLE &&
!(val & BMSR_ANEGCOMPLETE)) {
timeout++;
if (timeout > AUTONEG_TIMEOUT) {
timeout = 0;
val = mv2222_swap_line_type(phydev);
if (val < 0)
return val;
return mv2222_config_aneg(phydev);
}
return 0;
}
if (!(val & BMSR_LSTATUS))
return 0;
link = 1;
val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_PHY_STAT);
if (val < 0)
return val;
if (val & MV_1GBX_PHY_STAT_AN_RESOLVED) {
if (val & MV_1GBX_PHY_STAT_DUPLEX)
phydev->duplex = DUPLEX_FULL;
else
phydev->duplex = DUPLEX_HALF;
if (val & MV_1GBX_PHY_STAT_SPEED1000)
phydev->speed = SPEED_1000;
else if (val & MV_1GBX_PHY_STAT_SPEED100)
phydev->speed = SPEED_100;
else
phydev->speed = SPEED_10;
}
return link;
}
static bool mv2222_link_is_operational(struct phy_device *phydev)
{
struct mv2222_data *priv = phydev->priv;
int val;
val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MV_RX_SIGNAL_DETECT);
if (val < 0 || !(val & MV_RX_SIGNAL_DETECT_GLOBAL))
return false;
if (phydev->sfp_bus && !priv->sfp_link)
return false;
return true;
}
static int mv2222_read_status(struct phy_device *phydev)
{
struct mv2222_data *priv = phydev->priv;
int link;
phydev->link = 0;
phydev->speed = SPEED_UNKNOWN;
phydev->duplex = DUPLEX_UNKNOWN;
if (!mv2222_link_is_operational(phydev))
return 0;
if (priv->line_interface == PHY_INTERFACE_MODE_10GBASER)
link = mv2222_read_status_10g(phydev);
else
link = mv2222_read_status_1g(phydev);
if (link < 0)
return link;
phydev->link = link;
return 0;
}
static int mv2222_resume(struct phy_device *phydev)
{
return mv2222_tx_enable(phydev);
......@@ -424,11 +512,7 @@ static int mv2222_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
return ret;
if (mutex_trylock(&phydev->lock)) {
if (priv->line_interface == PHY_INTERFACE_MODE_10GBASER)
ret = mv2222_setup_forced(phydev);
else
ret = mv2222_config_aneg(phydev);
ret = mv2222_config_aneg(phydev);
mutex_unlock(&phydev->lock);
}
......@@ -446,9 +530,29 @@ static void mv2222_sfp_remove(void *upstream)
linkmode_zero(priv->supported);
}
static void mv2222_sfp_link_up(void *upstream)
{
struct phy_device *phydev = upstream;
struct mv2222_data *priv;
priv = phydev->priv;
priv->sfp_link = true;
}
static void mv2222_sfp_link_down(void *upstream)
{
struct phy_device *phydev = upstream;
struct mv2222_data *priv;
priv = phydev->priv;
priv->sfp_link = false;
}
static const struct sfp_upstream_ops sfp_phy_ops = {
.module_insert = mv2222_sfp_insert,
.module_remove = mv2222_sfp_remove,
.link_up = mv2222_sfp_link_up,
.link_down = mv2222_sfp_link_down,
.attach = phy_sfp_attach,
.detach = phy_sfp_detach,
};
......
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