Skip to content

Commit 4b6652b

Browse files
Bitterblue SmithPing-Ke Shih
authored andcommitted
wifi: rtw88: Add support for LED blinking
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 <[email protected]> Acked-by: Ping-Ke Shih <[email protected]> Signed-off-by: Ping-Ke Shih <[email protected]> Link: https://patch.msgid.link/[email protected]
1 parent fb2fcfb commit 4b6652b

File tree

11 files changed

+227
-2
lines changed

11 files changed

+227
-2
lines changed

drivers/net/wireless/realtek/rtw88/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ rtw88_core-y += main.o \
2020

2121
rtw88_core-$(CONFIG_PM) += wow.o
2222

23+
rtw88_core-$(CONFIG_LEDS_CLASS) += led.o
24+
2325
obj-$(CONFIG_RTW88_8822B) += rtw88_8822b.o
2426
rtw88_8822b-objs := rtw8822b.o rtw8822b_table.o
2527

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2+
/* Copyright(c) 2025 Realtek Corporation
3+
*/
4+
5+
#include "main.h"
6+
#include "debug.h"
7+
#include "led.h"
8+
9+
static int rtw_led_set_blocking(struct led_classdev *led,
10+
enum led_brightness brightness)
11+
{
12+
struct rtw_dev *rtwdev = container_of(led, struct rtw_dev, led_cdev);
13+
14+
rtwdev->chip->ops->led_set(led, brightness);
15+
16+
return 0;
17+
}
18+
19+
void rtw_led_init(struct rtw_dev *rtwdev)
20+
{
21+
static const struct ieee80211_tpt_blink rtw_tpt_blink[] = {
22+
{ .throughput = 0 * 1024, .blink_time = 334 },
23+
{ .throughput = 1 * 1024, .blink_time = 260 },
24+
{ .throughput = 5 * 1024, .blink_time = 220 },
25+
{ .throughput = 10 * 1024, .blink_time = 190 },
26+
{ .throughput = 20 * 1024, .blink_time = 170 },
27+
{ .throughput = 50 * 1024, .blink_time = 150 },
28+
{ .throughput = 70 * 1024, .blink_time = 130 },
29+
{ .throughput = 100 * 1024, .blink_time = 110 },
30+
{ .throughput = 200 * 1024, .blink_time = 80 },
31+
{ .throughput = 300 * 1024, .blink_time = 50 },
32+
};
33+
struct led_classdev *led = &rtwdev->led_cdev;
34+
int err;
35+
36+
if (!rtwdev->chip->ops->led_set)
37+
return;
38+
39+
if (rtw_hci_type(rtwdev) == RTW_HCI_TYPE_PCIE)
40+
led->brightness_set = rtwdev->chip->ops->led_set;
41+
else
42+
led->brightness_set_blocking = rtw_led_set_blocking;
43+
44+
snprintf(rtwdev->led_name, sizeof(rtwdev->led_name),
45+
"rtw88-%s", dev_name(rtwdev->dev));
46+
47+
led->name = rtwdev->led_name;
48+
led->max_brightness = LED_ON;
49+
led->default_trigger =
50+
ieee80211_create_tpt_led_trigger(rtwdev->hw,
51+
IEEE80211_TPT_LEDTRIG_FL_RADIO,
52+
rtw_tpt_blink,
53+
ARRAY_SIZE(rtw_tpt_blink));
54+
55+
err = led_classdev_register(rtwdev->dev, led);
56+
if (err) {
57+
rtw_warn(rtwdev, "Failed to register the LED, error %d\n", err);
58+
return;
59+
}
60+
61+
rtwdev->led_registered = true;
62+
}
63+
64+
void rtw_led_deinit(struct rtw_dev *rtwdev)
65+
{
66+
struct led_classdev *led = &rtwdev->led_cdev;
67+
68+
if (!rtwdev->led_registered)
69+
return;
70+
71+
rtwdev->chip->ops->led_set(led, LED_OFF);
72+
led_classdev_unregister(led);
73+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
2+
/* Copyright(c) 2025 Realtek Corporation
3+
*/
4+
5+
#ifndef __RTW_LED_H
6+
#define __RTW_LED_H
7+
8+
#ifdef CONFIG_LEDS_CLASS
9+
10+
void rtw_led_init(struct rtw_dev *rtwdev);
11+
void rtw_led_deinit(struct rtw_dev *rtwdev);
12+
13+
#else
14+
15+
static inline void rtw_led_init(struct rtw_dev *rtwdev)
16+
{
17+
}
18+
19+
static inline void rtw_led_deinit(struct rtw_dev *rtwdev)
20+
{
21+
}
22+
23+
#endif
24+
25+
#endif

drivers/net/wireless/realtek/rtw88/main.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "bf.h"
2020
#include "sar.h"
2121
#include "sdio.h"
22+
#include "led.h"
2223

2324
bool rtw_disable_lps_deep_mode;
2425
EXPORT_SYMBOL(rtw_disable_lps_deep_mode);
@@ -2292,16 +2293,18 @@ int rtw_register_hw(struct rtw_dev *rtwdev, struct ieee80211_hw *hw)
22922293
return ret;
22932294
}
22942295

2296+
rtw_led_init(rtwdev);
2297+
22952298
ret = ieee80211_register_hw(hw);
22962299
if (ret) {
22972300
rtw_err(rtwdev, "failed to register hw\n");
2298-
return ret;
2301+
goto led_deinit;
22992302
}
23002303

23012304
ret = rtw_regd_hint(rtwdev);
23022305
if (ret) {
23032306
rtw_err(rtwdev, "failed to hint regd\n");
2304-
return ret;
2307+
goto led_deinit;
23052308
}
23062309

23072310
rtw_debugfs_init(rtwdev);
@@ -2310,6 +2313,10 @@ int rtw_register_hw(struct rtw_dev *rtwdev, struct ieee80211_hw *hw)
23102313
rtwdev->bf_info.bfer_su_cnt = 0;
23112314

23122315
return 0;
2316+
2317+
led_deinit:
2318+
rtw_led_deinit(rtwdev);
2319+
return ret;
23132320
}
23142321
EXPORT_SYMBOL(rtw_register_hw);
23152322

@@ -2320,6 +2327,7 @@ void rtw_unregister_hw(struct rtw_dev *rtwdev, struct ieee80211_hw *hw)
23202327
ieee80211_unregister_hw(hw);
23212328
rtw_unset_supported_band(hw, chip);
23222329
rtw_debugfs_deinit(rtwdev);
2330+
rtw_led_deinit(rtwdev);
23232331
}
23242332
EXPORT_SYMBOL(rtw_unregister_hw);
23252333

drivers/net/wireless/realtek/rtw88/main.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,7 @@ struct rtw_chip_ops {
887887
bool is_tx2_path);
888888
void (*config_txrx_mode)(struct rtw_dev *rtwdev, u8 tx_path,
889889
u8 rx_path, bool is_tx2_path);
890+
void (*led_set)(struct led_classdev *led, enum led_brightness brightness);
890891
/* for USB/SDIO only */
891892
void (*fill_txdesc_checksum)(struct rtw_dev *rtwdev,
892893
struct rtw_tx_pkt_info *pkt_info,
@@ -2097,6 +2098,10 @@ struct rtw_dev {
20972098
struct completion fw_scan_density;
20982099
bool ap_active;
20992100

2101+
bool led_registered;
2102+
char led_name[32];
2103+
struct led_classdev led_cdev;
2104+
21002105
/* hci related data, must be last */
21012106
u8 priv[] __aligned(sizeof(void *));
21022107
};

drivers/net/wireless/realtek/rtw88/reg.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,19 @@
7878
#define BIT_PAPE_SEL_EN BIT(25)
7979
#define BIT_DPDT_WL_SEL BIT(24)
8080
#define BIT_DPDT_SEL_EN BIT(23)
81+
#define BIT_GPIO13_14_WL_CTRL_EN BIT(22)
82+
#define BIT_LED2_SV BIT(19)
83+
#define BIT_LED2_CM GENMASK(18, 16)
84+
#define BIT_LED1_SV BIT(11)
85+
#define BIT_LED1_CM GENMASK(10, 8)
86+
#define BIT_LED0_SV BIT(3)
87+
#define BIT_LED0_CM GENMASK(2, 0)
88+
#define BIT_LED_MODE_SW_CTRL 0
89+
#define BIT_LED_MODE_RX 6
90+
#define BIT_LED_MODE_TX 4
91+
#define BIT_LED_MODE_TRX 2
8192
#define REG_LEDCFG2 0x004E
93+
#define REG_GPIO_PIN_CTRL_2 0x0060
8294
#define REG_PAD_CTRL1 0x0064
8395
#define BIT_BT_BTG_SEL BIT(31)
8496
#define BIT_PAPE_WLBT_SEL BIT(29)

drivers/net/wireless/realtek/rtw88/rtw8812a.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,22 @@ static void rtw8812a_pwr_track(struct rtw_dev *rtwdev)
868868
dm_info->pwr_trk_triggered = false;
869869
}
870870

871+
static void rtw8812a_led_set(struct led_classdev *led,
872+
enum led_brightness brightness)
873+
{
874+
struct rtw_dev *rtwdev = container_of(led, struct rtw_dev, led_cdev);
875+
u8 ledcfg;
876+
877+
ledcfg = rtw_read8(rtwdev, REG_LED_CFG);
878+
ledcfg &= BIT(6) | BIT(4);
879+
ledcfg |= BIT(5);
880+
881+
if (brightness == LED_OFF)
882+
ledcfg |= BIT(3);
883+
884+
rtw_write8(rtwdev, REG_LED_CFG, ledcfg);
885+
}
886+
871887
static void rtw8812a_fill_txdesc_checksum(struct rtw_dev *rtwdev,
872888
struct rtw_tx_pkt_info *pkt_info,
873889
u8 *txdesc)
@@ -916,6 +932,7 @@ static const struct rtw_chip_ops rtw8812a_ops = {
916932
.config_bfee = NULL,
917933
.set_gid_table = NULL,
918934
.cfg_csi_rate = NULL,
935+
.led_set = rtw8812a_led_set,
919936
.fill_txdesc_checksum = rtw8812a_fill_txdesc_checksum,
920937
.coex_set_init = rtw8812a_coex_cfg_init,
921938
.coex_set_ant_switch = NULL,

drivers/net/wireless/realtek/rtw88/rtw8821a.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,31 @@ static void rtw8821a_pwr_track(struct rtw_dev *rtwdev)
706706
dm_info->pwr_trk_triggered = false;
707707
}
708708

709+
static void rtw8821a_led_set(struct led_classdev *led,
710+
enum led_brightness brightness)
711+
{
712+
struct rtw_dev *rtwdev = container_of(led, struct rtw_dev, led_cdev);
713+
u32 gpio8_cfg;
714+
u8 ledcfg;
715+
716+
if (brightness == LED_OFF) {
717+
gpio8_cfg = rtw_read32(rtwdev, REG_GPIO_PIN_CTRL_2);
718+
gpio8_cfg &= ~BIT(24);
719+
gpio8_cfg |= BIT(16) | BIT(8);
720+
rtw_write32(rtwdev, REG_GPIO_PIN_CTRL_2, gpio8_cfg);
721+
} else {
722+
ledcfg = rtw_read8(rtwdev, REG_LED_CFG + 2);
723+
gpio8_cfg = rtw_read32(rtwdev, REG_GPIO_PIN_CTRL_2);
724+
725+
ledcfg &= BIT(7) | BIT(6);
726+
rtw_write8(rtwdev, REG_LED_CFG + 2, ledcfg);
727+
728+
gpio8_cfg &= ~(BIT(24) | BIT(8));
729+
gpio8_cfg |= BIT(16);
730+
rtw_write32(rtwdev, REG_GPIO_PIN_CTRL_2, gpio8_cfg);
731+
}
732+
}
733+
709734
static void rtw8821a_fill_txdesc_checksum(struct rtw_dev *rtwdev,
710735
struct rtw_tx_pkt_info *pkt_info,
711736
u8 *txdesc)
@@ -853,6 +878,7 @@ static const struct rtw_chip_ops rtw8821a_ops = {
853878
.config_bfee = NULL,
854879
.set_gid_table = NULL,
855880
.cfg_csi_rate = NULL,
881+
.led_set = rtw8821a_led_set,
856882
.fill_txdesc_checksum = rtw8821a_fill_txdesc_checksum,
857883
.coex_set_init = rtw8821a_coex_cfg_init,
858884
.coex_set_ant_switch = rtw8821a_coex_cfg_ant_switch,

drivers/net/wireless/realtek/rtw88/rtw8821c.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1206,6 +1206,24 @@ static void rtw8821c_phy_cck_pd_set(struct rtw_dev *rtwdev, u8 new_lvl)
12061206
dm_info->cck_pd_default + new_lvl * 2);
12071207
}
12081208

