File Index
Files and directories
Sign in
FILE

wmi-capdata01.c

File preview with syntax highlighting.

linux-6.19.14/drivers/platform/x86/lenovo/wmi-capdata01.c C
  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
 36struct lwmi_cd01_priv {
 37	struct notifier_block acpi_nb; /* ACPI events */
 38	struct wmi_device *wdev;
 39	struct cd01_list *list;
 40};
 41
 42struct 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 */
 60static 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
 71static 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 */
 86int 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}
100EXPORT_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 */
110static 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 */
142static 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 */
175static 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 */
198static 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 */
226static void lwmi_cd01_unregister(void *data)
227{
228	struct notifier_block *acpi_nb = data;
229
230	unregister_acpi_notifier(acpi_nb);
231}
232
233static 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
263static void lwmi_cd01_remove(struct wmi_device *wdev)
264{
265	component_del(&wdev->dev, &lwmi_cd01_component_ops);
266}
267
268static const struct wmi_device_id lwmi_cd01_id_table[] = {
269	{ LENOVO_CAPABILITY_DATA_01_GUID, NULL },
270	{}
271};
272
273static 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 */
291int lwmi_cd01_match(struct device *dev, void *data)
292{
293	return dev->driver == &lwmi_cd01_driver.driver;
294}
295EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD01");
296
297module_wmi_driver(lwmi_cd01_driver);
298
299MODULE_DEVICE_TABLE(wmi, lwmi_cd01_id_table);
300MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
301MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver");
302MODULE_LICENSE("GPL");
~/ Read-only file preview.