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