From nobody Wed Dec 17 15:33:16 2025 Received: from mail-pj1-f46.google.com (mail-pj1-f46.google.com [209.85.216.46]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A121D1B0401; Mon, 17 Mar 2025 06:33:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.46 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742193241; cv=none; b=Wu/dsRo/t1vOhlRX4m1h2IgImtJCWQEB8jTpVAohIiNruHjiXgYkSRA9tA+EhaMJkNSZXYQ9+TN7vhdmv8TwKkk/8Y3twvhERRYwzfDw0OufLpFIG8b3uPBGKH3JYg1ptGesTlqiTvLMB3PFUFel4bpSBi6tDueOwF4RYTmcwMM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742193241; c=relaxed/simple; bh=cnzHwtcxR0J1F/xOvyMa2Ch1YB3B5zJbuKmfoCLlHGQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=nULxY+8jkaY8ivqmHgKU9pCdlpDZxNXOyit3VvGQTTJIRvOez8EJmAk8wpJ8V+ssHHdzYH7t8oKSRkStwSTwbbBNweXx2NFjmc/5FZBzUjFqLdCQUrPysfXFJz6PMViq5YA9VlpcdaVb9xjmE0XGI/QufPIXccpOmx9INkT7tx4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=iot4REpc; arc=none smtp.client-ip=209.85.216.46 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="iot4REpc" Received: by mail-pj1-f46.google.com with SMTP id 98e67ed59e1d1-3018c9c6b5fso646662a91.3; Sun, 16 Mar 2025 23:33:59 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1742193239; x=1742798039; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=yJvFT5GCHg1GWLrlxfBtd+P/oPS0GN0NKk2wNs1GpZw=; b=iot4REpcZjrOsRYp1RAzA8HgNpWSXEof6uVa1lnh+PwvcLyRLk/WaD/ts6XqDUfoR1 3d/VPPfs7FSsD55iaGxW5gfo25OxUALRWN7DPaJPhR8DKKY37wlLiT5SEXlciyOCd6mh RovJ/vd9gw4fxITMzdW0EIj2G3WFCtC+PAQ4e9FI6rUtTcx5x083lQlxj9u6McWn3uxI +BRh89dfZROUN0Ot4jQmJV8ffp/TDzYXbvtoS/b0si4mRpkruGd99DYZl+0o2UpUG5BG xmZ9dKS+E032kixtubH1UWLNpTYUV2iNLY/dwAwN+b3Ld7ow0dI/LDCVbiaslZaxfHtL dAdA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1742193239; x=1742798039; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=yJvFT5GCHg1GWLrlxfBtd+P/oPS0GN0NKk2wNs1GpZw=; b=uU0gnrQPHM98NhOfyWDFFXXX2eAQqftiX7RnpKvbPwggJbzECUx7oZWB4a1yVp7gTf aXUvhXtROC9DcSNsPhX+D8XmrmMPK9TPgzdLnistb+QReojcPMy0jZsDIS13YrMOYxzT SLA6Bor7tmOtN1L8XQYYXLcS+bdDsweENCE16mP/iAwxMNacj+ftbNKTFIAYGcbfW4RD CVdWmUJ8Qe7c2Kbho8JNeq/tdNB0sCWC/YFljjVoTI+lx2Iy9aXCwGO6arXBSnRdEryM FzF9ol9+F3W/RTVI4A9X0vW0FgQOAruPumf+3Eei4cecxCacvZp86GloF25vTKZWu6Zo 4lzQ== X-Forwarded-Encrypted: i=1; AJvYcCW/x9kmr8K9gMvltRuyvn355HZQ3olvk285NTUpRtnj6r+PzmpVxv0G1JNuPg5MjR3VGBHiO1Zx9gg=@vger.kernel.org, AJvYcCWO6Ke7V9AW+NM7JI3WMb/fTdFXJOeIuUcrLcUPEw1Dt5URixQ7eJRvZg2mznhfAdAltW7vdwNKtWQXy9F9vBZaqbiKNw==@vger.kernel.org, AJvYcCXSfQYGvp2olo3OJa92aYTajggJebemr5Q6yAuO5gkwiT2zeAvqPP8dNpx73ESWEGBw3oG3IrXnczs/VXwq@vger.kernel.org X-Gm-Message-State: AOJu0YyoHv4GJINJeP2aLsTdgOf8xz/csLTK8FO/1KT4OBTgi46JhxNy LJ5o7SHuJ1pXQdAtSsGJFV0Sriu5mrxiKsI8tv433VXKm1zTE/zT X-Gm-Gg: ASbGncuibsEoQ18P9FECciKMcOm6z3MJGmsfsO2DRW2gCQPKnPJrHJn5lBzY2U3+SIm EvvN8SxvQpcXy8nxYn69oOEHK84wHVTsxxL+WcsPmH7utqGEpQ+GQxnRaEjCviZu1CjYnmVv4rP r69qfJb8UVnC+dMTMJkpbf8FubATQbsSos7HuOoIjPdYetNhk9+3V7Rofzpd/OQAr+N1ndYKiAO Cp6joy1168IcAlXqqDp6zrKrlzVCRu7x7o+z+HewAWbpY/x7i7IW0SuH8jJFWI6eobnHVLI0kwd w54mmGPHcuXVOvEPFdtFgUa3nD6G30+IhlEACXPDRZB+az63yfPal9gk5y9hvC/n5UBFyMBzMnF +vIDyh+RSYk6mB+TWYEA3xMtTUlQdbV6ziEJAO8PSdqc= X-Google-Smtp-Source: AGHT+IFZT82O3nYeordHUA8x5UxM/1Rr2fdt07QrQCagee4x/x6EFJ2F7r+pMUkm36GZYn+hka2g5g== X-Received: by 2002:a17:90a:ea8c:b0:2ff:52e1:c4b4 with SMTP id 98e67ed59e1d1-30151d81707mr12103836a91.32.1742193238592; Sun, 16 Mar 2025 23:33:58 -0700 (PDT) Received: from localhost.localdomain (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-30153afee51sm5186555a91.28.2025.03.16.23.33.57 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 16 Mar 2025 23:33:58 -0700 (PDT) From: "Derek J. Clark" To: Hans de Goede , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Cc: Armin Wolf , Jonathan Corbet , Mario Limonciello , Luke Jones , Xino Ni , Zhixin Zhang , Mia Shao , Mark Pearson , "Pierre-Loup A . Griffais" , "Cody T . -H . Chiu" , John Martens , "Derek J . Clark" , platform-driver-x86@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v4 1/6] platform/x86: Add lenovo-wmi-* driver Documentation Date: Sun, 16 Mar 2025 23:33:28 -0700 Message-ID: <20250317063337.352966-2-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20250317063337.352966-1-derekjohn.clark@gmail.com> References: <20250317063337.352966-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds documentation for new lenovo-wmi drivers. Signed-off-by: Derek J. Clark Suggested-by: Mario Limonciello --- v4: - Fixed MOF formatting issues. - Fixed spelling mistakes. - Updated description of balanced-performance profile for Gamezone. - Updated description of thermal mode event GUID for Gamezone. v3: - Split documentation into multiple files, one for each parent driver for the Gamezone and Other Mode WMI interfaces. - Add MOF data for all parent and child interfaces. - Remove lenovo-wmi-camera.c driver from v2 documentation. v2: - Update description of Custom Profile to include the need to manually set it. - Remove all references to Legion hardware. - Add section for lenovo-wmi-camera.c driver as it follows the same naming convention. --- .../wmi/devices/lenovo-wmi-gamezone.rst | 203 ++++++++++++++++++ .../wmi/devices/lenovo-wmi-other-method.rst | 108 ++++++++++ MAINTAINERS | 7 + 3 files changed, 318 insertions(+) create mode 100644 Documentation/wmi/devices/lenovo-wmi-gamezone.rst create mode 100644 Documentation/wmi/devices/lenovo-wmi-other-method.rst diff --git a/Documentation/wmi/devices/lenovo-wmi-gamezone.rst b/Documentat= ion/wmi/devices/lenovo-wmi-gamezone.rst new file mode 100644 index 000000000000..bde63dde285d --- /dev/null +++ b/Documentation/wmi/devices/lenovo-wmi-gamezone.rst @@ -0,0 +1,203 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D +Lenovo WMI Interface Gamezone Driver (lenovo-wmi-gamezone) +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D + +Introduction +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +The Lenovo WMI gamezone interface is broken up into multiple GUIDs, +The priamry "Gamezone" GUID provides advanced features such as fan +profiles and overclocking. It is paired with multiple event GUIDs +and data block GUIDs that provide context for the various methods. + +Gamezone Data +------------- + +WMI GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Gamezone Data WMI interface provides platform-profile and fan curve +settings for devices that fall under the "Gaming Series" of Lenovo devices. +It uses a notifier chain to inform other Lenovo WMI interface drivers of t= he +current platform profile when it changes. + +The following platform profiles are supported: + - low-power + - balanced + - balanced-performance* + - performance + - custom + +Balanced-Performance +~~~~~~~~~~~~~~~~~~~~ +Some newer Lenovo "Gaming Series" laptops have an "Extreme Mode" profile +enabled in their BIOS. For these devices, the performance platform profile +will correspond to the BIOS Extreme Mode, while the balanced-performance +platform profile will correspond to the BIOS Performance mode. For legacy +devices, the performance platform profile will correspond with the BIOS +Performance mode. For some newer devices the "Extreme Mode" profile is +incomplete in the BIOS and setting it will cause undefined behavior. A +BIOS bug quirk table is provided to ensure these devices cannot set +"Extreme Mode" from the driver. + +Custom Profile +~~~~~~~~~~~~~~ +The custom profile represents a hardware mode on Lenovo devices that enabl= es +user modifications to Package Power Tracking (PPT) and fan curve settings. +When an attribute exposed by the Other Mode WMI interface is to be modifie= d, +the Gamezone driver must first be switched to the "custom" profile manuall= y, +or the setting will have no effect. If another profile is set from the list +of supported profiles, the BIOS will override any user PPT settings when +switching to that profile. + +Gamezone Thermal Mode Event +--------------------------- + +WMI GUID "D320289E-8FEA-41E0-86F9-911D83151B5F" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Gamezone Thermal Mode Event interface notifies the system when the pla= tform +profile has changed, either through the hardware event (Fn+Q for laptops or +Legion + Y for Go Series), or through the Gamezone WMI interface. This eve= nt is +implemented in the Lenovo WMI Events driver (lenovo-wmi-events). + + +WMI interface description +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D + +The WMI interface description can be decoded from the embedded binary MOF = (bmof) +data using the `bmfdec `_ utility: + +:: + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LE= NOVO_GAMEZONE_DATA class"), guid("{887B54E3-DDDC-4B2C-8B88-68A26A8835D0}")] + class LENOVO_GAMEZONE_DATA { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiMethodId(4), Implemented, Description("Is SupportGpu OverClock")] = void IsSupportGpuOC([out, Description("Is SupportGpu OverClock")] uint32 Da= ta); + [WmiMethodId(11), Implemented, Description("Get AslCode Version")] voi= d GetVersion ([out, Description("AslCode version")] UINT32 Data); + [WmiMethodId(12), Implemented, Description("Fan cooling capability")] = void IsSupportFanCooling([out, Description("Fan cooling capability")] UINT3= 2 Data); + [WmiMethodId(13), Implemented, Description("Set Fan cooling on/off")] = void SetFanCooling ([in, Description("Set Fan cooling on/off")] UINT32 Data= ); + [WmiMethodId(14), Implemented, Description("cpu oc capability")] void = IsSupportCpuOC ([out, Description("cpu oc capability")] UINT32 Data); + [WmiMethodId(15), Implemented, Description("bios has overclock capabil= ity")] void IsBIOSSupportOC ([out, Description("bios has overclock capabili= ty")] UINT32 Data); + [WmiMethodId(16), Implemented, Description("enable or disable overcloc= k in bios")] void SetBIOSOC ([in, Description("enable or disable overclock = in bios")] UINT32 Data); + [WmiMethodId(18), Implemented, Description("Get CPU temperature")] voi= d GetCPUTemp ([out, Description("Get CPU temperature")] UINT32 Data); + [WmiMethodId(19), Implemented, Description("Get GPU temperature")] voi= d GetGPUTemp ([out, Description("Get GPU temperature")] UINT32 Data); + [WmiMethodId(20), Implemented, Description("Get Fan cooling on/off sta= tus")] void GetFanCoolingStatus ([out, Description("Get Fan cooling on/off = status")] UINT32 Data); + [WmiMethodId(21), Implemented, Description("EC support disable windows= key capability")] void IsSupportDisableWinKey ([out, Description("EC suppo= rt disable windows key capability")] UINT32 Data); + [WmiMethodId(22), Implemented, Description("Set windows key disable/en= able")] void SetWinKeyStatus ([in, Description("Set windows key disable/ena= ble")] UINT32 Data); + [WmiMethodId(23), Implemented, Description("Get windows key disable/en= able status")] void GetWinKeyStatus ([out, Description("Get windows key dis= able/enable status")] UINT32 Data); + [WmiMethodId(24), Implemented, Description("EC support disable touchpa= d capability")] void IsSupportDisableTP ([out, Description("EC support disa= ble touchpad capability")] UINT32 Data); + [WmiMethodId(25), Implemented, Description("Set touchpad disable/enabl= e")] void SetTPStatus ([in, Description("Set touchpad disable/enable")] UIN= T32 Data); + [WmiMethodId(26), Implemented, Description("Get touchpad disable/enabl= e status")] void GetTPStatus ([out, Description("Get touchpad disable/enabl= e status")] UINT32 Data); + [WmiMethodId(30), Implemented, Description("Get Keyboard feature list"= )] void GetKeyboardfeaturelist ([out, Description("Get Keyboard feature lis= t")] UINT32 Data); + [WmiMethodId(31), Implemented, Description("Get Memory OC Information"= )] void GetMemoryOCInfo ([out, Description("Get Memory OC Information")] UI= NT32 Data); + [WmiMethodId(32), Implemented, Description("Water Cooling feature capa= bility")] void IsSupportWaterCooling ([out, Description("Water Cooling feat= ure capability")] UINT32 Data); + [WmiMethodId(33), Implemented, Description("Set Water Cooling status")= ] void SetWaterCoolingStatus ([in, Description("Set Water Cooling status")]= UINT32 Data); + [WmiMethodId(34), Implemented, Description("Get Water Cooling status")= ] void GetWaterCoolingStatus ([out, Description("Get Water Cooling status")= ] UINT32 Data); + [WmiMethodId(35), Implemented, Description("Lighting feature capabilit= y")] void IsSupportLightingFeature ([out, Description("Lighting feature cap= ability")] UINT32 Data); + [WmiMethodId(36), Implemented, Description("Set keyboard light off or = on to max")] void SetKeyboardLight ([in, Description("keyboard light off or= on switch")] UINT32 Data); + [WmiMethodId(37), Implemented, Description("Get keyboard light on/off = status")] void GetKeyboardLight ([out, Description("Get keyboard light on/o= ff status")] UINT32 Data); + [WmiMethodId(38), Implemented, Description("Get Macrokey scan code")] = void GetMacrokeyScancode ([in, Description("Macrokey index")] UINT32 idx, [= out, Description("Scan code")] UINT32 scancode); + [WmiMethodId(39), Implemented, Description("Get Macrokey count")] void= GetMacrokeyCount ([out, Description("Macrokey count")] UINT32 Data); + [WmiMethodId(40), Implemented, Description("Support G-Sync feature")] = void IsSupportGSync ([out, Description("Support G-Sync feature")] UINT32 Da= ta); + [WmiMethodId(41), Implemented, Description("Get G-Sync Status")] void = GetGSyncStatus ([out, Description("Get G-Sync Status")] UINT32 Data); + [WmiMethodId(42), Implemented, Description("Set G-Sync Status")] void = SetGSyncStatus ([in, Description("Set G-Sync Status")] UINT32 Data); + [WmiMethodId(43), Implemented, Description("Support Smart Fan feature"= )] void IsSupportSmartFan ([out, Description("Support Smart Fan feature")] = UINT32 Data); + [WmiMethodId(44), Implemented, Description("Set Smart Fan Mode")] void= SetSmartFanMode ([in, Description("Set Smart Fan Mode")] UINT32 Data); + [WmiMethodId(45), Implemented, Description("Get Smart Fan Mode")] void= GetSmartFanMode ([out, Description("Get Smart Fan Mode")] UINT32 Data); + [WmiMethodId(46), Implemented, Description("Get Smart Fan Setting Mode= ")] void GetSmartFanSetting ([out, Description("Get Smart Setting Mode")] U= INT32 Data); + [WmiMethodId(47), Implemented, Description("Get Power Charge Mode")] v= oid GetPowerChargeMode ([out, Description("Get Power Charge Mode")] UINT32 = Data); + [WmiMethodId(48), Implemented, Description("Get Gaming Product Info")]= void GetProductInfo ([out, Description("Get Gaming Product Info")] UINT32 = Data); + [WmiMethodId(49), Implemented, Description("Over Drive feature capabil= ity")] void IsSupportOD ([out, Description("Over Drive feature capability")= ] UINT32 Data); + [WmiMethodId(50), Implemented, Description("Get Over Drive status")] v= oid GetODStatus ([out, Description("Get Over Drive status")] UINT32 Data); + [WmiMethodId(51), Implemented, Description("Set Over Drive status")] v= oid SetODStatus ([in, Description("Set Over Drive status")] UINT32 Data); + [WmiMethodId(52), Implemented, Description("Set Light Control Owner")]= void SetLightControlOwner ([in, Description("Set Light Control Owner")] UI= NT32 Data); + [WmiMethodId(53), Implemented, Description("Set DDS Control Owner")] v= oid SetDDSControlOwner ([in, Description("Set DDS Control Owner")] UINT32 D= ata); + [WmiMethodId(54), Implemented, Description("Get the flag of restore OC= value")] void IsRestoreOCValue ([in, Description("Clean this flag")] UINT3= 2 idx, [out, Description("Restore oc value flag")] UINT32 Data); + [WmiMethodId(55), Implemented, Description("Get Real Thremal Mode")] v= oid GetThermalMode ([out, Description("Real Thremal Mode")] UINT32 Data); + [WmiMethodId(56), Implemented, Description("Get the OC switch status i= n BIOS")] void GetBIOSOCMode ([out, Description("OC Mode")] UINT32 Data); + [WmiMethodId(59), Implemented, Description("Get hardware info support = version")] void GetHardwareInfoSupportVersion ([out, Description("version")= ] UINT32 Data); + [WmiMethodId(60), Implemented, Description("Get Cpu core 0 max frequen= cy")] void GetCpuFrequency ([out, Description("frequency")] UINT32 Data); + [WmiMethodId(62), Implemented, Description("Check the Adapter type fit= for OC")] void IsACFitForOC ([out, Description("AC check result")] UINT32 = Data); + [WmiMethodId(63), Implemented, Description("Is support IGPU mode")] vo= id IsSupportIGPUMode ([out, Description("IGPU modes")] UINT32 Data); + [WmiMethodId(64), Implemented, Description("Get IGPU Mode Status")] vo= id GetIGPUModeStatus([out, Description("IGPU Mode Status")] UINT32 Data); + [WmiMethodId(65), Implemented, Description("Set IGPU Mode")] void SetI= GPUModeStatus([in, Description("IGPU Mode")] UINT32 mode, [out, Description= ("return code")] UINT32 Data); + [WmiMethodId(66), Implemented, Description("Notify DGPU Status")] void= NotifyDGPUStatus([in, Description("DGPU status")] UINT32 status, [out, Des= cription("return code")] UINT32 Data); + [WmiMethodId(67), Implemented, Description("Is changed Y log")] void I= sChangedYLog([out, Description("Is changed Y Log")] UINT32 Data); + [WmiMethodId(68), Implemented, Description("Get DGPU Hardwawre ID")] v= oid GetDGPUHWId([out, Description("Get DGPU Hardware ID")] string Data); + }; + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("De= finition of CPU OC parameter list"), guid("{B7F3CA0A-ACDC-42D2-9217-77C6C62= 8FBD2}")] + class LENOVO_GAMEZONE_CPU_OC_DATA { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiDataId(1), read, Description("OC tune id.")] uint32 Tuneid; + [WmiDataId(2), read, Description("Default value.")] uint32 DefaultValu= e; + [WmiDataId(3), read, Description("OC Value.")] uint32 OCValue; + [WmiDataId(4), read, Description("Min Value.")] uint32 MinValue; + [WmiDataId(5), read, Description("Max Value.")] uint32 MaxValue; + [WmiDataId(6), read, Description("Scale Value.")] uint32 ScaleValue; + [WmiDataId(7), read, Description("OC Order id.")] uint32 OCOrderid; + [WmiDataId(8), read, Description("NON-OC Order id.")] uint32 NOCOrderi= d; + [WmiDataId(9), read, Description("Delay time in ms.")] uint32 Interval; + }; + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("De= finition of GPU OC parameter list"), guid("{887B54E2-DDDC-4B2C-8B88-68A26A8= 835D0}")] + class LENOVO_GAMEZONE_GPU_OC_DATA { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiDataId(1), read, Description("P-State ID.")] uint32 PStateID; + [WmiDataId(2), read, Description("CLOCK ID.")] uint32 ClockID; + [WmiDataId(3), read, Description("Default value.")] uint32 defaultvalu= e; + [WmiDataId(4), read, Description("OC Offset freqency.")] uint32 OCOffs= etFreq; + [WmiDataId(5), read, Description("OC Min offset value.")] uint32 OCMin= Offset; + [WmiDataId(6), read, Description("OC Max offset value.")] uint32 OCMax= Offset; + [WmiDataId(7), read, Description("OC Offset Scale.")] uint32 OCOffsetS= cale; + [WmiDataId(8), read, Description("OC Order id.")] uint32 OCOrderid; + [WmiDataId(9), read, Description("NON-OC Order id.")] uint32 NOCOrderi= d; + }; + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Fa= ncooling finish event"), guid("{BC72A435-E8C1-4275-B3E2-D8B8074ABA59}")] + class LENOVO_GAMEZONE_FAN_COOLING_EVENT: WMIEvent { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiDataId(1), read, Description("Fancooling clean finish event")] uin= t32 EventId; + }; + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Sm= art Fan mode change event"), guid("{D320289E-8FEA-41E0-86F9-611D83151B5F}")] + class LENOVO_GAMEZONE_SMART_FAN_MODE_EVENT: WMIEvent { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiDataId(1), read, Description("Smart Fan Mode change event")] uint3= 2 mode; + [WmiDataId(2), read, Description("version of FN+Q")] uint32 version; + }; + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Sm= art Fan setting mode change event"), guid("{D320289E-8FEA-41E1-86F9-611D831= 51B5F}")] + class LENOVO_GAMEZONE_SMART_FAN_SETTING_EVENT: WMIEvent { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiDataId(1), read, Description("Smart Fan Setting mode change event"= )] uint32 mode; + }; + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("PO= WER CHARGE MODE Change EVENT"), guid("{D320289E-8FEA-41E0-86F9-711D83151B5F= }")] + class LENOVO_GAMEZONE_POWER_CHARGE_MODE_EVENT: WMIEvent { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiDataId(1), read, Description("POWER CHARGE MODE Change EVENT")] ui= nt32 mode; + }; + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Th= ermal Mode Real Mode change event"), guid("{D320289E-8FEA-41E0-86F9-911D831= 51B5F}")] + class LENOVO_GAMEZONE_THERMAL_MODE_EVENT: WMIEvent { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiDataId(1), read, Description("Thermal Mode Real Mode")] uint32 mod= e; + }; diff --git a/Documentation/wmi/devices/lenovo-wmi-other-method.rst b/Docume= ntation/wmi/devices/lenovo-wmi-other-method.rst new file mode 100644 index 000000000000..b48832726311 --- /dev/null +++ b/Documentation/wmi/devices/lenovo-wmi-other-method.rst @@ -0,0 +1,108 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +Lenovo WMI Interface Other Mode Driver (lenovo-wmi-other) +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Introduction +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +Lenovo WMI Other Mode interface is broken up into multiple GUIDs, +The primary Other Mode interface provides advanced power tuning features +such as Package Power Tracking (PPT). It is paired with multiple data block +GUIDs that provide context for the various methods. + + +Other Mode +---------- + +WMI GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Other Mode WMI interface uses the firmware_attributes class to expose +various WMI attributes provided by the interface in the sysfs. This enables +CPU and GPU power limit tuning as well as various other attributes for +devices that fall under the "Gaming Series" of Lenovo devices. Each +attribute exposed by the Other Mode interface has corresponding +capability data blocks which allow the driver to probe details about the +attribute. Each attribute has multiple pages, one for each of the platform +profiles managed by the Gamezone interface. Attributes are exposed in sysfs +under the following path: + +:: +/sys/class/firmware-attributes/lenovo-wmi-other/attributes// + +LENOVO_CAPABILITY_DATA_01 +------------------------- + +WMI GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The LENOVO_CAPABILITY_DATA_01 interface provides information on various +power limits of integrated CPU and GPU components. + +Each attribute has the following properties: + - current_value + - default_value + - display_name + - max_value + - min_value + - scalar_increment + - type + +The following attributes are implemented: + - ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit + - ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking + - ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking + + +WMI interface description +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D + +The WMI interface description can be decoded from the embedded binary MOF = (bmof) +data using the `bmfdec `_ utility: + +:: + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LE= NOVO_OTHER_METHOD class"), guid("{dc2a8805-3a8c-41ba-a6f7-092e0089cd3b}")] + class LENOVO_OTHER_METHOD { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiMethodId(17), Implemented, Description("Get Feature Value ")] void= GetFeatureValue([in] uint32 IDs, [out] uint32 value); + [WmiMethodId(18), Implemented, Description("Set Feature Value ")] void= SetFeatureValue([in] uint32 IDs, [in] uint32 value); + [WmiMethodId(19), Implemented, Description("Get Data By Command ")] vo= id GetDataByCommand([in] uint32 IDs, [in] uint32 Command, [out] uint32 Data= Size, [out, WmiSizeIs("DataSize")] uint32 Data[]); + [WmiMethodId(99), Implemented, Description("Get Data By Package for TA= C")] void GetDataByPackage([in, Max(40)] uint8 Input[], [out] uint32 DataSi= ze, [out, WmiSizeIs("DataSize")] uint8 Data[]); + }; + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LE= NOVO CAPABILITY DATA 00"), guid("{362a3afe-3d96-4665-8530-96dad5bb300e}")] + class LENOVO_CAPABILITY_DATA_00 { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiDataId(1), read, Description(" IDs.")] uint32 IDs; + [WmiDataId(2), read, Description("Capability.")] uint32 Capability; + [WmiDataId(3), read, Description("Capability Default Value.")] uint32 = DefaultValue; + }; + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LE= NOVO CAPABILITY DATA 01"), guid("{7a8f5407-cb67-4d6e-b547-39b3be018154}")] + class LENOVO_CAPABILITY_DATA_01 { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiDataId(1), read, Description(" IDs.")] uint32 IDs; + [WmiDataId(2), read, Description("Capability.")] uint32 Capability; + [WmiDataId(3), read, Description("Default Value.")] uint32 DefaultValu= e; + [WmiDataId(4), read, Description("Step.")] uint32 Step; + [WmiDataId(5), read, Description("Minimum Value.")] uint32 MinValue; + [WmiDataId(6), read, Description("Maximum Value.")] uint32 MaxValue; + }; + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LE= NOVO CAPABILITY DATA 02"), guid("{bbf1f790-6c2f-422b-bc8c-4e7369c7f6ab}")] + class LENOVO_CAPABILITY_DATA_02 { + [key, read] string InstanceName; + [read] boolean Active; + + [WmiDataId(1), read, Description(" IDs.")] uint32 IDs; + [WmiDataId(2), read, Description("Capability.")] uint32 Capability; + [WmiDataId(3), read, Description("Data Size.")] uint32 DataSize; + [WmiDataId(4), read, Description("Default Value"), WmiSizeIs("DataSize= ")] uint8 DefaultValue[]; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 1afd30d00aec..675f4b26426d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13158,6 +13158,13 @@ S: Maintained W: http://legousb.sourceforge.net/ F: drivers/usb/misc/legousbtower.c =20 +LENOVO WMI DRIVERS +M: Derek J. Clark +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst +F: Documentation/wmi/devices/lenovo-wmi-other.rst + LENOVO WMI HOTKEY UTILITIES DRIVER M: Jackie Dong L: platform-driver-x86@vger.kernel.org --=20 2.49.0 From nobody Wed Dec 17 15:33:16 2025 Received: from mail-pj1-f53.google.com (mail-pj1-f53.google.com [209.85.216.53]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id EB8AE219A75; Mon, 17 Mar 2025 06:34:00 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742193242; cv=none; b=RawwVkeaMkKL3KgtrpviLe0Hp8MEagdWV2ieVGhzJ31uE0V5cUDE/asjkr549btu3iDv0C9eCoTa0TJAF3XUWbka0A8uCRIMyFhFjzu6jtbU3dVtYC1RqT0YLxDQ3I+FNRQ5rx3x8bHGaB1i5fyrXgq59km91IOWSwBGsSO7/Es= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742193242; c=relaxed/simple; bh=KgiKPY1et3L31Ex3WiDa3YGM/BuIGMmENVw5pJgedpk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=qcXhTV0iT2yJv0fRvuLbOj4lu8DK8Ai02VaeG9Sb2917N+zdmh35kyyFWUZGUxDymPrZ8mGesGDR8zVchpHaplZI47bwCq8ZPQjUtHgk2X4Dk6TlumtOB4BJbOz5XuWDKgs/CQLbNe3hhTaUmJK9Fzp/I9xvadErZKCp+0lAY6c= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=V4/XMUOC; arc=none smtp.client-ip=209.85.216.53 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="V4/XMUOC" Received: by mail-pj1-f53.google.com with SMTP id 98e67ed59e1d1-2ff797f8f1bso2204633a91.3; Sun, 16 Mar 2025 23:34:00 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1742193240; x=1742798040; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=aAaxM7h2XIfL3QF5kjrKx1uZwv8q2oTQ36iUldCvr4Q=; b=V4/XMUOCi1wz27rhDyfJYIUU59RKWuCZ8U1ai7LHKuJl2JuPv9brx8Pm3CHlvfxqYy DTwn/8HUW+1gU9q/Ix7QphhUXYE4b33GU0hrUx3KEypoPgGLgBClWdexHYrfrPC/Vqg6 wcJO1vhihQePkY8azDHNuooovxnpsxwQRTFAa/pUadPbwnKNO5BnE8jvT/8s+RZG8PTM 9a8C38q7iE8wA8Ri64Cd//yoYy++C88gWqp5vLMoPGaFvyDW09YIr/vRn8g6jHumgqpk lT0ZbWR/nJ5zzlgHxZ86c7Ur7N7YKKZEI68v8IJjMFbTxtxI/kzDcMxH4K9F5j5m+CPh w0bw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1742193240; x=1742798040; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=aAaxM7h2XIfL3QF5kjrKx1uZwv8q2oTQ36iUldCvr4Q=; b=J7HudIG65Xk+HbR0HhEUbyiTTFJEiw66q4Kp/0Yc9pwJeRBz6hPpHAjSZ5J5NrGWDQ UGmEqL5gN7kJY44Mk3gBxvZGLgsXHctjfM38hGPc+1if6NLc1Ic5OV7guKXQ5+72z0Dm 7bZ6ETfuNmRlE3FnIK7kdOcUt942KZ19y0UCB7dgbEavc/diop1TLWK4NHsXz7U59QQe yQtsFxMv9Vl2eHZ3VE1CHFWCxVfCX5R4j+umKuUc43OlIpl/wpimGl5qKVOM0fAOEd4l DvqVmYZ7AAw6H5qfTWoIUmuNAEw4LpjeYb6hpLm+1b3PjFLB2IHkVsx4pMszkxWo1Ctg 10Hg== X-Forwarded-Encrypted: i=1; AJvYcCVGOzwJEF/Rolc3qZxIQjyCHNRpzfSWcFOg4Yjj+DUmOZ+jmK9ZTw6+LrEGddNKD9JH12Aa+sZSU444FRvU/hdYl77npw==@vger.kernel.org, AJvYcCW8JXVn0lPuhptnw66Fp9hl5mFtT32qJUKaRC3j6w19p9PWf9NjZk18QNyGvq//UcviOGNBTr7rY5qmA3PJ@vger.kernel.org, AJvYcCXa+2VEuDcnbF+wcx6fzSxKerfSmA9D0MrG6r6BpGTVVIieMB3hHnxMzE3h6qefAPX0YzHZWUd082g=@vger.kernel.org X-Gm-Message-State: AOJu0Yyy+Qc8ZmxugrbYHpx99LYCy5qd6K/lilP5fvOBo9VRp6mvHVpP uiaL0R5sK466IVf9cmIE9h2xT+GrSkg0RaTYXQTM1sNqv0K6KBG7Ln2Fn6aRg1o= X-Gm-Gg: ASbGnctJ7i8gll/vamJMV+WtO4xX7mh5p6RS+PEaYCPhZL7awvC43FQ3l80t2fySQH7 w4TYsjVs7lIHgioIw3A//bfgS3cC5H3ozWhIF9ynpdBsUHmCYVI2be7cskJadjspCdvw7zunS7z oyThHKi5Ui/pX22JLrOMvGHm6neOMRqpOPEH1JRYOgaKQUE6X0rT8E9SCD44i3m1jPKbPKh5mtb a/1k+0yYFm4L/FXtQUpsG2nPFDskM+vpn45JcvvrhfHhfr0B92WmePnDFDxbwWnQcfaEeXx6NsZ Os5l6zVQ2lOlKEDWzwXjIoYseWU7fJUIfBa1NA5/LVX7oabTjIVhQrlu6c5DBARVcpc01A1y1yI i3eewHoDtnLi+rlXKpZBtEIv3ebpXsM5lrO3fajlNEZk= X-Google-Smtp-Source: AGHT+IG4QBX+tGPO442IKKVzvF/Drz3cUj/i2K8eYZGCO+7fFSUy6vupN3fPK0iAt58bgba1r75iGQ== X-Received: by 2002:a17:90b:2752:b0:2f6:dcc9:38e0 with SMTP id 98e67ed59e1d1-30151aedbdbmr16386297a91.0.1742193239969; Sun, 16 Mar 2025 23:33:59 -0700 (PDT) Received: from localhost.localdomain (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-30153afee51sm5186555a91.28.2025.03.16.23.33.58 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 16 Mar 2025 23:33:59 -0700 (PDT) From: "Derek J. Clark" To: Hans de Goede , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Cc: Armin Wolf , Jonathan Corbet , Mario Limonciello , Luke Jones , Xino Ni , Zhixin Zhang , Mia Shao , Mark Pearson , "Pierre-Loup A . Griffais" , "Cody T . -H . Chiu" , John Martens , "Derek J . Clark" , platform-driver-x86@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v4 1/4] platform/x86: Add Lenovo WMI Events Driver Date: Sun, 16 Mar 2025 23:33:29 -0700 Message-ID: <20250317063337.352966-3-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20250317063337.352966-1-derekjohn.clark@gmail.com> References: <20250317063337.352966-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds lenovo-wmi-events driver. The events driver is designed as a general entrypoint for all Lenovo WMI Events. It acts as a notification chain head that will process event data and pass it on to registered drivers so they can react to the events. Currently only the Gamezone interface Thermal Mode Event GUID is implemented in this driver. It is documented in the Gamezone documentation. Suggested-by: Armin Wolf Signed-off-by: Derek J. Clark Suggested-by: Mario Limonciello --- v4: - Remove the Thermal Mode Event GUID from Gamezone and add this driver. --- MAINTAINERS | 2 + drivers/platform/x86/Kconfig | 4 + drivers/platform/x86/Makefile | 1 + drivers/platform/x86/lenovo-wmi-events.c | 132 +++++++++++++++++++++++ drivers/platform/x86/lenovo-wmi-events.h | 21 ++++ 5 files changed, 160 insertions(+) create mode 100644 drivers/platform/x86/lenovo-wmi-events.c create mode 100644 drivers/platform/x86/lenovo-wmi-events.h diff --git a/MAINTAINERS b/MAINTAINERS index 3a370a18b806..6dde75922aaf 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13164,6 +13164,8 @@ L: platform-driver-x86@vger.kernel.org S: Maintained F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst F: Documentation/wmi/devices/lenovo-wmi-other.rst +F: drivers/platform/x86/lenovo-wmi-events.c +F: drivers/platform/x86/lenovo-wmi-events.h F: drivers/platform/x86/lenovo-wmi-helpers.c F: drivers/platform/x86/lenovo-wmi-helpers.h =20 diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index bece1ba61417..13b8f4ac5dc5 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -459,6 +459,10 @@ config IBM_RTL state =3D 0 (BIOS SMIs on) state =3D 1 (BIOS SMIs off) =20 +config LENOVO_WMI_EVENTS + tristate + depends on ACPI_WMI + config LENOVO_WMI_HELPERS tristate depends on ACPI_WMI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 5a9f4e94f78b..fc039839286a 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -69,6 +69,7 @@ obj-$(CONFIG_THINKPAD_LMI) +=3D think-lmi.o obj-$(CONFIG_YOGABOOK) +=3D lenovo-yogabook.o obj-$(CONFIG_YT2_1380) +=3D lenovo-yoga-tab2-pro-1380-fastcharger.o obj-$(CONFIG_LENOVO_WMI_CAMERA) +=3D lenovo-wmi-camera.o +obj-$(CONFIG_LENOVO_WMI_EVENTS) +=3D lenovo-wmi-events.o obj-$(CONFIG_LENOVO_WMI_HELPERS) +=3D lenovo-wmi-helpers.o =20 # Intel diff --git a/drivers/platform/x86/lenovo-wmi-events.c b/drivers/platform/x8= 6/lenovo-wmi-events.c new file mode 100644 index 000000000000..3ea0face3c0d --- /dev/null +++ b/drivers/platform/x86/lenovo-wmi-events.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Lenovo WMI Events driver. Lenovo WMI interfaces provide various + * hardware triggered events that many drivers need to have propagated. + * This driver provides a uniform entrypoint for these events so that + * any driver that needs to respond to these events can subscribe to a + * notifier chain. + * + * Copyright(C) 2025 Derek J. Clark + */ + +#include +#include +#include +#include +#include "lenovo-wmi-events.h" + +/* Interface GUIDs */ +#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F" + +#define LENOVO_WMI_EVENT_DEVICE(guid, type) \ + .guid_string =3D (guid), .context =3D &(enum lwmi_events_type) \ + { \ + type \ + } + +static BLOCKING_NOTIFIER_HEAD(events_chain_head); + +struct lwmi_events_priv { + struct wmi_device *wdev; + enum lwmi_events_type type; +}; + +/* Notifier Methods */ +int lwmi_events_register_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&events_chain_head, nb); +} +EXPORT_SYMBOL_NS_GPL(lwmi_events_register_notifier, "LENOVO_WMI_EVENTS"); + +int lwmi_events_unregister_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&events_chain_head, nb); +} +EXPORT_SYMBOL_NS_GPL(lwmi_events_unregister_notifier, "LENOVO_WMI_EVENTS"); + +static void devm_lwmi_events_unregister_notifier(void *data) +{ + struct notifier_block *nb =3D data; + + lwmi_events_unregister_notifier(nb); +} + +int devm_lwmi_events_register_notifier(struct device *dev, + struct notifier_block *nb) +{ + int ret; + + ret =3D lwmi_events_register_notifier(nb); + if (ret < 0) + return ret; + + return devm_add_action_or_reset(dev, + devm_lwmi_events_unregister_notifier, nb); +} +EXPORT_SYMBOL_NS_GPL(devm_lwmi_events_register_notifier, "LENOVO_WMI_EVENT= S"); + +/* Driver Methods */ +static void lwmi_events_notify(struct wmi_device *wdev, union acpi_object = *obj) +{ + struct lwmi_events_priv *priv =3D dev_get_drvdata(&wdev->dev); + int sel_prof; + int ret; + + switch (priv->type) { + case THERMAL_MODE_EVENT: + if (obj->type !=3D ACPI_TYPE_INTEGER) + return; + + sel_prof =3D obj->integer.value; + ret =3D blocking_notifier_call_chain(&events_chain_head, + THERMAL_MODE_EVENT, &sel_prof); + if (ret =3D=3D NOTIFY_BAD) + dev_err(&wdev->dev, + "Failed to send notification to call chain for WMI Events\n"); + break; + default: + return; + } +} + +static int lwmi_events_probe(struct wmi_device *wdev, const void *context) +{ + struct lwmi_events_priv *priv; + + priv =3D devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + if (!context) + return -EINVAL; + + priv->wdev =3D wdev; + priv->type =3D *(enum lwmi_events_type *)context; + + dev_set_drvdata(&wdev->dev, priv); + return 0; +} + +static const struct wmi_device_id lwmi_events_id_table[] =3D { + { LENOVO_WMI_EVENT_DEVICE(THERMAL_MODE_EVENT_GUID, + THERMAL_MODE_EVENT) }, + {} +}; + +static struct wmi_driver lwmi_events_driver =3D { + .driver =3D { + .name =3D "lenovo_wmi_events", + .probe_type =3D PROBE_PREFER_ASYNCHRONOUS, + }, + .id_table =3D lwmi_events_id_table, + .probe =3D lwmi_events_probe, + .notify =3D lwmi_events_notify, + .no_singleton =3D true, +}; + +module_wmi_driver(lwmi_events_driver); + +MODULE_DEVICE_TABLE(wmi, lwmi_events_id_table); +MODULE_AUTHOR("Derek J. Clark "); +MODULE_DESCRIPTION("Lenovo WMI Events Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/lenovo-wmi-events.h b/drivers/platform/x8= 6/lenovo-wmi-events.h new file mode 100644 index 000000000000..a3fa934eaa10 --- /dev/null +++ b/drivers/platform/x86/lenovo-wmi-events.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright(C) 2025 Derek J. Clark + */ + +#include +#include + +#ifndef _LENOVO_WMI_EVENTS_H_ +#define _LENOVO_WMI_EVENTS_H_ + +enum lwmi_events_type { + THERMAL_MODE_EVENT =3D 1, +}; + +int lwmi_events_register_notifier(struct notifier_block *nb); +int lwmi_events_unregister_notifier(struct notifier_block *nb); +int devm_lwmi_events_register_notifier(struct device *dev, + struct notifier_block *nb); + +#endif /* !_LENOVO_WMI_EVENTS_H_ */ --=20 2.49.0 From nobody Wed Dec 17 15:33:16 2025 Received: from mail-pj1-f53.google.com (mail-pj1-f53.google.com [209.85.216.53]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2D5E721A424; Mon, 17 Mar 2025 06:34:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742193243; cv=none; b=X88rDBA3ifQJGIvaHKm6ae32AWbQSbPkyxeaKeGkiEunJoSHd9YgJMOX8//7iZ3f1WFM58flNOvgToGlfVTJS3+LymaMfWp1m1aRhKudoVPX/yZBnnOdas1wzJrEGiv6axLOYnznPUvG6Glo59foU8Ue58J94iTlrz/Clfir+U8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742193243; c=relaxed/simple; bh=QtJuuApcIxOnqq0q7ztcDfW6m82MqWlyJ0VShXMUOR0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=UWxBKENJC6yPOQ9GR2dcE7dsahHCUuRyODzMEyUC6kNfbNCbBAeFcBqNhf0CXuRLSb0cHcvp1vxB9f/kHSsg24Knev9ykzQ1lo/TsgHRI2Qe5a4WiOq5LQozyxZXRi9TRlKYkJHCZ0jB68n9mMsU7o68SqsqGp5Tw7zdH7lM4Hg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=Vk2ILcxR; arc=none smtp.client-ip=209.85.216.53 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="Vk2ILcxR" Received: by mail-pj1-f53.google.com with SMTP id 98e67ed59e1d1-2ff6cf448b8so3388774a91.3; Sun, 16 Mar 2025 23:34:01 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1742193241; x=1742798041; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=Hg3kUIYAZxHueF9SBco9xBAfkg5dY1set7XtTII34e0=; b=Vk2ILcxRtga3gfyIzJ/EH4u5yrLsw6ZEjpAZQSxAGU9nauewv/6/iFZjv2mVeTows1 rhV8PHG0inSuinU+a6FD/2HMLZm1WqEDx8vN/1J6w0CT34uASRoaSiB9alvez+Y+seMR ZUjgo0MieqKFZrewf29cun4EjI9ArTTVyxvW6NYqQWDzJ31fytvRoDpST2hUYCQ58Xcg 8oxiN2u7fnaFYnxaDA54x3G91p/BvTlcgip/7x7R8fhlEmbChqZRsJJI3DWsALgbvWSf 3sgY+Uu+z92xIln4JAr6Z/8OBOoVaq+LQxtlOJuSfCykA8yDxmKJ52sUc0hJB8Wc7Dhw 0zkQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1742193241; x=1742798041; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Hg3kUIYAZxHueF9SBco9xBAfkg5dY1set7XtTII34e0=; b=MDJH/QX0jwfw3cv1SroPqVHAJdn2nYRybunmgjwuG+zg9KiUTAyCxHdugTmIvs4EG+ 07ay8wfXfYN7f1dJOiu/Xkg1MXOeXan1rWjmlkTYYD3BwYcimxURuoCHhoVun0blwnki Tfi5NtOhyM951Q2H9rTeSsNJzbPAIKfeREUrAN+LAlDYEpQwJE0kCBPZG9t3KtlHJzba AStLtWbONDUb7x9Bqa95+iLvd2jAbEFein0tDXGZVEpuWqByBW1ndBiudiNBuETdyQ/X 7FGwZC8GOYiQwBvfAhH2whM/+F/DYPvS2drikaKO1S/SROVXASFhOD+GblIhY29VMW+f Mmew== X-Forwarded-Encrypted: i=1; AJvYcCValampmLuyOfAezFdTJKV/qtEVraIxTWAxmeuRjZ9vwwHxgJsORBF1xwNEgalORUrSRrTvbDJtuYePFOjX@vger.kernel.org, AJvYcCViKiGg/QNKk6/UwgtWpavEjZjDDakw0bJ7HgdGyR3vo2qkWtgxrk39zgXkb9OxI7iE3Fi7BaJPuV9bdO9acFgimUx5jQ==@vger.kernel.org, AJvYcCWD3+ufU4T2Xwn+oP4mENT23IqxEveVwkQp+IwZkrbIu9J6tyiM/o0DoYhefLEF2xtbR5x70VydH88=@vger.kernel.org X-Gm-Message-State: AOJu0YxKaT4qKA+jG/Ss9LV6960owl/2DT6mCfu/02naVAft6VMa+MAR kmAKC1JixspFWi7TG/MqgIcS+KfsxzE20bY/xIdwQwj910bcEnsn X-Gm-Gg: ASbGncsKnxw8F1BGXf+Q2At83Dk9JMlTAmB97HJiPl4VRi6yG1FgdqUkhQSW5zbYitK DFYoxWV84opJhtPk/BxzTBMnUt+ikW2LpyczG/8XiLaCAy9/DCVn80uxezdjYDl3tueFuyXu+M+ REj5fjCOw4aXp2UGU2SBhg1OFBfKqWTWYkh4ZL/IS5FL+RheAo/w41AEi+NLd1fI4bVBzymtXxG dPaOCVJW+ibRP6sxP43uWyzYW2Qya3MYerufXRzsum/U4BZhpGddkb6fN9ma+0CUu8VaAaeX1tp Km/yx89ZJWEH5d0Zu2YKa3sBkZunILXRXyMymvW8vlzoEWhZZqNWsbTvtJ4w5uoIt6mhSwzxhrB CxO5O/3KxpNM/llz6/25lZFH9PwHO+H2ZN5DokLR7K8KNJS8NMMa/+Q== X-Google-Smtp-Source: AGHT+IGfx0gol1Jl5nAIqn7x0EsJqxsKJH+ctm2D792o3MdKDujOkZ1RtprV1ca8NchQmdIV5sxKmw== X-Received: by 2002:a17:90a:fc4c:b0:2ee:e945:5355 with SMTP id 98e67ed59e1d1-30151cc9323mr12804556a91.19.1742193241300; Sun, 16 Mar 2025 23:34:01 -0700 (PDT) Received: from localhost.localdomain (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-30153afee51sm5186555a91.28.2025.03.16.23.34.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 16 Mar 2025 23:34:01 -0700 (PDT) From: "Derek J. Clark" To: Hans de Goede , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Cc: Armin Wolf , Jonathan Corbet , Mario Limonciello , Luke Jones , Xino Ni , Zhixin Zhang , Mia Shao , Mark Pearson , "Pierre-Loup A . Griffais" , "Cody T . -H . Chiu" , John Martens , "Derek J . Clark" , platform-driver-x86@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v4 2/4] platform/x86: Add Lenovo Capability Data 01 WMI Driver Date: Sun, 16 Mar 2025 23:33:30 -0700 Message-ID: <20250317063337.352966-4-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20250317063337.352966-1-derekjohn.clark@gmail.com> References: <20250317063337.352966-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds lenovo-wmi-capdata01 driver which provides the LENOVO_CAPABILITY_DATA_01 WMI data block that comes on "Other Mode" enabled hardware. Provides an interface for querying if a given attribute is supported by the hardware, as well as its default_value, max_value, min_value, and step increment. Signed-off-by: Derek J. Clark Suggested-by: Mario Limonciello --- v4: - Make driver data a private struct, remove references from Other Mode driver. - Don't cache data at device initialization. Instead, on component bind, cache the data on a member variable of the Other Mode driver data passed as a void pointer. - Add header file for capdata01 structs. - Add new struct to pass capdata01 array data and array length to Other Mode. v3: - Add as component to lenovo-wmi-other driver. v2: - Use devm_kmalloc to ensure driver can be instanced, remove global reference. - Ensure reverse Christmas tree for all variable declarations. - Remove extra whitespace. - Use guard(mutex) in all mutex instances, global mutex. - Use pr_fmt instead of adding the driver name to each pr_err. - Remove noisy pr_info usage. - Rename capdata_wmi to lenovo_wmi_cd01_priv and cd01_wmi to priv. - Use list to get the lenovo_wmi_cd01_priv instance in lenovo_wmi_capdata01_get as none of the data provided by the macros that will use it can pass a member of the struct for use in container_of. --- MAINTAINERS | 2 + drivers/platform/x86/Kconfig | 4 + drivers/platform/x86/Makefile | 1 + drivers/platform/x86/lenovo-wmi-capdata01.c | 136 ++++++++++++++++++++ drivers/platform/x86/lenovo-wmi-capdata01.h | 29 +++++ 5 files changed, 172 insertions(+) create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.h diff --git a/MAINTAINERS b/MAINTAINERS index 6dde75922aaf..56ead241a053 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13164,6 +13164,8 @@ L: platform-driver-x86@vger.kernel.org S: Maintained F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst F: Documentation/wmi/devices/lenovo-wmi-other.rst +F: drivers/platform/x86/lenovo-wmi-capdata01.c +F: drivers/platform/x86/lenovo-wmi-capdata01.h F: drivers/platform/x86/lenovo-wmi-events.c F: drivers/platform/x86/lenovo-wmi-events.h F: drivers/platform/x86/lenovo-wmi-helpers.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 13b8f4ac5dc5..64663667f0cb 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -467,6 +467,10 @@ config LENOVO_WMI_HELPERS tristate depends on ACPI_WMI =20 +config LENOVO_WMI_DATA01 + tristate + depends on ACPI_WMI + config IDEAPAD_LAPTOP tristate "Lenovo IdeaPad Laptop Extras" depends on ACPI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index fc039839286a..7a35c77221b7 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -69,6 +69,7 @@ obj-$(CONFIG_THINKPAD_LMI) +=3D think-lmi.o obj-$(CONFIG_YOGABOOK) +=3D lenovo-yogabook.o obj-$(CONFIG_YT2_1380) +=3D lenovo-yoga-tab2-pro-1380-fastcharger.o obj-$(CONFIG_LENOVO_WMI_CAMERA) +=3D lenovo-wmi-camera.o +obj-$(CONFIG_LENOVO_WMI_DATA01) +=3D lenovo-wmi-capdata01.o obj-$(CONFIG_LENOVO_WMI_EVENTS) +=3D lenovo-wmi-events.o obj-$(CONFIG_LENOVO_WMI_HELPERS) +=3D lenovo-wmi-helpers.o =20 diff --git a/drivers/platform/x86/lenovo-wmi-capdata01.c b/drivers/platform= /x86/lenovo-wmi-capdata01.c new file mode 100644 index 000000000000..b6876611ffd9 --- /dev/null +++ b/drivers/platform/x86/lenovo-wmi-capdata01.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * LENOVO_CAPABILITY_DATA_01 WMI data block driver. This interface provides + * information on tunable attributes used by the "Other Mode" WMI interfac= e, + * including if it is supported by the hardware, the default_value, max_va= lue, + * min_value, and step increment. + * + * Copyright(C) 2025 Derek J. Clark + */ + +#include +#include +#include +#include +#include +#include +#include +#include "lenovo-wmi-capdata01.h" + +/* Interface GUIDs */ +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE0181= 54" + +struct lwmi_cd01_priv { + struct wmi_device *wdev; +}; + +/* + * lenovo_cd01_component_bind() - On master bind, caches all capability da= ta on + * the master device. + * @cd01_dev: Pointer to the capability data 01 parent device. + * @om_dev: Pointer to the other mode parent device. + * @data: capdata01_list object pointer to return the capability data with. + * + * Returns: 0, or an error. + */ +static int lenovo_cd01_component_bind(struct device *cd01_dev, + struct device *om_dev, void *data) +{ + struct lwmi_cd01_priv *priv =3D dev_get_drvdata(cd01_dev); + int count, idx; + + if (!priv) + return -ENODEV; + + count =3D wmidev_instance_count(priv->wdev); + + if (count =3D=3D 0) + return -EINVAL; + + ((struct cd01_list *)data)->count =3D count; + ((struct cd01_list *)data)->data =3D devm_kmalloc_array(om_dev, count, + sizeof(struct capdata01 *), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + for (idx =3D 0; idx < count; idx++) { + union acpi_object *ret_obj __free(kfree) =3D NULL; + + ret_obj =3D wmidev_block_query(priv->wdev, idx); + if (!ret_obj) { + ((struct cd01_list *)data)->data[idx] =3D NULL; + continue; + } + if (ret_obj->type !=3D ACPI_TYPE_BUFFER) { + ((struct cd01_list *)data)->data[idx] =3D NULL; + continue; + } + + if (ret_obj->buffer.length !=3D sizeof(struct capdata01)) { + ((struct cd01_list *)data)->data[idx] =3D NULL; + continue; + } + + ((struct cd01_list *)data)->data[idx] =3D + devm_kmemdup(om_dev, ret_obj->buffer.pointer, + ret_obj->buffer.length, GFP_KERNEL); + } + return 0; +} + +static const struct component_ops lenovo_cd01_component_ops =3D { + .bind =3D lenovo_cd01_component_bind, +}; + +static int lwmi_cd01_probe(struct wmi_device *wdev, const void *context) + +{ + struct lwmi_cd01_priv *priv; + int ret; + + priv =3D devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->wdev =3D wdev; + dev_set_drvdata(&wdev->dev, priv); + + ret =3D component_add(&wdev->dev, &lenovo_cd01_component_ops); + + return ret; +} + +static void lwmi_cd01_remove(struct wmi_device *wdev) +{ + component_del(&wdev->dev, &lenovo_cd01_component_ops); +} + +static const struct wmi_device_id lwmi_cd01_id_table[] =3D { + { LENOVO_CAPABILITY_DATA_01_GUID, NULL }, + {} +}; + +static struct wmi_driver lwmi_cd01_driver =3D { + .driver =3D { + .name =3D "lenovo_wmi_cd01", + .probe_type =3D PROBE_PREFER_ASYNCHRONOUS, + }, + .id_table =3D lwmi_cd01_id_table, + .probe =3D lwmi_cd01_probe, + .remove =3D lwmi_cd01_remove, + .no_singleton =3D true, +}; + +int lwmi_cd01_match(struct device *dev, void *data) +{ + return dev->driver =3D=3D &lwmi_cd01_driver.driver; +} +EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD01"); + +module_wmi_driver(lwmi_cd01_driver); + +MODULE_DEVICE_TABLE(wmi, lwmi_cd01_id_table); +MODULE_AUTHOR("Derek J. Clark "); +MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/lenovo-wmi-capdata01.h b/drivers/platform= /x86/lenovo-wmi-capdata01.h new file mode 100644 index 000000000000..c7067a8d0398 --- /dev/null +++ b/drivers/platform/x86/lenovo-wmi-capdata01.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright(C) 2025 Derek J. Clark + * + */ + +#ifndef _LENOVO_WMI_CAPDATA01_H_ +#define _LENOVO_WMI_CAPDATA01_H_ + +#include +#include + +struct capdata01 { + u32 id; + u32 supported; + u32 default_value; + u32 step; + u32 min_value; + u32 max_value; +}; + +struct cd01_list { + struct capdata01 **data; + int count; +}; + +int lwmi_cd01_match(struct device *dev, void *data); + +#endif /* !_LENOVO_WMI_CAPDATA01_H_ */ --=20 2.49.0 From nobody Wed Dec 17 15:33:16 2025 Received: from mail-pj1-f50.google.com (mail-pj1-f50.google.com [209.85.216.50]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9D8E221A459; Mon, 17 Mar 2025 06:34:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.50 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742193245; cv=none; b=Z/HXjqHdIgASZzrq3He8/zB0uCy9G6GSRXaHdsUTqVzIZDduhwD0QYjtUiihtp+eMmFNwi2I9WCRQVPRJ+C6vCzSzdh5rfbzT32xzraB/sJSIwJZBSksNEFwwIvcfhPb60+dclFlDrqFxZ+BW4sNfehIYqRGvz7cgToIW2aZCIg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742193245; c=relaxed/simple; bh=y7uS2LgTYlS/plbwPtBW1UuH96DFrH2j13V/AvOhOlU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=pN9OtKGeDrvQc+Th5Em575otzbMS6TlAbVHNj7LJRj3tFEG7Wq0BFJcrt1gvIxkpVAwNOY1aB3LbsvdU1gsPv9AilGwh6AZ8pIrIHozFgbK2ezm9W611Vg0UZBq/QjRklKXUojhHAE/UcpMuP7IuYEOMP1vy223JC64rp+OnGiQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=M94XWJF5; arc=none smtp.client-ip=209.85.216.50 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="M94XWJF5" Received: by mail-pj1-f50.google.com with SMTP id 98e67ed59e1d1-30185d00446so637034a91.0; Sun, 16 Mar 2025 23:34:03 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1742193243; x=1742798043; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=m2x7FZV4OyGa9hp/8mTMSCLqQ8CtXKxRN5A1v+VR5Os=; b=M94XWJF53US9NZLA50UIqXFWbXF8XAU3fEDYF4Kd+nkjRXo3jM+KjGywapcjcJnnTR yg268WvXVWC0Yfj6NKCI0TGcDU+2CSPe5QM4npXK6S27lv7TqYIrUArtJz3pBW9gwqTA CBARgB0axc9CHDiP4JtquAIpgir0K38OhKkMN5UM6G//SYGSyYUpNG9DDbHjpprck6P/ WZaegaa2kB8XLAUuVX1IwDtS+jLkOeHChidJy9zIsLv/Qr7q03Y6lJoReE8q1Xaw7HuD qFdZMQ4EjO9iti0EhaDRMfkBKqIJMJ88/HBwCrW+aDPZRkSZQTTluua9Btg9r/eXYx5F lPIQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1742193243; x=1742798043; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=m2x7FZV4OyGa9hp/8mTMSCLqQ8CtXKxRN5A1v+VR5Os=; b=r8uxFSrOYcf31sJX3/OeOlVLnGTLgQEaSYCCSAPkx4ZtanpipV4bq/SkdAmpuPGPNW P7aF/1UcDUtYudpaw2PKzWdfsbfgpwsk4CmsXiZ1txAk0QPGs5/TnRxbwq51hBDbLc79 Q+zwLcJoOJA6wGKi3cq0OHtkEkPENrjgzRLQ12UUJqJ16bn8002XspBiKw9c1dCU2ZgT +Dey3A8oP3qBrI+C0GIwAOVaFr8NzOO0/lsBih/iqoBiLvxwDoUdi2kC7Nea1TBUm93c hPynipaJREuHVI/I90hQL+Gz5EdC6eqi/RKIyqtTvsCFL+umwJ4jdUUpMNLHNHPGGLwU rjYQ== X-Forwarded-Encrypted: i=1; AJvYcCVW3mfxus9lAqkd7fzJEGKIursJhPRZiiAPgRN6IglZoXHp/PdUzBXwoYzfDw597VfU1qlu01fHTLs=@vger.kernel.org, AJvYcCW2UIGV1pEYlDTjpBvb6lW1yzUI/u/gx7nosBEt0tuhdjuZ0615AtKTdM+fyXnDXDHkBP9YTKOS5a5w4MkO@vger.kernel.org, AJvYcCWPIkyzHCAJID0BRcLJAiMVEG6xR8j62dwHHjJyImqxRDzrpTBUq6BR6CHLw8X2EK8x8+ZslrLp3QFdd0ZfiqhrJsn0ZA==@vger.kernel.org X-Gm-Message-State: AOJu0YwhToNmz1x2AS0cL3+Q5yHAD2zZB5eTrk4HraFiFwkY6osUpgFw nEjRPWg6KCE9iSQHC6LotxUVrQOldhXNdPWAAUIgEXHvR79L3OUK X-Gm-Gg: ASbGncsWT1jk93YJrCwh4yeBQb5huqpwRICouGt1ENNxPZWPDRm+gxeBB34+R2ON/pt q/72by7yTV8WFX3bj4WeFsg5Uh3oRW78lKnGVQCEvSDhV4/BWWxWG1CYyEWwlS55Rd0L70Kq44I i17WdewVQqz1UQpUkbW2OD4K7SYc+ln1jiUKycy83cKjnuwuyEYWpn+pkQi+F3X3zU3wkvp5VZU ODTcPr8Z4IW373KkphYBrgJjh2ALNk2G5satSGnIBcgNacO0Kd110KLmA8X74CbJQqlhx1QvXHf 1C/Q9VGiRpiYQL1QsHmkWQh8EEkAiqvFFBUFYLKmE5oUOVD962BXyEPuL3q/z+9O+SCsrwYPtGr fIfrKgFuaccS6zjgJhBxW6xhS9+HI1rl1jTrVVoM8TV8= X-Google-Smtp-Source: AGHT+IHRTLgYsplRHi/sI4VHcwUBuB/vVTzS/3kjmXaRqew/IxQ4hA9HqAFoia08Eym4934LTyoCYw== X-Received: by 2002:a17:90b:3804:b0:2fa:4926:d18d with SMTP id 98e67ed59e1d1-30135f4cf2bmr20023358a91.13.1742193242630; Sun, 16 Mar 2025 23:34:02 -0700 (PDT) Received: from localhost.localdomain (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-30153afee51sm5186555a91.28.2025.03.16.23.34.01 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 16 Mar 2025 23:34:02 -0700 (PDT) From: "Derek J. Clark" To: Hans de Goede , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Cc: Armin Wolf , Jonathan Corbet , Mario Limonciello , Luke Jones , Xino Ni , Zhixin Zhang , Mia Shao , Mark Pearson , "Pierre-Loup A . Griffais" , "Cody T . -H . Chiu" , John Martens , "Derek J . Clark" , platform-driver-x86@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v4 2/6] platform/x86: Add lenovo-wmi-helpers Date: Sun, 16 Mar 2025 23:33:31 -0700 Message-ID: <20250317063337.352966-5-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20250317063337.352966-1-derekjohn.clark@gmail.com> References: <20250317063337.352966-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds documentation for all new lenovo-wmi drivers. Signed-off-by: Derek J. Clark Suggested-by: Mario Limonciello --- v4: - Changed namespace to LENOVO_WMI_HELPERS from LENOVO_WMI. - Changed filenames to lenovo-wmi-helpers from lenovo-wmi. - Removed structs and functions implemented by other drivers. --- MAINTAINERS | 2 + drivers/platform/x86/Kconfig | 4 ++ drivers/platform/x86/Makefile | 1 + drivers/platform/x86/lenovo-wmi-helpers.c | 64 +++++++++++++++++++++++ drivers/platform/x86/lenovo-wmi-helpers.h | 24 +++++++++ 5 files changed, 95 insertions(+) create mode 100644 drivers/platform/x86/lenovo-wmi-helpers.c create mode 100644 drivers/platform/x86/lenovo-wmi-helpers.h diff --git a/MAINTAINERS b/MAINTAINERS index 675f4b26426d..3a370a18b806 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13164,6 +13164,8 @@ L: platform-driver-x86@vger.kernel.org S: Maintained F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst F: Documentation/wmi/devices/lenovo-wmi-other.rst +F: drivers/platform/x86/lenovo-wmi-helpers.c +F: drivers/platform/x86/lenovo-wmi-helpers.h =20 LENOVO WMI HOTKEY UTILITIES DRIVER M: Jackie Dong diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 43407e76476b..bece1ba61417 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -459,6 +459,10 @@ config IBM_RTL state =3D 0 (BIOS SMIs on) state =3D 1 (BIOS SMIs off) =20 +config LENOVO_WMI_HELPERS + tristate + depends on ACPI_WMI + config IDEAPAD_LAPTOP tristate "Lenovo IdeaPad Laptop Extras" depends on ACPI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 650dfbebb6c8..5a9f4e94f78b 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -69,6 +69,7 @@ obj-$(CONFIG_THINKPAD_LMI) +=3D think-lmi.o obj-$(CONFIG_YOGABOOK) +=3D lenovo-yogabook.o obj-$(CONFIG_YT2_1380) +=3D lenovo-yoga-tab2-pro-1380-fastcharger.o obj-$(CONFIG_LENOVO_WMI_CAMERA) +=3D lenovo-wmi-camera.o +obj-$(CONFIG_LENOVO_WMI_HELPERS) +=3D lenovo-wmi-helpers.o =20 # Intel obj-y +=3D intel/ diff --git a/drivers/platform/x86/lenovo-wmi-helpers.c b/drivers/platform/x= 86/lenovo-wmi-helpers.c new file mode 100644 index 000000000000..36d553502223 --- /dev/null +++ b/drivers/platform/x86/lenovo-wmi-helpers.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Lenovo Legion WMI helpers driver. The Lenovo Legion WMI interface is + * broken up into multiple GUID interfaces that require cross-references + * between GUID's for some functionality. The "Custom Method" interface is= a + * legacy interface for managing and displaying CPU & GPU power and hwmon + * settings and readings. The "Other Mode" interface is a modern interface + * that replaces or extends the "Custom Method" interface methods. The + * "Gamezone" interface adds advanced features such as fan profiles and + * overclocking. The "Lighting" interface adds control of various status + * lights related to different hardware components. Each of these drivers + * uses a common procedure to get data fro the WMI interface, enumerated h= ere. + * + * Copyright(C) 2025 Derek J. Clark + * + */ + +#include +#include "lenovo-wmi-helpers.h" + +/* + * lwmi_dev_evaluate_method() - Helper function to call wmidev_evaluate_me= thod + * for Lenovo WMI device method calls that return an ACPI integer. + * @wdev: Pointer to the WMI device to be called. + * @instance: Instance of the called method. + * @method_id: WMI Method ID for the method to be called. + * @buf: Buffer of all arguments for the given method_id. + * @size: Length of the buffer. + * @retval: Pointer for the return value to be assigned. + * + * Returns: 0, or an error. + */ +int lwmi_dev_evaluate_method(struct wmi_device *wdev, u8 instance, + u32 method_id, unsigned char *buf, size_t size, + u32 *retval) +{ + struct acpi_buffer input =3D { size, buf }; + struct acpi_buffer output =3D { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *ret_obj __free(kfree) =3D NULL; + acpi_status status; + + status =3D wmidev_evaluate_method(wdev, instance, method_id, &input, + &output); + + if (ACPI_FAILURE(status)) + return -EIO; + + if (retval) { + ret_obj =3D output.pointer; + if (!ret_obj) + return -ENODATA; + + if (ret_obj->type !=3D ACPI_TYPE_INTEGER) + return -ENXIO; + + *retval =3D (u32)ret_obj->integer.value; + } + return 0; +}; +EXPORT_SYMBOL_NS_GPL(lwmi_dev_evaluate_method, "LENOVO_WMI_HELPERS"); + +MODULE_AUTHOR("Derek J. Clark "); +MODULE_DESCRIPTION("Lenovo WMI Helpers Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/lenovo-wmi-helpers.h b/drivers/platform/x= 86/lenovo-wmi-helpers.h new file mode 100644 index 000000000000..7e0d7870790e --- /dev/null +++ b/drivers/platform/x86/lenovo-wmi-helpers.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright(C) 2025 Derek J. Clark + * + */ +#include +#include + +#ifndef _LENOVO_WMI_HELPERS_H_ +#define _LENOVO_WMI_HELPERS_H_ + +#include +#include + +struct wmi_method_args_32 { + u32 arg0; + u32 arg1; +}; + +int lwmi_dev_evaluate_method(struct wmi_device *wdev, u8 instance, + u32 method_id, unsigned char *buf, size_t size, + u32 *retval); + +#endif /* !_LENOVO_WMI_HELPERS_H_ */ --=20 2.49.0 From nobody Wed Dec 17 15:33:16 2025 Received: from mail-pj1-f52.google.com (mail-pj1-f52.google.com [209.85.216.52]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E009921ABCB; Mon, 17 Mar 2025 06:34:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.52 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742193247; cv=none; b=Suhxex6JByTHqregnXDwQQoSJlhpeAAFjkzj9XJ/TnlI4CQDnicjC66G/XXF4613GCI3ziTlFX0ZylCv/EprmhwOYzA7jjYgoFHSf5get9zW0+cgXiZvsf0EONUBnJcVT0sjMj+tH/i02T3btTsWjK+1sgSLLhm53sPk1jI6Rro= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742193247; c=relaxed/simple; bh=KgiKPY1et3L31Ex3WiDa3YGM/BuIGMmENVw5pJgedpk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=PHtp54nQECS7BDu4fojJ3txlTWnhzRvO8TnZCHIADkz8hXqVTC+hKqkyPc/6F/I236B9zOkQVA0W1DKTdf0bwq4bz7waphuU/iP+iTWiM+Y9a+KDyA1miZWrI2gtQouQFGXTZHfUZH4knYJRJE8XbRDEGPlQZ96FCaPX14JHV2g= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=d+fzXS1V; arc=none smtp.client-ip=209.85.216.52 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="d+fzXS1V" Received: by mail-pj1-f52.google.com with SMTP id 98e67ed59e1d1-3014cb646ecso1837330a91.1; Sun, 16 Mar 2025 23:34:05 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1742193245; x=1742798045; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=aAaxM7h2XIfL3QF5kjrKx1uZwv8q2oTQ36iUldCvr4Q=; b=d+fzXS1VTjvXXeL/Jy+EqRf8gWZq2S9hMZuSS6wAW9VWfpRDKYWHg+rhR7fCRyHN1E 2A6rZmggO2JKuYDLwG/IAYhRK7E41Qg3bRRfiKUowJbtKd6p5zWFfvKhpJhOwi+w+cBp 7YEF9qEbn66LnWjAREzJeCAOowELrN/zpzAHcES3YYnG00Yr9N4eA843ksxBzt3WoBuE 8fCyb9WZpKk1zdI8B9+LAd+h5PYcRnLT4bAoNtxjIkMaZH+G8myaVrrkFyF/30ikJYzS MawKPGNi+ZNmePYdsH+pdJaKgqqqSILyM7x1JN8lQiRoj4WIcEoris5LRvcDAXZ0ukzl +Pxg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1742193245; x=1742798045; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=aAaxM7h2XIfL3QF5kjrKx1uZwv8q2oTQ36iUldCvr4Q=; b=VVUf8tl/cWBQcLAdsi5q08Q/BxHrlV2tXwPgUZDHIQtiMofZKzd9XNvbTdKObkHj36 lLCH/Z9O+USjkQyTjw6cFGXB0rwlabo9tH/dAXBMqJslHkezKjp4CkYoxH1Mb3oWeDc8 9pcotSwmmyju1IVwzneU1BcW8I0pa+tPGtVZTbbeOdcgRCfwF/pit0lhFKRJHjlJBcnd 96+k5JwOKfY0UYiOlpEqA32ukrnGmjCxbBwmwjZ2IQGVc4K0GWfivANWCdZYjKPJQAK3 QJCUW1Ye7afvaMn7yTpAQjwvM7HCnPLJVYyfbFpQtPlU541kzH5fFPqRXy/ACmL05vhY 36Eg== X-Forwarded-Encrypted: i=1; AJvYcCV9l3YN0uvHjQBqDDdebAWJUky+8BUaOgjpuRXe4LAFxT6pO5SW3AInw0hkphjp37J8BBbgrezypvU8Z1z+@vger.kernel.org, AJvYcCVOVFAbz2JX6eT8CtBWDba+Z3wMMi1L+w7bb6v/rpzCFYNY/neiERW3Qjz2QJcE92t+g6wMcp6S5Uc=@vger.kernel.org, AJvYcCWGR3ygdwit7k6FAwWmAKRfJT3v+7l+9r3i1u6NBmTSLck8sO1SlIol7CkvXKZZLlClA7xkSKnmlk695pedfIohOVOjpg==@vger.kernel.org X-Gm-Message-State: AOJu0YxcmjO3sbcMuzBLIpjqASpPDjrSFzpKvTRqB6ZDEqFmTy/Iyovj /eowoXRnGi3d7bE34Iifg2eUq2Tb1fjI3j71S64X8HbmX23kq22n X-Gm-Gg: ASbGncvxiIK4xQhguFW8QHZ5Yp2jSE4GfHI0rgRpeqimhkgepK8VZcBg0qYK7eK+8wd 2IdWvbKx3f13QKvxtb+jEp5WMIkK+ZLRVuJ/nrgDv7vREx2LdSOwt/L70BIs9YK5Nit6WwNpHkn NNi/36ybykAAA0zNChUcwL8AYaUbWuldT2U6GGfY2CUvyB+2oD8Jd+5H5UE/vTffq/bPnR9BRYP Sjr3PjqimnIPhj7PsvIVffguMMbacg3MDk1Wjjw2o+63WH7835X7QE3CqFH5Uev4fdQxVmwhImY HnZo1BwYvNreCWVSVBmGmwUwVs53wAw5oD37J5JtYlUMFodVWjiNBRwuuWIY2vHj1L9q7WpYVH7 8LCEjCG0cIVs+jmsIUpdzxJbbHAKK4wm4rMJ9V29GT2M= X-Google-Smtp-Source: AGHT+IG+ClIaPaW5PWE1pfCmAjhBZ/BTfDyb9OWmoHZxgJVGHvtjD/DT/t+2PzWDPCI272fPs3GYxw== X-Received: by 2002:a17:90b:3d50:b0:2fa:13d9:39c with SMTP id 98e67ed59e1d1-30151ce324fmr16596707a91.14.1742193245287; Sun, 16 Mar 2025 23:34:05 -0700 (PDT) Received: from localhost.localdomain (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-30153afee51sm5186555a91.28.2025.03.16.23.34.04 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 16 Mar 2025 23:34:05 -0700 (PDT) From: "Derek J. Clark" To: Hans de Goede , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Cc: Armin Wolf , Jonathan Corbet , Mario Limonciello , Luke Jones , Xino Ni , Zhixin Zhang , Mia Shao , Mark Pearson , "Pierre-Loup A . Griffais" , "Cody T . -H . Chiu" , John Martens , "Derek J . Clark" , platform-driver-x86@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v4 3/6] platform/x86: Add Lenovo WMI Events Driver Date: Sun, 16 Mar 2025 23:33:33 -0700 Message-ID: <20250317063337.352966-7-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20250317063337.352966-1-derekjohn.clark@gmail.com> References: <20250317063337.352966-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds lenovo-wmi-events driver. The events driver is designed as a general entrypoint for all Lenovo WMI Events. It acts as a notification chain head that will process event data and pass it on to registered drivers so they can react to the events. Currently only the Gamezone interface Thermal Mode Event GUID is implemented in this driver. It is documented in the Gamezone documentation. Suggested-by: Armin Wolf Signed-off-by: Derek J. Clark Suggested-by: Mario Limonciello --- v4: - Remove the Thermal Mode Event GUID from Gamezone and add this driver. --- MAINTAINERS | 2 + drivers/platform/x86/Kconfig | 4 + drivers/platform/x86/Makefile | 1 + drivers/platform/x86/lenovo-wmi-events.c | 132 +++++++++++++++++++++++ drivers/platform/x86/lenovo-wmi-events.h | 21 ++++ 5 files changed, 160 insertions(+) create mode 100644 drivers/platform/x86/lenovo-wmi-events.c create mode 100644 drivers/platform/x86/lenovo-wmi-events.h diff --git a/MAINTAINERS b/MAINTAINERS index 3a370a18b806..6dde75922aaf 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13164,6 +13164,8 @@ L: platform-driver-x86@vger.kernel.org S: Maintained F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst F: Documentation/wmi/devices/lenovo-wmi-other.rst +F: drivers/platform/x86/lenovo-wmi-events.c +F: drivers/platform/x86/lenovo-wmi-events.h F: drivers/platform/x86/lenovo-wmi-helpers.c F: drivers/platform/x86/lenovo-wmi-helpers.h =20 diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index bece1ba61417..13b8f4ac5dc5 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -459,6 +459,10 @@ config IBM_RTL state =3D 0 (BIOS SMIs on) state =3D 1 (BIOS SMIs off) =20 +config LENOVO_WMI_EVENTS + tristate + depends on ACPI_WMI + config LENOVO_WMI_HELPERS tristate depends on ACPI_WMI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 5a9f4e94f78b..fc039839286a 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -69,6 +69,7 @@ obj-$(CONFIG_THINKPAD_LMI) +=3D think-lmi.o obj-$(CONFIG_YOGABOOK) +=3D lenovo-yogabook.o obj-$(CONFIG_YT2_1380) +=3D lenovo-yoga-tab2-pro-1380-fastcharger.o obj-$(CONFIG_LENOVO_WMI_CAMERA) +=3D lenovo-wmi-camera.o +obj-$(CONFIG_LENOVO_WMI_EVENTS) +=3D lenovo-wmi-events.o obj-$(CONFIG_LENOVO_WMI_HELPERS) +=3D lenovo-wmi-helpers.o =20 # Intel diff --git a/drivers/platform/x86/lenovo-wmi-events.c b/drivers/platform/x8= 6/lenovo-wmi-events.c new file mode 100644 index 000000000000..3ea0face3c0d --- /dev/null +++ b/drivers/platform/x86/lenovo-wmi-events.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Lenovo WMI Events driver. Lenovo WMI interfaces provide various + * hardware triggered events that many drivers need to have propagated. + * This driver provides a uniform entrypoint for these events so that + * any driver that needs to respond to these events can subscribe to a + * notifier chain. + * + * Copyright(C) 2025 Derek J. Clark + */ + +#include +#include +#include +#include +#include "lenovo-wmi-events.h" + +/* Interface GUIDs */ +#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F" + +#define LENOVO_WMI_EVENT_DEVICE(guid, type) \ + .guid_string =3D (guid), .context =3D &(enum lwmi_events_type) \ + { \ + type \ + } + +static BLOCKING_NOTIFIER_HEAD(events_chain_head); + +struct lwmi_events_priv { + struct wmi_device *wdev; + enum lwmi_events_type type; +}; + +/* Notifier Methods */ +int lwmi_events_register_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&events_chain_head, nb); +} +EXPORT_SYMBOL_NS_GPL(lwmi_events_register_notifier, "LENOVO_WMI_EVENTS"); + +int lwmi_events_unregister_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&events_chain_head, nb); +} +EXPORT_SYMBOL_NS_GPL(lwmi_events_unregister_notifier, "LENOVO_WMI_EVENTS"); + +static void devm_lwmi_events_unregister_notifier(void *data) +{ + struct notifier_block *nb =3D data; + + lwmi_events_unregister_notifier(nb); +} + +int devm_lwmi_events_register_notifier(struct device *dev, + struct notifier_block *nb) +{ + int ret; + + ret =3D lwmi_events_register_notifier(nb); + if (ret < 0) + return ret; + + return devm_add_action_or_reset(dev, + devm_lwmi_events_unregister_notifier, nb); +} +EXPORT_SYMBOL_NS_GPL(devm_lwmi_events_register_notifier, "LENOVO_WMI_EVENT= S"); + +/* Driver Methods */ +static void lwmi_events_notify(struct wmi_device *wdev, union acpi_object = *obj) +{ + struct lwmi_events_priv *priv =3D dev_get_drvdata(&wdev->dev); + int sel_prof; + int ret; + + switch (priv->type) { + case THERMAL_MODE_EVENT: + if (obj->type !=3D ACPI_TYPE_INTEGER) + return; + + sel_prof =3D obj->integer.value; + ret =3D blocking_notifier_call_chain(&events_chain_head, + THERMAL_MODE_EVENT, &sel_prof); + if (ret =3D=3D NOTIFY_BAD) + dev_err(&wdev->dev, + "Failed to send notification to call chain for WMI Events\n"); + break; + default: + return; + } +} + +static int lwmi_events_probe(struct wmi_device *wdev, const void *context) +{ + struct lwmi_events_priv *priv; + + priv =3D devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + if (!context) + return -EINVAL; + + priv->wdev =3D wdev; + priv->type =3D *(enum lwmi_events_type *)context; + + dev_set_drvdata(&wdev->dev, priv); + return 0; +} + +static const struct wmi_device_id lwmi_events_id_table[] =3D { + { LENOVO_WMI_EVENT_DEVICE(THERMAL_MODE_EVENT_GUID, + THERMAL_MODE_EVENT) }, + {} +}; + +static struct wmi_driver lwmi_events_driver =3D { + .driver =3D { + .name =3D "lenovo_wmi_events", + .probe_type =3D PROBE_PREFER_ASYNCHRONOUS, + }, + .id_table =3D lwmi_events_id_table, + .probe =3D lwmi_events_probe, + .notify =3D lwmi_events_notify, + .no_singleton =3D true, +}; + +module_wmi_driver(lwmi_events_driver); + +MODULE_DEVICE_TABLE(wmi, lwmi_events_id_table); +MODULE_AUTHOR("Derek J. Clark "); +MODULE_DESCRIPTION("Lenovo WMI Events Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/lenovo-wmi-events.h b/drivers/platform/x8= 6/lenovo-wmi-events.h new file mode 100644 index 000000000000..a3fa934eaa10 --- /dev/null +++ b/drivers/platform/x86/lenovo-wmi-events.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright(C) 2025 Derek J. Clark + */ + +#include +#include + +#ifndef _LENOVO_WMI_EVENTS_H_ +#define _LENOVO_WMI_EVENTS_H_ + +enum lwmi_events_type { + THERMAL_MODE_EVENT =3D 1, +}; + +int lwmi_events_register_notifier(struct notifier_block *nb); +int lwmi_events_unregister_notifier(struct notifier_block *nb); +int devm_lwmi_events_register_notifier(struct device *dev, + struct notifier_block *nb); + +#endif /* !_LENOVO_WMI_EVENTS_H_ */ --=20 2.49.0 From nobody Wed Dec 17 15:33:16 2025 Received: from mail-pj1-f46.google.com (mail-pj1-f46.google.com [209.85.216.46]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C944021ABB0; Mon, 17 Mar 2025 06:34:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.46 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742193247; cv=none; b=RwgbpFrY94zvmTOjXvQ+5YIExcp22g580D1JeSdYHUs/dVbYEpbSvEPNNXM5u8DLdBlhfjGR8MOU4uwUjAs+dvMSFglrx+y1a8cNuzXPizjEfkfVzDIT/FSpVLSAdR8ogWvEPLqh3a1cJR+2f+mrGfAgcYy9pp7cQISH+KOwsl0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742193247; c=relaxed/simple; bh=0UVtzZ1P789Jk+XBpcwEL5d/OtfLWEuL86ZyNHBBe7w=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=aG6y06EGa3TtOk0Me/BBSX0eQvpw11eDbIhRHDmWSlFAPd271bQNloWnwRJ2pYGigq67wsaN3+FIo7wRnq2MB2GUW+VJDM11mY2E0yVUIP1zSmn64yeDNrf5R9TgijM9hiBsha1Kparkfb4h+9fuwWAc6cbstM+28c9ydiTvALI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=X2cRVm4u; arc=none smtp.client-ip=209.85.216.46 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="X2cRVm4u" Received: by mail-pj1-f46.google.com with SMTP id 98e67ed59e1d1-2ff85fec403so2480164a91.1; Sun, 16 Mar 2025 23:34:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1742193244; x=1742798044; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=Wpa2qWthIuAYVa7Vcui8zzT6mcuzIqqT3GKlMttqhmQ=; b=X2cRVm4uFYNh78cdz3Ro2s34kCZkOwSqW7NRdtgr1SdckqKawHvOmTp0cMPohMyBl9 KUxkS6ux/Npth7EVTTYEZPY05mH6xlCB2rOPe6013hdNuchqPUULW+G299B1d/EBOyA3 PKaBvtqR9pVKkIyaIq2OEAuJ9KG0xFNPH/0Fy47Dyuuq9rJmwqLzw51ho7cQKL4xi3ej 26MyRM2B9+pcAFyB/WcSR5Vic2uyCIv4tlTZiQZ17pUVnNdzmjN5Mnhl8j0X9Prvoqn7 El/2y5PJLSS+rgvZqYJ3uR3MVrYwbuvWZO4G0ETV6gO1OLyonAiP+cpsPFPhN+ILZzA3 xj1g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1742193244; x=1742798044; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Wpa2qWthIuAYVa7Vcui8zzT6mcuzIqqT3GKlMttqhmQ=; b=wcX5ZstVa5fFNlMGmOhTofOo0SBuvxSqIA+lM/ZkDfk0Nu2Xxvzj2LxCKcN/+bU5HP WmdIplsDGbXWK7JdeA0D770SFTw1nnVTxugs6dq0m6wA2VTeTx0d6O79XYMJMfRjq01W wWm8AOJxFzcJm7ejKCYb5WOyzlqojolRg04J2/N6uKaso0sbZZjzwhrUbfL+6q2rhWsr 5b28HasAOfRhFFIUFIIf36QJWkfRmUa4vWzQZqdNUhymvKHOoNlDleBD5Mjz+bOExadw FtCmqaxOt34ne8TZmVN03i/ukW8lNT0hJSp5vuIJSXUeCwPwUR06TTn/yRSbDleLMNKJ sUWg== X-Forwarded-Encrypted: i=1; AJvYcCVd+CgqK3GAm+x/Acijof/hfBJjv9XrDv2gPC7diCKoOMA80NXO/TIkF9ULSH6zD6wZEPiNVDHTvTM=@vger.kernel.org, AJvYcCVmSS9Nj3nI9D+d2WuSSK5PHZC7VUHIr0Qd4afJAOC9Q7WtCL2VllMjk1cPpcBacIH8UekMFWj6mqOC4Hkk@vger.kernel.org, AJvYcCWML9mrq0+S3Mg5ZhG8CdnupAHnTNxh2TjMaZE3wOcrR527vm4h0KbJeZ+TQ4w+paWugd78OodnKjUR9KvIHGsyvWdtLw==@vger.kernel.org X-Gm-Message-State: AOJu0Yyb5hiQF7L/PNipkuszISm6Eq4kFT3VomI66QM2FFu1aID7BesM q5X5pwt84idEoOuhsuq+FdwiqatVfc080tacrMeGmz5RUFMQ5z/W X-Gm-Gg: ASbGncsBYHiDdAoy9JSs0LfGHGg4lHJMQQm5qHM5tgT9huo7Jjex4H1RTc9hD9O4d1F 27eNdO/ieSwJihIXgTUG99rMKd4HIJI9ketmurOKJpqSrQOIJqwpiCo+QDvABovx91GpAF5QyPL lnHoDc7SofCrs+Fn/wuLDuLYHIcx51+FM6d6tXEwWR0+82i62Xt7BeRtYTF0rG3nxrRLP8DHT0S OarPELkjBrr7tkYfmAOm1TuUaUEmZlWO5TwwiUO5IXCx6L0ztcjms7jB64AmZKQ7pAKIiOpVjT4 3TDG8bnBkz/9F5Pctcc9rlMQpIathjbavHB0Stjc6838Qz+MO7oTLca455f43vAM/SU2maSyfI9 zZf6Ht5rhK85LIgY6PnOoTcjBoqDM80rltf0TWLzV25g= X-Google-Smtp-Source: AGHT+IEJU7Lz4p8fv4Or3wtFF6OTZwCjTjx9RX/MIb0DhYGB79c5WcSwDL83nDvdz10NSx/fYtfMoA== X-Received: by 2002:a17:90b:53ce:b0:2ff:4a8d:74f9 with SMTP id 98e67ed59e1d1-3015214a7d9mr13858059a91.10.1742193243989; Sun, 16 Mar 2025 23:34:03 -0700 (PDT) Received: from localhost.localdomain (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-30153afee51sm5186555a91.28.2025.03.16.23.34.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 16 Mar 2025 23:34:03 -0700 (PDT) From: "Derek J. Clark" To: Hans de Goede , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Cc: Armin Wolf , Jonathan Corbet , Mario Limonciello , Luke Jones , Xino Ni , Zhixin Zhang , Mia Shao , Mark Pearson , "Pierre-Loup A . Griffais" , "Cody T . -H . Chiu" , John Martens , "Derek J . Clark" , platform-driver-x86@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v4 3/4] platform/x86: Add Lenovo Other Mode WMI Driver Date: Sun, 16 Mar 2025 23:33:32 -0700 Message-ID: <20250317063337.352966-6-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20250317063337.352966-1-derekjohn.clark@gmail.com> References: <20250317063337.352966-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds lenovo-wmi-other driver which provides the Lenovo "Other Mode" WMI interface that comes on some Lenovo "Gaming Series" hardware. Provides a firmware-attributes class which enables the use of tunable knobs for SPL, SPPT, and FPPT. Signed-off-by: Derek J. Clark Suggested-by: Mario Limonciello --- v4: - Treat Other Mode as a notifier chain head, use the notifier chain to get the current mode from Gamezone. - Add header file for Other Mode specific structs and finctions. - Use component master bind to cache the capdata01 array locally. - Drop all reference to external driver private data structs. - Various fixes from review. v3: - Add notifier block and store result for getting the Gamezone interface profile changes. - Add driver as master component of capdata01 driver. - Use FIELD_PREP where appropriate. - Move macros and associated functions out of lemovo-wmi.h that are only used by this driver. v2: - Use devm_kmalloc to ensure driver can be instanced, remove global reference. - Ensure reverse Christmas tree for all variable declarations. - Remove extra whitespace. - Use guard(mutex) in all mutex instances, global mutex. - Use pr_fmt instead of adding the driver name to each pr_err. - Remove noisy pr_info usage. - Rename other_method_wmi to lenovo_wmi_om_priv and om_wmi to priv. - Use list to get the lenovo_wmi_om_priv instance in some macro called functions as the data provided by the macros that use it doesn't pass a member of the struct for use in container_of. - Do not rely on GameZone interface to grab the current fan mode. --- MAINTAINERS | 2 + drivers/platform/x86/Kconfig | 15 + drivers/platform/x86/Makefile | 1 + drivers/platform/x86/lenovo-wmi-other.c | 626 ++++++++++++++++++++++++ drivers/platform/x86/lenovo-wmi-other.h | 19 + 5 files changed, 663 insertions(+) create mode 100644 drivers/platform/x86/lenovo-wmi-other.c create mode 100644 drivers/platform/x86/lenovo-wmi-other.h diff --git a/MAINTAINERS b/MAINTAINERS index 56ead241a053..87daee6075ad 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13170,6 +13170,8 @@ F: drivers/platform/x86/lenovo-wmi-events.c F: drivers/platform/x86/lenovo-wmi-events.h F: drivers/platform/x86/lenovo-wmi-helpers.c F: drivers/platform/x86/lenovo-wmi-helpers.h +F: drivers/platform/x86/lenovo-wmi-other.c +F: drivers/platform/x86/lenovo-wmi-other.h =20 LENOVO WMI HOTKEY UTILITIES DRIVER M: Jackie Dong diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 64663667f0cb..fc47604e37f7 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -471,6 +471,21 @@ config LENOVO_WMI_DATA01 tristate depends on ACPI_WMI =20 +config LENOVO_WMI_TUNING + tristate "Lenovo Other Mode WMI Driver" + depends on ACPI_WMI + select FW_ATTR_CLASS + select LENOVO_WMI_DATA01 + select LENOVO_WMI_EVENTS + select LENOVO_WMI_HELPERS + help + Say Y here if you have a WMI aware Lenovo Legion device and would like = to use the + firmware_attributes API to control various tunable settings typically e= xposed by + Lenovo software in Windows. + + To compile this driver as a module, choose M here: the module will + be called lenovo-wmi-other. + config IDEAPAD_LAPTOP tristate "Lenovo IdeaPad Laptop Extras" depends on ACPI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 7a35c77221b7..c6ce3c8594b1 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -72,6 +72,7 @@ obj-$(CONFIG_LENOVO_WMI_CAMERA) +=3D lenovo-wmi-camera.o obj-$(CONFIG_LENOVO_WMI_DATA01) +=3D lenovo-wmi-capdata01.o obj-$(CONFIG_LENOVO_WMI_EVENTS) +=3D lenovo-wmi-events.o obj-$(CONFIG_LENOVO_WMI_HELPERS) +=3D lenovo-wmi-helpers.o +obj-$(CONFIG_LENOVO_WMI_TUNING) +=3D lenovo-wmi-other.o =20 # Intel obj-y +=3D intel/ diff --git a/drivers/platform/x86/lenovo-wmi-other.c b/drivers/platform/x86= /lenovo-wmi-other.c new file mode 100644 index 000000000000..b517e45338e0 --- /dev/null +++ b/drivers/platform/x86/lenovo-wmi-other.c @@ -0,0 +1,626 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Lenovo Other Mode WMI interface driver. This driver uses the fw_attribu= tes + * class to expose the various WMI functions provided by the "Other Mode" = WMI + * interface. This enables CPU and GPU power limit as well as various other + * attributes for devices that fall under the "Gaming Series" of Lenovo la= ptop + * devices. Each attribute exposed by the "Other Mode"" interface has a + * corresponding LENOVO_CAPABILITY_DATA_01 struct that allows the driver to + * probe details about the attribute such as set/get support, step, min, m= ax, + * and default value. Each attibute has multiple pages, one for each of the + * fan profiles managed by the Gamezone interface. + * + * These attributes typically don't fit anywhere else in the sysfs and are= set + * in Windows using one of Lenovo's multiple user applications. + * + * Copyright(C) 2025 Derek J. Clark + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lenovo-wmi-capdata01.h" +#include "lenovo-wmi-events.h" +#include "lenovo-wmi-gamezone.h" +#include "lenovo-wmi-helpers.h" +#include "lenovo-wmi-other.h" +#include "firmware_attributes_class.h" + +/* Interface GUIDs */ +#define LENOVO_OTHER_METHOD_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B" + +/* Device IDs */ +#define WMI_DEVICE_ID_CPU 0x01 + +/* WMI_DEVICE_ID_CPU feature IDs */ +#define WMI_FEATURE_ID_CPU_SPPT 0x01 /* Short Term Power Limit */ +#define WMI_FEATURE_ID_CPU_FPPT 0x03 /* Long Term Power Limit */ +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */ + +/* Type IDs*/ +#define WMI_TYPE_ID_NONE 0x00 + +/* Method IDs */ +#define WMI_FEATURE_VALUE_GET 17 /* Other Mode Getter */ +#define WMI_FEATURE_VALUE_SET 18 /* Other Mode Setter */ + +/* Attribute ID bitmasks */ +#define ATTR_DEV_ID_MASK GENMASK(31, 24) +#define ATTR_FEAT_ID_MASK GENMASK(23, 16) +#define ATTR_MODE_ID_MASK GENMASK(15, 8) +#define ATTR_TYPE_ID_MASK GENMASK(7, 0) + +static BLOCKING_NOTIFIER_HEAD(om_chain_head); + +enum attribute_property { + DEFAULT_VAL, + MAX_VAL, + MIN_VAL, + STEP_VAL, + SUPPORTED, +}; + +struct lwmi_om_priv { + struct blocking_notifier_head nhead; + struct component_master_ops *ops; + struct cd01_list cd01_list; + struct device *fw_attr_dev; + struct kset *fw_attr_kset; + struct notifier_block nb; + struct wmi_device *wdev; + struct ida ida; + int ida_id; +}; + +/* Tunable attribute that uses LENOVO_CAPABILITY_DATA_01 */ +struct tunable_attr_01 { + u32 type_id; + u32 device_id; + u32 feature_id; + u32 store_value; + struct device *dev; + struct capdata01 *capdata; +}; + +/* Tunable Attributes */ +struct tunable_attr_01 ppt_pl1_spl =3D { .device_id =3D WMI_DEVICE_ID_CPU, + .feature_id =3D WMI_FEATURE_ID_CPU_SPL, + .type_id =3D WMI_TYPE_ID_NONE }; +struct tunable_attr_01 ppt_pl2_sppt =3D { .device_id =3D WMI_DEVICE_ID_CPU, + .feature_id =3D WMI_FEATURE_ID_CPU_SPPT, + .type_id =3D WMI_TYPE_ID_NONE }; +struct tunable_attr_01 ppt_pl3_fppt =3D { .device_id =3D WMI_DEVICE_ID_CPU, + .feature_id =3D WMI_FEATURE_ID_CPU_FPPT, + .type_id =3D WMI_TYPE_ID_NONE }; + +struct capdata01_attr_group { + const struct attribute_group *attr_group; + struct tunable_attr_01 *tunable_attr; +}; + +#define FW_ATTR_FOLDER "lenovo-wmi-other" + +/* Notifier Methods */ +int lwmi_om_register_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&om_chain_head, nb); +} +EXPORT_SYMBOL_NS_GPL(lwmi_om_register_notifier, "LENOVO_WMI_OTHER"); + +int lwmi_om_unregister_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&om_chain_head, nb); +} +EXPORT_SYMBOL_NS_GPL(lwmi_om_unregister_notifier, "LENOVO_WMI_OTHER"); + +static void devm_lwmi_om_unregister_notifier(void *data) +{ + struct notifier_block *nb =3D data; + + lwmi_om_unregister_notifier(nb); +} + +int devm_lwmi_om_register_notifier(struct device *dev, + struct notifier_block *nb) +{ + int ret; + + ret =3D lwmi_om_register_notifier(nb); + if (ret < 0) + return ret; + + return devm_add_action_or_reset(dev, devm_lwmi_om_unregister_notifier, + nb); +} +EXPORT_SYMBOL_NS_GPL(devm_lwmi_om_register_notifier, "LENOVO_WMI_OTHER"); + +static int lwmi_om_notifier_call(enum thermal_mode *mode) +{ + int ret; + + ret =3D blocking_notifier_call_chain(&om_chain_head, THERMAL_MODE_EVENT, + mode); + + if (ret !=3D NOTIFY_OK) + return -EINVAL; + + if (*mode < SMARTFAN_MODE_QUIET || *mode > SMARTFAN_MODE_CUSTOM) + return -EINVAL; + + return 0; +} + +/* Attribute Methods */ +/* + * int_type_show() - Emit the data type for an integer attribute + * @kobj: Pointer to the driver object. + * @kobj_attribute: Pointer to the attribute calling this function. + * @buf: The buffer to write to. + * + * Returns: Number of characters written to buf. + */ +static ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *= kattr, + char *buf) +{ + return sysfs_emit(buf, "integer\n"); +} + +/* + * attr_capdata01_get - Get the data of the specified attribute + * from lwmi_om->cd01. + * @tunable_attr: The attribute to be populated. + * + * Returns: Either a pointer to capability data, or NULL. + */ +static struct capdata01 * +attr_capdata01_get_data(struct lwmi_om_priv *priv, + struct tunable_attr_01 *tunable_attr, + enum thermal_mode mode) +{ + u32 attribute_id =3D + FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) | + FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) | + FIELD_PREP(ATTR_MODE_ID_MASK, mode) | + FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id); + int idx; + + for (idx =3D 0; idx < priv->cd01_list.count; idx++) { + if (!priv->cd01_list.data[idx]) + continue; + + if (priv->cd01_list.data[idx]->id !=3D attribute_id) + continue; + return priv->cd01_list.data[idx]; + } + return NULL; +} + +/** + * attr_capdata01_show() - Get the value of the specified attribute proper= ty + * from LENOVO_CAPABILITY_DATA_01. + * @kobj: Pointer to the driver object. + * @kobj_attribute: Pointer to the attribute calling this function. + * @buf: The buffer to write to. + * @tunable_attr: The attribute to be read. + * @prop: The property of this attribute to be read. + * + * This function is intended to be generic so it can be called from any "_= show" + * attribute which works only with integers. + * + * If the WMI is success, then the sysfs attribute is notified. + * + * Returns: Either number of characters written to buf, or an error. + */ +static ssize_t attr_capdata01_show(struct kobject *kobj, + struct kobj_attribute *kattr, char *buf, + struct tunable_attr_01 *tunable_attr, + enum attribute_property prop) +{ + struct lwmi_om_priv *priv =3D dev_get_drvdata(tunable_attr->dev); + struct capdata01 *capdata; + int value; + + if (!priv) + return -ENODEV; + + capdata =3D attr_capdata01_get_data(priv, tunable_attr, + SMARTFAN_MODE_CUSTOM); + + if (!capdata) + return -ENODEV; + + switch (prop) { + case DEFAULT_VAL: + value =3D capdata->default_value; + break; + case MAX_VAL: + value =3D capdata->max_value; + break; + case MIN_VAL: + value =3D capdata->min_value; + break; + case STEP_VAL: + value =3D capdata->step; + break; + default: + return -EINVAL; + } + return sysfs_emit(buf, "%d\n", value); +} + +/* Simple attribute creation */ + +/* + * att_current_value_store() - Set the current value of the given attribute + * @kobj: Pointer to the driver object. + * @kobj_attribute: Pointer to the attribute calling this function. + * @buf: The buffer to read from, this is parsed to `int` type. + * @count: Required by sysfs attribute macros, pass in from the callee att= r. + * @tunable_attr: The attribute to be stored. + * + * This function is intended to be generic so it can be called from any + * attribute's "current_value_store" which works only with integers. The + * integer to be sent to the WMI method is range checked and an error retu= rned + * if out of range. + * + * If the value is valid and WMI is success, then the sysfs attribute is + * notified. + * + * Returns: Either count, or an error. + */ +static ssize_t attr_current_value_store(struct kobject *kobj, + struct kobj_attribute *kattr, + const char *buf, size_t count, + struct tunable_attr_01 *tunable_attr) +{ + struct lwmi_om_priv *priv =3D dev_get_drvdata(tunable_attr->dev); + struct wmi_method_args_32 args; + struct capdata01 *capdata; + enum thermal_mode mode; + u32 attribute_id; + u32 value; + int err; + + if (!priv) + return -ENODEV; + + err =3D lwmi_om_notifier_call(&mode); + if (err) + return err; + + if (mode !=3D SMARTFAN_MODE_CUSTOM) + return -EINVAL; + + capdata =3D attr_capdata01_get_data(priv, tunable_attr, mode); + + if (!capdata) + return -ENODEV; + + attribute_id =3D FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) | + FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) | + FIELD_PREP(ATTR_MODE_ID_MASK, mode) | + FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id); + + err =3D kstrtouint(buf, 10, &value); + if (err) + return err; + + if (value < capdata->min_value || value > capdata->max_value) + return -EINVAL; + + args.arg0 =3D attribute_id; + args.arg1 =3D value; + + err =3D lwmi_dev_evaluate_method(priv->wdev, 0x0, WMI_FEATURE_VALUE_SET, + (unsigned char *)&args, sizeof(args), + NULL); + + if (err) + return err; + + tunable_attr->store_value =3D value; + return count; +}; + +/* + * attr_current_value_show() - Get the current value of the given attribute + * @kobj: Pointer to the driver object. + * @kobj_attribute: Pointer to the attribute calling this function. + * @buf: The buffer to write to. + * @tunable_attr: The attribute to be read. + * + * This function is intended to be generic so it can be called from any "_= show" + * attribute which works only with integers. + * + * If the WMI is success, then the sysfs attribute is notified. + * + * Returns: Either number of characters written to buf, or an error. + */ +static ssize_t attr_current_value_show(struct kobject *kobj, + struct kobj_attribute *kattr, char *buf, + struct tunable_attr_01 *tunable_attr) +{ + struct lwmi_om_priv *priv =3D dev_get_drvdata(tunable_attr->dev); + struct wmi_method_args_32 args; + enum thermal_mode mode; + u32 attribute_id; + int retval; + int err; + + if (!priv) + return -ENODEV; + + err =3D lwmi_om_notifier_call(&mode); + if (err) + return err; + + attribute_id =3D FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) | + FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) | + FIELD_PREP(ATTR_MODE_ID_MASK, mode) | + FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id); + + args.arg0 =3D attribute_id; + + err =3D lwmi_dev_evaluate_method(priv->wdev, 0x0, WMI_FEATURE_VALUE_GET, + (unsigned char *)&args, sizeof(args), + &retval); + + if (err) + return err; + + return sysfs_emit(buf, "%d\n", retval); +} + +/* Attribute macros */ +#define __LL_ATTR_RO(_func, _name) \ + { \ + .attr =3D { .name =3D __stringify(_name), .mode =3D 0444 }, \ + .show =3D _func##_##_name##_show, \ + } + +#define __LL_ATTR_RO_AS(_name, _show) \ + { \ + .attr =3D { .name =3D __stringify(_name), .mode =3D 0444 }, \ + .show =3D _show, \ + } + +#define __LL_ATTR_RW(_func, _name) \ + __ATTR(_name, 0644, _func##_##_name##_show, _func##_##_name##_store) + +/* Shows a formatted static variable */ +#define __ATTR_SHOW_FMT(_prop, _attrname, _fmt, _val) = \ + static ssize_t _attrname##_##_prop##_show( \ + struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \ + { \ + return sysfs_emit(buf, _fmt, _val); \ + } \ + static struct kobj_attribute attr_##_attrname##_##_prop =3D \ + __LL_ATTR_RO(_attrname, _prop) + +/* Attribute current value read/write */ +#define __LL_TUNABLE_CURRENT_VALUE_CAP01(_attrname) = \ + static ssize_t _attrname##_current_value_store( \ + struct kobject *kobj, struct kobj_attribute *kattr, \ + const char *buf, size_t count) \ + { \ + return attr_current_value_store(kobj, kattr, buf, count, \ + &_attrname); \ + } \ + static ssize_t _attrname##_current_value_show( \ + struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \ + { \ + return attr_current_value_show(kobj, kattr, buf, &_attrname); \ + } \ + static struct kobj_attribute attr_##_attrname##_current_value =3D \ + __LL_ATTR_RW(_attrname, current_value) + +/* Attribute property read only */ +#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _prop_type) = \ + static ssize_t _attrname##_##_prop##_show( \ + struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \ + { \ + return attr_capdata01_show(kobj, kattr, buf, &_attrname, \ + _prop_type); \ + } \ + static struct kobj_attribute attr_##_attrname##_##_prop =3D \ + __LL_ATTR_RO(_attrname, _prop) + +#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dispname) \ + __LL_TUNABLE_CURRENT_VALUE_CAP01(_attrname); \ + __LL_TUNABLE_RO_CAP01(default_value, _attrname, DEFAULT_VAL); \ + __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \ + __LL_TUNABLE_RO_CAP01(max_value, _attrname, MAX_VAL); \ + __LL_TUNABLE_RO_CAP01(min_value, _attrname, MIN_VAL); \ + __LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, STEP_VAL); \ + static struct kobj_attribute attr_##_attrname##_type =3D \ + __LL_ATTR_RO_AS(type, int_type_show); \ + static struct attribute *_attrname##_attrs[] =3D { \ + &attr_##_attrname##_current_value.attr, \ + &attr_##_attrname##_default_value.attr, \ + &attr_##_attrname##_display_name.attr, \ + &attr_##_attrname##_max_value.attr, \ + &attr_##_attrname##_min_value.attr, \ + &attr_##_attrname##_scalar_increment.attr, \ + &attr_##_attrname##_type.attr, \ + NULL, \ + }; \ + static const struct attribute_group _attrname##_attr_group =3D { \ + .name =3D _fsname, .attrs =3D _attrname##_attrs \ + } + +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl", + "Set the CPU sustained power limit"); +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt", + "Set the CPU slow package power tracking limit"); +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt", + "Set the CPU fast package power tracking limit"); + +static struct capdata01_attr_group capdata01_attr_groups[] =3D { + { &ppt_pl1_spl_attr_group, &ppt_pl1_spl }, + { &ppt_pl2_sppt_attr_group, &ppt_pl2_sppt }, + { &ppt_pl3_fppt_attr_group, &ppt_pl3_fppt }, + {}, +}; + +/* + * lwmi_om_fw_attr_add() - Registers all capdata01_attr_groups[] attribute= s as + * firmware_attributes_class members. + * @priv: The Other Mode driver data. + * + * Returns: Either 0, or an error. + */ +static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv) +{ + int err, i; + + ida_init(&priv->ida); + priv->ida_id =3D ida_alloc(&priv->ida, GFP_KERNEL); + if (priv->ida_id < 0) + return priv->ida_id; + + priv->fw_attr_dev =3D device_create(&firmware_attributes_class, NULL, + MKDEV(0, 0), NULL, "%s", + FW_ATTR_FOLDER); + if (IS_ERR(priv->fw_attr_dev)) { + err =3D PTR_ERR(priv->fw_attr_dev); + return err; + } + + priv->fw_attr_kset =3D kset_create_and_add("attributes", NULL, + &priv->fw_attr_dev->kobj); + if (!priv->fw_attr_kset) { + err =3D -ENOMEM; + goto err_destroy_classdev; + } + + for (i =3D 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) { + err =3D sysfs_create_group(&priv->fw_attr_kset->kobj, + capdata01_attr_groups[i].attr_group); + if (err) { + pr_debug("Failed to create sysfs-group for %s: %d\n", + capdata01_attr_groups[i].attr_group->name, + err); + goto err_remove_groups; + } + capdata01_attr_groups[i].tunable_attr->dev =3D &priv->wdev->dev; + } + return 0; + +err_remove_groups: + ida_free(&priv->ida, priv->ida_id); + while (i-- >=3D 0) { + sysfs_remove_group(&priv->fw_attr_kset->kobj, + capdata01_attr_groups[i].attr_group); + } + kset_unregister(priv->fw_attr_kset); + +err_destroy_classdev: + device_unregister(priv->fw_attr_dev); + return err; +} + +/* + * lwmi_om_fw_attr_remove() - Unregisters all capdata01_attr_groups[] attr= ibutes as + * firmware_attributes_class members. + * @priv: The Other Mode driver data. + * + */ +static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv) +{ + int size =3D ARRAY_SIZE(capdata01_attr_groups); + + while (--size >=3D 0) { + sysfs_remove_group(&priv->fw_attr_kset->kobj, + capdata01_attr_groups[size].attr_group); + } + kset_unregister(priv->fw_attr_kset); + device_unregister(priv->fw_attr_dev); +} + +static int lwmi_om_master_bind(struct device *dev) +{ + struct lwmi_om_priv *priv =3D dev_get_drvdata(dev); + int ret; + + ret =3D component_bind_all(dev, &priv->cd01_list); + if (ret) + return ret; + + return lwmi_om_fw_attr_add(priv); +} + +static void lwmi_om_master_unbind(struct device *dev) +{ + component_unbind_all(dev, NULL); +} + +static const struct component_master_ops lwmi_om_master_ops =3D { + .bind =3D lwmi_om_master_bind, + .unbind =3D lwmi_om_master_unbind, +}; + +static int lwmi_other_probe(struct wmi_device *wdev, const void *context) +{ + struct component_match *master_match =3D NULL; + struct lwmi_om_priv *priv; + + priv =3D devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->wdev =3D wdev; + dev_set_drvdata(&wdev->dev, priv); + + component_match_add(&wdev->dev, &master_match, lwmi_cd01_match, NULL); + if (IS_ERR(master_match)) + return PTR_ERR(master_match); + + return component_master_add_with_match(&wdev->dev, &lwmi_om_master_ops, + master_match); +} + +static void lwmi_other_remove(struct wmi_device *wdev) +{ + struct lwmi_om_priv *priv =3D dev_get_drvdata(&wdev->dev); + + component_master_del(&wdev->dev, &lwmi_om_master_ops); + lwmi_om_fw_attr_remove(priv); + ida_free(&priv->ida, priv->ida_id); +} + +static const struct wmi_device_id lwmi_other_id_table[] =3D { + { LENOVO_OTHER_METHOD_GUID, NULL }, + {} +}; + +static struct wmi_driver lwmi_other_driver =3D { + .driver =3D { + .name =3D "lenovo_wmi_other", + .probe_type =3D PROBE_PREFER_ASYNCHRONOUS, + }, + .id_table =3D lwmi_other_id_table, + .probe =3D lwmi_other_probe, + .remove =3D lwmi_other_remove, + .no_singleton =3D true, +}; + +module_wmi_driver(lwmi_other_driver); + +MODULE_IMPORT_NS("LENOVO_WMI_CD01"); +MODULE_IMPORT_NS("LENOVO_WMI_HELPERS"); +MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table); +MODULE_AUTHOR("Derek J. Clark "); +MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/lenovo-wmi-other.h b/drivers/platform/x86= /lenovo-wmi-other.h new file mode 100644 index 000000000000..9fba35ef1137 --- /dev/null +++ b/drivers/platform/x86/lenovo-wmi-other.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright(C) 2025 Derek J. Clark + * + */ + +#ifndef _LENOVO_WMI_OTHER_H_ +#define _LENOVO_WMI_OTHER_H_ + +#include +#include +#include + +int lwmi_om_register_notifier(struct notifier_block *nb); +int lwmi_om_unregister_notifier(struct notifier_block *nb); +int devm_lwmi_om_register_notifier(struct device *dev, + struct notifier_block *nb); + +#endif /* !_LENOVO_WMI_H_ */ --=20 2.49.0 From nobody Wed Dec 17 15:33:16 2025 Received: from mail-pj1-f51.google.com (mail-pj1-f51.google.com [209.85.216.51]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 57D59219A70; Mon, 17 Mar 2025 06:34:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.51 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742193249; cv=none; b=gxEcVZtn//X8l8kAfNp9MS4+urtCC1F+Su31WkZ5fXtUuYCDA2Ux65/ZeL6WEfnsMnPXVz7wNfXfgy0MAWq4yHFO/pqfYsEmgM5xMkVPVGfNLPZfie397xuqfoYSKzvX+ea+cPNh6yKh0DpeKXKLbp6rofnq0ggXL3J2huW1x60= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742193249; c=relaxed/simple; bh=QtJuuApcIxOnqq0q7ztcDfW6m82MqWlyJ0VShXMUOR0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ICCg9V3WKahF3/kfJhm/QvK/EC0hMZseBaSg0IRTZWvjP2cOiO39X2t/eoNyt6l6Dph+dKUKG3nnZYi6i3J50Dl9/P2in9e2SVThrDCEnjTVGiy7GTy+5dXC+MYFH+GFCH5810ykamHto+qLNlyCfKH6zdw8oTRTGPZxLGOJXxA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=BG1AFzek; arc=none smtp.client-ip=209.85.216.51 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="BG1AFzek" Received: by mail-pj1-f51.google.com with SMTP id 98e67ed59e1d1-2ff187f027fso2567807a91.1; Sun, 16 Mar 2025 23:34:07 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1742193247; x=1742798047; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=Hg3kUIYAZxHueF9SBco9xBAfkg5dY1set7XtTII34e0=; b=BG1AFzek+top/RcR+9FGcie0sQhVlPDphEbfZNu54FShIFgw1Vt1eqF4ilaL+3Zni2 fOkYZQIN8jsnrafloF/4PGZeap9hSoyzupEp8EAL1Zkm7B3F+MWGzqsRxF2DX4aXsNqS 5HoTIiGLl7sjeNVGJ8bww/TPJ0RDox6DFeik2Wqx/MQS4HjVkcXA4pjhQ6dR7n90cx/q x/wQOHaEKOE5jojGtkllnlL2jGMD6KaIXgcHAA73zuJwRJW7abZhITYeVS9fi90IQn4r tId2kuGHN0Mr+9Vjy9p4RRjxDN+IRBVRHDemxPzP4glCoUxjwwx5hxDoTVKC4CVajWwC A6KA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1742193247; x=1742798047; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Hg3kUIYAZxHueF9SBco9xBAfkg5dY1set7XtTII34e0=; b=Uj/oSNGroqX1//XA8Enxo4739+3BrUaGOsrvZBpocKICr3A8gNkpYVZo8PSMEtOTWb zqexlnadJxJ689rjK2Tm6ro8DQMbb0g/lvdaZYwxuzuvrK9/Wpmzb1TQbGPs/FhanLzl yQGe55aR+7HHyubwlgcRk7ZqGzKi8Wj6mVNyTeRbqi8SwQWAKgpBm+RZ1ABuxruXMwsS q4/AnEkuCiY/ibXq2LmR92bDuo1L+XvI2qQpTOetE9+XItSqYaH/BXDmAznUGLIEaQWN bgYYQEr0WGGumkb7aRz8ri1GSG6h/kcIuo+meWlfBnuoSTvUIgmFLP8Jah0WIQCqI5Cp lafA== X-Forwarded-Encrypted: i=1; AJvYcCUEHVbmd1w541WVF/gbd315u8pi7ap9M3ouBXPY54UEZePXtgCratQvyAICtiYze78+AwAt8HlAWmc=@vger.kernel.org, AJvYcCUalZf6goonNI2oHTMJ83vdHUyy1VECfufOarVlWWmgpd/sZsRaxq9+bVKAMAmIFntRkElOid92ZLR1EVOSQFLGOBNmGg==@vger.kernel.org, AJvYcCWUBV9MAx02mzylfiXjZe7t2tke1cz8xTqzYz6aGUuYrvexzggvS3h4ag9QcNdCMRupt2dNR6Lq4XBtojWm@vger.kernel.org X-Gm-Message-State: AOJu0YyIFDAh+UvhejiMNY7tnUjUbfJfPVtnbj1ZFBMQUeia3sEkAUxU CfGGLZHO9+0OlCi20od/itFeHVbfbkn/pvVx1smLKMHP5iJbwuaS X-Gm-Gg: ASbGnctpWrNWpyG7F9Ndzrp2d2EIlkEYnz5VpLlnVsODMazhFYvGh/97LkqgODCOn1w pySOR9e2G0OeAb3+iWP6Xyo7EzG16DsstFhfoqGCMfBPINJQlAkOZ+YmFW4CzTX7LD3eJn5BQPQ 08D9ghigV7+Qv77YcKRjEcmc65KqqwJrKmw9QscPVvueWe3uCKGixDfAHU5NIFjOBNoKf9t52H1 4Y17vd21Q3e7NzIMG2OQrTPjluTaUjy41JAJZNkzixulAa+Do6TZ0YtX3rn9teLDR3t1n6RB1tq 1mF7p2ikplZ/SvPcq3Saonrrxj4lGSFL4xD3ql/rcc9Jrm0A/ulSNsJGrfcvJoz65KV99NHjkRq kgUwiARDfECI+C2fF6vmLP3CKa7by+tnDfKV9mEr0X9HVdLMFA3g9SA== X-Google-Smtp-Source: AGHT+IGth3ldPd6INVG7H7hRFPdKq6PwvOCUmeBRF1tGAyGIXRjLDrM3zkhkK2KkNsqCZSYNUzIq7w== X-Received: by 2002:a17:90b:264c:b0:2ff:5267:e7da with SMTP id 98e67ed59e1d1-3015211de7dmr12914921a91.3.1742193246548; Sun, 16 Mar 2025 23:34:06 -0700 (PDT) Received: from localhost.localdomain (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-30153afee51sm5186555a91.28.2025.03.16.23.34.05 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 16 Mar 2025 23:34:06 -0700 (PDT) From: "Derek J. Clark" To: Hans de Goede , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Cc: Armin Wolf , Jonathan Corbet , Mario Limonciello , Luke Jones , Xino Ni , Zhixin Zhang , Mia Shao , Mark Pearson , "Pierre-Loup A . Griffais" , "Cody T . -H . Chiu" , John Martens , "Derek J . Clark" , platform-driver-x86@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v4 4/6] platform/x86: Add Lenovo Capability Data 01 WMI Driver Date: Sun, 16 Mar 2025 23:33:34 -0700 Message-ID: <20250317063337.352966-8-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20250317063337.352966-1-derekjohn.clark@gmail.com> References: <20250317063337.352966-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds lenovo-wmi-capdata01 driver which provides the LENOVO_CAPABILITY_DATA_01 WMI data block that comes on "Other Mode" enabled hardware. Provides an interface for querying if a given attribute is supported by the hardware, as well as its default_value, max_value, min_value, and step increment. Signed-off-by: Derek J. Clark Suggested-by: Mario Limonciello --- v4: - Make driver data a private struct, remove references from Other Mode driver. - Don't cache data at device initialization. Instead, on component bind, cache the data on a member variable of the Other Mode driver data passed as a void pointer. - Add header file for capdata01 structs. - Add new struct to pass capdata01 array data and array length to Other Mode. v3: - Add as component to lenovo-wmi-other driver. v2: - Use devm_kmalloc to ensure driver can be instanced, remove global reference. - Ensure reverse Christmas tree for all variable declarations. - Remove extra whitespace. - Use guard(mutex) in all mutex instances, global mutex. - Use pr_fmt instead of adding the driver name to each pr_err. - Remove noisy pr_info usage. - Rename capdata_wmi to lenovo_wmi_cd01_priv and cd01_wmi to priv. - Use list to get the lenovo_wmi_cd01_priv instance in lenovo_wmi_capdata01_get as none of the data provided by the macros that will use it can pass a member of the struct for use in container_of. --- MAINTAINERS | 2 + drivers/platform/x86/Kconfig | 4 + drivers/platform/x86/Makefile | 1 + drivers/platform/x86/lenovo-wmi-capdata01.c | 136 ++++++++++++++++++++ drivers/platform/x86/lenovo-wmi-capdata01.h | 29 +++++ 5 files changed, 172 insertions(+) create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.h diff --git a/MAINTAINERS b/MAINTAINERS index 6dde75922aaf..56ead241a053 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13164,6 +13164,8 @@ L: platform-driver-x86@vger.kernel.org S: Maintained F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst F: Documentation/wmi/devices/lenovo-wmi-other.rst +F: drivers/platform/x86/lenovo-wmi-capdata01.c +F: drivers/platform/x86/lenovo-wmi-capdata01.h F: drivers/platform/x86/lenovo-wmi-events.c F: drivers/platform/x86/lenovo-wmi-events.h F: drivers/platform/x86/lenovo-wmi-helpers.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 13b8f4ac5dc5..64663667f0cb 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -467,6 +467,10 @@ config LENOVO_WMI_HELPERS tristate depends on ACPI_WMI =20 +config LENOVO_WMI_DATA01 + tristate + depends on ACPI_WMI + config IDEAPAD_LAPTOP tristate "Lenovo IdeaPad Laptop Extras" depends on ACPI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index fc039839286a..7a35c77221b7 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -69,6 +69,7 @@ obj-$(CONFIG_THINKPAD_LMI) +=3D think-lmi.o obj-$(CONFIG_YOGABOOK) +=3D lenovo-yogabook.o obj-$(CONFIG_YT2_1380) +=3D lenovo-yoga-tab2-pro-1380-fastcharger.o obj-$(CONFIG_LENOVO_WMI_CAMERA) +=3D lenovo-wmi-camera.o +obj-$(CONFIG_LENOVO_WMI_DATA01) +=3D lenovo-wmi-capdata01.o obj-$(CONFIG_LENOVO_WMI_EVENTS) +=3D lenovo-wmi-events.o obj-$(CONFIG_LENOVO_WMI_HELPERS) +=3D lenovo-wmi-helpers.o =20 diff --git a/drivers/platform/x86/lenovo-wmi-capdata01.c b/drivers/platform= /x86/lenovo-wmi-capdata01.c new file mode 100644 index 000000000000..b6876611ffd9 --- /dev/null +++ b/drivers/platform/x86/lenovo-wmi-capdata01.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * LENOVO_CAPABILITY_DATA_01 WMI data block driver. This interface provides + * information on tunable attributes used by the "Other Mode" WMI interfac= e, + * including if it is supported by the hardware, the default_value, max_va= lue, + * min_value, and step increment. + * + * Copyright(C) 2025 Derek J. Clark + */ + +#include +#include +#include +#include +#include +#include +#include +#include "lenovo-wmi-capdata01.h" + +/* Interface GUIDs */ +#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE0181= 54" + +struct lwmi_cd01_priv { + struct wmi_device *wdev; +}; + +/* + * lenovo_cd01_component_bind() - On master bind, caches all capability da= ta on + * the master device. + * @cd01_dev: Pointer to the capability data 01 parent device. + * @om_dev: Pointer to the other mode parent device. + * @data: capdata01_list object pointer to return the capability data with. + * + * Returns: 0, or an error. + */ +static int lenovo_cd01_component_bind(struct device *cd01_dev, + struct device *om_dev, void *data) +{ + struct lwmi_cd01_priv *priv =3D dev_get_drvdata(cd01_dev); + int count, idx; + + if (!priv) + return -ENODEV; + + count =3D wmidev_instance_count(priv->wdev); + + if (count =3D=3D 0) + return -EINVAL; + + ((struct cd01_list *)data)->count =3D count; + ((struct cd01_list *)data)->data =3D devm_kmalloc_array(om_dev, count, + sizeof(struct capdata01 *), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + for (idx =3D 0; idx < count; idx++) { + union acpi_object *ret_obj __free(kfree) =3D NULL; + + ret_obj =3D wmidev_block_query(priv->wdev, idx); + if (!ret_obj) { + ((struct cd01_list *)data)->data[idx] =3D NULL; + continue; + } + if (ret_obj->type !=3D ACPI_TYPE_BUFFER) { + ((struct cd01_list *)data)->data[idx] =3D NULL; + continue; + } + + if (ret_obj->buffer.length !=3D sizeof(struct capdata01)) { + ((struct cd01_list *)data)->data[idx] =3D NULL; + continue; + } + + ((struct cd01_list *)data)->data[idx] =3D + devm_kmemdup(om_dev, ret_obj->buffer.pointer, + ret_obj->buffer.length, GFP_KERNEL); + } + return 0; +} + +static const struct component_ops lenovo_cd01_component_ops =3D { + .bind =3D lenovo_cd01_component_bind, +}; + +static int lwmi_cd01_probe(struct wmi_device *wdev, const void *context) + +{ + struct lwmi_cd01_priv *priv; + int ret; + + priv =3D devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->wdev =3D wdev; + dev_set_drvdata(&wdev->dev, priv); + + ret =3D component_add(&wdev->dev, &lenovo_cd01_component_ops); + + return ret; +} + +static void lwmi_cd01_remove(struct wmi_device *wdev) +{ + component_del(&wdev->dev, &lenovo_cd01_component_ops); +} + +static const struct wmi_device_id lwmi_cd01_id_table[] =3D { + { LENOVO_CAPABILITY_DATA_01_GUID, NULL }, + {} +}; + +static struct wmi_driver lwmi_cd01_driver =3D { + .driver =3D { + .name =3D "lenovo_wmi_cd01", + .probe_type =3D PROBE_PREFER_ASYNCHRONOUS, + }, + .id_table =3D lwmi_cd01_id_table, + .probe =3D lwmi_cd01_probe, + .remove =3D lwmi_cd01_remove, + .no_singleton =3D true, +}; + +int lwmi_cd01_match(struct device *dev, void *data) +{ + return dev->driver =3D=3D &lwmi_cd01_driver.driver; +} +EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD01"); + +module_wmi_driver(lwmi_cd01_driver); + +MODULE_DEVICE_TABLE(wmi, lwmi_cd01_id_table); +MODULE_AUTHOR("Derek J. Clark "); +MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/lenovo-wmi-capdata01.h b/drivers/platform= /x86/lenovo-wmi-capdata01.h new file mode 100644 index 000000000000..c7067a8d0398 --- /dev/null +++ b/drivers/platform/x86/lenovo-wmi-capdata01.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright(C) 2025 Derek J. Clark + * + */ + +#ifndef _LENOVO_WMI_CAPDATA01_H_ +#define _LENOVO_WMI_CAPDATA01_H_ + +#include +#include + +struct capdata01 { + u32 id; + u32 supported; + u32 default_value; + u32 step; + u32 min_value; + u32 max_value; +}; + +struct cd01_list { + struct capdata01 **data; + int count; +}; + +int lwmi_cd01_match(struct device *dev, void *data); + +#endif /* !_LENOVO_WMI_CAPDATA01_H_ */ --=20 2.49.0 From nobody Wed Dec 17 15:33:16 2025 Received: from mail-pj1-f51.google.com (mail-pj1-f51.google.com [209.85.216.51]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B765321B191; Mon, 17 Mar 2025 06:34:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.51 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742193250; cv=none; b=bOz7qvmfJ0cU3OYH776pEUv8Bh30lrwLCa1CxAI3D7m85i2VDEhWdBF+/VBTkGmJAP80ZT31y470yZbH2tvE/i/xZmIoBN3h2TfkvLi98Q8GNE78DvXbsN20g0unSy2F98uU/9lPa14SpzEOFpvdE/1w9USjF44VVTnO9t3MIoE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742193250; c=relaxed/simple; bh=scHLMfjQeuz4T/HjBN4ZhUPoxcuhT+g/H5n+Ru0zf5A=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=n7S6pMKwadrMqoZinb2JDBF7GY2Avhr4kB3QkCGqQDOBMTfStxwXUz57bvaHsngNv7/unS1kBfF4ZwVPcmkzEJ1ofUfinkl0PPGrylWyiyfAyMOtH23jOgjD1SucRxbRVgNAhM73QAIv6wOhQABeyhUyDBllIE5b+NWpZZs+644= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=iPcH7Ten; arc=none smtp.client-ip=209.85.216.51 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="iPcH7Ten" Received: by mail-pj1-f51.google.com with SMTP id 98e67ed59e1d1-2ff6e91cff5so2186509a91.2; Sun, 16 Mar 2025 23:34:08 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1742193248; x=1742798048; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=x6WrkgLRJl8lUocm1cnTSBp25XPoazkPi4ONuRt/VwE=; b=iPcH7TenvgtCIEmyKdXvE7nV15Z/EQD7dOhYzYBTV1hvYsGXpVBXkZJMxw/hNce7Xi GBlGu3E0WdLf0DhCZyX4Le1TKf2IU+WIGOr728scy0eGm0pEclEd+Byl5GO0ds/IBRTj 41JD2VgfmChuE665P5ixeTHN0A8EN9/03zTXS/ye2TeUjS3o8hhQmKkgDxTDO0kzR6CY FwQBuq0OXnuBLcHohtJO+pURZDA08c8YhyacPZz+s5MnNccdxEcJYWwZXK9wkLwYRnR2 XDDUHr8CAZ9H656zTzCUzbUbyvfhMF3ynLdHFi70zDZN7p9gZjRCdYGUir01Sw1qQ0Ey tHzQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1742193248; x=1742798048; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=x6WrkgLRJl8lUocm1cnTSBp25XPoazkPi4ONuRt/VwE=; b=I5hY0j8+N75mjlpQMyxAfbEmUGnUZ2Z5FZZf94rPbQWljaiDl3sY5UPEApAIiSC+1J C0hPNtZOdW3B5ykpIplFmonnCzy4iPXQy+2uKNYzJzG5eUax0Je+bb58XkMnjN2JHfFW gzyAK7ViRJX8rvLCm2cARVgEqWCgEMteIWfcH9cbfCnydV8YkxKD8LXs8alSg7Jc7CGC U9TU/sVSee9pD1mSL4xACubN3ydg8UI74oJIN9Pu1WRLrDMLpDar7wgbCKK6ao2XPJpM N1HIXTGI9/qIchkdiJTJAZHGekdSbkc1MkSWnYTWURwAZEknCegUJpik0J0tB5jQMlXr cxAA== X-Forwarded-Encrypted: i=1; AJvYcCVnw7lE9MQ0s/HkLQl0C1dp8Wwlj4GtIot+Il4O3PYmtdAkRQCO0h/GcveMpy4ls+PLvKFWi1o646w=@vger.kernel.org, AJvYcCVv077EJMSeZE10cVmHyX1vbGEAlAc2hpeoEx0FWK0vqHvCjIEkLQhjaF4nlVa84KLGNI9QBiXrevQUPMOaOU8LaE1kcw==@vger.kernel.org, AJvYcCXGA6zipQO62CMQ3Mj/HexKNyPwEK0cuwEz4gulYpaIJDR+VhImLnNbTRnTVSd6Y4tc8S+SoAzNHmmNNgr1@vger.kernel.org X-Gm-Message-State: AOJu0Yyx8YBCxXfVQVziDpYk6MU0l6p7NFYVu5bpkBJkn5nS/ElKG8Kl 8GyQvJ64oHCa1JIuR5E85dHHvAOU3Io2PpeB/N4pPW2nAfIWv3Cu X-Gm-Gg: ASbGnctrjAKWa2hwruI0Afa3uCFsczQbTi2e0nub2YrHOY3N6zCOJkDPTtv96C40CxC 1bHuBHX/jyucJ1/mgAaY9FpJ5tRiY+OUhjRK1aRNlD6Lisqwyxmy8xicLJDwVK5glUYGdJked+E SJLcm9HzwNtv3G1+rV8Mh3f7/WIIHUfzIi0Rmz9KrvYYmZoV+4MtYE4+EB7rYqh7dz3Wvw7Yt13 Q1m38E4t2Hm+L5pod0ARUQ38glL2izsbsZvevefyOOFyMS7VBYh/lg1TE/eYcWj/7BSUHrg04/r fw2Kz0gOiP4PJ484DhG3Qr5Aivau93ioLnk9L9cSGeqW1aZsmYCnZdcAovQQgjddLX7WihhztaQ hf01m/4L2ezdwHwbuoYsZSypR8NnBvwoeMSkb7CY3SLY= X-Google-Smtp-Source: AGHT+IE2lkPqce+QOvuGUXkGhja68FmMMAiKYTQ66fN1piqW0Z2kK92kjuSKq/0EjyQVkFi7+P3kFw== X-Received: by 2002:a17:90a:d407:b0:2fe:9fd4:58f4 with SMTP id 98e67ed59e1d1-30151c9f4d2mr15063502a91.16.1742193247835; Sun, 16 Mar 2025 23:34:07 -0700 (PDT) Received: from localhost.localdomain (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-30153afee51sm5186555a91.28.2025.03.16.23.34.06 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 16 Mar 2025 23:34:07 -0700 (PDT) From: "Derek J. Clark" To: Hans de Goede , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Cc: Armin Wolf , Jonathan Corbet , Mario Limonciello , Luke Jones , Xino Ni , Zhixin Zhang , Mia Shao , Mark Pearson , "Pierre-Loup A . Griffais" , "Cody T . -H . Chiu" , John Martens , "Derek J . Clark" , platform-driver-x86@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v4 4/4] platform/x86: Add Lenovo Gamezone WMI Driver Date: Sun, 16 Mar 2025 23:33:35 -0700 Message-ID: <20250317063337.352966-9-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20250317063337.352966-1-derekjohn.clark@gmail.com> References: <20250317063337.352966-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds lenovo-wmi-gamezone driver which provides the Lenovo Gamezone WMI interface that comes on Lenovo "Gaming Series" hardware. Provides ACPI platform profiles over WMI. Signed-off-by: Derek J. Clark Suggested-by: Mario Limonciello --- v4: - Add notifier blocks for the Events and Other Mode drivers. - Remove notifier block chain head and all reference to Thermal Mode Event GUID. - Add header for Gamezone specific structs and functions. - Various fixes from review. v3: - Use notifier chain to report platform profile changes to any subscribed drivers. - Adds THERMAL_MODE_EVENT GUID and .notify function to trigger notifier chain. - Adds support for Extreme Mode profile on supported hardware, as well as a DMI quirk table for some devices that report extreme mode version support but so not have it fully implemented. - Update to include recent changes to platform-profile. v2: - Use devm_kmalloc to ensure driver can be instanced, remove global reference. - Ensure reverse Christmas tree for all variable declarations. - Remove extra whitespace. - Use guard(mutex) in all mutex instances, global mutex. - Use pr_fmt instead of adding the driver name to each pr_err. - Remove noisy pr_info usage. - Rename gamezone_wmi to lenovo_wmi_gz_priv and gz_wmi to priv. - Remove GZ_WMI symbol exporting. --- MAINTAINERS | 2 + drivers/platform/x86/Kconfig | 13 + drivers/platform/x86/Makefile | 1 + drivers/platform/x86/lenovo-wmi-gamezone.c | 380 +++++++++++++++++++++ drivers/platform/x86/lenovo-wmi-gamezone.h | 18 + 5 files changed, 414 insertions(+) create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.h diff --git a/MAINTAINERS b/MAINTAINERS index 87daee6075ad..0416afd997a0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13168,6 +13168,8 @@ F: drivers/platform/x86/lenovo-wmi-capdata01.c F: drivers/platform/x86/lenovo-wmi-capdata01.h F: drivers/platform/x86/lenovo-wmi-events.c F: drivers/platform/x86/lenovo-wmi-events.h +F: drivers/platform/x86/lenovo-wmi-gamezone.c +F: drivers/platform/x86/lenovo-wmi-gamezone.h F: drivers/platform/x86/lenovo-wmi-helpers.c F: drivers/platform/x86/lenovo-wmi-helpers.h F: drivers/platform/x86/lenovo-wmi-other.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index fc47604e37f7..ecf3246c8fda 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -467,6 +467,19 @@ config LENOVO_WMI_HELPERS tristate depends on ACPI_WMI =20 +config LENOVO_WMI_GAMEZONE + tristate "Lenovo GameZone WMI Driver" + depends on ACPI_WMI + select ACPI_PLATFORM_PROFILE + select LENOVO_WMI_EVENTS + select LENOVO_WMI_HELPERS + help + Say Y here if you have a WMI aware Lenovo Legion device and would like = to use the + platform-profile firmware interface to manage power usage. + + To compile this driver as a module, choose M here: the module will + be called lenovo-wmi-gamezone. + config LENOVO_WMI_DATA01 tristate depends on ACPI_WMI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index c6ce3c8594b1..f3e64926a96b 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -71,6 +71,7 @@ obj-$(CONFIG_YT2_1380) +=3D lenovo-yoga-tab2-pro-1380-fa= stcharger.o obj-$(CONFIG_LENOVO_WMI_CAMERA) +=3D lenovo-wmi-camera.o obj-$(CONFIG_LENOVO_WMI_DATA01) +=3D lenovo-wmi-capdata01.o obj-$(CONFIG_LENOVO_WMI_EVENTS) +=3D lenovo-wmi-events.o +obj-$(CONFIG_LENOVO_WMI_GAMEZONE) +=3D lenovo-wmi-gamezone.o obj-$(CONFIG_LENOVO_WMI_HELPERS) +=3D lenovo-wmi-helpers.o obj-$(CONFIG_LENOVO_WMI_TUNING) +=3D lenovo-wmi-other.o =20 diff --git a/drivers/platform/x86/lenovo-wmi-gamezone.c b/drivers/platform/= x86/lenovo-wmi-gamezone.c new file mode 100644 index 000000000000..9d453a836227 --- /dev/null +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Lenovo GameZone WMI interface driver. The GameZone WMI interface provid= es + * platform profile and fan curve settings for devices that fall under the + * "Gaming Series" of Lenovo Legion devices. + * + * Copyright(C) 2025 Derek J. Clark + */ + +#include +#include +#include +#include +#include +#include + +#include "lenovo-wmi-events.h" +#include "lenovo-wmi-gamezone.h" +#include "lenovo-wmi-helpers.h" +#include "lenovo-wmi-other.h" + +/* Interface GUIDs */ +#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0" + +/* Method IDs */ +#define WMI_METHOD_ID_SMARTFAN_SUPP 43 /* IsSupportSmartFan */ +#define WMI_METHOD_ID_SMARTFAN_SET 44 /* SetSmartFanMode */ +#define WMI_METHOD_ID_SMARTFAN_GET 45 /* GetSmartFanMode */ + +static BLOCKING_NOTIFIER_HEAD(gz_chain_head); + +struct lwmi_event_priv { + enum thermal_mode current_mode; + struct wmi_device *wdev; + bool extreme_supported; + struct device *ppdev; /*platform profile device */ + struct notifier_block event_nb; + struct notifier_block mode_nb; +}; + +struct quirk_entry { + bool extreme_supported; +}; + +static struct quirk_entry quirk_no_extreme_bug =3D { + .extreme_supported =3D false, +}; + +/* Notifier Methods */ +/* + * lwmi_gz_mode_call() - Call method for lenovo-wmi-other notifier + * block call chain. For THERMAL_MODE_EVENT, returns current_mode + * + * @nb: The notifier_block registered to lenovo-wmi-other + * @cmd: The event triggered by lenovo-wmi-other + * @data: The data to be returned by the event. + * + * Returns: notifier_block status. + */ +static int lwmi_gz_mode_call(struct notifier_block *nb, unsigned long cmd, + void *data) +{ + struct lwmi_event_priv *priv; + + priv =3D container_of(nb, struct lwmi_event_priv, mode_nb); + if (!priv) + return NOTIFY_BAD; + + switch (cmd) { + case THERMAL_MODE_EVENT: + *(enum thermal_mode *)data =3D priv->current_mode; + break; + default: + return NOTIFY_DONE; + } + + return NOTIFY_OK; +} + +/* + * lwmi_gz_event_call() - Call method for lenovo-wmi-events notifier + * block call chain. For THERMAL_MODE_EVENT, sets current_mode and + * notifies platform_profile of a change. + * + * @nb: The notifier_block registered to lenovo-wmi-events + * @cmd: The event triggered by lenovo-wmi-events + * @data: The data to be updated by the event. + * + * Returns: notifier_block status. + */ +static int lwmi_gz_event_call(struct notifier_block *nb, unsigned long cmd, + void *data) +{ + struct lwmi_event_priv *priv; + + priv =3D container_of(nb, struct lwmi_event_priv, event_nb); + if (!priv) + return NOTIFY_BAD; + + switch (cmd) { + case THERMAL_MODE_EVENT: + priv->current_mode =3D *((enum thermal_mode *)data); + platform_profile_notify(&priv->wdev->dev); + break; + default: + return NOTIFY_DONE; + } + + return NOTIFY_OK; +} + +/* Platform Profile Methods & Setup */ +/* + * lwmi_gz_platform_profile_supported() - Gets the version of the WMI + * interface to determine the support level. + * + * @wdev: The Gamezone WMI device. + * @supported: Pointer to return the support level with. + * + * Returns: 0, or an error. + */ +static int lwmi_gz_platform_profile_supported(struct wmi_device *wdev, + int *supported) +{ + return lwmi_dev_evaluate_method(wdev, 0x0, WMI_METHOD_ID_SMARTFAN_SUPP, + 0, 0, supported); +} + +/* + * lwmi_gz_thermal_mode_get() - Gets the currently set thermal mode from + * the Gamezone WMI interface. + * + * @wdev: The Gamezone WMI device. + * @mode: Pointer to return the thermal mode with. + * + * Returns: 0, or an error. + */ +static int lwmi_gz_thermal_mode_get(struct wmi_device *wdev, + enum thermal_mode *mode) +{ + return lwmi_dev_evaluate_method(wdev, 0x0, WMI_METHOD_ID_SMARTFAN_GET, + 0, 0, mode); +} + +static int lwmi_gz_profile_get(struct device *dev, + enum platform_profile_option *profile) +{ + struct lwmi_event_priv *priv =3D dev_get_drvdata(dev); + enum thermal_mode mode; + int ret; + + ret =3D lwmi_gz_thermal_mode_get(priv->wdev, &mode); + if (ret) + return ret; + + switch (mode) { + case SMARTFAN_MODE_QUIET: + *profile =3D PLATFORM_PROFILE_LOW_POWER; + break; + case SMARTFAN_MODE_BALANCED: + *profile =3D PLATFORM_PROFILE_BALANCED; + break; + case SMARTFAN_MODE_PERFORMANCE: + if (priv->extreme_supported) { + *profile =3D PLATFORM_PROFILE_BALANCED_PERFORMANCE; + break; + } + *profile =3D PLATFORM_PROFILE_PERFORMANCE; + break; + case SMARTFAN_MODE_EXTREME: + *profile =3D PLATFORM_PROFILE_PERFORMANCE; + break; + case SMARTFAN_MODE_CUSTOM: + *profile =3D PLATFORM_PROFILE_CUSTOM; + break; + default: + return -EINVAL; + } + + priv->current_mode =3D mode; + + return 0; +} + +static int lwmi_gz_profile_set(struct device *dev, + enum platform_profile_option profile) +{ + struct lwmi_event_priv *priv =3D dev_get_drvdata(dev); + struct wmi_method_args_32 args; + enum thermal_mode mode; + int ret; + + switch (profile) { + case PLATFORM_PROFILE_LOW_POWER: + mode =3D SMARTFAN_MODE_QUIET; + break; + case PLATFORM_PROFILE_BALANCED: + mode =3D SMARTFAN_MODE_BALANCED; + break; + case PLATFORM_PROFILE_BALANCED_PERFORMANCE: + mode =3D SMARTFAN_MODE_PERFORMANCE; + break; + case PLATFORM_PROFILE_PERFORMANCE: + if (priv->extreme_supported) { + mode =3D SMARTFAN_MODE_EXTREME; + break; + } + mode =3D SMARTFAN_MODE_PERFORMANCE; + break; + case PLATFORM_PROFILE_CUSTOM: + mode =3D SMARTFAN_MODE_CUSTOM; + break; + default: + return -EOPNOTSUPP; + } + + args.arg0 =3D mode; + + ret =3D lwmi_dev_evaluate_method(priv->wdev, 0x0, + WMI_METHOD_ID_SMARTFAN_SET, + (unsigned char *)&args, sizeof(args), + NULL); + if (ret) + return ret; + + priv->current_mode =3D mode; + + return 0; +} + +static const struct dmi_system_id fwbug_list[] =3D { + { + .ident =3D "Legion Go 8APU1", + .matches =3D { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"), + }, + .driver_data =3D &quirk_no_extreme_bug, + }, + { + .ident =3D "Legion Go S 8ARP1", + .matches =3D { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8ARP1"), + }, + .driver_data =3D &quirk_no_extreme_bug, + }, + { + .ident =3D "Legion Go S 8APU1", + .matches =3D { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8APU1"), + }, + .driver_data =3D &quirk_no_extreme_bug, + }, + {}, + +}; + +/* + * extreme_supported() - Evaluate if a device supports extreme thermal mod= e. + * For devices that have a profile_support_ver of 6 or greater a DMI check + * is done. Some devices report a version that supports extreme mode but + * have an incomplete entry in the BIOS. To ensure this cannot be set, they + * are quirked to prevent assignment. + * + * @profile_support_ver: Version of WMI interface provided by + * lwmi_gz_platform_profile_supported. + * + * Returns: bool + */ +static bool extreme_supported(int profile_support_ver) +{ + const struct dmi_system_id *dmi_id; + struct quirk_entry *quirks; + + if (profile_support_ver < 6) + return false; + + dmi_id =3D dmi_first_match(fwbug_list); + if (!dmi_id) + return true; + + quirks =3D dmi_id->driver_data; + return quirks->extreme_supported; +} + +static int lwmi_platform_profile_probe(void *drvdata, unsigned long *choic= es) +{ + struct lwmi_event_priv *priv =3D drvdata; + int profile_support_ver; + int ret; + + ret =3D lwmi_gz_platform_profile_supported(priv->wdev, + &profile_support_ver); + if (ret) + return ret; + + if (profile_support_ver < 1) + return -ENODEV; + + priv->extreme_supported =3D extreme_supported(profile_support_ver); + + set_bit(PLATFORM_PROFILE_LOW_POWER, choices); + set_bit(PLATFORM_PROFILE_BALANCED, choices); + set_bit(PLATFORM_PROFILE_PERFORMANCE, choices); + set_bit(PLATFORM_PROFILE_CUSTOM, choices); + + if (priv->extreme_supported) + set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices); + + return 0; +} + +static const struct platform_profile_ops lwmi_gz_platform_profile_ops =3D { + .probe =3D lwmi_platform_profile_probe, + .profile_get =3D lwmi_gz_profile_get, + .profile_set =3D lwmi_gz_profile_set, +}; + +/* Driver Methods */ +static int lwmi_gz_probe(struct wmi_device *wdev, const void *context) +{ + struct lwmi_event_priv *priv; + int ret; + + priv =3D devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->event_nb.notifier_call =3D lwmi_gz_event_call; + ret =3D devm_lwmi_events_register_notifier(&wdev->dev, &priv->event_nb); + if (ret) + return ret; + + priv->mode_nb.notifier_call =3D lwmi_gz_mode_call; + ret =3D devm_lwmi_om_register_notifier(&wdev->dev, &priv->mode_nb); + if (ret) + return ret; + + priv->wdev =3D wdev; + dev_set_drvdata(&wdev->dev, priv); + + priv->ppdev =3D platform_profile_register(&wdev->dev, + "lenovo-wmi-gamezone", priv, + &lwmi_gz_platform_profile_ops); + + if (IS_ERR(priv->ppdev)) + return -ENODEV; + + ret =3D lwmi_gz_thermal_mode_get(wdev, &priv->current_mode); + if (ret) + return ret; + + return 0; +} + +static const struct wmi_device_id lwmi_gz_id_table[] =3D { { LENOVO_GAMEZO= NE_GUID, + NULL }, + {} }; + +static struct wmi_driver lwmi_gz_driver =3D { + .driver =3D { + .name =3D "lenovo_wmi_gamezone", + .probe_type =3D PROBE_PREFER_ASYNCHRONOUS, + }, + .id_table =3D lwmi_gz_id_table, + .probe =3D lwmi_gz_probe, + .no_singleton =3D true, +}; + +module_wmi_driver(lwmi_gz_driver); + +MODULE_IMPORT_NS("LENOVO_WMI_EVENTS"); +MODULE_IMPORT_NS("LENOVO_WMI_HELPERS"); +MODULE_IMPORT_NS("LENOVO_WMI_OTHER"); +MODULE_DEVICE_TABLE(wmi, lwmi_gz_id_table); +MODULE_AUTHOR("Derek J. Clark "); +MODULE_DESCRIPTION("Lenovo GameZone WMI Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/lenovo-wmi-gamezone.h b/drivers/platform/= x86/lenovo-wmi-gamezone.h new file mode 100644 index 000000000000..ac536803160b --- /dev/null +++ b/drivers/platform/x86/lenovo-wmi-gamezone.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright(C) 2025 Derek J. Clark + * + */ + +#ifndef _LENOVO_WMI_GAMEZONE_H_ +#define _LENOVO_WMI_GAMEZONE_H_ + +enum thermal_mode { + SMARTFAN_MODE_QUIET =3D 0x01, + SMARTFAN_MODE_BALANCED =3D 0x02, + SMARTFAN_MODE_PERFORMANCE =3D 0x03, + SMARTFAN_MODE_EXTREME =3D 0xE0, /* Ver 6+ */ + SMARTFAN_MODE_CUSTOM =3D 0xFF, +}; + +#endif /* !_LENOVO_WMI_GAMEZONE_H_ */ --=20 2.49.0 From nobody Wed Dec 17 15:33:16 2025 Received: from mail-pj1-f44.google.com (mail-pj1-f44.google.com [209.85.216.44]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3A59621B9C1; Mon, 17 Mar 2025 06:34:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.44 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742193252; cv=none; b=mVZkuMS47eAZfbSusHXl18dNv6UEwP4/Dl7q7jYO5FY0BfDUmbqZoNQYEWEljlY0AOdAcAEBAwsGf4LfsJPGT6FPZXEipQMP0mss8KTiBC4b7yXnYCTY8Vp7dATypF/BqNtyaP9Div4HBRtLNsbGzapGbWosq1Pn7Uz6WHxuiiY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742193252; c=relaxed/simple; bh=0UVtzZ1P789Jk+XBpcwEL5d/OtfLWEuL86ZyNHBBe7w=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=fgcdn3c12DaH8COFP8D9cGYUk6e5GddsKYAhpO3724oHSrqYdKNN+2NZW3aamHaoi8EoM3HTjJ9Ih9MQr1SxWIGea2Djp3TeTq1a/69E1SdrhoDdSusVKpfqoNrq9pIl3uMYkIyqIisp80A8Wt1GfLY2k2N6EJguXF0KxDs/yYg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=KDzS4D9F; arc=none smtp.client-ip=209.85.216.44 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="KDzS4D9F" Received: by mail-pj1-f44.google.com with SMTP id 98e67ed59e1d1-3014ae35534so1921958a91.0; Sun, 16 Mar 2025 23:34:09 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1742193249; x=1742798049; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=Wpa2qWthIuAYVa7Vcui8zzT6mcuzIqqT3GKlMttqhmQ=; b=KDzS4D9Fv2JiQ3FsQzUts30Y07hQYhhqJbBSuUItAd01CZyjtDMacAEvaHcSzYmB2G N/pcQ1jMNwpSVr8tp808NcSXPZlBzq+3jqyfm5AFChWx3ey9zfF7dLas7i72VVpkyRF1 yEEsmN2VkP/WJXegADUSLKrorSa2hJ+SBrbiXCoySR/Qq3vUtYJ0K7XgPLBzuQJYSqW7 xw3yPy2b1/kfhNhygqrg0Ca4P81WzGkpPuZp6GPZGTXpB2mnGGYT0i9B35mZ6NTv8rXP JIajam+8J38KOZDWGxDCNqKK8wPm0u8ME9hQgiczxBZ3wXwKt/wDc+UE5RID6Sf5v1rj jpfg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1742193249; x=1742798049; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Wpa2qWthIuAYVa7Vcui8zzT6mcuzIqqT3GKlMttqhmQ=; b=ZCLi16eYjF1AyHORrmmV+caKu+cloFjxoZcRlzBTDG27TSM7bsDU63i2F59O1f1ErK UA4nu7o96BUfk8M9MIElca9pYnepC6Iq0DZG6FZ9tjFUrze/uVIDn/ivou8eiYUeonsD 1pBDRKHT2yqokHwSns6E3gl2HZvwn+gEOWBDcDS5wLCpYL+yXutLxAS0lbNJp43lQKOx 8eZgdV3nzJPMsTf8XxizKPi6Yy5sTtEozJUbyjirVs8IfVSXjephLLsueQun1yRdOI+P wUqYrZ+Rnnn2iZo3oPORAzRGOP/BceGdUJiMeAxLcMzlRn19tHR5Jv9Y+HUbmKqqdpCk A1Ng== X-Forwarded-Encrypted: i=1; AJvYcCU27TFZKGDGBJYS18MTTCAUJSf41ltWZmAEZwAhv3kINS2fOKZmcLmlV+yE1VIWI6aQ6Kc2Vm0uhFfD0QcF+XrFkhNx8g==@vger.kernel.org, AJvYcCVLFV2PEqv7Nx1F8BFXmFN7q4z+7EY7DqIziI3uQvPy4awSF0sXtvXc7YmETaW+gurB6yofDqrvX4W3OXf4@vger.kernel.org, AJvYcCWZkahTyhjIF9P+z1JK8CSRVjZmyiXVJS/gOrlT5Y7p/PGtmA5FzwhTKIUMEKrDHAf+3Mp1ubyj1VA=@vger.kernel.org X-Gm-Message-State: AOJu0YzYoX5zBetdzZ2+65MLV02ydlStSc+PeAI+NnDgbNCsybGjwg1/ pfF7DLNjC82ckxVkzUuufeEnJXGGqxB9f1F/8GcX263RG4e4pejj X-Gm-Gg: ASbGncv0ZjttTyRP0OTJ5UGk18Gyvnjg0c8v+VTNBL/7wygU5kq27NFQIbCmFo1Vyl0 KkLThr6AJzawXdQrmWmOGi4u8BzUEF3jwkUrJni1H+oh4iRi5oC8WbrI9GeLBNlTg9yGJw+49p4 2fiEz8eNmqXJXo1eKAuLGXlD0mNv8ehMD3+QhbtJiySEzf50NyQ93+yx2KlyTvqsqV+hI4HNkb9 hyoByCBffOufxPSZX0kbN0L7pAyop8ovkkK7txgr7iYntM5yw2LWNx/eCFRW09yRzjvz2Bv07Do VDyDMqS7yu3ULLp73iYYjWY+/WSI+ooUnnC0NRrmfVrWWaAnNk0PxEj06zaOjVx4GHP3UoC/wNo GB6a7VuqF8qDQHGEonWILRx0W9d93AwVj4iH9PB9ID7w= X-Google-Smtp-Source: AGHT+IEP9UK8qz95av7+1IkboBbT2aK28GWHliW9WEDG/D78LxJkXDZK512yQsquLsAufSok8r8IDA== X-Received: by 2002:a17:90b:1b45:b0:2ea:37b4:5373 with SMTP id 98e67ed59e1d1-30151c9d989mr14748202a91.10.1742193249320; Sun, 16 Mar 2025 23:34:09 -0700 (PDT) Received: from localhost.localdomain (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-30153afee51sm5186555a91.28.2025.03.16.23.34.08 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 16 Mar 2025 23:34:09 -0700 (PDT) From: "Derek J. Clark" To: Hans de Goede , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Cc: Armin Wolf , Jonathan Corbet , Mario Limonciello , Luke Jones , Xino Ni , Zhixin Zhang , Mia Shao , Mark Pearson , "Pierre-Loup A . Griffais" , "Cody T . -H . Chiu" , John Martens , "Derek J . Clark" , platform-driver-x86@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v4 5/6] platform/x86: Add Lenovo Other Mode WMI Driver Date: Sun, 16 Mar 2025 23:33:36 -0700 Message-ID: <20250317063337.352966-10-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20250317063337.352966-1-derekjohn.clark@gmail.com> References: <20250317063337.352966-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds lenovo-wmi-other driver which provides the Lenovo "Other Mode" WMI interface that comes on some Lenovo "Gaming Series" hardware. Provides a firmware-attributes class which enables the use of tunable knobs for SPL, SPPT, and FPPT. Signed-off-by: Derek J. Clark Suggested-by: Mario Limonciello --- v4: - Treat Other Mode as a notifier chain head, use the notifier chain to get the current mode from Gamezone. - Add header file for Other Mode specific structs and finctions. - Use component master bind to cache the capdata01 array locally. - Drop all reference to external driver private data structs. - Various fixes from review. v3: - Add notifier block and store result for getting the Gamezone interface profile changes. - Add driver as master component of capdata01 driver. - Use FIELD_PREP where appropriate. - Move macros and associated functions out of lemovo-wmi.h that are only used by this driver. v2: - Use devm_kmalloc to ensure driver can be instanced, remove global reference. - Ensure reverse Christmas tree for all variable declarations. - Remove extra whitespace. - Use guard(mutex) in all mutex instances, global mutex. - Use pr_fmt instead of adding the driver name to each pr_err. - Remove noisy pr_info usage. - Rename other_method_wmi to lenovo_wmi_om_priv and om_wmi to priv. - Use list to get the lenovo_wmi_om_priv instance in some macro called functions as the data provided by the macros that use it doesn't pass a member of the struct for use in container_of. - Do not rely on GameZone interface to grab the current fan mode. --- MAINTAINERS | 2 + drivers/platform/x86/Kconfig | 15 + drivers/platform/x86/Makefile | 1 + drivers/platform/x86/lenovo-wmi-other.c | 626 ++++++++++++++++++++++++ drivers/platform/x86/lenovo-wmi-other.h | 19 + 5 files changed, 663 insertions(+) create mode 100644 drivers/platform/x86/lenovo-wmi-other.c create mode 100644 drivers/platform/x86/lenovo-wmi-other.h diff --git a/MAINTAINERS b/MAINTAINERS index 56ead241a053..87daee6075ad 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13170,6 +13170,8 @@ F: drivers/platform/x86/lenovo-wmi-events.c F: drivers/platform/x86/lenovo-wmi-events.h F: drivers/platform/x86/lenovo-wmi-helpers.c F: drivers/platform/x86/lenovo-wmi-helpers.h +F: drivers/platform/x86/lenovo-wmi-other.c +F: drivers/platform/x86/lenovo-wmi-other.h =20 LENOVO WMI HOTKEY UTILITIES DRIVER M: Jackie Dong diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 64663667f0cb..fc47604e37f7 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -471,6 +471,21 @@ config LENOVO_WMI_DATA01 tristate depends on ACPI_WMI =20 +config LENOVO_WMI_TUNING + tristate "Lenovo Other Mode WMI Driver" + depends on ACPI_WMI + select FW_ATTR_CLASS + select LENOVO_WMI_DATA01 + select LENOVO_WMI_EVENTS + select LENOVO_WMI_HELPERS + help + Say Y here if you have a WMI aware Lenovo Legion device and would like = to use the + firmware_attributes API to control various tunable settings typically e= xposed by + Lenovo software in Windows. + + To compile this driver as a module, choose M here: the module will + be called lenovo-wmi-other. + config IDEAPAD_LAPTOP tristate "Lenovo IdeaPad Laptop Extras" depends on ACPI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 7a35c77221b7..c6ce3c8594b1 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -72,6 +72,7 @@ obj-$(CONFIG_LENOVO_WMI_CAMERA) +=3D lenovo-wmi-camera.o obj-$(CONFIG_LENOVO_WMI_DATA01) +=3D lenovo-wmi-capdata01.o obj-$(CONFIG_LENOVO_WMI_EVENTS) +=3D lenovo-wmi-events.o obj-$(CONFIG_LENOVO_WMI_HELPERS) +=3D lenovo-wmi-helpers.o +obj-$(CONFIG_LENOVO_WMI_TUNING) +=3D lenovo-wmi-other.o =20 # Intel obj-y +=3D intel/ diff --git a/drivers/platform/x86/lenovo-wmi-other.c b/drivers/platform/x86= /lenovo-wmi-other.c new file mode 100644 index 000000000000..b517e45338e0 --- /dev/null +++ b/drivers/platform/x86/lenovo-wmi-other.c @@ -0,0 +1,626 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Lenovo Other Mode WMI interface driver. This driver uses the fw_attribu= tes + * class to expose the various WMI functions provided by the "Other Mode" = WMI + * interface. This enables CPU and GPU power limit as well as various other + * attributes for devices that fall under the "Gaming Series" of Lenovo la= ptop + * devices. Each attribute exposed by the "Other Mode"" interface has a + * corresponding LENOVO_CAPABILITY_DATA_01 struct that allows the driver to + * probe details about the attribute such as set/get support, step, min, m= ax, + * and default value. Each attibute has multiple pages, one for each of the + * fan profiles managed by the Gamezone interface. + * + * These attributes typically don't fit anywhere else in the sysfs and are= set + * in Windows using one of Lenovo's multiple user applications. + * + * Copyright(C) 2025 Derek J. Clark + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lenovo-wmi-capdata01.h" +#include "lenovo-wmi-events.h" +#include "lenovo-wmi-gamezone.h" +#include "lenovo-wmi-helpers.h" +#include "lenovo-wmi-other.h" +#include "firmware_attributes_class.h" + +/* Interface GUIDs */ +#define LENOVO_OTHER_METHOD_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B" + +/* Device IDs */ +#define WMI_DEVICE_ID_CPU 0x01 + +/* WMI_DEVICE_ID_CPU feature IDs */ +#define WMI_FEATURE_ID_CPU_SPPT 0x01 /* Short Term Power Limit */ +#define WMI_FEATURE_ID_CPU_FPPT 0x03 /* Long Term Power Limit */ +#define WMI_FEATURE_ID_CPU_SPL 0x02 /* Peak Power Limit */ + +/* Type IDs*/ +#define WMI_TYPE_ID_NONE 0x00 + +/* Method IDs */ +#define WMI_FEATURE_VALUE_GET 17 /* Other Mode Getter */ +#define WMI_FEATURE_VALUE_SET 18 /* Other Mode Setter */ + +/* Attribute ID bitmasks */ +#define ATTR_DEV_ID_MASK GENMASK(31, 24) +#define ATTR_FEAT_ID_MASK GENMASK(23, 16) +#define ATTR_MODE_ID_MASK GENMASK(15, 8) +#define ATTR_TYPE_ID_MASK GENMASK(7, 0) + +static BLOCKING_NOTIFIER_HEAD(om_chain_head); + +enum attribute_property { + DEFAULT_VAL, + MAX_VAL, + MIN_VAL, + STEP_VAL, + SUPPORTED, +}; + +struct lwmi_om_priv { + struct blocking_notifier_head nhead; + struct component_master_ops *ops; + struct cd01_list cd01_list; + struct device *fw_attr_dev; + struct kset *fw_attr_kset; + struct notifier_block nb; + struct wmi_device *wdev; + struct ida ida; + int ida_id; +}; + +/* Tunable attribute that uses LENOVO_CAPABILITY_DATA_01 */ +struct tunable_attr_01 { + u32 type_id; + u32 device_id; + u32 feature_id; + u32 store_value; + struct device *dev; + struct capdata01 *capdata; +}; + +/* Tunable Attributes */ +struct tunable_attr_01 ppt_pl1_spl =3D { .device_id =3D WMI_DEVICE_ID_CPU, + .feature_id =3D WMI_FEATURE_ID_CPU_SPL, + .type_id =3D WMI_TYPE_ID_NONE }; +struct tunable_attr_01 ppt_pl2_sppt =3D { .device_id =3D WMI_DEVICE_ID_CPU, + .feature_id =3D WMI_FEATURE_ID_CPU_SPPT, + .type_id =3D WMI_TYPE_ID_NONE }; +struct tunable_attr_01 ppt_pl3_fppt =3D { .device_id =3D WMI_DEVICE_ID_CPU, + .feature_id =3D WMI_FEATURE_ID_CPU_FPPT, + .type_id =3D WMI_TYPE_ID_NONE }; + +struct capdata01_attr_group { + const struct attribute_group *attr_group; + struct tunable_attr_01 *tunable_attr; +}; + +#define FW_ATTR_FOLDER "lenovo-wmi-other" + +/* Notifier Methods */ +int lwmi_om_register_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&om_chain_head, nb); +} +EXPORT_SYMBOL_NS_GPL(lwmi_om_register_notifier, "LENOVO_WMI_OTHER"); + +int lwmi_om_unregister_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&om_chain_head, nb); +} +EXPORT_SYMBOL_NS_GPL(lwmi_om_unregister_notifier, "LENOVO_WMI_OTHER"); + +static void devm_lwmi_om_unregister_notifier(void *data) +{ + struct notifier_block *nb =3D data; + + lwmi_om_unregister_notifier(nb); +} + +int devm_lwmi_om_register_notifier(struct device *dev, + struct notifier_block *nb) +{ + int ret; + + ret =3D lwmi_om_register_notifier(nb); + if (ret < 0) + return ret; + + return devm_add_action_or_reset(dev, devm_lwmi_om_unregister_notifier, + nb); +} +EXPORT_SYMBOL_NS_GPL(devm_lwmi_om_register_notifier, "LENOVO_WMI_OTHER"); + +static int lwmi_om_notifier_call(enum thermal_mode *mode) +{ + int ret; + + ret =3D blocking_notifier_call_chain(&om_chain_head, THERMAL_MODE_EVENT, + mode); + + if (ret !=3D NOTIFY_OK) + return -EINVAL; + + if (*mode < SMARTFAN_MODE_QUIET || *mode > SMARTFAN_MODE_CUSTOM) + return -EINVAL; + + return 0; +} + +/* Attribute Methods */ +/* + * int_type_show() - Emit the data type for an integer attribute + * @kobj: Pointer to the driver object. + * @kobj_attribute: Pointer to the attribute calling this function. + * @buf: The buffer to write to. + * + * Returns: Number of characters written to buf. + */ +static ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *= kattr, + char *buf) +{ + return sysfs_emit(buf, "integer\n"); +} + +/* + * attr_capdata01_get - Get the data of the specified attribute + * from lwmi_om->cd01. + * @tunable_attr: The attribute to be populated. + * + * Returns: Either a pointer to capability data, or NULL. + */ +static struct capdata01 * +attr_capdata01_get_data(struct lwmi_om_priv *priv, + struct tunable_attr_01 *tunable_attr, + enum thermal_mode mode) +{ + u32 attribute_id =3D + FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) | + FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) | + FIELD_PREP(ATTR_MODE_ID_MASK, mode) | + FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id); + int idx; + + for (idx =3D 0; idx < priv->cd01_list.count; idx++) { + if (!priv->cd01_list.data[idx]) + continue; + + if (priv->cd01_list.data[idx]->id !=3D attribute_id) + continue; + return priv->cd01_list.data[idx]; + } + return NULL; +} + +/** + * attr_capdata01_show() - Get the value of the specified attribute proper= ty + * from LENOVO_CAPABILITY_DATA_01. + * @kobj: Pointer to the driver object. + * @kobj_attribute: Pointer to the attribute calling this function. + * @buf: The buffer to write to. + * @tunable_attr: The attribute to be read. + * @prop: The property of this attribute to be read. + * + * This function is intended to be generic so it can be called from any "_= show" + * attribute which works only with integers. + * + * If the WMI is success, then the sysfs attribute is notified. + * + * Returns: Either number of characters written to buf, or an error. + */ +static ssize_t attr_capdata01_show(struct kobject *kobj, + struct kobj_attribute *kattr, char *buf, + struct tunable_attr_01 *tunable_attr, + enum attribute_property prop) +{ + struct lwmi_om_priv *priv =3D dev_get_drvdata(tunable_attr->dev); + struct capdata01 *capdata; + int value; + + if (!priv) + return -ENODEV; + + capdata =3D attr_capdata01_get_data(priv, tunable_attr, + SMARTFAN_MODE_CUSTOM); + + if (!capdata) + return -ENODEV; + + switch (prop) { + case DEFAULT_VAL: + value =3D capdata->default_value; + break; + case MAX_VAL: + value =3D capdata->max_value; + break; + case MIN_VAL: + value =3D capdata->min_value; + break; + case STEP_VAL: + value =3D capdata->step; + break; + default: + return -EINVAL; + } + return sysfs_emit(buf, "%d\n", value); +} + +/* Simple attribute creation */ + +/* + * att_current_value_store() - Set the current value of the given attribute + * @kobj: Pointer to the driver object. + * @kobj_attribute: Pointer to the attribute calling this function. + * @buf: The buffer to read from, this is parsed to `int` type. + * @count: Required by sysfs attribute macros, pass in from the callee att= r. + * @tunable_attr: The attribute to be stored. + * + * This function is intended to be generic so it can be called from any + * attribute's "current_value_store" which works only with integers. The + * integer to be sent to the WMI method is range checked and an error retu= rned + * if out of range. + * + * If the value is valid and WMI is success, then the sysfs attribute is + * notified. + * + * Returns: Either count, or an error. + */ +static ssize_t attr_current_value_store(struct kobject *kobj, + struct kobj_attribute *kattr, + const char *buf, size_t count, + struct tunable_attr_01 *tunable_attr) +{ + struct lwmi_om_priv *priv =3D dev_get_drvdata(tunable_attr->dev); + struct wmi_method_args_32 args; + struct capdata01 *capdata; + enum thermal_mode mode; + u32 attribute_id; + u32 value; + int err; + + if (!priv) + return -ENODEV; + + err =3D lwmi_om_notifier_call(&mode); + if (err) + return err; + + if (mode !=3D SMARTFAN_MODE_CUSTOM) + return -EINVAL; + + capdata =3D attr_capdata01_get_data(priv, tunable_attr, mode); + + if (!capdata) + return -ENODEV; + + attribute_id =3D FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) | + FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) | + FIELD_PREP(ATTR_MODE_ID_MASK, mode) | + FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id); + + err =3D kstrtouint(buf, 10, &value); + if (err) + return err; + + if (value < capdata->min_value || value > capdata->max_value) + return -EINVAL; + + args.arg0 =3D attribute_id; + args.arg1 =3D value; + + err =3D lwmi_dev_evaluate_method(priv->wdev, 0x0, WMI_FEATURE_VALUE_SET, + (unsigned char *)&args, sizeof(args), + NULL); + + if (err) + return err; + + tunable_attr->store_value =3D value; + return count; +}; + +/* + * attr_current_value_show() - Get the current value of the given attribute + * @kobj: Pointer to the driver object. + * @kobj_attribute: Pointer to the attribute calling this function. + * @buf: The buffer to write to. + * @tunable_attr: The attribute to be read. + * + * This function is intended to be generic so it can be called from any "_= show" + * attribute which works only with integers. + * + * If the WMI is success, then the sysfs attribute is notified. + * + * Returns: Either number of characters written to buf, or an error. + */ +static ssize_t attr_current_value_show(struct kobject *kobj, + struct kobj_attribute *kattr, char *buf, + struct tunable_attr_01 *tunable_attr) +{ + struct lwmi_om_priv *priv =3D dev_get_drvdata(tunable_attr->dev); + struct wmi_method_args_32 args; + enum thermal_mode mode; + u32 attribute_id; + int retval; + int err; + + if (!priv) + return -ENODEV; + + err =3D lwmi_om_notifier_call(&mode); + if (err) + return err; + + attribute_id =3D FIELD_PREP(ATTR_DEV_ID_MASK, tunable_attr->device_id) | + FIELD_PREP(ATTR_FEAT_ID_MASK, tunable_attr->feature_id) | + FIELD_PREP(ATTR_MODE_ID_MASK, mode) | + FIELD_PREP(ATTR_TYPE_ID_MASK, tunable_attr->type_id); + + args.arg0 =3D attribute_id; + + err =3D lwmi_dev_evaluate_method(priv->wdev, 0x0, WMI_FEATURE_VALUE_GET, + (unsigned char *)&args, sizeof(args), + &retval); + + if (err) + return err; + + return sysfs_emit(buf, "%d\n", retval); +} + +/* Attribute macros */ +#define __LL_ATTR_RO(_func, _name) \ + { \ + .attr =3D { .name =3D __stringify(_name), .mode =3D 0444 }, \ + .show =3D _func##_##_name##_show, \ + } + +#define __LL_ATTR_RO_AS(_name, _show) \ + { \ + .attr =3D { .name =3D __stringify(_name), .mode =3D 0444 }, \ + .show =3D _show, \ + } + +#define __LL_ATTR_RW(_func, _name) \ + __ATTR(_name, 0644, _func##_##_name##_show, _func##_##_name##_store) + +/* Shows a formatted static variable */ +#define __ATTR_SHOW_FMT(_prop, _attrname, _fmt, _val) = \ + static ssize_t _attrname##_##_prop##_show( \ + struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \ + { \ + return sysfs_emit(buf, _fmt, _val); \ + } \ + static struct kobj_attribute attr_##_attrname##_##_prop =3D \ + __LL_ATTR_RO(_attrname, _prop) + +/* Attribute current value read/write */ +#define __LL_TUNABLE_CURRENT_VALUE_CAP01(_attrname) = \ + static ssize_t _attrname##_current_value_store( \ + struct kobject *kobj, struct kobj_attribute *kattr, \ + const char *buf, size_t count) \ + { \ + return attr_current_value_store(kobj, kattr, buf, count, \ + &_attrname); \ + } \ + static ssize_t _attrname##_current_value_show( \ + struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \ + { \ + return attr_current_value_show(kobj, kattr, buf, &_attrname); \ + } \ + static struct kobj_attribute attr_##_attrname##_current_value =3D \ + __LL_ATTR_RW(_attrname, current_value) + +/* Attribute property read only */ +#define __LL_TUNABLE_RO_CAP01(_prop, _attrname, _prop_type) = \ + static ssize_t _attrname##_##_prop##_show( \ + struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \ + { \ + return attr_capdata01_show(kobj, kattr, buf, &_attrname, \ + _prop_type); \ + } \ + static struct kobj_attribute attr_##_attrname##_##_prop =3D \ + __LL_ATTR_RO(_attrname, _prop) + +#define ATTR_GROUP_LL_TUNABLE_CAP01(_attrname, _fsname, _dispname) \ + __LL_TUNABLE_CURRENT_VALUE_CAP01(_attrname); \ + __LL_TUNABLE_RO_CAP01(default_value, _attrname, DEFAULT_VAL); \ + __ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \ + __LL_TUNABLE_RO_CAP01(max_value, _attrname, MAX_VAL); \ + __LL_TUNABLE_RO_CAP01(min_value, _attrname, MIN_VAL); \ + __LL_TUNABLE_RO_CAP01(scalar_increment, _attrname, STEP_VAL); \ + static struct kobj_attribute attr_##_attrname##_type =3D \ + __LL_ATTR_RO_AS(type, int_type_show); \ + static struct attribute *_attrname##_attrs[] =3D { \ + &attr_##_attrname##_current_value.attr, \ + &attr_##_attrname##_default_value.attr, \ + &attr_##_attrname##_display_name.attr, \ + &attr_##_attrname##_max_value.attr, \ + &attr_##_attrname##_min_value.attr, \ + &attr_##_attrname##_scalar_increment.attr, \ + &attr_##_attrname##_type.attr, \ + NULL, \ + }; \ + static const struct attribute_group _attrname##_attr_group =3D { \ + .name =3D _fsname, .attrs =3D _attrname##_attrs \ + } + +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl", + "Set the CPU sustained power limit"); +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt", + "Set the CPU slow package power tracking limit"); +ATTR_GROUP_LL_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt", + "Set the CPU fast package power tracking limit"); + +static struct capdata01_attr_group capdata01_attr_groups[] =3D { + { &ppt_pl1_spl_attr_group, &ppt_pl1_spl }, + { &ppt_pl2_sppt_attr_group, &ppt_pl2_sppt }, + { &ppt_pl3_fppt_attr_group, &ppt_pl3_fppt }, + {}, +}; + +/* + * lwmi_om_fw_attr_add() - Registers all capdata01_attr_groups[] attribute= s as + * firmware_attributes_class members. + * @priv: The Other Mode driver data. + * + * Returns: Either 0, or an error. + */ +static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv) +{ + int err, i; + + ida_init(&priv->ida); + priv->ida_id =3D ida_alloc(&priv->ida, GFP_KERNEL); + if (priv->ida_id < 0) + return priv->ida_id; + + priv->fw_attr_dev =3D device_create(&firmware_attributes_class, NULL, + MKDEV(0, 0), NULL, "%s", + FW_ATTR_FOLDER); + if (IS_ERR(priv->fw_attr_dev)) { + err =3D PTR_ERR(priv->fw_attr_dev); + return err; + } + + priv->fw_attr_kset =3D kset_create_and_add("attributes", NULL, + &priv->fw_attr_dev->kobj); + if (!priv->fw_attr_kset) { + err =3D -ENOMEM; + goto err_destroy_classdev; + } + + for (i =3D 0; i < ARRAY_SIZE(capdata01_attr_groups) - 1; i++) { + err =3D sysfs_create_group(&priv->fw_attr_kset->kobj, + capdata01_attr_groups[i].attr_group); + if (err) { + pr_debug("Failed to create sysfs-group for %s: %d\n", + capdata01_attr_groups[i].attr_group->name, + err); + goto err_remove_groups; + } + capdata01_attr_groups[i].tunable_attr->dev =3D &priv->wdev->dev; + } + return 0; + +err_remove_groups: + ida_free(&priv->ida, priv->ida_id); + while (i-- >=3D 0) { + sysfs_remove_group(&priv->fw_attr_kset->kobj, + capdata01_attr_groups[i].attr_group); + } + kset_unregister(priv->fw_attr_kset); + +err_destroy_classdev: + device_unregister(priv->fw_attr_dev); + return err; +} + +/* + * lwmi_om_fw_attr_remove() - Unregisters all capdata01_attr_groups[] attr= ibutes as + * firmware_attributes_class members. + * @priv: The Other Mode driver data. + * + */ +static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv) +{ + int size =3D ARRAY_SIZE(capdata01_attr_groups); + + while (--size >=3D 0) { + sysfs_remove_group(&priv->fw_attr_kset->kobj, + capdata01_attr_groups[size].attr_group); + } + kset_unregister(priv->fw_attr_kset); + device_unregister(priv->fw_attr_dev); +} + +static int lwmi_om_master_bind(struct device *dev) +{ + struct lwmi_om_priv *priv =3D dev_get_drvdata(dev); + int ret; + + ret =3D component_bind_all(dev, &priv->cd01_list); + if (ret) + return ret; + + return lwmi_om_fw_attr_add(priv); +} + +static void lwmi_om_master_unbind(struct device *dev) +{ + component_unbind_all(dev, NULL); +} + +static const struct component_master_ops lwmi_om_master_ops =3D { + .bind =3D lwmi_om_master_bind, + .unbind =3D lwmi_om_master_unbind, +}; + +static int lwmi_other_probe(struct wmi_device *wdev, const void *context) +{ + struct component_match *master_match =3D NULL; + struct lwmi_om_priv *priv; + + priv =3D devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->wdev =3D wdev; + dev_set_drvdata(&wdev->dev, priv); + + component_match_add(&wdev->dev, &master_match, lwmi_cd01_match, NULL); + if (IS_ERR(master_match)) + return PTR_ERR(master_match); + + return component_master_add_with_match(&wdev->dev, &lwmi_om_master_ops, + master_match); +} + +static void lwmi_other_remove(struct wmi_device *wdev) +{ + struct lwmi_om_priv *priv =3D dev_get_drvdata(&wdev->dev); + + component_master_del(&wdev->dev, &lwmi_om_master_ops); + lwmi_om_fw_attr_remove(priv); + ida_free(&priv->ida, priv->ida_id); +} + +static const struct wmi_device_id lwmi_other_id_table[] =3D { + { LENOVO_OTHER_METHOD_GUID, NULL }, + {} +}; + +static struct wmi_driver lwmi_other_driver =3D { + .driver =3D { + .name =3D "lenovo_wmi_other", + .probe_type =3D PROBE_PREFER_ASYNCHRONOUS, + }, + .id_table =3D lwmi_other_id_table, + .probe =3D lwmi_other_probe, + .remove =3D lwmi_other_remove, + .no_singleton =3D true, +}; + +module_wmi_driver(lwmi_other_driver); + +MODULE_IMPORT_NS("LENOVO_WMI_CD01"); +MODULE_IMPORT_NS("LENOVO_WMI_HELPERS"); +MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table); +MODULE_AUTHOR("Derek J. Clark "); +MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/lenovo-wmi-other.h b/drivers/platform/x86= /lenovo-wmi-other.h new file mode 100644 index 000000000000..9fba35ef1137 --- /dev/null +++ b/drivers/platform/x86/lenovo-wmi-other.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright(C) 2025 Derek J. Clark + * + */ + +#ifndef _LENOVO_WMI_OTHER_H_ +#define _LENOVO_WMI_OTHER_H_ + +#include +#include +#include + +int lwmi_om_register_notifier(struct notifier_block *nb); +int lwmi_om_unregister_notifier(struct notifier_block *nb); +int devm_lwmi_om_register_notifier(struct device *dev, + struct notifier_block *nb); + +#endif /* !_LENOVO_WMI_H_ */ --=20 2.49.0 From nobody Wed Dec 17 15:33:16 2025 Received: from mail-pj1-f46.google.com (mail-pj1-f46.google.com [209.85.216.46]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6DAF2219E8F; Mon, 17 Mar 2025 06:34:11 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.46 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742193253; cv=none; b=Z6SjGmWhywq2vseHeezO2puy++AJrJQQ8/D6xm75Pm/bnL4klKm9yiOdcCAdEaBOsx9ESLyHUA/2bJbArn38uGGplcMsjPMsdbNIvN8Lg+p19noCt0Wf0brcjwi+wiPWZf/iEttZBoyszVhVh9R8aKBj6mzh89TiZLlIDUqtkz8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1742193253; c=relaxed/simple; bh=scHLMfjQeuz4T/HjBN4ZhUPoxcuhT+g/H5n+Ru0zf5A=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=X8980QOeUCZhdG1hE2gkaprvazWyn28Ea8lV2hzk1GWbOIEZZSVCFNQkCoDwBvwuXW5zrCV19RtYPL25w7ACAL+74QB0S7x42+Nk7GMCQ5a27Msvf6gue6+jvApXNgxlH9n6Y53Vz2Bv3RPnrCEVET380D3PHgXBOcjWZQstrCQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=D5uI3F7M; arc=none smtp.client-ip=209.85.216.46 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="D5uI3F7M" Received: by mail-pj1-f46.google.com with SMTP id 98e67ed59e1d1-2ff80290debso2547858a91.3; Sun, 16 Mar 2025 23:34:11 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1742193251; x=1742798051; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=x6WrkgLRJl8lUocm1cnTSBp25XPoazkPi4ONuRt/VwE=; b=D5uI3F7MgpxNOEYfZafVQsRsa/Q8C7HX7ZCuiFaAa3XABHYz1XNbf7Ca6M+sKNv2XJ gOmX893w7rReVO7e5fYDlRG94NHYUYoGY+w/HO89WPZ6rMTcWuoDOB5io1kzKGfuAr86 ZwpbuH2ZvN6coKiDaq+lMn4UDea3jqX1Y0MYVUp6z0nsgUMaUWXpm+T9oWdLyajUYt0U GcTTYJiV5I3qlOPxM8rvzObtA9VSGr2sl24QzfQ1rQr81xDLzXvDL6KBAPXnKj81cwdI qEheJAnlYOq/GBdK33sQHyMsshpMn0ZxjXXEINxyzjmErVsL+1YLAh8jsQBjBskWo8K9 a4gQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1742193251; x=1742798051; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=x6WrkgLRJl8lUocm1cnTSBp25XPoazkPi4ONuRt/VwE=; b=aupyFr7QkskbyYavUxyyEhok4AJ8V8ihb05JYe75Gw2wq//iBNLSKYffyWP0wqc4mx 68ly0TAYIFbGI6Nmr2T/2UolnEC/w5+RQMGnmqGZVzMi2obWj1o03b036WT98DdwbLBC da0VRL6sF02TezMVvyGY+pNwRPfWs+yC4Fcbikz8nES2kHhBj5kPYOiQnV8sUM/C/Lhl yta0BIf4MTHRrPhM9LppEoe2ZpYZkT3US+M0qd/GFK551sr9B08JisCElITbmrelruOq Lh7tPb/cH36qn4gVADisFQutI3+Fr1wfRBF+4S8vrf33F5JBmAwlLWjXZ8BEcwjN59fs g15A== X-Forwarded-Encrypted: i=1; AJvYcCUNK2mZag32o4SOJJViWTquo3nv3PIopglATXrLM1sks/drFqNSJ4C7bCI0OLgXWhaK/NIJYtSGZefPP4GG9LIfJTA5qw==@vger.kernel.org, AJvYcCWEkG3R3zGIM0ibZ6VRxXSfn+gtF/Z06oOQl3Yryg8/DB4k5xPUs8PlMWRcUo+xUtIKcTY0BOSAn5cSTpRc@vger.kernel.org, AJvYcCXDbpepUYONV6VnAVdmD7q9haVLjrbKiOUdwXxJe8AfB9iZIFDXZ4KwfxVMS1hk/MkKGZWrW9RH2M8=@vger.kernel.org X-Gm-Message-State: AOJu0Yxxr+qe8Mncoxwv7v9v1S0xFgYBklonUmJLxegnmKQEi35o4jXT whydUbZHqc8DYeAerYJk2AN3sXhdRt0H6YDSsfI4lDOFQhD/5FbG X-Gm-Gg: ASbGncvsjU5dnjB1zi9kfEpZxTLuhiHKJQebfJJEWoK7Nli1wAo6jSpEPzWI7laxMsm GdUTZoIq2gOuluR4Akr0T/1CxOBhBj/hwKUp2scVPGS/kOZ+/YGLIS6xYKR62vY9gT7lwovBaz1 wEkvDX2cXq12eE0hUa4mx21ooh5e6w6sbTE6mzaYGWsJpvLCKGBkYFgtxwwCu9LzZ+cDcM5V9Vs v95lF7ANF9VCHLFKilc71eVikAPgWKS8tpE3ie4myfpuWQxAFa/LK1cgK7zRO9y+EnQuIudN7QV lD+CPWqXPm9PHGCZ6c7qJhyGL0faOwBajtP9Rt9xmp+h2ZeTwHb7rp2OTRa0iMXSyhxNON/ZBPw YwPfp4wdpdpYrleevLxTdncwm8UqQbB9+FWxaeyaF1+vWYpnuJLKf3A== X-Google-Smtp-Source: AGHT+IHa0PdmCoCJFmnjghf2nI9AZenNbxKmso3ckTa8/zZLi5DK3y18Bb/XnQtaPn4P1vrmCr/4fw== X-Received: by 2002:a17:90b:520e:b0:2ff:6a5f:9b39 with SMTP id 98e67ed59e1d1-30151ca8599mr15359231a91.18.1742193250664; Sun, 16 Mar 2025 23:34:10 -0700 (PDT) Received: from localhost.localdomain (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-30153afee51sm5186555a91.28.2025.03.16.23.34.09 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 16 Mar 2025 23:34:10 -0700 (PDT) From: "Derek J. Clark" To: Hans de Goede , =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Cc: Armin Wolf , Jonathan Corbet , Mario Limonciello , Luke Jones , Xino Ni , Zhixin Zhang , Mia Shao , Mark Pearson , "Pierre-Loup A . Griffais" , "Cody T . -H . Chiu" , John Martens , "Derek J . Clark" , platform-driver-x86@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v4 6/6] platform/x86: Add Lenovo Gamezone WMI Driver Date: Sun, 16 Mar 2025 23:33:37 -0700 Message-ID: <20250317063337.352966-11-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20250317063337.352966-1-derekjohn.clark@gmail.com> References: <20250317063337.352966-1-derekjohn.clark@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds lenovo-wmi-gamezone driver which provides the Lenovo Gamezone WMI interface that comes on Lenovo "Gaming Series" hardware. Provides ACPI platform profiles over WMI. Signed-off-by: Derek J. Clark Suggested-by: Mario Limonciello --- v4: - Add notifier blocks for the Events and Other Mode drivers. - Remove notifier block chain head and all reference to Thermal Mode Event GUID. - Add header for Gamezone specific structs and functions. - Various fixes from review. v3: - Use notifier chain to report platform profile changes to any subscribed drivers. - Adds THERMAL_MODE_EVENT GUID and .notify function to trigger notifier chain. - Adds support for Extreme Mode profile on supported hardware, as well as a DMI quirk table for some devices that report extreme mode version support but so not have it fully implemented. - Update to include recent changes to platform-profile. v2: - Use devm_kmalloc to ensure driver can be instanced, remove global reference. - Ensure reverse Christmas tree for all variable declarations. - Remove extra whitespace. - Use guard(mutex) in all mutex instances, global mutex. - Use pr_fmt instead of adding the driver name to each pr_err. - Remove noisy pr_info usage. - Rename gamezone_wmi to lenovo_wmi_gz_priv and gz_wmi to priv. - Remove GZ_WMI symbol exporting. --- MAINTAINERS | 2 + drivers/platform/x86/Kconfig | 13 + drivers/platform/x86/Makefile | 1 + drivers/platform/x86/lenovo-wmi-gamezone.c | 380 +++++++++++++++++++++ drivers/platform/x86/lenovo-wmi-gamezone.h | 18 + 5 files changed, 414 insertions(+) create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.h diff --git a/MAINTAINERS b/MAINTAINERS index 87daee6075ad..0416afd997a0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13168,6 +13168,8 @@ F: drivers/platform/x86/lenovo-wmi-capdata01.c F: drivers/platform/x86/lenovo-wmi-capdata01.h F: drivers/platform/x86/lenovo-wmi-events.c F: drivers/platform/x86/lenovo-wmi-events.h +F: drivers/platform/x86/lenovo-wmi-gamezone.c +F: drivers/platform/x86/lenovo-wmi-gamezone.h F: drivers/platform/x86/lenovo-wmi-helpers.c F: drivers/platform/x86/lenovo-wmi-helpers.h F: drivers/platform/x86/lenovo-wmi-other.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index fc47604e37f7..ecf3246c8fda 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -467,6 +467,19 @@ config LENOVO_WMI_HELPERS tristate depends on ACPI_WMI =20 +config LENOVO_WMI_GAMEZONE + tristate "Lenovo GameZone WMI Driver" + depends on ACPI_WMI + select ACPI_PLATFORM_PROFILE + select LENOVO_WMI_EVENTS + select LENOVO_WMI_HELPERS + help + Say Y here if you have a WMI aware Lenovo Legion device and would like = to use the + platform-profile firmware interface to manage power usage. + + To compile this driver as a module, choose M here: the module will + be called lenovo-wmi-gamezone. + config LENOVO_WMI_DATA01 tristate depends on ACPI_WMI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index c6ce3c8594b1..f3e64926a96b 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -71,6 +71,7 @@ obj-$(CONFIG_YT2_1380) +=3D lenovo-yoga-tab2-pro-1380-fa= stcharger.o obj-$(CONFIG_LENOVO_WMI_CAMERA) +=3D lenovo-wmi-camera.o obj-$(CONFIG_LENOVO_WMI_DATA01) +=3D lenovo-wmi-capdata01.o obj-$(CONFIG_LENOVO_WMI_EVENTS) +=3D lenovo-wmi-events.o +obj-$(CONFIG_LENOVO_WMI_GAMEZONE) +=3D lenovo-wmi-gamezone.o obj-$(CONFIG_LENOVO_WMI_HELPERS) +=3D lenovo-wmi-helpers.o obj-$(CONFIG_LENOVO_WMI_TUNING) +=3D lenovo-wmi-other.o =20 diff --git a/drivers/platform/x86/lenovo-wmi-gamezone.c b/drivers/platform/= x86/lenovo-wmi-gamezone.c new file mode 100644 index 000000000000..9d453a836227 --- /dev/null +++ b/drivers/platform/x86/lenovo-wmi-gamezone.c @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Lenovo GameZone WMI interface driver. The GameZone WMI interface provid= es + * platform profile and fan curve settings for devices that fall under the + * "Gaming Series" of Lenovo Legion devices. + * + * Copyright(C) 2025 Derek J. Clark + */ + +#include +#include +#include +#include +#include +#include + +#include "lenovo-wmi-events.h" +#include "lenovo-wmi-gamezone.h" +#include "lenovo-wmi-helpers.h" +#include "lenovo-wmi-other.h" + +/* Interface GUIDs */ +#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0" + +/* Method IDs */ +#define WMI_METHOD_ID_SMARTFAN_SUPP 43 /* IsSupportSmartFan */ +#define WMI_METHOD_ID_SMARTFAN_SET 44 /* SetSmartFanMode */ +#define WMI_METHOD_ID_SMARTFAN_GET 45 /* GetSmartFanMode */ + +static BLOCKING_NOTIFIER_HEAD(gz_chain_head); + +struct lwmi_event_priv { + enum thermal_mode current_mode; + struct wmi_device *wdev; + bool extreme_supported; + struct device *ppdev; /*platform profile device */ + struct notifier_block event_nb; + struct notifier_block mode_nb; +}; + +struct quirk_entry { + bool extreme_supported; +}; + +static struct quirk_entry quirk_no_extreme_bug =3D { + .extreme_supported =3D false, +}; + +/* Notifier Methods */ +/* + * lwmi_gz_mode_call() - Call method for lenovo-wmi-other notifier + * block call chain. For THERMAL_MODE_EVENT, returns current_mode + * + * @nb: The notifier_block registered to lenovo-wmi-other + * @cmd: The event triggered by lenovo-wmi-other + * @data: The data to be returned by the event. + * + * Returns: notifier_block status. + */ +static int lwmi_gz_mode_call(struct notifier_block *nb, unsigned long cmd, + void *data) +{ + struct lwmi_event_priv *priv; + + priv =3D container_of(nb, struct lwmi_event_priv, mode_nb); + if (!priv) + return NOTIFY_BAD; + + switch (cmd) { + case THERMAL_MODE_EVENT: + *(enum thermal_mode *)data =3D priv->current_mode; + break; + default: + return NOTIFY_DONE; + } + + return NOTIFY_OK; +} + +/* + * lwmi_gz_event_call() - Call method for lenovo-wmi-events notifier + * block call chain. For THERMAL_MODE_EVENT, sets current_mode and + * notifies platform_profile of a change. + * + * @nb: The notifier_block registered to lenovo-wmi-events + * @cmd: The event triggered by lenovo-wmi-events + * @data: The data to be updated by the event. + * + * Returns: notifier_block status. + */ +static int lwmi_gz_event_call(struct notifier_block *nb, unsigned long cmd, + void *data) +{ + struct lwmi_event_priv *priv; + + priv =3D container_of(nb, struct lwmi_event_priv, event_nb); + if (!priv) + return NOTIFY_BAD; + + switch (cmd) { + case THERMAL_MODE_EVENT: + priv->current_mode =3D *((enum thermal_mode *)data); + platform_profile_notify(&priv->wdev->dev); + break; + default: + return NOTIFY_DONE; + } + + return NOTIFY_OK; +} + +/* Platform Profile Methods & Setup */ +/* + * lwmi_gz_platform_profile_supported() - Gets the version of the WMI + * interface to determine the support level. + * + * @wdev: The Gamezone WMI device. + * @supported: Pointer to return the support level with. + * + * Returns: 0, or an error. + */ +static int lwmi_gz_platform_profile_supported(struct wmi_device *wdev, + int *supported) +{ + return lwmi_dev_evaluate_method(wdev, 0x0, WMI_METHOD_ID_SMARTFAN_SUPP, + 0, 0, supported); +} + +/* + * lwmi_gz_thermal_mode_get() - Gets the currently set thermal mode from + * the Gamezone WMI interface. + * + * @wdev: The Gamezone WMI device. + * @mode: Pointer to return the thermal mode with. + * + * Returns: 0, or an error. + */ +static int lwmi_gz_thermal_mode_get(struct wmi_device *wdev, + enum thermal_mode *mode) +{ + return lwmi_dev_evaluate_method(wdev, 0x0, WMI_METHOD_ID_SMARTFAN_GET, + 0, 0, mode); +} + +static int lwmi_gz_profile_get(struct device *dev, + enum platform_profile_option *profile) +{ + struct lwmi_event_priv *priv =3D dev_get_drvdata(dev); + enum thermal_mode mode; + int ret; + + ret =3D lwmi_gz_thermal_mode_get(priv->wdev, &mode); + if (ret) + return ret; + + switch (mode) { + case SMARTFAN_MODE_QUIET: + *profile =3D PLATFORM_PROFILE_LOW_POWER; + break; + case SMARTFAN_MODE_BALANCED: + *profile =3D PLATFORM_PROFILE_BALANCED; + break; + case SMARTFAN_MODE_PERFORMANCE: + if (priv->extreme_supported) { + *profile =3D PLATFORM_PROFILE_BALANCED_PERFORMANCE; + break; + } + *profile =3D PLATFORM_PROFILE_PERFORMANCE; + break; + case SMARTFAN_MODE_EXTREME: + *profile =3D PLATFORM_PROFILE_PERFORMANCE; + break; + case SMARTFAN_MODE_CUSTOM: + *profile =3D PLATFORM_PROFILE_CUSTOM; + break; + default: + return -EINVAL; + } + + priv->current_mode =3D mode; + + return 0; +} + +static int lwmi_gz_profile_set(struct device *dev, + enum platform_profile_option profile) +{ + struct lwmi_event_priv *priv =3D dev_get_drvdata(dev); + struct wmi_method_args_32 args; + enum thermal_mode mode; + int ret; + + switch (profile) { + case PLATFORM_PROFILE_LOW_POWER: + mode =3D SMARTFAN_MODE_QUIET; + break; + case PLATFORM_PROFILE_BALANCED: + mode =3D SMARTFAN_MODE_BALANCED; + break; + case PLATFORM_PROFILE_BALANCED_PERFORMANCE: + mode =3D SMARTFAN_MODE_PERFORMANCE; + break; + case PLATFORM_PROFILE_PERFORMANCE: + if (priv->extreme_supported) { + mode =3D SMARTFAN_MODE_EXTREME; + break; + } + mode =3D SMARTFAN_MODE_PERFORMANCE; + break; + case PLATFORM_PROFILE_CUSTOM: + mode =3D SMARTFAN_MODE_CUSTOM; + break; + default: + return -EOPNOTSUPP; + } + + args.arg0 =3D mode; + + ret =3D lwmi_dev_evaluate_method(priv->wdev, 0x0, + WMI_METHOD_ID_SMARTFAN_SET, + (unsigned char *)&args, sizeof(args), + NULL); + if (ret) + return ret; + + priv->current_mode =3D mode; + + return 0; +} + +static const struct dmi_system_id fwbug_list[] =3D { + { + .ident =3D "Legion Go 8APU1", + .matches =3D { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"), + }, + .driver_data =3D &quirk_no_extreme_bug, + }, + { + .ident =3D "Legion Go S 8ARP1", + .matches =3D { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8ARP1"), + }, + .driver_data =3D &quirk_no_extreme_bug, + }, + { + .ident =3D "Legion Go S 8APU1", + .matches =3D { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8APU1"), + }, + .driver_data =3D &quirk_no_extreme_bug, + }, + {}, + +}; + +/* + * extreme_supported() - Evaluate if a device supports extreme thermal mod= e. + * For devices that have a profile_support_ver of 6 or greater a DMI check + * is done. Some devices report a version that supports extreme mode but + * have an incomplete entry in the BIOS. To ensure this cannot be set, they + * are quirked to prevent assignment. + * + * @profile_support_ver: Version of WMI interface provided by + * lwmi_gz_platform_profile_supported. + * + * Returns: bool + */ +static bool extreme_supported(int profile_support_ver) +{ + const struct dmi_system_id *dmi_id; + struct quirk_entry *quirks; + + if (profile_support_ver < 6) + return false; + + dmi_id =3D dmi_first_match(fwbug_list); + if (!dmi_id) + return true; + + quirks =3D dmi_id->driver_data; + return quirks->extreme_supported; +} + +static int lwmi_platform_profile_probe(void *drvdata, unsigned long *choic= es) +{ + struct lwmi_event_priv *priv =3D drvdata; + int profile_support_ver; + int ret; + + ret =3D lwmi_gz_platform_profile_supported(priv->wdev, + &profile_support_ver); + if (ret) + return ret; + + if (profile_support_ver < 1) + return -ENODEV; + + priv->extreme_supported =3D extreme_supported(profile_support_ver); + + set_bit(PLATFORM_PROFILE_LOW_POWER, choices); + set_bit(PLATFORM_PROFILE_BALANCED, choices); + set_bit(PLATFORM_PROFILE_PERFORMANCE, choices); + set_bit(PLATFORM_PROFILE_CUSTOM, choices); + + if (priv->extreme_supported) + set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices); + + return 0; +} + +static const struct platform_profile_ops lwmi_gz_platform_profile_ops =3D { + .probe =3D lwmi_platform_profile_probe, + .profile_get =3D lwmi_gz_profile_get, + .profile_set =3D lwmi_gz_profile_set, +}; + +/* Driver Methods */ +static int lwmi_gz_probe(struct wmi_device *wdev, const void *context) +{ + struct lwmi_event_priv *priv; + int ret; + + priv =3D devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->event_nb.notifier_call =3D lwmi_gz_event_call; + ret =3D devm_lwmi_events_register_notifier(&wdev->dev, &priv->event_nb); + if (ret) + return ret; + + priv->mode_nb.notifier_call =3D lwmi_gz_mode_call; + ret =3D devm_lwmi_om_register_notifier(&wdev->dev, &priv->mode_nb); + if (ret) + return ret; + + priv->wdev =3D wdev; + dev_set_drvdata(&wdev->dev, priv); + + priv->ppdev =3D platform_profile_register(&wdev->dev, + "lenovo-wmi-gamezone", priv, + &lwmi_gz_platform_profile_ops); + + if (IS_ERR(priv->ppdev)) + return -ENODEV; + + ret =3D lwmi_gz_thermal_mode_get(wdev, &priv->current_mode); + if (ret) + return ret; + + return 0; +} + +static const struct wmi_device_id lwmi_gz_id_table[] =3D { { LENOVO_GAMEZO= NE_GUID, + NULL }, + {} }; + +static struct wmi_driver lwmi_gz_driver =3D { + .driver =3D { + .name =3D "lenovo_wmi_gamezone", + .probe_type =3D PROBE_PREFER_ASYNCHRONOUS, + }, + .id_table =3D lwmi_gz_id_table, + .probe =3D lwmi_gz_probe, + .no_singleton =3D true, +}; + +module_wmi_driver(lwmi_gz_driver); + +MODULE_IMPORT_NS("LENOVO_WMI_EVENTS"); +MODULE_IMPORT_NS("LENOVO_WMI_HELPERS"); +MODULE_IMPORT_NS("LENOVO_WMI_OTHER"); +MODULE_DEVICE_TABLE(wmi, lwmi_gz_id_table); +MODULE_AUTHOR("Derek J. Clark "); +MODULE_DESCRIPTION("Lenovo GameZone WMI Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/lenovo-wmi-gamezone.h b/drivers/platform/= x86/lenovo-wmi-gamezone.h new file mode 100644 index 000000000000..ac536803160b --- /dev/null +++ b/drivers/platform/x86/lenovo-wmi-gamezone.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright(C) 2025 Derek J. Clark + * + */ + +#ifndef _LENOVO_WMI_GAMEZONE_H_ +#define _LENOVO_WMI_GAMEZONE_H_ + +enum thermal_mode { + SMARTFAN_MODE_QUIET =3D 0x01, + SMARTFAN_MODE_BALANCED =3D 0x02, + SMARTFAN_MODE_PERFORMANCE =3D 0x03, + SMARTFAN_MODE_EXTREME =3D 0xE0, /* Ver 6+ */ + SMARTFAN_MODE_CUSTOM =3D 0xFF, +}; + +#endif /* !_LENOVO_WMI_GAMEZONE_H_ */ --=20 2.49.0