]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
platform/x86: Add Lenovo Capability Data 01 WMI Driver
authorDerek J. Clark <derekjohn.clark@gmail.com>
Wed, 2 Jul 2025 03:38:24 +0000 (20:38 -0700)
committerIlpo Järvinen <ilpo.jarvinen@linux.intel.com>
Thu, 3 Jul 2025 07:54:30 +0000 (10:54 +0300)
Adds lenovo-wmi-capdata01 driver which provides the
LENOVO_CAPABILITY_DATA_01 WMI data block that comes on "Other Mode"
enabled hardware. Provides an interface for querying if a given
attribute is supported by the hardware, as well as its default_value,
max_value, min_value, and step increment.

Reviewed-by: Alok Tiwari <alok.a.tiwari@oracle.com>
Reviewed-by: Armin Wolf <W_Armin@gmx.de>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
Link: https://lore.kernel.org/r/20250702033826.1057762-5-derekjohn.clark@gmail.com
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
drivers/platform/x86/lenovo/Kconfig
drivers/platform/x86/lenovo/Makefile
drivers/platform/x86/lenovo/wmi-capdata01.c [new file with mode: 0644]
drivers/platform/x86/lenovo/wmi-capdata01.h [new file with mode: 0644]

index 95dce00f257e8597d4af2bc52995b3f5e20cf3fe..1943e3a24f4302d22c8e5d81b439fa24f037a5f1 100644 (file)
@@ -233,6 +233,10 @@ config YT2_1380
          To compile this driver as a module, choose M here: the module will
          be called lenovo-yogabook.
 
+config LENOVO_WMI_DATA01
+       tristate
+       depends on ACPI_WMI
+
 config LENOVO_WMI_EVENTS
        tristate
        depends on ACPI_WMI
index 814c4ba9bdbe32535688314c4726c51894a544bf..1ce970e4ddb84fd06f43dc2d93eeb0ec69c05835 100644 (file)
@@ -12,6 +12,7 @@ lenovo-target-$(CONFIG_LENOVO_YMC)    += ymc.o
 lenovo-target-$(CONFIG_YOGABOOK)       += yogabook.o
 lenovo-target-$(CONFIG_YT2_1380)       += yoga-tab2-pro-1380-fastcharger.o
 lenovo-target-$(CONFIG_LENOVO_WMI_CAMERA)      += wmi-camera.o
+lenovo-target-$(CONFIG_LENOVO_WMI_DATA01)      += wmi-capdata01.o
 lenovo-target-$(CONFIG_LENOVO_WMI_EVENTS)      += wmi-events.o
 lenovo-target-$(CONFIG_LENOVO_WMI_HELPERS)     += wmi-helpers.o
 
