linux/drivers/platform/x86/tuxedo/nb04/wmi_util.c
Werner Sembach cfd84b3f41
platform/x86/tuxedo: Add virtual LampArray for TUXEDO NB04 devices
The TUXEDO Sirius 16 Gen1 and TUXEDO Sirius 16 Gen2 devices have a per-key
controllable RGB keyboard backlight. The firmware API for it is implemented
via WMI.

To make the backlight userspace configurable this driver emulates a
LampArray HID device and translates the input from hidraw to the
corresponding WMI calls. This is a new approach as the leds subsystem lacks
a suitable UAPI for per-key keyboard backlights, and like this no new UAPI
needs to be established.

The handle_* functions an corresponding structs are named based on the HID
spec: HID Usage Tables 1.6 -> 26 Lighting And Illumination Page (0x59)

Signed-off-by: Werner Sembach <wse@tuxedocomputers.com>
Link: https://lore.kernel.org/r/20250425210043.342288-2-wse@tuxedocomputers.com
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
2025-05-08 16:03:58 +03:00

91 lines
2.4 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* This code gives functions to avoid code duplication while interacting with
* the TUXEDO NB04 wmi interfaces.
*
* Copyright (C) 2024-2025 Werner Sembach <wse@tuxedocomputers.com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/cleanup.h>
#include <linux/wmi.h>
#include "wmi_util.h"
static int __wmi_method_acpi_object_out(struct wmi_device *wdev,
u32 wmi_method_id,
u8 *in,
acpi_size in_len,
union acpi_object **out)
{
struct acpi_buffer acpi_buffer_in = { in_len, in };
struct acpi_buffer acpi_buffer_out = { ACPI_ALLOCATE_BUFFER, NULL };
dev_dbg(&wdev->dev, "Evaluate WMI method: %u in:\n", wmi_method_id);
print_hex_dump_bytes("", DUMP_PREFIX_OFFSET, in, in_len);
acpi_status status = wmidev_evaluate_method(wdev, 0, wmi_method_id,
&acpi_buffer_in,
&acpi_buffer_out);
if (ACPI_FAILURE(status)) {
dev_err(&wdev->dev, "Failed to evaluate WMI method.\n");
return -EIO;
}
if (!acpi_buffer_out.pointer) {
dev_err(&wdev->dev, "Unexpected empty out buffer.\n");
return -ENODATA;
}
*out = acpi_buffer_out.pointer;
return 0;
}
static int __wmi_method_buffer_out(struct wmi_device *wdev,
u32 wmi_method_id,
u8 *in,
acpi_size in_len,
u8 *out,
acpi_size out_len)
{
int ret;
union acpi_object *acpi_object_out __free(kfree) = NULL;
ret = __wmi_method_acpi_object_out(wdev, wmi_method_id,
in, in_len,
&acpi_object_out);
if (ret)
return ret;
if (acpi_object_out->type != ACPI_TYPE_BUFFER) {
dev_err(&wdev->dev, "Unexpected out buffer type. Expected: %u Got: %u\n",
ACPI_TYPE_BUFFER, acpi_object_out->type);
return -EIO;
}
if (acpi_object_out->buffer.length < out_len) {
dev_err(&wdev->dev, "Unexpected out buffer length.\n");
return -EIO;
}
memcpy(out, acpi_object_out->buffer.pointer, out_len);
return 0;
}
int tux_wmi_xx_8in_80out(struct wmi_device *wdev,
enum tux_wmi_xx_8in_80out_methods method,
union tux_wmi_xx_8in_80out_in_t *in,
union tux_wmi_xx_8in_80out_out_t *out)
{
return __wmi_method_buffer_out(wdev, method, in->raw, 8, out->raw, 80);
}
int tux_wmi_xx_496in_80out(struct wmi_device *wdev,
enum tux_wmi_xx_496in_80out_methods method,
union tux_wmi_xx_496in_80out_in_t *in,
union tux_wmi_xx_496in_80out_out_t *out)
{
return __wmi_method_buffer_out(wdev, method, in->raw, 496, out->raw, 80);
}