[RFC PATCH wireless-next 16/17] wifi: bcm4323x: add LED and WPS button support
From: Ragul Balaji
Date: Mon Oct 26 2026 - 01:47:30 EST
Add the LED and WPS button to the device driver.
Currently the GPIOs are hardcoded to values in specific devices.
Signed-off-by: Ragul Balaji <ragulbaby@xxxxxxxxx>
Signed-off-by: Ho Jie Feng <hjf3108@xxxxxxxxx>
---
.../brcm80211/bcm4323x/bcm4323x_led.c | 221 ++++++++++++++++++
1 file changed, 221 insertions(+)
create mode 100644 drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_led.c
diff --git a/drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_led.c b/drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_led.c
new file mode 100644
index 000000000..8d2aa459a
--- /dev/null
+++ b/drivers/net/wireless/broadcom/brcm80211/bcm4323x/bcm4323x_led.c
@@ -0,0 +1,221 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bits.h>
+#include <linux/input.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/leds.h>
+#include <linux/usb/input.h>
+
+#include "bcm4323x.h"
+
+/* Power/Activity LED */
+#define BCM4323X_LED_GPIO 1
+#define BCM4323X_LED_BLINK_MS 500
+/* TODO: This is Belkin F9L1101 specific*/
+#define BCM4323X_WPS_GREEN_GPIO 2
+#define BCM4323X_WPS_RED_GPIO 3
+/* TODO: this is Belkin F9L1101 and WNA3100 specific */
+#define BCM4323X_WPS_BUTTON_GPIO 10
+#define BCM4323X_WPS_BUTTON_POLL_MS 200
+
+static const struct ieee80211_tpt_blink bcm4323x_tpt_blink[] = {
+ { .throughput = 0 * 1024, .blink_time = 334 },
+ { .throughput = 1 * 1024, .blink_time = 260 },
+ { .throughput = 5 * 1024, .blink_time = 220 },
+ { .throughput = 10 * 1024, .blink_time = 190 },
+ { .throughput = 20 * 1024, .blink_time = 170 },
+ { .throughput = 50 * 1024, .blink_time = 150 },
+ { .throughput = 70 * 1024, .blink_time = 130 },
+ { .throughput = 100 * 1024, .blink_time = 110 },
+ { .throughput = 200 * 1024, .blink_time = 80 },
+ { .throughput = 300 * 1024, .blink_time = 50 },
+};
+
+static int bcm4323x_led_blink(struct bcm4323x *dev, unsigned long on_ms, unsigned long off_ms)
+{
+ int ret = bcm4323x_rpc_send_u32(dev, BCM4323X_RPC_PROC_LED_BLINK, BCM4323X_LED_GPIO, on_ms,
+ off_ms);
+
+ return ret == -ENODEV ? 0 : ret;
+}
+
+static int bcm4323x_led_gpio_out(struct bcm4323x *dev, u32 mask, u32 value)
+{
+ int ret = bcm4323x_rpc_send_u32(dev, BCM4323X_RPC_PROC_GPIO_OUT, mask, value, 1);
+
+ return ret == -ENODEV ? 0 : ret;
+}
+
+static int bcm4323x_led_set(struct bcm4323x *dev, bool on)
+{
+ int ret;
+
+ ret = bcm4323x_led_blink(dev, 0, 0);
+ if (ret)
+ return ret;
+ return bcm4323x_led_gpio_out(dev, BIT(BCM4323X_LED_GPIO),
+ on ? BIT(BCM4323X_LED_GPIO) : 0);
+}
+
+static int bcm4323x_led_brightness_set(struct led_classdev *led, enum led_brightness brightness)
+{
+ return bcm4323x_led_set(container_of(led, struct bcm4323x, led), brightness);
+}
+
+static int bcm4323x_led_blink_set(struct led_classdev *led, unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct bcm4323x *dev = container_of(led, struct bcm4323x, led);
+
+ if (!*delay_on && !*delay_off)
+ *delay_on = *delay_off = BCM4323X_LED_BLINK_MS;
+ if (!*delay_on || !*delay_off)
+ return bcm4323x_led_set(dev, *delay_on);
+ return bcm4323x_led_blink(dev, *delay_on, *delay_off);
+}
+
+#if IS_REACHABLE(CONFIG_LEDS_CLASS_MULTICOLOR)
+static int bcm4323x_wps_led_brightness_set(struct led_classdev *led,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc = lcdev_to_mccdev(led);
+ struct bcm4323x *dev = container_of(mc, struct bcm4323x, wps_led);
+ u32 value = 0;
+ unsigned int i;
+
+ led_mc_calc_color_components(mc, brightness);
+ for (i = 0; i < mc->num_colors; i++) {
+ if (mc->subled_info[i].brightness)
+ value |= BIT(mc->subled_info[i].channel);
+ }
+ return bcm4323x_led_gpio_out(dev, BIT(BCM4323X_WPS_GREEN_GPIO) | BIT(BCM4323X_WPS_RED_GPIO),
+ value);
+}
+
+/* F9L1101 v1 specific */
+static void bcm4323x_wps_led_register(struct bcm4323x *dev)
+{
+ struct led_classdev *led = &dev->wps_led.led_cdev;
+ int ret;
+
+ if (!dev->chip->has_wps_led)
+ return;
+ dev->wps_subleds[0] = (struct mc_subled) {
+ .color_index = LED_COLOR_ID_GREEN,
+ .intensity = 1,
+ .max_intensity = 1,
+ .channel = BCM4323X_WPS_GREEN_GPIO,
+ };
+ dev->wps_subleds[1] = (struct mc_subled) {
+ .color_index = LED_COLOR_ID_RED,
+ .intensity = 1,
+ .max_intensity = 1,
+ .channel = BCM4323X_WPS_RED_GPIO,
+ };
+ dev->wps_led.subled_info = dev->wps_subleds;
+ dev->wps_led.num_colors = ARRAY_SIZE(dev->wps_subleds);
+ snprintf(dev->wps_led_name, sizeof(dev->wps_led_name), "bcm4323x-%s:multicolor:wps",
+ wiphy_name(dev->hw->wiphy));
+ led->name = dev->wps_led_name;
+ led->max_brightness = 1;
+ led->brightness_set_blocking = bcm4323x_wps_led_brightness_set;
+ ret = bcm4323x_wps_led_brightness_set(led, LED_OFF);
+ if (!ret)
+ ret = led_classdev_multicolor_register(&dev->intf->dev, &dev->wps_led);
+ if (ret) {
+ led->dev = NULL;
+ wiphy_warn(dev->hw->wiphy, "cannot register the WPS LED: %d\n", ret);
+ }
+}
+
+static void bcm4323x_wps_led_unregister(struct bcm4323x *dev)
+{
+ if (dev->wps_led.led_cdev.dev)
+ led_classdev_multicolor_unregister(&dev->wps_led);
+}
+#else
+static void bcm4323x_wps_led_register(struct bcm4323x *dev) {}
+static void bcm4323x_wps_led_unregister(struct bcm4323x *dev) {}
+#endif
+
+static void bcm4323x_wps_button_poll(struct input_dev *input)
+{
+ struct bcm4323x *dev = input_get_drvdata(input);
+ __le32 value;
+
+ if (bcm4323x_rpc_call_u32(dev, BCM4323X_RPC_PROC_REG_READ, &value,
+ D11REGOFFS(psm_gpio_in), sizeof(u16)))
+ return;
+ input_report_key(input, KEY_WPS_BUTTON,
+ !(le32_to_cpu(value) & BIT(BCM4323X_WPS_BUTTON_GPIO)));
+ input_sync(input);
+}
+
+static void bcm4323x_wps_button_register(struct bcm4323x *dev)
+{
+ struct input_dev *input;
+ int ret;
+
+ if (!dev->chip->has_wps_button)
+ return;
+ input = input_allocate_device();
+ if (!input)
+ return;
+ input->name = "BCM4323x WPS button";
+ snprintf(dev->wps_button_phys, sizeof(dev->wps_button_phys), "ieee80211/%s/input0",
+ wiphy_name(dev->hw->wiphy));
+ input->phys = dev->wps_button_phys;
+ usb_to_input_id(dev->udev, &input->id);
+ input->dev.parent = &dev->hw->wiphy->dev;
+ input_set_capability(input, EV_KEY, KEY_WPS_BUTTON);
+ input_set_drvdata(input, dev);
+ ret = input_setup_polling(input, bcm4323x_wps_button_poll);
+ if (!ret) {
+ input_set_poll_interval(input, BCM4323X_WPS_BUTTON_POLL_MS);
+ ret = input_register_device(input);
+ }
+ if (ret) {
+ input_free_device(input);
+ wiphy_warn(dev->hw->wiphy, "cannot register the WPS button: %d\n", ret);
+ return;
+ }
+ dev->wps_button = input;
+}
+
+void bcm4323x_led_init(struct bcm4323x *dev)
+{
+ dev->led.default_trigger =
+ ieee80211_create_tpt_led_trigger(dev->hw, IEEE80211_TPT_LEDTRIG_FL_RADIO,
+ bcm4323x_tpt_blink,
+ ARRAY_SIZE(bcm4323x_tpt_blink));
+}
+
+void bcm4323x_led_register(struct bcm4323x *dev)
+{
+ struct wiphy *wiphy = dev->hw->wiphy;
+ int ret;
+
+ snprintf(dev->led_name, sizeof(dev->led_name), "bcm4323x-%s:radio", wiphy_name(wiphy));
+ dev->led.name = dev->led_name;
+ dev->led.max_brightness = 1;
+ dev->led.brightness_set_blocking = bcm4323x_led_brightness_set;
+ dev->led.blink_set = bcm4323x_led_blink_set;
+ ret = bcm4323x_led_set(dev, false);
+ if (!ret)
+ ret = led_classdev_register(&dev->intf->dev, &dev->led);
+ if (ret) {
+ dev->led.dev = NULL;
+ wiphy_warn(wiphy, "cannot register the LED: %d\n", ret);
+ }
+ bcm4323x_wps_led_register(dev);
+ bcm4323x_wps_button_register(dev);
+}
+
+void bcm4323x_led_unregister(struct bcm4323x *dev)
+{
+ if (dev->wps_button)
+ input_unregister_device(dev->wps_button);
+ bcm4323x_wps_led_unregister(dev);
+ if (dev->led.dev)
+ led_classdev_unregister(&dev->led);
+}
--
2.53.0