xref: /linux/drivers/platform/x86/lenovo/wmi-capdata.c (revision c17ee635fd3a482b2ad2bf5e269755c2eae5f25e)
1f28d76b1SRong Zhang // SPDX-License-Identifier: GPL-2.0-or-later
2f28d76b1SRong Zhang /*
3f28d76b1SRong Zhang  * Lenovo Capability Data WMI Data Block driver.
4f28d76b1SRong Zhang  *
5f28d76b1SRong Zhang  * Lenovo Capability Data provides information on tunable attributes used by
6f28d76b1SRong Zhang  * the "Other Mode" WMI interface.
7f28d76b1SRong Zhang  *
8c05f67e6SRong Zhang  * Capability Data 00 includes if the attribute is supported by the hardware,
9c05f67e6SRong Zhang  * and the default_value. All attributes are independent of thermal modes.
10c05f67e6SRong Zhang  *
11f28d76b1SRong Zhang  * Capability Data 01 includes if the attribute is supported by the hardware,
12f28d76b1SRong Zhang  * and the default_value, max_value, min_value, and step increment. Each
13f28d76b1SRong Zhang  * attribute has multiple pages, one for each of the thermal modes managed by
14f28d76b1SRong Zhang  * the Gamezone interface.
15f28d76b1SRong Zhang  *
16012a8f96SRong Zhang  * Fan Test Data includes the max/min fan speed RPM for each fan. This is
17012a8f96SRong Zhang  * reference data for self-test. If the fan is in good condition, it is capable
18012a8f96SRong Zhang  * to spin faster than max RPM or slower than min RPM.
19012a8f96SRong Zhang  *
20f28d76b1SRong Zhang  * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
21f28d76b1SRong Zhang  *   - Initial implementation (formerly named lenovo-wmi-capdata01)
224ff1a029SRong Zhang  *
234ff1a029SRong Zhang  * Copyright (C) 2025 Rong Zhang <i@rong.moe>
244ff1a029SRong Zhang  *   - Unified implementation
25f28d76b1SRong Zhang  */
26f28d76b1SRong Zhang 
274ff1a029SRong Zhang #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
284ff1a029SRong Zhang 
29f28d76b1SRong Zhang #include <linux/acpi.h>
30*67d9a39cSRong Zhang #include <linux/bitfield.h>
314ff1a029SRong Zhang #include <linux/bug.h>
32f28d76b1SRong Zhang #include <linux/cleanup.h>
33f28d76b1SRong Zhang #include <linux/component.h>
34f28d76b1SRong Zhang #include <linux/container_of.h>
35f28d76b1SRong Zhang #include <linux/device.h>
364ff1a029SRong Zhang #include <linux/dev_printk.h>
374ff1a029SRong Zhang #include <linux/err.h>
38f28d76b1SRong Zhang #include <linux/export.h>
39f28d76b1SRong Zhang #include <linux/gfp_types.h>
40012a8f96SRong Zhang #include <linux/limits.h>
41f28d76b1SRong Zhang #include <linux/module.h>
42f28d76b1SRong Zhang #include <linux/mutex.h>
43f28d76b1SRong Zhang #include <linux/mutex_types.h>
44f28d76b1SRong Zhang #include <linux/notifier.h>
45f28d76b1SRong Zhang #include <linux/overflow.h>
464ff1a029SRong Zhang #include <linux/stddef.h>
47f28d76b1SRong Zhang #include <linux/types.h>
48f28d76b1SRong Zhang #include <linux/wmi.h>
49f28d76b1SRong Zhang 
50f28d76b1SRong Zhang #include "wmi-capdata.h"
51f28d76b1SRong Zhang 
52c05f67e6SRong Zhang #define LENOVO_CAPABILITY_DATA_00_GUID "362A3AFE-3D96-4665-8530-96DAD5BB300E"
53f28d76b1SRong Zhang #define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
54012a8f96SRong Zhang #define LENOVO_FAN_TEST_DATA_GUID "B642801B-3D21-45DE-90AE-6E86F164FB21"
55f28d76b1SRong Zhang 
56f28d76b1SRong Zhang #define ACPI_AC_CLASS "ac_adapter"
57f28d76b1SRong Zhang #define ACPI_AC_NOTIFY_STATUS 0x80
58f28d76b1SRong Zhang 
59*67d9a39cSRong Zhang #define LWMI_FEATURE_ID_FAN_TEST 0x05
60*67d9a39cSRong Zhang 
61*67d9a39cSRong Zhang #define LWMI_ATTR_ID_FAN_TEST							\
62*67d9a39cSRong Zhang 	(FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, LWMI_DEVICE_ID_FAN) |		\
63*67d9a39cSRong Zhang 	 FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, LWMI_FEATURE_ID_FAN_TEST))
64*67d9a39cSRong Zhang 
654ff1a029SRong Zhang enum lwmi_cd_type {
66c05f67e6SRong Zhang 	LENOVO_CAPABILITY_DATA_00,
674ff1a029SRong Zhang 	LENOVO_CAPABILITY_DATA_01,
68012a8f96SRong Zhang 	LENOVO_FAN_TEST_DATA,
69*67d9a39cSRong Zhang 	CD_TYPE_NONE = -1,
704ff1a029SRong Zhang };
714ff1a029SRong Zhang 
724ff1a029SRong Zhang #define LWMI_CD_TABLE_ITEM(_type)		\
734ff1a029SRong Zhang 	[_type] = {				\
744ff1a029SRong Zhang 		.name = #_type,			\
754ff1a029SRong Zhang 		.type = _type,			\
764ff1a029SRong Zhang 	}
774ff1a029SRong Zhang 
784ff1a029SRong Zhang static const struct lwmi_cd_info {
794ff1a029SRong Zhang 	const char *name;
804ff1a029SRong Zhang 	enum lwmi_cd_type type;
814ff1a029SRong Zhang } lwmi_cd_table[] = {
82c05f67e6SRong Zhang 	LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_00),
834ff1a029SRong Zhang 	LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_01),
84012a8f96SRong Zhang 	LWMI_CD_TABLE_ITEM(LENOVO_FAN_TEST_DATA),
854ff1a029SRong Zhang };
864ff1a029SRong Zhang 
87f28d76b1SRong Zhang struct lwmi_cd_priv {
88f28d76b1SRong Zhang 	struct notifier_block acpi_nb; /* ACPI events */
89f28d76b1SRong Zhang 	struct wmi_device *wdev;
90f28d76b1SRong Zhang 	struct cd_list *list;
91*67d9a39cSRong Zhang 
92*67d9a39cSRong Zhang 	/*
93*67d9a39cSRong Zhang 	 * A capdata device may be a component master of another capdata device.
94*67d9a39cSRong Zhang 	 * E.g., lenovo-wmi-other <-> capdata00 <-> capdata_fan
95*67d9a39cSRong Zhang 	 *       |- master            |- component
96*67d9a39cSRong Zhang 	 *                            |- sub-master
97*67d9a39cSRong Zhang 	 *                                          |- sub-component
98*67d9a39cSRong Zhang 	 */
99*67d9a39cSRong Zhang 	struct lwmi_cd_sub_master_priv {
100*67d9a39cSRong Zhang 		struct device *master_dev;
101*67d9a39cSRong Zhang 		cd_list_cb_t master_cb;
102*67d9a39cSRong Zhang 		struct cd_list *sub_component_list; /* ERR_PTR(-ENODEV) implies no sub-component. */
103*67d9a39cSRong Zhang 		bool registered;                    /* Has the sub-master been registered? */
104*67d9a39cSRong Zhang 	} *sub_master;
105f28d76b1SRong Zhang };
106f28d76b1SRong Zhang 
107f28d76b1SRong Zhang struct cd_list {
108f28d76b1SRong Zhang 	struct mutex list_mutex; /* list R/W mutex */
1094ff1a029SRong Zhang 	enum lwmi_cd_type type;
110f28d76b1SRong Zhang 	u8 count;
1114ff1a029SRong Zhang 
1124ff1a029SRong Zhang 	union {
113c05f67e6SRong Zhang 		DECLARE_FLEX_ARRAY(struct capdata00, cd00);
1144ff1a029SRong Zhang 		DECLARE_FLEX_ARRAY(struct capdata01, cd01);
115012a8f96SRong Zhang 		DECLARE_FLEX_ARRAY(struct capdata_fan, cd_fan);
116f28d76b1SRong Zhang 	};
1174ff1a029SRong Zhang };
1184ff1a029SRong Zhang 
1194ff1a029SRong Zhang static struct wmi_driver lwmi_cd_driver;
1204ff1a029SRong Zhang 
1214ff1a029SRong Zhang /**
1224ff1a029SRong Zhang  * lwmi_cd_match() - Match rule for the master driver.
1234ff1a029SRong Zhang  * @dev: Pointer to the capability data parent device.
1244ff1a029SRong Zhang  * @type: Pointer to capability data type (enum lwmi_cd_type *) to match.
1254ff1a029SRong Zhang  *
1264ff1a029SRong Zhang  * Return: int.
1274ff1a029SRong Zhang  */
1284ff1a029SRong Zhang static int lwmi_cd_match(struct device *dev, void *type)
1294ff1a029SRong Zhang {
1304ff1a029SRong Zhang 	struct lwmi_cd_priv *priv;
1314ff1a029SRong Zhang 
1324ff1a029SRong Zhang 	if (dev->driver != &lwmi_cd_driver.driver)
1334ff1a029SRong Zhang 		return false;
1344ff1a029SRong Zhang 
1354ff1a029SRong Zhang 	priv = dev_get_drvdata(dev);
1364ff1a029SRong Zhang 	return priv->list->type == *(enum lwmi_cd_type *)type;
1374ff1a029SRong Zhang }
1384ff1a029SRong Zhang 
1394ff1a029SRong Zhang /**
1404ff1a029SRong Zhang  * lwmi_cd_match_add_all() - Add all match rule for the master driver.
1414ff1a029SRong Zhang  * @master: Pointer to the master device.
1424ff1a029SRong Zhang  * @matchptr: Pointer to the returned component_match pointer.
1434ff1a029SRong Zhang  *
1444ff1a029SRong Zhang  * Adds all component matches to the list stored in @matchptr for the @master
1454ff1a029SRong Zhang  * device. @matchptr must be initialized to NULL.
1464ff1a029SRong Zhang  */
1474ff1a029SRong Zhang void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr)
1484ff1a029SRong Zhang {
1494ff1a029SRong Zhang 	int i;
1504ff1a029SRong Zhang 
1514ff1a029SRong Zhang 	if (WARN_ON(*matchptr))
1524ff1a029SRong Zhang 		return;
1534ff1a029SRong Zhang 
1544ff1a029SRong Zhang 	for (i = 0; i < ARRAY_SIZE(lwmi_cd_table); i++) {
155012a8f96SRong Zhang 		/* Skip sub-components. */
156012a8f96SRong Zhang 		if (lwmi_cd_table[i].type == LENOVO_FAN_TEST_DATA)
157012a8f96SRong Zhang 			continue;
158012a8f96SRong Zhang 
1594ff1a029SRong Zhang 		component_match_add(master, matchptr, lwmi_cd_match,
1604ff1a029SRong Zhang 				    (void *)&lwmi_cd_table[i].type);
1614ff1a029SRong Zhang 		if (IS_ERR(*matchptr))
1624ff1a029SRong Zhang 			return;
1634ff1a029SRong Zhang 	}
1644ff1a029SRong Zhang }
1654ff1a029SRong Zhang EXPORT_SYMBOL_NS_GPL(lwmi_cd_match_add_all, "LENOVO_WMI_CAPDATA");
166f28d76b1SRong Zhang 
167f28d76b1SRong Zhang /**
168*67d9a39cSRong Zhang  * lwmi_cd_call_master_cb() - Call the master callback for the sub-component.
169*67d9a39cSRong Zhang  * @priv: Pointer to the capability data private data.
170*67d9a39cSRong Zhang  *
171*67d9a39cSRong Zhang  * Call the master callback and pass the sub-component list to it if the
172*67d9a39cSRong Zhang  * dependency chain (master <-> sub-master <-> sub-component) is complete.
173*67d9a39cSRong Zhang  */
174*67d9a39cSRong Zhang static void lwmi_cd_call_master_cb(struct lwmi_cd_priv *priv)
175*67d9a39cSRong Zhang {
176*67d9a39cSRong Zhang 	struct cd_list *sub_component_list = priv->sub_master->sub_component_list;
177*67d9a39cSRong Zhang 
178*67d9a39cSRong Zhang 	/*
179*67d9a39cSRong Zhang 	 * Call the callback only if the dependency chain is ready:
180*67d9a39cSRong Zhang 	 * - Binding between master and sub-master: fills master_dev and master_cb
181*67d9a39cSRong Zhang 	 * - Binding between sub-master and sub-component: fills sub_component_list
182*67d9a39cSRong Zhang 	 *
183*67d9a39cSRong Zhang 	 * If a binding has been unbound before the other binding is bound, the
184*67d9a39cSRong Zhang 	 * corresponding members filled by the former are guaranteed to be cleared.
185*67d9a39cSRong Zhang 	 *
186*67d9a39cSRong Zhang 	 * This function is only called in bind callbacks, and the component
187*67d9a39cSRong Zhang 	 * framework guarantees bind/unbind callbacks may never execute
188*67d9a39cSRong Zhang 	 * simultaneously, which implies that it's impossible to have a race
189*67d9a39cSRong Zhang 	 * condition.
190*67d9a39cSRong Zhang 	 *
191*67d9a39cSRong Zhang 	 * Hence, this check is sufficient to ensure that the callback is called
192*67d9a39cSRong Zhang 	 * at most once and with the correct state, without relying on a specific
193*67d9a39cSRong Zhang 	 * sequence of binding establishment.
194*67d9a39cSRong Zhang 	 */
195*67d9a39cSRong Zhang 	if (!sub_component_list ||
196*67d9a39cSRong Zhang 	    !priv->sub_master->master_dev ||
197*67d9a39cSRong Zhang 	    !priv->sub_master->master_cb)
198*67d9a39cSRong Zhang 		return;
199*67d9a39cSRong Zhang 
200*67d9a39cSRong Zhang 	if (PTR_ERR(sub_component_list) == -ENODEV)
201*67d9a39cSRong Zhang 		sub_component_list = NULL;
202*67d9a39cSRong Zhang 	else if (WARN_ON(IS_ERR(sub_component_list)))
203*67d9a39cSRong Zhang 		return;
204*67d9a39cSRong Zhang 
205*67d9a39cSRong Zhang 	priv->sub_master->master_cb(priv->sub_master->master_dev,
206*67d9a39cSRong Zhang 				    sub_component_list);
207*67d9a39cSRong Zhang 
208*67d9a39cSRong Zhang 	/*
209*67d9a39cSRong Zhang 	 * Userspace may unbind a device from its driver and bind it again
210*67d9a39cSRong Zhang 	 * through sysfs. Let's call this operation "reprobe" to distinguish it
211*67d9a39cSRong Zhang 	 * from component "rebind".
212*67d9a39cSRong Zhang 	 *
213*67d9a39cSRong Zhang 	 * When reprobing capdata00/01 or the master device, the master device
214*67d9a39cSRong Zhang 	 * is unbound from us with appropriate cleanup before we bind to it and
215*67d9a39cSRong Zhang 	 * call master_cb. Everything is fine in this case.
216*67d9a39cSRong Zhang 	 *
217*67d9a39cSRong Zhang 	 * When reprobing capdata_fan, the master device has never been unbound
218*67d9a39cSRong Zhang 	 * from us (hence no cleanup is done)[1], but we call master_cb the
219*67d9a39cSRong Zhang 	 * second time. To solve this issue, we clear master_cb and master_dev
220*67d9a39cSRong Zhang 	 * so we won't call master_cb twice while a binding is still complete.
221*67d9a39cSRong Zhang 	 *
222*67d9a39cSRong Zhang 	 * Note that we can't clear sub_component_list, otherwise reprobing
223*67d9a39cSRong Zhang 	 * capdata01 or the master device causes master_cb to be never called
224*67d9a39cSRong Zhang 	 * after we rebind to the master device.
225*67d9a39cSRong Zhang 	 *
226*67d9a39cSRong Zhang 	 * [1]: The master device does not need capdata_fan in run time, so
227*67d9a39cSRong Zhang 	 * losing capdata_fan will not break the binding to the master device.
228*67d9a39cSRong Zhang 	 */
229*67d9a39cSRong Zhang 	priv->sub_master->master_cb = NULL;
230*67d9a39cSRong Zhang 	priv->sub_master->master_dev = NULL;
231*67d9a39cSRong Zhang }
232*67d9a39cSRong Zhang 
233*67d9a39cSRong Zhang /**
234f28d76b1SRong Zhang  * lwmi_cd_component_bind() - Bind component to master device.
235f28d76b1SRong Zhang  * @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device.
236f28d76b1SRong Zhang  * @om_dev: Pointer to the lenovo-wmi-other driver parent device.
2374ff1a029SRong Zhang  * @data: lwmi_cd_binder object pointer used to return the capability data.
238f28d76b1SRong Zhang  *
239f28d76b1SRong Zhang  * On lenovo-wmi-other's master bind, provide a pointer to the local capdata
240f28d76b1SRong Zhang  * list. This is used to call lwmi_cd*_get_data to look up attribute data
241f28d76b1SRong Zhang  * from the lenovo-wmi-other driver.
242f28d76b1SRong Zhang  *
243*67d9a39cSRong Zhang  * If cd_dev is a sub-master, try to call the master callback.
244*67d9a39cSRong Zhang  *
245f28d76b1SRong Zhang  * Return: 0
246f28d76b1SRong Zhang  */
247f28d76b1SRong Zhang static int lwmi_cd_component_bind(struct device *cd_dev,
248f28d76b1SRong Zhang 				  struct device *om_dev, void *data)
249f28d76b1SRong Zhang {
250f28d76b1SRong Zhang 	struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev);
2514ff1a029SRong Zhang 	struct lwmi_cd_binder *binder = data;
252f28d76b1SRong Zhang 
2534ff1a029SRong Zhang 	switch (priv->list->type) {
254c05f67e6SRong Zhang 	case LENOVO_CAPABILITY_DATA_00:
255c05f67e6SRong Zhang 		binder->cd00_list = priv->list;
256*67d9a39cSRong Zhang 
257*67d9a39cSRong Zhang 		priv->sub_master->master_dev = om_dev;
258*67d9a39cSRong Zhang 		priv->sub_master->master_cb = binder->cd_fan_list_cb;
259*67d9a39cSRong Zhang 		lwmi_cd_call_master_cb(priv);
260*67d9a39cSRong Zhang 
261c05f67e6SRong Zhang 		break;
2624ff1a029SRong Zhang 	case LENOVO_CAPABILITY_DATA_01:
2634ff1a029SRong Zhang 		binder->cd01_list = priv->list;
2644ff1a029SRong Zhang 		break;
2654ff1a029SRong Zhang 	default:
2664ff1a029SRong Zhang 		return -EINVAL;
2674ff1a029SRong Zhang 	}
268f28d76b1SRong Zhang 
269f28d76b1SRong Zhang 	return 0;
270f28d76b1SRong Zhang }
271f28d76b1SRong Zhang 
272*67d9a39cSRong Zhang /**
273*67d9a39cSRong Zhang  * lwmi_cd_component_unbind() - Unbind component to master device.
274*67d9a39cSRong Zhang  * @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device.
275*67d9a39cSRong Zhang  * @om_dev: Pointer to the lenovo-wmi-other driver parent device.
276*67d9a39cSRong Zhang  * @data: Unused.
277*67d9a39cSRong Zhang  *
278*67d9a39cSRong Zhang  * If cd_dev is a sub-master, clear the collected data from the master device to
279*67d9a39cSRong Zhang  * prevent the binding establishment between the sub-master and the sub-
280*67d9a39cSRong Zhang  * component (if it's about to happen) from calling the master callback.
281*67d9a39cSRong Zhang  */
282*67d9a39cSRong Zhang static void lwmi_cd_component_unbind(struct device *cd_dev,
283*67d9a39cSRong Zhang 				     struct device *om_dev, void *data)
284*67d9a39cSRong Zhang {
285*67d9a39cSRong Zhang 	struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev);
286*67d9a39cSRong Zhang 
287*67d9a39cSRong Zhang 	switch (priv->list->type) {
288*67d9a39cSRong Zhang 	case LENOVO_CAPABILITY_DATA_00:
289*67d9a39cSRong Zhang 		priv->sub_master->master_dev = NULL;
290*67d9a39cSRong Zhang 		priv->sub_master->master_cb = NULL;
291*67d9a39cSRong Zhang 		return;
292*67d9a39cSRong Zhang 	default:
293*67d9a39cSRong Zhang 		return;
294*67d9a39cSRong Zhang 	}
295*67d9a39cSRong Zhang }
296*67d9a39cSRong Zhang 
297f28d76b1SRong Zhang static const struct component_ops lwmi_cd_component_ops = {
298f28d76b1SRong Zhang 	.bind = lwmi_cd_component_bind,
299*67d9a39cSRong Zhang 	.unbind = lwmi_cd_component_unbind,
300*67d9a39cSRong Zhang };
301*67d9a39cSRong Zhang 
302*67d9a39cSRong Zhang /**
303*67d9a39cSRong Zhang  * lwmi_cd_sub_master_bind() - Bind sub-component of sub-master device
304*67d9a39cSRong Zhang  * @dev: The sub-master capdata basic device.
305*67d9a39cSRong Zhang  *
306*67d9a39cSRong Zhang  * Call component_bind_all to bind the sub-component device to the sub-master
307*67d9a39cSRong Zhang  * device. On success, collect the pointer to the sub-component list and try
308*67d9a39cSRong Zhang  * to call the master callback.
309*67d9a39cSRong Zhang  *
310*67d9a39cSRong Zhang  * Return: 0 on success, or an error code.
311*67d9a39cSRong Zhang  */
312*67d9a39cSRong Zhang static int lwmi_cd_sub_master_bind(struct device *dev)
313*67d9a39cSRong Zhang {
314*67d9a39cSRong Zhang 	struct lwmi_cd_priv *priv = dev_get_drvdata(dev);
315*67d9a39cSRong Zhang 	struct cd_list *sub_component_list;
316*67d9a39cSRong Zhang 	int ret;
317*67d9a39cSRong Zhang 
318*67d9a39cSRong Zhang 	ret = component_bind_all(dev, &sub_component_list);
319*67d9a39cSRong Zhang 	if (ret)
320*67d9a39cSRong Zhang 		return ret;
321*67d9a39cSRong Zhang 
322*67d9a39cSRong Zhang 	priv->sub_master->sub_component_list = sub_component_list;
323*67d9a39cSRong Zhang 	lwmi_cd_call_master_cb(priv);
324*67d9a39cSRong Zhang 
325*67d9a39cSRong Zhang 	return 0;
326*67d9a39cSRong Zhang }
327*67d9a39cSRong Zhang 
328*67d9a39cSRong Zhang /**
329*67d9a39cSRong Zhang  * lwmi_cd_sub_master_unbind() - Unbind sub-component of sub-master device
330*67d9a39cSRong Zhang  * @dev: The sub-master capdata basic device
331*67d9a39cSRong Zhang  *
332*67d9a39cSRong Zhang  * Clear the collected pointer to the sub-component list to prevent the binding
333*67d9a39cSRong Zhang  * establishment between the sub-master and the sub-component (if it's about to
334*67d9a39cSRong Zhang  * happen) from calling the master callback. Then, call component_unbind_all to
335*67d9a39cSRong Zhang  * unbind the sub-component device from the sub-master device.
336*67d9a39cSRong Zhang  */
337*67d9a39cSRong Zhang static void lwmi_cd_sub_master_unbind(struct device *dev)
338*67d9a39cSRong Zhang {
339*67d9a39cSRong Zhang 	struct lwmi_cd_priv *priv = dev_get_drvdata(dev);
340*67d9a39cSRong Zhang 
341*67d9a39cSRong Zhang 	priv->sub_master->sub_component_list = NULL;
342*67d9a39cSRong Zhang 
343*67d9a39cSRong Zhang 	component_unbind_all(dev, NULL);
344*67d9a39cSRong Zhang }
345*67d9a39cSRong Zhang 
346*67d9a39cSRong Zhang static const struct component_master_ops lwmi_cd_sub_master_ops = {
347*67d9a39cSRong Zhang 	.bind = lwmi_cd_sub_master_bind,
348*67d9a39cSRong Zhang 	.unbind = lwmi_cd_sub_master_unbind,
349*67d9a39cSRong Zhang };
350*67d9a39cSRong Zhang 
351*67d9a39cSRong Zhang /**
352*67d9a39cSRong Zhang  * lwmi_cd_sub_master_add() - Register a sub-master with its sub-component
353*67d9a39cSRong Zhang  * @priv: Pointer to the sub-master capdata device private data.
354*67d9a39cSRong Zhang  * @sub_component_type: Type of the sub-component.
355*67d9a39cSRong Zhang  *
356*67d9a39cSRong Zhang  * Match the sub-component type and register the current capdata device as a
357*67d9a39cSRong Zhang  * sub-master. If the given sub-component type is CD_TYPE_NONE, mark the sub-
358*67d9a39cSRong Zhang  * component as non-existent without registering sub-master.
359*67d9a39cSRong Zhang  *
360*67d9a39cSRong Zhang  * Return: 0 on success, or an error code.
361*67d9a39cSRong Zhang  */
362*67d9a39cSRong Zhang static int lwmi_cd_sub_master_add(struct lwmi_cd_priv *priv,
363*67d9a39cSRong Zhang 				  enum lwmi_cd_type sub_component_type)
364*67d9a39cSRong Zhang {
365*67d9a39cSRong Zhang 	struct component_match *master_match = NULL;
366*67d9a39cSRong Zhang 	int ret;
367*67d9a39cSRong Zhang 
368*67d9a39cSRong Zhang 	priv->sub_master = devm_kzalloc(&priv->wdev->dev, sizeof(*priv->sub_master), GFP_KERNEL);
369*67d9a39cSRong Zhang 	if (!priv->sub_master)
370*67d9a39cSRong Zhang 		return -ENOMEM;
371*67d9a39cSRong Zhang 
372*67d9a39cSRong Zhang 	if (sub_component_type == CD_TYPE_NONE) {
373*67d9a39cSRong Zhang 		/* The master callback will be called with NULL on bind. */
374*67d9a39cSRong Zhang 		priv->sub_master->sub_component_list = ERR_PTR(-ENODEV);
375*67d9a39cSRong Zhang 		priv->sub_master->registered = false;
376*67d9a39cSRong Zhang 		return 0;
377*67d9a39cSRong Zhang 	}
378*67d9a39cSRong Zhang 
379*67d9a39cSRong Zhang 	/*
380*67d9a39cSRong Zhang 	 * lwmi_cd_match() needs a pointer to enum lwmi_cd_type, but on-stack
381*67d9a39cSRong Zhang 	 * data cannot be used here. Steal one from lwmi_cd_table.
382*67d9a39cSRong Zhang 	 */
383*67d9a39cSRong Zhang 	component_match_add(&priv->wdev->dev, &master_match, lwmi_cd_match,
384*67d9a39cSRong Zhang 			    (void *)&lwmi_cd_table[sub_component_type].type);
385*67d9a39cSRong Zhang 	if (IS_ERR(master_match))
386*67d9a39cSRong Zhang 		return PTR_ERR(master_match);
387*67d9a39cSRong Zhang 
388*67d9a39cSRong Zhang 	ret = component_master_add_with_match(&priv->wdev->dev, &lwmi_cd_sub_master_ops,
389*67d9a39cSRong Zhang 					      master_match);
390*67d9a39cSRong Zhang 	if (ret)
391*67d9a39cSRong Zhang 		return ret;
392*67d9a39cSRong Zhang 
393*67d9a39cSRong Zhang 	priv->sub_master->registered = true;
394*67d9a39cSRong Zhang 	return 0;
395*67d9a39cSRong Zhang }
396*67d9a39cSRong Zhang 
397*67d9a39cSRong Zhang /**
398*67d9a39cSRong Zhang  * lwmi_cd_sub_master_del() - Unregister a sub-master if it's registered
399*67d9a39cSRong Zhang  * @priv: Pointer to the sub-master capdata device private data.
400*67d9a39cSRong Zhang  */
401*67d9a39cSRong Zhang static void lwmi_cd_sub_master_del(struct lwmi_cd_priv *priv)
402*67d9a39cSRong Zhang {
403*67d9a39cSRong Zhang 	if (!priv->sub_master->registered)
404*67d9a39cSRong Zhang 		return;
405*67d9a39cSRong Zhang 
406*67d9a39cSRong Zhang 	component_master_del(&priv->wdev->dev, &lwmi_cd_sub_master_ops);
407*67d9a39cSRong Zhang 	priv->sub_master->registered = false;
408*67d9a39cSRong Zhang }
409*67d9a39cSRong Zhang 
410*67d9a39cSRong Zhang /**
411*67d9a39cSRong Zhang  * lwmi_cd_sub_component_bind() - Bind sub-component to sub-master device.
412*67d9a39cSRong Zhang  * @sc_dev: Pointer to the sub-component capdata parent device.
413*67d9a39cSRong Zhang  * @sm_dev: Pointer to the sub-master capdata parent device.
414*67d9a39cSRong Zhang  * @data: Pointer used to return the capability data list pointer.
415*67d9a39cSRong Zhang  *
416*67d9a39cSRong Zhang  * On sub-master's bind, provide a pointer to the local capdata list.
417*67d9a39cSRong Zhang  * This is used by the sub-master to call the master callback.
418*67d9a39cSRong Zhang  *
419*67d9a39cSRong Zhang  * Return: 0
420*67d9a39cSRong Zhang  */
421*67d9a39cSRong Zhang static int lwmi_cd_sub_component_bind(struct device *sc_dev,
422*67d9a39cSRong Zhang 				      struct device *sm_dev, void *data)
423*67d9a39cSRong Zhang {
424*67d9a39cSRong Zhang 	struct lwmi_cd_priv *priv = dev_get_drvdata(sc_dev);
425*67d9a39cSRong Zhang 	struct cd_list **listp = data;
426*67d9a39cSRong Zhang 
427*67d9a39cSRong Zhang 	*listp = priv->list;
428*67d9a39cSRong Zhang 
429*67d9a39cSRong Zhang 	return 0;
430*67d9a39cSRong Zhang }
431*67d9a39cSRong Zhang 
432*67d9a39cSRong Zhang static const struct component_ops lwmi_cd_sub_component_ops = {
433*67d9a39cSRong Zhang 	.bind = lwmi_cd_sub_component_bind,
434f28d76b1SRong Zhang };
435f28d76b1SRong Zhang 
4364ff1a029SRong Zhang /*
4374ff1a029SRong Zhang  * lwmi_cd*_get_data - Get the data of the specified attribute
438f28d76b1SRong Zhang  * @list: The lenovo-wmi-capdata pointer to its cd_list struct.
439f28d76b1SRong Zhang  * @attribute_id: The capdata attribute ID to be found.
4404ff1a029SRong Zhang  * @output: Pointer to a capdata* struct to return the data.
441f28d76b1SRong Zhang  *
4424ff1a029SRong Zhang  * Retrieves the capability data struct pointer for the given
4434ff1a029SRong Zhang  * attribute.
444f28d76b1SRong Zhang  *
445f28d76b1SRong Zhang  * Return: 0 on success, or -EINVAL.
446f28d76b1SRong Zhang  */
4474ff1a029SRong Zhang #define DEF_LWMI_CDXX_GET_DATA(_cdxx, _cd_type, _output_t)					\
4484ff1a029SRong Zhang 	int lwmi_##_cdxx##_get_data(struct cd_list *list, u32 attribute_id, _output_t *output)	\
4494ff1a029SRong Zhang 	{											\
4504ff1a029SRong Zhang 		u8 idx;										\
4514ff1a029SRong Zhang 												\
4524ff1a029SRong Zhang 		if (WARN_ON(list->type != _cd_type))						\
4534ff1a029SRong Zhang 			return -EINVAL;								\
4544ff1a029SRong Zhang 												\
4554ff1a029SRong Zhang 		guard(mutex)(&list->list_mutex);						\
4564ff1a029SRong Zhang 		for (idx = 0; idx < list->count; idx++) {					\
4574ff1a029SRong Zhang 			if (list->_cdxx[idx].id != attribute_id)				\
4584ff1a029SRong Zhang 				continue;							\
4594ff1a029SRong Zhang 			memcpy(output, &list->_cdxx[idx], sizeof(list->_cdxx[idx]));		\
4604ff1a029SRong Zhang 			return 0;								\
4614ff1a029SRong Zhang 		}										\
4624ff1a029SRong Zhang 		return -EINVAL;									\
463f28d76b1SRong Zhang 	}
464f28d76b1SRong Zhang 
465c05f67e6SRong Zhang DEF_LWMI_CDXX_GET_DATA(cd00, LENOVO_CAPABILITY_DATA_00, struct capdata00);
466c05f67e6SRong Zhang EXPORT_SYMBOL_NS_GPL(lwmi_cd00_get_data, "LENOVO_WMI_CAPDATA");
467c05f67e6SRong Zhang 
4684ff1a029SRong Zhang DEF_LWMI_CDXX_GET_DATA(cd01, LENOVO_CAPABILITY_DATA_01, struct capdata01);
469f28d76b1SRong Zhang EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CAPDATA");
470f28d76b1SRong Zhang 
471012a8f96SRong Zhang DEF_LWMI_CDXX_GET_DATA(cd_fan, LENOVO_FAN_TEST_DATA, struct capdata_fan);
472012a8f96SRong Zhang EXPORT_SYMBOL_NS_GPL(lwmi_cd_fan_get_data, "LENOVO_WMI_CAPDATA");
473012a8f96SRong Zhang 
474f28d76b1SRong Zhang /**
475f28d76b1SRong Zhang  * lwmi_cd_cache() - Cache all WMI data block information
476f28d76b1SRong Zhang  * @priv: lenovo-wmi-capdata driver data.
477f28d76b1SRong Zhang  *
478f28d76b1SRong Zhang  * Loop through each WMI data block and cache the data.
479f28d76b1SRong Zhang  *
480f28d76b1SRong Zhang  * Return: 0 on success, or an error.
481f28d76b1SRong Zhang  */
482f28d76b1SRong Zhang static int lwmi_cd_cache(struct lwmi_cd_priv *priv)
483f28d76b1SRong Zhang {
4844ff1a029SRong Zhang 	size_t size;
485f28d76b1SRong Zhang 	int idx;
4864ff1a029SRong Zhang 	void *p;
4874ff1a029SRong Zhang 
4884ff1a029SRong Zhang 	switch (priv->list->type) {
489c05f67e6SRong Zhang 	case LENOVO_CAPABILITY_DATA_00:
490c05f67e6SRong Zhang 		p = &priv->list->cd00[0];
491c05f67e6SRong Zhang 		size = sizeof(priv->list->cd00[0]);
492c05f67e6SRong Zhang 		break;
4934ff1a029SRong Zhang 	case LENOVO_CAPABILITY_DATA_01:
4944ff1a029SRong Zhang 		p = &priv->list->cd01[0];
4954ff1a029SRong Zhang 		size = sizeof(priv->list->cd01[0]);
4964ff1a029SRong Zhang 		break;
497012a8f96SRong Zhang 	case LENOVO_FAN_TEST_DATA:
498012a8f96SRong Zhang 		/* Done by lwmi_cd_alloc() => lwmi_cd_fan_list_alloc_cache(). */
499012a8f96SRong Zhang 		return 0;
5004ff1a029SRong Zhang 	default:
5014ff1a029SRong Zhang 		return -EINVAL;
5024ff1a029SRong Zhang 	}
503f28d76b1SRong Zhang 
504f28d76b1SRong Zhang 	guard(mutex)(&priv->list->list_mutex);
5054ff1a029SRong Zhang 	for (idx = 0; idx < priv->list->count; idx++, p += size) {
506f28d76b1SRong Zhang 		union acpi_object *ret_obj __free(kfree) = NULL;
507f28d76b1SRong Zhang 
508f28d76b1SRong Zhang 		ret_obj = wmidev_block_query(priv->wdev, idx);
509f28d76b1SRong Zhang 		if (!ret_obj)
510f28d76b1SRong Zhang 			return -ENODEV;
511f28d76b1SRong Zhang 
512f28d76b1SRong Zhang 		if (ret_obj->type != ACPI_TYPE_BUFFER ||
5134ff1a029SRong Zhang 		    ret_obj->buffer.length < size)
514f28d76b1SRong Zhang 			continue;
515f28d76b1SRong Zhang 
5164ff1a029SRong Zhang 		memcpy(p, ret_obj->buffer.pointer, size);
517f28d76b1SRong Zhang 	}
518f28d76b1SRong Zhang 
519f28d76b1SRong Zhang 	return 0;
520f28d76b1SRong Zhang }
521f28d76b1SRong Zhang 
522f28d76b1SRong Zhang /**
523012a8f96SRong Zhang  * lwmi_cd_fan_list_alloc_cache() - Alloc and cache Fan Test Data list
524012a8f96SRong Zhang  * @priv: lenovo-wmi-capdata driver data.
525012a8f96SRong Zhang  * @listptr: Pointer to returned cd_list pointer.
526012a8f96SRong Zhang  *
527012a8f96SRong Zhang  * Return: count of fans found, or an error.
528012a8f96SRong Zhang  */
529012a8f96SRong Zhang static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv, struct cd_list **listptr)
530012a8f96SRong Zhang {
531012a8f96SRong Zhang 	struct cd_list *list;
532012a8f96SRong Zhang 	size_t size;
533012a8f96SRong Zhang 	u32 count;
534012a8f96SRong Zhang 	int idx;
535012a8f96SRong Zhang 
536012a8f96SRong Zhang 	/* Emit unaligned access to u8 buffer with __packed. */
537012a8f96SRong Zhang 	struct cd_fan_block {
538012a8f96SRong Zhang 		u32 nr;
539012a8f96SRong Zhang 		u32 data[]; /* id[nr], max_rpm[nr], min_rpm[nr] */
540012a8f96SRong Zhang 	} __packed * block;
541012a8f96SRong Zhang 
542012a8f96SRong Zhang 	union acpi_object *ret_obj __free(kfree) = wmidev_block_query(priv->wdev, 0);
543012a8f96SRong Zhang 	if (!ret_obj)
544012a8f96SRong Zhang 		return -ENODEV;
545012a8f96SRong Zhang 
546012a8f96SRong Zhang 	if (ret_obj->type == ACPI_TYPE_BUFFER) {
547012a8f96SRong Zhang 		block = (struct cd_fan_block *)ret_obj->buffer.pointer;
548012a8f96SRong Zhang 		size = ret_obj->buffer.length;
549012a8f96SRong Zhang 
550012a8f96SRong Zhang 		count = size >= sizeof(*block) ? block->nr : 0;
551012a8f96SRong Zhang 		if (size < struct_size(block, data, count * 3)) {
552012a8f96SRong Zhang 			dev_warn(&priv->wdev->dev,
553012a8f96SRong Zhang 				 "incomplete fan test data block: %zu < %zu, ignoring\n",
554012a8f96SRong Zhang 				 size, struct_size(block, data, count * 3));
555012a8f96SRong Zhang 			count = 0;
556012a8f96SRong Zhang 		} else if (count > U8_MAX) {
557012a8f96SRong Zhang 			dev_warn(&priv->wdev->dev,
558012a8f96SRong Zhang 				 "too many fans reported: %u > %u, truncating\n",
559012a8f96SRong Zhang 				 count, U8_MAX);
560012a8f96SRong Zhang 			count = U8_MAX;
561012a8f96SRong Zhang 		}
562012a8f96SRong Zhang 	} else {
563012a8f96SRong Zhang 		/*
564012a8f96SRong Zhang 		 * This is usually caused by a dummy ACPI method. Do not return an error
565012a8f96SRong Zhang 		 * as failing to probe this device will result in sub-master device being
566012a8f96SRong Zhang 		 * unbound. This behavior aligns with lwmi_cd_cache().
567012a8f96SRong Zhang 		 */
568012a8f96SRong Zhang 		count = 0;
569012a8f96SRong Zhang 	}
570012a8f96SRong Zhang 
571012a8f96SRong Zhang 	list = devm_kzalloc(&priv->wdev->dev, struct_size(list, cd_fan, count), GFP_KERNEL);
572012a8f96SRong Zhang 	if (!list)
573012a8f96SRong Zhang 		return -ENOMEM;
574012a8f96SRong Zhang 
575012a8f96SRong Zhang 	for (idx = 0; idx < count; idx++) {
576012a8f96SRong Zhang 		/* Do not calculate array index using count, as it may be truncated. */
577012a8f96SRong Zhang 		list->cd_fan[idx] = (struct capdata_fan) {
578012a8f96SRong Zhang 			.id      = block->data[idx],
579012a8f96SRong Zhang 			.max_rpm = block->data[idx + block->nr],
580012a8f96SRong Zhang 			.min_rpm = block->data[idx + (2 * block->nr)],
581012a8f96SRong Zhang 		};
582012a8f96SRong Zhang 	}
583012a8f96SRong Zhang 
584012a8f96SRong Zhang 	*listptr = list;
585012a8f96SRong Zhang 	return count;
586012a8f96SRong Zhang }
587012a8f96SRong Zhang 
588012a8f96SRong Zhang /**
589f28d76b1SRong Zhang  * lwmi_cd_alloc() - Allocate a cd_list struct in drvdata
590f28d76b1SRong Zhang  * @priv: lenovo-wmi-capdata driver data.
5914ff1a029SRong Zhang  * @type: The type of capability data.
592f28d76b1SRong Zhang  *
593f28d76b1SRong Zhang  * Allocate a cd_list struct large enough to contain data from all WMI data
594f28d76b1SRong Zhang  * blocks provided by the interface.
595f28d76b1SRong Zhang  *
596f28d76b1SRong Zhang  * Return: 0 on success, or an error.
597f28d76b1SRong Zhang  */
5984ff1a029SRong Zhang static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type)
599f28d76b1SRong Zhang {
600f28d76b1SRong Zhang 	struct cd_list *list;
601f28d76b1SRong Zhang 	size_t list_size;
602f28d76b1SRong Zhang 	int count, ret;
603f28d76b1SRong Zhang 
604f28d76b1SRong Zhang 	count = wmidev_instance_count(priv->wdev);
6054ff1a029SRong Zhang 
6064ff1a029SRong Zhang 	switch (type) {
607c05f67e6SRong Zhang 	case LENOVO_CAPABILITY_DATA_00:
608c05f67e6SRong Zhang 		list_size = struct_size(list, cd00, count);
609c05f67e6SRong Zhang 		break;
6104ff1a029SRong Zhang 	case LENOVO_CAPABILITY_DATA_01:
6114ff1a029SRong Zhang 		list_size = struct_size(list, cd01, count);
6124ff1a029SRong Zhang 		break;
613012a8f96SRong Zhang 	case LENOVO_FAN_TEST_DATA:
614012a8f96SRong Zhang 		count = lwmi_cd_fan_list_alloc_cache(priv, &list);
615012a8f96SRong Zhang 		if (count < 0)
616012a8f96SRong Zhang 			return count;
617012a8f96SRong Zhang 
618012a8f96SRong Zhang 		goto got_list;
6194ff1a029SRong Zhang 	default:
6204ff1a029SRong Zhang 		return -EINVAL;
6214ff1a029SRong Zhang 	}
622f28d76b1SRong Zhang 
623f28d76b1SRong Zhang 	list = devm_kzalloc(&priv->wdev->dev, list_size, GFP_KERNEL);
624f28d76b1SRong Zhang 	if (!list)
625f28d76b1SRong Zhang 		return -ENOMEM;
626f28d76b1SRong Zhang 
627012a8f96SRong Zhang got_list:
628f28d76b1SRong Zhang 	ret = devm_mutex_init(&priv->wdev->dev, &list->list_mutex);
629f28d76b1SRong Zhang 	if (ret)
630f28d76b1SRong Zhang 		return ret;
631f28d76b1SRong Zhang 
6324ff1a029SRong Zhang 	list->type = type;
633f28d76b1SRong Zhang 	list->count = count;
634f28d76b1SRong Zhang 	priv->list = list;
635f28d76b1SRong Zhang 
636f28d76b1SRong Zhang 	return 0;
637f28d76b1SRong Zhang }
638f28d76b1SRong Zhang 
639f28d76b1SRong Zhang /**
640f28d76b1SRong Zhang  * lwmi_cd_setup() - Cache all WMI data block information
641f28d76b1SRong Zhang  * @priv: lenovo-wmi-capdata driver data.
6424ff1a029SRong Zhang  * @type: The type of capability data.
643f28d76b1SRong Zhang  *
644f28d76b1SRong Zhang  * Allocate a cd_list struct large enough to contain data from all WMI data
645f28d76b1SRong Zhang  * blocks provided by the interface. Then loop through each data block and
646f28d76b1SRong Zhang  * cache the data.
647f28d76b1SRong Zhang  *
648f28d76b1SRong Zhang  * Return: 0 on success, or an error code.
649f28d76b1SRong Zhang  */
6504ff1a029SRong Zhang static int lwmi_cd_setup(struct lwmi_cd_priv *priv, enum lwmi_cd_type type)
651f28d76b1SRong Zhang {
652f28d76b1SRong Zhang 	int ret;
653f28d76b1SRong Zhang 
6544ff1a029SRong Zhang 	ret = lwmi_cd_alloc(priv, type);
655f28d76b1SRong Zhang 	if (ret)
656f28d76b1SRong Zhang 		return ret;
657f28d76b1SRong Zhang 
658f28d76b1SRong Zhang 	return lwmi_cd_cache(priv);
659f28d76b1SRong Zhang }
660f28d76b1SRong Zhang 
661f28d76b1SRong Zhang /**
662f28d76b1SRong Zhang  * lwmi_cd01_notifier_call() - Call method for cd01 notifier.
663f28d76b1SRong Zhang  * block call chain.
664f28d76b1SRong Zhang  * @nb: The notifier_block registered to lenovo-wmi-events driver.
665f28d76b1SRong Zhang  * @action: Unused.
666f28d76b1SRong Zhang  * @data: The ACPI event.
667f28d76b1SRong Zhang  *
668f28d76b1SRong Zhang  * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile
669f28d76b1SRong Zhang  * of a change.
670f28d76b1SRong Zhang  *
671f28d76b1SRong Zhang  * Return: notifier_block status.
672f28d76b1SRong Zhang  */
673f28d76b1SRong Zhang static int lwmi_cd01_notifier_call(struct notifier_block *nb, unsigned long action,
674f28d76b1SRong Zhang 				   void *data)
675f28d76b1SRong Zhang {
676f28d76b1SRong Zhang 	struct acpi_bus_event *event = data;
677f28d76b1SRong Zhang 	struct lwmi_cd_priv *priv;
678f28d76b1SRong Zhang 	int ret;
679f28d76b1SRong Zhang 
680f28d76b1SRong Zhang 	if (strcmp(event->device_class, ACPI_AC_CLASS) != 0)
681f28d76b1SRong Zhang 		return NOTIFY_DONE;
682f28d76b1SRong Zhang 
683f28d76b1SRong Zhang 	priv = container_of(nb, struct lwmi_cd_priv, acpi_nb);
684f28d76b1SRong Zhang 
685f28d76b1SRong Zhang 	switch (event->type) {
686f28d76b1SRong Zhang 	case ACPI_AC_NOTIFY_STATUS:
687f28d76b1SRong Zhang 		ret = lwmi_cd_cache(priv);
688f28d76b1SRong Zhang 		if (ret)
689f28d76b1SRong Zhang 			return NOTIFY_BAD;
690f28d76b1SRong Zhang 
691f28d76b1SRong Zhang 		return NOTIFY_OK;
692f28d76b1SRong Zhang 	default:
693f28d76b1SRong Zhang 		return NOTIFY_DONE;
694f28d76b1SRong Zhang 	}
695f28d76b1SRong Zhang }
696f28d76b1SRong Zhang 
697f28d76b1SRong Zhang /**
698f28d76b1SRong Zhang  * lwmi_cd01_unregister() - Unregister the cd01 ACPI notifier_block.
699f28d76b1SRong Zhang  * @data: The ACPI event notifier_block to unregister.
700f28d76b1SRong Zhang  */
701f28d76b1SRong Zhang static void lwmi_cd01_unregister(void *data)
702f28d76b1SRong Zhang {
703f28d76b1SRong Zhang 	struct notifier_block *acpi_nb = data;
704f28d76b1SRong Zhang 
705f28d76b1SRong Zhang 	unregister_acpi_notifier(acpi_nb);
706f28d76b1SRong Zhang }
707f28d76b1SRong Zhang 
708f28d76b1SRong Zhang static int lwmi_cd_probe(struct wmi_device *wdev, const void *context)
709f28d76b1SRong Zhang {
7104ff1a029SRong Zhang 	const struct lwmi_cd_info *info = context;
711f28d76b1SRong Zhang 	struct lwmi_cd_priv *priv;
712f28d76b1SRong Zhang 	int ret;
713f28d76b1SRong Zhang 
7144ff1a029SRong Zhang 	if (!info)
7154ff1a029SRong Zhang 		return -EINVAL;
7164ff1a029SRong Zhang 
717f28d76b1SRong Zhang 	priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
718f28d76b1SRong Zhang 	if (!priv)
719f28d76b1SRong Zhang 		return -ENOMEM;
720f28d76b1SRong Zhang 
721f28d76b1SRong Zhang 	priv->wdev = wdev;
722f28d76b1SRong Zhang 	dev_set_drvdata(&wdev->dev, priv);
723f28d76b1SRong Zhang 
7244ff1a029SRong Zhang 	ret = lwmi_cd_setup(priv, info->type);
725f28d76b1SRong Zhang 	if (ret)
7264ff1a029SRong Zhang 		goto out;
727f28d76b1SRong Zhang 
7284ff1a029SRong Zhang 	switch (info->type) {
729*67d9a39cSRong Zhang 	case LENOVO_CAPABILITY_DATA_00: {
730*67d9a39cSRong Zhang 		enum lwmi_cd_type sub_component_type = LENOVO_FAN_TEST_DATA;
731*67d9a39cSRong Zhang 		struct capdata00 capdata00;
732*67d9a39cSRong Zhang 
733*67d9a39cSRong Zhang 		ret = lwmi_cd00_get_data(priv->list, LWMI_ATTR_ID_FAN_TEST, &capdata00);
734*67d9a39cSRong Zhang 		if (ret || !(capdata00.supported & LWMI_SUPP_VALID)) {
735*67d9a39cSRong Zhang 			dev_dbg(&wdev->dev, "capdata00 declares no fan test support\n");
736*67d9a39cSRong Zhang 			sub_component_type = CD_TYPE_NONE;
737*67d9a39cSRong Zhang 		}
738*67d9a39cSRong Zhang 
739*67d9a39cSRong Zhang 		/* Sub-master (capdata00) <-> sub-component (capdata_fan) */
740*67d9a39cSRong Zhang 		ret = lwmi_cd_sub_master_add(priv, sub_component_type);
741*67d9a39cSRong Zhang 		if (ret)
742c05f67e6SRong Zhang 			goto out;
743*67d9a39cSRong Zhang 
744*67d9a39cSRong Zhang 		/* Master (lenovo-wmi-other) <-> sub-master (capdata00) */
745*67d9a39cSRong Zhang 		ret = component_add(&wdev->dev, &lwmi_cd_component_ops);
746*67d9a39cSRong Zhang 		if (ret)
747*67d9a39cSRong Zhang 			lwmi_cd_sub_master_del(priv);
748*67d9a39cSRong Zhang 
749*67d9a39cSRong Zhang 		goto out;
750*67d9a39cSRong Zhang 	}
7514ff1a029SRong Zhang 	case LENOVO_CAPABILITY_DATA_01:
752f28d76b1SRong Zhang 		priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call;
753f28d76b1SRong Zhang 
754f28d76b1SRong Zhang 		ret = register_acpi_notifier(&priv->acpi_nb);
755f28d76b1SRong Zhang 		if (ret)
7564ff1a029SRong Zhang 			goto out;
757f28d76b1SRong Zhang 
7584ff1a029SRong Zhang 		ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister,
7594ff1a029SRong Zhang 					       &priv->acpi_nb);
760f28d76b1SRong Zhang 		if (ret)
7614ff1a029SRong Zhang 			goto out;
762f28d76b1SRong Zhang 
7634ff1a029SRong Zhang 		ret = component_add(&wdev->dev, &lwmi_cd_component_ops);
7644ff1a029SRong Zhang 		goto out;
765012a8f96SRong Zhang 	case LENOVO_FAN_TEST_DATA:
766*67d9a39cSRong Zhang 		ret = component_add(&wdev->dev, &lwmi_cd_sub_component_ops);
767012a8f96SRong Zhang 		goto out;
7684ff1a029SRong Zhang 	default:
7694ff1a029SRong Zhang 		return -EINVAL;
7704ff1a029SRong Zhang 	}
7714ff1a029SRong Zhang out:
7724ff1a029SRong Zhang 	if (ret) {
7734ff1a029SRong Zhang 		dev_err(&wdev->dev, "failed to register %s: %d\n",
7744ff1a029SRong Zhang 			info->name, ret);
7754ff1a029SRong Zhang 	} else {
7764ff1a029SRong Zhang 		dev_dbg(&wdev->dev, "registered %s with %u items\n",
7774ff1a029SRong Zhang 			info->name, priv->list->count);
7784ff1a029SRong Zhang 	}
7794ff1a029SRong Zhang 	return ret;
780f28d76b1SRong Zhang }
781f28d76b1SRong Zhang 
782f28d76b1SRong Zhang static void lwmi_cd_remove(struct wmi_device *wdev)
783f28d76b1SRong Zhang {
7844ff1a029SRong Zhang 	struct lwmi_cd_priv *priv = dev_get_drvdata(&wdev->dev);
7854ff1a029SRong Zhang 
7864ff1a029SRong Zhang 	switch (priv->list->type) {
787c05f67e6SRong Zhang 	case LENOVO_CAPABILITY_DATA_00:
788*67d9a39cSRong Zhang 		lwmi_cd_sub_master_del(priv);
789*67d9a39cSRong Zhang 		fallthrough;
7904ff1a029SRong Zhang 	case LENOVO_CAPABILITY_DATA_01:
791f28d76b1SRong Zhang 		component_del(&wdev->dev, &lwmi_cd_component_ops);
7924ff1a029SRong Zhang 		break;
793012a8f96SRong Zhang 	case LENOVO_FAN_TEST_DATA:
794*67d9a39cSRong Zhang 		component_del(&wdev->dev, &lwmi_cd_sub_component_ops);
795012a8f96SRong Zhang 		break;
7964ff1a029SRong Zhang 	default:
7974ff1a029SRong Zhang 		WARN_ON(1);
7984ff1a029SRong Zhang 	}
799f28d76b1SRong Zhang }
800f28d76b1SRong Zhang 
8014ff1a029SRong Zhang #define LWMI_CD_WDEV_ID(_type)				\
8024ff1a029SRong Zhang 	.guid_string = _type##_GUID,			\
8034ff1a029SRong Zhang 	.context = &lwmi_cd_table[_type],
8044ff1a029SRong Zhang 
805f28d76b1SRong Zhang static const struct wmi_device_id lwmi_cd_id_table[] = {
806c05f67e6SRong Zhang 	{ LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_00) },
8074ff1a029SRong Zhang 	{ LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_01) },
808012a8f96SRong Zhang 	{ LWMI_CD_WDEV_ID(LENOVO_FAN_TEST_DATA) },
809f28d76b1SRong Zhang 	{}
810f28d76b1SRong Zhang };
811f28d76b1SRong Zhang 
812f28d76b1SRong Zhang static struct wmi_driver lwmi_cd_driver = {
813f28d76b1SRong Zhang 	.driver = {
814f28d76b1SRong Zhang 		.name = "lenovo_wmi_capdata",
815f28d76b1SRong Zhang 		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
816f28d76b1SRong Zhang 	},
817f28d76b1SRong Zhang 	.id_table = lwmi_cd_id_table,
818f28d76b1SRong Zhang 	.probe = lwmi_cd_probe,
819f28d76b1SRong Zhang 	.remove = lwmi_cd_remove,
820f28d76b1SRong Zhang 	.no_singleton = true,
821f28d76b1SRong Zhang };
822f28d76b1SRong Zhang 
823f28d76b1SRong Zhang module_wmi_driver(lwmi_cd_driver);
824f28d76b1SRong Zhang 
825f28d76b1SRong Zhang MODULE_DEVICE_TABLE(wmi, lwmi_cd_id_table);
826f28d76b1SRong Zhang MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
8274ff1a029SRong Zhang MODULE_AUTHOR("Rong Zhang <i@rong.moe>");
828f28d76b1SRong Zhang MODULE_DESCRIPTION("Lenovo Capability Data WMI Driver");
829f28d76b1SRong Zhang MODULE_LICENSE("GPL");
830