linux/drivers/net/wireless/realtek/rtw89/led.c
Johnson Tsai 721d90c850 wifi: rtw89: add multicolor LED support for RTL8852CU valve board
Add multicolor LED support for the RTL8852CU valve board to reflect
wireless connection status (by default, green LED is ON when associated
and OFF when disconnected).

Extend the rtw89 LED subsystem with a multicolor path via led_classdev_mc,
for board-level variants with more than one LED GPIO channel.

Add a new RTW89_LEDS_MC Kconfig option and support multicolor LEDs
through led_classdev_mc, with per-channel caching to minimize
redundant register writes. The RTL8852CU valve board is wired up as
the first user, driving a multicolor WRGB LED over four GPIO channels
(8, 18, 16, and 17) that map to LED_COLOR_ID_WHITE/RED/GREEN/BLUE.

Usage:
- Auto-triggered (default):
  Green LED ON when connected, OFF when disconnected

- Manual override (e.g., set red; channel order: WHITE RED GREEN BLUE):
    echo "0 1 0 0" > /sys/class/leds/rtw89-phyX-multicolor/multi_intensity
    echo 1         > /sys/class/leds/rtw89-phyX-multicolor/brightness

Signed-off-by: Johnson Tsai <wenjie.tsai@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
Link: https://patch.msgid.link/20260717061910.54466-3-pkshih@realtek.com
2026-07-24 10:31:35 +08:00

140 lines
3.7 KiB
C

// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright(c) 2026 Realtek Corporation
*/
#include "core.h"
#include "debug.h"
#include "led.h"
#include "reg.h"
static const struct rtw89_led_gpio_entry rtw89_common_led_gpios[] = {{
.pin = 8,
.pinmux = {.addr = R_AX_GPIO8_15_FUNC_SEL, .mask = GENMASK(3, 0), .data = 0xf},
.mode = {.addr = R_AX_GPIO_EXT_CTRL + 2, .data = BIT(0) | BIT(8)},
.out = {.addr = R_AX_GPIO_EXT_CTRL + 1, .data = BIT(0)},
}};
static const struct rtw89_led_desc rtw89_common_led_desc = {
.gpios = rtw89_common_led_gpios,
.n_gpio = ARRAY_SIZE(rtw89_common_led_gpios),
};
void rtw89_led_gpio_config(struct rtw89_dev *rtwdev,
const struct rtw89_led_gpio_entry *e)
{
rtw89_write32_mask(rtwdev, e->pinmux.addr, e->pinmux.mask, e->pinmux.data);
rtw89_write16_clr(rtwdev, e->mode.addr, e->mode.data);
}
void rtw89_led_gpio_set(struct rtw89_dev *rtwdev,
const struct rtw89_led_gpio_entry *e,
enum led_brightness brightness)
{
if (brightness == LED_OFF) {
rtw89_write16_clr(rtwdev, e->mode.addr, e->mode.data);
} else {
rtw89_write16_set(rtwdev, e->mode.addr, e->mode.data);
rtw89_write8_clr(rtwdev, e->out.addr, e->out.data);
}
}
static int rtw89_led_set(struct led_classdev *led, enum led_brightness brightness)
{
struct rtw89_led *rtw_led = container_of(led, struct rtw89_led, led);
struct rtw89_dev *rtwdev = container_of(rtw_led, struct rtw89_dev, led);
const struct rtw89_led_desc *desc = rtw_led->desc;
const struct rtw89_led_gpio_entry *e = &desc->gpios[0];
if (rtw_led->brightness_cache[0] == brightness) {
rtw89_debug(rtwdev, RTW89_DBG_LED, "led_set: pin=%u skip (no change)\n",
e->pin);
return 0;
}
wiphy_lock(rtwdev->hw->wiphy);
rtw89_debug(rtwdev, RTW89_DBG_LED, "led_set: pin=%u brightness=%u\n",
e->pin, brightness);
rtw89_led_gpio_set(rtwdev, e, brightness);
rtw_led->brightness_cache[0] = brightness;
wiphy_unlock(rtwdev->hw->wiphy);
return 0;
}
static int rtw89_led_sc_init(struct rtw89_dev *rtwdev, const struct rtw89_led_desc *desc)
{
struct rtw89_led *rtw_led = &rtwdev->led;
int ret;
snprintf(rtw_led->name, sizeof(rtw_led->name), "rtw89-%s",
wiphy_name(rtwdev->hw->wiphy));
rtw_led->led.name = rtw_led->name;
rtw_led->led.brightness_set_blocking = rtw89_led_set;
rtw_led->led.max_brightness = LED_ON;
rtw_led->led.default_trigger = ieee80211_get_assoc_led_name(rtwdev->hw);
ret = led_classdev_register(rtwdev->dev, &rtw_led->led);
if (ret) {
rtw89_warn(rtwdev, "failed to register LED, ret=%d\n", ret);
return ret;
}
rtw89_led_gpio_config(rtwdev, &desc->gpios[0]);
return 0;
}
static void rtw89_led_sc_deinit(struct rtw89_dev *rtwdev)
{
struct rtw89_led *rtw_led = &rtwdev->led;
led_classdev_unregister(&rtw_led->led);
}
void rtw89_led_init(struct rtw89_dev *rtwdev)
{
const struct rtw89_led_desc *desc = &rtw89_common_led_desc;
struct rtw89_led *rtw_led = &rtwdev->led;
const struct rtw89_board_variant *board = rtwdev->board;
int ret;
int i;
/* single-GPIO monochrome LED is the only supported layout */
BUILD_BUG_ON(ARRAY_SIZE(rtw89_common_led_gpios) != 1);
if (board)
desc = board->led_desc;
if (!desc->n_gpio || desc->n_gpio > RTW89_LED_MAX_NUM)
return;
rtw_led->desc = desc;
for (i = 0; i < ARRAY_SIZE(rtw_led->brightness_cache); i++)
rtw_led->brightness_cache[i] = LED_OFF;
if (desc->n_gpio == 1)
ret = rtw89_led_sc_init(rtwdev, desc);
else
ret = rtw89_led_mc_init(rtwdev, desc);
if (ret)
return;
rtw_led->registered = true;
}
void rtw89_led_deinit(struct rtw89_dev *rtwdev)
{
struct rtw89_led *rtw_led = &rtwdev->led;
const struct rtw89_led_desc *desc = rtw_led->desc;
if (!rtw_led->registered)
return;
if (desc->n_gpio == 1)
rtw89_led_sc_deinit(rtwdev);
else
rtw89_led_mc_deinit(rtwdev);
}