// 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. Load more
// 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;