From: mike.marciniszyn@gmail.com
To: Lee Jones <lee@kernel.org>, Pavel Machek <pavel@kernel.org>,
Alexander Duyck <alexanderduyck@fb.com>,
Jakub Kicinski <kuba@kernel.org>,
kernel-team@meta.com, Andrew Lunn <andrew+netdev@lunn.ch>,
"David S. Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
Paolo Abeni <pabeni@redhat.com>,
Russell King <linux@armlinux.org.uk>,
Daniel Golle <daniel@makrotopia.org>, Kees Cook <kees@kernel.org>,
Simon Horman <horms@kernel.org>,
Dimitri Daskalakis <dimitri.daskalakis1@gmail.com>,
Jacob Keller <jacob.e.keller@intel.com>,
Lee Trager <lee@trager.us>,
Mohsin Bashir <mohsin.bashr@gmail.com>,
Alok Tiwari <alok.a.tiwari@oracle.com>,
Chengfeng Ye <dg573847474@gmail.com>,
Andy Shevchenko <andriy.shevchenko@linux.intel.com>,
Andrew Lunn <andrew@lunn.ch>
Cc: mike.marciniszyn@gmail.com, linux-kernel@vger.kernel.org,
linux-leds@vger.kernel.org, netdev@vger.kernel.org
Subject: [PATCH net-next 3/3] net: eth: fbnic: Add led support
Date: Wed, 20 May 2026 16:03:37 -0400 [thread overview]
Message-ID: <20260520200337.204431-4-mike.marciniszyn@gmail.com> (raw)
In-Reply-To: <20260520200337.204431-1-mike.marciniszyn@gmail.com>
From: "Mike Marciniszyn (Meta)" <mike.marciniszyn@gmail.com>
This patch adds the LED support for fbnic and adds the
user /sys/class/leds interfaces based on CONFIG_FBNIC_LEDS.
Signed-off-by: Mike Marciniszyn (Meta) <mike.marciniszyn@gmail.com>
---
drivers/net/ethernet/meta/Kconfig | 8 +
drivers/net/ethernet/meta/fbnic/Makefile | 2 +
drivers/net/ethernet/meta/fbnic/fbnic.h | 13 +
drivers/net/ethernet/meta/fbnic/fbnic_csr.h | 15 ++
.../net/ethernet/meta/fbnic/fbnic_devlink.c | 4 +
.../net/ethernet/meta/fbnic/fbnic_ethtool.c | 59 +++++
drivers/net/ethernet/meta/fbnic/fbnic_led.c | 228 +++++++++++++++++
drivers/net/ethernet/meta/fbnic/fbnic_mac.c | 239 ++++++++++++++++++
drivers/net/ethernet/meta/fbnic/fbnic_mac.h | 62 +++++
drivers/net/ethernet/meta/fbnic/fbnic_pci.c | 8 +
.../net/ethernet/meta/fbnic/fbnic_phylink.c | 2 +
11 files changed, 640 insertions(+)
create mode 100644 drivers/net/ethernet/meta/fbnic/fbnic_led.c
diff --git a/drivers/net/ethernet/meta/Kconfig b/drivers/net/ethernet/meta/Kconfig
index ca5c7ac2a5bc..a8ca2aaf798a 100644
--- a/drivers/net/ethernet/meta/Kconfig
+++ b/drivers/net/ethernet/meta/Kconfig
@@ -35,4 +35,12 @@ config FBNIC
To compile this driver as a module, choose M here. The module
will be called fbnic. MSI-X interrupt support is required.
+config FBNIC_LEDS
+ def_bool LEDS_TRIGGER_NETDEV
+ depends on FBNIC && LEDS_CLASS
+ depends on LEDS_CLASS=y || FBNIC=m
+ help
+ Optional support for controlling the NIC LEDs with the netdev
+ LED trigger.
+
endif # NET_VENDOR_META
diff --git a/drivers/net/ethernet/meta/fbnic/Makefile b/drivers/net/ethernet/meta/fbnic/Makefile
index 72c41af65364..7f3b70a1f444 100644
--- a/drivers/net/ethernet/meta/fbnic/Makefile
+++ b/drivers/net/ethernet/meta/fbnic/Makefile
@@ -26,3 +26,5 @@ fbnic-y := fbnic_csr.o \
fbnic_tlv.o \
fbnic_txrx.o \
# End of objects
+
+fbnic-$(CONFIG_FBNIC_LEDS) += fbnic_led.o
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic.h b/drivers/net/ethernet/meta/fbnic/fbnic.h
index d0715695c43e..cf98beca53d3 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic.h
+++ b/drivers/net/ethernet/meta/fbnic/fbnic.h
@@ -97,6 +97,11 @@ struct fbnic_dev {
struct fbnic_fw_log fw_log;
+ /* Used to synchronize updates to LED state and CSR */
+ struct mutex led_mutex;
+ bool led_link_up;
+ struct fbnic_led_cdev leds[FBNIC_NUM_LEDS];
+
/* MDIO bus for PHYs */
struct mii_bus *mdio_bus;
@@ -243,6 +248,14 @@ void fbnic_dbg_exit(void);
void fbnic_rpc_reset_valid_entries(struct fbnic_dev *fbd);
+#if IS_ENABLED(CONFIG_FBNIC_LEDS)
+int fbnic_led_init(struct fbnic_dev *fbd);
+void fbnic_led_exit(struct fbnic_dev *fbd);
+#else
+static inline int fbnic_led_init(struct fbnic_dev *fbd) { return 0; }
+static inline void fbnic_led_exit(struct fbnic_dev *fbd) {}
+#endif
+
int fbnic_mdiobus_create(struct fbnic_dev *fbd);
void fbnic_csr_get_regs(struct fbnic_dev *fbd, u32 *data, u32 *regs_version);
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_csr.h b/drivers/net/ethernet/meta/fbnic/fbnic_csr.h
index 64b958df7774..6f4ae091652b 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_csr.h
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_csr.h
@@ -857,6 +857,21 @@ enum {
#define FBNIC_SIG_PCS_INTR_LINK_DOWN CSR_BIT(1)
#define FBNIC_SIG_PCS_INTR_LINK_UP CSR_BIT(0)
#define FBNIC_SIG_PCS_INTR_MASK 0x11816 /* 0x46058 */
+#define FBNIC_SIG_LED 0x11820 /* 0x46080 */
+#define FBNIC_SIG_LED_OVERRIDE_EN CSR_GENMASK(2, 0)
+#define FBNIC_SIG_LED_OVERRIDE_VAL CSR_GENMASK(6, 4)
+
+enum {
+ FBNIC_SIG_LED_OVERRIDE_ACTIVITY = 0x1,
+ FBNIC_SIG_LED_OVERRIDE_AMBER = 0x2,
+ FBNIC_SIG_LED_OVERRIDE_BLUE = 0x4,
+};
+
+#define FBNIC_SIG_LED_BLINK_RATE_MASK CSR_GENMASK(11, 8)
+enum {
+ FBNIC_SIG_LED_BLINK_RATE_5HZ = 0xf,
+};
+
#define FBNIC_CSR_END_SIG 0x1184e /* CSR section delimiter */
#define FBNIC_CSR_START_MAC_STAT 0x11a00
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_devlink.c b/drivers/net/ethernet/meta/fbnic/fbnic_devlink.c
index 546e1c12d287..8aabec5143cd 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_devlink.c
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_devlink.c
@@ -622,6 +622,8 @@ void fbnic_devlink_free(struct fbnic_dev *fbd)
{
struct devlink *devlink = priv_to_devlink(fbd);
+ mutex_destroy(&fbd->led_mutex);
+
devlink_free(devlink);
}
@@ -651,6 +653,8 @@ struct fbnic_dev *fbnic_devlink_alloc(struct pci_dev *pdev)
fbd->mac_addr_boundary = FBNIC_RPC_TCAM_MACDA_DEFAULT_BOUNDARY;
+ mutex_init(&fbd->led_mutex);
+
return fbd;
}
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c
index f14de2366854..3904081e24f9 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c
@@ -2005,6 +2005,64 @@ fbnic_get_rmon_stats(struct net_device *netdev,
*ranges = fbnic_rmon_ranges;
}
+/**
+ * fbnic_set_phys_id - Used to strobe the MAC LEDs in a recognizable pattern
+ * @netdev: Interface/port to strobe the LEDs for
+ * @phys_id_state: State requested by the call
+ *
+ * This function can really be broken down into two parts. There are the
+ * ACTIVE/INACTIVE states which really are meant to be defining the start
+ * and stop of the LED strobing. There is also the ON/OFF states which are
+ * used to provide us with a way of telling us that we should be turning
+ * the LED on and/or off.
+ *
+ * We translate these calls and pass them off to the MAC layer. They will
+ * be used to initialize a strobe, then on and off will be used to cycle
+ * between the patterns, and finally we will restore the original LED state.
+ *
+ * We will return 2 when we are requested to go active. This will tell the
+ * call that it will need to call back to turn on/off the LED twice every
+ * second.
+ *
+ * Return: blink in half second intervals on success, negative value on failure
+ */
+static int fbnic_set_phys_id(struct net_device *netdev,
+ enum ethtool_phys_id_state phys_id_state)
+{
+ struct fbnic_net *fbn = netdev_priv(netdev);
+ struct fbnic_dev *fbd = fbn->fbd;
+ const struct fbnic_mac *mac;
+ int cycle_interval = 0;
+ int state;
+
+ mac = fbd->mac;
+
+ if (!mac || !mac->set_led_state)
+ return -EOPNOTSUPP;
+
+ switch (phys_id_state) {
+ case ETHTOOL_ID_ACTIVE:
+ state = FBNIC_LED_STROBE_INIT;
+ cycle_interval = 2;
+ break;
+ case ETHTOOL_ID_INACTIVE:
+ state = FBNIC_LED_RESTORE;
+ break;
+ case ETHTOOL_ID_ON:
+ state = FBNIC_LED_ON;
+ break;
+ case ETHTOOL_ID_OFF:
+ state = FBNIC_LED_OFF;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ mac->set_led_state(fbd, state);
+
+ return cycle_interval;
+}
+
static void fbnic_get_link_ext_stats(struct net_device *netdev,
struct ethtool_link_ext_stats *stats)
{
@@ -2062,6 +2120,7 @@ static const struct ethtool_ops fbnic_ethtool_ops = {
.get_eth_mac_stats = fbnic_get_eth_mac_stats,
.get_eth_ctrl_stats = fbnic_get_eth_ctrl_stats,
.get_rmon_stats = fbnic_get_rmon_stats,
+ .set_phys_id = fbnic_set_phys_id,
};
void fbnic_set_ethtool_ops(struct net_device *dev)
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_led.c b/drivers/net/ethernet/meta/fbnic/fbnic_led.c
new file mode 100644
index 000000000000..44d0dc1be448
--- /dev/null
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_led.c
@@ -0,0 +1,228 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) Meta Platforms, Inc. and affiliates. */
+#include <linux/netdevice.h>
+#include <linux/pci.h>
+#include <uapi/linux/uleds.h>
+
+#include "fbnic.h"
+#include "fbnic_csr.h"
+#include "fbnic_mac.h"
+#include "fbnic_netdev.h"
+
+#define led_classdev_to_fbnic_led(cdev) \
+ container_of(cdev, struct fbnic_led_cdev, led)
+
+static const char *fbnic_led_names[FBNIC_NUM_LEDS] = {
+ "activity",
+ "link-amber",
+ "link-blue"
+};
+
+static void fbnic_led_get_name(struct fbnic_dev *fbd,
+ int led_idx, char *name)
+{
+ snprintf(name, LED_MAX_NAME_SIZE, "%s-%s",
+ fbd->netdev->name, fbnic_led_names[led_idx]);
+}
+
+static int fbnic_led_get_idx(struct fbnic_dev *fbd,
+ struct fbnic_led_cdev *ldev)
+{
+ return ldev - &fbd->leds[0];
+}
+
+static struct fbnic_dev *led_cdev_to_fbd(struct led_classdev *led_cdev)
+{
+ struct fbnic_led_cdev *ldev = led_classdev_to_fbnic_led(led_cdev);
+
+ return ldev->fbd;
+}
+
+static u32 fbnic_led_get_supported_modes(struct fbnic_dev *fbd, int led_idx)
+{
+ u32 modes = 0;
+
+ if (led_idx == FBNIC_LED_ACTIVITY) {
+ modes = BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX);
+
+ return modes;
+ }
+
+ /* Set modes for link LEDs - note 40G not supported */
+ modes = BIT(TRIGGER_NETDEV_LINK) |
+ BIT(TRIGGER_NETDEV_LINK_25000) |
+ BIT(TRIGGER_NETDEV_LINK_50000) |
+ BIT(TRIGGER_NETDEV_LINK_100000);
+
+ return modes;
+}
+
+static int fbnic_led_hw_ctl_set(struct led_classdev *led_cdev,
+ unsigned long flags)
+{
+ struct fbnic_led_cdev *ldev = led_classdev_to_fbnic_led(led_cdev);
+ struct fbnic_dev *fbd = led_cdev_to_fbd(led_cdev);
+ int led_idx = fbnic_led_get_idx(fbd, ldev);
+ u32 supported_modes;
+
+ supported_modes = fbnic_led_get_supported_modes(fbd, led_idx);
+
+ if (flags & ~supported_modes)
+ return -EINVAL;
+
+ /* Turn on activity LED only when both the TX and RX flags are set. */
+ if (led_idx == FBNIC_LED_ACTIVITY && (flags & supported_modes) &&
+ flags != supported_modes) {
+ dev_warn(fbd->dev,
+ "Invalid activity LED mode(s): 0x%lx\n", flags);
+ return -EINVAL;
+ }
+
+ /* Preserve the configured modes for restoration after LED strobe */
+ mutex_lock(&fbd->led_mutex);
+ ldev->enabled_modes = flags;
+
+ if (ldev->strobe_mode)
+ dev_warn(fbd->dev,
+ "LED config takes effect after strobe completes.\n");
+
+ fbnic_led_update_csr(fbd);
+ mutex_unlock(&fbd->led_mutex);
+
+ return 0;
+}
+
+static int fbnic_led_hw_ctl_get(struct led_classdev *led_cdev,
+ unsigned long *flags)
+{
+ struct fbnic_led_cdev *ldev = led_classdev_to_fbnic_led(led_cdev);
+ struct fbnic_dev *fbd = led_cdev_to_fbd(led_cdev);
+
+ mutex_lock(&fbd->led_mutex);
+
+ *flags = ldev->enabled_modes;
+
+ mutex_unlock(&fbd->led_mutex);
+
+ return 0;
+}
+
+static struct device *fbnic_led_hw_ctl_get_device(struct led_classdev *led_cdev)
+{
+ struct fbnic_dev *fbd = led_cdev_to_fbd(led_cdev);
+ struct net_device *netdev = fbd->netdev;
+
+ return &netdev->dev;
+}
+
+static int fbnic_led_hw_ctl_is_supported(struct led_classdev *led_cdev,
+ unsigned long flags)
+{
+ struct fbnic_led_cdev *ldev = led_classdev_to_fbnic_led(led_cdev);
+ struct fbnic_dev *fbd = led_cdev_to_fbd(led_cdev);
+ int led_idx = fbnic_led_get_idx(fbd, ldev);
+ u32 modes;
+
+ modes = fbnic_led_get_supported_modes(fbd, led_idx);
+
+ if (led_idx == FBNIC_LED_ACTIVITY && (flags & modes) && flags != modes)
+ return -EOPNOTSUPP;
+ if (flags & ~modes)
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+static int fbnic_led_brightness_set_blocking(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct fbnic_led_cdev *ldev = led_classdev_to_fbnic_led(led_cdev);
+ struct fbnic_dev *fbd = led_cdev_to_fbd(led_cdev);
+ int led_idx = fbnic_led_get_idx(fbd, ldev);
+
+ mutex_lock(&fbd->led_mutex);
+ if (!brightness) {
+ fbd->leds[led_idx].enabled_modes = 0;
+ fbd->leds[led_idx].strobe_mode = 0;
+ } else {
+ u32 mode;
+
+ switch (led_idx) {
+ case FBNIC_LED_ACTIVITY:
+ fbd->leds[led_idx].enabled_modes =
+ BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX);
+ break;
+ default:
+ mode = fbnic_led_get_link_speed_mode(fbd);
+ fbd->leds[led_idx].enabled_modes = mode;
+ break;
+ }
+ }
+
+ fbnic_led_update_csr(fbd);
+
+ mutex_unlock(&fbd->led_mutex);
+
+ return 0;
+}
+
+static int fbnic_led_setup(struct fbnic_dev *fbd, int led_idx)
+{
+ struct pci_dev *pdev = to_pci_dev(fbd->dev);
+ struct led_classdev *led_cdev;
+
+ fbd->leds[led_idx].fbd = fbd;
+ led_cdev = &fbd->leds[led_idx].led;
+ led_cdev->name = fbd->leds[led_idx].name;
+ fbnic_led_get_name(fbd, led_idx, fbd->leds[led_idx].name);
+ led_cdev->max_brightness = 1;
+ led_cdev->hw_control_trigger = "netdev";
+ led_cdev->flags |= LED_RETAIN_AT_SHUTDOWN;
+ led_cdev->hw_control_set = fbnic_led_hw_ctl_set;
+ led_cdev->hw_control_get = fbnic_led_hw_ctl_get;
+ led_cdev->hw_control_get_device = fbnic_led_hw_ctl_get_device;
+ led_cdev->hw_control_is_supported = fbnic_led_hw_ctl_is_supported;
+ led_cdev->brightness_set_blocking = fbnic_led_brightness_set_blocking;
+
+ return led_classdev_register(&pdev->dev, led_cdev);
+}
+
+/**
+ * fbnic_led_init - initialize the linux led interface for fbnic
+ *
+ * @fbd: FBNIC device structure
+ *
+ * Return: zero on success, negative value on failure
+ *
+ * This function creates three led devices for the fbnic device. One for the
+ * activity LED and two for the color LEDs. The successful initialization
+ * creates <netdev>-activity, <netdev>-link-amber and <netdev>-link-blue
+ * under /sys/class/leds/
+ */
+int fbnic_led_init(struct fbnic_dev *fbd)
+{
+ int i, ret;
+
+ for (i = 0; i < FBNIC_NUM_LEDS; i++) {
+ ret = fbnic_led_setup(fbd, i);
+
+ if (ret)
+ goto err_led_setup;
+ }
+
+ return 0;
+
+err_led_setup:
+ while (i--)
+ led_classdev_unregister(&fbd->leds[i].led);
+
+ return ret;
+}
+
+void fbnic_led_exit(struct fbnic_dev *fbd)
+{
+ int i;
+
+ for (i = 0; i < FBNIC_NUM_LEDS; i++)
+ led_classdev_unregister(&fbd->leds[i].led);
+}
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_mac.c b/drivers/net/ethernet/meta/fbnic/fbnic_mac.c
index 53b7a938b4c2..c6b1130f9159 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_mac.c
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_mac.c
@@ -623,6 +623,53 @@ static bool fbnic_pmd_update_state(struct fbnic_dev *fbd, bool signal_detect)
return false;
}
+u32 fbnic_get_link_speed(u8 link_speed)
+{
+ switch (link_speed) {
+ case FBNIC_FW_LINK_MODE_25CR:
+ return SPEED_25000;
+ case FBNIC_FW_LINK_MODE_50CR2:
+ case FBNIC_FW_LINK_MODE_50CR:
+ return SPEED_50000;
+ case FBNIC_FW_LINK_MODE_100CR2:
+ return SPEED_100000;
+ default:
+ return 0;
+ }
+}
+
+static void fbnic_set_led_state(struct fbnic_dev *fbd, int state)
+{
+ mutex_lock(&fbd->led_mutex);
+
+ /* alternating amber,blue IDs device every half second */
+ switch (state) {
+ case FBNIC_LED_OFF: /* amber on, blue off */
+ fbd->leds[FBNIC_LED_LINK_AMBER].strobe_mode = FBNIC_STROBE_ON;
+ fbd->leds[FBNIC_LED_LINK_BLUE].strobe_mode = FBNIC_STROBE_OFF;
+ break;
+ case FBNIC_LED_ON: /* amber off, blue on */
+ fbd->leds[FBNIC_LED_LINK_AMBER].strobe_mode = FBNIC_STROBE_OFF;
+ fbd->leds[FBNIC_LED_LINK_BLUE].strobe_mode = FBNIC_STROBE_ON;
+ break;
+ case FBNIC_LED_RESTORE:
+ fbd->leds[FBNIC_LED_LINK_AMBER].strobe_mode =
+ FBNIC_STROBE_DISABLED;
+ fbd->leds[FBNIC_LED_LINK_BLUE].strobe_mode =
+ FBNIC_STROBE_DISABLED;
+ break;
+ case FBNIC_LED_STROBE_INIT: /* a no-op */
+ /* Initialization is a no-op; LED toggling happens on ON/OFF */
+ goto out_unlock;
+ default:
+ goto out_unlock;
+ }
+
+ fbnic_led_update_csr(fbd);
+out_unlock:
+ mutex_unlock(&fbd->led_mutex);
+}
+
static bool fbnic_mac_get_link(struct fbnic_dev *fbd, u8 aui, u8 fec)
{
bool link;
@@ -967,6 +1014,7 @@ static const struct fbnic_mac fbnic_mac_asic = {
.link_down = fbnic_mac_link_down_asic,
.link_up = fbnic_mac_link_up_asic,
.get_sensor = fbnic_mac_get_sensor_asic,
+ .set_led_state = fbnic_set_led_state,
};
/**
@@ -987,6 +1035,197 @@ int fbnic_mac_init(struct fbnic_dev *fbd)
return 0;
}
+/**
+ * __fbnic_led_get_link_speed_mode - Get link speed mode
+ * @curr_speed: ethtool speed
+ *
+ * Return: netdev trigger bit on success, 0 if link speed is not supported
+ *
+ * Convert ethtool speed to mode bit using parameter
+ */
+static u32
+__fbnic_led_get_link_speed_mode(u32 curr_speed)
+{
+ switch (curr_speed) {
+ case SPEED_25000:
+ return BIT(TRIGGER_NETDEV_LINK_25000);
+ case SPEED_50000:
+ return BIT(TRIGGER_NETDEV_LINK_50000);
+ case SPEED_100000:
+ return BIT(TRIGGER_NETDEV_LINK_100000);
+ default:
+ return 0;
+ }
+}
+
+/**
+ * fbnic_led_get_link_speed_mode - Get link speed mode
+ * @fbd: FBNIC Device pointer
+ *
+ * Return: Link speed mode on success, 0 if link speed is not supported
+ *
+ * For the current link speed, this function returns trigger mode bit
+ */
+u32 fbnic_led_get_link_speed_mode(struct fbnic_dev *fbd)
+{
+ u32 curr_speed = fbnic_get_link_speed(fbd->fw_cap.link_speed);
+
+ return __fbnic_led_get_link_speed_mode(curr_speed);
+}
+
+/**
+ * fbnic_led_set_modes_ocp - Set LED state based on OCP link speed
+ * @fbd: FBNIC Device pointer
+ *
+ * Set LED state based on OCP link speed. This function sets up the
+ * LED state based on the current link speed while accounting for the
+ * OCP specs. Called from fbnic_led_link_up() on link state changes.
+ */
+void fbnic_led_set_modes_ocp(struct fbnic_dev *fbd)
+{
+ u32 speed_mode, curr_speed, max_speed;
+
+ lockdep_assert_held(&fbd->led_mutex);
+
+ /* Turn on the activity LED independent of the link speed */
+ fbd->leds[FBNIC_LED_ACTIVITY].enabled_modes = BIT(TRIGGER_NETDEV_TX) |
+ BIT(TRIGGER_NETDEV_RX);
+ fbd->leds[FBNIC_LED_LINK_BLUE].enabled_modes = 0;
+ fbd->leds[FBNIC_LED_LINK_AMBER].enabled_modes = 0;
+
+ curr_speed = fbnic_get_link_speed(fbd->fw_cap.link_speed);
+ if (!curr_speed) {
+ dev_warn(fbd->dev,
+ "Unsupported link speed detected.\n");
+ goto bail;
+ }
+
+ /* can't be zero */
+ speed_mode = __fbnic_led_get_link_speed_mode(curr_speed);
+
+ /* need max_speed for amber/blue leds values */
+ max_speed = fbnic_get_link_speed(fbd->fw_cap.max_link_speed);
+ if (!max_speed) {
+ dev_warn(fbd->dev,
+ "Unsupported max link speed detected.\n");
+ goto bail;
+ }
+
+ /* Set the LED modes according to OCP specs */
+ if (curr_speed < max_speed)
+ fbd->leds[FBNIC_LED_LINK_AMBER].enabled_modes = speed_mode;
+ else
+ fbd->leds[FBNIC_LED_LINK_BLUE].enabled_modes = speed_mode;
+
+bail:
+ /* sync csr with above changes */
+ fbnic_led_update_csr(fbd);
+}
+
+static u32 fbnic_led_get_current_mode(struct fbnic_dev *fbd)
+{
+ u32 modes = BIT(TRIGGER_NETDEV_RX) | BIT(TRIGGER_NETDEV_TX);
+
+ if (fbd->led_link_up) {
+ modes |= BIT(TRIGGER_NETDEV_LINK);
+ modes |= fbnic_led_get_link_speed_mode(fbd);
+ }
+
+ return modes;
+}
+
+/**
+ * fbnic_led_get_state - Get current LED state
+ * @fbd: FBNIC Device pointer
+ * @led_idx: LED index
+ *
+ * Get current LED state. This function returns the current LED state
+ * based on the currently configured enabled modes. If current configured
+ * modes are not set, it returns LED_OFF.
+ */
+static int fbnic_led_get_state(struct fbnic_dev *fbd, int led_idx)
+{
+ u8 strobe_mode = fbd->leds[led_idx].strobe_mode;
+ int led_state = LED_OFF;
+ u32 mask;
+
+ if (strobe_mode == FBNIC_STROBE_ON)
+ return LED_ON;
+ if (strobe_mode == FBNIC_STROBE_OFF)
+ return LED_OFF;
+
+ mask = fbnic_led_get_current_mode(fbd);
+ led_state = (mask & fbd->leds[led_idx].enabled_modes) ?
+ LED_ON : LED_OFF;
+
+ return led_state;
+}
+
+/**
+ * fbnic_led_update_csr - Update LED CSR
+ * @fbd: FBNIC Device pointer
+ *
+ * This function updates the LED CSR. This should be called every time
+ * the link speed changes, or when the enabled modes are changed via
+ * the linux LED API.
+ */
+void fbnic_led_update_csr(struct fbnic_dev *fbd)
+{
+ u32 led_csr = FBNIC_SIG_LED_ACTIVITY_OFF;
+ int led_state;
+
+ lockdep_assert_held(&fbd->led_mutex);
+
+ led_state = fbnic_led_get_state(fbd, FBNIC_LED_ACTIVITY);
+ if (led_state == LED_ON)
+ led_csr = FBNIC_SIG_LED_ACTIVITY_DEFAULT;
+
+ led_state = fbnic_led_get_state(fbd, FBNIC_LED_LINK_AMBER);
+ if (led_state == LED_ON)
+ led_csr |= FBNIC_SIG_LED_AMBER_ON;
+
+ led_state = fbnic_led_get_state(fbd, FBNIC_LED_LINK_BLUE);
+ if (led_state == LED_ON)
+ led_csr |= FBNIC_SIG_LED_BLUE_ON;
+
+ dev_dbg(fbd->dev, "writing led_csr %x\n", led_csr);
+ wr32(fbd, FBNIC_SIG_LED, led_csr);
+}
+
+/**
+ * fbnic_led_link_up - Update LED state on link up
+ * @fbd: FBNIC Device pointer
+ *
+ * Called from phylink mac_link_up callback. Sets the internal link state
+ * and configures LED modes according to OCP specs based on current link speed.
+ */
+void fbnic_led_link_up(struct fbnic_dev *fbd)
+{
+ mutex_lock(&fbd->led_mutex);
+
+ fbd->led_link_up = true;
+ fbnic_led_set_modes_ocp(fbd);
+
+ mutex_unlock(&fbd->led_mutex);
+}
+
+/**
+ * fbnic_led_link_down - Update LED state on link down
+ * @fbd: FBNIC Device pointer
+ *
+ * Called from phylink mac_link_down callback. Clears the internal link state
+ * and updates the LED CSR to reflect link-down condition.
+ */
+void fbnic_led_link_down(struct fbnic_dev *fbd)
+{
+ mutex_lock(&fbd->led_mutex);
+
+ fbd->led_link_up = false;
+ fbnic_led_update_csr(fbd);
+
+ mutex_unlock(&fbd->led_mutex);
+}
+
int fbnic_mac_ps_protect_to_config(struct fbnic_dev *fbd, u16 timeout_ms)
{
u16 old_timeout_ms = fbd->ps_timeout;
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_mac.h b/drivers/net/ethernet/meta/fbnic/fbnic_mac.h
index 10f30e0e8f69..089afdde7b55 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_mac.h
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_mac.h
@@ -5,6 +5,8 @@
#define _FBNIC_MAC_H_
#include <linux/types.h>
+#include <linux/leds.h>
+#include <uapi/linux/uleds.h>
struct fbnic_dev;
@@ -33,6 +35,27 @@ struct fbnic_dev;
FBNIC_MAC_RXB_PS_TO_MS(FIELD_MAX(FBNIC_RXB_PAUSE_STORM_THLD_TIME))
#define FBNIC_MAX_JUMBO_FRAME_SIZE 9742
+#define FBNIC_NUM_LEDS 3
+
+struct fbnic_led_cdev {
+ char name[LED_MAX_NAME_SIZE];
+ struct led_classdev led;
+ struct fbnic_dev *fbd;
+ u32 enabled_modes;
+ u8 strobe_mode;
+};
+
+enum {
+ FBNIC_STROBE_DISABLED = 0,
+ FBNIC_STROBE_ON = 1,
+ FBNIC_STROBE_OFF = 2,
+};
+
+enum {
+ FBNIC_LED_ACTIVITY = 0,
+ FBNIC_LED_LINK_AMBER,
+ FBNIC_LED_LINK_BLUE,
+};
/* States loosely based on section 136.8.11.7.5 of IEEE 802.3-2022 Ethernet
* Standard. These are needed to track the state of the PHY as it has a delay
@@ -58,6 +81,13 @@ enum {
FBNIC_LINK_EVENT_DOWN = 2,
};
+enum {
+ FBNIC_LED_STROBE_INIT,
+ FBNIC_LED_ON,
+ FBNIC_LED_OFF,
+ FBNIC_LED_RESTORE,
+};
+
/* Treat the FEC bits as a bitmask laid out as follows:
* Bit 0: RS Enabled
* Bit 1: BASER(Firecode) Enabled
@@ -91,6 +121,29 @@ enum fbnic_sensor_id {
FBNIC_SENSOR_VOLTAGE, /* Voltage in millivolts */
};
+#define FBNIC_SIG_LED_ACTIVITY_DEFAULT \
+ FIELD_PREP(FBNIC_SIG_LED_BLINK_RATE_MASK, \
+ FBNIC_SIG_LED_BLINK_RATE_5HZ)
+#define FBNIC_SIG_LED_ACTIVITY_ON \
+ FIELD_PREP(FBNIC_SIG_LED_OVERRIDE_EN, \
+ FBNIC_SIG_LED_OVERRIDE_ACTIVITY)
+/* override_en=1 with override_val=0 forces the activity LED off
+ * while preserving the default blink rate setting
+ */
+#define FBNIC_SIG_LED_ACTIVITY_OFF \
+ (FBNIC_SIG_LED_ACTIVITY_DEFAULT | \
+ FBNIC_SIG_LED_ACTIVITY_ON)
+#define FBNIC_SIG_LED_AMBER_ON \
+ (FIELD_PREP(FBNIC_SIG_LED_OVERRIDE_EN, \
+ FBNIC_SIG_LED_OVERRIDE_AMBER) | \
+ FIELD_PREP(FBNIC_SIG_LED_OVERRIDE_VAL, \
+ FBNIC_SIG_LED_OVERRIDE_AMBER))
+#define FBNIC_SIG_LED_BLUE_ON \
+ (FIELD_PREP(FBNIC_SIG_LED_OVERRIDE_EN, \
+ FBNIC_SIG_LED_OVERRIDE_BLUE) | \
+ FIELD_PREP(FBNIC_SIG_LED_OVERRIDE_VAL, \
+ FBNIC_SIG_LED_OVERRIDE_BLUE))
+
/* This structure defines the interface hooks for the MAC. The MAC hooks
* will be configured as a const struct provided with a set of function
* pointers.
@@ -112,6 +165,8 @@ enum fbnic_sensor_id {
* Configure MAC for link down event
* void (*link_up)(struct fbnic_dev *fbd, bool tx_pause, bool rx_pause);
* Configure MAC for link up event;
+ * void (*set_led_state)(struct fbnic_dev *fbd, int state);
+ * Configure MAC for physical identification states
*
*/
struct fbnic_mac {
@@ -139,10 +194,17 @@ struct fbnic_mac {
void (*link_up)(struct fbnic_dev *fbd, bool tx_pause, bool rx_pause);
int (*get_sensor)(struct fbnic_dev *fbd, int id, long *val);
+ void (*set_led_state)(struct fbnic_dev *fbd, int state);
};
int fbnic_mac_init(struct fbnic_dev *fbd);
void fbnic_mac_get_fw_settings(struct fbnic_dev *fbd, u8 *aui, u8 *fec);
+u32 fbnic_led_get_link_speed_mode(struct fbnic_dev *fbd);
+u32 fbnic_get_link_speed(u8 link_speed);
+void fbnic_led_set_modes_ocp(struct fbnic_dev *fbd);
+void fbnic_led_update_csr(struct fbnic_dev *fbd);
+void fbnic_led_link_up(struct fbnic_dev *fbd);
+void fbnic_led_link_down(struct fbnic_dev *fbd);
int fbnic_mac_ps_protect_to_config(struct fbnic_dev *fbd, u16 timeout);
void fbnic_mac_ps_protect_handler(struct fbnic_dev *fbd);
bool fbnic_mac_check_tx_pause(struct fbnic_dev *fbd);
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_pci.c b/drivers/net/ethernet/meta/fbnic/fbnic_pci.c
index 7e85b480203c..06a40081f603 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_pci.c
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_pci.c
@@ -368,9 +368,16 @@ static int fbnic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
dev_err(&pdev->dev, "Netdev registration failed: %d\n", err);
goto ifm_destroy_ptp;
}
+ err = fbnic_led_init(fbd);
+ if (err) {
+ dev_err(&pdev->dev, "led init failed: %d\n", err);
+ goto led_init_fail;
+ }
return 0;
+led_init_fail:
+ fbnic_netdev_unregister(netdev);
ifm_destroy_ptp:
fbnic_ptp_destroy(fbd);
ifm_free_netdev:
@@ -408,6 +415,7 @@ static void fbnic_remove(struct pci_dev *pdev)
if (!fbnic_init_failure(fbd)) {
struct net_device *netdev = fbd->netdev;
+ fbnic_led_exit(fbd);
fbnic_netdev_unregister(netdev);
cancel_delayed_work_sync(&fbd->service_task);
fbnic_ptp_destroy(fbd);
diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_phylink.c b/drivers/net/ethernet/meta/fbnic/fbnic_phylink.c
index 09c5225111be..86fccef901cc 100644
--- a/drivers/net/ethernet/meta/fbnic/fbnic_phylink.c
+++ b/drivers/net/ethernet/meta/fbnic/fbnic_phylink.c
@@ -154,6 +154,7 @@ fbnic_phylink_mac_link_down(struct phylink_config *config, unsigned int mode,
struct fbnic_dev *fbd = fbn->fbd;
fbd->mac->link_down(fbd);
+ fbnic_led_link_down(fbd);
fbn->link_down_events++;
}
@@ -172,6 +173,7 @@ fbnic_phylink_mac_link_up(struct phylink_config *config,
fbnic_config_drop_mode(fbn, tx_pause);
fbd->mac->link_up(fbd, tx_pause, rx_pause);
+ fbnic_led_link_up(fbd);
}
static const struct phylink_mac_ops fbnic_phylink_mac_ops = {
--
2.43.0
next prev parent reply other threads:[~2026-05-20 20:03 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-20 20:03 [PATCH net-next 0/3] dd LED support for fbnic mike.marciniszyn
2026-05-20 20:03 ` [PATCH net-next 1/3] leds: trigger: netdev: Extend speeds up to 100G mike.marciniszyn
2026-05-20 20:16 ` Andrew Lunn
2026-05-20 20:03 ` [PATCH net-next 2/3] net: eth: fbnic: Store max_speed from firmware dialog mike.marciniszyn
2026-05-20 20:03 ` mike.marciniszyn [this message]
2026-05-20 20:37 ` [PATCH net-next 3/3] net: eth: fbnic: Add led support Andrew Lunn
2026-05-21 14:05 ` Mike Marciniszyn
2026-05-21 17:15 ` Andrew Lunn
2026-05-20 20:14 ` [PATCH net-next 0/3] dd LED support for fbnic Andrew Lunn
2026-05-21 11:03 ` Lee Jones
2026-05-21 11:59 ` Andrew Lunn
2026-05-21 13:33 ` Lee Jones
2026-05-21 14:13 ` Mike Marciniszyn
2026-05-21 14:28 ` Andrew Lunn
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260520200337.204431-4-mike.marciniszyn@gmail.com \
--to=mike.marciniszyn@gmail.com \
--cc=alexanderduyck@fb.com \
--cc=alok.a.tiwari@oracle.com \
--cc=andrew+netdev@lunn.ch \
--cc=andrew@lunn.ch \
--cc=andriy.shevchenko@linux.intel.com \
--cc=daniel@makrotopia.org \
--cc=davem@davemloft.net \
--cc=dg573847474@gmail.com \
--cc=dimitri.daskalakis1@gmail.com \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=jacob.e.keller@intel.com \
--cc=kees@kernel.org \
--cc=kernel-team@meta.com \
--cc=kuba@kernel.org \
--cc=lee@kernel.org \
--cc=lee@trager.us \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-leds@vger.kernel.org \
--cc=linux@armlinux.org.uk \
--cc=mohsin.bashr@gmail.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=pavel@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox