Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

net: phy: bcm84881: add LED framework support for BCM84891/BCM84892

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 <wagner.daniel.t@gmail.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Link: https://patch.msgid.link/20260401114931.3091818-1-wagner.daniel.t@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Daniel Wagner and committed by
Jakub Kicinski
7eaff1ef 9b79da5d

+156
+156
drivers/net/phy/bcm84881.c
··· 20 20 MDIO_AN_C22 = 0xffe0, 21 21 }; 22 22 23 + /* BCM8489x LED controller (BCM84891L datasheet 2.4.1.58). Each pin has 24 + * CTL bits in 0xA83B (stride 3: 2-bit CTL + 1-bit OE_N) plus MASK_LOW/ 25 + * MASK_EXT source selects. LED4 is firmware-controlled; always RMW. 26 + */ 27 + #define BCM8489X_LED_CTL 0xa83b 28 + #define BCM8489X_LED_CTL_ON(i) (0x2 << ((i) * 3)) 29 + #define BCM8489X_LED_CTL_MASK(i) (0x3 << ((i) * 3)) 30 + 31 + #define BCM8489X_LED_SRC_RX BIT(1) 32 + #define BCM8489X_LED_SRC_TX BIT(2) 33 + #define BCM8489X_LED_SRC_1000 BIT(3) /* high only at 1000 */ 34 + #define BCM8489X_LED_SRC_100_1000 BIT(4) /* high at 100 and 1000 */ 35 + #define BCM8489X_LED_SRC_FORCE BIT(5) /* always-1 source */ 36 + #define BCM8489X_LED_SRC_10G BIT(7) 37 + #define BCM8489X_LED_SRCX_2500 BIT(2) 38 + #define BCM8489X_LED_SRCX_5000 BIT(3) 39 + 40 + #define BCM8489X_MAX_LEDS 2 41 + 42 + static const struct { 43 + u16 mask_low; 44 + u16 mask_ext; 45 + } bcm8489x_led_regs[BCM8489X_MAX_LEDS] = { 46 + { 0xa82c, 0xa8ef }, /* LED1 */ 47 + { 0xa82f, 0xa8f0 }, /* LED2 */ 48 + }; 49 + 23 50 static int bcm84881_wait_init(struct phy_device *phydev) 24 51 { 25 52 int val; ··· 94 67 */ 95 68 return phy_clear_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_CTRL1, 96 69 MDIO_CTRL1_LPOWER); 70 + } 71 + 72 + static int bcm8489x_led_write(struct phy_device *phydev, u8 index, 73 + u16 low, u16 ext) 74 + { 75 + int ret; 76 + 77 + ret = phy_write_mmd(phydev, MDIO_MMD_PMAPMD, 78 + bcm8489x_led_regs[index].mask_low, low); 79 + if (ret) 80 + return ret; 81 + ret = phy_write_mmd(phydev, MDIO_MMD_PMAPMD, 82 + bcm8489x_led_regs[index].mask_ext, ext); 83 + if (ret) 84 + return ret; 85 + return phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, BCM8489X_LED_CTL, 86 + BCM8489X_LED_CTL_MASK(index), 87 + (low | ext) ? BCM8489X_LED_CTL_ON(index) : 0); 88 + } 89 + 90 + static int bcm8489x_led_brightness_set(struct phy_device *phydev, 91 + u8 index, enum led_brightness value) 92 + { 93 + if (index >= BCM8489X_MAX_LEDS) 94 + return -EINVAL; 95 + 96 + return bcm8489x_led_write(phydev, index, 97 + value ? BCM8489X_LED_SRC_FORCE : 0, 0); 98 + } 99 + 100 + static const unsigned long bcm8489x_supported_triggers = 101 + BIT(TRIGGER_NETDEV_LINK) | 102 + BIT(TRIGGER_NETDEV_LINK_100) | 103 + BIT(TRIGGER_NETDEV_LINK_1000) | 104 + BIT(TRIGGER_NETDEV_LINK_2500) | 105 + BIT(TRIGGER_NETDEV_LINK_5000) | 106 + BIT(TRIGGER_NETDEV_LINK_10000) | 107 + BIT(TRIGGER_NETDEV_RX) | 108 + BIT(TRIGGER_NETDEV_TX); 109 + 110 + static int bcm8489x_led_hw_is_supported(struct phy_device *phydev, u8 index, 111 + unsigned long rules) 112 + { 113 + if (index >= BCM8489X_MAX_LEDS) 114 + return -EINVAL; 115 + 116 + if (rules & ~bcm8489x_supported_triggers) 117 + return -EOPNOTSUPP; 118 + 119 + /* Source bit 4 lights at both 100 and 1000; "100 only" isn't 120 + * representable in hardware. Accept LINK_100 only alongside 121 + * LINK_1000 or LINK so the offload is precise. 122 + */ 123 + if ((rules & BIT(TRIGGER_NETDEV_LINK_100)) && 124 + !(rules & (BIT(TRIGGER_NETDEV_LINK_1000) | 125 + BIT(TRIGGER_NETDEV_LINK)))) 126 + return -EOPNOTSUPP; 127 + 128 + return 0; 129 + } 130 + 131 + static int bcm8489x_led_hw_control_set(struct phy_device *phydev, u8 index, 132 + unsigned long rules) 133 + { 134 + u16 low = 0, ext = 0; 135 + 136 + if (index >= BCM8489X_MAX_LEDS) 137 + return -EINVAL; 138 + 139 + if (rules & (BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK))) 140 + low |= BCM8489X_LED_SRC_100_1000; 141 + if (rules & (BIT(TRIGGER_NETDEV_LINK_1000) | BIT(TRIGGER_NETDEV_LINK))) 142 + low |= BCM8489X_LED_SRC_1000; 143 + if (rules & (BIT(TRIGGER_NETDEV_LINK_2500) | BIT(TRIGGER_NETDEV_LINK))) 144 + ext |= BCM8489X_LED_SRCX_2500; 145 + if (rules & (BIT(TRIGGER_NETDEV_LINK_5000) | BIT(TRIGGER_NETDEV_LINK))) 146 + ext |= BCM8489X_LED_SRCX_5000; 147 + if (rules & (BIT(TRIGGER_NETDEV_LINK_10000) | BIT(TRIGGER_NETDEV_LINK))) 148 + low |= BCM8489X_LED_SRC_10G; 149 + if (rules & BIT(TRIGGER_NETDEV_RX)) 150 + low |= BCM8489X_LED_SRC_RX; 151 + if (rules & BIT(TRIGGER_NETDEV_TX)) 152 + low |= BCM8489X_LED_SRC_TX; 153 + 154 + return bcm8489x_led_write(phydev, index, low, ext); 155 + } 156 + 157 + static int bcm8489x_led_hw_control_get(struct phy_device *phydev, u8 index, 158 + unsigned long *rules) 159 + { 160 + int low, ext; 161 + 162 + if (index >= BCM8489X_MAX_LEDS) 163 + return -EINVAL; 164 + 165 + low = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, 166 + bcm8489x_led_regs[index].mask_low); 167 + if (low < 0) 168 + return low; 169 + ext = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, 170 + bcm8489x_led_regs[index].mask_ext); 171 + if (ext < 0) 172 + return ext; 173 + 174 + *rules = 0; 175 + if (low & BCM8489X_LED_SRC_100_1000) 176 + *rules |= BIT(TRIGGER_NETDEV_LINK_100); 177 + if (low & BCM8489X_LED_SRC_1000) 178 + *rules |= BIT(TRIGGER_NETDEV_LINK_1000); 179 + if (ext & BCM8489X_LED_SRCX_2500) 180 + *rules |= BIT(TRIGGER_NETDEV_LINK_2500); 181 + if (ext & BCM8489X_LED_SRCX_5000) 182 + *rules |= BIT(TRIGGER_NETDEV_LINK_5000); 183 + if (low & BCM8489X_LED_SRC_10G) 184 + *rules |= BIT(TRIGGER_NETDEV_LINK_10000); 185 + if (low & BCM8489X_LED_SRC_RX) 186 + *rules |= BIT(TRIGGER_NETDEV_RX); 187 + if (low & BCM8489X_LED_SRC_TX) 188 + *rules |= BIT(TRIGGER_NETDEV_TX); 189 + 190 + return 0; 97 191 } 98 192 99 193 static int bcm84881_probe(struct phy_device *phydev) ··· 438 290 .config_aneg = bcm84881_config_aneg, 439 291 .aneg_done = bcm84881_aneg_done, 440 292 .read_status = bcm84881_read_status, 293 + .led_brightness_set = bcm8489x_led_brightness_set, 294 + .led_hw_is_supported = bcm8489x_led_hw_is_supported, 295 + .led_hw_control_set = bcm8489x_led_hw_control_set, 296 + .led_hw_control_get = bcm8489x_led_hw_control_get, 441 297 }, { 442 298 PHY_ID_MATCH_MODEL(0x359050a0), 443 299 .name = "Broadcom BCM84892", ··· 452 300 .config_aneg = bcm84881_config_aneg, 453 301 .aneg_done = bcm84881_aneg_done, 454 302 .read_status = bcm84881_read_status, 303 + .led_brightness_set = bcm8489x_led_brightness_set, 304 + .led_hw_is_supported = bcm8489x_led_hw_is_supported, 305 + .led_hw_control_set = bcm8489x_led_hw_control_set, 306 + .led_hw_control_get = bcm8489x_led_hw_control_get, 455 307 }, 456 308 }; 457 309