960 lines · 24.8 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0+/* *	drivers/net/phy/broadcom.c * *	Broadcom BCM5411, BCM5421 and BCM5461 Gigabit Ethernet *	transceivers. * *	Copyright (c) 2006  Maciej W. Rozycki * *	Inspired by code written by Amy Fong. */ #include "bcm-phy-lib.h"#include <linux/module.h>#include <linux/phy.h>#include <linux/brcmphy.h>#include <linux/of.h> #define BRCM_PHY_MODEL(phydev) \	((phydev)->drv->phy_id & (phydev)->drv->phy_id_mask) #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"); 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 (BRCM_PHY_MODEL(phydev) == PHY_ID_BCM50610 ||	    BRCM_PHY_MODEL(phydev) == 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 (BRCM_PHY_MODEL(phydev) == 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 (BRCM_PHY_MODEL(phydev) != PHY_ID_BCM57780 &&	    BRCM_PHY_MODEL(phydev) != PHY_ID_BCM50610 &&	    BRCM_PHY_MODEL(phydev) != PHY_ID_BCM50610M &&	    BRCM_PHY_MODEL(phydev) != PHY_ID_BCM54210E &&	    BRCM_PHY_MODEL(phydev) != PHY_ID_BCM54810 &&	    BRCM_PHY_MODEL(phydev) != PHY_ID_BCM54811)		return; 	val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR3);	if (val < 0)		return; 	orig = val; 	if ((BRCM_PHY_MODEL(phydev) == PHY_ID_BCM50610 ||	     BRCM_PHY_MODEL(phydev) == 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 (BRCM_PHY_MODEL(phydev) != 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 (BRCM_PHY_MODEL(phydev) == PHY_ID_BCM54210E ||		    BRCM_PHY_MODEL(phydev) == PHY_ID_BCM54810 ||		    BRCM_PHY_MODEL(phydev) == PHY_ID_BCM54210E)			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 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 ((BRCM_PHY_MODEL(phydev) == PHY_ID_BCM50610 ||	     BRCM_PHY_MODEL(phydev) == 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 (BRCM_PHY_MODEL(phydev)) {	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;	case PHY_ID_BCM54810:		/* For BCM54810, we need to disable BroadR-Reach function */		val = bcm_phy_read_exp(phydev,				       BCM54810_EXP_BROADREACH_LRE_MISC_CTL);		val &= ~BCM54810_EXP_BROADREACH_LRE_MISC_CTL_EN;		err = bcm_phy_write_exp(phydev,					BCM54810_EXP_BROADREACH_LRE_MISC_CTL,					val);		break;	}	if (err)		return err; 	bcm54xx_phydsp_config(phydev); 	/* For non-SFP setups, encode link speed into LED1 and LED3 pair	 * (green/amber).	 * Also flash these two LEDs on activity. This means configuring	 * them for MULTICOLOR and encoding link/activity into them.	 * Don't do this for devices on an SFP module, since some of these	 * use the LED outputs to control the SFP LOS signal, and changing	 * these settings will cause LOS to malfunction.	 */	if (!phy_on_sfp(phydev)) {		val = BCM5482_SHD_LEDS1_LED1(BCM_LED_SRC_MULTICOLOR1) |			BCM5482_SHD_LEDS1_LED3(BCM_LED_SRC_MULTICOLOR1);		bcm_phy_write_shadow(phydev, BCM5482_SHD_LEDS1, val); 		val = BCM_LED_MULTICOLOR_IN_PHASE |			BCM5482_SHD_LEDS1_LED1(BCM_LED_MULTICOLOR_LINK_ACT) |			BCM5482_SHD_LEDS1_LED3(BCM_LED_MULTICOLOR_LINK_ACT);		bcm_phy_write_exp(phydev, BCM_EXP_MULTICOLOR, val);	} 	return 0;} static int bcm54xx_resume(struct phy_device *phydev){	int ret; 	/* Writes to register other than BMCR would be ignored	 * unless we clear the PDOWN bit first	 */	ret = genphy_resume(phydev);	if (ret < 0)		return ret; 	/* Upon exiting power down, the PHY remains in an internal reset state	 * for 40us	 */	fsleep(40); 	return bcm54xx_config_init(phydev);} static int bcm54811_config_init(struct phy_device *phydev){	int err, reg; 	/* Disable BroadR-Reach function. */	reg = bcm_phy_read_exp(phydev, BCM54810_EXP_BROADREACH_LRE_MISC_CTL);	reg &= ~BCM54810_EXP_BROADREACH_LRE_MISC_CTL_EN;	err = bcm_phy_write_exp(phydev, BCM54810_EXP_BROADREACH_LRE_MISC_CTL,				reg);	if (err < 0)		return err; 	err = bcm54xx_config_init(phydev); 	/* 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);		err = bcm_phy_write_exp(phydev, BCM54612E_EXP_SPARE0,					BCM54612E_LED4_CLK125OUT_EN | reg);		if (err < 0)			return err;	} 	return err;} static int bcm5481_config_aneg(struct phy_device *phydev){	struct device_node *np = phydev->mdio.dev.of_node;	int ret; 	/* Aneg firstly. */	ret = genphy_config_aneg(phydev); 	/* Then we can set up the delay. */	bcm54xx_config_clock_delay(phydev); 	if (of_property_read_bool(np, "enet-phy-lane-swap")) {		/* Lane Swap - Undocumented register...magic! */		ret = bcm_phy_write_exp(phydev, MII_BCM54XX_EXP_SEL_ER + 0x9,					0x11B);		if (ret < 0)			return ret;	} 	return ret;} struct bcm54616s_phy_priv {	bool mode_1000bx_en;}; static int bcm54616s_probe(struct phy_device *phydev){	struct bcm54616s_phy_priv *priv;	int val; 	priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);	if (!priv)		return -ENOMEM; 	phydev->priv = priv; 	val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_MODE);	if (val < 0)		return val; 	/* The PHY is strapped in RGMII-fiber mode when INTERF_SEL[1:0]	 * is 01b, and the link between PHY and its link partner can be	 * either 1000Base-X or 100Base-FX.	 * RGMII-1000Base-X is properly supported, but RGMII-100Base-FX	 * support is still missing as of now.	 */	if ((val & BCM54XX_SHD_INTF_SEL_MASK) == BCM54XX_SHD_INTF_SEL_RGMII) {		val = bcm_phy_read_shadow(phydev, BCM54616S_SHD_100FX_CTRL);		if (val < 0)			return val; 		/* Bit 0 of the SerDes 100-FX Control register, when set		 * to 1, sets the MII/RGMII -> 100BASE-FX configuration.		 * When this bit is set to 0, it sets the GMII/RGMII ->		 * 1000BASE-X configuration.		 */		if (!(val & BCM54616S_100FX_MODE))			priv->mode_1000bx_en = true; 		phydev->port = PORT_FIBRE;

Lines 1–500 of 960.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t