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>
---
v8:
- Use devm_mutex_init() instead of mutex_init().
- Check return of devm_add_action_or_reset during probe.
- Don't check if cd01 list exists in bind as the driver will bail if
there is a malloc/populating issue prior to bind.
- Fix typos.
v7:
- Do memcpy instead of returning pointer in lwmi_cd01_get_data.
- Put list mutex inside lwmi_cd01_priv struct.
- Unregister from acpi events on dev remove.
- Fix typos.
v6:
- Recache capabiltiy data on ACPI AC events to ensure accutare
max_value.
- Fix typos and rewordings from v5 review.
v5:
- Return to cache at device initialization. On component bind, pass a
pointer to lenovo-wmi-other.
- Fixes from v4 review.
v4:
- Make driver data a private struct, remove references from Other Mode
driver.
- Don't cache data at device initialization. Instead, on component bind,
cache the data on a member variable of the Other Mode driver data
passed as a void pointer.
- Add header file for capdata01 structs.
- Add new struct to pass capdata01 array data and array length to Other
Mode.
v3:
- Add as component to lenovo-wmi-other driver.
v2:
- Use devm_kmalloc to ensure driver can be instanced, remove global
reference.
- Ensure reverse Christmas tree for all variable declarations.
- Remove extra whitespace.
- Use guard(mutex) in all mutex instances, global mutex.
- Use pr_fmt instead of adding the driver name to each pr_err.
- Remove noisy pr_info usage.
- Rename capdata_wmi to lenovo_wmi_cd01_priv and cd01_wmi to priv.
- Use list to get the lenovo_wmi_cd01_priv instance in
lenovo_wmi_capdata01_get as none of the data provided by the macros
that will use it can pass a member of the struct for use in
container_of.
---
MAINTAINERS | 1 +
drivers/platform/x86/Kconfig | 4 +
drivers/platform/x86/Makefile | 1 +
drivers/platform/x86/lenovo-wmi-capdata01.c | 300 ++++++++++++++++++++
drivers/platform/x86/lenovo-wmi-capdata01.h | 25 ++
5 files changed, 331 insertions(+)
create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 2b4b06e81192..1b22e41cc730 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13164,6 +13164,7 @@ L: platform-driver-x86@vger.kernel.org
S: Maintained
F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
F: Documentation/wmi/devices/lenovo-wmi-other.rst
+F: drivers/platform/x86/lenovo-wmi-capdata01.*
F: drivers/platform/x86/lenovo-wmi-events.*
F: drivers/platform/x86/lenovo-wmi-helpers.*
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 13b8f4ac5dc5..64663667f0cb 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -467,6 +467,10 @@ config LENOVO_WMI_HELPERS
tristate
depends on ACPI_WMI
+config LENOVO_WMI_DATA01
+ tristate
+ depends on ACPI_WMI
+
config IDEAPAD_LAPTOP
tristate "Lenovo IdeaPad Laptop Extras"
depends on ACPI
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index fc039839286a..7a35c77221b7 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -69,6 +69,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o
obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o
obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o
+obj-$(CONFIG_LENOVO_WMI_DATA01) += lenovo-wmi-capdata01.o
obj-$(CONFIG_LENOVO_WMI_EVENTS) += lenovo-wmi-events.o
obj-$(CONFIG_LENOVO_WMI_HELPERS) += lenovo-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
index 000000000000..9e2758426564
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
@@ -0,0 +1,300 @@
+// 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/printk.h"
+#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 "lenovo-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
+ * @dev: The lenovo-wmi-capdata01 parent device.
+ * @tunable_attr: The attribute 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_setup() - 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;
+
+ 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;
+
+ devm_mutex_init(&priv->wdev->dev, &list->list_mutex);
+ 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
index 000000000000..bd06c5751f68
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-capdata01.h
@@ -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_ */
--
2.49.0
Am 05.05.25 um 03:06 schrieb Derek J. Clark:
> 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>
> ---
> v8:
> - Use devm_mutex_init() instead of mutex_init().
> - Check return of devm_add_action_or_reset during probe.
> - Don't check if cd01 list exists in bind as the driver will bail if
> there is a malloc/populating issue prior to bind.
> - Fix typos.
> v7:
> - Do memcpy instead of returning pointer in lwmi_cd01_get_data.
> - Put list mutex inside lwmi_cd01_priv struct.
> - Unregister from acpi events on dev remove.
> - Fix typos.
> v6:
> - Recache capabiltiy data on ACPI AC events to ensure accutare
> max_value.
> - Fix typos and rewordings from v5 review.
> v5:
> - Return to cache at device initialization. On component bind, pass a
> pointer to lenovo-wmi-other.
> - Fixes from v4 review.
> v4:
> - Make driver data a private struct, remove references from Other Mode
> driver.
> - Don't cache data at device initialization. Instead, on component bind,
> cache the data on a member variable of the Other Mode driver data
> passed as a void pointer.
> - Add header file for capdata01 structs.
> - Add new struct to pass capdata01 array data and array length to Other
> Mode.
> v3:
> - Add as component to lenovo-wmi-other driver.
> v2:
> - Use devm_kmalloc to ensure driver can be instanced, remove global
> reference.
> - Ensure reverse Christmas tree for all variable declarations.
> - Remove extra whitespace.
> - Use guard(mutex) in all mutex instances, global mutex.
> - Use pr_fmt instead of adding the driver name to each pr_err.
> - Remove noisy pr_info usage.
> - Rename capdata_wmi to lenovo_wmi_cd01_priv and cd01_wmi to priv.
> - Use list to get the lenovo_wmi_cd01_priv instance in
> lenovo_wmi_capdata01_get as none of the data provided by the macros
> that will use it can pass a member of the struct for use in
> container_of.
> ---
> MAINTAINERS | 1 +
> drivers/platform/x86/Kconfig | 4 +
> drivers/platform/x86/Makefile | 1 +
> drivers/platform/x86/lenovo-wmi-capdata01.c | 300 ++++++++++++++++++++
> drivers/platform/x86/lenovo-wmi-capdata01.h | 25 ++
> 5 files changed, 331 insertions(+)
> create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
> create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 2b4b06e81192..1b22e41cc730 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13164,6 +13164,7 @@ L: platform-driver-x86@vger.kernel.org
> S: Maintained
> F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
> F: Documentation/wmi/devices/lenovo-wmi-other.rst
> +F: drivers/platform/x86/lenovo-wmi-capdata01.*
> F: drivers/platform/x86/lenovo-wmi-events.*
> F: drivers/platform/x86/lenovo-wmi-helpers.*
>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 13b8f4ac5dc5..64663667f0cb 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -467,6 +467,10 @@ config LENOVO_WMI_HELPERS
> tristate
> depends on ACPI_WMI
>
> +config LENOVO_WMI_DATA01
> + tristate
> + depends on ACPI_WMI
> +
> config IDEAPAD_LAPTOP
> tristate "Lenovo IdeaPad Laptop Extras"
> depends on ACPI
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index fc039839286a..7a35c77221b7 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -69,6 +69,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
> obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o
> obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o
> obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o
> +obj-$(CONFIG_LENOVO_WMI_DATA01) += lenovo-wmi-capdata01.o
> obj-$(CONFIG_LENOVO_WMI_EVENTS) += lenovo-wmi-events.o
> obj-$(CONFIG_LENOVO_WMI_HELPERS) += lenovo-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
> index 000000000000..9e2758426564
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
> @@ -0,0 +1,300 @@
> +// 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/printk.h"
> +#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 "lenovo-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
> + * @dev: The lenovo-wmi-capdata01 parent device.
> + * @tunable_attr: The attribute 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_setup() - 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;
> +
> + 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;
> +
> + devm_mutex_init(&priv->wdev->dev, &list->list_mutex);
Please check the result of devm_mutex_init(), as this function might fail.
Other than that the whole series looks good to me. Hopefully we can finally merge this
for the 6.16 kernel cycle.
Thanks,
Armin Wolf
> + 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
> index 000000000000..bd06c5751f68
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo-wmi-capdata01.h
> @@ -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_ */
On May 4, 2025 6:32:43 PM PDT, Armin Wolf <W_Armin@gmx.de> wrote:
>Am 05.05.25 um 03:06 schrieb Derek J. Clark:
>
>> 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>
>> ---
>> v8:
>> - Use devm_mutex_init() instead of mutex_init().
>> - Check return of devm_add_action_or_reset during probe.
>> - Don't check if cd01 list exists in bind as the driver will bail if
>> there is a malloc/populating issue prior to bind.
>> - Fix typos.
>> v7:
>> - Do memcpy instead of returning pointer in lwmi_cd01_get_data.
>> - Put list mutex inside lwmi_cd01_priv struct.
>> - Unregister from acpi events on dev remove.
>> - Fix typos.
>> v6:
>> - Recache capabiltiy data on ACPI AC events to ensure accutare
>> max_value.
>> - Fix typos and rewordings from v5 review.
>> v5:
>> - Return to cache at device initialization. On component bind, pass a
>> pointer to lenovo-wmi-other.
>> - Fixes from v4 review.
>> v4:
>> - Make driver data a private struct, remove references from Other Mode
>> driver.
>> - Don't cache data at device initialization. Instead, on component bind,
>> cache the data on a member variable of the Other Mode driver data
>> passed as a void pointer.
>> - Add header file for capdata01 structs.
>> - Add new struct to pass capdata01 array data and array length to Other
>> Mode.
>> v3:
>> - Add as component to lenovo-wmi-other driver.
>> v2:
>> - Use devm_kmalloc to ensure driver can be instanced, remove global
>> reference.
>> - Ensure reverse Christmas tree for all variable declarations.
>> - Remove extra whitespace.
>> - Use guard(mutex) in all mutex instances, global mutex.
>> - Use pr_fmt instead of adding the driver name to each pr_err.
>> - Remove noisy pr_info usage.
>> - Rename capdata_wmi to lenovo_wmi_cd01_priv and cd01_wmi to priv.
>> - Use list to get the lenovo_wmi_cd01_priv instance in
>> lenovo_wmi_capdata01_get as none of the data provided by the macros
>> that will use it can pass a member of the struct for use in
>> container_of.
>> ---
>> MAINTAINERS | 1 +
>> drivers/platform/x86/Kconfig | 4 +
>> drivers/platform/x86/Makefile | 1 +
>> drivers/platform/x86/lenovo-wmi-capdata01.c | 300 ++++++++++++++++++++
>> drivers/platform/x86/lenovo-wmi-capdata01.h | 25 ++
>> 5 files changed, 331 insertions(+)
>> create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
>> create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.h
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 2b4b06e81192..1b22e41cc730 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -13164,6 +13164,7 @@ L: platform-driver-x86@vger.kernel.org
>> S: Maintained
>> F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
>> F: Documentation/wmi/devices/lenovo-wmi-other.rst
>> +F: drivers/platform/x86/lenovo-wmi-capdata01.*
>> F: drivers/platform/x86/lenovo-wmi-events.*
>> F: drivers/platform/x86/lenovo-wmi-helpers.*
>> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
>> index 13b8f4ac5dc5..64663667f0cb 100644
>> --- a/drivers/platform/x86/Kconfig
>> +++ b/drivers/platform/x86/Kconfig
>> @@ -467,6 +467,10 @@ config LENOVO_WMI_HELPERS
>> tristate
>> depends on ACPI_WMI
>> +config LENOVO_WMI_DATA01
>> + tristate
>> + depends on ACPI_WMI
>> +
>> config IDEAPAD_LAPTOP
>> tristate "Lenovo IdeaPad Laptop Extras"
>> depends on ACPI
>> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
>> index fc039839286a..7a35c77221b7 100644
>> --- a/drivers/platform/x86/Makefile
>> +++ b/drivers/platform/x86/Makefile
>> @@ -69,6 +69,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
>> obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o
>> obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o
>> obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o
>> +obj-$(CONFIG_LENOVO_WMI_DATA01) += lenovo-wmi-capdata01.o
>> obj-$(CONFIG_LENOVO_WMI_EVENTS) += lenovo-wmi-events.o
>> obj-$(CONFIG_LENOVO_WMI_HELPERS) += lenovo-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
>> index 000000000000..9e2758426564
>> --- /dev/null
>> +++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
>> @@ -0,0 +1,300 @@
>> +// 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/printk.h"
>> +#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 "lenovo-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
>> + * @dev: The lenovo-wmi-capdata01 parent device.
>> + * @tunable_attr: The attribute 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_setup() - 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;
>> +
>> + 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;
>> +
>> + devm_mutex_init(&priv->wdev->dev, &list->list_mutex);
>
>Please check the result of devm_mutex_init(), as this function might fail.
>
>Other than that the whole series looks good to me. Hopefully we can finally merge this
>for the 6.16 kernel cycle.
>
>Thanks,
>Armin Wolf
>
Will do. I'll give this version a few more days for additional comments before I submit that change.
Thanks,
Derek
>> + 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
>> index 000000000000..bd06c5751f68
>> --- /dev/null
>> +++ b/drivers/platform/x86/lenovo-wmi-capdata01.h
>> @@ -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_ */
- Derek
© 2016 - 2026 Red Hat, Inc.