16e3bac3eSIvan Bornyakov // SPDX-License-Identifier: GPL-2.0+
26e3bac3eSIvan Bornyakov /*
36e3bac3eSIvan Bornyakov  * Marvell 88x2222 dual-port multi-speed ethernet transceiver.
46e3bac3eSIvan Bornyakov  *
56e3bac3eSIvan Bornyakov  * Supports:
66e3bac3eSIvan Bornyakov  *	XAUI on the host side.
76e3bac3eSIvan Bornyakov  *	1000Base-X or 10GBase-R on the line side.
86e3bac3eSIvan Bornyakov  *	SGMII over 1000Base-X.
96e3bac3eSIvan Bornyakov  */
106e3bac3eSIvan Bornyakov #include <linux/module.h>
116e3bac3eSIvan Bornyakov #include <linux/phy.h>
126e3bac3eSIvan Bornyakov #include <linux/gpio.h>
136e3bac3eSIvan Bornyakov #include <linux/delay.h>
146e3bac3eSIvan Bornyakov #include <linux/mdio.h>
156e3bac3eSIvan Bornyakov #include <linux/marvell_phy.h>
166e3bac3eSIvan Bornyakov #include <linux/of.h>
176e3bac3eSIvan Bornyakov #include <linux/of_device.h>
186e3bac3eSIvan Bornyakov #include <linux/of_gpio.h>
196e3bac3eSIvan Bornyakov #include <linux/sfp.h>
206e3bac3eSIvan Bornyakov #include <linux/netdevice.h>
216e3bac3eSIvan Bornyakov 
226e3bac3eSIvan Bornyakov /* Port PCS Configuration */
236e3bac3eSIvan Bornyakov #define	MV_PCS_CONFIG		0xF002
246e3bac3eSIvan Bornyakov #define	MV_PCS_HOST_XAUI	0x73
256e3bac3eSIvan Bornyakov #define	MV_PCS_LINE_10GBR	(0x71 << 8)
266e3bac3eSIvan Bornyakov #define	MV_PCS_LINE_1GBX_AN	(0x7B << 8)
276e3bac3eSIvan Bornyakov #define	MV_PCS_LINE_SGMII_AN	(0x7F << 8)
286e3bac3eSIvan Bornyakov 
296e3bac3eSIvan Bornyakov /* Port Reset and Power Down */
306e3bac3eSIvan Bornyakov #define	MV_PORT_RST	0xF003
316e3bac3eSIvan Bornyakov #define	MV_LINE_RST_SW	BIT(15)
326e3bac3eSIvan Bornyakov #define	MV_HOST_RST_SW	BIT(7)
336e3bac3eSIvan Bornyakov #define	MV_PORT_RST_SW	(MV_LINE_RST_SW | MV_HOST_RST_SW)
346e3bac3eSIvan Bornyakov 
3558581478SIvan Bornyakov /* PMD Receive Signal Detect */
3658581478SIvan Bornyakov #define	MV_RX_SIGNAL_DETECT		0x000A
3758581478SIvan Bornyakov #define	MV_RX_SIGNAL_DETECT_GLOBAL	BIT(0)
3858581478SIvan Bornyakov 
396e3bac3eSIvan Bornyakov /* 1000Base-X/SGMII Control Register */
406e3bac3eSIvan Bornyakov #define	MV_1GBX_CTRL		(0x2000 + MII_BMCR)
416e3bac3eSIvan Bornyakov 
426e3bac3eSIvan Bornyakov /* 1000BASE-X/SGMII Status Register */
436e3bac3eSIvan Bornyakov #define	MV_1GBX_STAT		(0x2000 + MII_BMSR)
446e3bac3eSIvan Bornyakov 
456e3bac3eSIvan Bornyakov /* 1000Base-X Auto-Negotiation Advertisement Register */
466e3bac3eSIvan Bornyakov #define	MV_1GBX_ADVERTISE	(0x2000 + MII_ADVERTISE)
476e3bac3eSIvan Bornyakov 
486e3bac3eSIvan Bornyakov /* 1000Base-X PHY Specific Status Register */
496e3bac3eSIvan Bornyakov #define	MV_1GBX_PHY_STAT		0xA003
506e3bac3eSIvan Bornyakov #define	MV_1GBX_PHY_STAT_AN_RESOLVED	BIT(11)
516e3bac3eSIvan Bornyakov #define	MV_1GBX_PHY_STAT_DUPLEX		BIT(13)
526e3bac3eSIvan Bornyakov #define	MV_1GBX_PHY_STAT_SPEED100	BIT(14)
536e3bac3eSIvan Bornyakov #define	MV_1GBX_PHY_STAT_SPEED1000	BIT(15)
546e3bac3eSIvan Bornyakov 
55d7029f55SIvan Bornyakov #define	AUTONEG_TIMEOUT	3
56d7029f55SIvan Bornyakov 
576e3bac3eSIvan Bornyakov struct mv2222_data {
586e3bac3eSIvan Bornyakov 	phy_interface_t line_interface;
596e3bac3eSIvan Bornyakov 	__ETHTOOL_DECLARE_LINK_MODE_MASK(supported);
6058581478SIvan Bornyakov 	bool sfp_link;
616e3bac3eSIvan Bornyakov };
626e3bac3eSIvan Bornyakov 
636e3bac3eSIvan Bornyakov /* SFI PMA transmit enable */
646e3bac3eSIvan Bornyakov static int mv2222_tx_enable(struct phy_device *phydev)
656e3bac3eSIvan Bornyakov {
666e3bac3eSIvan Bornyakov 	return phy_clear_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_TXDIS,
676e3bac3eSIvan Bornyakov 				  MDIO_PMD_TXDIS_GLOBAL);
686e3bac3eSIvan Bornyakov }
696e3bac3eSIvan Bornyakov 
706e3bac3eSIvan Bornyakov /* SFI PMA transmit disable */
716e3bac3eSIvan Bornyakov static int mv2222_tx_disable(struct phy_device *phydev)
726e3bac3eSIvan Bornyakov {
736e3bac3eSIvan Bornyakov 	return phy_set_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_TXDIS,
746e3bac3eSIvan Bornyakov 				MDIO_PMD_TXDIS_GLOBAL);
756e3bac3eSIvan Bornyakov }
766e3bac3eSIvan Bornyakov 
776e3bac3eSIvan Bornyakov static int mv2222_soft_reset(struct phy_device *phydev)
786e3bac3eSIvan Bornyakov {
796e3bac3eSIvan Bornyakov 	int val, ret;
806e3bac3eSIvan Bornyakov 
816e3bac3eSIvan Bornyakov 	ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, MV_PORT_RST,
826e3bac3eSIvan Bornyakov 			    MV_PORT_RST_SW);
836e3bac3eSIvan Bornyakov 	if (ret < 0)
846e3bac3eSIvan Bornyakov 		return ret;
856e3bac3eSIvan Bornyakov 
866e3bac3eSIvan Bornyakov 	return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND2, MV_PORT_RST,
876e3bac3eSIvan Bornyakov 					 val, !(val & MV_PORT_RST_SW),
886e3bac3eSIvan Bornyakov 					 5000, 1000000, true);
896e3bac3eSIvan Bornyakov }
906e3bac3eSIvan Bornyakov 
916e3bac3eSIvan Bornyakov static int mv2222_disable_aneg(struct phy_device *phydev)
926e3bac3eSIvan Bornyakov {
936e3bac3eSIvan Bornyakov 	int ret = phy_clear_bits_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_CTRL,
946e3bac3eSIvan Bornyakov 				     BMCR_ANENABLE | BMCR_ANRESTART);
956e3bac3eSIvan Bornyakov 	if (ret < 0)
966e3bac3eSIvan Bornyakov 		return ret;
976e3bac3eSIvan Bornyakov 
986e3bac3eSIvan Bornyakov 	return mv2222_soft_reset(phydev);
996e3bac3eSIvan Bornyakov }
1006e3bac3eSIvan Bornyakov 
1016e3bac3eSIvan Bornyakov static int mv2222_enable_aneg(struct phy_device *phydev)
1026e3bac3eSIvan Bornyakov {
1036e3bac3eSIvan Bornyakov 	int ret = phy_set_bits_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_CTRL,
1046e3bac3eSIvan Bornyakov 				   BMCR_ANENABLE | BMCR_RESET);
1056e3bac3eSIvan Bornyakov 	if (ret < 0)
1066e3bac3eSIvan Bornyakov 		return ret;
1076e3bac3eSIvan Bornyakov 
1086e3bac3eSIvan Bornyakov 	return mv2222_soft_reset(phydev);
1096e3bac3eSIvan Bornyakov }
1106e3bac3eSIvan Bornyakov 
1116e3bac3eSIvan Bornyakov static int mv2222_set_sgmii_speed(struct phy_device *phydev)
1126e3bac3eSIvan Bornyakov {
1136e3bac3eSIvan Bornyakov 	struct mv2222_data *priv = phydev->priv;
1146e3bac3eSIvan Bornyakov 
1156e3bac3eSIvan Bornyakov 	switch (phydev->speed) {
1166e3bac3eSIvan Bornyakov 	default:
1176e3bac3eSIvan Bornyakov 	case SPEED_1000:
1186e3bac3eSIvan Bornyakov 		if ((linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
1196e3bac3eSIvan Bornyakov 				       priv->supported) ||
1206e3bac3eSIvan Bornyakov 		     linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
1216e3bac3eSIvan Bornyakov 				       priv->supported)))
1226e3bac3eSIvan Bornyakov 			return phy_modify_mmd(phydev, MDIO_MMD_PCS,
1236e3bac3eSIvan Bornyakov 					      MV_1GBX_CTRL,
1246e3bac3eSIvan Bornyakov 					      BMCR_SPEED1000 | BMCR_SPEED100,
1256e3bac3eSIvan Bornyakov 					      BMCR_SPEED1000);
1266e3bac3eSIvan Bornyakov 
1276e3bac3eSIvan Bornyakov 		fallthrough;
1286e3bac3eSIvan Bornyakov 	case SPEED_100:
1296e3bac3eSIvan Bornyakov 		if ((linkmode_test_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT,
1306e3bac3eSIvan Bornyakov 				       priv->supported) ||
1316e3bac3eSIvan Bornyakov 		     linkmode_test_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT,
1326e3bac3eSIvan Bornyakov 				       priv->supported)))
1336e3bac3eSIvan Bornyakov 			return phy_modify_mmd(phydev, MDIO_MMD_PCS,
1346e3bac3eSIvan Bornyakov 					      MV_1GBX_CTRL,
1356e3bac3eSIvan Bornyakov 					      BMCR_SPEED1000 | BMCR_SPEED100,
1366e3bac3eSIvan Bornyakov 					      BMCR_SPEED100);
1376e3bac3eSIvan Bornyakov 		fallthrough;
1386e3bac3eSIvan Bornyakov 	case SPEED_10:
1396e3bac3eSIvan Bornyakov 		if ((linkmode_test_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT,
1406e3bac3eSIvan Bornyakov 				       priv->supported) ||
1416e3bac3eSIvan Bornyakov 		     linkmode_test_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT,
1426e3bac3eSIvan Bornyakov 				       priv->supported)))
1436e3bac3eSIvan Bornyakov 			return phy_modify_mmd(phydev, MDIO_MMD_PCS,
1446e3bac3eSIvan Bornyakov 					      MV_1GBX_CTRL,
1456e3bac3eSIvan Bornyakov 					      BMCR_SPEED1000 | BMCR_SPEED100,
1466e3bac3eSIvan Bornyakov 					      BMCR_SPEED10);
1476e3bac3eSIvan Bornyakov 
1486e3bac3eSIvan Bornyakov 		return -EINVAL;
1496e3bac3eSIvan Bornyakov 	}
1506e3bac3eSIvan Bornyakov }
1516e3bac3eSIvan Bornyakov 
1526e3bac3eSIvan Bornyakov static bool mv2222_is_10g_capable(struct phy_device *phydev)
1536e3bac3eSIvan Bornyakov {
1546e3bac3eSIvan Bornyakov 	struct mv2222_data *priv = phydev->priv;
1556e3bac3eSIvan Bornyakov 
1566e3bac3eSIvan Bornyakov 	return (linkmode_test_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
1576e3bac3eSIvan Bornyakov 				  priv->supported) ||
1586e3bac3eSIvan Bornyakov 		linkmode_test_bit(ETHTOOL_LINK_MODE_10000baseCR_Full_BIT,
1596e3bac3eSIvan Bornyakov 				  priv->supported) ||
1606e3bac3eSIvan Bornyakov 		linkmode_test_bit(ETHTOOL_LINK_MODE_10000baseSR_Full_BIT,
1616e3bac3eSIvan Bornyakov 				  priv->supported) ||
1626e3bac3eSIvan Bornyakov 		linkmode_test_bit(ETHTOOL_LINK_MODE_10000baseLR_Full_BIT,
1636e3bac3eSIvan Bornyakov 				  priv->supported) ||
1646e3bac3eSIvan Bornyakov 		linkmode_test_bit(ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT,
1656e3bac3eSIvan Bornyakov 				  priv->supported) ||
1666e3bac3eSIvan Bornyakov 		linkmode_test_bit(ETHTOOL_LINK_MODE_10000baseER_Full_BIT,
1676e3bac3eSIvan Bornyakov 				  priv->supported));
1686e3bac3eSIvan Bornyakov }
1696e3bac3eSIvan Bornyakov 
1706e3bac3eSIvan Bornyakov static bool mv2222_is_1gbx_capable(struct phy_device *phydev)
1716e3bac3eSIvan Bornyakov {
1726e3bac3eSIvan Bornyakov 	struct mv2222_data *priv = phydev->priv;
1736e3bac3eSIvan Bornyakov 
1746e3bac3eSIvan Bornyakov 	return linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
1756e3bac3eSIvan Bornyakov 				 priv->supported);
1766e3bac3eSIvan Bornyakov }
1776e3bac3eSIvan Bornyakov 
178d7029f55SIvan Bornyakov static bool mv2222_is_sgmii_capable(struct phy_device *phydev)
179d7029f55SIvan Bornyakov {
180d7029f55SIvan Bornyakov 	struct mv2222_data *priv = phydev->priv;
181d7029f55SIvan Bornyakov 
182d7029f55SIvan Bornyakov 	return (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
183d7029f55SIvan Bornyakov 				  priv->supported) ||
184d7029f55SIvan Bornyakov 		linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
185d7029f55SIvan Bornyakov 				  priv->supported) ||
186d7029f55SIvan Bornyakov 		linkmode_test_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT,
187d7029f55SIvan Bornyakov 				  priv->supported) ||
188d7029f55SIvan Bornyakov 		linkmode_test_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT,
189d7029f55SIvan Bornyakov 				  priv->supported) ||
190d7029f55SIvan Bornyakov 		linkmode_test_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT,
191d7029f55SIvan Bornyakov 				  priv->supported) ||
192d7029f55SIvan Bornyakov 		linkmode_test_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT,
193d7029f55SIvan Bornyakov 				  priv->supported));
194d7029f55SIvan Bornyakov }
195d7029f55SIvan Bornyakov 
1966e3bac3eSIvan Bornyakov static int mv2222_config_line(struct phy_device *phydev)
1976e3bac3eSIvan Bornyakov {
1986e3bac3eSIvan Bornyakov 	struct mv2222_data *priv = phydev->priv;
1996e3bac3eSIvan Bornyakov 
2006e3bac3eSIvan Bornyakov 	switch (priv->line_interface) {
2016e3bac3eSIvan Bornyakov 	case PHY_INTERFACE_MODE_10GBASER:
2026e3bac3eSIvan Bornyakov 		return phy_write_mmd(phydev, MDIO_MMD_VEND2, MV_PCS_CONFIG,
2036e3bac3eSIvan Bornyakov 				     MV_PCS_HOST_XAUI | MV_PCS_LINE_10GBR);
2046e3bac3eSIvan Bornyakov 	case PHY_INTERFACE_MODE_1000BASEX:
2056e3bac3eSIvan Bornyakov 		return phy_write_mmd(phydev, MDIO_MMD_VEND2, MV_PCS_CONFIG,
2066e3bac3eSIvan Bornyakov 				     MV_PCS_HOST_XAUI | MV_PCS_LINE_1GBX_AN);
2076e3bac3eSIvan Bornyakov 	case PHY_INTERFACE_MODE_SGMII:
2086e3bac3eSIvan Bornyakov 		return phy_write_mmd(phydev, MDIO_MMD_VEND2, MV_PCS_CONFIG,
2096e3bac3eSIvan Bornyakov 				     MV_PCS_HOST_XAUI | MV_PCS_LINE_SGMII_AN);
2106e3bac3eSIvan Bornyakov 	default:
2116e3bac3eSIvan Bornyakov 		return -EINVAL;
2126e3bac3eSIvan Bornyakov 	}
2136e3bac3eSIvan Bornyakov }
2146e3bac3eSIvan Bornyakov 
215d7029f55SIvan Bornyakov /* Switch between 1G (1000Base-X/SGMII) and 10G (10GBase-R) modes */
216d7029f55SIvan Bornyakov static int mv2222_swap_line_type(struct phy_device *phydev)
2176e3bac3eSIvan Bornyakov {
2186e3bac3eSIvan Bornyakov 	struct mv2222_data *priv = phydev->priv;
2196e3bac3eSIvan Bornyakov 	bool changed = false;
2206e3bac3eSIvan Bornyakov 	int ret;
2216e3bac3eSIvan Bornyakov 
2226e3bac3eSIvan Bornyakov 	switch (priv->line_interface) {
2236e3bac3eSIvan Bornyakov 	case PHY_INTERFACE_MODE_10GBASER:
224d7029f55SIvan Bornyakov 		if (mv2222_is_1gbx_capable(phydev)) {
2256e3bac3eSIvan Bornyakov 			priv->line_interface = PHY_INTERFACE_MODE_1000BASEX;
2266e3bac3eSIvan Bornyakov 			changed = true;
2276e3bac3eSIvan Bornyakov 		}
2286e3bac3eSIvan Bornyakov 
229d7029f55SIvan Bornyakov 		if (mv2222_is_sgmii_capable(phydev)) {
230d7029f55SIvan Bornyakov 			priv->line_interface = PHY_INTERFACE_MODE_SGMII;
231d7029f55SIvan Bornyakov 			changed = true;
232d7029f55SIvan Bornyakov 		}
233d7029f55SIvan Bornyakov 
2346e3bac3eSIvan Bornyakov 		break;
2356e3bac3eSIvan Bornyakov 	case PHY_INTERFACE_MODE_1000BASEX:
236d7029f55SIvan Bornyakov 	case PHY_INTERFACE_MODE_SGMII:
237d7029f55SIvan Bornyakov 		if (mv2222_is_10g_capable(phydev)) {
2386e3bac3eSIvan Bornyakov 			priv->line_interface = PHY_INTERFACE_MODE_10GBASER;
2396e3bac3eSIvan Bornyakov 			changed = true;
2406e3bac3eSIvan Bornyakov 		}
2416e3bac3eSIvan Bornyakov 
2426e3bac3eSIvan Bornyakov 		break;
2436e3bac3eSIvan Bornyakov 	default:
2446e3bac3eSIvan Bornyakov 		return -EINVAL;
2456e3bac3eSIvan Bornyakov 	}
2466e3bac3eSIvan Bornyakov 
2476e3bac3eSIvan Bornyakov 	if (changed) {
2486e3bac3eSIvan Bornyakov 		ret = mv2222_config_line(phydev);
2496e3bac3eSIvan Bornyakov 		if (ret < 0)
2506e3bac3eSIvan Bornyakov 			return ret;
2516e3bac3eSIvan Bornyakov 	}
2526e3bac3eSIvan Bornyakov 
253d7029f55SIvan Bornyakov 	return 0;
254d7029f55SIvan Bornyakov }
255d7029f55SIvan Bornyakov 
256d7029f55SIvan Bornyakov static int mv2222_setup_forced(struct phy_device *phydev)
257d7029f55SIvan Bornyakov {
258d7029f55SIvan Bornyakov 	struct mv2222_data *priv = phydev->priv;
259d7029f55SIvan Bornyakov 	int ret;
260d7029f55SIvan Bornyakov 
261d7029f55SIvan Bornyakov 	if (priv->line_interface == PHY_INTERFACE_MODE_10GBASER) {
262d7029f55SIvan Bornyakov 		if (phydev->speed < SPEED_10000 &&
263d7029f55SIvan Bornyakov 		    phydev->speed != SPEED_UNKNOWN) {
264d7029f55SIvan Bornyakov 			ret = mv2222_swap_line_type(phydev);
265d7029f55SIvan Bornyakov 			if (ret < 0)
266d7029f55SIvan Bornyakov 				return ret;
267d7029f55SIvan Bornyakov 		}
268d7029f55SIvan Bornyakov 	}
269d7029f55SIvan Bornyakov 
270d7029f55SIvan Bornyakov 	if (priv->line_interface == PHY_INTERFACE_MODE_SGMII) {
271d7029f55SIvan Bornyakov 		ret = mv2222_set_sgmii_speed(phydev);
272d7029f55SIvan Bornyakov 		if (ret < 0)
273d7029f55SIvan Bornyakov 			return ret;
274d7029f55SIvan Bornyakov 	}
275d7029f55SIvan Bornyakov 
2766e3bac3eSIvan Bornyakov 	return mv2222_disable_aneg(phydev);
2776e3bac3eSIvan Bornyakov }
2786e3bac3eSIvan Bornyakov 
2796e3bac3eSIvan Bornyakov static int mv2222_config_aneg(struct phy_device *phydev)
2806e3bac3eSIvan Bornyakov {
2816e3bac3eSIvan Bornyakov 	struct mv2222_data *priv = phydev->priv;
2826e3bac3eSIvan Bornyakov 	int ret, adv;
2836e3bac3eSIvan Bornyakov 
2846e3bac3eSIvan Bornyakov 	/* SFP is not present, do nothing */
2856e3bac3eSIvan Bornyakov 	if (priv->line_interface == PHY_INTERFACE_MODE_NA)
2866e3bac3eSIvan Bornyakov 		return 0;
2876e3bac3eSIvan Bornyakov 
2886e3bac3eSIvan Bornyakov 	if (phydev->autoneg == AUTONEG_DISABLE ||
289d7029f55SIvan Bornyakov 	    priv->line_interface == PHY_INTERFACE_MODE_10GBASER)
2906e3bac3eSIvan Bornyakov 		return mv2222_setup_forced(phydev);
2916e3bac3eSIvan Bornyakov 
2926e3bac3eSIvan Bornyakov 	adv = linkmode_adv_to_mii_adv_x(priv->supported,
2936e3bac3eSIvan Bornyakov 					ETHTOOL_LINK_MODE_1000baseX_Full_BIT);
2946e3bac3eSIvan Bornyakov 
2956e3bac3eSIvan Bornyakov 	ret = phy_modify_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_ADVERTISE,
2966e3bac3eSIvan Bornyakov 			     ADVERTISE_1000XFULL |
2976e3bac3eSIvan Bornyakov 			     ADVERTISE_1000XPAUSE | ADVERTISE_1000XPSE_ASYM,
2986e3bac3eSIvan Bornyakov 			     adv);
2996e3bac3eSIvan Bornyakov 	if (ret < 0)
3006e3bac3eSIvan Bornyakov 		return ret;
3016e3bac3eSIvan Bornyakov 
3026e3bac3eSIvan Bornyakov 	return mv2222_enable_aneg(phydev);
3036e3bac3eSIvan Bornyakov }
3046e3bac3eSIvan Bornyakov 
3056e3bac3eSIvan Bornyakov static int mv2222_aneg_done(struct phy_device *phydev)
3066e3bac3eSIvan Bornyakov {
3076e3bac3eSIvan Bornyakov 	int ret;
3086e3bac3eSIvan Bornyakov 
3096e3bac3eSIvan Bornyakov 	if (mv2222_is_10g_capable(phydev)) {
3106e3bac3eSIvan Bornyakov 		ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_STAT1);
3116e3bac3eSIvan Bornyakov 		if (ret < 0)
3126e3bac3eSIvan Bornyakov 			return ret;
3136e3bac3eSIvan Bornyakov 
3146e3bac3eSIvan Bornyakov 		if (ret & MDIO_STAT1_LSTATUS)
3156e3bac3eSIvan Bornyakov 			return 1;
3166e3bac3eSIvan Bornyakov 	}
3176e3bac3eSIvan Bornyakov 
3186e3bac3eSIvan Bornyakov 	ret = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_STAT);
3196e3bac3eSIvan Bornyakov 	if (ret < 0)
3206e3bac3eSIvan Bornyakov 		return ret;
3216e3bac3eSIvan Bornyakov 
3226e3bac3eSIvan Bornyakov 	return (ret & BMSR_ANEGCOMPLETE);
3236e3bac3eSIvan Bornyakov }
3246e3bac3eSIvan Bornyakov 
325473960a7SIvan Bornyakov /* Returns negative on error, 0 if link is down, 1 if link is up */
326473960a7SIvan Bornyakov static int mv2222_read_status_10g(struct phy_device *phydev)
327473960a7SIvan Bornyakov {
328d7029f55SIvan Bornyakov 	static int timeout;
329473960a7SIvan Bornyakov 	int val, link = 0;
330473960a7SIvan Bornyakov 
331473960a7SIvan Bornyakov 	val = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_STAT1);
332473960a7SIvan Bornyakov 	if (val < 0)
333473960a7SIvan Bornyakov 		return val;
334473960a7SIvan Bornyakov 
335473960a7SIvan Bornyakov 	if (val & MDIO_STAT1_LSTATUS) {
336473960a7SIvan Bornyakov 		link = 1;
337473960a7SIvan Bornyakov 
338473960a7SIvan Bornyakov 		/* 10GBASE-R do not support auto-negotiation */
339473960a7SIvan Bornyakov 		phydev->autoneg = AUTONEG_DISABLE;
340473960a7SIvan Bornyakov 		phydev->speed = SPEED_10000;
341473960a7SIvan Bornyakov 		phydev->duplex = DUPLEX_FULL;
342d7029f55SIvan Bornyakov 	} else {
343d7029f55SIvan Bornyakov 		if (phydev->autoneg == AUTONEG_ENABLE) {
344d7029f55SIvan Bornyakov 			timeout++;
345d7029f55SIvan Bornyakov 
346d7029f55SIvan Bornyakov 			if (timeout > AUTONEG_TIMEOUT) {
347d7029f55SIvan Bornyakov 				timeout = 0;
348d7029f55SIvan Bornyakov 
349d7029f55SIvan Bornyakov 				val = mv2222_swap_line_type(phydev);
350d7029f55SIvan Bornyakov 				if (val < 0)
351d7029f55SIvan Bornyakov 					return val;
352d7029f55SIvan Bornyakov 
353d7029f55SIvan Bornyakov 				return mv2222_config_aneg(phydev);
354d7029f55SIvan Bornyakov 			}
355d7029f55SIvan Bornyakov 		}
356473960a7SIvan Bornyakov 	}
357473960a7SIvan Bornyakov 
358473960a7SIvan Bornyakov 	return link;
359473960a7SIvan Bornyakov }
360473960a7SIvan Bornyakov 
361473960a7SIvan Bornyakov /* Returns negative on error, 0 if link is down, 1 if link is up */
362473960a7SIvan Bornyakov static int mv2222_read_status_1g(struct phy_device *phydev)
363473960a7SIvan Bornyakov {
364d7029f55SIvan Bornyakov 	static int timeout;
365473960a7SIvan Bornyakov 	int val, link = 0;
366473960a7SIvan Bornyakov 
367473960a7SIvan Bornyakov 	val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_STAT);
368473960a7SIvan Bornyakov 	if (val < 0)
369473960a7SIvan Bornyakov 		return val;
370473960a7SIvan Bornyakov 
371d7029f55SIvan Bornyakov 	if (phydev->autoneg == AUTONEG_ENABLE &&
372d7029f55SIvan Bornyakov 	    !(val & BMSR_ANEGCOMPLETE)) {
373d7029f55SIvan Bornyakov 		timeout++;
374d7029f55SIvan Bornyakov 
375d7029f55SIvan Bornyakov 		if (timeout > AUTONEG_TIMEOUT) {
376d7029f55SIvan Bornyakov 			timeout = 0;
377d7029f55SIvan Bornyakov 
378d7029f55SIvan Bornyakov 			val = mv2222_swap_line_type(phydev);
379d7029f55SIvan Bornyakov 			if (val < 0)
380d7029f55SIvan Bornyakov 				return val;
381d7029f55SIvan Bornyakov 
382d7029f55SIvan Bornyakov 			return mv2222_config_aneg(phydev);
383d7029f55SIvan Bornyakov 		}
384d7029f55SIvan Bornyakov 
385d7029f55SIvan Bornyakov 		return 0;
386d7029f55SIvan Bornyakov 	}
387d7029f55SIvan Bornyakov 
388d7029f55SIvan Bornyakov 	if (!(val & BMSR_LSTATUS))
389473960a7SIvan Bornyakov 		return 0;
390473960a7SIvan Bornyakov 
391473960a7SIvan Bornyakov 	link = 1;
392473960a7SIvan Bornyakov 
393473960a7SIvan Bornyakov 	val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_PHY_STAT);
394473960a7SIvan Bornyakov 	if (val < 0)
395473960a7SIvan Bornyakov 		return val;
396473960a7SIvan Bornyakov 
397473960a7SIvan Bornyakov 	if (val & MV_1GBX_PHY_STAT_AN_RESOLVED) {
398473960a7SIvan Bornyakov 		if (val & MV_1GBX_PHY_STAT_DUPLEX)
399473960a7SIvan Bornyakov 			phydev->duplex = DUPLEX_FULL;
400473960a7SIvan Bornyakov 		else
401473960a7SIvan Bornyakov 			phydev->duplex = DUPLEX_HALF;
402473960a7SIvan Bornyakov 
403473960a7SIvan Bornyakov 		if (val & MV_1GBX_PHY_STAT_SPEED1000)
404473960a7SIvan Bornyakov 			phydev->speed = SPEED_1000;
405473960a7SIvan Bornyakov 		else if (val & MV_1GBX_PHY_STAT_SPEED100)
406473960a7SIvan Bornyakov 			phydev->speed = SPEED_100;
407473960a7SIvan Bornyakov 		else
408473960a7SIvan Bornyakov 			phydev->speed = SPEED_10;
409473960a7SIvan Bornyakov 	}
410473960a7SIvan Bornyakov 
411473960a7SIvan Bornyakov 	return link;
412473960a7SIvan Bornyakov }
413473960a7SIvan Bornyakov 
414473960a7SIvan Bornyakov static bool mv2222_link_is_operational(struct phy_device *phydev)
415473960a7SIvan Bornyakov {
416473960a7SIvan Bornyakov 	struct mv2222_data *priv = phydev->priv;
417473960a7SIvan Bornyakov 	int val;
418473960a7SIvan Bornyakov 
419473960a7SIvan Bornyakov 	val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MV_RX_SIGNAL_DETECT);
420473960a7SIvan Bornyakov 	if (val < 0 || !(val & MV_RX_SIGNAL_DETECT_GLOBAL))
421473960a7SIvan Bornyakov 		return false;
422473960a7SIvan Bornyakov 
423473960a7SIvan Bornyakov 	if (phydev->sfp_bus && !priv->sfp_link)
424473960a7SIvan Bornyakov 		return false;
425473960a7SIvan Bornyakov 
426473960a7SIvan Bornyakov 	return true;
427473960a7SIvan Bornyakov }
428473960a7SIvan Bornyakov 
429473960a7SIvan Bornyakov static int mv2222_read_status(struct phy_device *phydev)
430473960a7SIvan Bornyakov {
431473960a7SIvan Bornyakov 	struct mv2222_data *priv = phydev->priv;
432473960a7SIvan Bornyakov 	int link;
433473960a7SIvan Bornyakov 
434473960a7SIvan Bornyakov 	phydev->link = 0;
435473960a7SIvan Bornyakov 	phydev->speed = SPEED_UNKNOWN;
436473960a7SIvan Bornyakov 	phydev->duplex = DUPLEX_UNKNOWN;
437473960a7SIvan Bornyakov 
438473960a7SIvan Bornyakov 	if (!mv2222_link_is_operational(phydev))
439473960a7SIvan Bornyakov 		return 0;
440473960a7SIvan Bornyakov 
441473960a7SIvan Bornyakov 	if (priv->line_interface == PHY_INTERFACE_MODE_10GBASER)
442473960a7SIvan Bornyakov 		link = mv2222_read_status_10g(phydev);
443473960a7SIvan Bornyakov 	else
444473960a7SIvan Bornyakov 		link = mv2222_read_status_1g(phydev);
445473960a7SIvan Bornyakov 
446473960a7SIvan Bornyakov 	if (link < 0)
447473960a7SIvan Bornyakov 		return link;
448473960a7SIvan Bornyakov 
449473960a7SIvan Bornyakov 	phydev->link = link;
450473960a7SIvan Bornyakov 
451473960a7SIvan Bornyakov 	return 0;
452473960a7SIvan Bornyakov }
453473960a7SIvan Bornyakov 
4546e3bac3eSIvan Bornyakov static int mv2222_resume(struct phy_device *phydev)
4556e3bac3eSIvan Bornyakov {
4566e3bac3eSIvan Bornyakov 	return mv2222_tx_enable(phydev);
4576e3bac3eSIvan Bornyakov }
4586e3bac3eSIvan Bornyakov 
4596e3bac3eSIvan Bornyakov static int mv2222_suspend(struct phy_device *phydev)
4606e3bac3eSIvan Bornyakov {
4616e3bac3eSIvan Bornyakov 	return mv2222_tx_disable(phydev);
4626e3bac3eSIvan Bornyakov }
4636e3bac3eSIvan Bornyakov 
4646e3bac3eSIvan Bornyakov static int mv2222_get_features(struct phy_device *phydev)
4656e3bac3eSIvan Bornyakov {
4666e3bac3eSIvan Bornyakov 	/* All supported linkmodes are set at probe */
4676e3bac3eSIvan Bornyakov 
4686e3bac3eSIvan Bornyakov 	return 0;
4696e3bac3eSIvan Bornyakov }
4706e3bac3eSIvan Bornyakov 
4716e3bac3eSIvan Bornyakov static int mv2222_config_init(struct phy_device *phydev)
4726e3bac3eSIvan Bornyakov {
4736e3bac3eSIvan Bornyakov 	if (phydev->interface != PHY_INTERFACE_MODE_XAUI)
4746e3bac3eSIvan Bornyakov 		return -EINVAL;
4756e3bac3eSIvan Bornyakov 
4766e3bac3eSIvan Bornyakov 	return 0;
4776e3bac3eSIvan Bornyakov }
4786e3bac3eSIvan Bornyakov 
4796e3bac3eSIvan Bornyakov static int mv2222_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
4806e3bac3eSIvan Bornyakov {
4816e3bac3eSIvan Bornyakov 	struct phy_device *phydev = upstream;
4826e3bac3eSIvan Bornyakov 	phy_interface_t sfp_interface;
4836e3bac3eSIvan Bornyakov 	struct mv2222_data *priv;
4846e3bac3eSIvan Bornyakov 	struct device *dev;
4856e3bac3eSIvan Bornyakov 	int ret;
4866e3bac3eSIvan Bornyakov 
4876e3bac3eSIvan Bornyakov 	__ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_supported) = { 0, };
4886e3bac3eSIvan Bornyakov 
4896e3bac3eSIvan Bornyakov 	priv = (struct mv2222_data *)phydev->priv;
4906e3bac3eSIvan Bornyakov 	dev = &phydev->mdio.dev;
4916e3bac3eSIvan Bornyakov 
4926e3bac3eSIvan Bornyakov 	sfp_parse_support(phydev->sfp_bus, id, sfp_supported);
493*9794ef5aSIvan Bornyakov 	phydev->port = sfp_parse_port(phydev->sfp_bus, id, sfp_supported);
4946e3bac3eSIvan Bornyakov 	sfp_interface = sfp_select_interface(phydev->sfp_bus, sfp_supported);
4956e3bac3eSIvan Bornyakov 
4966e3bac3eSIvan Bornyakov 	dev_info(dev, "%s SFP module inserted\n", phy_modes(sfp_interface));
4976e3bac3eSIvan Bornyakov 
4986e3bac3eSIvan Bornyakov 	if (sfp_interface != PHY_INTERFACE_MODE_10GBASER &&
4996e3bac3eSIvan Bornyakov 	    sfp_interface != PHY_INTERFACE_MODE_1000BASEX &&
5006e3bac3eSIvan Bornyakov 	    sfp_interface != PHY_INTERFACE_MODE_SGMII) {
5016e3bac3eSIvan Bornyakov 		dev_err(dev, "Incompatible SFP module inserted\n");
5026e3bac3eSIvan Bornyakov 
5036e3bac3eSIvan Bornyakov 		return -EINVAL;
5046e3bac3eSIvan Bornyakov 	}
5056e3bac3eSIvan Bornyakov 
5066e3bac3eSIvan Bornyakov 	priv->line_interface = sfp_interface;
5076e3bac3eSIvan Bornyakov 	linkmode_and(priv->supported, phydev->supported, sfp_supported);
5086e3bac3eSIvan Bornyakov 
5096e3bac3eSIvan Bornyakov 	ret = mv2222_config_line(phydev);
5106e3bac3eSIvan Bornyakov 	if (ret < 0)
5116e3bac3eSIvan Bornyakov 		return ret;
5126e3bac3eSIvan Bornyakov 
5136e3bac3eSIvan Bornyakov 	if (mutex_trylock(&phydev->lock)) {
5146e3bac3eSIvan Bornyakov 		ret = mv2222_config_aneg(phydev);
5156e3bac3eSIvan Bornyakov 		mutex_unlock(&phydev->lock);
5166e3bac3eSIvan Bornyakov 	}
5176e3bac3eSIvan Bornyakov 
5186e3bac3eSIvan Bornyakov 	return ret;
5196e3bac3eSIvan Bornyakov }
5206e3bac3eSIvan Bornyakov 
5216e3bac3eSIvan Bornyakov static void mv2222_sfp_remove(void *upstream)
5226e3bac3eSIvan Bornyakov {
5236e3bac3eSIvan Bornyakov 	struct phy_device *phydev = upstream;
5246e3bac3eSIvan Bornyakov 	struct mv2222_data *priv;
5256e3bac3eSIvan Bornyakov 
5266e3bac3eSIvan Bornyakov 	priv = (struct mv2222_data *)phydev->priv;
5276e3bac3eSIvan Bornyakov 
5286e3bac3eSIvan Bornyakov 	priv->line_interface = PHY_INTERFACE_MODE_NA;
5296e3bac3eSIvan Bornyakov 	linkmode_zero(priv->supported);
530*9794ef5aSIvan Bornyakov 	phydev->port = PORT_NONE;
5316e3bac3eSIvan Bornyakov }
5326e3bac3eSIvan Bornyakov 
53358581478SIvan Bornyakov static void mv2222_sfp_link_up(void *upstream)
53458581478SIvan Bornyakov {
53558581478SIvan Bornyakov 	struct phy_device *phydev = upstream;
53658581478SIvan Bornyakov 	struct mv2222_data *priv;
53758581478SIvan Bornyakov 
53858581478SIvan Bornyakov 	priv = phydev->priv;
53958581478SIvan Bornyakov 	priv->sfp_link = true;
54058581478SIvan Bornyakov }
54158581478SIvan Bornyakov 
54258581478SIvan Bornyakov static void mv2222_sfp_link_down(void *upstream)
54358581478SIvan Bornyakov {
54458581478SIvan Bornyakov 	struct phy_device *phydev = upstream;
54558581478SIvan Bornyakov 	struct mv2222_data *priv;
54658581478SIvan Bornyakov 
54758581478SIvan Bornyakov 	priv = phydev->priv;
54858581478SIvan Bornyakov 	priv->sfp_link = false;
54958581478SIvan Bornyakov }
55058581478SIvan Bornyakov 
5516e3bac3eSIvan Bornyakov static const struct sfp_upstream_ops sfp_phy_ops = {
5526e3bac3eSIvan Bornyakov 	.module_insert = mv2222_sfp_insert,
5536e3bac3eSIvan Bornyakov 	.module_remove = mv2222_sfp_remove,
55458581478SIvan Bornyakov 	.link_up = mv2222_sfp_link_up,
55558581478SIvan Bornyakov 	.link_down = mv2222_sfp_link_down,
5566e3bac3eSIvan Bornyakov 	.attach = phy_sfp_attach,
5576e3bac3eSIvan Bornyakov 	.detach = phy_sfp_detach,
5586e3bac3eSIvan Bornyakov };
5596e3bac3eSIvan Bornyakov 
5606e3bac3eSIvan Bornyakov static int mv2222_probe(struct phy_device *phydev)
5616e3bac3eSIvan Bornyakov {
5626e3bac3eSIvan Bornyakov 	struct device *dev = &phydev->mdio.dev;
5636e3bac3eSIvan Bornyakov 	struct mv2222_data *priv = NULL;
5646e3bac3eSIvan Bornyakov 
5656e3bac3eSIvan Bornyakov 	__ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = { 0, };
5666e3bac3eSIvan Bornyakov 
5676e3bac3eSIvan Bornyakov 	linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, supported);
5686e3bac3eSIvan Bornyakov 	linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT, supported);
5696e3bac3eSIvan Bornyakov 	linkmode_set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, supported);
5706e3bac3eSIvan Bornyakov 	linkmode_set_bit(ETHTOOL_LINK_MODE_FIBRE_BIT, supported);
5716e3bac3eSIvan Bornyakov 	linkmode_set_bit(ETHTOOL_LINK_MODE_TP_BIT, supported);
5726e3bac3eSIvan Bornyakov 	linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, supported);
5736e3bac3eSIvan Bornyakov 	linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, supported);
5746e3bac3eSIvan Bornyakov 	linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, supported);
5756e3bac3eSIvan Bornyakov 	linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, supported);
5766e3bac3eSIvan Bornyakov 	linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, supported);
5776e3bac3eSIvan Bornyakov 	linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, supported);
5786e3bac3eSIvan Bornyakov 	linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, supported);
5796e3bac3eSIvan Bornyakov 	linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseT_Full_BIT, supported);
5806e3bac3eSIvan Bornyakov 	linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseCR_Full_BIT, supported);
5816e3bac3eSIvan Bornyakov 	linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseSR_Full_BIT, supported);
5826e3bac3eSIvan Bornyakov 	linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseLR_Full_BIT, supported);
5836e3bac3eSIvan Bornyakov 	linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT, supported);
5846e3bac3eSIvan Bornyakov 	linkmode_set_bit(ETHTOOL_LINK_MODE_10000baseER_Full_BIT, supported);
5856e3bac3eSIvan Bornyakov 
5866e3bac3eSIvan Bornyakov 	linkmode_copy(phydev->supported, supported);
5876e3bac3eSIvan Bornyakov 
5886e3bac3eSIvan Bornyakov 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
5896e3bac3eSIvan Bornyakov 	if (!priv)
5906e3bac3eSIvan Bornyakov 		return -ENOMEM;
5916e3bac3eSIvan Bornyakov 
5926e3bac3eSIvan Bornyakov 	priv->line_interface = PHY_INTERFACE_MODE_NA;
5936e3bac3eSIvan Bornyakov 	phydev->priv = priv;
5946e3bac3eSIvan Bornyakov 
5956e3bac3eSIvan Bornyakov 	return phy_sfp_probe(phydev, &sfp_phy_ops);
5966e3bac3eSIvan Bornyakov }
5976e3bac3eSIvan Bornyakov 
5986e3bac3eSIvan Bornyakov static struct phy_driver mv2222_drivers[] = {
5996e3bac3eSIvan Bornyakov 	{
6006e3bac3eSIvan Bornyakov 		.phy_id = MARVELL_PHY_ID_88X2222,
6016e3bac3eSIvan Bornyakov 		.phy_id_mask = MARVELL_PHY_ID_MASK,
6026e3bac3eSIvan Bornyakov 		.name = "Marvell 88X2222",
6036e3bac3eSIvan Bornyakov 		.get_features = mv2222_get_features,
6046e3bac3eSIvan Bornyakov 		.soft_reset = mv2222_soft_reset,
6056e3bac3eSIvan Bornyakov 		.config_init = mv2222_config_init,
6066e3bac3eSIvan Bornyakov 		.config_aneg = mv2222_config_aneg,
6076e3bac3eSIvan Bornyakov 		.aneg_done = mv2222_aneg_done,
6086e3bac3eSIvan Bornyakov 		.probe = mv2222_probe,
6096e3bac3eSIvan Bornyakov 		.suspend = mv2222_suspend,
6106e3bac3eSIvan Bornyakov 		.resume = mv2222_resume,
6116e3bac3eSIvan Bornyakov 		.read_status = mv2222_read_status,
6126e3bac3eSIvan Bornyakov 	},
6136e3bac3eSIvan Bornyakov };
6146e3bac3eSIvan Bornyakov module_phy_driver(mv2222_drivers);
6156e3bac3eSIvan Bornyakov 
6166e3bac3eSIvan Bornyakov static struct mdio_device_id __maybe_unused mv2222_tbl[] = {
6176e3bac3eSIvan Bornyakov 	{ MARVELL_PHY_ID_88X2222, MARVELL_PHY_ID_MASK },
6186e3bac3eSIvan Bornyakov 	{ }
6196e3bac3eSIvan Bornyakov };
6206e3bac3eSIvan Bornyakov MODULE_DEVICE_TABLE(mdio, mv2222_tbl);
6216e3bac3eSIvan Bornyakov 
6226e3bac3eSIvan Bornyakov MODULE_DESCRIPTION("Marvell 88x2222 ethernet transceiver driver");
6236e3bac3eSIvan Bornyakov MODULE_LICENSE("GPL");
624