// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2015-2017 Broadcom */ #include "bcm-phy-lib.h" #include #include #include #include #include #include #include #include #include #include #define MII_BCM_CHANNEL_WIDTH 0x2000 #define BCM_CL45VEN_EEE_ADV 0x3c int __bcm_phy_write_exp(struct phy_device *phydev, u16 reg, u16 val) { int rc; rc = __phy_write(phydev, MII_BCM54XX_EXP_SEL, reg); if (rc < 0) return rc; return __phy_write(phydev, MII_BCM54XX_EXP_DATA, val); } EXPORT_SYMBOL_GPL(__bcm_phy_write_exp); int bcm_phy_write_exp(struct phy_device *phydev, u16 reg, u16 val) { int rc; phy_lock_mdio_bus(phydev); rc = __bcm_phy_write_exp(phydev, reg, val); phy_unlock_mdio_bus(phydev); return rc; } EXPORT_SYMBOL_GPL(bcm_phy_write_exp); int __bcm_phy_read_exp(struct phy_device *phydev, u16 reg) { int val; val = __phy_write(phydev, MII_BCM54XX_EXP_SEL, reg); if (val < 0) return val; val = __phy_read(phydev, MII_BCM54XX_EXP_DATA); /* Restore default value. It's O.K. if this write fails. */ __phy_write(phydev, MII_BCM54XX_EXP_SEL, 0); return val; } EXPORT_SYMBOL_GPL(__bcm_phy_read_exp); int bcm_phy_read_exp(struct phy_device *phydev, u16 reg) { int rc; phy_lock_mdio_bus(phydev); rc = __bcm_phy_read_exp(phydev, reg); phy_unlock_mdio_bus(phydev); return rc; } EXPORT_SYMBOL_GPL(bcm_phy_read_exp); int __bcm_phy_modify_exp(struct phy_device *phydev, u16 reg, u16 mask, u16 set) { int new, ret; ret = __phy_write(phydev, MII_BCM54XX_EXP_SEL, reg); if (ret < 0) return ret; ret = __phy_read(phydev, MII_BCM54XX_EXP_DATA); if (ret < 0) return ret; new = (ret & ~mask) | set; if (new == ret) return 0; return __phy_write(phydev, MII_BCM54XX_EXP_DATA, new); } EXPORT_SYMBOL_GPL(__bcm_phy_modify_exp); int bcm_phy_modify_exp(struct phy_device *phydev, u16 reg, u16 mask, u16 set) { int ret; phy_lock_mdio_bus(phydev); ret = __bcm_phy_modify_exp(phydev, reg, mask, set); phy_unlock_mdio_bus(phydev); return ret; } EXPORT_SYMBOL_GPL(bcm_phy_modify_exp); int bcm54xx_auxctl_read(struct phy_device *phydev, u16 regnum) { /* The register must be written to both the Shadow Register Select and * the Shadow Read Register Selector */ phy_write(phydev, MII_BCM54XX_AUX_CTL, MII_BCM54XX_AUXCTL_SHDWSEL_MASK | regnum << MII_BCM54XX_AUXCTL_SHDWSEL_READ_SHIFT); return phy_read(phydev, MII_BCM54XX_AUX_CTL); } EXPORT_SYMBOL_GPL(bcm54xx_auxctl_read); int bcm54xx_auxctl_write(struct phy_device *phydev, u16 regnum, u16 val) { return phy_write(phydev, MII_BCM54XX_AUX_CTL, regnum | val); } EXPORT_SYMBOL(bcm54xx_auxctl_write); int bcm_phy_write_misc(struct phy_device *phydev, u16 reg, u16 chl, u16 val) { int rc; int tmp; rc = phy_write(phydev, MII_BCM54XX_AUX_CTL, MII_BCM54XX_AUXCTL_SHDWSEL_MISC); if (rc < 0) return rc; tmp = phy_read(phydev, MII_BCM54XX_AUX_CTL); tmp |= MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA; rc = phy_write(phydev, MII_BCM54XX_AUX_CTL, tmp); if (rc < 0) return rc; tmp = (chl * MII_BCM_CHANNEL_WIDTH) | reg; rc = bcm_phy_write_exp(phydev, tmp, val); return rc; } EXPORT_SYMBOL_GPL(bcm_phy_write_misc); int bcm_phy_read_misc(struct phy_device *phydev, u16 reg, u16 chl) { int rc; int tmp; rc = phy_write(phydev, MII_BCM54XX_AUX_CTL, MII_BCM54XX_AUXCTL_SHDWSEL_MISC); if (rc < 0) return rc; tmp = phy_read(phydev, MII_BCM54XX_AUX_CTL); tmp |= MII_BCM54XX_AUXCTL_ACTL_SMDSP_ENA; rc = phy_write(phydev, MII_BCM54XX_AUX_CTL, tmp); if (rc < 0) return rc; tmp = (chl * MII_BCM_CHANNEL_WIDTH) | reg; rc = bcm_phy_read_exp(phydev, tmp); return rc; } EXPORT_SYMBOL_GPL(bcm_phy_read_misc); int bcm_phy_ack_intr(struct phy_device *phydev) { int reg; /* Clear pending interrupts. */ reg = phy_read(phydev, MII_BCM54XX_ISR); if (reg < 0) return reg; return 0; } EXPORT_SYMBOL_GPL(bcm_phy_ack_intr); int bcm_phy_config_intr(struct phy_device *phydev) { int reg, err; reg = phy_read(phydev, MII_BCM54XX_ECR); if (reg < 0) return reg; if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { err = bcm_phy_ack_intr(phydev); if (err) return err; reg &= ~MII_BCM54XX_ECR_IM; err = phy_write(phydev, MII_BCM54XX_ECR, reg); } else { reg |= MII_BCM54XX_ECR_IM; err = phy_write(phydev, MII_BCM54XX_ECR, reg); if (err) return err; err = bcm_phy_ack_intr(phydev); } return err; } EXPORT_SYMBOL_GPL(bcm_phy_config_intr); irqreturn_t bcm_phy_handle_interrupt(struct phy_device *phydev) { int irq_status, irq_mask; irq_status = phy_read(phydev, MII_BCM54XX_ISR); if (irq_status < 0) { phy_error(phydev); return IRQ_NONE; } /* If a bit from the Interrupt Mask register is set, the corresponding * bit from the Interrupt Status register is masked. So read the IMR * and then flip the bits to get the list of possible interrupt * sources. */ irq_mask = phy_read(phydev, MII_BCM54XX_IMR); if (irq_mask < 0) { phy_error(phydev); return IRQ_NONE; } irq_mask = ~irq_mask; if (!(irq_status & irq_mask)) return IRQ_NONE; phy_trigger_machine(phydev); return IRQ_HANDLED; } EXPORT_SYMBOL_GPL(bcm_phy_handle_interrupt); int bcm_phy_read_shadow(struct phy_device *phydev, u16 shadow) { phy_write(phydev, MII_BCM54XX_SHD, MII_BCM54XX_SHD_VAL(shadow)); return MII_BCM54XX_SHD_DATA(phy_read(phydev, MII_BCM54XX_SHD)); } EXPORT_SYMBOL_GPL(bcm_phy_read_shadow); int bcm_phy_write_shadow(struct phy_device *phydev, u16 shadow, u16 val) { return phy_write(phydev, MII_BCM54XX_SHD, MII_BCM54XX_SHD_WRITE | MII_BCM54XX_SHD_VAL(shadow) | MII_BCM54XX_SHD_DATA(val)); } EXPORT_SYMBOL_GPL(bcm_phy_write_shadow); int __bcm_phy_read_rdb(struct phy_device *phydev, u16 rdb) { int val; val = __phy_write(phydev, MII_BCM54XX_RDB_ADDR, rdb); if (val < 0) return val; return __phy_read(phydev, MII_BCM54XX_RDB_DATA); } EXPORT_SYMBOL_GPL(__bcm_phy_read_rdb); int bcm_phy_read_rdb(struct phy_device *phydev, u16 rdb) { int ret; phy_lock_mdio_bus(phydev); ret = __bcm_phy_read_rdb(phydev, rdb); phy_unlock_mdio_bus(phydev); return ret; } EXPORT_SYMBOL_GPL(bcm_phy_read_rdb); int __bcm_phy_write_rdb(struct phy_device *phydev, u16 rdb, u16 val) { int ret; ret = __phy_write(phydev, MII_BCM54XX_RDB_ADDR, rdb); if (ret < 0) return ret; return __phy_write(phydev, MII_BCM54XX_RDB_DATA, val); } EXPORT_SYMBOL_GPL(__bcm_phy_write_rdb); int bcm_phy_write_rdb(struct phy_device *phydev, u16 rdb, u16 val) { int ret; phy_lock_mdio_bus(phydev); ret = __bcm_phy_write_rdb(phydev, rdb, val); phy_unlock_mdio_bus(phydev); return ret; } EXPORT_SYMBOL_GPL(bcm_phy_write_rdb); int __bcm_phy_modify_rdb(struct phy_device *phydev, u16 rdb, u16 mask, u16 set) { int new, ret; ret = __phy_write(phydev, MII_BCM54XX_RDB_ADDR, rdb); if (ret < 0) return ret; ret = __phy_read(phydev, MII_BCM54XX_RDB_DATA); if (ret < 0) return ret; new = (ret & ~mask) | set; if (new == ret) return 0; return __phy_write(phydev, MII_BCM54XX_RDB_DATA, new); } EXPORT_SYMBOL_GPL(__bcm_phy_modify_rdb); int bcm_phy_modify_rdb(struct phy_device *phydev, u16 rdb, u16 mask, u16 set) { int ret; phy_lock_mdio_bus(phydev); ret = __bcm_phy_modify_rdb(phydev, rdb, mask, set); phy_unlock_mdio_bus(phydev); return ret; } EXPORT_SYMBOL_GPL(bcm_phy_modify_rdb); int bcm_phy_enable_apd(struct phy_device *phydev, bool dll_pwr_down) { int val; if (dll_pwr_down) { val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR3); if (val < 0) return val; val |= BCM54XX_SHD_SCR3_DLLAPD_DIS; bcm_phy_write_shadow(phydev, BCM54XX_SHD_SCR3, val); } val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_APD); if (val < 0) return val; /* Clear APD bits */ val &= BCM_APD_CLR_MASK; if (phydev->autoneg == AUTONEG_ENABLE) val |= BCM54XX_SHD_APD_EN; else val |= BCM_NO_ANEG_APD_EN; /* Enable energy detect single link pulse for easy wakeup */ val |= BCM_APD_SINGLELP_EN; /* Enable Auto Power-Down (APD) for the PHY */ return bcm_phy_write_shadow(phydev, BCM54XX_SHD_APD, val); } EXPORT_SYMBOL_GPL(bcm_phy_enable_apd); int bcm_phy_set_eee(struct phy_device *phydev, bool enable) { int val, mask = 0; /* Enable EEE at PHY level */ val = phy_read_mmd(phydev, MDIO_MMD_AN, BRCM_CL45VEN_EEE_CONTROL); if (val < 0) return val; if (enable) val |= LPI_FEATURE_EN | LPI_FEATURE_EN_DIG1000X; else val &= ~(LPI_FEATURE_EN | LPI_FEATURE_EN_DIG1000X); phy_write_mmd(phydev, MDIO_MMD_AN, BRCM_CL45VEN_EEE_CONTROL, (u32)val); /* Advertise EEE */ val = phy_read_mmd(phydev, MDIO_MMD_AN, BCM_CL45VEN_EEE_ADV); if (val < 0) return val; if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, phydev->supported)) mask |= MDIO_EEE_1000T; if (linkmode_test_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, phydev->supported)) mask |= MDIO_EEE_100TX; if (enable) val |= mask; else val &= ~mask; phy_write_mmd(phydev, MDIO_MMD_AN, BCM_CL45VEN_EEE_ADV, (u32)val); return 0; } EXPORT_SYMBOL_GPL(bcm_phy_set_eee); int bcm_phy_downshift_get(struct phy_device *phydev, u8 *count) { int val; val = bcm54xx_auxctl_read(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC); if (val < 0) return val; /* Check if wirespeed is enabled or not */ if (!(val & MII_BCM54XX_AUXCTL_SHDWSEL_MISC_WIRESPEED_EN)) { *count = DOWNSHIFT_DEV_DISABLE; return 0; } val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR2); if (val < 0) return val; /* Downgrade after one link attempt */ if (val & BCM54XX_SHD_SCR2_WSPD_RTRY_DIS) { *count = 1; } else { /* Downgrade after configured retry count */ val >>= BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT; val &= BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_MASK; *count = val + BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_OFFSET; } return 0; } EXPORT_SYMBOL_GPL(bcm_phy_downshift_get); int bcm_phy_downshift_set(struct phy_device *phydev, u8 count) { int val = 0, ret = 0; /* Range check the number given */ if (count - BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_OFFSET > BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_MASK && count != DOWNSHIFT_DEV_DEFAULT_COUNT) { return -ERANGE; } val = bcm54xx_auxctl_read(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC); if (val < 0) return val; /* Se the write enable bit */ val |= MII_BCM54XX_AUXCTL_MISC_WREN; if (count == DOWNSHIFT_DEV_DISABLE) { val &= ~MII_BCM54XX_AUXCTL_SHDWSEL_MISC_WIRESPEED_EN; return bcm54xx_auxctl_write(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC, val); } else { val |= MII_BCM54XX_AUXCTL_SHDWSEL_MISC_WIRESPEED_EN; ret = bcm54xx_auxctl_write(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_MISC, val); if (ret < 0) return ret; } val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR2); val &= ~(BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_MASK << BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT | BCM54XX_SHD_SCR2_WSPD_RTRY_DIS); switch (count) { case 1: val |= BCM54XX_SHD_SCR2_WSPD_RTRY_DIS; break; case DOWNSHIFT_DEV_DEFAULT_COUNT: val |= 1 << BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT; break; default: val |= (count - BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_OFFSET) << BCM54XX_SHD_SCR2_WSPD_RTRY_LMT_SHIFT; break; } return bcm_phy_write_shadow(phydev, BCM54XX_SHD_SCR2, val); } EXPORT_SYMBOL_GPL(bcm_phy_downshift_set); struct bcm_phy_hw_stat { const char *string; int devad; u16 reg; u8 shift; u8 bits; }; /* Counters freeze at either 0xffff or 0xff, better than nothing */ static const struct bcm_phy_hw_stat bcm_phy_hw_stats[] = { { "phy_receive_errors", -1, MII_BRCM_CORE_BASE12, 0, 16 }, { "phy_serdes_ber_errors", -1, MII_BRCM_CORE_BASE13, 8, 8 }, { "phy_false_carrier_sense_errors", -1, MII_BRCM_CORE_BASE13, 0, 8 }, { "phy_local_rcvr_nok", -1, MII_BRCM_CORE_BASE14, 8, 8 }, { "phy_remote_rcv_nok", -1, MII_BRCM_CORE_BASE14, 0, 8 }, { "phy_lpi_count", MDIO_MMD_AN, BRCM_CL45VEN_EEE_LPI_CNT, 0, 16 }, }; int bcm_phy_get_sset_count(struct phy_device *phydev) { return ARRAY_SIZE(bcm_phy_hw_stats); } EXPORT_SYMBOL_GPL(bcm_phy_get_sset_count); void bcm_phy_get_strings(struct phy_device *phydev, u8 *data) { unsigned int i; for (i = 0; i < ARRAY_SIZE(bcm_phy_hw_stats); i++) ethtool_puts(&data, bcm_phy_hw_stats[i].string); } EXPORT_SYMBOL_GPL(bcm_phy_get_strings); /* Caller is supposed to provide appropriate storage for the library code to * access the shadow copy */ static u64 bcm_phy_get_stat(struct phy_device *phydev, u64 *shadow, unsigned int i) { struct bcm_phy_hw_stat stat = bcm_phy_hw_stats[i]; int val; u64 ret; if (stat.devad < 0) val = phy_read(phydev, stat.reg); else val = phy_read_mmd(phydev, stat.devad, stat.reg); if (val < 0) { ret = U64_MAX; } else { val >>= stat.shift; val = val & ((1 << stat.bits) - 1); shadow[i] += val; ret = shadow[i]; } return ret; } void bcm_phy_get_stats(struct phy_device *phydev, u64 *shadow, struct ethtool_stats *stats, u64 *data) { unsigned int i; for (i = 0; i < ARRAY_SIZE(bcm_phy_hw_stats); i++) data[i] = bcm_phy_get_stat(phydev, shadow, i); } EXPORT_SYMBOL_GPL(bcm_phy_get_stats); void bcm_phy_r_rc_cal_reset(struct phy_device *phydev) { /* Reset R_CAL/RC_CAL Engine */ bcm_phy_write_exp_sel(phydev, 0x00b0, 0x0010); /* Disable Reset R_AL/RC_CAL Engine */ bcm_phy_write_exp_sel(phydev, 0x00b0, 0x0000); } EXPORT_SYMBOL_GPL(bcm_phy_r_rc_cal_reset); int bcm_phy_28nm_a0b0_afe_config_init(struct phy_device *phydev) { /* Increase VCO range to prevent unlocking problem of PLL at low * temp */ bcm_phy_write_misc(phydev, PLL_PLLCTRL_1, 0x0048); /* Change Ki to 011 */ bcm_phy_write_misc(phydev, PLL_PLLCTRL_2, 0x021b); /* Disable loading of TVCO buffer to bandgap, set bandgap trim * to 111 */ bcm_phy_write_misc(phydev, PLL_PLLCTRL_4, 0x0e20); /* Adjust bias current trim by -3 */ bcm_phy_write_misc(phydev, DSP_TAP10, 0x690b); /* Switch to CORE_BASE1E */ phy_write(phydev, MII_BRCM_CORE_BASE1E, 0xd); bcm_phy_r_rc_cal_reset(phydev); /* write AFE_RXCONFIG_0 */ bcm_phy_write_misc(phydev, AFE_RXCONFIG_0, 0xeb19); /* write AFE_RXCONFIG_1 */ bcm_phy_write_misc(phydev, AFE_RXCONFIG_1, 0x9a3f); /* write AFE_RX_LP_COUNTER */ bcm_phy_write_misc(phydev, AFE_RX_LP_COUNTER, 0x7fc0); /* write AFE_HPF_TRIM_OTHERS */ bcm_phy_write_misc(phydev, AFE_HPF_TRIM_OTHERS, 0x000b); /* write AFTE_TX_CONFIG */ bcm_phy_write_misc(phydev, AFE_TX_CONFIG, 0x0800); return 0; } EXPORT_SYMBOL_GPL(bcm_phy_28nm_a0b0_afe_config_init); int bcm_phy_enable_jumbo(struct phy_device *phydev) { int ret; ret = bcm54xx_auxctl_read(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL); if (ret < 0) return ret; /* Enable extended length packet reception */ ret = bcm54xx_auxctl_write(phydev, MII_BCM54XX_AUXCTL_SHDWSEL_AUXCTL, ret | MII_BCM54XX_AUXCTL_ACTL_EXT_PKT_LEN); if (ret < 0) return ret; /* Enable the elastic FIFO for raising the transmission limit from * 4.5KB to 10KB, at the expense of an additional 16 ns in propagation * latency. */ return phy_set_bits(phydev, MII_BCM54XX_ECR, MII_BCM54XX_ECR_FIFOE); } EXPORT_SYMBOL_GPL(bcm_phy_enable_jumbo); static int __bcm_phy_enable_rdb_access(struct phy_device *phydev) { return __bcm_phy_write_exp(phydev, BCM54XX_EXP_REG7E, 0); } static int __bcm_phy_enable_legacy_access(struct phy_device *phydev) { return __bcm_phy_write_rdb(phydev, BCM54XX_RDB_REG0087, BCM54XX_ACCESS_MODE_LEGACY_EN); } static int _bcm_phy_cable_test_start(struct phy_device *phydev, bool is_rdb) { u16 mask, set; int ret; /* Auto-negotiation must be enabled for cable diagnostics to work, but * don't advertise any capabilities. */ phy_write(phydev, MII_BMCR, BMCR_ANENABLE); phy_write(phydev, MII_ADVERTISE, ADVERTISE_CSMA); phy_write(phydev, MII_CTRL1000, 0); phy_lock_mdio_bus(phydev); if (is_rdb) { ret = __bcm_phy_enable_legacy_access(phydev); if (ret) goto out; } mask = BCM54XX_ECD_CTRL_CROSS_SHORT_DIS | BCM54XX_ECD_CTRL_UNIT_MASK; set = BCM54XX_ECD_CTRL_RUN | BCM54XX_ECD_CTRL_BREAK_LINK | FIELD_PREP(BCM54XX_ECD_CTRL_UNIT_MASK, BCM54XX_ECD_CTRL_UNIT_CM); ret = __bcm_phy_modify_exp(phydev, BCM54XX_EXP_ECD_CTRL, mask, set); out: /* re-enable the RDB access even if there was an error */ if (is_rdb) ret = __bcm_phy_enable_rdb_access(phydev) ? : ret; phy_unlock_mdio_bus(phydev); return ret; } static int bcm_phy_cable_test_report_trans(int result) { switch (result) { case BCM54XX_ECD_FAULT_TYPE_OK: return ETHTOOL_A_CABLE_RESULT_CODE_OK; case BCM54XX_ECD_FAULT_TYPE_OPEN: return ETHTOOL_A_CABLE_RESULT_CODE_OPEN; case BCM54XX_ECD_FAULT_TYPE_SAME_SHORT: return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT; case BCM54XX_ECD_FAULT_TYPE_CROSS_SHORT: return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT; case BCM54XX_ECD_FAULT_TYPE_INVALID: case BCM54XX_ECD_FAULT_TYPE_BUSY: default: return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC; } } static bool bcm_phy_distance_valid(int result) { switch (result) { case BCM54XX_ECD_FAULT_TYPE_OPEN: case BCM54XX_ECD_FAULT_TYPE_SAME_SHORT: case BCM54XX_ECD_FAULT_TYPE_CROSS_SHORT: return true; } return false; } static int bcm_phy_report_length(struct phy_device *phydev, int pair) { int val; val = __bcm_phy_read_exp(phydev, BCM54XX_EXP_ECD_PAIR_A_LENGTH_RESULTS + pair); if (val < 0) return val; if (val == BCM54XX_ECD_LENGTH_RESULTS_INVALID) return 0; ethnl_cable_test_fault_length(phydev, pair, val); return 0; } static int _bcm_phy_cable_test_get_status(struct phy_device *phydev, bool *finished, bool is_rdb) { int pair_a, pair_b, pair_c, pair_d, ret; *finished = false; phy_lock_mdio_bus(phydev); if (is_rdb) { ret = __bcm_phy_enable_legacy_access(phydev); if (ret) goto out; } ret = __bcm_phy_read_exp(phydev, BCM54XX_EXP_ECD_CTRL); if (ret < 0) goto out; if (ret & BCM54XX_ECD_CTRL_IN_PROGRESS) { ret = 0; goto out; } ret = __bcm_phy_read_exp(phydev, BCM54XX_EXP_ECD_FAULT_TYPE); if (ret < 0) goto out; pair_a = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_A_MASK, ret); pair_b = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_B_MASK, ret); pair_c = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_C_MASK, ret); pair_d = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_D_MASK, ret); ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A, bcm_phy_cable_test_report_trans(pair_a)); ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_B, bcm_phy_cable_test_report_trans(pair_b)); ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_C, bcm_phy_cable_test_report_trans(pair_c)); ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_D, bcm_phy_cable_test_report_trans(pair_d)); if (bcm_phy_distance_valid(pair_a)) bcm_phy_report_length(phydev, 0); if (bcm_phy_distance_valid(pair_b)) bcm_phy_report_length(phydev, 1); if (bcm_phy_distance_valid(pair_c)) bcm_phy_report_length(phydev, 2); if (bcm_phy_distance_valid(pair_d)) bcm_phy_report_length(phydev, 3); ret = 0; *finished = true; out: /* re-enable the RDB access even if there was an error */ if (is_rdb) ret = __bcm_phy_enable_rdb_access(phydev) ? : ret; phy_unlock_mdio_bus(phydev); return ret; } static int bcm_setup_lre_forced(struct phy_device *phydev) { u16 ctl = 0; phydev->pause = 0; phydev->asym_pause = 0; if (phydev->speed == SPEED_100) ctl |= LRECR_SPEED100; if (phydev->duplex != DUPLEX_FULL) return -EOPNOTSUPP; return phy_modify(phydev, MII_BCM54XX_LRECR, LRECR_SPEED100, ctl); } /** * bcm_linkmode_adv_to_lre_adv_t - translate linkmode advertisement to LDS * @advertising: the linkmode advertisement settings * Return: LDS Auto-Negotiation Advertised Ability register value * * A small helper function that translates linkmode advertisement * settings to phy LDS autonegotiation advertisements for the * MII_BCM54XX_LREANAA register of Broadcom PHYs capable of LDS */ static u32 bcm_linkmode_adv_to_lre_adv_t(unsigned long *advertising) { u32 result = 0; if (linkmode_test_bit(ETHTOOL_LINK_MODE_10baseT1BRR_Full_BIT, advertising)) result |= LREANAA_10_1PAIR; if (linkmode_test_bit(ETHTOOL_LINK_MODE_100baseT1_Full_BIT, advertising)) result |= LREANAA_100_1PAIR; if (linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT, advertising)) result |= LRELPA_PAUSE; if (linkmode_test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, advertising)) result |= LRELPA_PAUSE_ASYM; return result; } int bcm_phy_cable_test_start(struct phy_device *phydev) { return _bcm_phy_cable_test_start(phydev, false); } EXPORT_SYMBOL_GPL(bcm_phy_cable_test_start); int bcm_phy_cable_test_get_status(struct phy_device *phydev, bool *finished) { return _bcm_phy_cable_test_get_status(phydev, finished, false); } EXPORT_SYMBOL_GPL(bcm_phy_cable_test_get_status); /* We assume that all PHYs which support RDB access can be switched to legacy * mode. If, in the future, this is not true anymore, we have to re-implement * this with RDB access. */ int bcm_phy_cable_test_start_rdb(struct phy_device *phydev) { return _bcm_phy_cable_test_start(phydev, true); } EXPORT_SYMBOL_GPL(bcm_phy_cable_test_start_rdb); int bcm_phy_cable_test_get_status_rdb(struct phy_device *phydev, bool *finished) { return _bcm_phy_cable_test_get_status(phydev, finished, true); } EXPORT_SYMBOL_GPL(bcm_phy_cable_test_get_status_rdb); #define BCM54XX_WOL_SUPPORTED_MASK (WAKE_UCAST | \ WAKE_MCAST | \ WAKE_BCAST | \ WAKE_MAGIC | \ WAKE_MAGICSECURE) int bcm_phy_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol) { struct net_device *ndev = phydev->attached_dev; u8 da[ETH_ALEN], mask[ETH_ALEN]; unsigned int i; u16 ctl; int ret; /* Allow a MAC driver to play through its own Wake-on-LAN * implementation */ if (wol->wolopts & ~BCM54XX_WOL_SUPPORTED_MASK) return -EOPNOTSUPP; /* The PHY supports passwords of 4, 6 and 8 bytes in size, but Linux's * ethtool only supports 6, for now. */ BUILD_BUG_ON(sizeof(wol->sopass) != ETH_ALEN); /* Clear previous interrupts */ ret = bcm_phy_read_exp(phydev, BCM54XX_WOL_INT_STATUS); if (ret < 0) return ret; ret = bcm_phy_read_exp(phydev, BCM54XX_WOL_MAIN_CTL); if (ret < 0) return ret; ctl = ret; if (!wol->wolopts) { if (phy_interrupt_is_valid(phydev)) disable_irq_wake(phydev->irq); /* Leave all interrupts disabled */ ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_INT_MASK, BCM54XX_WOL_ALL_INTRS); if (ret < 0) return ret; /* Disable the global Wake-on-LAN enable bit */ ctl &= ~BCM54XX_WOL_EN; return bcm_phy_write_exp(phydev, BCM54XX_WOL_MAIN_CTL, ctl); } /* Clear the previously configured mode and mask mode for Wake-on-LAN */ ctl &= ~(BCM54XX_WOL_MODE_MASK << BCM54XX_WOL_MODE_SHIFT); ctl &= ~(BCM54XX_WOL_MASK_MODE_MASK << BCM54XX_WOL_MASK_MODE_SHIFT); ctl &= ~BCM54XX_WOL_DIR_PKT_EN; ctl &= ~(BCM54XX_WOL_SECKEY_OPT_MASK << BCM54XX_WOL_SECKEY_OPT_SHIFT); /* When using WAKE_MAGIC, we program the magic pattern filter to match * the device's MAC address and we accept any MAC DA in the Ethernet * frame. * * When using WAKE_UCAST, WAKE_BCAST or WAKE_MCAST, we program the * following: * - WAKE_UCAST -> MAC DA is the device's MAC with a perfect match * - WAKE_MCAST -> MAC DA is X1:XX:XX:XX:XX:XX where XX is don't care * - WAKE_BCAST -> MAC DA is FF:FF:FF:FF:FF:FF with a perfect match * * Note that the Broadcast MAC DA is inherently going to match the * multicast pattern being matched. */ memset(mask, 0, sizeof(mask)); if (wol->wolopts & WAKE_MCAST) { memset(da, 0, sizeof(da)); memset(mask, 0xff, sizeof(mask)); da[0] = 0x01; mask[0] = ~da[0]; } else { if (wol->wolopts & WAKE_UCAST) { ether_addr_copy(da, ndev->dev_addr); } else if (wol->wolopts & WAKE_BCAST) { eth_broadcast_addr(da); } else if (wol->wolopts & WAKE_MAGICSECURE) { ether_addr_copy(da, wol->sopass); } else if (wol->wolopts & WAKE_MAGIC) { memset(da, 0, sizeof(da)); memset(mask, 0xff, sizeof(mask)); } } for (i = 0; i < ETH_ALEN / 2; i++) { if (wol->wolopts & (WAKE_MAGIC | WAKE_MAGICSECURE)) { ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_MPD_DATA1(2 - i), ndev->dev_addr[i * 2] << 8 | ndev->dev_addr[i * 2 + 1]); if (ret < 0) return ret; } ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_MPD_DATA2(2 - i), da[i * 2] << 8 | da[i * 2 + 1]); if (ret < 0) return ret; ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_MASK(2 - i), mask[i * 2] << 8 | mask[i * 2 + 1]); if (ret) return ret; } if (wol->wolopts & WAKE_MAGICSECURE) { ctl |= BCM54XX_WOL_SECKEY_OPT_6B << BCM54XX_WOL_SECKEY_OPT_SHIFT; ctl |= BCM54XX_WOL_MODE_SINGLE_MPDSEC << BCM54XX_WOL_MODE_SHIFT; ctl |= BCM54XX_WOL_MASK_MODE_DA_FF << BCM54XX_WOL_MASK_MODE_SHIFT; } else { if (wol->wolopts & WAKE_MAGIC) ctl |= BCM54XX_WOL_MODE_SINGLE_MPD; else ctl |= BCM54XX_WOL_DIR_PKT_EN; ctl |= BCM54XX_WOL_MASK_MODE_DA_ONLY << BCM54XX_WOL_MASK_MODE_SHIFT; } /* Globally enable Wake-on-LAN */ ctl |= BCM54XX_WOL_EN | BCM54XX_WOL_CRC_CHK; ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_MAIN_CTL, ctl); if (ret < 0) return ret; /* Enable WOL interrupt on LED4 */ ret = bcm_phy_read_exp(phydev, BCM54XX_TOP_MISC_LED_CTL); if (ret < 0) return ret; ret |= BCM54XX_LED4_SEL_INTR; ret = bcm_phy_write_exp(phydev, BCM54XX_TOP_MISC_LED_CTL, ret); if (ret < 0) return ret; /* Enable all Wake-on-LAN interrupt sources */ ret = bcm_phy_write_exp(phydev, BCM54XX_WOL_INT_MASK, 0); if (ret < 0) return ret; if (phy_interrupt_is_valid(phydev)) enable_irq_wake(phydev->irq); return 0; } EXPORT_SYMBOL_GPL(bcm_phy_set_wol); void bcm_phy_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol) { struct net_device *ndev = phydev->attached_dev; u8 da[ETH_ALEN]; unsigned int i; int ret; u16 ctl; wol->supported = BCM54XX_WOL_SUPPORTED_MASK; wol->wolopts = 0; ret = bcm_phy_read_exp(phydev, BCM54XX_WOL_MAIN_CTL); if (ret < 0) return; ctl = ret; if (!(ctl & BCM54XX_WOL_EN)) return; for (i = 0; i < sizeof(da) / 2; i++) { ret = bcm_phy_read_exp(phydev, BCM54XX_WOL_MPD_DATA2(2 - i)); if (ret < 0) return; da[i * 2] = ret >> 8; da[i * 2 + 1] = ret & 0xff; } if (ctl & BCM54XX_WOL_DIR_PKT_EN) { if (is_broadcast_ether_addr(da)) wol->wolopts |= WAKE_BCAST; else if (is_multicast_ether_addr(da)) wol->wolopts |= WAKE_MCAST; else if (ether_addr_equal(da, ndev->dev_addr)) wol->wolopts |= WAKE_UCAST; } else { ctl = (ctl >> BCM54XX_WOL_MODE_SHIFT) & BCM54XX_WOL_MODE_MASK; switch (ctl) { case BCM54XX_WOL_MODE_SINGLE_MPD: wol->wolopts |= WAKE_MAGIC; break; case BCM54XX_WOL_MODE_SINGLE_MPDSEC: wol->wolopts |= WAKE_MAGICSECURE; memcpy(wol->sopass, da, sizeof(da)); break; default: break; } } } EXPORT_SYMBOL_GPL(bcm_phy_get_wol); irqreturn_t bcm_phy_wol_isr(int irq, void *dev_id) { return IRQ_HANDLED; } EXPORT_SYMBOL_GPL(bcm_phy_wol_isr); int bcm_phy_led_brightness_set(struct phy_device *phydev, u8 index, enum led_brightness value) { u8 led_num; int ret; u16 reg; if (index >= 4) return -EINVAL; /* Two LEDS per register */ led_num = index % 2; reg = index >= 2 ? BCM54XX_SHD_LEDS2 : BCM54XX_SHD_LEDS1; ret = bcm_phy_read_shadow(phydev, reg); if (ret < 0) return ret; ret &= ~(BCM_LED_SRC_MASK << BCM54XX_SHD_LEDS_SHIFT(led_num)); if (value == LED_OFF) ret |= BCM_LED_SRC_OFF << BCM54XX_SHD_LEDS_SHIFT(led_num); else ret |= BCM_LED_SRC_ON << BCM54XX_SHD_LEDS_SHIFT(led_num); return bcm_phy_write_shadow(phydev, reg, ret); } EXPORT_SYMBOL_GPL(bcm_phy_led_brightness_set); int bcm_setup_lre_master_slave(struct phy_device *phydev) { u16 ctl = 0; switch (phydev->master_slave_set) { case MASTER_SLAVE_CFG_MASTER_PREFERRED: case MASTER_SLAVE_CFG_MASTER_FORCE: ctl = LRECR_MASTER; break; case MASTER_SLAVE_CFG_SLAVE_PREFERRED: case MASTER_SLAVE_CFG_SLAVE_FORCE: break; case MASTER_SLAVE_CFG_UNKNOWN: case MASTER_SLAVE_CFG_UNSUPPORTED: return 0; default: phydev_warn(phydev, "Unsupported Master/Slave mode\n"); return -EOPNOTSUPP; } return phy_modify_changed(phydev, MII_BCM54XX_LRECR, LRECR_MASTER, ctl); } EXPORT_SYMBOL_GPL(bcm_setup_lre_master_slave); int bcm_config_lre_aneg(struct phy_device *phydev, bool changed) { int err; if (genphy_c45_an_config_eee_aneg(phydev) > 0) changed = true; err = bcm_setup_lre_master_slave(phydev); if (err < 0) return err; else if (err) changed = true; if (phydev->autoneg != AUTONEG_ENABLE) return bcm_setup_lre_forced(phydev); err = bcm_config_lre_advert(phydev); if (err < 0) return err; else if (err) changed = true; return genphy_check_and_restart_aneg(phydev, changed); } EXPORT_SYMBOL_GPL(bcm_config_lre_aneg); /** * bcm_config_lre_advert - sanitize and advertise Long-Distance Signaling * auto-negotiation parameters * @phydev: target phy_device struct * Return: 0 if the PHY's advertisement hasn't changed, < 0 on error, * > 0 if it has changed * * Writes MII_BCM54XX_LREANAA with the appropriate values. The values are to be * sanitized before, to make sure we only advertise what is supported. * The sanitization is done already in phy_ethtool_ksettings_set() */ int bcm_config_lre_advert(struct phy_device *phydev) { u32 adv = bcm_linkmode_adv_to_lre_adv_t(phydev->advertising); /* Setup BroadR-Reach mode advertisement */ return phy_modify_changed(phydev, MII_BCM54XX_LREANAA, LRE_ADVERTISE_ALL | LREANAA_PAUSE | LREANAA_PAUSE_ASYM, adv); } EXPORT_SYMBOL_GPL(bcm_config_lre_advert); MODULE_DESCRIPTION("Broadcom PHY Library"); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Broadcom Corporation");