Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 // Broadcom BCM84881 NBASE-T PHY driver, as found on a SFP+ module.
0003 // Copyright (C) 2019 Russell King, Deep Blue Solutions Ltd.
0004 //
0005 // Like the Marvell 88x3310, the Broadcom 84881 changes its host-side
0006 // interface according to the operating speed between 10GBASE-R,
0007 // 2500BASE-X and SGMII (but unlike the 88x3310, without the control
0008 // word).
0009 //
0010 // This driver only supports those aspects of the PHY that I'm able to
0011 // observe and test with the SFP+ module, which is an incomplete subset
0012 // of what this PHY is able to support. For example, I only assume it
0013 // supports a single lane Serdes connection, but it may be that the PHY
0014 // is able to support more than that.
0015 #include <linux/delay.h>
0016 #include <linux/module.h>
0017 #include <linux/phy.h>
0018 
0019 enum {
0020     MDIO_AN_C22 = 0xffe0,
0021 };
0022 
0023 static int bcm84881_wait_init(struct phy_device *phydev)
0024 {
0025     int val;
0026 
0027     return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1,
0028                      val, !(val & MDIO_CTRL1_RESET),
0029                      100000, 2000000, false);
0030 }
0031 
0032 static int bcm84881_config_init(struct phy_device *phydev)
0033 {
0034     switch (phydev->interface) {
0035     case PHY_INTERFACE_MODE_SGMII:
0036     case PHY_INTERFACE_MODE_2500BASEX:
0037     case PHY_INTERFACE_MODE_10GBASER:
0038         break;
0039     default:
0040         return -ENODEV;
0041     }
0042     return 0;
0043 }
0044 
0045 static int bcm84881_probe(struct phy_device *phydev)
0046 {
0047     /* This driver requires PMAPMD and AN blocks */
0048     const u32 mmd_mask = MDIO_DEVS_PMAPMD | MDIO_DEVS_AN;
0049 
0050     if (!phydev->is_c45 ||
0051         (phydev->c45_ids.devices_in_package & mmd_mask) != mmd_mask)
0052         return -ENODEV;
0053 
0054     return 0;
0055 }
0056 
0057 static int bcm84881_get_features(struct phy_device *phydev)
0058 {
0059     int ret;
0060 
0061     ret = genphy_c45_pma_read_abilities(phydev);
0062     if (ret)
0063         return ret;
0064 
0065     /* Although the PHY sets bit 1.11.8, it does not support 10M modes */
0066     linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT,
0067                phydev->supported);
0068     linkmode_clear_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT,
0069                phydev->supported);
0070 
0071     return 0;
0072 }
0073 
0074 static int bcm84881_config_aneg(struct phy_device *phydev)
0075 {
0076     bool changed = false;
0077     u32 adv;
0078     int ret;
0079 
0080     /* Wait for the PHY to finish initialising, otherwise our
0081      * advertisement may be overwritten.
0082      */
0083     ret = bcm84881_wait_init(phydev);
0084     if (ret)
0085         return ret;
0086 
0087     /* We don't support manual MDI control */
0088     phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
0089 
0090     /* disabled autoneg doesn't seem to work with this PHY */
0091     if (phydev->autoneg == AUTONEG_DISABLE)
0092         return -EINVAL;
0093 
0094     ret = genphy_c45_an_config_aneg(phydev);
0095     if (ret < 0)
0096         return ret;
0097     if (ret > 0)
0098         changed = true;
0099 
0100     adv = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising);
0101     ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN,
0102                      MDIO_AN_C22 + MII_CTRL1000,
0103                      ADVERTISE_1000FULL | ADVERTISE_1000HALF,
0104                      adv);
0105     if (ret < 0)
0106         return ret;
0107     if (ret > 0)
0108         changed = true;
0109 
0110     return genphy_c45_check_and_restart_aneg(phydev, changed);
0111 }
0112 
0113 static int bcm84881_aneg_done(struct phy_device *phydev)
0114 {
0115     int bmsr, val;
0116 
0117     val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
0118     if (val < 0)
0119         return val;
0120 
0121     bmsr = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_C22 + MII_BMSR);
0122     if (bmsr < 0)
0123         return val;
0124 
0125     return !!(val & MDIO_AN_STAT1_COMPLETE) &&
0126            !!(bmsr & BMSR_ANEGCOMPLETE);
0127 }
0128 
0129 static int bcm84881_read_status(struct phy_device *phydev)
0130 {
0131     unsigned int mode;
0132     int bmsr, val;
0133 
0134     val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_CTRL1);
0135     if (val < 0)
0136         return val;
0137 
0138     if (val & MDIO_AN_CTRL1_RESTART) {
0139         phydev->link = 0;
0140         return 0;
0141     }
0142 
0143     val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
0144     if (val < 0)
0145         return val;
0146 
0147     bmsr = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_C22 + MII_BMSR);
0148     if (bmsr < 0)
0149         return val;
0150 
0151     phydev->autoneg_complete = !!(val & MDIO_AN_STAT1_COMPLETE) &&
0152                    !!(bmsr & BMSR_ANEGCOMPLETE);
0153     phydev->link = !!(val & MDIO_STAT1_LSTATUS) &&
0154                !!(bmsr & BMSR_LSTATUS);
0155     if (phydev->autoneg == AUTONEG_ENABLE && !phydev->autoneg_complete)
0156         phydev->link = false;
0157 
0158     linkmode_zero(phydev->lp_advertising);
0159     phydev->speed = SPEED_UNKNOWN;
0160     phydev->duplex = DUPLEX_UNKNOWN;
0161     phydev->pause = 0;
0162     phydev->asym_pause = 0;
0163     phydev->mdix = 0;
0164 
0165     if (!phydev->link)
0166         return 0;
0167 
0168     if (phydev->autoneg_complete) {
0169         val = genphy_c45_read_lpa(phydev);
0170         if (val < 0)
0171             return val;
0172 
0173         val = phy_read_mmd(phydev, MDIO_MMD_AN,
0174                    MDIO_AN_C22 + MII_STAT1000);
0175         if (val < 0)
0176             return val;
0177 
0178         mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, val);
0179 
0180         if (phydev->autoneg == AUTONEG_ENABLE)
0181             phy_resolve_aneg_linkmode(phydev);
0182     }
0183 
0184     if (phydev->autoneg == AUTONEG_DISABLE) {
0185         /* disabled autoneg doesn't seem to work, so force the link
0186          * down.
0187          */
0188         phydev->link = 0;
0189         return 0;
0190     }
0191 
0192     /* Set the host link mode - we set the phy interface mode and
0193      * the speed according to this register so that downshift works.
0194      * We leave the duplex setting as per the resolution from the
0195      * above.
0196      */
0197     val = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x4011);
0198     mode = (val & 0x1e) >> 1;
0199     if (mode == 1 || mode == 2)
0200         phydev->interface = PHY_INTERFACE_MODE_SGMII;
0201     else if (mode == 3)
0202         phydev->interface = PHY_INTERFACE_MODE_10GBASER;
0203     else if (mode == 4)
0204         phydev->interface = PHY_INTERFACE_MODE_2500BASEX;
0205     switch (mode & 7) {
0206     case 1:
0207         phydev->speed = SPEED_100;
0208         break;
0209     case 2:
0210         phydev->speed = SPEED_1000;
0211         break;
0212     case 3:
0213         phydev->speed = SPEED_10000;
0214         break;
0215     case 4:
0216         phydev->speed = SPEED_2500;
0217         break;
0218     case 5:
0219         phydev->speed = SPEED_5000;
0220         break;
0221     }
0222 
0223     return genphy_c45_read_mdix(phydev);
0224 }
0225 
0226 static struct phy_driver bcm84881_drivers[] = {
0227     {
0228         .phy_id     = 0xae025150,
0229         .phy_id_mask    = 0xfffffff0,
0230         .name       = "Broadcom BCM84881",
0231         .config_init    = bcm84881_config_init,
0232         .probe      = bcm84881_probe,
0233         .get_features   = bcm84881_get_features,
0234         .config_aneg    = bcm84881_config_aneg,
0235         .aneg_done  = bcm84881_aneg_done,
0236         .read_status    = bcm84881_read_status,
0237     },
0238 };
0239 
0240 module_phy_driver(bcm84881_drivers);
0241 
0242 /* FIXME: module auto-loading for Clause 45 PHYs seems non-functional */
0243 static struct mdio_device_id __maybe_unused bcm84881_tbl[] = {
0244     { 0xae025150, 0xfffffff0 },
0245     { },
0246 };
0247 MODULE_AUTHOR("Russell King");
0248 MODULE_DESCRIPTION("Broadcom BCM84881 PHY driver");
0249 MODULE_DEVICE_TABLE(mdio, bcm84881_tbl);
0250 MODULE_LICENSE("GPL");