Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

platform/x86: lenovo-wmi-other: Add HWMON for fan reporting/tuning

Register an HWMON device for fan reporting/tuning according to
Capability Data 00 (capdata00) and Fan Test Data (capdata_fan) provided
by lenovo-wmi-capdata. The corresponding HWMON nodes are:

- fanX_div: internal RPM divisor
- fanX_input: current RPM
- fanX_max: maximum RPM
- fanX_min: minimum RPM
- fanX_target: target RPM (tunable, 0=auto)

Information from capdata00 and capdata_fan are used to control the
visibility and constraints of HWMON attributes. Fan info from capdata00
is collected on bind, while fan info from capdata_fan is collected in a
callback. Once all fan info is collected, register the HWMON device.

Signed-off-by: Rong Zhang <i@rong.moe>
Reviewed-by: Derek J. Clark <derekjohn.clark@gmail.com>
Tested-by: Kurt Borja <kuurtb@gmail.com>
Link: https://patch.msgid.link/20260120182104.163424-8-i@rong.moe
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>

authored by

Rong Zhang and committed by
Ilpo Järvinen
51ed3428 67d9a39c

+507 -10
+14
Documentation/wmi/devices/lenovo-wmi-other.rst
··· 31 31 32 32 /sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/ 33 33 34 + Additionally, this driver also exports attributes to HWMON. 35 + 34 36 LENOVO_CAPABILITY_DATA_00 35 37 ------------------------- 36 38 ··· 40 38 41 39 The LENOVO_CAPABILITY_DATA_00 interface provides various information that 42 40 does not rely on the gamezone thermal mode. 41 + 42 + The following HWMON attributes are implemented: 43 + - fanX_div: internal RPM divisor 44 + - fanX_input: current RPM 45 + - fanX_target: target RPM (tunable, 0=auto) 46 + 47 + Due to the internal RPM divisor, the current/target RPMs are rounded down to 48 + its nearest multiple. The divisor itself is not necessary to be a power of two. 43 49 44 50 LENOVO_CAPABILITY_DATA_01 45 51 ------------------------- ··· 79 69 80 70 The LENOVO_FAN_TEST_DATA interface provides reference data for self-test of 81 71 cooling fans. 72 + 73 + The following HWMON attributes are implemented: 74 + - fanX_max: maximum RPM 75 + - fanX_min: minimum RPM 82 76 83 77 WMI interface description 84 78 =========================
+1
drivers/platform/x86/lenovo/Kconfig
··· 263 263 config LENOVO_WMI_TUNING 264 264 tristate "Lenovo Other Mode WMI Driver" 265 265 depends on ACPI_WMI 266 + select HWMON 266 267 select FW_ATTR_CLASS 267 268 select LENOVO_WMI_CAPDATA 268 269 select LENOVO_WMI_EVENTS
+492 -10
drivers/platform/x86/lenovo/wmi-other.c
··· 14 14 * These attributes typically don't fit anywhere else in the sysfs and are set 15 15 * in Windows using one of Lenovo's multiple user applications. 16 16 * 17 + * Additionally, this driver also exports tunable fan speed RPM to HWMON. 18 + * Min/max RPM are also provided for reference. 19 + * 17 20 * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> 21 + * - fw_attributes 22 + * - binding to Capability Data 01 23 + * 24 + * Copyright (C) 2025 Rong Zhang <i@rong.moe> 25 + * - HWMON 26 + * - binding to Capability Data 00 and Fan 18 27 */ 19 28 20 29 #include <linux/acpi.h> ··· 34 25 #include <linux/device.h> 35 26 #include <linux/export.h> 36 27 #include <linux/gfp_types.h> 28 + #include <linux/hwmon.h> 37 29 #include <linux/idr.h> 38 30 #include <linux/kdev_t.h> 39 31 #include <linux/kobject.h> 32 + #include <linux/limits.h> 40 33 #include <linux/module.h> 41 34 #include <linux/notifier.h> 42 35 #include <linux/platform_profile.h> ··· 60 49 #define LWMI_FEATURE_ID_CPU_SPL 0x02 61 50 #define LWMI_FEATURE_ID_CPU_FPPT 0x03 62 51 52 + #define LWMI_FEATURE_ID_FAN_RPM 0x03 53 + 63 54 #define LWMI_TYPE_ID_NONE 0x00 64 55 65 56 #define LWMI_FEATURE_VALUE_GET 17 66 57 #define LWMI_FEATURE_VALUE_SET 18 67 58 59 + #define LWMI_FAN_ID_BASE 1 60 + #define LWMI_FAN_NR 4 61 + #define LWMI_FAN_ID(x) ((x) + LWMI_FAN_ID_BASE) 62 + 63 + #define LWMI_ATTR_ID_FAN_RPM(x) \ 64 + (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, LWMI_DEVICE_ID_FAN) | \ 65 + FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, LWMI_FEATURE_ID_FAN_RPM) | \ 66 + FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, LWMI_FAN_ID(x))) 67 + 68 + #define LWMI_FAN_DIV 100 69 + 68 70 #define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other" 71 + #define LWMI_OM_HWMON_NAME "lenovo_wmi_other" 69 72 70 73 static BLOCKING_NOTIFIER_HEAD(om_chain_head); 71 74 static DEFINE_IDA(lwmi_om_ida); ··· 92 67 SUPPORTED, 93 68 }; 94 69 70 + struct lwmi_fan_info { 71 + u32 supported; 72 + u32 last_target; 73 + long min_rpm; 74 + long max_rpm; 75 + }; 76 + 95 77 struct lwmi_om_priv { 96 78 struct component_master_ops *ops; 97 79 98 80 /* only valid after capdata bind */ 81 + struct cd_list *cd00_list; 99 82 struct cd_list *cd01_list; 100 83 84 + struct device *hwmon_dev; 101 85 struct device *fw_attr_dev; 102 86 struct kset *fw_attr_kset; 103 87 struct notifier_block nb; 104 88 struct wmi_device *wdev; 105 89 int ida_id; 90 + 91 + struct lwmi_fan_info fan_info[LWMI_FAN_NR]; 92 + 93 + struct { 94 + bool capdata00_collected : 1; 95 + bool capdata_fan_collected : 1; 96 + } fan_flags; 106 97 }; 98 + 99 + /* 100 + * Visibility of fan channels: 101 + * 102 + * +-------------------+---------+------------------+-----------------------+------------+ 103 + * | | default | +expose_all_fans | +relax_fan_constraint | +both | 104 + * +-------------------+---------+------------------+-----------------------+------------+ 105 + * | canonical | RW | RW | RW+relaxed | RW+relaxed | 106 + * +-------------------+---------+------------------+-----------------------+------------+ 107 + * | -capdata_fan[idx] | N | RO | N | RW+relaxed | 108 + * +-------------------+---------+------------------+-----------------------+------------+ 109 + * 110 + * Note: 111 + * 1. LWMI_ATTR_ID_FAN_RPM[idx].supported is always checked before exposing a channel. 112 + * 2. -capdata_fan implies -capdata_fan[idx]. 113 + */ 114 + static bool expose_all_fans; 115 + module_param(expose_all_fans, bool, 0444); 116 + MODULE_PARM_DESC(expose_all_fans, 117 + "This option skips some capability checks and solely relies on per-channel ones " 118 + "to expose fan attributes. Use with caution."); 119 + 120 + static bool relax_fan_constraint; 121 + module_param(relax_fan_constraint, bool, 0444); 122 + MODULE_PARM_DESC(relax_fan_constraint, 123 + "Do not enforce fan RPM constraint (div/min/max) " 124 + "and enables fan tuning when such data is missing. " 125 + "Enabling this may results in HWMON attributes being out-of-sync, " 126 + "and setting a too low RPM stops the fan. Use with caution."); 127 + 128 + /* ======== HWMON (component: lenovo-wmi-capdata 00 & fan) ======== */ 129 + 130 + /** 131 + * lwmi_om_fan_get_set() - Get or set fan RPM value of specified fan 132 + * @priv: Driver private data structure 133 + * @channel: Fan channel index (0-based) 134 + * @val: Pointer to value (input for set, output for get) 135 + * @set: True to set value, false to get value 136 + * 137 + * Communicates with WMI interface to either retrieve current fan RPM 138 + * or set target fan RPM. 139 + * 140 + * Return: 0 on success, or an error code. 141 + */ 142 + static int lwmi_om_fan_get_set(struct lwmi_om_priv *priv, int channel, u32 *val, bool set) 143 + { 144 + struct wmi_method_args_32 args; 145 + u32 method_id, retval; 146 + int err; 147 + 148 + method_id = set ? LWMI_FEATURE_VALUE_SET : LWMI_FEATURE_VALUE_GET; 149 + args.arg0 = LWMI_ATTR_ID_FAN_RPM(channel); 150 + args.arg1 = set ? *val : 0; 151 + 152 + err = lwmi_dev_evaluate_int(priv->wdev, 0x0, method_id, 153 + (unsigned char *)&args, sizeof(args), &retval); 154 + if (err) 155 + return err; 156 + 157 + if (!set) { 158 + *val = retval; 159 + return 0; 160 + } 161 + 162 + /* 163 + * It seems that 0 means "no error" and 1 means "done". Apparently 164 + * different firmware teams have different thoughts on indicating 165 + * success, so we accepts both. 166 + */ 167 + return (retval == 0 || retval == 1) ? 0 : -EIO; 168 + } 169 + 170 + /** 171 + * lwmi_om_hwmon_is_visible() - Determine visibility of HWMON attributes 172 + * @drvdata: Driver private data 173 + * @type: Sensor type 174 + * @attr: Attribute identifier 175 + * @channel: Channel index 176 + * 177 + * Determines whether an HWMON attribute should be visible in sysfs 178 + * based on hardware capabilities and current configuration. 179 + * 180 + * Return: permission mode, or 0 if invisible. 181 + */ 182 + static umode_t lwmi_om_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type, 183 + u32 attr, int channel) 184 + { 185 + struct lwmi_om_priv *priv = (struct lwmi_om_priv *)drvdata; 186 + bool visible = false; 187 + 188 + if (type == hwmon_fan) { 189 + if (!(priv->fan_info[channel].supported & LWMI_SUPP_VALID)) 190 + return 0; 191 + 192 + switch (attr) { 193 + case hwmon_fan_target: 194 + if (!(priv->fan_info[channel].supported & LWMI_SUPP_MAY_SET)) 195 + return 0; 196 + 197 + if (relax_fan_constraint || 198 + (priv->fan_info[channel].min_rpm >= 0 && 199 + priv->fan_info[channel].max_rpm >= 0)) 200 + return 0644; 201 + 202 + /* 203 + * Reaching here implies expose_all_fans is set. 204 + * See lwmi_om_hwmon_add(). 205 + */ 206 + dev_warn_once(&priv->wdev->dev, 207 + "fan tuning disabled due to missing RPM constraint\n"); 208 + return 0; 209 + case hwmon_fan_div: 210 + case hwmon_fan_input: 211 + visible = priv->fan_info[channel].supported & LWMI_SUPP_MAY_GET; 212 + break; 213 + case hwmon_fan_min: 214 + visible = priv->fan_info[channel].min_rpm >= 0; 215 + break; 216 + case hwmon_fan_max: 217 + visible = priv->fan_info[channel].max_rpm >= 0; 218 + break; 219 + } 220 + } 221 + 222 + return visible ? 0444 : 0; 223 + } 224 + 225 + /** 226 + * lwmi_om_hwmon_read() - Read HWMON sensor data 227 + * @dev: Device pointer 228 + * @type: Sensor type 229 + * @attr: Attribute identifier 230 + * @channel: Channel index 231 + * @val: Pointer to store value 232 + * 233 + * Reads current sensor values from hardware through WMI interface. 234 + * 235 + * Return: 0 on success, or an error code. 236 + */ 237 + static int lwmi_om_hwmon_read(struct device *dev, enum hwmon_sensor_types type, 238 + u32 attr, int channel, long *val) 239 + { 240 + struct lwmi_om_priv *priv = dev_get_drvdata(dev); 241 + u32 retval = 0; 242 + int err; 243 + 244 + if (type == hwmon_fan) { 245 + switch (attr) { 246 + /* 247 + * The EC has an internal RPM divisor (i.e., the raw register value is 248 + * RPM / fanY_div). For fanY_input, the WMI method reads the register 249 + * value and returns raw * fanY_div. For fanY_target, the WMI method 250 + * divides the written value by fanY_div before writing it to the EC. 251 + * 252 + * As a result, reading fanY_input always returns a multiple of fanY_div, 253 + * while writing to fanY_target loses the remainder. 254 + */ 255 + case hwmon_fan_div: 256 + *val = LWMI_FAN_DIV; 257 + return 0; 258 + case hwmon_fan_input: 259 + err = lwmi_om_fan_get_set(priv, channel, &retval, false); 260 + if (err) 261 + return err; 262 + 263 + *val = retval; 264 + return 0; 265 + case hwmon_fan_target: 266 + *val = priv->fan_info[channel].last_target; 267 + return 0; 268 + case hwmon_fan_min: 269 + *val = priv->fan_info[channel].min_rpm; 270 + return 0; 271 + case hwmon_fan_max: 272 + *val = priv->fan_info[channel].max_rpm; 273 + return 0; 274 + } 275 + } 276 + 277 + return -EOPNOTSUPP; 278 + } 279 + 280 + /** 281 + * lwmi_om_hwmon_write() - Write HWMON sensor data 282 + * @dev: Device pointer 283 + * @type: Sensor type 284 + * @attr: Attribute identifier 285 + * @channel: Channel index 286 + * @val: Value to write 287 + * 288 + * Writes configuration values to hardware through WMI interface. 289 + * 290 + * Return: 0 on success, or an error code. 291 + */ 292 + static int lwmi_om_hwmon_write(struct device *dev, enum hwmon_sensor_types type, 293 + u32 attr, int channel, long val) 294 + { 295 + struct lwmi_om_priv *priv = dev_get_drvdata(dev); 296 + u32 raw, min_rpm, max_rpm; 297 + int err; 298 + 299 + if (type == hwmon_fan) { 300 + switch (attr) { 301 + case hwmon_fan_target: 302 + if (relax_fan_constraint) { 303 + min_rpm = 1; 304 + max_rpm = U16_MAX; 305 + } else { 306 + min_rpm = priv->fan_info[channel].min_rpm; 307 + max_rpm = priv->fan_info[channel].max_rpm; 308 + } 309 + 310 + /* 0 means "auto". */ 311 + if (val != 0 && (val < min_rpm || val > max_rpm)) 312 + return -EINVAL; 313 + 314 + /* 315 + * The effective fanY_target is always a multiple of fanY_div 316 + * due to the EC's internal RPM divisor (see lwmi_om_hwmon_read). 317 + * 318 + * Round down the written value to the nearest multiple of fanY_div 319 + * to prevent mismatch between the effective value and last_target. 320 + * 321 + * For relax_fan_constraint, skip this conversion as setting a 322 + * sub-fanY_div value is necessary to completely stop the fan on 323 + * some devices. 324 + */ 325 + if (!relax_fan_constraint) 326 + raw = val / LWMI_FAN_DIV * LWMI_FAN_DIV; 327 + 328 + err = lwmi_om_fan_get_set(priv, channel, &raw, true); 329 + if (err) 330 + return err; 331 + 332 + priv->fan_info[channel].last_target = raw; 333 + return 0; 334 + } 335 + } 336 + 337 + return -EOPNOTSUPP; 338 + } 339 + 340 + static const struct hwmon_channel_info * const lwmi_om_hwmon_info[] = { 341 + /* Must match LWMI_FAN_NR. */ 342 + HWMON_CHANNEL_INFO(fan, 343 + HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV | 344 + HWMON_F_MIN | HWMON_F_MAX, 345 + HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV | 346 + HWMON_F_MIN | HWMON_F_MAX, 347 + HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV | 348 + HWMON_F_MIN | HWMON_F_MAX, 349 + HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV | 350 + HWMON_F_MIN | HWMON_F_MAX), 351 + NULL 352 + }; 353 + 354 + static const struct hwmon_ops lwmi_om_hwmon_ops = { 355 + .is_visible = lwmi_om_hwmon_is_visible, 356 + .read = lwmi_om_hwmon_read, 357 + .write = lwmi_om_hwmon_write, 358 + }; 359 + 360 + static const struct hwmon_chip_info lwmi_om_hwmon_chip_info = { 361 + .ops = &lwmi_om_hwmon_ops, 362 + .info = lwmi_om_hwmon_info, 363 + }; 364 + 365 + /** 366 + * lwmi_om_hwmon_add() - Register HWMON device if all info is collected 367 + * @priv: Driver private data 368 + */ 369 + static void lwmi_om_hwmon_add(struct lwmi_om_priv *priv) 370 + { 371 + int i, valid; 372 + 373 + if (WARN_ON(priv->hwmon_dev)) 374 + return; 375 + 376 + if (!priv->fan_flags.capdata00_collected || !priv->fan_flags.capdata_fan_collected) { 377 + dev_dbg(&priv->wdev->dev, "HWMON registration pending (00: %d, fan: %d)\n", 378 + priv->fan_flags.capdata00_collected, 379 + priv->fan_flags.capdata_fan_collected); 380 + return; 381 + } 382 + 383 + if (expose_all_fans) 384 + dev_warn(&priv->wdev->dev, "all fans exposed. Use with caution\n"); 385 + 386 + if (relax_fan_constraint) 387 + dev_warn(&priv->wdev->dev, "fan RPM constraint relaxed. Use with caution\n"); 388 + 389 + valid = 0; 390 + for (i = 0; i < LWMI_FAN_NR; i++) { 391 + if (!(priv->fan_info[i].supported & LWMI_SUPP_VALID)) 392 + continue; 393 + 394 + valid++; 395 + 396 + if (!expose_all_fans && 397 + (priv->fan_info[i].min_rpm < 0 || priv->fan_info[i].max_rpm < 0)) { 398 + dev_dbg(&priv->wdev->dev, "missing RPM constraint for fan%d, hiding\n", 399 + LWMI_FAN_ID(i)); 400 + priv->fan_info[i].supported = 0; 401 + valid--; 402 + } 403 + } 404 + 405 + if (valid == 0) { 406 + dev_warn(&priv->wdev->dev, 407 + "fan reporting/tuning is unsupported on this device\n"); 408 + return; 409 + } 410 + 411 + priv->hwmon_dev = hwmon_device_register_with_info(&priv->wdev->dev, 412 + LWMI_OM_HWMON_NAME, priv, 413 + &lwmi_om_hwmon_chip_info, 414 + NULL); 415 + if (IS_ERR(priv->hwmon_dev)) { 416 + dev_warn(&priv->wdev->dev, "failed to register HWMON device: %ld\n", 417 + PTR_ERR(priv->hwmon_dev)); 418 + priv->hwmon_dev = NULL; 419 + return; 420 + } 421 + 422 + dev_dbg(&priv->wdev->dev, "registered HWMON device\n"); 423 + } 424 + 425 + /** 426 + * lwmi_om_hwmon_remove() - Unregister HWMON device 427 + * @priv: Driver private data 428 + * 429 + * Unregisters the HWMON device if applicable. 430 + */ 431 + static void lwmi_om_hwmon_remove(struct lwmi_om_priv *priv) 432 + { 433 + if (!priv->hwmon_dev) 434 + return; 435 + 436 + hwmon_device_unregister(priv->hwmon_dev); 437 + priv->hwmon_dev = NULL; 438 + } 439 + 440 + /** 441 + * lwmi_om_fan_info_init() - Initialzie fan info 442 + * @priv: Driver private data 443 + * 444 + * lwmi_om_fan_info_collect_cd00() and lwmi_om_fan_info_collect_cd_fan() may be 445 + * called in an arbitrary order. Hence, initializion must be done before. 446 + */ 447 + static void lwmi_om_fan_info_init(struct lwmi_om_priv *priv) 448 + { 449 + int i; 450 + 451 + for (i = 0; i < LWMI_FAN_NR; i++) { 452 + priv->fan_info[i] = (struct lwmi_fan_info) { 453 + .supported = 0, 454 + /* 455 + * Assume 0 on probe as the EC resets all fans to auto mode on (re)boot. 456 + * 457 + * Note that S0ix (s2idle) preserves the RPM target, so we don't need 458 + * suspend/resume callbacks. This behavior has not been tested on S3- 459 + * capable devices, but I doubt if such devices even have this interface. 460 + */ 461 + .last_target = 0, 462 + .min_rpm = -ENODATA, 463 + .max_rpm = -ENODATA, 464 + }; 465 + } 466 + 467 + priv->fan_flags.capdata00_collected = false; 468 + priv->fan_flags.capdata_fan_collected = false; 469 + } 470 + 471 + /** 472 + * lwmi_om_fan_info_collect_cd00() - Collect fan info from capdata 00 473 + * @priv: Driver private data 474 + */ 475 + static void lwmi_om_fan_info_collect_cd00(struct lwmi_om_priv *priv) 476 + { 477 + struct capdata00 capdata00; 478 + int i, err; 479 + 480 + dev_dbg(&priv->wdev->dev, "Collecting fan info from capdata00\n"); 481 + 482 + for (i = 0; i < LWMI_FAN_NR; i++) { 483 + err = lwmi_cd00_get_data(priv->cd00_list, LWMI_ATTR_ID_FAN_RPM(i), &capdata00); 484 + priv->fan_info[i].supported = err ? 0 : capdata00.supported; 485 + } 486 + 487 + priv->fan_flags.capdata00_collected = true; 488 + lwmi_om_hwmon_add(priv); 489 + } 490 + 491 + /** 492 + * lwmi_om_fan_info_collect_cd_fan() - Collect fan info from capdata fan 493 + * @dev: Pointer to the lenovo-wmi-other device 494 + * @cd_fan_list: Pointer to the capdata fan list 495 + */ 496 + static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list *cd_fan_list) 497 + { 498 + struct lwmi_om_priv *priv = dev_get_drvdata(dev); 499 + struct capdata_fan capdata_fan; 500 + int i, err; 501 + 502 + dev_dbg(dev, "Collecting fan info from capdata_fan\n"); 503 + 504 + if (!cd_fan_list) 505 + goto out; 506 + 507 + for (i = 0; i < LWMI_FAN_NR; i++) { 508 + err = lwmi_cd_fan_get_data(cd_fan_list, LWMI_FAN_ID(i), &capdata_fan); 509 + if (err) 510 + continue; 511 + 512 + priv->fan_info[i].min_rpm = capdata_fan.min_rpm; 513 + priv->fan_info[i].max_rpm = capdata_fan.max_rpm; 514 + } 515 + 516 + out: 517 + priv->fan_flags.capdata_fan_collected = true; 518 + lwmi_om_hwmon_add(priv); 519 + } 520 + 521 + /* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */ 107 522 108 523 struct tunable_attr_01 { 109 524 struct capdata01 *capdata; ··· 1024 559 device_unregister(priv->fw_attr_dev); 1025 560 } 1026 561 562 + /* ======== Self (master: lenovo-wmi-other) ======== */ 563 + 1027 564 /** 1028 565 * lwmi_om_master_bind() - Bind all components of the other mode driver 1029 566 * @dev: The lenovo-wmi-other driver basic device. 1030 567 * 1031 - * Call component_bind_all to bind the lenovo-wmi-capdata01 driver to the 1032 - * lenovo-wmi-other master driver. On success, assign the capability data 01 1033 - * list pointer to the driver data struct for later access. This pointer 1034 - * is only valid while the capdata01 interface exists. Finally, register all 1035 - * firmware attribute groups. 568 + * Call component_bind_all to bind the lenovo-wmi-capdata devices to the 569 + * lenovo-wmi-other master driver, with a callback to collect fan info from 570 + * capdata_fan. On success, assign the capability data list pointers to the 571 + * driver data struct for later access. These pointers are only valid while the 572 + * capdata interfaces exist. Finally, collect fan info from capdata00 and 573 + * register all firmware attribute groups. Note that the HWMON device is 574 + * registered only if all fan info is collected. Hence, it is not registered 575 + * here. See lwmi_om_fan_info_collect_cd00() and 576 + * lwmi_om_fan_info_collect_cd_fan(). 1036 577 * 1037 578 * Return: 0 on success, or an error code. 1038 579 */ 1039 580 static int lwmi_om_master_bind(struct device *dev) 1040 581 { 1041 582 struct lwmi_om_priv *priv = dev_get_drvdata(dev); 1042 - struct lwmi_cd_binder binder = {}; 583 + struct lwmi_cd_binder binder = { 584 + .cd_fan_list_cb = lwmi_om_fan_info_collect_cd_fan, 585 + }; 1043 586 int ret; 587 + 588 + lwmi_om_fan_info_init(priv); 1044 589 1045 590 ret = component_bind_all(dev, &binder); 1046 591 if (ret) 1047 592 return ret; 1048 593 594 + priv->cd00_list = binder.cd00_list; 1049 595 priv->cd01_list = binder.cd01_list; 1050 - if (!priv->cd01_list) 596 + if (!priv->cd00_list || !priv->cd01_list) 1051 597 return -ENODEV; 598 + 599 + lwmi_om_fan_info_collect_cd00(priv); 1052 600 1053 601 return lwmi_om_fw_attr_add(priv); 1054 602 } ··· 1070 592 * lwmi_om_master_unbind() - Unbind all components of the other mode driver 1071 593 * @dev: The lenovo-wmi-other driver basic device 1072 594 * 1073 - * Unregister all capability data attribute groups. Then call 1074 - * component_unbind_all to unbind the lenovo-wmi-capdata01 driver from the 1075 - * lenovo-wmi-other master driver. Finally, free the IDA for this device. 595 + * Unregister all firmware attribute groups and the HWMON device. Then call 596 + * component_unbind_all to unbind lenovo-wmi-capdata devices from the 597 + * lenovo-wmi-other master driver. 1076 598 */ 1077 599 static void lwmi_om_master_unbind(struct device *dev) 1078 600 { 1079 601 struct lwmi_om_priv *priv = dev_get_drvdata(dev); 1080 602 1081 603 lwmi_om_fw_attr_remove(priv); 604 + 605 + lwmi_om_hwmon_remove(priv); 606 + 1082 607 component_unbind_all(dev, NULL); 1083 608 } 1084 609 ··· 1146 665 MODULE_IMPORT_NS("LENOVO_WMI_HELPERS"); 1147 666 MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table); 1148 667 MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); 668 + MODULE_AUTHOR("Rong Zhang <i@rong.moe>"); 1149 669 MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver"); 1150 670 MODULE_LICENSE("GPL");