Merge branch 'platform-drivers-x86-asus-kbd' into for-next

This commit is contained in:
Ilpo Järvinen 2026-01-28 14:03:40 +02:00
commit c46f7cb338
No known key found for this signature in database
GPG key ID: 59AC4F6153E5CE31
4 changed files with 329 additions and 194 deletions

View file

@ -27,7 +27,6 @@
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/platform_data/x86/asus-wmi.h>
#include <linux/platform_data/x86/asus-wmi-leds-ids.h>
#include <linux/input/mt.h>
#include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */
#include <linux/power_supply.h>
@ -48,8 +47,9 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
#define T100CHI_MOUSE_REPORT_ID 0x06
#define FEATURE_REPORT_ID 0x0d
#define INPUT_REPORT_ID 0x5d
#define HID_USAGE_PAGE_VENDOR 0xff310000
#define FEATURE_KBD_REPORT_ID 0x5a
#define FEATURE_KBD_REPORT_SIZE 16
#define FEATURE_KBD_REPORT_SIZE 64
#define FEATURE_KBD_LED_REPORT_ID1 0x5d
#define FEATURE_KBD_LED_REPORT_ID2 0x5e
@ -90,6 +90,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
#define QUIRK_ROG_NKEY_KEYBOARD BIT(11)
#define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
#define QUIRK_ROG_ALLY_XPAD BIT(13)
#define QUIRK_ROG_NKEY_ID1ID2_INIT BIT(14)
#define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \
QUIRK_NO_INIT_REPORTS | \
@ -101,7 +102,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
#define TRKID_SGN ((TRKID_MAX + 1) >> 1)
struct asus_kbd_leds {
struct led_classdev cdev;
struct asus_hid_listener listener;
struct hid_device *hdev;
struct work_struct work;
unsigned int brightness;
@ -126,7 +127,6 @@ struct asus_drvdata {
struct input_dev *tp_kbd_input;
struct asus_kbd_leds *kbd_backlight;
const struct asus_touchpad_info *tp;
bool enable_backlight;
struct power_supply *battery;
struct power_supply_desc battery_desc;
int battery_capacity;
@ -317,13 +317,24 @@ static int asus_e1239t_event(struct asus_drvdata *drvdat, u8 *data, int size)
static int asus_event(struct hid_device *hdev, struct hid_field *field,
struct hid_usage *usage, __s32 value)
{
if ((usage->hid & HID_USAGE_PAGE) == 0xff310000 &&
if ((usage->hid & HID_USAGE_PAGE) == HID_USAGE_PAGE_VENDOR &&
(usage->hid & HID_USAGE) != 0x00 &&
(usage->hid & HID_USAGE) != 0xff && !usage->type) {
hid_warn(hdev, "Unmapped Asus vendor usagepage code 0x%02x\n",
usage->hid & HID_USAGE);
}
if (usage->type == EV_KEY && value) {
switch (usage->code) {
case KEY_KBDILLUMUP:
return !asus_hid_event(ASUS_EV_BRTUP);
case KEY_KBDILLUMDOWN:
return !asus_hid_event(ASUS_EV_BRTDOWN);
case KEY_KBDILLUMTOGGLE:
return !asus_hid_event(ASUS_EV_BRTTOGGLE);
}
}
return 0;
}
@ -394,15 +405,41 @@ static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu
static int asus_kbd_init(struct hid_device *hdev, u8 report_id)
{
/*
* The handshake is first sent as a set_report, then retrieved
* from a get_report. They should be equal.
*/
const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54,
0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
int ret;
ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
if (ret < 0)
hid_err(hdev, "Asus failed to send init command: %d\n", ret);
if (ret < 0) {
hid_err(hdev, "Asus handshake %02x failed to send: %d\n",
report_id, ret);
return ret;
}
return ret;
u8 *readbuf __free(kfree) = kzalloc(FEATURE_KBD_REPORT_SIZE, GFP_KERNEL);
if (!readbuf)
return -ENOMEM;
ret = hid_hw_raw_request(hdev, report_id, readbuf,
FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
HID_REQ_GET_REPORT);
if (ret < 0) {
hid_warn(hdev, "Asus handshake %02x failed to receive ack: %d\n",
report_id, ret);
} else if (memcmp(readbuf, buf, sizeof(buf)) != 0) {
hid_warn(hdev, "Asus handshake %02x returned invalid response: %*ph\n",
report_id, FEATURE_KBD_REPORT_SIZE, readbuf);
}
/*
* Do not return error if handshake is wrong until this is
* verified to work for all devices.
*/
return 0;
}
static int asus_kbd_get_functions(struct hid_device *hdev,
@ -423,7 +460,7 @@ static int asus_kbd_get_functions(struct hid_device *hdev,
if (!readbuf)
return -ENOMEM;
ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, readbuf,
ret = hid_hw_raw_request(hdev, report_id, readbuf,
FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
HID_REQ_GET_REPORT);
if (ret < 0) {
@ -468,11 +505,11 @@ static void asus_schedule_work(struct asus_kbd_leds *led)
spin_unlock_irqrestore(&led->lock, flags);
}
static void asus_kbd_backlight_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
static void asus_kbd_backlight_set(struct asus_hid_listener *listener,
int brightness)
{
struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
cdev);
struct asus_kbd_leds *led = container_of(listener, struct asus_kbd_leds,
listener);
unsigned long flags;
spin_lock_irqsave(&led->lock, flags);
@ -482,20 +519,6 @@ static void asus_kbd_backlight_set(struct led_classdev *led_cdev,
asus_schedule_work(led);
}
static enum led_brightness asus_kbd_backlight_get(struct led_classdev *led_cdev)
{
struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
cdev);
enum led_brightness brightness;
unsigned long flags;
spin_lock_irqsave(&led->lock, flags);
brightness = led->brightness;
spin_unlock_irqrestore(&led->lock, flags);
return brightness;
}
static void asus_kbd_backlight_work(struct work_struct *work)
{
struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work);
@ -512,34 +535,6 @@ static void asus_kbd_backlight_work(struct work_struct *work)
hid_err(led->hdev, "Asus failed to set keyboard backlight: %d\n", ret);
}
/* WMI-based keyboard backlight LED control (via asus-wmi driver) takes
* precedence. We only activate HID-based backlight control when the
* WMI control is not available.
*/
static bool asus_kbd_wmi_led_control_present(struct hid_device *hdev)
{
struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
u32 value;
int ret;
if (!IS_ENABLED(CONFIG_ASUS_WMI))
return false;
if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD &&
dmi_check_system(asus_use_hid_led_dmi_ids)) {
hid_info(hdev, "using HID for asus::kbd_backlight\n");
return false;
}
ret = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS,
ASUS_WMI_DEVID_KBD_BACKLIGHT, 0, &value);
hid_dbg(hdev, "WMI backlight check: rc %d value %x", ret, value);
if (ret)
return false;
return !!(value & ASUS_WMI_DSTS_PRESENCE_BIT);
}
/*
* We don't care about any other part of the string except the version section.
* Example strings: FGA80100.RC72LA.312_T01, FGA80100.RC71LS.318_T01
@ -639,48 +634,35 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
unsigned char kbd_func;
int ret;
if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
/* Initialize keyboard */
ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
if (ret < 0)
return ret;
/* Get keyboard functions */
ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
if (ret < 0)
return ret;
/* Check for backlight support */
if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
return -ENODEV;
if (drvdata->quirks & QUIRK_ROG_NKEY_ID1ID2_INIT) {
asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2);
}
if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
ret = asus_kbd_disable_oobe(hdev);
if (ret < 0)
return ret;
}
/* The LED endpoint is initialised in two HID */
ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
if (ret < 0)
return ret;
ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2);
if (ret < 0)
return ret;
if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
ret = asus_kbd_disable_oobe(hdev);
if (ret < 0)
return ret;
}
if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) {
intf = to_usb_interface(hdev->dev.parent);
udev = interface_to_usbdev(intf);
validate_mcu_fw_version(hdev,
le16_to_cpu(udev->descriptor.idProduct));
}
} else {
/* Initialize keyboard */
ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
if (ret < 0)
return ret;
/* Get keyboard functions */
ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
if (ret < 0)
return ret;
/* Check for backlight support */
if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
return -ENODEV;
if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) {
intf = to_usb_interface(hdev->dev.parent);
udev = interface_to_usbdev(intf);
validate_mcu_fw_version(hdev,
le16_to_cpu(udev->descriptor.idProduct));
}
drvdata->kbd_backlight = devm_kzalloc(&hdev->dev,
@ -692,14 +674,11 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
drvdata->kbd_backlight->removed = false;
drvdata->kbd_backlight->brightness = 0;
drvdata->kbd_backlight->hdev = hdev;
drvdata->kbd_backlight->cdev.name = "asus::kbd_backlight";
drvdata->kbd_backlight->cdev.max_brightness = 3;
drvdata->kbd_backlight->cdev.brightness_set = asus_kbd_backlight_set;
drvdata->kbd_backlight->cdev.brightness_get = asus_kbd_backlight_get;
drvdata->kbd_backlight->listener.brightness_set = asus_kbd_backlight_set;
INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work);
spin_lock_init(&drvdata->kbd_backlight->lock);
ret = devm_led_classdev_register(&hdev->dev, &drvdata->kbd_backlight->cdev);
ret = asus_hid_register_listener(&drvdata->kbd_backlight->listener);
if (ret < 0) {
/* No need to have this still around */
devm_kfree(&hdev->dev, drvdata->kbd_backlight);
@ -924,11 +903,6 @@ static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi)
drvdata->input = input;
if (drvdata->enable_backlight &&
!asus_kbd_wmi_led_control_present(hdev) &&
asus_kbd_register_leds(hdev))
hid_warn(hdev, "Failed to initialize backlight.\n");
return 0;
}
@ -1001,15 +975,6 @@ static int asus_input_mapping(struct hid_device *hdev,
return -1;
}
/*
* Check and enable backlight only on devices with UsagePage ==
* 0xff31 to avoid initializing the keyboard firmware multiple
* times on devices with multiple HID descriptors but same
* PID/VID.
*/
if (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT)
drvdata->enable_backlight = true;
set_bit(EV_REP, hi->input->evbit);
return 1;
}
@ -1102,7 +1067,7 @@ static int __maybe_unused asus_resume(struct hid_device *hdev) {
if (drvdata->kbd_backlight) {
const u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4,
drvdata->kbd_backlight->cdev.brightness };
drvdata->kbd_backlight->brightness };
ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
if (ret < 0) {
hid_err(hdev, "Asus failed to set keyboard backlight: %d\n", ret);
@ -1126,8 +1091,11 @@ static int __maybe_unused asus_reset_resume(struct hid_device *hdev)
static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
struct hid_report_enum *rep_enum;
struct asus_drvdata *drvdata;
struct hid_report *rep;
bool is_vendor = false;
int ret;
drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
if (drvdata == NULL) {
@ -1211,12 +1179,30 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
return ret;
}
/* Check for vendor for RGB init and handle generic devices properly. */
rep_enum = &hdev->report_enum[HID_INPUT_REPORT];
list_for_each_entry(rep, &rep_enum->report_list, list) {
if ((rep->application & HID_USAGE_PAGE) == HID_USAGE_PAGE_VENDOR)
is_vendor = true;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "Asus hw start failed: %d\n", ret);
return ret;
}
if (is_vendor && (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT) &&
asus_kbd_register_leds(hdev))
hid_warn(hdev, "Failed to initialize backlight.\n");
/*
* For ROG keyboards, skip rename for consistency and ->input check as
* some devices do not have inputs.
*/
if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD)
return 0;
/*
* Check that input registration succeeded. Checking that
* HID_CLAIMED_INPUT is set prevents a UAF when all input devices
@ -1253,6 +1239,8 @@ static void asus_remove(struct hid_device *hdev)
unsigned long flags;
if (drvdata->kbd_backlight) {
asus_hid_unregister_listener(&drvdata->kbd_backlight->listener);
spin_lock_irqsave(&drvdata->kbd_backlight->lock, flags);
drvdata->kbd_backlight->removed = true;
spin_unlock_irqrestore(&drvdata->kbd_backlight->lock, flags);
@ -1384,10 +1372,10 @@ static const struct hid_device_id asus_devices[] = {
QUIRK_USE_KBD_BACKLIGHT },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD),
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_ID1ID2_INIT },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2),
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_ID1ID2_INIT },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },

View file

@ -31,13 +31,13 @@
#include <linux/pci.h>
#include <linux/pci_hotplug.h>
#include <linux/platform_data/x86/asus-wmi.h>
#include <linux/platform_data/x86/asus-wmi-leds-ids.h>
#include <linux/platform_device.h>
#include <linux/platform_profile.h>
#include <linux/power_supply.h>
#include <linux/rfkill.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/units.h>
@ -256,6 +256,9 @@ struct asus_wmi {
int tpd_led_wk;
struct led_classdev kbd_led;
int kbd_led_wk;
bool kbd_led_notify;
bool kbd_led_avail;
bool kbd_led_registered;
struct led_classdev lightbar_led;
int lightbar_led_wk;
struct led_classdev micmute_led;
@ -264,6 +267,7 @@ struct asus_wmi {
struct work_struct tpd_led_work;
struct work_struct wlan_led_work;
struct work_struct lightbar_led_work;
struct work_struct kbd_led_work;
struct asus_rfkill wlan;
struct asus_rfkill bluetooth;
@ -1615,6 +1619,144 @@ static void asus_wmi_battery_exit(struct asus_wmi *asus)
/* LEDs ***********************************************************************/
struct asus_hid_ref {
struct list_head listeners;
struct asus_wmi *asus;
/* Protects concurrent access from hid-asus and asus-wmi to leds */
spinlock_t lock;
};
static struct asus_hid_ref asus_ref = {
.listeners = LIST_HEAD_INIT(asus_ref.listeners),
.asus = NULL,
/*
* Protects .asus, .asus.kbd_led_{wk,notify}, and .listener refs. Other
* asus variables are read-only after .asus is set.
*
* The led cdev device is not protected because it calls backlight_get
* during initialization, which would result in a nested lock attempt.
*
* The led cdev is safe to access without a lock because if
* kbd_led_avail is true it is initialized before .asus is set and never
* changed until .asus is dropped. If kbd_led_avail is false, the led
* cdev is registered by the workqueue, which is single-threaded and
* cancelled before asus-wmi would access the led cdev to unregister it.
*
* A spinlock is used, because the protected variables can be accessed
* from an IRQ context from asus-hid.
*/
.lock = __SPIN_LOCK_UNLOCKED(asus_ref.lock),
};
/*
* Allows registering hid-asus listeners that want to be notified of
* keyboard backlight changes.
*/
int asus_hid_register_listener(struct asus_hid_listener *bdev)
{
struct asus_wmi *asus;
guard(spinlock_irqsave)(&asus_ref.lock);
list_add_tail(&bdev->list, &asus_ref.listeners);
asus = asus_ref.asus;
if (asus)
queue_work(asus->led_workqueue, &asus->kbd_led_work);
return 0;
}
EXPORT_SYMBOL_GPL(asus_hid_register_listener);
/*
* Allows unregistering hid-asus listeners that were added with
* asus_hid_register_listener().
*/
void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
{
guard(spinlock_irqsave)(&asus_ref.lock);
list_del(&bdev->list);
}
EXPORT_SYMBOL_GPL(asus_hid_unregister_listener);
static void do_kbd_led_set(struct led_classdev *led_cdev, int value);
static void kbd_led_update_all(struct work_struct *work)
{
struct asus_wmi *asus;
bool registered, notify;
int ret, value;
asus = container_of(work, struct asus_wmi, kbd_led_work);
scoped_guard(spinlock_irqsave, &asus_ref.lock) {
registered = asus->kbd_led_registered;
value = asus->kbd_led_wk;
notify = asus->kbd_led_notify;
}
if (!registered) {
/*
* This workqueue runs under asus-wmi, which means probe has
* completed and asus-wmi will keep running until it finishes.
* Therefore, we can safely register the LED without holding
* a spinlock.
*/
ret = devm_led_classdev_register(&asus->platform_device->dev,
&asus->kbd_led);
if (!ret) {
scoped_guard(spinlock_irqsave, &asus_ref.lock)
asus->kbd_led_registered = true;
} else {
pr_warn("Failed to register keyboard backlight LED: %d\n", ret);
return;
}
}
if (value >= 0)
do_kbd_led_set(&asus->kbd_led, value);
if (notify) {
scoped_guard(spinlock_irqsave, &asus_ref.lock)
asus->kbd_led_notify = false;
led_classdev_notify_brightness_hw_changed(&asus->kbd_led, value);
}
}
/*
* This function is called from hid-asus to inform asus-wmi of brightness
* changes initiated by the keyboard backlight keys.
*/
int asus_hid_event(enum asus_hid_event event)
{
struct asus_wmi *asus;
int brightness;
guard(spinlock_irqsave)(&asus_ref.lock);
asus = asus_ref.asus;
if (!asus || !asus->kbd_led_registered)
return -EBUSY;
brightness = asus->kbd_led_wk;
switch (event) {
case ASUS_EV_BRTUP:
brightness += 1;
break;
case ASUS_EV_BRTDOWN:
brightness -= 1;
break;
case ASUS_EV_BRTTOGGLE:
if (brightness >= ASUS_EV_MAX_BRIGHTNESS)
brightness = 0;
else
brightness += 1;
break;
}
asus->kbd_led_wk = clamp_val(brightness, 0, ASUS_EV_MAX_BRIGHTNESS);
asus->kbd_led_notify = true;
queue_work(asus->led_workqueue, &asus->kbd_led_work);
return 0;
}
EXPORT_SYMBOL_GPL(asus_hid_event);
/*
* These functions actually update the LED's, and are called from a
* workqueue. By doing this as separate work rather than when the LED
@ -1661,7 +1803,8 @@ static void kbd_led_update(struct asus_wmi *asus)
{
int ctrl_param = 0;
ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F);
scoped_guard(spinlock_irqsave, &asus_ref.lock)
ctrl_param = 0x80 | (asus->kbd_led_wk & 0x7F);
asus_wmi_set_devstate(ASUS_WMI_DEVID_KBD_BACKLIGHT, ctrl_param, NULL);
}
@ -1694,14 +1837,21 @@ static int kbd_led_read(struct asus_wmi *asus, int *level, int *env)
static void do_kbd_led_set(struct led_classdev *led_cdev, int value)
{
struct asus_hid_listener *listener;
struct asus_wmi *asus;
int max_level;
asus = container_of(led_cdev, struct asus_wmi, kbd_led);
max_level = asus->kbd_led.max_brightness;
asus->kbd_led_wk = clamp_val(value, 0, max_level);
kbd_led_update(asus);
scoped_guard(spinlock_irqsave, &asus_ref.lock)
asus->kbd_led_wk = clamp_val(value, 0, ASUS_EV_MAX_BRIGHTNESS);
if (asus->kbd_led_avail)
kbd_led_update(asus);
scoped_guard(spinlock_irqsave, &asus_ref.lock) {
list_for_each_entry(listener, &asus_ref.listeners, list)
listener->brightness_set(listener, asus->kbd_led_wk);
}
}
static int kbd_led_set(struct led_classdev *led_cdev, enum led_brightness value)
@ -1716,10 +1866,11 @@ static int kbd_led_set(struct led_classdev *led_cdev, enum led_brightness value)
static void kbd_led_set_by_kbd(struct asus_wmi *asus, enum led_brightness value)
{
struct led_classdev *led_cdev = &asus->kbd_led;
do_kbd_led_set(led_cdev, value);
led_classdev_notify_brightness_hw_changed(led_cdev, asus->kbd_led_wk);
scoped_guard(spinlock_irqsave, &asus_ref.lock) {
asus->kbd_led_wk = value;
asus->kbd_led_notify = true;
}
queue_work(asus->led_workqueue, &asus->kbd_led_work);
}
static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
@ -1729,10 +1880,18 @@ static enum led_brightness kbd_led_get(struct led_classdev *led_cdev)
asus = container_of(led_cdev, struct asus_wmi, kbd_led);
scoped_guard(spinlock_irqsave, &asus_ref.lock) {
if (!asus->kbd_led_avail)
return asus->kbd_led_wk;
}
retval = kbd_led_read(asus, &value, NULL);
if (retval < 0)
return retval;
scoped_guard(spinlock_irqsave, &asus_ref.lock)
asus->kbd_led_wk = value;
return value;
}
@ -1844,7 +2003,9 @@ static int camera_led_set(struct led_classdev *led_cdev,
static void asus_wmi_led_exit(struct asus_wmi *asus)
{
led_classdev_unregister(&asus->kbd_led);
scoped_guard(spinlock_irqsave, &asus_ref.lock)
asus_ref.asus = NULL;
led_classdev_unregister(&asus->tpd_led);
led_classdev_unregister(&asus->wlan_led);
led_classdev_unregister(&asus->lightbar_led);
@ -1882,22 +2043,26 @@ static int asus_wmi_led_init(struct asus_wmi *asus)
goto error;
}
if (!kbd_led_read(asus, &led_val, NULL) && !dmi_check_system(asus_use_hid_led_dmi_ids)) {
pr_info("using asus-wmi for asus::kbd_backlight\n");
asus->kbd_led_wk = led_val;
asus->kbd_led.name = "asus::kbd_backlight";
asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
asus->kbd_led.brightness_set_blocking = kbd_led_set;
asus->kbd_led.brightness_get = kbd_led_get;
asus->kbd_led.max_brightness = 3;
asus->kbd_led.name = "asus::kbd_backlight";
asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED;
asus->kbd_led.brightness_set_blocking = kbd_led_set;
asus->kbd_led.brightness_get = kbd_led_get;
asus->kbd_led.max_brightness = ASUS_EV_MAX_BRIGHTNESS;
asus->kbd_led_avail = !kbd_led_read(asus, &led_val, NULL);
INIT_WORK(&asus->kbd_led_work, kbd_led_update_all);
if (asus->kbd_led_avail) {
asus->kbd_led_wk = led_val;
if (num_rgb_groups != 0)
asus->kbd_led.groups = kbd_rgb_mode_groups;
} else {
asus->kbd_led_wk = -1;
}
rv = led_classdev_register(&asus->platform_device->dev,
&asus->kbd_led);
if (rv)
goto error;
scoped_guard(spinlock_irqsave, &asus_ref.lock) {
asus_ref.asus = asus;
if (asus->kbd_led_avail || !list_empty(&asus_ref.listeners))
queue_work(asus->led_workqueue, &asus->kbd_led_work);
}
if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_WIRELESS_LED)
@ -4372,6 +4537,7 @@ static int asus_wmi_get_event_code(union acpi_object *obj)
static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
{
enum led_brightness led_value;
unsigned int key_value = 1;
bool autorelease = 1;
@ -4388,19 +4554,22 @@ static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus)
return;
}
scoped_guard(spinlock_irqsave, &asus_ref.lock)
led_value = asus->kbd_led_wk;
if (code == NOTIFY_KBD_BRTUP) {
kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1);
kbd_led_set_by_kbd(asus, led_value + 1);
return;
}
if (code == NOTIFY_KBD_BRTDWN) {
kbd_led_set_by_kbd(asus, asus->kbd_led_wk - 1);
kbd_led_set_by_kbd(asus, led_value - 1);
return;
}
if (code == NOTIFY_KBD_BRTTOGGLE) {
if (asus->kbd_led_wk == asus->kbd_led.max_brightness)
if (led_value >= ASUS_EV_MAX_BRIGHTNESS)
kbd_led_set_by_kbd(asus, 0);
else
kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1);
kbd_led_set_by_kbd(asus, led_value + 1);
return;
}

View file

@ -1,50 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __PLATFORM_DATA_X86_ASUS_WMI_LEDS_IDS_H
#define __PLATFORM_DATA_X86_ASUS_WMI_LEDS_IDS_H
#include <linux/dmi.h>
#include <linux/types.h>
/* To be used by both hid-asus and asus-wmi to determine which controls kbd_brightness */
#if IS_REACHABLE(CONFIG_ASUS_WMI) || IS_REACHABLE(CONFIG_HID_ASUS)
static const struct dmi_system_id asus_use_hid_led_dmi_ids[] = {
{
.matches = {
DMI_MATCH(DMI_PRODUCT_FAMILY, "ROG Zephyrus"),
},
},
{
.matches = {
DMI_MATCH(DMI_PRODUCT_FAMILY, "ROG Strix"),
},
},
{
.matches = {
DMI_MATCH(DMI_PRODUCT_FAMILY, "ROG Flow"),
},
},
{
.matches = {
DMI_MATCH(DMI_PRODUCT_FAMILY, "ProArt P16"),
},
},
{
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "GA403U"),
},
},
{
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "GU605M"),
},
},
{
.matches = {
DMI_MATCH(DMI_BOARD_NAME, "RC71L"),
},
},
{ },
};
#endif
#endif /* __PLATFORM_DATA_X86_ASUS_WMI_LEDS_IDS_H */

