wifi: rtw88: Add support for LED blinking
authorBitterblue Smith <rtl8821cerfe2@gmail.com>
Wed, 8 Jan 2025 11:41:23 +0000 (13:41 +0200)
committerPing-Ke Shih <pkshih@realtek.com>
Sun, 12 Jan 2025 02:07:38 +0000 (10:07 +0800)
Register a struct led_classdev with the kernel's LED subsystem and
create a throughput-based trigger for it. Then mac80211 makes the LED
blink.

Tested with Tenda U12 (RTL8812AU), Tenda U9 (RTL8811CU), TP-Link Archer
T2U Nano (RTL8811AU), TP-Link Archer T3U Plus (RTL8812BU), Edimax
EW-7611UCB (RTL8821AU), LM842 (RTL8822CU).

Also tested with devices which don't have LEDs: the laptop's internal
RTL8822CE and a no-name RTL8723DU.

Signed-off-by: Bitterblue Smith <rtl8821cerfe2@gmail.com>
Acked-by: Ping-Ke Shih <pkshih@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
Link: https://patch.msgid.link/6c43451f-ab2f-4e76-ac6e-ff5a18dd981d@gmail.com
drivers/net/wireless/realtek/rtw88/Makefile
drivers/net/wireless/realtek/rtw88/led.c [new file with mode: 0644]
drivers/net/wireless/realtek/rtw88/led.h [new file with mode: 0644]
drivers/net/wireless/realtek/rtw88/main.c
drivers/net/wireless/realtek/rtw88/main.h
drivers/net/wireless/realtek/rtw88/reg.h
drivers/net/wireless/realtek/rtw88/rtw8812a.c
drivers/net/wireless/realtek/rtw88/rtw8821a.c
drivers/net/wireless/realtek/rtw88/rtw8821c.c
drivers/net/wireless/realtek/rtw88/rtw8822b.c
drivers/net/wireless/realtek/rtw88/rtw8822c.c

index f0b49f5a8a5a883dcdae34c44bc658d521364c6c..e8bad9d099a4f04aa7023067aa10bd3049bce249 100644 (file)
@@ -20,6 +20,8 @@ rtw88_core-y += main.o \
 
 rtw88_core-$(CONFIG_PM) += wow.o
 
+rtw88_core-$(CONFIG_LEDS_CLASS) += led.o
+
 obj-$(CONFIG_RTW88_8822B)      += rtw88_8822b.o
 rtw88_8822b-objs               := rtw8822b.o rtw8822b_table.o
 
