diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
commit | ace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch) | |
tree | b2d64bc10158fdd5497876388cd68142ca374ed3 /drivers/net/phy/bcm-phy-lib.c | |
parent | Initial commit. (diff) | |
download | linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip |
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/net/phy/bcm-phy-lib.c')
-rw-r--r-- | drivers/net/phy/bcm-phy-lib.c | 1071 |
1 files changed, 1071 insertions, 0 deletions
diff --git a/drivers/net/phy/bcm-phy-lib.c b/drivers/net/phy/bcm-phy-lib.c new file mode 100644 index 0000000000..876f28fd82 --- /dev/null +++ b/drivers/net/phy/bcm-phy-lib.c @@ -0,0 +1,1071 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2015-2017 Broadcom + */ + +#include "bcm-phy-lib.h" +#include <linux/bitfield.h> +#include <linux/brcmphy.h> +#include <linux/etherdevice.h> +#include <linux/export.h> +#include <linux/mdio.h> +#include <linux/module.h> +#include <linux/phy.h> +#include <linux/ethtool.h> +#include <linux/ethtool_netlink.h> +#include <linux/netdevice.h> + +#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++) + strscpy(data + i * ETH_GSTRING_LEN, + bcm_phy_hw_stats[i].string, ETH_GSTRING_LEN); +} +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; +} + +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); + +MODULE_DESCRIPTION("Broadcom PHY Library"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Broadcom Corporation"); |