View file

@ -172,12 +172,29 @@ enum asus_ally_mcu_hack {
ASUS_WMI_ALLY_MCU_HACK_DISABLED,
};
/* Used to notify hid-asus when asus-wmi changes keyboard backlight */
struct asus_hid_listener {
struct list_head list;
void (*brightness_set)(struct asus_hid_listener *listener, int brightness);
};
enum asus_hid_event {
ASUS_EV_BRTUP,
ASUS_EV_BRTDOWN,
ASUS_EV_BRTTOGGLE,
};
#define ASUS_EV_MAX_BRIGHTNESS 3
#if IS_REACHABLE(CONFIG_ASUS_WMI)
void set_ally_mcu_hack(enum asus_ally_mcu_hack status);
void set_ally_mcu_powersave(bool enabled);
int asus_wmi_get_devstate_dsts(u32 dev_id, u32 *retval);
int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, u32 *retval);
int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval);
int asus_hid_register_listener(struct asus_hid_listener *cdev);
void asus_hid_unregister_listener(struct asus_hid_listener *cdev);
int asus_hid_event(enum asus_hid_event event);
#else
static inline void set_ally_mcu_hack(enum asus_ally_mcu_hack status)
{
@ -198,6 +215,17 @@ static inline int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1,
{
return -ENODEV;
}
static inline int asus_hid_register_listener(struct asus_hid_listener *bdev)
{
return -ENODEV;
}
static inline void asus_hid_unregister_listener(struct asus_hid_listener *bdev)
{
}
static inline int asus_hid_event(enum asus_hid_event event)
{
return -ENODEV;
}
#endif
#endif /* __PLATFORM_DATA_X86_ASUS_WMI_H */