diff --git a/drivers/net/wireless/realtek/rtw88/led.c b/drivers/net/wireless/realtek/rtw88/led.c
new file mode 100644 (file)
index 0000000..25aa6cb
--- /dev/null
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+/* Copyright(c) 2025  Realtek Corporation
+ */
+
+#include "main.h"
+#include "debug.h"
+#include "led.h"
+
+static int rtw_led_set_blocking(struct led_classdev *led,
+                               enum led_brightness brightness)
+{
+       struct rtw_dev *rtwdev = container_of(led, struct rtw_dev, led_cdev);
+
+       rtwdev->chip->ops->led_set(led, brightness);
+
+       return 0;
+}
+
+void rtw_led_init(struct rtw_dev *rtwdev)
+{
+       static const struct ieee80211_tpt_blink rtw_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 },
+       };
+       struct led_classdev *led = &rtwdev->led_cdev;
+       int err;
+
+       if (!rtwdev->chip->ops->led_set)
+               return;
+
+       if (rtw_hci_type(rtwdev) == RTW_HCI_TYPE_PCIE)
+               led->brightness_set = rtwdev->chip->ops->led_set;
+       else
+               led->brightness_set_blocking = rtw_led_set_blocking;
+
+       snprintf(rtwdev->led_name, sizeof(rtwdev->led_name),
+                "rtw88-%s", dev_name(rtwdev->dev));
+
+       led->name = rtwdev->led_name;
+       led->max_brightness = LED_ON;
+       led->default_trigger =
+               ieee80211_create_tpt_led_trigger(rtwdev->hw,
+                                                IEEE80211_TPT_LEDTRIG_FL_RADIO,
+                                                rtw_tpt_blink,
+                                                ARRAY_SIZE(rtw_tpt_blink));
+
+       err = led_classdev_register(rtwdev->dev, led);
+       if (err) {
+               rtw_warn(rtwdev, "Failed to register the LED, error %d\n", err);
+               return;
+       }
+
+       rtwdev->led_registered = true;
+}
+
+void rtw_led_deinit(struct rtw_dev *rtwdev)
+{
+       struct led_classdev *led = &rtwdev->led_cdev;
+
+       if (!rtwdev->led_registered)
+               return;
+
+       rtwdev->chip->ops->led_set(led, LED_OFF);
+       led_classdev_unregister(led);
+}
diff --git a/drivers/net/wireless/realtek/rtw88/led.h b/drivers/net/wireless/realtek/rtw88/led.h
new file mode 100644 (file)
index 0000000..c3bb6fe
--- /dev/null
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
+/* Copyright(c) 2025  Realtek Corporation
+ */
+
+#ifndef __RTW_LED_H
+#define __RTW_LED_H
+
+#ifdef CONFIG_LEDS_CLASS
+
+void rtw_led_init(struct rtw_dev *rtwdev);
+void rtw_led_deinit(struct rtw_dev *rtwdev);
+
+#else
+
+static inline void rtw_led_init(struct rtw_dev *rtwdev)
+{
+}
+
+static inline void rtw_led_deinit(struct rtw_dev *rtwdev)
+{
+}
+
+#endif
+
+#endif
index 6993f93c8f0631ed2022af795d53a5f6d5455665..0cee0fd8c0ef074ec623eebf1fde48d64f836159 100644 (file)
@@ -19,6 +19,7 @@
 #include "bf.h"
 #include "sar.h"
 #include "sdio.h"
+#include "led.h"
 
 bool rtw_disable_lps_deep_mode;
 EXPORT_SYMBOL(rtw_disable_lps_deep_mode);
@@ -2292,16 +2293,18 @@ int rtw_register_hw(struct rtw_dev *rtwdev, struct ieee80211_hw *hw)
                return ret;
        }
 
+       rtw_led_init(rtwdev);
+
        ret = ieee80211_register_hw(hw);
        if (ret) {
                rtw_err(rtwdev, "failed to register hw\n");
-               return ret;
+               goto led_deinit;
        }
 
        ret = rtw_regd_hint(rtwdev);
        if (ret) {
                rtw_err(rtwdev, "failed to hint regd\n");
-               return ret;
+               goto led_deinit;
        }
 
        rtw_debugfs_init(rtwdev);
@@ -2310,6 +2313,10 @@ int rtw_register_hw(struct rtw_dev *rtwdev, struct ieee80211_hw *hw)
        rtwdev->bf_info.bfer_su_cnt = 0;
 
        return 0;
+
+led_deinit:
+       rtw_led_deinit(rtwdev);
+       return ret;
 }
 EXPORT_SYMBOL(rtw_register_hw);
 
@@ -2320,6 +2327,7 @@ void rtw_unregister_hw(struct rtw_dev *rtwdev, struct ieee80211_hw *hw)
        ieee80211_unregister_hw(hw);
        rtw_unset_supported_band(hw, chip);
        rtw_debugfs_deinit(rtwdev);
+       rtw_led_deinit(rtwdev);
 }
 EXPORT_SYMBOL(rtw_unregister_hw);
 
index 6ba9e0dcf9fd2ba7e6645600723f371cbbb32384..62cd4c526301921f3d5007a0b187ac48d9c98f5f 100644 (file)
@@ -887,6 +887,7 @@ struct rtw_chip_ops {
                               bool is_tx2_path);
        void (*config_txrx_mode)(struct rtw_dev *rtwdev, u8 tx_path,
                                 u8 rx_path, bool is_tx2_path);
