1781 lines · 47.3 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0+/* *	drivers/net/phy/broadcom.c * *	Broadcom BCM5411, BCM5421 and BCM5461 Gigabit Ethernet *	transceivers. * *	Broadcom BCM54810, BCM54811 BroadR-Reach transceivers. * *	Copyright (c) 2006  Maciej W. Rozycki * *	Inspired by code written by Amy Fong. */ #include "bcm-phy-lib.h"#include <linux/delay.h>#include <linux/module.h>#include <linux/phy.h>#include <linux/device.h>#include <linux/brcmphy.h>#include <linux/of.h>#include <linux/interrupt.h>#include <linux/irq.h>#include <linux/gpio/consumer.h> #define BRCM_PHY_REV(phydev) \	((phydev)->drv->phy_id & ~((phydev)->drv->phy_id_mask)) MODULE_DESCRIPTION("Broadcom PHY driver");MODULE_AUTHOR("Maciej W. Rozycki");MODULE_LICENSE("GPL"); struct bcm54xx_phy_priv {	u64	*stats;	struct bcm_ptp_private *ptp;	int	wake_irq;	bool	wake_irq_enabled;	bool	brr_mode;}; /* Link modes for BCM58411 PHY */static const int bcm54811_linkmodes[] = {	ETHTOOL_LINK_MODE_100baseT1_Full_BIT,	ETHTOOL_LINK_MODE_10baseT1BRR_Full_BIT,	ETHTOOL_LINK_MODE_1000baseT_Full_BIT,	ETHTOOL_LINK_MODE_1000baseX_Full_BIT,	ETHTOOL_LINK_MODE_1000baseT_Half_BIT,	ETHTOOL_LINK_MODE_100baseT_Full_BIT,	ETHTOOL_LINK_MODE_100baseT_Half_BIT,	ETHTOOL_LINK_MODE_10baseT_Full_BIT,	ETHTOOL_LINK_MODE_10baseT_Half_BIT}; /* Long-Distance Signaling (BroadR-Reach mode aneg) relevant linkmode bits */static const int lds_br_bits[] = {	ETHTOOL_LINK_MODE_Autoneg_BIT,	ETHTOOL_LINK_MODE_Pause_BIT,	ETHTOOL_LINK_MODE_Asym_Pause_BIT,	ETHTOOL_LINK_MODE_10baseT1BRR_Full_BIT,	ETHTOOL_LINK_MODE_100baseT1_Full_BIT}; static bool bcm54xx_phy_can_wakeup(struct phy_device *phydev){	struct bcm54xx_phy_priv *priv = phydev->priv; 	return phy_interrupt_is_valid(phydev) || priv->wake_irq >= 0;} static int bcm54xx_config_clock_delay(struct phy_device *phydev){	int rc, val; 	/* handling PHY's internal RX clock delay */	val = bcm54xx_auxctl_read(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC);	val |= MII_BCM54XX_AUXCTL_MISC_WREN;	if (phydev->interface == PHY_INTERFACE_MODE_RGMII ||	    phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) {		/* Disable RGMII RXC-RXD skew */		val &= ~MII_BCM54XX_AUXCTL_SHDWSEL_MISC_RGMII_SKEW_EN;	}	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||	    phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) {		/* Enable RGMII RXC-RXD skew */		val |= MII_BCM54XX_AUXCTL_SHDWSEL_MISC_RGMII_SKEW_EN;	}	rc = bcm54xx_auxctl_write(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC,				  val);	if (rc < 0)		return rc; 	/* handling PHY's internal TX clock delay */	val = bcm_phy_read_shadow(phydev, BCM54810_SHD_CLK_CTL);	if (phydev->interface == PHY_INTERFACE_MODE_RGMII ||	    phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) {		/* Disable internal TX clock delay */		val &= ~BCM54810_SHD_CLK_CTL_GTXCLK_EN;	}	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID ||	    phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) {		/* Enable internal TX clock delay */		val |= BCM54810_SHD_CLK_CTL_GTXCLK_EN;	}	rc = bcm_phy_write_shadow(phydev, BCM54810_SHD_CLK_CTL, val);	if (rc < 0)		return rc; 	return 0;} static int bcm54210e_config_init(struct phy_device *phydev){	int val; 	bcm54xx_config_clock_delay(phydev); 	if (phydev->dev_flags & PHY_BRCM_EN_MASTER_MODE) {		val = phy_read(phydev, MII_CTRL1000);		val |= CTL1000_AS_MASTER | CTL1000_ENABLE_MASTER;		phy_write(phydev, MII_CTRL1000, val);	} 	return 0;} static int bcm54612e_config_init(struct phy_device *phydev){	int reg; 	bcm54xx_config_clock_delay(phydev); 	/* Enable CLK125 MUX on LED4 if ref clock is enabled. */	if (!(phydev->dev_flags & PHY_BRCM_RX_REFCLK_UNUSED)) {		int err; 		reg = bcm_phy_read_exp(phydev, BCM54612E_EXP_SPARE0);		err = bcm_phy_write_exp(phydev, BCM54612E_EXP_SPARE0,					BCM54612E_LED4_CLK125OUT_EN | reg); 		if (err < 0)			return err;	} 	return 0;} static int bcm54616s_config_init(struct phy_device *phydev){	int rc, val; 	if (phydev->interface != PHY_INTERFACE_MODE_SGMII &&	    phydev->interface != PHY_INTERFACE_MODE_1000BASEX)		return 0; 	/* Ensure proper interface mode is selected. */	/* Disable RGMII mode */	val = bcm54xx_auxctl_read(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC);	if (val < 0)		return val;	val &= ~MII_BCM54XX_AUXCTL_SHDWSEL_MISC_RGMII_EN;	val |= MII_BCM54XX_AUXCTL_MISC_WREN;	rc = bcm54xx_auxctl_write(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC,				  val);	if (rc < 0)		return rc; 	/* Select 1000BASE-X register set (primary SerDes) */	val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_MODE);	if (val < 0)		return val;	val |= BCM54XX_SHD_MODE_1000BX;	rc = bcm_phy_write_shadow(phydev, BCM54XX_SHD_MODE, val);	if (rc < 0)		return rc; 	/* Power down SerDes interface */	rc = phy_set_bits(phydev, MII_BMCR, BMCR_PDOWN);	if (rc < 0)		return rc; 	/* Select proper interface mode */	val &= ~BCM54XX_SHD_INTF_SEL_MASK;	val |= phydev->interface == PHY_INTERFACE_MODE_SGMII ?		BCM54XX_SHD_INTF_SEL_SGMII :		BCM54XX_SHD_INTF_SEL_GBIC;	rc = bcm_phy_write_shadow(phydev, BCM54XX_SHD_MODE, val);	if (rc < 0)		return rc; 	/* Power up SerDes interface */	rc = phy_clear_bits(phydev, MII_BMCR, BMCR_PDOWN);	if (rc < 0)		return rc; 	/* Select copper register set */	val &= ~BCM54XX_SHD_MODE_1000BX;	rc = bcm_phy_write_shadow(phydev, BCM54XX_SHD_MODE, val);	if (rc < 0)		return rc; 	/* Power up copper interface */	return phy_clear_bits(phydev, MII_BMCR, BMCR_PDOWN);} /* Needs SMDSP clock enabled via bcm54xx_phydsp_config() */static int bcm50610_a0_workaround(struct phy_device *phydev){	int err; 	err = bcm_phy_write_exp(phydev, MII_BCM54XX_EXP_AADJ1CH0,				MII_BCM54XX_EXP_AADJ1CH0_SWP_ABCD_OEN |				MII_BCM54XX_EXP_AADJ1CH0_SWSEL_THPF);	if (err < 0)		return err; 	err = bcm_phy_write_exp(phydev, MII_BCM54XX_EXP_AADJ1CH3,				MII_BCM54XX_EXP_AADJ1CH3_ADCCKADJ);	if (err < 0)		return err; 	err = bcm_phy_write_exp(phydev, MII_BCM54XX_EXP_EXP75,				MII_BCM54XX_EXP_EXP75_VDACCTRL);	if (err < 0)		return err; 	err = bcm_phy_write_exp(phydev, MII_BCM54XX_EXP_EXP96,				MII_BCM54XX_EXP_EXP96_MYST);	if (err < 0)		return err; 	err = bcm_phy_write_exp(phydev, MII_BCM54XX_EXP_EXP97,				MII_BCM54XX_EXP_EXP97_MYST); 	return err;} static int bcm54xx_phydsp_config(struct phy_device *phydev){	int err, err2; 	/* Enable the SMDSP clock */	err = bcm54xx_auxctl_write(phydev,				   MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL,				   MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA |				   MII_BCM54XX_AUXCTL_ACTL_TX_6DB);	if (err < 0)		return err; 	if (phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM50610) ||	    phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM50610M)) {		/* Clear bit 9 to fix a phy interop issue. */		err = bcm_phy_write_exp(phydev, MII_BCM54XX_EXP_EXP08,					MII_BCM54XX_EXP_EXP08_RJCT_2MHZ);		if (err < 0)			goto error; 		if (phydev->drv->phy_id == PHY_ID_BCM50610) {			err = bcm50610_a0_workaround(phydev);			if (err < 0)				goto error;		}	} 	if (phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM57780)) {		int val; 		val = bcm_phy_read_exp(phydev, MII_BCM54XX_EXP_EXP75);		if (val < 0)			goto error; 		val |= MII_BCM54XX_EXP_EXP75_CM_OSC;		err = bcm_phy_write_exp(phydev, MII_BCM54XX_EXP_EXP75, val);	} error:	/* Disable the SMDSP clock */	err2 = bcm54xx_auxctl_write(phydev,				    MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL,				    MII_BCM54XX_AUXCTL_ACTL_TX_6DB); 	/* Return the first error reported. */	return err ? err : err2;} static void bcm54xx_adjust_rxrefclk(struct phy_device *phydev){	u32 orig;	int val;	bool clk125en = true; 	/* Abort if we are using an untested phy. */	if (!(phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM57780) ||	      phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM50610) ||	      phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM50610M) ||	      phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM54210E) ||	      phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM54810) ||	      phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM54811)))		return; 	val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR3);	if (val < 0)		return; 	orig = val; 	if ((phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM50610) ||	     phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM50610M)) &&	    BRCM_PHY_REV(phydev) >= 0x3) {		/*		 * Here, bit 0 _disables_ CLK125 when set.		 * This bit is set by default.		 */		clk125en = false;	} else {		if (phydev->dev_flags & PHY_BRCM_RX_REFCLK_UNUSED) {			if (!phy_id_compare_model(phydev->drv->phy_id,						  PHY_ID_BCM54811)) {				/* Here, bit 0 _enables_ CLK125 when set */				val &= ~BCM54XX_SHD_SCR3_DEF_CLK125;			}			clk125en = false;		}	} 	if (!clk125en || (phydev->dev_flags & PHY_BRCM_AUTO_PWRDWN_ENABLE))		val &= ~BCM54XX_SHD_SCR3_DLLAPD_DIS;	else		val |= BCM54XX_SHD_SCR3_DLLAPD_DIS; 	if (phydev->dev_flags & PHY_BRCM_DIS_TXCRXC_NOENRGY) {		if (phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM54210E) ||		    phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM54810) ||		    phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM54811))			val |= BCM54XX_SHD_SCR3_RXCTXC_DIS;		else			val |= BCM54XX_SHD_SCR3_TRDDAPD;	} 	if (orig != val)		bcm_phy_write_shadow(phydev, BCM54XX_SHD_SCR3, val); 	val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_APD);	if (val < 0)		return; 	orig = val; 	if (!clk125en || (phydev->dev_flags & PHY_BRCM_AUTO_PWRDWN_ENABLE))		val |= BCM54XX_SHD_APD_EN;	else		val &= ~BCM54XX_SHD_APD_EN; 	if (orig != val)		bcm_phy_write_shadow(phydev, BCM54XX_SHD_APD, val);} static void bcm54xx_ptp_stop(struct phy_device *phydev){	struct bcm54xx_phy_priv *priv = phydev->priv; 	if (priv->ptp)		bcm_ptp_stop(priv->ptp);} static void bcm54xx_ptp_config_init(struct phy_device *phydev){	struct bcm54xx_phy_priv *priv = phydev->priv; 	if (priv->ptp)		bcm_ptp_config_init(phydev);} static int bcm5481x_set_brrmode(struct phy_device *phydev, bool on){	int reg;	int err;	u16 val; 	reg = bcm_phy_read_exp(phydev, BCM54810_EXP_BROADREACH_LRE_MISC_CTL); 	if (reg < 0)		return reg; 	if (on)		reg |= BCM54810_EXP_BROADREACH_LRE_MISC_CTL_EN;	else		reg &= ~BCM54810_EXP_BROADREACH_LRE_MISC_CTL_EN; 	err = bcm_phy_write_exp(phydev,				BCM54810_EXP_BROADREACH_LRE_MISC_CTL, reg);	if (err)		return err; 	/* Ensure LRE or IEEE register set is accessed according to the brr	 *  on/off, thus set the override	 */	val = BCM54811_EXP_BROADREACH_LRE_OVERLAY_CTL_EN;	if (!on)		val |= BCM54811_EXP_BROADREACH_LRE_OVERLAY_CTL_OVERRIDE_VAL; 	return bcm_phy_write_exp(phydev,				 BCM54811_EXP_BROADREACH_LRE_OVERLAY_CTL, val);} static int bcm54811_config_init(struct phy_device *phydev){	struct bcm54xx_phy_priv *priv = phydev->priv;	int err, reg, exp_sync_ethernet, aux_rgmii_en; 	/* Enable CLK125 MUX on LED4 if ref clock is enabled. */	if (!(phydev->dev_flags & PHY_BRCM_RX_REFCLK_UNUSED)) {		reg = bcm_phy_read_exp(phydev, BCM54612E_EXP_SPARE0);		if (reg < 0)			return reg;		err = bcm_phy_write_exp(phydev, BCM54612E_EXP_SPARE0,					BCM54612E_LED4_CLK125OUT_EN | reg);		if (err < 0)			return err;	} 	/* With BCM54811, BroadR-Reach implies no autoneg */	if (priv->brr_mode)		phydev->autoneg = 0; 	/* Enable MII Lite (No TXER, RXER, CRS, COL) if configured */	if (phydev->interface == PHY_INTERFACE_MODE_MIILITE)		exp_sync_ethernet = BCM_EXP_SYNC_ETHERNET_MII_LITE;	else		exp_sync_ethernet = 0; 	err = bcm_phy_modify_exp(phydev, BCM_EXP_SYNC_ETHERNET,				 BCM_EXP_SYNC_ETHERNET_MII_LITE,				 exp_sync_ethernet);	if (err < 0)		return err; 	/* Enable RGMII if configured */	if (phy_interface_is_rgmii(phydev))		aux_rgmii_en = MII_BCM54XX_AUXCTL_SHDWSEL_MISC_RGMII_EN |			       MII_BCM54XX_AUXCTL_SHDWSEL_MISC_RGMII_SKEW_EN;	else		aux_rgmii_en = 0; 	/* Also writing Reserved bits 6:5 because the documentation requires	 * them to be written to 0b11	 */	err = bcm54xx_auxctl_write(phydev,				   MII_BCM54XX_AUXCTL_SHDWSEL_MISC,				   MII_BCM54XX_AUXCTL_MISC_WREN |				   aux_rgmii_en |				   MII_BCM54XX_AUXCTL_SHDWSEL_MISC_RSVD);	if (err < 0)		return err; 	return bcm5481x_set_brrmode(phydev, priv->brr_mode);} static int bcm54xx_config_init(struct phy_device *phydev){	int reg, err, val; 	reg = phy_read(phydev, MII_BCM54XX_ECR);	if (reg < 0)		return reg; 	/* Mask interrupts globally.  */	reg |= MII_BCM54XX_ECR_IM;	err = phy_write(phydev, MII_BCM54XX_ECR, reg);	if (err < 0)		return err; 	/* Unmask events we are interested in.  */	reg = ~(MII_BCM54XX_INT_DUPLEX |		MII_BCM54XX_INT_SPEED |		MII_BCM54XX_INT_LINK);	err = phy_write(phydev, MII_BCM54XX_IMR, reg);	if (err < 0)		return err; 	if ((phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM50610) ||	     phy_id_compare_model(phydev->drv->phy_id, PHY_ID_BCM50610M)) &&	    (phydev->dev_flags & PHY_BRCM_CLEAR_RGMII_MODE))		bcm_phy_write_shadow(phydev, BCM54XX_SHD_RGMII_MODE, 0); 	bcm54xx_adjust_rxrefclk(phydev); 	switch (phydev->drv->phy_id & PHY_ID_MATCH_MODEL_MASK) {	case PHY_ID_BCM50610:	case PHY_ID_BCM50610M:		err = bcm54xx_config_clock_delay(phydev);		break;	case PHY_ID_BCM54210E:		err = bcm54210e_config_init(phydev);		break;	case PHY_ID_BCM54612E:		err = bcm54612e_config_init(phydev);		break;	case PHY_ID_BCM54616S:		err = bcm54616s_config_init(phydev);		break;

Lines 1–500 of 1781.