726 lines · 19.1 KB · C
RawDownload
/* *	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. * *	This program is free software; you can redistribute it and/or *	modify it under the terms of the GNU General Public License *	as published by the Free Software Foundation; either version *	2 of the License, or (at your option) any later version. */ #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 bcm54210e_config_init(struct phy_device *phydev){	int val; 	val = bcm54xx_auxctl_read(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC);	val &= ~MII_BCM54XX_AUXCTL_SHDWSEL_MISC_RGMII_SKEW_EN;	val |= MII_BCM54XX_AUXCTL_MISC_WREN;	bcm54xx_auxctl_write(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC, val); 	val = bcm_phy_read_shadow(phydev, BCM54810_SHD_CLK_CTL);	val &= ~BCM54810_SHD_CLK_CTL_GTXCLK_EN;	bcm_phy_write_shadow(phydev, BCM54810_SHD_CLK_CTL, val); 	return 0;} static int bcm54612e_config_init(struct phy_device *phydev){	/* Clear TX internal delay unless requested. */	if ((phydev->interface != PHY_INTERFACE_MODE_RGMII_ID) &&	    (phydev->interface != PHY_INTERFACE_MODE_RGMII_TXID)) {		/* Disable TXD to GTXCLK clock delay (default set) */		/* Bit 9 is the only field in shadow register 00011 */		bcm_phy_write_shadow(phydev, 0x03, 0);	} 	/* Clear RX internal delay unless requested. */	if ((phydev->interface != PHY_INTERFACE_MODE_RGMII_ID) &&	    (phydev->interface != PHY_INTERFACE_MODE_RGMII_RXID)) {		u16 reg; 		reg = bcm54xx_auxctl_read(phydev,					  MII_BCM54XX_AUXCTL_SHDWSEL_MISC);		/* Disable RXD to RXC delay (default set) */		reg &= ~MII_BCM54XX_AUXCTL_SHDWSEL_MISC_RGMII_SKEW_EN;		/* Clear shadow selector field */		reg &= ~MII_BCM54XX_AUXCTL_SHDWSEL_MASK;		bcm54xx_auxctl_write(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC,				     MII_BCM54XX_AUXCTL_MISC_WREN | reg);	} 	return 0;} static int bcm5481x_config(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;} /* 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)		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) {			/* 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)		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); 	if ((phydev->dev_flags & PHY_BRCM_RX_REFCLK_UNUSED) ||	    (phydev->dev_flags & PHY_BRCM_DIS_TXCRXC_NOENRGY) ||	    (phydev->dev_flags & PHY_BRCM_AUTO_PWRDWN_ENABLE))		bcm54xx_adjust_rxrefclk(phydev); 	if (BRCM_PHY_MODEL(phydev) == PHY_ID_BCM54210E) {		err = bcm54210e_config_init(phydev);		if (err)			return err;	} else if (BRCM_PHY_MODEL(phydev) == PHY_ID_BCM54612E) {		err = bcm54612e_config_init(phydev);		if (err)			return err;	} else if (BRCM_PHY_MODEL(phydev) == 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);		if (err < 0)			return err;	} 	bcm54xx_phydsp_config(phydev); 	return 0;} static int bcm5482_config_init(struct phy_device *phydev){	int err, reg; 	err = bcm54xx_config_init(phydev); 	if (phydev->dev_flags & PHY_BCM_FLAGS_MODE_1000BX) {		/*		 * Enable secondary SerDes and its use as an LED source		 */		reg = bcm_phy_read_shadow(phydev, BCM5482_SHD_SSD);		bcm_phy_write_shadow(phydev, BCM5482_SHD_SSD,				     reg |				     BCM5482_SHD_SSD_LEDM |				     BCM5482_SHD_SSD_EN); 		/*		 * Enable SGMII slave mode and auto-detection		 */		reg = BCM5482_SSD_SGMII_SLAVE | MII_BCM54XX_EXP_SEL_SSD;		err = bcm_phy_read_exp(phydev, reg);		if (err < 0)			return err;		err = bcm_phy_write_exp(phydev, reg, err |					BCM5482_SSD_SGMII_SLAVE_EN |					BCM5482_SSD_SGMII_SLAVE_AD);		if (err < 0)			return err; 		/*		 * Disable secondary SerDes powerdown		 */		reg = BCM5482_SSD_1000BX_CTL | MII_BCM54XX_EXP_SEL_SSD;		err = bcm_phy_read_exp(phydev, reg);		if (err < 0)			return err;		err = bcm_phy_write_exp(phydev, reg,					err & ~BCM5482_SSD_1000BX_CTL_PWRDOWN);		if (err < 0)			return err; 		/*		 * Select 1000BASE-X register set (primary SerDes)		 */		reg = bcm_phy_read_shadow(phydev, BCM5482_SHD_MODE);		bcm_phy_write_shadow(phydev, BCM5482_SHD_MODE,				     reg | BCM5482_SHD_MODE_1000BX); 		/*		 * LED1=ACTIVITYLED, LED3=LINKSPD[2]		 * (Use LED1 as secondary SerDes ACTIVITY LED)		 */		bcm_phy_write_shadow(phydev, BCM5482_SHD_LEDS1,			BCM5482_SHD_LEDS1_LED1(BCM_LED_SRC_ACTIVITYLED) |			BCM5482_SHD_LEDS1_LED3(BCM_LED_SRC_LINKSPD2)); 		/*		 * Auto-negotiation doesn't seem to work quite right		 * in this mode, so we disable it and force it to the		 * right speed/duplex setting.  Only 'link status'		 * is important.		 */		phydev->autoneg = AUTONEG_DISABLE;		phydev->speed = SPEED_1000;		phydev->duplex = DUPLEX_FULL;	} 	return err;} static int bcm5482_read_status(struct phy_device *phydev){	int err; 	err = genphy_read_status(phydev); 	if (phydev->dev_flags & PHY_BCM_FLAGS_MODE_1000BX) {		/*		 * Only link status matters for 1000Base-X mode, so force		 * 1000 Mbit/s full-duplex status		 */		if (phydev->link) {			phydev->speed = SPEED_1000;			phydev->duplex = DUPLEX_FULL;		}	} 	return err;} static int bcm5481_config_aneg(struct phy_device *phydev){	struct device_node *np = phydev->mdio.dev.of_node;	int ret; 	/* Aneg firsly. */	ret = genphy_config_aneg(phydev); 	/* Then we can set up the delay. */	bcm5481x_config(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;} static int brcm_phy_setbits(struct phy_device *phydev, int reg, int set){	int val; 	val = phy_read(phydev, reg);	if (val < 0)		return val; 	return phy_write(phydev, reg, val | set);} static int brcm_fet_config_init(struct phy_device *phydev){	int reg, err, err2, brcmtest; 	/* Reset the PHY to bring it to a known state. */	err = phy_write(phydev, MII_BMCR, BMCR_RESET);	if (err < 0)		return err; 	reg = phy_read(phydev, MII_BRCM_FET_INTREG);	if (reg < 0)		return reg; 	/* Unmask events we are interested in and mask interrupts globally. */	reg = MII_BRCM_FET_IR_DUPLEX_EN |	      MII_BRCM_FET_IR_SPEED_EN |	      MII_BRCM_FET_IR_LINK_EN |	      MII_BRCM_FET_IR_ENABLE |	      MII_BRCM_FET_IR_MASK; 	err = phy_write(phydev, MII_BRCM_FET_INTREG, reg);	if (err < 0)		return err; 	/* Enable shadow register access */	brcmtest = phy_read(phydev, MII_BRCM_FET_BRCMTEST);	if (brcmtest < 0)		return brcmtest; 	reg = brcmtest | MII_BRCM_FET_BT_SRE; 	err = phy_write(phydev, MII_BRCM_FET_BRCMTEST, reg);	if (err < 0)		return err; 	/* Set the LED mode */	reg = phy_read(phydev, MII_BRCM_FET_SHDW_AUXMODE4);	if (reg < 0) {		err = reg;		goto done;	} 	reg &= ~MII_BRCM_FET_SHDW_AM4_LED_MASK;	reg |= MII_BRCM_FET_SHDW_AM4_LED_MODE1; 	err = phy_write(phydev, MII_BRCM_FET_SHDW_AUXMODE4, reg);	if (err < 0)		goto done; 	/* Enable auto MDIX */	err = brcm_phy_setbits(phydev, MII_BRCM_FET_SHDW_MISCCTRL,				       MII_BRCM_FET_SHDW_MC_FAME);	if (err < 0)		goto done; 	if (phydev->dev_flags & PHY_BRCM_AUTO_PWRDWN_ENABLE) {		/* Enable auto power down */		err = brcm_phy_setbits(phydev, MII_BRCM_FET_SHDW_AUXSTAT2,					       MII_BRCM_FET_SHDW_AS2_APDE);	} done:	/* Disable shadow register access */

Lines 1–500 of 726.