xref: /linux/drivers/platform/x86/lenovo/wmi-events.c (revision 22c55fb9eb92395d999b8404d73e58540d11bdd8)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Lenovo WMI Events driver. Lenovo WMI interfaces provide various
4  * hardware triggered events that many drivers need to have propagated.
5  * This driver provides a uniform entrypoint for these events so that
6  * any driver that needs to respond to these events can subscribe to a
7  * notifier chain.
8  *
9  * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
10  */
11 
12 #include <linux/acpi.h>
13 #include <linux/export.h>
14 #include <linux/module.h>
15 #include <linux/notifier.h>
16 #include <linux/types.h>
17 #include <linux/wmi.h>
18 
19 #include "wmi-events.h"
20 #include "wmi-gamezone.h"
21 
22 #define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
23 
24 #define LWMI_EVENT_DEVICE(guid, type)                        \
25 	.guid_string = (guid), .context = &(enum lwmi_events_type) \
26 	{                                                          \
27 		type                                               \
28 	}
29 
30 static BLOCKING_NOTIFIER_HEAD(events_chain_head);
31 
32 struct lwmi_events_priv {
33 	struct wmi_device *wdev;
34 	enum lwmi_events_type type;
35 };
36 
37 /**
38  * lwmi_events_register_notifier() - Add a notifier to the notifier chain.
39  * @nb: The notifier_block struct to register
40  *
41  * Call blocking_notifier_chain_register to register the notifier block to the
42  * lenovo-wmi-events driver blocking notifier chain.
43  *
44  * Return: 0 on success, %-EEXIST on error.
45  */
46 int lwmi_events_register_notifier(struct notifier_block *nb)
47 {
48 	return blocking_notifier_chain_register(&events_chain_head, nb);
49 }
50 EXPORT_SYMBOL_NS_GPL(lwmi_events_register_notifier, "LENOVO_WMI_EVENTS");
51 
52 /**
53  * lwmi_events_unregister_notifier() - Remove a notifier from the notifier
54  * chain.
55  * @nb: The notifier_block struct to unregister
56  *
57  * Call blocking_notifier_chain_unregister to unregister the notifier block
58  * from the lenovo-wmi-events driver blocking notifier chain.
59  *
60  * Return: 0 on success, %-ENOENT on error.
61  */
62 int lwmi_events_unregister_notifier(struct notifier_block *nb)
63 {
64 	return blocking_notifier_chain_unregister(&events_chain_head, nb);
65 }
66 EXPORT_SYMBOL_NS_GPL(lwmi_events_unregister_notifier, "LENOVO_WMI_EVENTS");
67 
68 /**
69  * devm_lwmi_events_unregister_notifier() - Remove a notifier from the notifier
70  * chain.
71  * @data: Void pointer to the notifier_block struct to unregister.
72  *
73  * Call lwmi_events_unregister_notifier to unregister the notifier block from
74  * the lenovo-wmi-events driver blocking notifier chain.
75  *
76  * Return: 0 on success, %-ENOENT on error.
77  */
78 static void devm_lwmi_events_unregister_notifier(void *data)
79 {
80 	struct notifier_block *nb = data;
81 
82 	lwmi_events_unregister_notifier(nb);
83 }
84 
85 /**
86  * devm_lwmi_events_register_notifier() - Add a notifier to the notifier chain.
87  * @dev: The parent device of the notifier_block struct.
88  * @nb: The notifier_block struct to register
89  *
90  * Call lwmi_events_register_notifier to register the notifier block to the
91  * lenovo-wmi-events driver blocking notifier chain. Then add, as a device
92  * managed action, unregister_notifier to automatically unregister the
93  * notifier block upon its parent device removal.
94  *
95  * Return: 0 on success, or an error code.
96  */
97 int devm_lwmi_events_register_notifier(struct device *dev,
98 				       struct notifier_block *nb)
99 {
100 	int ret;
101 
102 	ret = lwmi_events_register_notifier(nb);
103 	if (ret < 0)
104 		return ret;
105 
106 	return devm_add_action_or_reset(dev, devm_lwmi_events_unregister_notifier, nb);
107 }
108 EXPORT_SYMBOL_NS_GPL(devm_lwmi_events_register_notifier, "LENOVO_WMI_EVENTS");
109 
110 /**
111  * lwmi_events_notify() - Call functions for the notifier call chain.
112  * @wdev: The parent WMI device of the driver.
113  * @obj: ACPI object passed by the registered WMI Event.
114  *
115  * Validate WMI event data and notify all registered drivers of the event and
116  * its output.
117  *
118  * Return: 0 on success, or an error code.
119  */
120 static void lwmi_events_notify(struct wmi_device *wdev, union acpi_object *obj)
121 {
122 	struct lwmi_events_priv *priv = dev_get_drvdata(&wdev->dev);
123 	int sel_prof;
124 	int ret;
125 
126 	switch (priv->type) {
127 	case LWMI_EVENT_THERMAL_MODE:
128 		if (obj->type != ACPI_TYPE_INTEGER)
129 			return;
130 
131 		sel_prof = obj->integer.value;
132 
133 		switch (sel_prof) {
134 		case LWMI_GZ_THERMAL_MODE_QUIET:
135 		case LWMI_GZ_THERMAL_MODE_BALANCED:
136 		case LWMI_GZ_THERMAL_MODE_PERFORMANCE:
137 		case LWMI_GZ_THERMAL_MODE_EXTREME:
138 		case LWMI_GZ_THERMAL_MODE_CUSTOM:
139 			ret = blocking_notifier_call_chain(&events_chain_head,
140 							   LWMI_EVENT_THERMAL_MODE,
141 							   &sel_prof);
142 			if (ret == NOTIFY_BAD)
143 				dev_err(&wdev->dev,
144 					"Failed to send notification to call chain for WMI Events\n");
145 			return;
146 		default:
147 			dev_err(&wdev->dev, "Got invalid thermal mode: %x",
148 				sel_prof);
149 			return;
150 		}
151 		break;
152 	default:
153 		return;
154 	}
155 }
156 
157 static int lwmi_events_probe(struct wmi_device *wdev, const void *context)
158 {
159 	struct lwmi_events_priv *priv;
160 
161 	if (!context)
162 		return -EINVAL;
163 
164 	priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
165 	if (!priv)
166 		return -ENOMEM;
167 
168 	priv->wdev = wdev;
169 	priv->type = *(enum lwmi_events_type *)context;
170 	dev_set_drvdata(&wdev->dev, priv);
171 
172 	return 0;
173 }
174 
175 static const struct wmi_device_id lwmi_events_id_table[] = {
176 	{ LWMI_EVENT_DEVICE(THERMAL_MODE_EVENT_GUID, LWMI_EVENT_THERMAL_MODE) },
177 	{}
178 };
179 
180 static struct wmi_driver lwmi_events_driver = {
181 	.driver = {
182 		.name = "lenovo_wmi_events",
183 		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
184 	},
185 	.id_table = lwmi_events_id_table,
186 	.probe = lwmi_events_probe,
187 	.notify = lwmi_events_notify,
188 	.no_singleton = true,
189 };
190 
191 module_wmi_driver(lwmi_events_driver);
192 
193 MODULE_DEVICE_TABLE(wmi, lwmi_events_id_table);
194 MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
195 MODULE_DESCRIPTION("Lenovo WMI Events Driver");
196 MODULE_LICENSE("GPL");
197