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