diff --git a/drivers/platform/x86/lenovo/wmi-capdata01.c b/drivers/platform/x86/lenovo/wmi-capdata01.c
new file mode 100644 (file)
index 0000000..c922680
--- /dev/null
@@ -0,0 +1,302 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo Capability Data 01 WMI Data Block driver.
+ *
+ * Lenovo Capability Data 01 provides information on tunable attributes used by
+ * the "Other Mode" WMI interface. The data includes if the attribute is
+ * supported by the hardware, the default_value, max_value, min_value, and step
+ * increment. Each attribute has multiple pages, one for each of the thermal
+ * modes managed by the Gamezone interface.
+ *
+ * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/cleanup.h>
+#include <linux/component.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/export.h>
+#include <linux/gfp_types.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/mutex_types.h>
+#include <linux/notifier.h>
+#include <linux/overflow.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#include "wmi-capdata01.h"
+
+#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
+
+#define ACPI_AC_CLASS "ac_adapter"
+#define ACPI_AC_NOTIFY_STATUS 0x80
+
+struct lwmi_cd01_priv {
+       struct notifier_block acpi_nb; /* ACPI events */
+       struct wmi_device *wdev;
+       struct cd01_list *list;
+};
+
+struct cd01_list {
+       struct mutex list_mutex; /* list R/W mutex */
+       u8 count;
+       struct capdata01 data[];
+};
+
+/**
+ * lwmi_cd01_component_bind() - Bind component to master device.
+ * @cd01_dev: Pointer to the lenovo-wmi-capdata01 driver parent device.
+ * @om_dev: Pointer to the lenovo-wmi-other driver parent device.
+ * @data: capdata01_list object pointer used to return the capability data.
+ *
+ * On lenovo-wmi-other's master bind, provide a pointer to the local capdata01
+ * list. This is used to call lwmi_cd01_get_data to look up attribute data
+ * from the lenovo-wmi-other driver.
+ *
+ * Return: 0
+ */
+static int lwmi_cd01_component_bind(struct device *cd01_dev,
+                                   struct device *om_dev, void *data)
+{
+       struct lwmi_cd01_priv *priv = dev_get_drvdata(cd01_dev);
+       struct cd01_list **cd01_list = data;
+
+       *cd01_list = priv->list;
+
+       return 0;
+}
+
+static const struct component_ops lwmi_cd01_component_ops = {
+       .bind = lwmi_cd01_component_bind,
+};
+
+/**
+ * lwmi_cd01_get_data - Get the data of the specified attribute
+ * @list: The lenovo-wmi-capdata01 pointer to its cd01_list struct.
+ * @attribute_id: The capdata attribute ID to be found.
+ * @output: Pointer to a capdata01 struct to return the data.
+ *
+ * Retrieves the capability data 01 struct pointer for the given
+ * attribute for its specified thermal mode.
+ *
+ * Return: 0 on success, or -EINVAL.
+ */
+int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata01 *output)
+{
+       u8 idx;
+
+       guard(mutex)(&list->list_mutex);
+       for (idx = 0; idx < list->count; idx++) {
+               if (list->data[idx].id != attribute_id)
+                       continue;
+               memcpy(output, &list->data[idx], sizeof(list->data[idx]));
+               return 0;
+       };
+
+       return -EINVAL;
+}
+EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD01");
+
+/**
+ * lwmi_cd01_cache() - Cache all WMI data block information
+ * @priv: lenovo-wmi-capdata01 driver data.
+ *
+ * Loop through each WMI data block and cache the data.
+ *
+ * Return: 0 on success, or an error.
+ */
+static int lwmi_cd01_cache(struct lwmi_cd01_priv *priv)
+{
+       int idx;
+
+       guard(mutex)(&priv->list->list_mutex);
+       for (idx = 0; idx < priv->list->count; idx++) {
+               union acpi_object *ret_obj __free(kfree) = NULL;
+
+               ret_obj = wmidev_block_query(priv->wdev, idx);
+               if (!ret_obj)
+                       return -ENODEV;
+
+               if (ret_obj->type != ACPI_TYPE_BUFFER ||
+                   ret_obj->buffer.length < sizeof(priv->list->data[idx]))
+                       continue;
+
+               memcpy(&priv->list->data[idx], ret_obj->buffer.pointer,
+                      ret_obj->buffer.length);
+       }
+
+       return 0;
+}
+
+/**
+ * lwmi_cd01_alloc() - Allocate a cd01_list struct in drvdata
+ * @priv: lenovo-wmi-capdata01 driver data.
+ *
+ * Allocate a cd01_list struct large enough to contain data from all WMI data
+ * blocks provided by the interface.
+ *
+ * Return: 0 on success, or an error.
+ */
+static int lwmi_cd01_alloc(struct lwmi_cd01_priv *priv)
+{
+       struct cd01_list *list;
+       size_t list_size;
+       int count, ret;
+
+       count = wmidev_instance_count(priv->wdev);
+       list_size = struct_size(list, data, count);
+
+       list = devm_kzalloc(&priv->wdev->dev, list_size, GFP_KERNEL);
+       if (!list)
+               return -ENOMEM;
+
+       ret = devm_mutex_init(&priv->wdev->dev, &list->list_mutex);
+       if (ret)
+               return ret;
+
+       list->count = count;
+       priv->list = list;
+
+       return 0;
+}
+
+/**
+ * lwmi_cd01_setup() - Cache all WMI data block information
+ * @priv: lenovo-wmi-capdata01 driver data.
+ *
+ * Allocate a cd01_list struct large enough to contain data from all WMI data
+ * blocks provided by the interface. Then loop through each data block and
+ * cache the data.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_cd01_setup(struct lwmi_cd01_priv *priv)
+{
+       int ret;
+
+       ret = lwmi_cd01_alloc(priv);
+       if (ret)
+               return ret;
+
+       return lwmi_cd01_cache(priv);
+}
+
+/**
+ * lwmi_cd01_notifier_call() - Call method for lenovo-wmi-capdata01 driver notifier.
+ * block call chain.
+ * @nb: The notifier_block registered to lenovo-wmi-events driver.
+ * @action: Unused.
+ * @data: The ACPI event.
+ *
+ * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile
+ * of a change.
+ *
+ * Return: notifier_block status.
+ */
+static int lwmi_cd01_notifier_call(struct notifier_block *nb, unsigned long action,
+                                  void *data)
+{
+       struct acpi_bus_event *event = data;
+       struct lwmi_cd01_priv *priv;
+       int ret;
+
+       if (strcmp(event->device_class, ACPI_AC_CLASS) != 0)
+               return NOTIFY_DONE;
+
+       priv = container_of(nb, struct lwmi_cd01_priv, acpi_nb);
+
+       switch (event->type) {
+       case ACPI_AC_NOTIFY_STATUS:
+               ret = lwmi_cd01_cache(priv);
+               if (ret)
+                       return NOTIFY_BAD;
+
+               return NOTIFY_OK;
+       default:
+               return NOTIFY_DONE;
+       }
+}
+
+/**
+ * lwmi_cd01_unregister() - Unregister the cd01 ACPI notifier_block.
+ * @data: The ACPI event notifier_block to unregister.
+ */
+static void lwmi_cd01_unregister(void *data)
+{
+       struct notifier_block *acpi_nb = data;
+
+       unregister_acpi_notifier(acpi_nb);
+}
+
+static int lwmi_cd01_probe(struct wmi_device *wdev, const void *context)
+
+{
+       struct lwmi_cd01_priv *priv;
+       int ret;
+
+       priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       priv->wdev = wdev;
+       dev_set_drvdata(&wdev->dev, priv);
+
+       ret = lwmi_cd01_setup(priv);
+       if (ret)
+               return ret;
+
+       priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call;
+
+       ret = register_acpi_notifier(&priv->acpi_nb);
+       if (ret)
+               return ret;
+
+       ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister, &priv->acpi_nb);
+       if (ret)
+               return ret;
+
+       return component_add(&wdev->dev, &lwmi_cd01_component_ops);
+}
+
+static void lwmi_cd01_remove(struct wmi_device *wdev)
+{
+       component_del(&wdev->dev, &lwmi_cd01_component_ops);
+}
+
+static const struct wmi_device_id lwmi_cd01_id_table[] = {
+       { LENOVO_CAPABILITY_DATA_01_GUID, NULL },
+       {}
+};
+
+static struct wmi_driver lwmi_cd01_driver = {
+       .driver = {
+               .name = "lenovo_wmi_cd01",
+               .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+       },
+       .id_table = lwmi_cd01_id_table,
+       .probe = lwmi_cd01_probe,
+       .remove = lwmi_cd01_remove,
+       .no_singleton = true,
+};
+
+/**
+ * lwmi_cd01_match() - Match rule for the master driver.
+ * @dev: Pointer to the capability data 01 parent device.
+ * @data: Unused void pointer for passing match criteria.
+ *
+ * Return: int.
+ */
+int lwmi_cd01_match(struct device *dev, void *data)
+{
+       return dev->driver == &lwmi_cd01_driver.driver;
+}
+EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD01");
+
+module_wmi_driver(lwmi_cd01_driver);
+
+MODULE_DEVICE_TABLE(wmi, lwmi_cd01_id_table);
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo/wmi-capdata01.h b/drivers/platform/x86/lenovo/wmi-capdata01.h
new file mode 100644 (file)
index 0000000..bd06c57
--- /dev/null
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
+
+#ifndef _LENOVO_WMI_CAPDATA01_H_
+#define _LENOVO_WMI_CAPDATA01_H_
+
+#include <linux/types.h>
+
+struct device;
+struct cd01_list;
+
+struct capdata01 {
+       u32 id;
+       u32 supported;
+       u32 default_value;
+       u32 step;
+       u32 min_value;
+       u32 max_value;
+};
+
+int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata01 *output);
+int lwmi_cd01_match(struct device *dev, void *data);
+
+#endif /* !_LENOVO_WMI_CAPDATA01_H_ */