+       void (*led_set)(struct led_classdev *led, enum led_brightness brightness);
        /* for USB/SDIO only */
        void (*fill_txdesc_checksum)(struct rtw_dev *rtwdev,
                                     struct rtw_tx_pkt_info *pkt_info,
@@ -2097,6 +2098,10 @@ struct rtw_dev {
        struct completion fw_scan_density;
        bool ap_active;
 
+       bool led_registered;
+       char led_name[32];
+       struct led_classdev led_cdev;
+
        /* hci related data, must be last */
        u8 priv[] __aligned(sizeof(void *));
 };
index 95a39ae74cd3e08559516107a4d4c984ef4e7706..e438405fba566b85e1989f67e460f01cd7f90616 100644 (file)
 #define BIT_PAPE_SEL_EN                BIT(25)
 #define BIT_DPDT_WL_SEL                BIT(24)
 #define BIT_DPDT_SEL_EN                BIT(23)
+#define BIT_GPIO13_14_WL_CTRL_EN       BIT(22)
+#define BIT_LED2_SV            BIT(19)
+#define BIT_LED2_CM            GENMASK(18, 16)
+#define BIT_LED1_SV            BIT(11)
+#define BIT_LED1_CM            GENMASK(10, 8)
+#define BIT_LED0_SV            BIT(3)
+#define BIT_LED0_CM            GENMASK(2, 0)
+#define BIT_LED_MODE_SW_CTRL   0
+#define BIT_LED_MODE_RX                6
+#define BIT_LED_MODE_TX                4
+#define BIT_LED_MODE_TRX       2
 #define REG_LEDCFG2            0x004E
+#define REG_GPIO_PIN_CTRL_2    0x0060
 #define REG_PAD_CTRL1          0x0064
 #define BIT_BT_BTG_SEL         BIT(31)
 #define BIT_PAPE_WLBT_SEL      BIT(29)
index 21795286a1a0637f84744b4fccb1e704616c3ea0..f9ba2aa2928a42a392c32dc38bac0c6356de3a2e 100644 (file)
@@ -868,6 +868,22 @@ static void rtw8812a_pwr_track(struct rtw_dev *rtwdev)
        dm_info->pwr_trk_triggered = false;
 }
 
+static void rtw8812a_led_set(struct led_classdev *led,
+                            enum led_brightness brightness)
+{
+       struct rtw_dev *rtwdev = container_of(led, struct rtw_dev, led_cdev);
+       u8 ledcfg;
+
+       ledcfg = rtw_read8(rtwdev, REG_LED_CFG);
+       ledcfg &= BIT(6) | BIT(4);
+       ledcfg |= BIT(5);
+
+       if (brightness == LED_OFF)
+               ledcfg |= BIT(3);
+
+       rtw_write8(rtwdev, REG_LED_CFG, ledcfg);
+}
+
 static void rtw8812a_fill_txdesc_checksum(struct rtw_dev *rtwdev,
                                          struct rtw_tx_pkt_info *pkt_info,
                                          u8 *txdesc)
@@ -916,6 +932,7 @@ static const struct rtw_chip_ops rtw8812a_ops = {
        .config_bfee            = NULL,
        .set_gid_table          = NULL,
        .cfg_csi_rate           = NULL,
+       .led_set                = rtw8812a_led_set,
        .fill_txdesc_checksum   = rtw8812a_fill_txdesc_checksum,
        .coex_set_init          = rtw8812a_coex_cfg_init,
        .coex_set_ant_switch    = NULL,
index dafab2af33bc2ef196af436121ae726447bd2768..f68239b073191da49c8c7a906dbf5ba358377b3f 100644 (file)
@@ -706,6 +706,31 @@ static void rtw8821a_pwr_track(struct rtw_dev *rtwdev)
        dm_info->pwr_trk_triggered = false;
 }
 
