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

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
Loading
Loading
Loading
Loading
+209 −105
Original line number Diff line number Diff line
@@ -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);

		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,
};