Expose LED1 and LED2 pins via the PHY LED framework. Each pin has a source mask (MASK_LOW + MASK_EXT registers) selecting which hardware events light it, plus a CTL field in the shared 0xA83B register (RMW; LED4 is firmware-controlled per the datasheet). Hardware can offload per-speed link triggers (1000/2500/5000/10000), RX/TX activity, and force-on. LINK_100 is accepted only alongside LINK_1000: source bit 4 lights at both speeds and 100-alone isn't representable, so the unrepresentable case falls to software. The chip has five LED pins; only LED1/LED2 are exposed here as those are the only ones characterized on tested hardware. LED4 is firmware- controlled regardless of strap configuration. Tested on TRENDnet TEG-S750 (LED1/LED2 wired to an antiparallel bicolor LED): brightness_set via sysfs; netdev trigger offloaded=1 with amber lit at 100M/1G/2.5G and green lit at 10G via respective link_* modes; LED off immediately on cable unplug with no software involvement. Signed-off-by: Daniel Wagner --- drivers/net/phy/bcm84881.c | 156 +++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/drivers/net/phy/bcm84881.c b/drivers/net/phy/bcm84881.c index f114212dd3..2ae70dcf82 100644 --- a/drivers/net/phy/bcm84881.c +++ b/drivers/net/phy/bcm84881.c @@ -20,6 +20,33 @@ enum { MDIO_AN_C22 = 0xffe0, }; +/* BCM8489x LED controller (BCM84891L datasheet 2.4.1.58). Each pin has + * CTL bits in 0xA83B (stride 3: 2-bit CTL + 1-bit OE_N) plus MASK_LOW/ + * MASK_EXT source selects. LED4 is firmware-controlled; always RMW. + */ +#define BCM8489X_LED_CTL 0xa83b +#define BCM8489X_LED_CTL_ON(i) (0x2 << ((i) * 3)) +#define BCM8489X_LED_CTL_MASK(i) (0x3 << ((i) * 3)) + +#define BCM8489X_LED_SRC_RX BIT(1) +#define BCM8489X_LED_SRC_TX BIT(2) +#define BCM8489X_LED_SRC_1000 BIT(3) /* high only at 1000 */ +#define BCM8489X_LED_SRC_100_1000 BIT(4) /* high at 100 and 1000 */ +#define BCM8489X_LED_SRC_FORCE BIT(5) /* always-1 source */ +#define BCM8489X_LED_SRC_10G BIT(7) +#define BCM8489X_LED_SRCX_2500 BIT(2) +#define BCM8489X_LED_SRCX_5000 BIT(3) + +#define BCM8489X_MAX_LEDS 2 + +static const struct { + u16 mask_low; + u16 mask_ext; +} bcm8489x_led_regs[BCM8489X_MAX_LEDS] = { + { 0xa82c, 0xa8ef }, /* LED1 */ + { 0xa82f, 0xa8f0 }, /* LED2 */ +}; + static int bcm84881_wait_init(struct phy_device *phydev) { int val; @@ -69,6 +96,127 @@ static int bcm8489x_config_init(struct phy_device *phydev) MDIO_CTRL1_LPOWER); } +static int bcm8489x_led_write(struct phy_device *phydev, u8 index, + u16 low, u16 ext) +{ + int ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_PMAPMD, + bcm8489x_led_regs[index].mask_low, low); + if (ret) + return ret; + ret = phy_write_mmd(phydev, MDIO_MMD_PMAPMD, + bcm8489x_led_regs[index].mask_ext, ext); + if (ret) + return ret; + return phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, BCM8489X_LED_CTL, + BCM8489X_LED_CTL_MASK(index), + (low | ext) ? BCM8489X_LED_CTL_ON(index) : 0); +} + +static int bcm8489x_led_brightness_set(struct phy_device *phydev, + u8 index, enum led_brightness value) +{ + if (index >= BCM8489X_MAX_LEDS) + return -EINVAL; + + return bcm8489x_led_write(phydev, index, + value ? BCM8489X_LED_SRC_FORCE : 0, 0); +} + +static const unsigned long bcm8489x_supported_triggers = + BIT(TRIGGER_NETDEV_LINK) | + BIT(TRIGGER_NETDEV_LINK_100) | + BIT(TRIGGER_NETDEV_LINK_1000) | + BIT(TRIGGER_NETDEV_LINK_2500) | + BIT(TRIGGER_NETDEV_LINK_5000) | + BIT(TRIGGER_NETDEV_LINK_10000) | + BIT(TRIGGER_NETDEV_RX) | + BIT(TRIGGER_NETDEV_TX); + +static int bcm8489x_led_hw_is_supported(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + if (index >= BCM8489X_MAX_LEDS) + return -EINVAL; + + if (rules & ~bcm8489x_supported_triggers) + return -EOPNOTSUPP; + + /* Source bit 4 lights at both 100 and 1000; "100 only" isn't + * representable in hardware. Accept LINK_100 only alongside + * LINK_1000 or LINK so the offload is precise. + */ + if ((rules & BIT(TRIGGER_NETDEV_LINK_100)) && + !(rules & (BIT(TRIGGER_NETDEV_LINK_1000) | + BIT(TRIGGER_NETDEV_LINK)))) + return -EOPNOTSUPP; + + return 0; +} + +static int bcm8489x_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + u16 low = 0, ext = 0; + + if (index >= BCM8489X_MAX_LEDS) + return -EINVAL; + + if (rules & (BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK))) + low |= BCM8489X_LED_SRC_100_1000; + if (rules & (BIT(TRIGGER_NETDEV_LINK_1000) | BIT(TRIGGER_NETDEV_LINK))) + low |= BCM8489X_LED_SRC_1000; + if (rules & (BIT(TRIGGER_NETDEV_LINK_2500) | BIT(TRIGGER_NETDEV_LINK))) + ext |= BCM8489X_LED_SRCX_2500; + if (rules & (BIT(TRIGGER_NETDEV_LINK_5000) | BIT(TRIGGER_NETDEV_LINK))) + ext |= BCM8489X_LED_SRCX_5000; + if (rules & (BIT(TRIGGER_NETDEV_LINK_10000) | BIT(TRIGGER_NETDEV_LINK))) + low |= BCM8489X_LED_SRC_10G; + if (rules & BIT(TRIGGER_NETDEV_RX)) + low |= BCM8489X_LED_SRC_RX; + if (rules & BIT(TRIGGER_NETDEV_TX)) + low |= BCM8489X_LED_SRC_TX; + + return bcm8489x_led_write(phydev, index, low, ext); +} + +static int bcm8489x_led_hw_control_get(struct phy_device *phydev, u8 index, + unsigned long *rules) +{ + int low, ext; + + if (index >= BCM8489X_MAX_LEDS) + return -EINVAL; + + low = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, + bcm8489x_led_regs[index].mask_low); + if (low < 0) + return low; + ext = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, + bcm8489x_led_regs[index].mask_ext); + if (ext < 0) + return ext; + + *rules = 0; + if (low & BCM8489X_LED_SRC_100_1000) + *rules |= BIT(TRIGGER_NETDEV_LINK_100); + if (low & BCM8489X_LED_SRC_1000) + *rules |= BIT(TRIGGER_NETDEV_LINK_1000); + if (ext & BCM8489X_LED_SRCX_2500) + *rules |= BIT(TRIGGER_NETDEV_LINK_2500); + if (ext & BCM8489X_LED_SRCX_5000) + *rules |= BIT(TRIGGER_NETDEV_LINK_5000); + if (low & BCM8489X_LED_SRC_10G) + *rules |= BIT(TRIGGER_NETDEV_LINK_10000); + if (low & BCM8489X_LED_SRC_RX) + *rules |= BIT(TRIGGER_NETDEV_RX); + if (low & BCM8489X_LED_SRC_TX) + *rules |= BIT(TRIGGER_NETDEV_TX); + + return 0; +} + static int bcm84881_probe(struct phy_device *phydev) { /* This driver requires PMAPMD and AN blocks */ @@ -290,6 +438,10 @@ static struct phy_driver bcm84881_drivers[] = { .config_aneg = bcm84881_config_aneg, .aneg_done = bcm84881_aneg_done, .read_status = bcm84881_read_status, + .led_brightness_set = bcm8489x_led_brightness_set, + .led_hw_is_supported = bcm8489x_led_hw_is_supported, + .led_hw_control_set = bcm8489x_led_hw_control_set, + .led_hw_control_get = bcm8489x_led_hw_control_get, }, { PHY_ID_MATCH_MODEL(0x359050a0), .name = "Broadcom BCM84892", @@ -300,6 +452,10 @@ static struct phy_driver bcm84881_drivers[] = { .config_aneg = bcm84881_config_aneg, .aneg_done = bcm84881_aneg_done, .read_status = bcm84881_read_status, + .led_brightness_set = bcm8489x_led_brightness_set, + .led_hw_is_supported = bcm8489x_led_hw_is_supported, + .led_hw_control_set = bcm8489x_led_hw_control_set, + .led_hw_control_get = bcm8489x_led_hw_control_get, }, }; -- 2.47.3