+static void rtw8821a_led_set(struct led_classdev *led,
+                            enum led_brightness brightness)
+{
+       struct rtw_dev *rtwdev = container_of(led, struct rtw_dev, led_cdev);
+       u32 gpio8_cfg;
+       u8 ledcfg;
+
+       if (brightness == LED_OFF) {
+               gpio8_cfg = rtw_read32(rtwdev, REG_GPIO_PIN_CTRL_2);
+               gpio8_cfg &= ~BIT(24);
+               gpio8_cfg |= BIT(16) | BIT(8);
+               rtw_write32(rtwdev, REG_GPIO_PIN_CTRL_2, gpio8_cfg);
+       } else {
+               ledcfg = rtw_read8(rtwdev, REG_LED_CFG + 2);
+               gpio8_cfg = rtw_read32(rtwdev, REG_GPIO_PIN_CTRL_2);
+
+               ledcfg &= BIT(7) | BIT(6);
+               rtw_write8(rtwdev, REG_LED_CFG + 2, ledcfg);
+
+               gpio8_cfg &= ~(BIT(24) | BIT(8));
+               gpio8_cfg |= BIT(16);
+               rtw_write32(rtwdev, REG_GPIO_PIN_CTRL_2, gpio8_cfg);
+       }
+}
+
 static void rtw8821a_fill_txdesc_checksum(struct rtw_dev *rtwdev,
                                          struct rtw_tx_pkt_info *pkt_info,
                                          u8 *txdesc)
@@ -853,6 +878,7 @@ static const struct rtw_chip_ops rtw8821a_ops = {
        .config_bfee            = NULL,
        .set_gid_table          = NULL,
        .cfg_csi_rate           = NULL,
+       .led_set                = rtw8821a_led_set,
        .fill_txdesc_checksum   = rtw8821a_fill_txdesc_checksum,
        .coex_set_init          = rtw8821a_coex_cfg_init,
        .coex_set_ant_switch    = rtw8821a_coex_cfg_ant_switch,
index 0270225b9c20bc18b1e6e4f5eafb16bdd95ceec1..eb7e34c545d0c82541f8629146da764c589dbb9a 100644 (file)
@@ -1206,6 +1206,24 @@ static void rtw8821c_phy_cck_pd_set(struct rtw_dev *rtwdev, u8 new_lvl)
                         dm_info->cck_pd_default + new_lvl * 2);
 }
 
+static void rtw8821c_led_set(struct led_classdev *led,
+                            enum led_brightness brightness)
+{
+       struct rtw_dev *rtwdev = container_of(led, struct rtw_dev, led_cdev);
+       u32 ledcfg;
+
+       ledcfg = rtw_read32(rtwdev, REG_LED_CFG);
+       u32p_replace_bits(&ledcfg, BIT_LED_MODE_SW_CTRL, BIT_LED2_CM);
+       ledcfg &= ~BIT_GPIO13_14_WL_CTRL_EN;
+
+       if (brightness == LED_OFF)
+               ledcfg |= BIT_LED2_SV;
+       else
+               ledcfg &= ~BIT_LED2_SV;
+
+       rtw_write32(rtwdev, REG_LED_CFG, ledcfg);
+}
+
 static void rtw8821c_fill_txdesc_checksum(struct rtw_dev *rtwdev,
                                          struct rtw_tx_pkt_info *pkt_info,
                                          u8 *txdesc)
