197 lines
5.5 KiB
C
197 lines
5.5 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Lenovo WMI Events driver. Lenovo WMI interfaces provide various
|
|
* hardware triggered events that many drivers need to have propagated.
|
|
* This driver provides a uniform entrypoint for these events so that
|
|
* any driver that needs to respond to these events can subscribe to a
|
|
* notifier chain.
|
|
*
|
|
* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
|
|
*/
|
|
|
|
#include <linux/acpi.h>
|
|
#include <linux/export.h>
|
|
#include <linux/module.h>
|
|
#include <linux/notifier.h>
|
|
#include <linux/types.h>
|
|
#include <linux/wmi.h>
|
|
|
|
#include "wmi-events.h"
|
|
#include "wmi-gamezone.h"
|
|
|
|
#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
|
|
|
|
#define LWMI_EVENT_DEVICE(guid, type) \
|
|
.guid_string = (guid), .context = &(enum lwmi_events_type) \
|
|
{ \
|
|
type \
|
|
}
|
|
|
|
static BLOCKING_NOTIFIER_HEAD(events_chain_head);
|
|
|
|
struct lwmi_events_priv {
|
|
struct wmi_device *wdev;
|
|
enum lwmi_events_type type;
|
|
};
|
|
|
|
/**
|
|
* lwmi_events_register_notifier() - Add a notifier to the notifier chain.
|
|
* @nb: The notifier_block struct to register
|
|
*
|
|
* Call blocking_notifier_chain_register to register the notifier block to the
|
|
* lenovo-wmi-events driver blocking notifier chain.
|
|
*
|
|
* Return: 0 on success, %-EEXIST on error.
|
|
*/
|
|
int lwmi_events_register_notifier(struct notifier_block *nb)
|
|
{
|
|
return blocking_notifier_chain_register(&events_chain_head, nb);
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(lwmi_events_register_notifier, "LENOVO_WMI_EVENTS");
|
|
|
|
/**
|
|
* lwmi_events_unregister_notifier() - Remove a notifier from the notifier
|
|
* chain.
|
|
* @nb: The notifier_block struct to unregister
|
|
*
|
|
* Call blocking_notifier_chain_unregister to unregister the notifier block
|
|
* from the lenovo-wmi-events driver blocking notifier chain.
|
|
*
|
|
* Return: 0 on success, %-ENOENT on error.
|
|
*/
|
|
int lwmi_events_unregister_notifier(struct notifier_block *nb)
|
|
{
|
|
return blocking_notifier_chain_unregister(&events_chain_head, nb);
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(lwmi_events_unregister_notifier, "LENOVO_WMI_EVENTS");
|
|
|
|
/**
|
|
* devm_lwmi_events_unregister_notifier() - Remove a notifier from the notifier
|
|
* chain.
|
|
* @data: Void pointer to the notifier_block struct to unregister.
|
|
*
|
|
* Call lwmi_events_unregister_notifier to unregister the notifier block from
|
|
* the lenovo-wmi-events driver blocking notifier chain.
|
|
*
|
|
* Return: 0 on success, %-ENOENT on error.
|
|
*/
|
|
static void devm_lwmi_events_unregister_notifier(void *data)
|
|
{
|
|
struct notifier_block *nb = data;
|
|
|
|
lwmi_events_unregister_notifier(nb);
|
|
}
|
|
|
|
/**
|
|
* devm_lwmi_events_register_notifier() - Add a notifier to the notifier chain.
|
|
* @dev: The parent device of the notifier_block struct.
|
|
* @nb: The notifier_block struct to register
|
|
*
|
|
* Call lwmi_events_register_notifier to register the notifier block to the
|
|
* lenovo-wmi-events driver blocking notifier chain. Then add, as a device
|
|
* managed action, unregister_notifier to automatically unregister the
|
|
* notifier block upon its parent device removal.
|
|
*
|
|
* Return: 0 on success, or an error code.
|
|
*/
|
|
int devm_lwmi_events_register_notifier(struct device *dev,
|
|
struct notifier_block *nb)
|
|
{
|
|
int ret;
|
|
|
|
ret = lwmi_events_register_notifier(nb);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return devm_add_action_or_reset(dev, devm_lwmi_events_unregister_notifier, nb);
|
|
}
|
|
EXPORT_SYMBOL_NS_GPL(devm_lwmi_events_register_notifier, "LENOVO_WMI_EVENTS");
|
|
|
|
/**
|
|
* lwmi_events_notify() - Call functions for the notifier call chain.
|
|
* @wdev: The parent WMI device of the driver.
|
|
* @obj: ACPI object passed by the registered WMI Event.
|
|
*
|
|
* Validate WMI event data and notify all registered drivers of the event and
|
|
* its output.
|
|
*
|
|
* Return: 0 on success, or an error code.
|
|
*/
|
|
static void lwmi_events_notify(struct wmi_device *wdev, union acpi_object *obj)
|
|
{
|
|
struct lwmi_events_priv *priv = dev_get_drvdata(&wdev->dev);
|
|
int sel_prof;
|
|
int ret;
|
|
|
|
switch (priv->type) {
|
|
case LWMI_EVENT_THERMAL_MODE:
|
|
if (obj->type != ACPI_TYPE_INTEGER)
|
|
return;
|
|
|
|
sel_prof = obj->integer.value;
|
|
|
|
switch (sel_prof) {
|
|
case LWMI_GZ_THERMAL_MODE_QUIET:
|
|
case LWMI_GZ_THERMAL_MODE_BALANCED:
|
|
case LWMI_GZ_THERMAL_MODE_PERFORMANCE:
|
|
case LWMI_GZ_THERMAL_MODE_EXTREME:
|
|
case LWMI_GZ_THERMAL_MODE_CUSTOM:
|
|
ret = blocking_notifier_call_chain(&events_chain_head,
|
|
LWMI_EVENT_THERMAL_MODE,
|
|
&sel_prof);
|
|
if (ret == NOTIFY_BAD)
|
|
dev_err(&wdev->dev,
|
|
"Failed to send notification to call chain for WMI Events\n");
|
|
return;
|
|
default:
|
|
dev_err(&wdev->dev, "Got invalid thermal mode: %x",
|
|
sel_prof);
|
|
return;
|
|
}
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
static int lwmi_events_probe(struct wmi_device *wdev, const void *context)
|
|
{
|
|
struct lwmi_events_priv *priv;
|
|
|
|
if (!context)
|
|
return -EINVAL;
|
|
|
|
priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
priv->wdev = wdev;
|
|
priv->type = *(enum lwmi_events_type *)context;
|
|
dev_set_drvdata(&wdev->dev, priv);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct wmi_device_id lwmi_events_id_table[] = {
|
|
{ LWMI_EVENT_DEVICE(THERMAL_MODE_EVENT_GUID, LWMI_EVENT_THERMAL_MODE) },
|
|
{}
|
|
};
|
|
|
|
static struct wmi_driver lwmi_events_driver = {
|
|
.driver = {
|
|
.name = "lenovo_wmi_events",
|
|
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
},
|
|
.id_table = lwmi_events_id_table,
|
|
.probe = lwmi_events_probe,
|
|
.notify = lwmi_events_notify,
|
|
.no_singleton = true,
|
|
};
|
|
|
|
module_wmi_driver(lwmi_events_driver);
|
|
|
|
MODULE_DEVICE_TABLE(wmi, lwmi_events_id_table);
|
|
MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
|
|
MODULE_DESCRIPTION("Lenovo WMI Events Driver");
|
|
MODULE_LICENSE("GPL");
|