linux/drivers/net/phy/bcm-phy-lib.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Copyright (C) 2015-2017 Broadcom
   4 */
   5
   6#include "bcm-phy-lib.h"
   7#include <linux/bitfield.h>
   8#include <linux/brcmphy.h>
   9#include <linux/export.h>
  10#include <linux/mdio.h>
  11#include <linux/module.h>
  12#include <linux/phy.h>
  13#include <linux/ethtool.h>
  14#include <linux/ethtool_netlink.h>
  15
  16#define MII_BCM_CHANNEL_WIDTH     0x2000
  17#define BCM_CL45VEN_EEE_ADV       0x3c
  18
  19int __bcm_phy_write_exp(struct phy_device *phydev, u16 reg, u16 val)
  20{
  21        int rc;
  22
  23        rc = __phy_write(phydev, MII_BCM54XX_EXP_SEL, reg);
  24        if (rc < 0)
  25                return rc;
  26
  27        return __phy_write(phydev, MII_BCM54XX_EXP_DATA, val);
  28}
  29EXPORT_SYMBOL_GPL(__bcm_phy_write_exp);
  30
  31int bcm_phy_write_exp(struct phy_device *phydev, u16 reg, u16 val)
  32{
  33        int rc;
  34
  35        phy_lock_mdio_bus(phydev);
  36        rc = __bcm_phy_write_exp(phydev, reg, val);
  37        phy_unlock_mdio_bus(phydev);
  38
  39        return rc;
  40}
  41EXPORT_SYMBOL_GPL(bcm_phy_write_exp);
  42
  43int __bcm_phy_read_exp(struct phy_device *phydev, u16 reg)
  44{
  45        int val;
  46
  47        val = __phy_write(phydev, MII_BCM54XX_EXP_SEL, reg);
  48        if (val < 0)
  49                return val;
  50
  51        val = __phy_read(phydev, MII_BCM54XX_EXP_DATA);
  52
  53        /* Restore default value.  It's O.K. if this write fails. */
  54        __phy_write(phydev, MII_BCM54XX_EXP_SEL, 0);
  55
  56        return val;
  57}
  58EXPORT_SYMBOL_GPL(__bcm_phy_read_exp);
  59
  60int bcm_phy_read_exp(struct phy_device *phydev, u16 reg)
  61{
  62        int rc;
  63
  64        phy_lock_mdio_bus(phydev);
  65        rc = __bcm_phy_read_exp(phydev, reg);
  66        phy_unlock_mdio_bus(phydev);
  67
  68        return rc;
  69}
  70EXPORT_SYMBOL_GPL(bcm_phy_read_exp);
  71
  72int __bcm_phy_modify_exp(struct phy_device *phydev, u16 reg, u16 mask, u16 set)
  73{
  74        int new, ret;
  75
  76        ret = __phy_write(phydev, MII_BCM54XX_EXP_SEL, reg);
  77        if (ret < 0)
  78                return ret;
  79
  80        ret = __phy_read(phydev, MII_BCM54XX_EXP_DATA);
  81        if (ret < 0)
  82                return ret;
  83
  84        new = (ret & ~mask) | set;
  85        if (new == ret)
  86                return 0;
  87
  88        return __phy_write(phydev, MII_BCM54XX_EXP_DATA, new);
  89}
  90EXPORT_SYMBOL_GPL(__bcm_phy_modify_exp);
  91
  92int bcm_phy_modify_exp(struct phy_device *phydev, u16 reg, u16 mask, u16 set)
  93{
  94        int ret;
  95
  96        phy_lock_mdio_bus(phydev);
  97        ret = __bcm_phy_modify_exp(phydev, reg, mask, set);
  98        phy_unlock_mdio_bus(phydev);
  99
 100        return ret;
 101}
 102EXPORT_SYMBOL_GPL(bcm_phy_modify_exp);
 103
 104int bcm54xx_auxctl_read(struct phy_device *phydev, u16 regnum)
 105{
 106        /* The register must be written to both the Shadow Register Select and
 107         * the Shadow Read Register Selector
 108         */
 109        phy_write(phydev, MII_BCM54XX_AUX_CTL, MII_BCM54XX_AUXCTL_SHDWSEL_MASK |
 110                  regnum << MII_BCM54XX_AUXCTL_SHDWSEL_READ_SHIFT);
 111        return phy_read(phydev, MII_BCM54XX_AUX_CTL);
 112}
 113EXPORT_SYMBOL_GPL(bcm54xx_auxctl_read);
 114
 115int bcm54xx_auxctl_write(struct phy_device *phydev, u16 regnum, u16 val)
 116{
 117        return phy_write(phydev, MII_BCM54XX_AUX_CTL, regnum | val);
 118}
 119EXPORT_SYMBOL(bcm54xx_auxctl_write);
 120
 121int bcm_phy_write_misc(struct phy_device *phydev,
 122                       u16 reg, u16 chl, u16 val)
 123{
 124        int rc;
 125        int tmp;
 126
 127        rc = phy_write(phydev, MII_BCM54XX_AUX_CTL,
 128                       MII_BCM54XX_AUXCTL_SHDWSEL_MISC);
 129        if (rc < 0)
 130                return rc;
 131
 132        tmp = phy_read(phydev, MII_BCM54XX_AUX_CTL);
 133        tmp |= MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA;
 134        rc = phy_write(phydev, MII_BCM54XX_AUX_CTL, tmp);
 135        if (rc < 0)
 136                return rc;
 137
 138        tmp = (chl * MII_BCM_CHANNEL_WIDTH) | reg;
 139        rc = bcm_phy_write_exp(phydev, tmp, val);
 140
 141        return rc;
 142}
 143EXPORT_SYMBOL_GPL(bcm_phy_write_misc);
 144
 145int bcm_phy_read_misc(struct phy_device *phydev,
 146                      u16 reg, u16 chl)
 147{
 148        int rc;
 149        int tmp;
 150
 151        rc = phy_write(phydev, MII_BCM54XX_AUX_CTL,
 152                       MII_BCM54XX_AUXCTL_SHDWSEL_MISC);
 153        if (rc < 0)
 154                return rc;
 155
 156        tmp = phy_read(phydev, MII_BCM54XX_AUX_CTL);
 157        tmp |= MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA;
 158        rc = phy_write(phydev, MII_BCM54XX_AUX_CTL, tmp);
 159        if (rc < 0)
 160                return rc;
 161
 162        tmp = (chl * MII_BCM_CHANNEL_WIDTH) | reg;
 163        rc = bcm_phy_read_exp(phydev, tmp);
 164
 165        return rc;
 166}
 167EXPORT_SYMBOL_GPL(bcm_phy_read_misc);
 168
 169int bcm_phy_ack_intr(struct phy_device *phydev)
 170{
 171        int reg;
 172
 173        /* Clear pending interrupts.  */
 174        reg = phy_read(phydev, MII_BCM54XX_ISR);
 175        if (reg < 0)
 176                return reg;
 177
 178        return 0;
 179}
 180EXPORT_SYMBOL_GPL(bcm_phy_ack_intr);
 181
 182int bcm_phy_config_intr(struct phy_device *phydev)
 183{
 184        int reg;
 185
 186        reg = phy_read(phydev, MII_BCM54XX_ECR);
 187        if (reg < 0)
 188                return reg;
 189
 190        if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
 191                reg &= ~MII_BCM54XX_ECR_IM;
 192        else
 193                reg |= MII_BCM54XX_ECR_IM;
 194
 195        return phy_write(phydev, MII_BCM54XX_ECR, reg);
 196}
 197EXPORT_SYMBOL_GPL(bcm_phy_config_intr);
 198
 199int bcm_phy_read_shadow(struct phy_device *phydev, u16 shadow)
 200{
 201        phy_write(phydev, MII_BCM54XX_SHD, MII_BCM54XX_SHD_VAL(shadow));
 202        return MII_BCM54XX_SHD_DATA(phy_read(phydev, MII_BCM54XX_SHD));
 203}
 204EXPORT_SYMBOL_GPL(bcm_phy_read_shadow);
 205
 206int bcm_phy_write_shadow(struct phy_device *phydev, u16 shadow,
 207                         u16 val)
 208{
 209        return phy_write(phydev, MII_BCM54XX_SHD,
 210                         MII_BCM54XX_SHD_WRITE |
 211                         MII_BCM54XX_SHD_VAL(shadow) |
 212                         MII_BCM54XX_SHD_DATA(val));
 213}
 214EXPORT_SYMBOL_GPL(bcm_phy_write_shadow);
 215
 216int __bcm_phy_read_rdb(struct phy_device *phydev, u16 rdb)
 217{
 218        int val;
 219
 220        val = __phy_write(phydev, MII_BCM54XX_RDB_ADDR, rdb);
 221        if (val < 0)
 222                return val;
 223
 224        return __phy_read(phydev, MII_BCM54XX_RDB_DATA);
 225}
 226EXPORT_SYMBOL_GPL(__bcm_phy_read_rdb);
 227
 228int bcm_phy_read_rdb(struct phy_device *phydev, u16 rdb)
 229{
 230        int ret;
 231
 232        phy_lock_mdio_bus(phydev);
 233        ret = __bcm_phy_read_rdb(phydev, rdb);
 234        phy_unlock_mdio_bus(phydev);
 235
 236        return ret;
 237}
 238EXPORT_SYMBOL_GPL(bcm_phy_read_rdb);
 239
 240int __bcm_phy_write_rdb(struct phy_device *phydev, u16 rdb, u16 val)
 241{
 242        int ret;
 243
 244        ret = __phy_write(phydev, MII_BCM54XX_RDB_ADDR, rdb);
 245        if (ret < 0)
 246                return ret;
 247
 248        return __phy_write(phydev, MII_BCM54XX_RDB_DATA, val);
 249}
 250EXPORT_SYMBOL_GPL(__bcm_phy_write_rdb);
 251
 252int bcm_phy_write_rdb(struct phy_device *phydev, u16 rdb, u16 val)
 253{
 254        int ret;
 255
 256        phy_lock_mdio_bus(phydev);
 257        ret = __bcm_phy_write_rdb(phydev, rdb, val);
 258        phy_unlock_mdio_bus(phydev);
 259
 260        return ret;
 261}
 262EXPORT_SYMBOL_GPL(bcm_phy_write_rdb);
 263
 264int __bcm_phy_modify_rdb(struct phy_device *phydev, u16 rdb, u16 mask, u16 set)
 265{
 266        int new, ret;
 267
 268        ret = __phy_write(phydev, MII_BCM54XX_RDB_ADDR, rdb);
 269        if (ret < 0)
 270                return ret;
 271
 272        ret = __phy_read(phydev, MII_BCM54XX_RDB_DATA);
 273        if (ret < 0)
 274                return ret;
 275
 276        new = (ret & ~mask) | set;
 277        if (new == ret)
 278                return 0;
 279
 280        return __phy_write(phydev, MII_BCM54XX_RDB_DATA, new);
 281}
 282EXPORT_SYMBOL_GPL(__bcm_phy_modify_rdb);
 283
 284int bcm_phy_modify_rdb(struct phy_device *phydev, u16 rdb, u16 mask, u16 set)
 285{
 286        int ret;
 287
 288        phy_lock_mdio_bus(phydev);
 289        ret = __bcm_phy_modify_rdb(phydev, rdb, mask, set);
 290        phy_unlock_mdio_bus(phydev);
 291
 292        return ret;
 293}
 294EXPORT_SYMBOL_GPL(bcm_phy_modify_rdb);
 295
 296int bcm_phy_enable_apd(struct phy_device *phydev, bool dll_pwr_down)
 297{
 298        int val;
 299
 300        if (dll_pwr_down) {
 301                val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR3);
 302                if (val < 0)
 303                        return val;
 304
 305                val |= BCM54XX_SHD_SCR3_DLLAPD_DIS;
 306                bcm_phy_write_shadow(phydev, BCM54XX_SHD_SCR3, val);
 307        }
 308
 309        val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_APD);
 310        if (val < 0)
 311                return val;
 312
 313        /* Clear APD bits */
 314        val &= BCM_APD_CLR_MASK;
 315
 316        if (phydev->autoneg == AUTONEG_ENABLE)
 317                val |= BCM54XX_SHD_APD_EN;
 318        else
 319                val |= BCM_NO_ANEG_APD_EN;
 320
 321        /* Enable energy detect single link pulse for easy wakeup */
 322        val |= BCM_APD_SINGLELP_EN;
 323
 324        /* Enable Auto Power-Down (APD) for the PHY */
 325        return bcm_phy_write_shadow(phydev, BCM54XX_SHD_APD, val);
 326}
 327EXPORT_SYMBOL_GPL(bcm_phy_enable_apd);
 328
 329int bcm_phy_set_eee(struct phy_device *phydev, bool enable)
 330{
 331        int val;
 332
 333        /* Enable EEE at PHY level */
 334        val = phy_read_mmd(phydev, MDIO_MMD_AN, BRCM_CL45VEN_EEE_CONTROL);
 335        if (val < 0)
 336                return val;
 337
 338        if (enable)
 339                val |= LPI_FEATURE_EN | LPI_FEATURE_EN_DIG1000X;
 340        else
 341                val &= ~(LPI_FEATURE_EN | LPI_FEATURE_EN_DIG1000X);
 342
 343        phy_write_mmd(phydev, MDIO_MMD_AN, BRCM_CL45VEN_EEE_CONTROL, (u32)val);
 344
 345        /* Advertise EEE */
 346        val = phy_read_mmd(phydev, MDIO_MMD_AN, BCM_CL45VEN_EEE_ADV);
 347        if (val < 0)
 348                return val;
 349
 350        if (enable)
 351                val |= (MDIO_EEE_100TX | MDIO_EEE_1000T);
 352        else
 353                val &= ~(MDIO_EEE_100TX | MDIO_EEE_1000T);
 354
 355        phy_write_mmd(phydev, MDIO_MMD_AN, BCM_CL45VEN_EEE_ADV, (u32)val);
 356
 357        return 0;
 358}
 359EXPORT_SYMBOL_GPL(bcm_phy_set_eee);
 360
 361int bcm_phy_downshift_get(struct phy_device *phydev, u8 *count)
 362{
 363        int val;
 364
 365        val = bcm54xx_auxctl_read(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC);
 366        if (val < 0)
 367                return val;
 368
 369        /* Check if wirespeed is enabled or not */
 370        if (!(val & MII_BCM54XX_AUXCTL_SHDWSEL_MISC_WIRESPEED_EN)) {
 371                *count = DOWNSHIFT_DEV_DISABLE;
 372                return 0;
 373        }
 374
 375        val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR2);
 376        if (val < 0)
 377                return val;
 378
 379        /* Downgrade after one link attempt */
 380        if (val & BCM54XX_SHD_SCR2_WSPD_RTRY_DIS) {
 381                *count = 1;
 382        } else {
 383                /* Downgrade after configured retry count */
 384                val >>= BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT;
 385                val &= BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_MASK;
 386                *count = val + BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_OFFSET;
 387        }
 388
 389        return 0;
 390}
 391EXPORT_SYMBOL_GPL(bcm_phy_downshift_get);
 392
 393int bcm_phy_downshift_set(struct phy_device *phydev, u8 count)
 394{
 395        int val = 0, ret = 0;
 396
 397        /* Range check the number given */
 398        if (count - BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_OFFSET >
 399            BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_MASK &&
 400            count != DOWNSHIFT_DEV_DEFAULT_COUNT) {
 401                return -ERANGE;
 402        }
 403
 404        val = bcm54xx_auxctl_read(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC);
 405        if (val < 0)
 406                return val;
 407
 408        /* Se the write enable bit */
 409        val |= MII_BCM54XX_AUXCTL_MISC_WREN;
 410
 411        if (count == DOWNSHIFT_DEV_DISABLE) {
 412                val &= ~MII_BCM54XX_AUXCTL_SHDWSEL_MISC_WIRESPEED_EN;
 413                return bcm54xx_auxctl_write(phydev,
 414                                            MII_BCM54XX_AUXCTL_SHDWSEL_MISC,
 415                                            val);
 416        } else {
 417                val |= MII_BCM54XX_AUXCTL_SHDWSEL_MISC_WIRESPEED_EN;
 418                ret = bcm54xx_auxctl_write(phydev,
 419                                           MII_BCM54XX_AUXCTL_SHDWSEL_MISC,
 420                                           val);
 421                if (ret < 0)
 422                        return ret;
 423        }
 424
 425        val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR2);
 426        val &= ~(BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_MASK <<
 427                 BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT |
 428                 BCM54XX_SHD_SCR2_WSPD_RTRY_DIS);
 429
 430        switch (count) {
 431        case 1:
 432                val |= BCM54XX_SHD_SCR2_WSPD_RTRY_DIS;
 433                break;
 434        case DOWNSHIFT_DEV_DEFAULT_COUNT:
 435                val |= 1 << BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT;
 436                break;
 437        default:
 438                val |= (count - BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_OFFSET) <<
 439                        BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT;
 440                break;
 441        }
 442
 443        return bcm_phy_write_shadow(phydev, BCM54XX_SHD_SCR2, val);
 444}
 445EXPORT_SYMBOL_GPL(bcm_phy_downshift_set);
 446
 447struct bcm_phy_hw_stat {
 448        const char *string;
 449        u8 reg;
 450        u8 shift;
 451        u8 bits;
 452};
 453
 454/* Counters freeze at either 0xffff or 0xff, better than nothing */
 455static const struct bcm_phy_hw_stat bcm_phy_hw_stats[] = {
 456        { "phy_receive_errors", MII_BRCM_CORE_BASE12, 0, 16 },
 457        { "phy_serdes_ber_errors", MII_BRCM_CORE_BASE13, 8, 8 },
 458        { "phy_false_carrier_sense_errors", MII_BRCM_CORE_BASE13, 0, 8 },
 459        { "phy_local_rcvr_nok", MII_BRCM_CORE_BASE14, 8, 8 },
 460        { "phy_remote_rcv_nok", MII_BRCM_CORE_BASE14, 0, 8 },
 461};
 462
 463int bcm_phy_get_sset_count(struct phy_device *phydev)
 464{
 465        return ARRAY_SIZE(bcm_phy_hw_stats);
 466}
 467EXPORT_SYMBOL_GPL(bcm_phy_get_sset_count);
 468
 469void bcm_phy_get_strings(struct phy_device *phydev, u8 *data)
 470{
 471        unsigned int i;
 472
 473        for (i = 0; i < ARRAY_SIZE(bcm_phy_hw_stats); i++)
 474                strlcpy(data + i * ETH_GSTRING_LEN,
 475                        bcm_phy_hw_stats[i].string, ETH_GSTRING_LEN);
 476}
 477EXPORT_SYMBOL_GPL(bcm_phy_get_strings);
 478
 479/* Caller is supposed to provide appropriate storage for the library code to
 480 * access the shadow copy
 481 */
 482static u64 bcm_phy_get_stat(struct phy_device *phydev, u64 *shadow,
 483                            unsigned int i)
 484{
 485        struct bcm_phy_hw_stat stat = bcm_phy_hw_stats[i];
 486        int val;
 487        u64 ret;
 488
 489        val = phy_read(phydev, stat.reg);
 490        if (val < 0) {
 491                ret = U64_MAX;
 492        } else {
 493                val >>= stat.shift;
 494                val = val & ((1 << stat.bits) - 1);
 495                shadow[i] += val;
 496                ret = shadow[i];
 497        }
 498
 499        return ret;
 500}
 501
 502void bcm_phy_get_stats(struct phy_device *phydev, u64 *shadow,
 503                       struct ethtool_stats *stats, u64 *data)
 504{
 505        unsigned int i;
 506
 507        for (i = 0; i < ARRAY_SIZE(bcm_phy_hw_stats); i++)
 508                data[i] = bcm_phy_get_stat(phydev, shadow, i);
 509}
 510EXPORT_SYMBOL_GPL(bcm_phy_get_stats);
 511
 512void bcm_phy_r_rc_cal_reset(struct phy_device *phydev)
 513{
 514        /* Reset R_CAL/RC_CAL Engine */
 515        bcm_phy_write_exp_sel(phydev, 0x00b0, 0x0010);
 516
 517        /* Disable Reset R_AL/RC_CAL Engine */
 518        bcm_phy_write_exp_sel(phydev, 0x00b0, 0x0000);
 519}
 520EXPORT_SYMBOL_GPL(bcm_phy_r_rc_cal_reset);
 521
 522int bcm_phy_28nm_a0b0_afe_config_init(struct phy_device *phydev)
 523{
 524        /* Increase VCO range to prevent unlocking problem of PLL at low
 525         * temp
 526         */
 527        bcm_phy_write_misc(phydev, PLL_PLLCTRL_1, 0x0048);
 528
 529        /* Change Ki to 011 */
 530        bcm_phy_write_misc(phydev, PLL_PLLCTRL_2, 0x021b);
 531
 532        /* Disable loading of TVCO buffer to bandgap, set bandgap trim
 533         * to 111
 534         */
 535        bcm_phy_write_misc(phydev, PLL_PLLCTRL_4, 0x0e20);
 536
 537        /* Adjust bias current trim by -3 */
 538        bcm_phy_write_misc(phydev, DSP_TAP10, 0x690b);
 539
 540        /* Switch to CORE_BASE1E */
 541        phy_write(phydev, MII_BRCM_CORE_BASE1E, 0xd);
 542
 543        bcm_phy_r_rc_cal_reset(phydev);
 544
 545        /* write AFE_RXCONFIG_0 */
 546        bcm_phy_write_misc(phydev, AFE_RXCONFIG_0, 0xeb19);
 547
 548        /* write AFE_RXCONFIG_1 */
 549        bcm_phy_write_misc(phydev, AFE_RXCONFIG_1, 0x9a3f);
 550
 551        /* write AFE_RX_LP_COUNTER */
 552        bcm_phy_write_misc(phydev, AFE_RX_LP_COUNTER, 0x7fc0);
 553
 554        /* write AFE_HPF_TRIM_OTHERS */
 555        bcm_phy_write_misc(phydev, AFE_HPF_TRIM_OTHERS, 0x000b);
 556
 557        /* write AFTE_TX_CONFIG */
 558        bcm_phy_write_misc(phydev, AFE_TX_CONFIG, 0x0800);
 559
 560        return 0;
 561}
 562EXPORT_SYMBOL_GPL(bcm_phy_28nm_a0b0_afe_config_init);
 563
 564int bcm_phy_enable_jumbo(struct phy_device *phydev)
 565{
 566        int ret;
 567
 568        ret = bcm54xx_auxctl_read(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL);
 569        if (ret < 0)
 570                return ret;
 571
 572        /* Enable extended length packet reception */
 573        ret = bcm54xx_auxctl_write(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL,
 574                                   ret | MII_BCM54XX_AUXCTL_ACTL_EXT_PKT_LEN);
 575        if (ret < 0)
 576                return ret;
 577
 578        /* Enable the elastic FIFO for raising the transmission limit from
 579         * 4.5KB to 10KB, at the expense of an additional 16 ns in propagation
 580         * latency.
 581         */
 582        return phy_set_bits(phydev, MII_BCM54XX_ECR, MII_BCM54XX_ECR_FIFOE);
 583}
 584EXPORT_SYMBOL_GPL(bcm_phy_enable_jumbo);
 585
 586static int __bcm_phy_enable_rdb_access(struct phy_device *phydev)
 587{
 588        return __bcm_phy_write_exp(phydev, BCM54XX_EXP_REG7E, 0);
 589}
 590
 591static int __bcm_phy_enable_legacy_access(struct phy_device *phydev)
 592{
 593        return __bcm_phy_write_rdb(phydev, BCM54XX_RDB_REG0087,
 594                                   BCM54XX_ACCESS_MODE_LEGACY_EN);
 595}
 596
 597static int _bcm_phy_cable_test_start(struct phy_device *phydev, bool is_rdb)
 598{
 599        u16 mask, set;
 600        int ret;
 601
 602        /* Auto-negotiation must be enabled for cable diagnostics to work, but
 603         * don't advertise any capabilities.
 604         */
 605        phy_write(phydev, MII_BMCR, BMCR_ANENABLE);
 606        phy_write(phydev, MII_ADVERTISE, ADVERTISE_CSMA);
 607        phy_write(phydev, MII_CTRL1000, 0);
 608
 609        phy_lock_mdio_bus(phydev);
 610        if (is_rdb) {
 611                ret = __bcm_phy_enable_legacy_access(phydev);
 612                if (ret)
 613                        goto out;
 614        }
 615
 616        mask = BCM54XX_ECD_CTRL_CROSS_SHORT_DIS | BCM54XX_ECD_CTRL_UNIT_MASK;
 617        set = BCM54XX_ECD_CTRL_RUN | BCM54XX_ECD_CTRL_BREAK_LINK |
 618              FIELD_PREP(BCM54XX_ECD_CTRL_UNIT_MASK,
 619                         BCM54XX_ECD_CTRL_UNIT_CM);
 620
 621        ret = __bcm_phy_modify_exp(phydev, BCM54XX_EXP_ECD_CTRL, mask, set);
 622
 623out:
 624        /* re-enable the RDB access even if there was an error */
 625        if (is_rdb)
 626                ret = __bcm_phy_enable_rdb_access(phydev) ? : ret;
 627
 628        phy_unlock_mdio_bus(phydev);
 629
 630        return ret;
 631}
 632
 633static int bcm_phy_cable_test_report_trans(int result)
 634{
 635        switch (result) {
 636        case BCM54XX_ECD_FAULT_TYPE_OK:
 637                return ETHTOOL_A_CABLE_RESULT_CODE_OK;
 638        case BCM54XX_ECD_FAULT_TYPE_OPEN:
 639                return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
 640        case BCM54XX_ECD_FAULT_TYPE_SAME_SHORT:
 641                return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
 642        case BCM54XX_ECD_FAULT_TYPE_CROSS_SHORT:
 643                return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;
 644        case BCM54XX_ECD_FAULT_TYPE_INVALID:
 645        case BCM54XX_ECD_FAULT_TYPE_BUSY:
 646        default:
 647                return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
 648        }
 649}
 650
 651static bool bcm_phy_distance_valid(int result)
 652{
 653        switch (result) {
 654        case BCM54XX_ECD_FAULT_TYPE_OPEN:
 655        case BCM54XX_ECD_FAULT_TYPE_SAME_SHORT:
 656        case BCM54XX_ECD_FAULT_TYPE_CROSS_SHORT:
 657                return true;
 658        }
 659        return false;
 660}
 661
 662static int bcm_phy_report_length(struct phy_device *phydev, int pair)
 663{
 664        int val;
 665
 666        val = __bcm_phy_read_exp(phydev,
 667                                 BCM54XX_EXP_ECD_PAIR_A_LENGTH_RESULTS + pair);
 668        if (val < 0)
 669                return val;
 670
 671        if (val == BCM54XX_ECD_LENGTH_RESULTS_INVALID)
 672                return 0;
 673
 674        ethnl_cable_test_fault_length(phydev, pair, val);
 675
 676        return 0;
 677}
 678
 679static int _bcm_phy_cable_test_get_status(struct phy_device *phydev,
 680                                          bool *finished, bool is_rdb)
 681{
 682        int pair_a, pair_b, pair_c, pair_d, ret;
 683
 684        *finished = false;
 685
 686        phy_lock_mdio_bus(phydev);
 687
 688        if (is_rdb) {
 689                ret = __bcm_phy_enable_legacy_access(phydev);
 690                if (ret)
 691                        goto out;
 692        }
 693
 694        ret = __bcm_phy_read_exp(phydev, BCM54XX_EXP_ECD_CTRL);
 695        if (ret < 0)
 696                goto out;
 697
 698        if (ret & BCM54XX_ECD_CTRL_IN_PROGRESS) {
 699                ret = 0;
 700                goto out;
 701        }
 702
 703        ret = __bcm_phy_read_exp(phydev, BCM54XX_EXP_ECD_FAULT_TYPE);
 704        if (ret < 0)
 705                goto out;
 706
 707        pair_a = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_A_MASK, ret);
 708        pair_b = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_B_MASK, ret);
 709        pair_c = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_C_MASK, ret);
 710        pair_d = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_D_MASK, ret);
 711
 712        ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A,
 713                                bcm_phy_cable_test_report_trans(pair_a));
 714        ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_B,
 715                                bcm_phy_cable_test_report_trans(pair_b));
 716        ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_C,
 717                                bcm_phy_cable_test_report_trans(pair_c));
 718        ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_D,
 719                                bcm_phy_cable_test_report_trans(pair_d));
 720
 721        if (bcm_phy_distance_valid(pair_a))
 722                bcm_phy_report_length(phydev, 0);
 723        if (bcm_phy_distance_valid(pair_b))
 724                bcm_phy_report_length(phydev, 1);
 725        if (bcm_phy_distance_valid(pair_c))
 726                bcm_phy_report_length(phydev, 2);
 727        if (bcm_phy_distance_valid(pair_d))
 728                bcm_phy_report_length(phydev, 3);
 729
 730        ret = 0;
 731        *finished = true;
 732out:
 733        /* re-enable the RDB access even if there was an error */
 734        if (is_rdb)
 735                ret = __bcm_phy_enable_rdb_access(phydev) ? : ret;
 736
 737        phy_unlock_mdio_bus(phydev);
 738
 739        return ret;
 740}
 741
 742int bcm_phy_cable_test_start(struct phy_device *phydev)
 743{
 744        return _bcm_phy_cable_test_start(phydev, false);
 745}
 746EXPORT_SYMBOL_GPL(bcm_phy_cable_test_start);
 747
 748int bcm_phy_cable_test_get_status(struct phy_device *phydev, bool *finished)
 749{
 750        return _bcm_phy_cable_test_get_status(phydev, finished, false);
 751}
 752EXPORT_SYMBOL_GPL(bcm_phy_cable_test_get_status);
 753
 754/* We assume that all PHYs which support RDB access can be switched to legacy
 755 * mode. If, in the future, this is not true anymore, we have to re-implement
 756 * this with RDB access.
 757 */
 758int bcm_phy_cable_test_start_rdb(struct phy_device *phydev)
 759{
 760        return _bcm_phy_cable_test_start(phydev, true);
 761}
 762EXPORT_SYMBOL_GPL(bcm_phy_cable_test_start_rdb);
 763
 764int bcm_phy_cable_test_get_status_rdb(struct phy_device *phydev,
 765                                      bool *finished)
 766{
 767        return _bcm_phy_cable_test_get_status(phydev, finished, true);
 768}
 769EXPORT_SYMBOL_GPL(bcm_phy_cable_test_get_status_rdb);
 770
 771MODULE_DESCRIPTION("Broadcom PHY Library");
 772MODULE_LICENSE("GPL v2");
 773MODULE_AUTHOR("Broadcom Corporation");
 774