@@ -1655,6 +1673,7 @@ static const struct rtw_chip_ops rtw8821c_ops = {
        .config_bfee            = rtw8821c_bf_config_bfee,
        .set_gid_table          = rtw_bf_set_gid_table,
        .cfg_csi_rate           = rtw_bf_cfg_csi_rate,
+       .led_set                = rtw8821c_led_set,
        .fill_txdesc_checksum   = rtw8821c_fill_txdesc_checksum,
 
        .coex_set_init          = rtw8821c_coex_cfg_init,
index 739809f4cab5577e564556c3213a40a2c1d3c83c..7f03903ddf4bbc1b796d8dc3bd910555e35a9cb0 100644 (file)
@@ -1566,6 +1566,24 @@ static void rtw8822b_adaptivity(struct rtw_dev *rtwdev)
        rtw_phy_set_edcca_th(rtwdev, l2h, h2l);
 }
 
+static void rtw8822b_led_set(struct led_classdev *led,
+                            enum led_brightness brightness)
+{
+       struct rtw_dev *rtwdev = container_of(led, struct rtw_dev, led_cdev);
+       u32 ledcfg;
+
+       ledcfg = rtw_read32(rtwdev, REG_LED_CFG);
+       u32p_replace_bits(&ledcfg, BIT_LED_MODE_SW_CTRL, BIT_LED2_CM);
+       ledcfg &= ~BIT_GPIO13_14_WL_CTRL_EN;
+
+       if (brightness == LED_OFF)
+               ledcfg |= BIT_LED2_SV;
+       else
+               ledcfg &= ~BIT_LED2_SV;
+
+       rtw_write32(rtwdev, REG_LED_CFG, ledcfg);
+}
+
 static void rtw8822b_fill_txdesc_checksum(struct rtw_dev *rtwdev,
                                          struct rtw_tx_pkt_info *pkt_info,
                                          u8 *txdesc)
@@ -2146,6 +2164,7 @@ static const struct rtw_chip_ops rtw8822b_ops = {
        .cfg_csi_rate           = rtw_bf_cfg_csi_rate,
        .adaptivity_init        = rtw8822b_adaptivity_init,
        .adaptivity             = rtw8822b_adaptivity,
+       .led_set                = rtw8822b_led_set,
        .fill_txdesc_checksum   = rtw8822b_fill_txdesc_checksum,
 
        .coex_set_init          = rtw8822b_coex_cfg_init,
index af6b76937f1dc5e7f4dc0ee331b4f1e036aa6b29..ec362a817f5f529952e1cfca7589cbabbebdea67 100644 (file)
@@ -4537,6 +4537,24 @@ static void rtw8822c_adaptivity(struct rtw_dev *rtwdev)
        rtw_phy_set_edcca_th(rtwdev, l2h, h2l);
 }
 
+static void rtw8822c_led_set(struct led_classdev *led,
+                            enum led_brightness brightness)
+{
+       struct rtw_dev *rtwdev = container_of(led, struct rtw_dev, led_cdev);
+       u32 ledcfg;
+
+       ledcfg = rtw_read32(rtwdev, REG_LED_CFG);
+       u32p_replace_bits(&ledcfg, BIT_LED_MODE_SW_CTRL, BIT_LED2_CM);
+       ledcfg &= ~BIT_GPIO13_14_WL_CTRL_EN;
+
+       if (brightness == LED_OFF)
+               ledcfg |= BIT_LED2_SV;
+       else
+               ledcfg &= ~BIT_LED2_SV;
+
+       rtw_write32(rtwdev, REG_LED_CFG, ledcfg);
+}
+
 static void rtw8822c_fill_txdesc_checksum(struct rtw_dev *rtwdev,
                                          struct rtw_tx_pkt_info *pkt_info,
                                          u8 *txdesc)
@@ -4964,6 +4982,7 @@ static const struct rtw_chip_ops rtw8822c_ops = {
        .cfo_track              = rtw8822c_cfo_track,
        .config_tx_path         = rtw8822c_config_tx_path,
        .config_txrx_mode       = rtw8822c_config_trx_mode,
+       .led_set                = rtw8822c_led_set,
        .fill_txdesc_checksum   = rtw8822c_fill_txdesc_checksum,
 
        .coex_set_init          = rtw8822c_coex_cfg_init,