FILE
wmi-gamezone.c
File preview with syntax highlighting.
linux-6.19.14/drivers/platform/x86/lenovo/wmi-gamezone.c
1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * Lenovo GameZone WMI interface driver. 4 * 5 * The GameZone WMI interface provides platform profile and fan curve settings 6 * for devices that fall under the "Gaming Series" of Lenovo Legion devices. 7 * 8 * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> 9 */ 10 11#include <linux/acpi.h> 12#include <linux/dmi.h> 13#include <linux/export.h> 14#include <linux/list.h> 15#include <linux/module.h> 16#include <linux/notifier.h> 17#include <linux/platform_profile.h> 18#include <linux/spinlock.h> 19#include <linux/spinlock_types.h> 20#include <linux/types.h> 21#include <linux/wmi.h> 22 23#include "wmi-events.h" 24#include "wmi-gamezone.h" 25#include "wmi-helpers.h" 26#include "wmi-other.h" 27 28#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0" 29 30#define LWMI_GZ_METHOD_ID_SMARTFAN_SUP 43 31#define LWMI_GZ_METHOD_ID_SMARTFAN_SET 44 32#define LWMI_GZ_METHOD_ID_SMARTFAN_GET 45 33 34struct lwmi_gz_priv { 35 enum thermal_mode current_mode; 36 struct notifier_block event_nb; 37 struct notifier_block mode_nb; 38 spinlock_t gz_mode_lock; /* current_mode lock */ 39 struct wmi_device *wdev; 40 int extreme_supported; 41 struct device *ppdev; 42}; 43 44struct quirk_entry { 45 bool extreme_supported; 46}; 47 48static struct quirk_entry quirk_no_extreme_bug = { 49 .extreme_supported = false, 50}; 51 52/** 53 * lwmi_gz_mode_call() - Call method for lenovo-wmi-other driver notifier. 54 * 55 * @nb: The notifier_block registered to lenovo-wmi-other driver. 56 * @cmd: The event type. 57 * @data: Thermal mode enum pointer pointer for returning the thermal mode. 58 * 59 * For LWMI_GZ_GET_THERMAL_MODE, retrieve the current thermal mode. 60 * 61 * Return: Notifier_block status. 62 */ 63static int lwmi_gz_mode_call(struct notifier_block *nb, unsigned long cmd, 64 void *data) 65{ 66 enum thermal_mode **mode = data; 67 struct lwmi_gz_priv *priv; 68 69 priv = container_of(nb, struct lwmi_gz_priv, mode_nb); 70 71 switch (cmd) { 72 case LWMI_GZ_GET_THERMAL_MODE: 73 scoped_guard(spinlock, &priv->gz_mode_lock) { 74 **mode = priv->current_mode; 75 } 76 return NOTIFY_OK; 77 default: 78 return NOTIFY_DONE; 79 } 80} 81 82/** 83 * lwmi_gz_event_call() - Call method for lenovo-wmi-events driver notifier. 84 * block call chain. 85 * @nb: The notifier_block registered to lenovo-wmi-events driver. 86 * @cmd: The event type. 87 * @data: The data to be updated by the event. 88 * 89 * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile 90 * of a change. 91 * 92 * Return: notifier_block status. 93 */ 94static int lwmi_gz_event_call(struct notifier_block *nb, unsigned long cmd, 95 void *data) 96{ 97 enum thermal_mode *mode = data; 98 struct lwmi_gz_priv *priv; 99 100 priv = container_of(nb, struct lwmi_gz_priv, event_nb); 101 102 switch (cmd) { 103 case LWMI_EVENT_THERMAL_MODE: 104 scoped_guard(spinlock, &priv->gz_mode_lock) { 105 priv->current_mode = *mode; 106 } 107 platform_profile_notify(priv->ppdev); 108 return NOTIFY_STOP; 109 default: 110 return NOTIFY_DONE; 111 } 112} 113 114/** 115 * lwmi_gz_thermal_mode_supported() - Get the version of the WMI 116 * interface to determine the support level. 117 * @wdev: The Gamezone WMI device. 118 * @supported: Pointer to return the support level with. 119 * 120 * Return: 0 on success, or an error code. 121 */ 122static int lwmi_gz_thermal_mode_supported(struct wmi_device *wdev, 123 int *supported) 124{ 125 return lwmi_dev_evaluate_int(wdev, 0x0, LWMI_GZ_METHOD_ID_SMARTFAN_SUP, 126 NULL, 0, supported); 127} 128 129/** 130 * lwmi_gz_thermal_mode_get() - Get the current thermal mode. 131 * @wdev: The Gamezone interface WMI device. 132 * @mode: Pointer to return the thermal mode with. 133 * 134 * Return: 0 on success, or an error code. 135 */ 136static int lwmi_gz_thermal_mode_get(struct wmi_device *wdev, 137 enum thermal_mode *mode) 138{ 139 return lwmi_dev_evaluate_int(wdev, 0x0, LWMI_GZ_METHOD_ID_SMARTFAN_GET, 140 NULL, 0, mode); 141} 142 143/** 144 * lwmi_gz_profile_get() - Get the current platform profile. 145 * @dev: the Gamezone interface parent device. 146 * @profile: Pointer to provide the current platform profile with. 147 * 148 * Call lwmi_gz_thermal_mode_get and convert the thermal mode into a platform 149 * profile based on the support level of the interface. 150 * 151 * Return: 0 on success, or an error code. 152 */ 153static int lwmi_gz_profile_get(struct device *dev, 154 enum platform_profile_option *profile) 155{ 156 struct lwmi_gz_priv *priv = dev_get_drvdata(dev); 157 enum thermal_mode mode; 158 int ret; 159 160 ret = lwmi_gz_thermal_mode_get(priv->wdev, &mode); 161 if (ret) 162 return ret; 163 164 switch (mode) { 165 case LWMI_GZ_THERMAL_MODE_QUIET: 166 *profile = PLATFORM_PROFILE_LOW_POWER; 167 break; 168 case LWMI_GZ_THERMAL_MODE_BALANCED: 169 *profile = PLATFORM_PROFILE_BALANCED; 170 break; 171 case LWMI_GZ_THERMAL_MODE_PERFORMANCE: 172 *profile = PLATFORM_PROFILE_PERFORMANCE; 173 break; 174 case LWMI_GZ_THERMAL_MODE_EXTREME: 175 *profile = PLATFORM_PROFILE_MAX_POWER; 176 break; 177 case LWMI_GZ_THERMAL_MODE_CUSTOM: 178 *profile = PLATFORM_PROFILE_CUSTOM; 179 break; 180 default: 181 return -EINVAL; 182 } 183 184 guard(spinlock)(&priv->gz_mode_lock); 185 priv->current_mode = mode; 186 187 return 0; 188} 189 190/** 191 * lwmi_gz_profile_set() - Set the current platform profile. 192 * @dev: The Gamezone interface parent device. 193 * @profile: Pointer to the desired platform profile. 194 * 195 * Convert the given platform profile into a thermal mode based on the support 196 * level of the interface, then call the WMI method to set the thermal mode. 197 * 198 * Return: 0 on success, or an error code. 199 */ 200static int lwmi_gz_profile_set(struct device *dev, 201 enum platform_profile_option profile) 202{ 203 struct lwmi_gz_priv *priv = dev_get_drvdata(dev); 204 struct wmi_method_args_32 args; 205 enum thermal_mode mode; 206 int ret; 207 208 switch (profile) { 209 case PLATFORM_PROFILE_LOW_POWER: 210 mode = LWMI_GZ_THERMAL_MODE_QUIET; 211 break; 212 case PLATFORM_PROFILE_BALANCED: 213 mode = LWMI_GZ_THERMAL_MODE_BALANCED; 214 break; 215 case PLATFORM_PROFILE_PERFORMANCE: 216 mode = LWMI_GZ_THERMAL_MODE_PERFORMANCE; 217 break; 218 case PLATFORM_PROFILE_MAX_POWER: 219 mode = LWMI_GZ_THERMAL_MODE_EXTREME; 220 break; 221 case PLATFORM_PROFILE_CUSTOM: 222 mode = LWMI_GZ_THERMAL_MODE_CUSTOM; 223 break; 224 default: 225 return -EOPNOTSUPP; 226 } 227 228 args.arg0 = mode; 229 230 ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, 231 LWMI_GZ_METHOD_ID_SMARTFAN_SET, 232 (u8 *)&args, sizeof(args), NULL); 233 if (ret) 234 return ret; 235 236 guard(spinlock)(&priv->gz_mode_lock); 237 priv->current_mode = mode; 238 239 return 0; 240} 241 242static const struct dmi_system_id fwbug_list[] = { 243 { 244 .ident = "Legion Go 8APU1", 245 .matches = { 246 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 247 DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"), 248 }, 249 .driver_data = &quirk_no_extreme_bug, 250 }, 251 { 252 .ident = "Legion Go S 8APU1", 253 .matches = { 254 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 255 DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8APU1"), 256 }, 257 .driver_data = &quirk_no_extreme_bug, 258 }, 259 { 260 .ident = "Legion Go S 8ARP1", 261 .matches = { 262 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 263 DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8ARP1"), 264 }, 265 .driver_data = &quirk_no_extreme_bug, 266 }, 267 { 268 .ident = "Legion Go 8ASP2", 269 .matches = { 270 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 271 DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8ASP2"), 272 }, 273 .driver_data = &quirk_no_extreme_bug, 274 }, 275 { 276 .ident = "Legion Go 8AHP2", 277 .matches = { 278 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 279 DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8AHP2"), 280 }, 281 .driver_data = &quirk_no_extreme_bug, 282 }, 283 {}, 284}; 285 286/** 287 * lwmi_gz_extreme_supported() - Evaluate if a device supports extreme thermal mode. 288 * @profile_support_ver: Version of the WMI interface. 289 * 290 * Determine if the extreme thermal mode is supported by the hardware. 291 * Anything version 5 or lower does not. For devices with a version 6 or 292 * greater do a DMI check, as some devices report a version that supports 293 * extreme mode but have an incomplete entry in the BIOS. To ensure this 294 * cannot be set, quirk them to prevent assignment. 295 * 296 * Return: bool. 297 */ 298static bool lwmi_gz_extreme_supported(int profile_support_ver) 299{ 300 const struct dmi_system_id *dmi_id; 301 struct quirk_entry *quirks; 302 303 if (profile_support_ver < 6) 304 return false; 305 306 dmi_id = dmi_first_match(fwbug_list); 307 if (!dmi_id) 308 return true; 309 310 quirks = dmi_id->driver_data; 311 312 return quirks->extreme_supported; 313} 314 315/** 316 * lwmi_gz_platform_profile_probe - Enable and set up the platform profile 317 * device. 318 * @drvdata: Driver data for the interface. 319 * @choices: Container for enabled platform profiles. 320 * 321 * Determine if thermal mode is supported, and if so to what feature level. 322 * Then enable all supported platform profiles. 323 * 324 * Return: 0 on success, or an error code. 325 */ 326static int lwmi_gz_platform_profile_probe(void *drvdata, unsigned long *choices) 327{ 328 struct lwmi_gz_priv *priv = drvdata; 329 int profile_support_ver; 330 int ret; 331 332 ret = lwmi_gz_thermal_mode_supported(priv->wdev, &profile_support_ver); 333 if (ret) 334 return ret; 335 336 if (profile_support_ver < 1) 337 return -ENODEV; 338 339 set_bit(PLATFORM_PROFILE_LOW_POWER, choices); 340 set_bit(PLATFORM_PROFILE_BALANCED, choices); 341 set_bit(PLATFORM_PROFILE_PERFORMANCE, choices); 342 set_bit(PLATFORM_PROFILE_CUSTOM, choices); 343 344 priv->extreme_supported = lwmi_gz_extreme_supported(profile_support_ver); 345 if (priv->extreme_supported) 346 set_bit(PLATFORM_PROFILE_MAX_POWER, choices); 347 348 return 0; 349} 350 351static const struct platform_profile_ops lwmi_gz_platform_profile_ops = { 352 .probe = lwmi_gz_platform_profile_probe, 353 .profile_get = lwmi_gz_profile_get, 354 .profile_set = lwmi_gz_profile_set, 355}; 356 357static int lwmi_gz_probe(struct wmi_device *wdev, const void *context) 358{ 359 struct lwmi_gz_priv *priv; 360 int ret; 361 362 priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); 363 if (!priv) 364 return -ENOMEM; 365 366 priv->wdev = wdev; 367 dev_set_drvdata(&wdev->dev, priv); 368 369 priv->ppdev = devm_platform_profile_register(&wdev->dev, "lenovo-wmi-gamezone", 370 priv, &lwmi_gz_platform_profile_ops); 371 if (IS_ERR(priv->ppdev)) 372 return -ENODEV; 373 374 spin_lock_init(&priv->gz_mode_lock); 375 376 ret = lwmi_gz_thermal_mode_get(wdev, &priv->current_mode); 377 if (ret) 378 return ret; 379 380 priv->event_nb.notifier_call = lwmi_gz_event_call; 381 ret = devm_lwmi_events_register_notifier(&wdev->dev, &priv->event_nb); 382 if (ret) 383 return ret; 384 385 priv->mode_nb.notifier_call = lwmi_gz_mode_call; 386 return devm_lwmi_om_register_notifier(&wdev->dev, &priv->mode_nb); 387} 388 389static const struct wmi_device_id lwmi_gz_id_table[] = { 390 { LENOVO_GAMEZONE_GUID, NULL }, 391 {} 392}; 393 394static struct wmi_driver lwmi_gz_driver = { 395 .driver = { 396 .name = "lenovo_wmi_gamezone", 397 .probe_type = PROBE_PREFER_ASYNCHRONOUS, 398 }, 399 .id_table = lwmi_gz_id_table, 400 .probe = lwmi_gz_probe, 401 .no_singleton = true, 402}; 403 404module_wmi_driver(lwmi_gz_driver); 405 406MODULE_IMPORT_NS("LENOVO_WMI_EVENTS"); 407MODULE_IMPORT_NS("LENOVO_WMI_HELPERS"); 408MODULE_IMPORT_NS("LENOVO_WMI_OTHER"); 409MODULE_DEVICE_TABLE(wmi, lwmi_gz_id_table); 410MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>"); 411MODULE_DESCRIPTION("Lenovo GameZone WMI Driver"); 412MODULE_LICENSE("GPL");
~/
Read-only file preview.