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>master
parent
9b79da5d69
commit
7eaff1eff0
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue