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@xxxxxxxxx> --- v2: - Move LED (de)init to a new file led.c - Use #ifdef CONFIF_LEDS_CLASS only in the new file led.h --- drivers/net/wireless/realtek/rtw88/Makefile | 2 + drivers/net/wireless/realtek/rtw88/led.c | 73 +++++++++++++++++++ drivers/net/wireless/realtek/rtw88/led.h | 25 +++++++ drivers/net/wireless/realtek/rtw88/main.c | 12 ++- drivers/net/wireless/realtek/rtw88/main.h | 5 ++ drivers/net/wireless/realtek/rtw88/reg.h | 12 +++ drivers/net/wireless/realtek/rtw88/rtw8812a.c | 17 +++++ drivers/net/wireless/realtek/rtw88/rtw8821a.c | 26 +++++++ drivers/net/wireless/realtek/rtw88/rtw8821c.c | 19 +++++ drivers/net/wireless/realtek/rtw88/rtw8822b.c | 19 +++++ drivers/net/wireless/realtek/rtw88/rtw8822c.c | 19 +++++ 11 files changed, 227 insertions(+), 2 deletions(-) create mode 100644 drivers/net/wireless/realtek/rtw88/led.c create mode 100644 drivers/net/wireless/realtek/rtw88/led.h diff --git a/drivers/net/wireless/realtek/rtw88/Makefile b/drivers/net/wireless/realtek/rtw88/Makefile index f0b49f5a8a5a..e8bad9d099a4 100644 --- a/drivers/net/wireless/realtek/rtw88/Makefile +++ b/drivers/net/wireless/realtek/rtw88/Makefile @@ -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 index 000000000000..aa50b2f0abff --- /dev/null +++ b/drivers/net/wireless/realtek/rtw88/led.c @@ -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_USB) + led->brightness_set_blocking = rtw_led_set_blocking; + else + led->brightness_set = rtwdev->chip->ops->led_set; + + 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 index 000000000000..9f0f21f1bb46 --- /dev/null +++ b/drivers/net/wireless/realtek/rtw88/led.h @@ -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 void rtw_led_init(struct rtw_dev *rtwdev) +{ +} + +static void rtw_led_deinit(struct rtw_dev *rtwdev) +{ +} + +#endif + +#endif diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c index 6993f93c8f06..0cee0fd8c0ef 100644 --- a/drivers/net/wireless/realtek/rtw88/main.c +++ b/drivers/net/wireless/realtek/rtw88/main.c @@ -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); diff --git a/drivers/net/wireless/realtek/rtw88/main.h b/drivers/net/wireless/realtek/rtw88/main.h index 6ba9e0dcf9fd..62cd4c526301 100644 --- a/drivers/net/wireless/realtek/rtw88/main.h +++ b/drivers/net/wireless/realtek/rtw88/main.h @@ -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 *)); }; diff --git a/drivers/net/wireless/realtek/rtw88/reg.h b/drivers/net/wireless/realtek/rtw88/reg.h index 95a39ae74cd3..e438405fba56 100644 --- a/drivers/net/wireless/realtek/rtw88/reg.h +++ b/drivers/net/wireless/realtek/rtw88/reg.h @@ -78,7 +78,19 @@ #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) diff --git a/drivers/net/wireless/realtek/rtw88/rtw8812a.c b/drivers/net/wireless/realtek/rtw88/rtw8812a.c index 21795286a1a0..f9ba2aa2928a 100644 --- a/drivers/net/wireless/realtek/rtw88/rtw8812a.c +++ b/drivers/net/wireless/realtek/rtw88/rtw8812a.c @@ -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, diff --git a/drivers/net/wireless/realtek/rtw88/rtw8821a.c b/drivers/net/wireless/realtek/rtw88/rtw8821a.c index dafab2af33bc..f68239b07319 100644 --- a/drivers/net/wireless/realtek/rtw88/rtw8821a.c +++ b/drivers/net/wireless/realtek/rtw88/rtw8821a.c @@ -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, diff --git a/drivers/net/wireless/realtek/rtw88/rtw8821c.c b/drivers/net/wireless/realtek/rtw88/rtw8821c.c index 0270225b9c20..eb7e34c545d0 100644 --- a/drivers/net/wireless/realtek/rtw88/rtw8821c.c +++ b/drivers/net/wireless/realtek/rtw88/rtw8821c.c @@ -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, diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822b.c b/drivers/net/wireless/realtek/rtw88/rtw8822b.c index 739809f4cab5..7f03903ddf4b 100644 --- a/drivers/net/wireless/realtek/rtw88/rtw8822b.c +++ b/drivers/net/wireless/realtek/rtw88/rtw8822b.c @@ -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, diff --git a/drivers/net/wireless/realtek/rtw88/rtw8822c.c b/drivers/net/wireless/realtek/rtw88/rtw8822c.c index af6b76937f1d..ec362a817f5f 100644 --- a/drivers/net/wireless/realtek/rtw88/rtw8822c.c +++ b/drivers/net/wireless/realtek/rtw88/rtw8822c.c @@ -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, -- 2.47.1