xref: /linux/drivers/platform/x86/lenovo/wmi-capdata01.c (revision 9669b2499ea377764f8320dd562dd6cd4ea80a5d)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Lenovo Capability Data 01 WMI Data Block driver.
4  *
5  * Lenovo Capability Data 01 provides information on tunable attributes used by
6  * the "Other Mode" WMI interface. The data includes if the attribute is
7  * supported by the hardware, the default_value, max_value, min_value, and step
8  * increment. Each attribute has multiple pages, one for each of the thermal
9  * modes managed by the Gamezone interface.
10  *
11  * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
12  */
13 
14 #include <linux/acpi.h>
15 #include <linux/cleanup.h>
16 #include <linux/component.h>
17 #include <linux/container_of.h>
18 #include <linux/device.h>
19 #include <linux/export.h>
20 #include <linux/gfp_types.h>
21 #include <linux/module.h>
22 #include <linux/mutex.h>
23 #include <linux/mutex_types.h>
24 #include <linux/notifier.h>
25 #include <linux/overflow.h>
26 #include <linux/types.h>
27 #include <linux/wmi.h>
28 
29 #include "wmi-capdata01.h"
30 
31 #define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
32 
33 #define ACPI_AC_CLASS "ac_adapter"
34 #define ACPI_AC_NOTIFY_STATUS 0x80
35 
36 struct lwmi_cd01_priv {
37 	struct notifier_block acpi_nb; /* ACPI events */
38 	struct wmi_device *wdev;
39 	struct cd01_list *list;
40 };
41 
42 struct cd01_list {
43 	struct mutex list_mutex; /* list R/W mutex */
44 	u8 count;
45 	struct capdata01 data[];
46 };
47 
48 /**
49  * lwmi_cd01_component_bind() - Bind component to master device.
50  * @cd01_dev: Pointer to the lenovo-wmi-capdata01 driver parent device.
51  * @om_dev: Pointer to the lenovo-wmi-other driver parent device.
52  * @data: capdata01_list object pointer used to return the capability data.
53  *
54  * On lenovo-wmi-other's master bind, provide a pointer to the local capdata01
55  * list. This is used to call lwmi_cd01_get_data to look up attribute data
56  * from the lenovo-wmi-other driver.
57  *
58  * Return: 0
59  */
lwmi_cd01_component_bind(struct device * cd01_dev,struct device * om_dev,void * data)60 static int lwmi_cd01_component_bind(struct device *cd01_dev,
61 				    struct device *om_dev, void *data)
62 {
63 	struct lwmi_cd01_priv *priv = dev_get_drvdata(cd01_dev);
64 	struct cd01_list **cd01_list = data;
65 
66 	*cd01_list = priv->list;
67 
68 	return 0;
69 }
70 
71 static const struct component_ops lwmi_cd01_component_ops = {
72 	.bind = lwmi_cd01_component_bind,
73 };
74 
75 /**
76  * lwmi_cd01_get_data - Get the data of the specified attribute
77  * @list: The lenovo-wmi-capdata01 pointer to its cd01_list struct.
78  * @attribute_id: The capdata attribute ID to be found.
79  * @output: Pointer to a capdata01 struct to return the data.
80  *
81  * Retrieves the capability data 01 struct pointer for the given
82  * attribute for its specified thermal mode.
83  *
84  * Return: 0 on success, or -EINVAL.
85  */
lwmi_cd01_get_data(struct cd01_list * list,u32 attribute_id,struct capdata01 * output)86 int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata01 *output)
87 {
88 	u8 idx;
89 
90 	guard(mutex)(&list->list_mutex);
91 	for (idx = 0; idx < list->count; idx++) {
92 		if (list->data[idx].id != attribute_id)
93 			continue;
94 		memcpy(output, &list->data[idx], sizeof(list->data[idx]));
95 		return 0;
96 	};
97 
98 	return -EINVAL;
99 }
100 EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD01");
101 
102 /**
103  * lwmi_cd01_cache() - Cache all WMI data block information
104  * @priv: lenovo-wmi-capdata01 driver data.
105  *
106  * Loop through each WMI data block and cache the data.
107  *
108  * Return: 0 on success, or an error.
109  */
lwmi_cd01_cache(struct lwmi_cd01_priv * priv)110 static int lwmi_cd01_cache(struct lwmi_cd01_priv *priv)
111 {
112 	int idx;
113 
114 	guard(mutex)(&priv->list->list_mutex);
115 	for (idx = 0; idx < priv->list->count; idx++) {
116 		union acpi_object *ret_obj __free(kfree) = NULL;
117 
118 		ret_obj = wmidev_block_query(priv->wdev, idx);
119 		if (!ret_obj)
120 			return -ENODEV;
121 
122 		if (ret_obj->type != ACPI_TYPE_BUFFER ||
123 		    ret_obj->buffer.length < sizeof(priv->list->data[idx]))
124 			continue;
125 
126 		memcpy(&priv->list->data[idx], ret_obj->buffer.pointer,
127 		       ret_obj->buffer.length);
128 	}
129 
130 	return 0;
131 }
132 
133 /**
134  * lwmi_cd01_alloc() - Allocate a cd01_list struct in drvdata
135  * @priv: lenovo-wmi-capdata01 driver data.
136  *
137  * Allocate a cd01_list struct large enough to contain data from all WMI data
138  * blocks provided by the interface.
139  *
140  * Return: 0 on success, or an error.
141  */
lwmi_cd01_alloc(struct lwmi_cd01_priv * priv)142 static int lwmi_cd01_alloc(struct lwmi_cd01_priv *priv)
143 {
144 	struct cd01_list *list;
145 	size_t list_size;
146 	int count, ret;
147 
148 	count = wmidev_instance_count(priv->wdev);
149 	list_size = struct_size(list, data, count);
150 
151 	list = devm_kzalloc(&priv->wdev->dev, list_size, GFP_KERNEL);
152 	if (!list)
153 		return -ENOMEM;
154 
155 	ret = devm_mutex_init(&priv->wdev->dev, &list->list_mutex);
156 	if (ret)
157 		return ret;
158 
159 	list->count = count;
160 	priv->list = list;
161 
162 	return 0;
163 }
164 
165 /**
166  * lwmi_cd01_setup() - Cache all WMI data block information
167  * @priv: lenovo-wmi-capdata01 driver data.
168  *
169  * Allocate a cd01_list struct large enough to contain data from all WMI data
170  * blocks provided by the interface. Then loop through each data block and
171  * cache the data.
172  *
173  * Return: 0 on success, or an error code.
174  */
lwmi_cd01_setup(struct lwmi_cd01_priv * priv)175 static int lwmi_cd01_setup(struct lwmi_cd01_priv *priv)
176 {
177 	int ret;
178 
179 	ret = lwmi_cd01_alloc(priv);
180 	if (ret)
181 		return ret;
182 
183 	return lwmi_cd01_cache(priv);
184 }
185 
186 /**
187  * lwmi_cd01_notifier_call() - Call method for lenovo-wmi-capdata01 driver notifier.
188  * block call chain.
189  * @nb: The notifier_block registered to lenovo-wmi-events driver.
190  * @action: Unused.
191  * @data: The ACPI event.
192  *
193  * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile
194  * of a change.
195  *
196  * Return: notifier_block status.
197  */
lwmi_cd01_notifier_call(struct notifier_block * nb,unsigned long action,void * data)198 static int lwmi_cd01_notifier_call(struct notifier_block *nb, unsigned long action,
199 				   void *data)
200 {
201 	struct acpi_bus_event *event = data;
202 	struct lwmi_cd01_priv *priv;
203 	int ret;
204 
205 	if (strcmp(event->device_class, ACPI_AC_CLASS) != 0)
206 		return NOTIFY_DONE;
207 
208 	priv = container_of(nb, struct lwmi_cd01_priv, acpi_nb);
209 
210 	switch (event->type) {
211 	case ACPI_AC_NOTIFY_STATUS:
212 		ret = lwmi_cd01_cache(priv);
213 		if (ret)
214 			return NOTIFY_BAD;
215 
216 		return NOTIFY_OK;
217 	default:
218 		return NOTIFY_DONE;
219 	}
220 }
221 
222 /**
223  * lwmi_cd01_unregister() - Unregister the cd01 ACPI notifier_block.
224  * @data: The ACPI event notifier_block to unregister.
225  */
lwmi_cd01_unregister(void * data)226 static void lwmi_cd01_unregister(void *data)
227 {
228 	struct notifier_block *acpi_nb = data;
229 
230 	unregister_acpi_notifier(acpi_nb);
231 }
232 
lwmi_cd01_probe(struct wmi_device * wdev,const void * context)233 static int lwmi_cd01_probe(struct wmi_device *wdev, const void *context)
234 
235 {
236 	struct lwmi_cd01_priv *priv;
237 	int ret;
238 
239 	priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
240 	if (!priv)
241 		return -ENOMEM;
242 
243 	priv->wdev = wdev;
244 	dev_set_drvdata(&wdev->dev, priv);
245 
246 	ret = lwmi_cd01_setup(priv);
247 	if (ret)
248 		return ret;
249 
250 	priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call;
251 
252 	ret = register_acpi_notifier(&priv->acpi_nb);
253 	if (ret)
254 		return ret;
255 
256 	ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister, &priv->acpi_nb);
257 	if (ret)
258 		return ret;
259 
260 	return component_add(&wdev->dev, &lwmi_cd01_component_ops);
261 }
262 
lwmi_cd01_remove(struct wmi_device * wdev)263 static void lwmi_cd01_remove(struct wmi_device *wdev)
264 {
265 	component_del(&wdev->dev, &lwmi_cd01_component_ops);
266 }
267 
268 static const struct wmi_device_id lwmi_cd01_id_table[] = {
269 	{ LENOVO_CAPABILITY_DATA_01_GUID, NULL },
270 	{}
271 };
272 
273 static struct wmi_driver lwmi_cd01_driver = {
274 	.driver = {
275 		.name = "lenovo_wmi_cd01",
276 		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
277 	},
278 	.id_table = lwmi_cd01_id_table,
279 	.probe = lwmi_cd01_probe,
280 	.remove = lwmi_cd01_remove,
281 	.no_singleton = true,
282 };
283 
284 /**
285  * lwmi_cd01_match() - Match rule for the master driver.
286  * @dev: Pointer to the capability data 01 parent device.
287  * @data: Unused void pointer for passing match criteria.
288  *
289  * Return: int.
290  */
lwmi_cd01_match(struct device * dev,void * data)291 int lwmi_cd01_match(struct device *dev, void *data)
292 {
293 	return dev->driver == &lwmi_cd01_driver.driver;
294 }
295 EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD01");
296 
297 module_wmi_driver(lwmi_cd01_driver);
298 
299 MODULE_DEVICE_TABLE(wmi, lwmi_cd01_id_table);
300 MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
301 MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver");
302 MODULE_LICENSE("GPL");
303