1209+
static void rtw8821c_led_set(struct led_classdev *led,
1210+
enum led_brightness brightness)
1211+
{
1212+
struct rtw_dev *rtwdev = container_of(led, struct rtw_dev, led_cdev);
1213+
u32 ledcfg;
1214+
1215+
ledcfg = rtw_read32(rtwdev, REG_LED_CFG);
1216+
u32p_replace_bits(&ledcfg, BIT_LED_MODE_SW_CTRL, BIT_LED2_CM);
1217+
ledcfg &= ~BIT_GPIO13_14_WL_CTRL_EN;
1218+
1219+
if (brightness == LED_OFF)
1220+
ledcfg |= BIT_LED2_SV;
1221+
else
1222+
ledcfg &= ~BIT_LED2_SV;
1223+
1224+
rtw_write32(rtwdev, REG_LED_CFG, ledcfg);
1225+
}
1226+
12091227
static void rtw8821c_fill_txdesc_checksum(struct rtw_dev *rtwdev,
12101228
struct rtw_tx_pkt_info *pkt_info,
12111229
u8 *txdesc)
@@ -1655,6 +1673,7 @@ static const struct rtw_chip_ops rtw8821c_ops = {
16551673
.config_bfee = rtw8821c_bf_config_bfee,
16561674
.set_gid_table = rtw_bf_set_gid_table,
16571675
.cfg_csi_rate = rtw_bf_cfg_csi_rate,
1676+
.led_set = rtw8821c_led_set,
16581677
.fill_txdesc_checksum = rtw8821c_fill_txdesc_checksum,
16591678

16601679
.coex_set_init = rtw8821c_coex_cfg_init,

drivers/net/wireless/realtek/rtw88/rtw8822b.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1566,6 +1566,24 @@ static void rtw8822b_adaptivity(struct rtw_dev *rtwdev)
15661566
rtw_phy_set_edcca_th(rtwdev, l2h, h2l);
15671567
}
15681568

1569+
static void rtw8822b_led_set(struct led_classdev *led,
1570+
enum led_brightness brightness)
1571+
{
1572+
struct rtw_dev *rtwdev = container_of(led, struct rtw_dev, led_cdev);
1573+
u32 ledcfg;
1574+
1575+
ledcfg = rtw_read32(rtwdev, REG_LED_CFG);
1576+
u32p_replace_bits(&ledcfg, BIT_LED_MODE_SW_CTRL, BIT_LED2_CM);
1577+
ledcfg &= ~BIT_GPIO13_14_WL_CTRL_EN;
1578+
1579+
if (brightness == LED_OFF)
1580+
ledcfg |= BIT_LED2_SV;
1581+
else
1582+
ledcfg &= ~BIT_LED2_SV;
1583+
1584+
rtw_write32(rtwdev, REG_LED_CFG, ledcfg);
1585+
}
1586+
15691587
static void rtw8822b_fill_txdesc_checksum(struct rtw_dev *rtwdev,
15701588
struct rtw_tx_pkt_info *pkt_info,
15711589
u8 *txdesc)
@@ -2146,6 +2164,7 @@ static const struct rtw_chip_ops rtw8822b_ops = {
21462164
.cfg_csi_rate = rtw_bf_cfg_csi_rate,
21472165
.adaptivity_init = rtw8822b_adaptivity_init,
21482166
.adaptivity = rtw8822b_adaptivity,
2167+
.led_set = rtw8822b_led_set,
21492168
.fill_txdesc_checksum = rtw8822b_fill_txdesc_checksum,
21502169

21512170
.coex_set_init = rtw8822b_coex_cfg_init,

drivers/net/wireless/realtek/rtw88/rtw8822c.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4537,6 +4537,24 @@ static void rtw8822c_adaptivity(struct rtw_dev *rtwdev)
45374537
rtw_phy_set_edcca_th(rtwdev, l2h, h2l);
45384538
}
45394539

4540+
static void rtw8822c_led_set(struct led_classdev *led,
4541+
enum led_brightness brightness)
4542+
{
4543+
struct rtw_dev *rtwdev = container_of(led, struct rtw_dev, led_cdev);
4544+
u32 ledcfg;
4545+
4546+
ledcfg = rtw_read32(rtwdev, REG_LED_CFG);
4547+
u32p_replace_bits(&ledcfg, BIT_LED_MODE_SW_CTRL, BIT_LED2_CM);
4548+
ledcfg &= ~BIT_GPIO13_14_WL_CTRL_EN;
4549+
4550+
if (brightness == LED_OFF)
4551+
ledcfg |= BIT_LED2_SV;
4552+
else
4553+
ledcfg &= ~BIT_LED2_SV;
4554+
4555+
rtw_write32(rtwdev, REG_LED_CFG, ledcfg);
4556+
}
4557+
45404558
static void rtw8822c_fill_txdesc_checksum(struct rtw_dev *rtwdev,
45414559
struct rtw_tx_pkt_info *pkt_info,
45424560
u8 *txdesc)
@@ -4964,6 +4982,7 @@ static const struct rtw_chip_ops rtw8822c_ops = {
49644982
.cfo_track = rtw8822c_cfo_track,
49654983
.config_tx_path = rtw8822c_config_tx_path,
49664984
.config_txrx_mode = rtw8822c_config_trx_mode,
4985+
.led_set = rtw8822c_led_set,
49674986
.fill_txdesc_checksum = rtw8822c_fill_txdesc_checksum,
49684987

49694988
.coex_set_init = rtw8822c_coex_cfg_init,

0 commit comments

Comments
 (0)